initial upload

This commit is contained in:
tom.hempel
2025-09-21 22:42:26 +02:00
commit d03bcd4ba5
6231 changed files with 351582 additions and 0 deletions

View File

@ -0,0 +1,283 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Handles smooth aim target switching, weight blending, target interpolation and root rotation.
/// </summary>
public class AimController : MonoBehaviour {
[Tooltip("Reference to the AimIK component.")]
/// <summary>
/// Reference to the AimIK component.
/// </summary>
public AimIK ik;
[Tooltip("Master weight of the IK solver.")]
/// <summary>
/// Master weight of the IK solver.
/// </summary>
[Range(0f, 1f)] public float weight = 1f;
[Header("Target Smoothing")]
[Tooltip("The target to aim at. Do not use the Target transform that is assigned to AimIK. Set to null if you wish to stop aiming.")]
/// <summary>
/// The target to aim at. Do not use the Target transform that is assigned to AimIK. Set to null if you wish to stop aiming.
/// </summary>
public Transform target;
[Tooltip("The time it takes to switch targets.")]
/// <summary>
/// The time it takes to switch targets.
/// </summary>
public float targetSwitchSmoothTime = 0.3f;
[Tooltip("The time it takes to blend in/out of AimIK weight.")]
/// <summary>
/// The time it takes to blend in/out of AimIK weight.
/// </summary>
public float weightSmoothTime = 0.3f;
[Header("Turning Towards The Target")]
[Tooltip("Enables smooth turning towards the target according to the parameters under this header.")]
/// <summary>
/// Enables smooth turning towards the target according to the parameters under this header.
/// </summary>
public bool smoothTurnTowardsTarget = true;
[Tooltip("Speed of turning towards the target using Vector3.RotateTowards.")]
/// <summary>
/// Speed of turning towards the target using Vector3.RotateTowards.
/// </summary>
public float maxRadiansDelta = 3f;
[Tooltip("Speed of moving towards the target using Vector3.RotateTowards.")]
/// <summary>
/// Speed of moving towards the target using Vector3.RotateTowards.
/// </summary>
public float maxMagnitudeDelta = 3f;
[Tooltip("Speed of slerping towards the target.")]
/// <summary>
/// Speed of slerping towards the target.
/// </summary>
public float slerpSpeed = 3f;
[Tooltip("Smoothing time for turning towards the yaw and pitch of the target using Mathf.SmoothDampAngle. Value of 0 means smooth damping is disabled." )]
/// <summary>
/// Smoothing time for turning towards the yaw and pitch of the target using Mathf.SmoothDampAngle. Value of 0 means smooth damping is disabled.
/// </summary>
public float smoothDampTime = 0f;
[Tooltip("The position of the pivot that the aim target is rotated around relative to the root of the character.")]
/// <summary>
/// The position of the pivot that the aim target is rotated around relative to the root of the character.
/// </summary>
public Vector3 pivotOffsetFromRoot = Vector3.up;
[Tooltip("Minimum distance of aiming from the first bone. Keeps the solver from failing if the target is too close.")]
/// <summary>
/// Minimum distance of aiming from the first bone. Keeps the solver from failing if the target is too close.
/// </summary>
public float minDistance = 1f;
[Tooltip("Offset applied to the target in world space. Convenient for scripting aiming inaccuracy.")]
/// <summary>
/// Offset applied to the target in world space. Convenient for scripting aiming inaccuracy.
/// </summary>
public Vector3 offset;
[Header("RootRotation")]
[Tooltip("Character root will be rotate around the Y axis to keep root forward within this angle from the aiming direction.")]
/// <summary>
///Character root will be rotate around the Y axis to keep root forward within this angle from the aiming direction.
/// </summary>
[Range(0f, 180f)] public float maxRootAngle = 45f;
[Tooltip("If enabled, aligns the root forward to target direction after 'Max Root Angle' has been exceeded.")]
/// <summary>
/// If enabled, aligns the root forward to target direction after 'Max Root Angle' has been exceeded.
/// </summary>
public bool turnToTarget;
[Tooltip("The time of turning towards the target direction if 'Max Root Angle has been exceeded and 'Turn To Target' is enabled.")]
/// <summary>
/// The time of turning towards the target direction if 'Max Root Angle has been exceeded and 'Turn To Target' is enabled.
/// </summary>
public float turnToTargetTime = 0.2f;
[Header("Mode")]
[Tooltip("If true, AimIK will consider whatever the current direction of the weapon to be the forward aiming direction and work additively on top of that. This enables you to use recoil and reloading animations seamlessly with AimIK. Adjust the Vector3 value below if the weapon is not aiming perfectly forward in the aiming animation clip.")]
/// <summary>
/// If true, AimIK will consider whatever the current direction of the weapon to be the forward aiming direction and work additively on top of that. This enables you to use recoil and reloading animations seamlessly with AimIK. Adjust the Vector3 value below if the weapon is not aiming perfectly forward in the aiming animation clip.
/// </summary>
public bool useAnimatedAimDirection;
[Tooltip("The direction of the animated weapon aiming in character space. Tweak this value to adjust the aiming. 'Use Animated Aim Direction' must be enabled for this property to work.")]
/// <summary>
/// The direction of the animated weapon aiming in character space. Tweak this value to adjust the aiming. 'Use Animated Aim Direction' must be enabled for this property to work.
/// </summary>
public Vector3 animatedAimDirection = Vector3.forward;
private Transform lastTarget;
private float switchWeight, switchWeightV;
private float weightV;
private Vector3 lastPosition;
private Vector3 dir;
private bool lastSmoothTowardsTarget;
private bool turningToTarget;
private float turnToTargetMlp = 1f;
private float turnToTargetMlpV;
void Start() {
lastPosition = ik.solver.IKPosition;
dir = ik.solver.IKPosition - pivot;
ik.solver.target = null;
}
void LateUpdate () {
// If target has changed...
if (target != lastTarget) {
if (lastTarget == null && target != null && ik.solver.IKPositionWeight <= 0f)
{
lastPosition = target.position;
dir = target.position - pivot;
ik.solver.IKPosition = target.position + offset;
}
else
{
lastPosition = ik.solver.IKPosition;
dir = ik.solver.IKPosition - pivot;
}
switchWeight = 0f;
lastTarget = target;
}
// Smooth weight
float targetWeight = target != null ? weight : 0f;
ik.solver.IKPositionWeight = Mathf.SmoothDamp(ik.solver.IKPositionWeight, targetWeight, ref weightV, weightSmoothTime);
if (ik.solver.IKPositionWeight >= 0.999f && targetWeight > ik.solver.IKPositionWeight) ik.solver.IKPositionWeight = 1f;
if (ik.solver.IKPositionWeight <= 0.001f && targetWeight < ik.solver.IKPositionWeight) ik.solver.IKPositionWeight = 0f;
if (ik.solver.IKPositionWeight <= 0f) return;
// Smooth target switching
switchWeight = Mathf.SmoothDamp(switchWeight, 1f, ref switchWeightV, targetSwitchSmoothTime);
if (switchWeight >= 0.999f) switchWeight = 1f;
if (target != null) {
ik.solver.IKPosition = Vector3.Lerp(lastPosition, target.position + offset, switchWeight);
}
// Smooth turn towards target
if (smoothTurnTowardsTarget != lastSmoothTowardsTarget) {
dir = ik.solver.IKPosition - pivot;
lastSmoothTowardsTarget = smoothTurnTowardsTarget;
}
if (smoothTurnTowardsTarget) {
Vector3 targetDir = ik.solver.IKPosition - pivot;
// Slerp
if (slerpSpeed > 0f) dir = Vector3.Slerp(dir, targetDir, Time.deltaTime * slerpSpeed);
// RotateTowards
if (maxRadiansDelta > 0 || maxMagnitudeDelta > 0f) dir = Vector3.RotateTowards(dir, targetDir, Time.deltaTime * maxRadiansDelta, maxMagnitudeDelta);
// SmoothDamp
if (smoothDampTime > 0f)
{
float yaw = V3Tools.GetYaw(dir);
float targetYaw = V3Tools.GetYaw(targetDir);
float y = Mathf.SmoothDampAngle(yaw, targetYaw, ref yawV, smoothDampTime);
float pitch = V3Tools.GetPitch(dir);
float targetPitch = V3Tools.GetPitch(targetDir);
float p = Mathf.SmoothDampAngle(pitch, targetPitch, ref pitchV, smoothDampTime);
float dirMag = Mathf.SmoothDamp(dir.magnitude, targetDir.magnitude, ref dirMagV, smoothDampTime);
dir = Quaternion.Euler(p, y, 0f) * Vector3.forward * dirMag;
}
ik.solver.IKPosition = pivot + dir;
}
// Min distance from the pivot
ApplyMinDistance();
// Root rotation
RootRotation();
// Offset mode
if (useAnimatedAimDirection) {
ik.solver.axis = ik.solver.transform.InverseTransformVector(ik.transform.rotation * animatedAimDirection);
}
}
private float yawV, pitchV, dirMagV;
// Pivot of rotating the aiming direction.
private Vector3 pivot {
get {
return ik.transform.position + ik.transform.rotation * pivotOffsetFromRoot;
}
}
// Make sure aiming target is not too close (might make the solver instable when the target is closer to the first bone than the last bone is).
void ApplyMinDistance() {
Vector3 aimFrom = pivot;
Vector3 direction = (ik.solver.IKPosition - aimFrom);
direction = direction.normalized * Mathf.Max(direction.magnitude, minDistance);
ik.solver.IKPosition = aimFrom + direction;
}
// Character root will be rotate around the Y axis to keep root forward within this angle from the aiming direction.
private void RootRotation() {
float max = Mathf.Lerp(180f, maxRootAngle * turnToTargetMlp, ik.solver.IKPositionWeight);
if (max < 180f) {
Vector3 faceDirLocal = Quaternion.Inverse(ik.transform.rotation) * (ik.solver.IKPosition - pivot);
float angle = Mathf.Atan2(faceDirLocal.x, faceDirLocal.z) * Mathf.Rad2Deg;
float rotation = 0f;
if (angle > max) {
rotation = angle - max;
if (!turningToTarget && turnToTarget) StartCoroutine(TurnToTarget());
}
if (angle < -max) {
rotation = angle + max;
if (!turningToTarget && turnToTarget) StartCoroutine(TurnToTarget());
}
ik.transform.rotation = Quaternion.AngleAxis(rotation, ik.transform.up) * ik.transform.rotation;
}
}
// Aligns the root forward to target direction after "Max Root Angle" has been exceeded.
private IEnumerator TurnToTarget()
{
turningToTarget = true;
while (turnToTargetMlp > 0f)
{
turnToTargetMlp = Mathf.SmoothDamp(turnToTargetMlp, 0f, ref turnToTargetMlpV, turnToTargetTime);
if (turnToTargetMlp < 0.01f) turnToTargetMlp = 0f;
yield return null;
}
turnToTargetMlp = 1f;
turningToTarget = false;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c9df7d42e7cb1a54a8eb68e9e9f0dc68
timeCreated: 1512472681
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,78 @@
using UnityEngine;
using System.Collections;
using RootMotion;
namespace RootMotion.FinalIK {
/// <summary>
/// Aim Poser returns a reference by direction.
/// </summary>
public class AimPoser : MonoBehaviour {
/// <summary>
/// the pose definition
/// </summary>
[System.Serializable]
public class Pose {
public bool visualize = true; // Show the direction and range of this pose in the scene view
public string name; // the reference
public Vector3 direction; // the direction of the pose
public float yaw = 75f; // the yaw range
public float pitch = 45f; // the pitch range
private float angleBuffer;
// Determines whether this Pose is in the specified direction.
public bool IsInDirection(Vector3 d) {
if (direction == Vector3.zero) return false;
if (yaw <= 0 || pitch <= 0) return false;
// Yaw
if (yaw < 180f) {
Vector3 directionYaw = new Vector3(direction.x, 0f, direction.z);
if (directionYaw == Vector3.zero) directionYaw = Vector3.forward;
Vector3 dYaw = new Vector3(d.x, 0f, d.z);
float yawAngle = Vector3.Angle(dYaw, directionYaw);
if (yawAngle > yaw + angleBuffer) return false;
}
// Pitch
if (pitch >= 180f) return true;
float directionPitch = Vector3.Angle(Vector3.up, direction);
float dPitch = Vector3.Angle(Vector3.up, d);
return Mathf.Abs(dPitch - directionPitch) < pitch + angleBuffer;
}
// Sets the angle buffer to prevent immediatelly switching back to the last pose if the angle should change a bit.
public void SetAngleBuffer(float value) {
angleBuffer = value;
}
}
public float angleBuffer = 5f; // The angle buffer
public Pose[] poses = new Pose[0]; // The array of poses.
/// <summary>
/// Gets the pose by direction. GetPose will go through the poses array and return the first pose that has the direction in range.
/// </summary>
public Pose GetPose(Vector3 localDirection) {
if (poses.Length == 0) return null;
for (int i = 0; i < poses.Length - 1; i++) if (poses[i].IsInDirection(localDirection)) return poses[i];
return poses[poses.Length - 1];
}
/// <summary>
/// Sets the pose active, increasing its angle buffer.
/// </summary>
public void SetPoseActive(Pose pose) {
for (int i = 0; i < poses.Length; i++) {
poses[i].SetAngleBuffer(poses[i] == pose? angleBuffer: 0f);
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 4267160fb35f54254be87414e585c1a7
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,101 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Demo script that amplifies the motion of a body part relative to the root of the character or another body part.
/// </summary>
public class Amplifier: OffsetModifier {
/// <summary>
/// Body is amplifying the motion of "transform" relative to the "relativeTo".
/// </summary>
[System.Serializable]
public class Body {
/// <summary>
/// Linking this to an effector
/// </summary>
[System.Serializable]
public class EffectorLink {
[Tooltip("Type of the FBBIK effector to use")]
public FullBodyBipedEffector effector;
[Tooltip("Weight of using this effector")]
public float weight;
}
[Tooltip("The Transform that's motion we are reading.")]
public Transform transform;
[Tooltip("Amplify the 'transform's' position relative to this Transform.")]
public Transform relativeTo;
[Tooltip("Linking the body to effectors. One Body can be used to offset more than one effector.")]
public EffectorLink[] effectorLinks;
[Tooltip("Amplification magnitude along the up axis of the character.")]
public float verticalWeight = 1f;
[Tooltip("Amplification magnitude along the horizontal axes of the character.")]
public float horizontalWeight = 1f;
[Tooltip("Speed of the amplifier. 0 means instant.")]
public float speed = 3f;
private Vector3 lastRelativePos;
private Vector3 smoothDelta;
private bool firstUpdate;
// Update the Body
public void Update(IKSolverFullBodyBiped solver, float w, float deltaTime) {
if (transform == null || relativeTo == null) return;
// Find the relative position of the transform
Vector3 relativePos = relativeTo.InverseTransformDirection(transform.position - relativeTo.position);
// Initiating
if (firstUpdate) {
lastRelativePos = relativePos;
firstUpdate = false;
}
// Find how much the relative position has changed
Vector3 delta = (relativePos - lastRelativePos) / deltaTime;
// Smooth the change
smoothDelta = speed <= 0f? delta: Vector3.Lerp(smoothDelta, delta, deltaTime * speed);
// Convert to world space
Vector3 worldDelta = relativeTo.TransformDirection(smoothDelta);
// Extract horizontal and vertical offset
Vector3 offset = V3Tools.ExtractVertical(worldDelta, solver.GetRoot().up, verticalWeight) + V3Tools.ExtractHorizontal(worldDelta, solver.GetRoot().up, horizontalWeight);
// Apply the amplitude to the effector links
for (int i = 0; i < effectorLinks.Length; i++) {
solver.GetEffector(effectorLinks[i].effector).positionOffset += offset * w * effectorLinks[i].weight;
}
lastRelativePos = relativePos;
}
// Multiply 2 vectors
private static Vector3 Multiply(Vector3 v1, Vector3 v2) {
v1.x *= v2.x;
v1.y *= v2.y;
v1.z *= v2.z;
return v1;
}
}
[Tooltip("The amplified bodies.")]
public Body[] bodies;
// Called by IKSolverFullBody before updating
protected override void OnModifyOffset() {
if (!ik.fixTransforms) {
if (!Warning.logged) Warning.Log("Amplifier needs the Fix Transforms option of the FBBIK to be set to true. Otherwise it might amplify to infinity, should the animator of the character stop because of culling.", transform);
return;
}
// Update the Bodies
foreach (Body body in bodies) body.Update(ik.solver, weight, deltaTime);
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: e45ea437e115a4fb6be25b8464e6de7a
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,52 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Procedural body tilting with FBBIK.
/// </summary>
public class BodyTilt: OffsetModifier {
[Tooltip("Speed of tilting")]
public float tiltSpeed = 6f;
[Tooltip("Sensitivity of tilting")]
public float tiltSensitivity = 0.07f;
[Tooltip("The OffsetPose components")]
public OffsetPose poseLeft, poseRight;
private float tiltAngle;
private Vector3 lastForward;
protected override void Start() {
base.Start();
// Store current character forward axis and Time
lastForward = transform.forward;
}
// Called by IKSolverFullBody before updating
protected override void OnModifyOffset() {
// Calculate the angular delta in character rotation
Quaternion change = Quaternion.FromToRotation(lastForward, transform.forward);
float deltaAngle = 0;
Vector3 axis = Vector3.zero;
change.ToAngleAxis(out deltaAngle, out axis);
if (axis.y > 0) deltaAngle = -deltaAngle;
deltaAngle *= tiltSensitivity * 0.01f;
deltaAngle /= deltaTime;
deltaAngle = Mathf.Clamp(deltaAngle, -1f, 1f);
tiltAngle = Mathf.Lerp(tiltAngle, deltaAngle, deltaTime * tiltSpeed);
// Applying positionOffsets
float tiltF = Mathf.Abs(tiltAngle) / 1f;
if (tiltAngle < 0) poseRight.Apply(ik.solver, tiltF);
else poseLeft.Apply(ik.solver, tiltF);
// Store current character forward axis and Time
lastForward = transform.forward;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 8e1cd8d7247544f61bad689b61e39770
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,44 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RootMotion.FinalIK
{
/// <summary>
/// Bend goal object for CCDIK. Add this to a GameObject you wish CCD to bend towards.
/// </summary>
public class CCDBendGoal : MonoBehaviour
{
public CCDIK ik;
[Range(0f, 1f)] public float weight = 1f;
private void Start()
{
ik.solver.OnPreUpdate += BeforeIK;
}
private void BeforeIK()
{
if (!enabled) return;
float w = ik.solver.IKPositionWeight * weight;
if (w <= 0f) return;
Vector3 firstBonePos = ik.solver.bones[0].transform.position;
Vector3 lastBonePos = ik.solver.bones[ik.solver.bones.Length - 1].transform.position;
// Rotating the CCD chain towards this gameobject before it solves so it rolls in from that direction
Quaternion f = Quaternion.FromToRotation(lastBonePos - firstBonePos, transform.position - firstBonePos);
if (w < 1f) f = Quaternion.Slerp(Quaternion.identity, f, w);
ik.solver.bones[0].transform.rotation = f * ik.solver.bones[0].transform.rotation;
}
private void OnDestroy()
{
if (ik != null) ik.solver.OnPreUpdate -= BeforeIK;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 285f2e66b1fe97b4797ef0cb776a5682
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,59 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RootMotion.FinalIK
{
/// <summary>
/// Automatic stretch and squash for CCDIK.
/// </summary>
public class CCDStretch : MonoBehaviour
{
public CCDIK ik;
[Range(0f, 0.999f)] public float maxSquash = 0f;
public float maxStretch = 2f;
private Vector3[] defaultLocalPositions = new Vector3[0];
private void Start()
{
// Store default localPositions
defaultLocalPositions = new Vector3[ik.solver.bones.Length - 1];
for (int i = 1; i < ik.solver.bones.Length; i++)
{
defaultLocalPositions[i - 1] = ik.solver.bones[i].transform.localPosition;
}
}
private void LateUpdate()
{
// Reset to default localPositions
for (int i = 1; i < ik.solver.bones.Length; i++)
{
ik.solver.bones[i].transform.localPosition = defaultLocalPositions[i - 1];
}
// Get distance from first bone to target
float targetDist = Vector3.Magnitude((ik.solver.target != null ? ik.solver.target.position : ik.solver.IKPosition) - ik.solver.bones[0].transform.position);
// Get bone chain length
float chainLength = 0f;
for (int i = 1; i < ik.solver.bones.Length; i++)
{
chainLength += Vector3.Magnitude(ik.solver.bones[i].transform.position - ik.solver.bones[i - 1].transform.position);
}
// Compare target distance to chain length...
maxStretch = Mathf.Max(maxStretch, 1f);
float mlp = Mathf.Clamp(targetDist / chainLength, 1f - maxSquash, maxStretch);
// Stretch
for (int i = 1; i < ik.solver.bones.Length; i++)
{
ik.solver.bones[i].transform.localPosition *= mlp;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4b2826fa4f637534ebedd76eb2cf6565
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,104 @@
using UnityEngine;
using System.Collections;
using UnityEngine.Playables;
namespace RootMotion.FinalIK
{
/// <summary>
/// Updates any Final IK component in Editor mode
/// </summary>
[ExecuteInEditMode]
public class EditorIK : MonoBehaviour
{
[Tooltip("If slot assigned, will evaluate PlayableDirector before solving the IK.")] public PlayableDirector playableDirector;
[Tooltip("If slot assigned, will update Animator before solving the IK.")] public Animator animator;
[Tooltip("Create/Final IK/Editor IK Pose")] public EditorIKPose defaultPose;
[HideInInspector] public Transform[] bones = new Transform[0];
public IK ik { get; private set; }
private void OnEnable()
{
if (Application.isPlaying) return;
if (ik == null) ik = GetComponent<IK>();
if (ik == null)
{
Debug.LogError("EditorIK needs to have an IK component on the same GameObject.", transform);
return;
}
if (bones.Length == 0) bones = ik.transform.GetComponentsInChildren<Transform>();
}
private void OnDisable()
{
if (Application.isPlaying) return;
if (defaultPose != null && defaultPose.poseStored) defaultPose.Restore(bones);
if (ik != null) ik.GetIKSolver().executedInEditor = false;
}
private void OnDestroy()
{
if (Application.isPlaying) return;
if (ik == null) return;
if (bones.Length == 0) bones = ik.transform.GetComponentsInChildren<Transform>();
if (defaultPose != null && defaultPose.poseStored && bones.Length != 0) defaultPose.Restore(bones);
ik.GetIKSolver().executedInEditor = false;
}
public void StoreDefaultPose()
{
bones = ik.transform.GetComponentsInChildren<Transform>();
defaultPose.Store(bones);
}
public bool Initiate()
{
if (defaultPose == null) return false;
if (!defaultPose.poseStored) return false;
if (bones.Length == 0) return false;
if (ik == null) ik = GetComponent<IK>();
if (ik == null)
{
Debug.LogError("EditorIK can not find an IK component.", transform);
return false;
}
defaultPose.Restore(bones);
ik.GetIKSolver().executedInEditor = false;
ik.GetIKSolver().Initiate(ik.transform);
ik.GetIKSolver().executedInEditor = true;
return true;
}
public void Update()
{
if (Application.isPlaying) return;
if (ik == null) return;
if (!ik.enabled) return;
if (!ik.GetIKSolver().executedInEditor) return;
if (bones.Length == 0) bones = ik.transform.GetComponentsInChildren<Transform>();
if (bones.Length == 0) return;
if (!defaultPose.Restore(bones)) return;
ik.GetIKSolver().executedInEditor = false;
if (!ik.GetIKSolver().initiated) ik.GetIKSolver().Initiate(ik.transform);
if (!ik.GetIKSolver().initiated) return;
ik.GetIKSolver().executedInEditor = true;
if (playableDirector != null)
{
playableDirector.Evaluate();
}
else
{
if (animator != null && animator.runtimeAnimatorController != null) animator.Update(Time.deltaTime);
}
ik.GetIKSolver().Update();
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c52e6f5c6ccaca0479ac0f3748e75992
timeCreated: 1520429340
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,51 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RootMotion.FinalIK
{
[CreateAssetMenu(fileName = "Editor IK Pose", menuName = "Final IK/Editor IK Pose", order = 1)]
public class EditorIKPose : ScriptableObject
{
public Vector3[] localPositions = new Vector3[0];
public Quaternion[] localRotations = new Quaternion[0];
public bool poseStored
{
get
{
return localPositions.Length > 0;
}
}
public void Store(Transform[] T)
{
localPositions = new Vector3[T.Length];
localRotations = new Quaternion[T.Length];
for (int i = 1; i < T.Length; i++)
{
localPositions[i] = T[i].localPosition;
localRotations[i] = T[i].localRotation;
}
}
public bool Restore(Transform[] T)
{
if (localPositions.Length != T.Length)
{
Debug.LogError("Can not restore pose (unmatched bone count). Please stop the solver and click on 'Store Default Pose' if you have made changes to character hierarchy.");
return false;
}
for (int i = 1; i < T.Length; i++)
{
T[i].localPosition = localPositions[i];
T[i].localRotation = localRotations[i];
}
return true;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c8598cb8df1b85a4e8e2bd3c92a6ba62
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,41 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RootMotion.FinalIK;
namespace RootMotion.FinalIK
{
/// <summary>
/// Bend goal object for FABRIK. Add this to a GameObject you wish FABRIK to bend towards.
/// </summary>
public class FABRIKBendGoal : MonoBehaviour
{
public FABRIK ik;
[Range(0f, 1f)] public float weight = 1f;
private void Start()
{
ik.solver.OnPreIteration += OnPreIteration;
}
void OnPreIteration(int it)
{
if (it != 0) return;
if (weight <= 0f) return;
Vector3 bendDirection = transform.position - ik.solver.bones[0].transform.position;
bendDirection *= weight;
foreach (IKSolverFABRIK.Bone bone in ik.solver.bones)
{
bone.solverPosition += bendDirection;
}
}
private void OnDestroy()
{
if (ik != null) ik.solver.OnPreIteration -= OnPreIteration;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e00635d593e21414082963c80e4a4327
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,59 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace RootMotion.FinalIK
{
/// <summary>
/// Automatic stretch and squash for FABRIK.
/// </summary>
public class FABRIKStretch : MonoBehaviour
{
public FABRIK ik;
[Range(0f, 0.999f)] public float maxSquash = 0f;
public float maxStretch = 2f;
private Vector3[] defaultLocalPositions = new Vector3[0];
private void Start()
{
// Store default localPositions
defaultLocalPositions = new Vector3[ik.solver.bones.Length - 1];
for (int i = 1; i < ik.solver.bones.Length; i++)
{
defaultLocalPositions[i - 1] = ik.solver.bones[i].transform.localPosition;
}
}
private void LateUpdate()
{
// Reset to default localPositions
for (int i = 1; i < ik.solver.bones.Length; i++)
{
ik.solver.bones[i].transform.localPosition = defaultLocalPositions[i - 1];
}
// Get distance from first bone to target
float targetDist = Vector3.Magnitude((ik.solver.target != null ? ik.solver.target.position : ik.solver.IKPosition) - ik.solver.bones[0].transform.position);
// Get bone chain length
float chainLength = 0f;
for (int i = 1; i < ik.solver.bones.Length; i++)
{
chainLength += Vector3.Magnitude(ik.solver.bones[i].transform.position - ik.solver.bones[i - 1].transform.position);
}
// Compare target distance to chain length...
maxStretch = Mathf.Max(maxStretch, 1f);
float mlp = Mathf.Clamp(targetDist / chainLength, 1f - maxSquash, maxStretch);
// Stretch
for (int i = 1; i < ik.solver.bones.Length; i++)
{
ik.solver.bones[i].transform.localPosition *= mlp;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 977397d3381a5a2438c10b308b9d43ad
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,253 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Class for creating procedural FBBIK hit reactions.
/// </summary>
public class HitReaction : OffsetModifier {
/// <summary>
/// Hit point definition
/// </summary>
[System.Serializable]
public abstract class HitPoint {
[Tooltip("Just for visual clarity, not used at all")]
public string name;
[Tooltip("Linking this hit point to a collider")]
public Collider collider;
[Tooltip("Only used if this hit point gets hit when already processing another hit")]
[SerializeField] float crossFadeTime = 0.1f;
public bool inProgress { get { return timer < length; } }
protected float crossFader { get; private set; }
protected float timer { get; private set; }
protected Vector3 force { get; private set; }
private float length;
private float crossFadeSpeed;
private float lastTime;
// Start processing the hit
public virtual void Hit(Vector3 force, Vector3 point) {
if (length == 0f) length = GetLength();
if (length <= 0f) {
Debug.LogError("Hit Point WeightCurve length is zero.");
return;
}
// Start crossfading if the last hit has not completed yet
if (timer < 1f) crossFader = 0f;
crossFadeSpeed = crossFadeTime > 0f? 1f / crossFadeTime: 0f;
CrossFadeStart();
// Reset timer
timer = 0f;
// Remember hit direction and point
this.force = force;
}
// Apply to IKSolverFullBodyBiped
public void Apply(IKSolverFullBodyBiped solver, float weight) {
float deltaTime = Time.time - lastTime;
lastTime = Time.time;
if (timer >= length) {
return;
}
// Advance the timer
timer = Mathf.Clamp(timer + deltaTime, 0f, length);
// Advance the crossFader
if (crossFadeSpeed > 0f) crossFader = Mathf.Clamp(crossFader + (deltaTime * crossFadeSpeed), 0f, 1f);
else crossFader = 1f;
// Pass this on to the hit points
OnApply(solver, weight);
}
protected abstract float GetLength();
protected abstract void CrossFadeStart();
protected abstract void OnApply(IKSolverFullBodyBiped solver, float weight);
}
/// <summary>
/// Hit Point for FBBIK effectors
/// </summary>
[System.Serializable]
public class HitPointEffector: HitPoint {
/// <summary>
/// Linking a FBBIK effector to this effector hit point
/// </summary>
[System.Serializable]
public class EffectorLink {
[Tooltip("The FBBIK effector type")]
public FullBodyBipedEffector effector;
[Tooltip("The weight of this effector (could also be negative)")]
public float weight;
private Vector3 lastValue;
private Vector3 current;
// Apply an offset to this effector
public void Apply(IKSolverFullBodyBiped solver, Vector3 offset, float crossFader) {
current = Vector3.Lerp(lastValue, offset * weight, crossFader);
solver.GetEffector(effector).positionOffset += current;
}
// Remember the current offset value, so we can smoothly crossfade from it
public void CrossFadeStart() {
lastValue = current;
}
}
[Tooltip("Offset magnitude in the direction of the hit force")]
public AnimationCurve offsetInForceDirection; //
[Tooltip("Offset magnitude in the direction of character.up")]
public AnimationCurve offsetInUpDirection; //
[Tooltip("Linking this offset to the FBBIK effectors")]
public EffectorLink[] effectorLinks;
// Returns the length of this hit (last key in the AnimationCurves)
protected override float GetLength() {
float time1 = offsetInForceDirection.keys.Length > 0? offsetInForceDirection.keys[offsetInForceDirection.length - 1].time: 0f;
float time2 = offsetInUpDirection.keys.Length > 0? offsetInUpDirection.keys[offsetInUpDirection.length - 1].time: 0f;
return Mathf.Clamp(time1, time2, time1);
}
// Remember the current offset values for each effector, so we can smoothly crossfade from it
protected override void CrossFadeStart() {
foreach (EffectorLink e in effectorLinks) e.CrossFadeStart();
}
// Calculate offset, apply to FBBIK effectors
protected override void OnApply(IKSolverFullBodyBiped solver, float weight) {
Vector3 up = solver.GetRoot().up * force.magnitude;
Vector3 offset = (offsetInForceDirection.Evaluate(timer) * force) + (offsetInUpDirection.Evaluate(timer) * up);
offset *= weight;
foreach (EffectorLink e in effectorLinks) e.Apply(solver, offset, crossFader);
}
}
/// <summary>
/// Hit Point for simple bone Transforms that don't have a FBBIK effector
/// </summary>
[System.Serializable]
public class HitPointBone: HitPoint {
/// <summary>
/// Linking a bone Transform to this bone hit point
/// </summary>
[System.Serializable]
public class BoneLink {
[Tooltip("Reference to the bone that this hit point rotates")]
public Transform bone;
[Tooltip("Weight of rotating the bone")]
[Range(0f, 1f)] public float weight;
private Quaternion lastValue = Quaternion.identity;
private Quaternion current = Quaternion.identity;
// Apply a rotational offset to this effector
public void Apply(IKSolverFullBodyBiped solver, Quaternion offset, float crossFader) {
current = Quaternion.Lerp(lastValue, Quaternion.Lerp(Quaternion.identity, offset, weight), crossFader);
bone.rotation = current * bone.rotation;
}
// Remember the current offset value, so we can smoothly crossfade from it
public void CrossFadeStart() {
lastValue = current;
}
}
[Tooltip("The angle to rotate the bone around its rigidbody's world center of mass")]
public AnimationCurve aroundCenterOfMass;
[Tooltip("Linking this hit point to bone(s)")]
public BoneLink[] boneLinks;
private Rigidbody rigidbody;
private Vector3 comAxis;
public override void Hit(Vector3 force, Vector3 point)
{
base.Hit(force, point);
if (rigidbody == null) rigidbody = collider.GetComponent<Rigidbody>();
Vector3 com = rigidbody != null ? rigidbody.worldCenterOfMass : collider.transform.position;
comAxis = Vector3.Cross(force, point - com);
}
// Returns the length of this hit (last key in the AnimationCurves)
protected override float GetLength() {
return aroundCenterOfMass.keys.Length > 0? aroundCenterOfMass.keys[aroundCenterOfMass.length - 1].time: 0f;
}
// Remember the current offset values for each bone, so we can smoothly crossfade from it
protected override void CrossFadeStart() {
foreach (BoneLink b in boneLinks) b.CrossFadeStart();
}
// Calculate offset, apply to the bones
protected override void OnApply(IKSolverFullBodyBiped solver, float weight) {
float comValue = aroundCenterOfMass.Evaluate(timer) * weight;
Quaternion offset = Quaternion.AngleAxis(comValue, comAxis);
foreach (BoneLink b in boneLinks) b.Apply(solver, offset, crossFader);
}
}
[Tooltip("Hit points for the FBBIK effectors")]
public HitPointEffector[] effectorHitPoints;
[Tooltip(" Hit points for bones without an effector, such as the head")]
public HitPointBone[] boneHitPoints;
/// <summary>
/// Returns true if any of the hits are being processed.
/// </summary>
public bool inProgress {
get {
foreach (HitPointEffector h in effectorHitPoints) {
if (h.inProgress) return true;
}
foreach (HitPointBone h in boneHitPoints) {
if (h.inProgress) return true;
}
return false;
}
}
// Called by IKSolverFullBody before updating
protected override void OnModifyOffset() {
foreach (HitPointEffector e in effectorHitPoints) e.Apply(ik.solver, weight);
foreach (HitPointBone b in boneHitPoints) b.Apply(ik.solver, weight);
}
// Hit one of the hit points (defined by hit.collider)
public void Hit(Collider collider, Vector3 force, Vector3 point) {
if (ik == null) {
Debug.LogError("No IK assigned in HitReaction");
return;
}
foreach (HitPointEffector e in effectorHitPoints) {
if (e.collider == collider) e.Hit(force, point);
}
foreach (HitPointBone b in boneHitPoints) {
if (b.collider == collider) b.Hit(force, point);
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 6acf54e73e0704cd79c7669317e0cd14
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,247 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Class for creating procedural FBBIK hit reactions.
/// </summary>
public class HitReactionVRIK : OffsetModifierVRIK {
public AnimationCurve[] offsetCurves;
/// <summary>
/// Hit point definition
/// </summary>
[System.Serializable]
public abstract class Offset {
[Tooltip("Just for visual clarity, not used at all")]
public string name;
[Tooltip("Linking this hit point to a collider")]
public Collider collider;
[Tooltip("Only used if this hit point gets hit when already processing another hit")]
[SerializeField] float crossFadeTime = 0.1f;
protected float crossFader { get; private set; }
protected float timer { get; private set; }
protected Vector3 force { get; private set; }
private float length;
private float crossFadeSpeed;
private float lastTime;
// Start processing the hit
public virtual void Hit(Vector3 force, AnimationCurve[] curves, Vector3 point) {
if (length == 0f) length = GetLength(curves);
if (length <= 0f) {
Debug.LogError("Hit Point WeightCurve length is zero.");
return;
}
// Start crossfading if the last hit has not completed yet
if (timer < 1f) crossFader = 0f;
crossFadeSpeed = crossFadeTime > 0f? 1f / crossFadeTime: 0f;
CrossFadeStart();
// Reset timer
timer = 0f;
// Remember hit direction and point
this.force = force;
}
// Apply to IKSolverFullBodyBiped
public void Apply(VRIK ik, AnimationCurve[] curves, float weight) {
float deltaTime = Time.time - lastTime;
lastTime = Time.time;
if (timer >= length) {
return;
}
// Advance the timer
timer = Mathf.Clamp(timer + deltaTime, 0f, length);
// Advance the crossFader
if (crossFadeSpeed > 0f) crossFader = Mathf.Clamp(crossFader + (deltaTime * crossFadeSpeed), 0f, 1f);
else crossFader = 1f;
// Pass this on to the hit points
OnApply(ik, curves, weight);
}
protected abstract float GetLength(AnimationCurve[] curves);
protected abstract void CrossFadeStart();
protected abstract void OnApply(VRIK ik, AnimationCurve[] curves, float weight);
}
/// <summary>
/// Hit Point for FBBIK effectors
/// </summary>
[System.Serializable]
public class PositionOffset: Offset {
/// <summary>
/// Linking a FBBIK effector to this effector hit point
/// </summary>
[System.Serializable]
public class PositionOffsetLink {
[Tooltip("The FBBIK effector type")]
public IKSolverVR.PositionOffset positionOffset;
[Tooltip("The weight of this effector (could also be negative)")]
public float weight;
private Vector3 lastValue;
private Vector3 current;
// Apply an offset to this effector
public void Apply(VRIK ik, Vector3 offset, float crossFader) {
current = Vector3.Lerp(lastValue, offset * weight, crossFader);
ik.solver.AddPositionOffset(positionOffset, current);
}
// Remember the current offset value, so we can smoothly crossfade from it
public void CrossFadeStart() {
lastValue = current;
}
}
[Tooltip("Offset magnitude in the direction of the hit force")]
public int forceDirCurveIndex;
[Tooltip("Offset magnitude in the direction of character.up")]
public int upDirCurveIndex = 1;
[Tooltip("Linking this offset to the VRIK position offsets")]
public PositionOffsetLink[] offsetLinks;
// Returns the length of this hit (last key in the AnimationCurves)
protected override float GetLength(AnimationCurve[] curves) {
float time1 = curves[forceDirCurveIndex].keys.Length > 0? curves[forceDirCurveIndex].keys[curves[forceDirCurveIndex].length - 1].time: 0f;
float time2 = curves[upDirCurveIndex].keys.Length > 0? curves[upDirCurveIndex].keys[curves[upDirCurveIndex].length - 1].time: 0f;
return Mathf.Clamp(time1, time2, time1);
}
// Remember the current offset values for each effector, so we can smoothly crossfade from it
protected override void CrossFadeStart() {
foreach (PositionOffsetLink l in offsetLinks) l.CrossFadeStart();
}
// Calculate offset, apply to FBBIK effectors
protected override void OnApply(VRIK ik, AnimationCurve[] curves, float weight) {
Vector3 up = ik.transform.up * force.magnitude;
Vector3 offset = (curves[forceDirCurveIndex].Evaluate(timer) * force) + (curves[upDirCurveIndex].Evaluate(timer) * up);
offset *= weight;
foreach (PositionOffsetLink l in offsetLinks) l.Apply(ik, offset, crossFader);
}
}
/// <summary>
/// Hit Point for simple bone Transforms that don't have a FBBIK effector
/// </summary>
[System.Serializable]
public class RotationOffset: Offset {
/// <summary>
/// Linking a bone Transform to this bone hit point
/// </summary>
[System.Serializable]
public class RotationOffsetLink {
[Tooltip("Reference to the bone that this hit point rotates")]
public IKSolverVR.RotationOffset rotationOffset;
[Tooltip("Weight of rotating the bone")]
[Range(0f, 1f)] public float weight;
private Quaternion lastValue = Quaternion.identity;
private Quaternion current = Quaternion.identity;
// Apply a rotational offset to this effector
public void Apply(VRIK ik, Quaternion offset, float crossFader) {
current = Quaternion.Lerp(lastValue, Quaternion.Lerp(Quaternion.identity, offset, weight), crossFader);
ik.solver.AddRotationOffset(rotationOffset, current);
}
// Remember the current offset value, so we can smoothly crossfade from it
public void CrossFadeStart() {
lastValue = current;
}
}
[Tooltip("The angle to rotate the bone around its rigidbody's world center of mass")]
public int curveIndex;
[Tooltip("Linking this hit point to bone(s)")]
public RotationOffsetLink[] offsetLinks;
private Rigidbody rigidbody;
private Vector3 comAxis;
public override void Hit(Vector3 force, AnimationCurve[] curves, Vector3 point)
{
base.Hit(force, curves, point);
if (rigidbody == null) rigidbody = collider.GetComponent<Rigidbody>();
Vector3 com = rigidbody != null ? rigidbody.worldCenterOfMass : collider.transform.position;
comAxis = Vector3.Cross(force, point - com);
}
// Returns the length of this hit (last key in the AnimationCurves)
protected override float GetLength(AnimationCurve[] curves) {
return curves[curveIndex].keys.Length > 0? curves[curveIndex].keys[ curves[curveIndex].length - 1].time: 0f;
}
// Remember the current offset values for each bone, so we can smoothly crossfade from it
protected override void CrossFadeStart() {
foreach (RotationOffsetLink l in offsetLinks) l.CrossFadeStart();
}
// Calculate offset, apply to the bones
protected override void OnApply(VRIK ik, AnimationCurve[] curves, float weight) {
if (collider == null) {
Debug.LogError ("No collider assigned for a HitPointBone in the HitReaction component.");
return;
}
if (rigidbody == null) rigidbody = collider.GetComponent<Rigidbody>();
if (rigidbody != null) {
float comValue = curves[curveIndex].Evaluate(timer) * weight;
Quaternion offset = Quaternion.AngleAxis(comValue, comAxis);
foreach (RotationOffsetLink l in offsetLinks) l.Apply(ik, offset, crossFader);
}
}
}
[Tooltip("Hit points for the FBBIK effectors")]
public PositionOffset[] positionOffsets;
[Tooltip(" Hit points for bones without an effector, such as the head")]
public RotationOffset[] rotationOffsets;
// Called by IKSolverFullBody before updating
protected override void OnModifyOffset() {
foreach (PositionOffset p in positionOffsets) p.Apply(ik, offsetCurves, weight);
foreach (RotationOffset r in rotationOffsets) r.Apply(ik, offsetCurves, weight);
}
// Hit one of the hit points (defined by hit.collider)
public void Hit(Collider collider, Vector3 force, Vector3 point) {
if (ik == null) {
Debug.LogError("No IK assigned in HitReaction");
return;
}
foreach (PositionOffset p in positionOffsets) {
if (p.collider == collider) p.Hit(force, offsetCurves, point);
}
foreach (RotationOffset r in rotationOffsets) {
if (r.collider == collider) r.Hit(force, offsetCurves, point);
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 01147feb37a92684a9c362eb43225c67
timeCreated: 1460978425
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,108 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Demo script that adds the illusion of mass to your character using FullBodyBipedIK.
/// </summary>
public class Inertia : OffsetModifier {
/// <summary>
/// Body is just following its transform in a lazy and bouncy way.
/// </summary>
[System.Serializable]
public class Body {
/// <summary>
/// Linking this to an effector
/// </summary>
[System.Serializable]
public class EffectorLink {
[Tooltip("Type of the FBBIK effector to use")]
public FullBodyBipedEffector effector;
[Tooltip("Weight of using this effector")]
public float weight;
}
[Tooltip("The Transform to follow, can be any bone of the character")]
public Transform transform;
[Tooltip("Linking the body to effectors. One Body can be used to offset more than one effector")]
public EffectorLink[] effectorLinks;
[Tooltip("The speed to follow the Transform")]
public float speed = 10f;
[Tooltip("The acceleration, smaller values means lazyer following")]
public float acceleration = 3f;
[Tooltip("Matching target velocity")]
[Range(0f, 1f)] public float matchVelocity;
[Tooltip("gravity applied to the Body")]
public float gravity;
private Vector3 delta;
private Vector3 lazyPoint;
private Vector3 direction;
private Vector3 lastPosition;
private bool firstUpdate = true;
// Reset to Transform
public void Reset() {
if (transform == null) return;
lazyPoint = transform.position;
lastPosition = transform.position;
direction = Vector3.zero;
}
// Update this body, apply the offset to the effector
public void Update(IKSolverFullBodyBiped solver, float weight, float deltaTime) {
if (transform == null) return;
// If first update, set this body to Transform
if (firstUpdate) {
Reset();
firstUpdate = false;
}
// Acceleration
direction = Vector3.Lerp(direction, ((transform.position - lazyPoint) / deltaTime) * 0.01f, deltaTime * acceleration);
// Lazy follow
lazyPoint += direction * deltaTime * speed;
// Match velocity
delta = transform.position - lastPosition;
lazyPoint += delta * matchVelocity;
// Gravity
lazyPoint.y += gravity * deltaTime;
// Apply position offset to the effector
foreach (EffectorLink effectorLink in effectorLinks) {
solver.GetEffector(effectorLink.effector).positionOffset += (lazyPoint - transform.position) * effectorLink.weight * weight;
}
lastPosition = transform.position;
}
}
[Tooltip("The array of Bodies")]
public Body[] bodies;
[Tooltip("The array of OffsetLimits")]
public OffsetLimits[] limits;
// Reset all Bodies
public void ResetBodies() {
lastTime = Time.time;
foreach (Body body in bodies) body.Reset();
}
// Called by IKSolverFullBody before updating
protected override void OnModifyOffset() {
// Update the Bodies
foreach (Body body in bodies) body.Update(ik.solver, weight, deltaTime);
// Apply the offset limits
ApplyLimits(limits);
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 22a01f59b6ac94f1b95f51f60eff7eeb
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,151 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
public class LookAtController : MonoBehaviour {
public LookAtIK ik;
[Header("Target Smoothing")]
[Tooltip("The target to look at. Do not use the Target transform that is assigned to LookAtIK. Set to null if you wish to stop looking.")]
public Transform target;
[Range(0f, 1f)] public float weight = 1f;
public Vector3 offset;
[Tooltip("The time it takes to switch targets.")]
public float targetSwitchSmoothTime = 0.3f;
[Tooltip("The time it takes to blend in/out of LookAtIK weight.")]
public float weightSmoothTime = 0.3f;
[Header("Turning Towards The Target")]
[Tooltip("Enables smooth turning towards the target according to the parameters under this header.")]
public bool smoothTurnTowardsTarget = true;
[Tooltip("Speed of turning towards the target using Vector3.RotateTowards.")]
public float maxRadiansDelta = 3f;
[Tooltip("Speed of moving towards the target using Vector3.RotateTowards.")]
public float maxMagnitudeDelta = 3f;
[Tooltip("Speed of slerping towards the target.")]
public float slerpSpeed = 3f;
[Tooltip("The position of the pivot that the look at target is rotated around relative to the root of the character.")]
public Vector3 pivotOffsetFromRoot = Vector3.up;
[Tooltip("Minimum distance of looking from the first bone. Keeps the solver from failing if the target is too close.")]
public float minDistance = 1f;
[Header("RootRotation")]
[Tooltip("Character root will be rotate around the Y axis to keep root forward within this angle from the look direction.")]
[Range(0f, 180f)]
public float maxRootAngle = 45f;
private Transform lastTarget;
private float switchWeight, switchWeightV;
private float weightV;
private Vector3 lastPosition;
private Vector3 dir;
private bool lastSmoothTowardsTarget;
void Start() {
lastPosition = ik.solver.IKPosition;
dir = ik.solver.IKPosition - pivot;
}
void LateUpdate () {
// If target has changed...
if (target != lastTarget) {
if (lastTarget == null && target != null && ik.solver.IKPositionWeight <= 0f) {
lastPosition = target.position;
dir = target.position - pivot;
ik.solver.IKPosition = target.position + offset;
} else {
lastPosition = ik.solver.IKPosition;
dir = ik.solver.IKPosition - pivot;
}
switchWeight = 0f;
lastTarget = target;
}
// Smooth weight
float targetWeight = target != null ? weight : 0f;
ik.solver.IKPositionWeight = Mathf.SmoothDamp(ik.solver.IKPositionWeight, targetWeight, ref weightV, weightSmoothTime);
if (ik.solver.IKPositionWeight >= 0.999f && targetWeight > ik.solver.IKPositionWeight) ik.solver.IKPositionWeight = 1f;
if (ik.solver.IKPositionWeight <= 0.001f && targetWeight < ik.solver.IKPositionWeight) ik.solver.IKPositionWeight = 0f;
if (ik.solver.IKPositionWeight <= 0f) return;
// Smooth target switching
switchWeight = Mathf.SmoothDamp(switchWeight, 1f, ref switchWeightV, targetSwitchSmoothTime);
if (switchWeight >= 0.999f) switchWeight = 1f;
if (target != null) {
ik.solver.IKPosition = Vector3.Lerp(lastPosition, target.position + offset, switchWeight);
}
// Smooth turn towards target
if (smoothTurnTowardsTarget != lastSmoothTowardsTarget) {
dir = ik.solver.IKPosition - pivot;
lastSmoothTowardsTarget = smoothTurnTowardsTarget;
}
if (smoothTurnTowardsTarget) {
Vector3 targetDir = ik.solver.IKPosition - pivot;
dir = Vector3.Slerp(dir, targetDir, Time.deltaTime * slerpSpeed);
dir = Vector3.RotateTowards(dir, targetDir, Time.deltaTime * maxRadiansDelta, maxMagnitudeDelta);
ik.solver.IKPosition = pivot + dir;
}
// Min distance from the pivot
ApplyMinDistance();
// Root rotation
RootRotation();
}
// Pivot of rotating the aiming direction.
private Vector3 pivot {
get {
return ik.transform.position + ik.transform.rotation * pivotOffsetFromRoot;
}
}
// Make sure aiming target is not too close (might make the solver instable when the target is closer to the first bone than the last bone is).
void ApplyMinDistance() {
Vector3 aimFrom = pivot;
Vector3 direction = (ik.solver.IKPosition - aimFrom);
direction = direction.normalized * Mathf.Max(direction.magnitude, minDistance);
ik.solver.IKPosition = aimFrom + direction;
}
// Character root will be rotate around the Y axis to keep root forward within this angle from the looking direction.
private void RootRotation() {
float max = Mathf.Lerp(180f, maxRootAngle, ik.solver.IKPositionWeight);
if (max < 180f) {
Vector3 faceDirLocal = Quaternion.Inverse(ik.transform.rotation) * (ik.solver.IKPosition - pivot);
float angle = Mathf.Atan2(faceDirLocal.x, faceDirLocal.z) * Mathf.Rad2Deg;
float rotation = 0f;
if (angle > max) {
rotation = angle - max;
}
if (angle < -max) {
rotation = angle + max;
}
ik.transform.rotation = Quaternion.AngleAxis(rotation, ik.transform.up) * ik.transform.rotation;
}
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8362d7c66e966dd498d59b31392d5148
timeCreated: 1517046310
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,113 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Base class for all FBBIK effector positionOffset modifiers. Works with animatePhysics, safe delegates, offset limits.
/// </summary>
public abstract class OffsetModifier: MonoBehaviour {
/// <summary>
/// Limiting effector position offsets
/// </summary>
[System.Serializable]
public class OffsetLimits {
[Tooltip("The effector type (this is just an enum)")]
public FullBodyBipedEffector effector;
[Tooltip("Spring force, if zero then this is a hard limit, if not, offset can exceed the limit.")]
public float spring = 0f;
[Tooltip("Which axes to limit the offset on?")]
public bool x, y, z;
[Tooltip("The limits")]
public float minX, maxX, minY, maxY, minZ, maxZ;
// Apply the limit to the effector
public void Apply(IKEffector e, Quaternion rootRotation) {
Vector3 offset = Quaternion.Inverse(rootRotation) * e.positionOffset;
if (spring <= 0f) {
// Hard limits
if (x) offset.x = Mathf.Clamp(offset.x, minX, maxX);
if (y) offset.y = Mathf.Clamp(offset.y, minY, maxY);
if (z) offset.z = Mathf.Clamp(offset.z, minZ, maxZ);
} else {
// Soft limits
if (x) offset.x = SpringAxis(offset.x, minX, maxX);
if (y) offset.y = SpringAxis(offset.y, minY, maxY);
if (z) offset.z = SpringAxis(offset.z, minZ, maxZ);
}
// Apply to the effector
e.positionOffset = rootRotation * offset;
}
// Just math for limiting floats
private float SpringAxis(float value, float min, float max) {
if (value > min && value < max) return value;
if (value < min) return Spring(value, min, true);
return Spring(value, max, false);
}
// Spring math
private float Spring(float value, float limit, bool negative) {
float illegal = value - limit;
float s = illegal * spring;
if (negative) return value + Mathf.Clamp(-s, 0, -illegal);
return value - Mathf.Clamp(s, 0, illegal);
}
}
[Tooltip("The master weight")]
public float weight = 1f;
[Tooltip("Reference to the FBBIK component")]
public FullBodyBipedIK ik;
// not using Time.deltaTime or Time.fixedDeltaTime here, because we don't know if animatePhysics is true or not on the character, so we have to keep track of time ourselves.
protected float deltaTime { get { return Time.time - lastTime; }}
protected abstract void OnModifyOffset();
protected float lastTime;
protected virtual void Start() {
StartCoroutine(Initiate());
}
private IEnumerator Initiate() {
while (ik == null) yield return null;
// You can use just LateUpdate, but note that it doesn't work when you have animatePhysics turned on for the character.
ik.solver.OnPreUpdate += ModifyOffset;
lastTime = Time.time;
}
// The main function that checks for all conditions and calls OnModifyOffset if they are met
private void ModifyOffset() {
if (!enabled) return;
if (weight <= 0f) return;
if (ik == null) return;
weight = Mathf.Clamp(weight, 0f, 1f);
if (deltaTime <= 0f) return;
OnModifyOffset();
lastTime = Time.time;
}
protected void ApplyLimits(OffsetLimits[] limits) {
// Apply the OffsetLimits
foreach (OffsetLimits limit in limits) {
limit.Apply(ik.solver.GetEffector(limit.effector), transform.rotation);
}
}
// Remove the delegate when destroyed
protected virtual void OnDestroy() {
if (ik != null) ik.solver.OnPreUpdate -= ModifyOffset;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 7953027cc122548b0861e027c35af21c
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,54 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Base class for all FBBIK effector positionOffset modifiers. Works with animatePhysics, safe delegates, offset limits.
/// </summary>
public abstract class OffsetModifierVRIK: MonoBehaviour {
[Tooltip("The master weight")]
public float weight = 1f;
[Tooltip("Reference to the VRIK component")]
public VRIK ik;
// not using Time.deltaTime or Time.fixedDeltaTime here, because we don't know if animatePhysics is true or not on the character, so we have to keep track of time ourselves.
protected float deltaTime { get { return Time.time - lastTime; }}
protected abstract void OnModifyOffset();
private float lastTime;
protected virtual void Start() {
StartCoroutine(Initiate());
}
private IEnumerator Initiate() {
while (ik == null) yield return null;
// You can use just LateUpdate, but note that it doesn't work when you have animatePhysics turned on for the character.
ik.solver.OnPreUpdate += ModifyOffset;
lastTime = Time.time;
}
// The main function that checks for all conditions and calls OnModifyOffset if they are met
private void ModifyOffset() {
if (!enabled) return;
if (weight <= 0f) return;
if (deltaTime <= 0f) return;
if (ik == null) return;
weight = Mathf.Clamp(weight, 0f, 1f);
OnModifyOffset();
lastTime = Time.time;
}
// Remove the delegate when destroyed
protected virtual void OnDestroy() {
if (ik != null) ik.solver.OnPreUpdate -= ModifyOffset;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 8e5a797878a3ba546bb923162b51a8e6
timeCreated: 1460978461
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,58 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Definition of FBBIK Offset pose.
/// </summary>
public class OffsetPose: MonoBehaviour {
/// <summary>
/// State of an effector in this pose
/// </summary>
[System.Serializable]
public class EffectorLink {
[Tooltip("The effector type (this is just an enum)")] public FullBodyBipedEffector effector;
[Tooltip("Offset of the effector in this pose")] public Vector3 offset;
[Tooltip("Pin position relative to the solver root Transform")] public Vector3 pin;
[Tooltip("Pin weight vector")] public Vector3 pinWeight;
[Tooltip("Only applies for end effectors (hands, feet)")] public Vector3 rotationOffset;
// Apply positionOffset to the effector
public void Apply(IKSolverFullBodyBiped solver, float weight, Quaternion rotation) {
var e = solver.GetEffector(effector);
// Offset
e.positionOffset += rotation * offset * weight;
// Calculating pinned position
Vector3 pinPosition = solver.GetRoot().position + rotation * pin;
Vector3 pinPositionOffset = pinPosition - e.bone.position;
Vector3 pinWeightVector = pinWeight * Mathf.Abs(weight);
// Lerping to pinned position
e.positionOffset = new Vector3(
Mathf.Lerp(e.positionOffset.x, pinPositionOffset.x, pinWeightVector.x),
Mathf.Lerp(e.positionOffset.y, pinPositionOffset.y, pinWeightVector.y),
Mathf.Lerp(e.positionOffset.z, pinPositionOffset.z, pinWeightVector.z)
);
if (e.isEndEffector) e.bone.localRotation *= Quaternion.Euler(rotationOffset * weight);
}
}
public EffectorLink[] effectorLinks = new EffectorLink[0];
// Apply positionOffsets of all the EffectorLinks
public void Apply(IKSolverFullBodyBiped solver, float weight) {
for (int i = 0; i < effectorLinks.Length; i++) effectorLinks[i].Apply(solver, weight, solver.GetRoot().rotation);
}
public void Apply(IKSolverFullBodyBiped solver, float weight, Quaternion rotation) {
for (int i = 0; i < effectorLinks.Length; i++) effectorLinks[i].Apply(solver, weight, rotation);
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 5405d3fc16a5b4a0bada53e2da651d3a
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,89 @@
using UnityEngine;
using System.Collections;
using RootMotion.FinalIK;
namespace RootMotion.FinalIK {
/// <summary>
/// Prevents body parts from penetrating scene geometry by offsetting effectors away from the colliders.
/// </summary>
public class PenetrationAvoidance : OffsetModifier {
/// <summary>
/// Definition of avoidance and raycasting info.
/// </summary>
[System.Serializable]
public class Avoider {
/// <summary>
/// Linking this to an effector
/// </summary>
[System.Serializable]
public class EffectorLink {
[Tooltip("Effector to apply the offset to.")] public FullBodyBipedEffector effector;
[Tooltip("Multiplier of the offset value, can be negative.")] public float weight;
}
[Tooltip("Bones to start the raycast from. Multiple raycasts can be used by assigning more than 1 bone.")] public Transform[] raycastFrom;
[Tooltip("The Transform to raycast towards. Usually the body part that you want to keep from penetrating.")] public Transform raycastTo;
[Tooltip("If 0, will use simple raycasting, if > 0, will use sphere casting (better, but slower).")] [Range(0f, 1f)] public float raycastRadius;
[Tooltip("Linking this to FBBIK effectors.")] public EffectorLink[] effectors;
[Tooltip("The time of smooth interpolation of the offset value to avoid penetration.")] public float smoothTimeIn = 0.1f;
[Tooltip("The time of smooth interpolation of the offset value blending out of penetration avoidance.")] public float smoothTimeOut = 0.3f;
[Tooltip("Layers to keep penetrating from.")] public LayerMask layers;
private Vector3 offset;
private Vector3 offsetTarget;
private Vector3 offsetV;
public void Solve(IKSolverFullBodyBiped solver, float weight) {
// Get the offset to interpolate to
offsetTarget = GetOffsetTarget(solver);
// Interpolating the offset value
float smoothDampTime = offsetTarget.sqrMagnitude > offset.sqrMagnitude? smoothTimeIn: smoothTimeOut;
offset = Vector3.SmoothDamp(offset, offsetTarget, ref offsetV, smoothDampTime);
// Apply offset to the FBBIK effectors
foreach (EffectorLink link in effectors) {
solver.GetEffector(link.effector).positionOffset += offset * weight * link.weight;
}
}
// Multiple raycasting to accumulate the offset
private Vector3 GetOffsetTarget(IKSolverFullBodyBiped solver) {
Vector3 t = Vector3.zero;
foreach (Transform from in raycastFrom) {
t += Raycast(from.position, raycastTo.position + t);
}
return t;
}
// Raycast, return the offset that would not penetrate any colliders
private Vector3 Raycast(Vector3 from, Vector3 to) {
Vector3 direction = to - from;
float distance = direction.magnitude;
RaycastHit hit;
if (raycastRadius <= 0f) {
Physics.Raycast(from, direction, out hit, distance, layers);
} else {
Physics.SphereCast(from, raycastRadius, direction, out hit, distance, layers);
}
if (hit.collider == null) return Vector3.zero;
return Vector3.Project(-direction.normalized * (distance - hit.distance), hit.normal);
}
}
[Tooltip("Definitions of penetration avoidances.")] public Avoider[] avoiders;
// Called by IKSolverFullBody before updating
protected override void OnModifyOffset() {
foreach (Avoider avoider in avoiders) avoider.Solve(ik.solver, weight);
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 0095dcee0a4564ca993da4df9dfb070e
timeCreated: 1436511903
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,287 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK
{
/// <summary>
/// Procedural recoil using FBBIK.
/// </summary>
public class Recoil : OffsetModifier
{
[System.Serializable]
public class RecoilOffset
{
[Tooltip("Offset vector for the associated effector when doing recoil.")]
public Vector3 offset;
[Tooltip("When firing before the last recoil has faded, how much of the current recoil offset will be maintained?")]
[Range(0f, 1f)] public float additivity = 1f;
[Tooltip("Max additive recoil for automatic fire.")]
public float maxAdditiveOffsetMag = 0.2f;
// Linking this to an effector
[System.Serializable]
public class EffectorLink
{
[Tooltip("Type of the FBBIK effector to use")]
public FullBodyBipedEffector effector;
[Tooltip("Weight of using this effector")]
public float weight;
}
[Tooltip("Linking this recoil offset to FBBIK effectors.")]
public EffectorLink[] effectorLinks;
private Vector3 additiveOffset;
private Vector3 lastOffset;
// Start recoil
public void Start()
{
if (additivity <= 0f) return;
additiveOffset = Vector3.ClampMagnitude(lastOffset * additivity, maxAdditiveOffsetMag);
}
// Apply offset to FBBIK effectors
public void Apply(IKSolverFullBodyBiped solver, Quaternion rotation, float masterWeight, float length, float timeLeft)
{
additiveOffset = Vector3.Lerp(Vector3.zero, additiveOffset, timeLeft / length);
lastOffset = (rotation * (offset * masterWeight)) + (rotation * additiveOffset);
foreach (EffectorLink e in effectorLinks)
{
solver.GetEffector(e.effector).positionOffset += lastOffset * e.weight;
}
}
}
[System.Serializable]
public enum Handedness
{
Right,
Left
}
[Tooltip("Reference to the AimIK component. Optional, only used to getting the aiming direction.")]
public AimIK aimIK;
[Tooltip("Optional head AimIK solver. This solver should only use neck and head bones and have the head as Aim Transform")]
public AimIK headIK;
[Tooltip("Set this true if you are using IKExecutionOrder.cs or a custom script to force AimIK solve after FBBIK.")]
public bool aimIKSolvedLast;
[Tooltip("Which hand is holding the weapon?")]
public Handedness handedness;
[Tooltip("Check for 2-handed weapons.")]
public bool twoHanded = true;
[Tooltip("Weight curve for the recoil offsets. Recoil procedure is as long as this curve.")]
public AnimationCurve recoilWeight;
[Tooltip("How much is the magnitude randomized each time Recoil is called?")]
public float magnitudeRandom = 0.1f;
[Tooltip("How much is the rotation randomized each time Recoil is called?")]
public Vector3 rotationRandom;
[Tooltip("Rotating the primary hand bone for the recoil (in local space).")]
public Vector3 handRotationOffset;
[Tooltip("Time of blending in another recoil when doing automatic fire.")]
public float blendTime;
[Space(10)]
[Tooltip("FBBIK effector position offsets for the recoil (in aiming direction space).")]
public RecoilOffset[] offsets;
[HideInInspector] public Quaternion rotationOffset = Quaternion.identity;
private float magnitudeMlp = 1f;
private float endTime = -1f;
private Quaternion handRotation, secondaryHandRelativeRotation, randomRotation;
private float length = 1f;
private bool initiated;
private float blendWeight;
private float w;
private Quaternion primaryHandRotation = Quaternion.identity;
//private Quaternion secondaryHandRotation = Quaternion.identity;
private bool handRotationsSet;
private Vector3 aimIKAxis;
/// <summary>
/// Returns true if recoil has finished or has not been called at all.
/// </summary>
public bool isFinished
{
get
{
return Time.time > endTime;
}
}
/// <summary>
/// Sets the starting rotations for the hands for 1 frame. Use this if the final rotation of the hands will not be the same as before FBBIK solves.
/// </summary>
public void SetHandRotations(Quaternion leftHandRotation, Quaternion rightHandRotation)
{
if (handedness == Handedness.Left)
{
primaryHandRotation = leftHandRotation;
//secondaryHandRotation = rightHandRotation;
}
else
{
primaryHandRotation = rightHandRotation;
//secondaryHandRotation = leftHandRotation;
}
handRotationsSet = true;
}
/// <summary>
/// Starts the recoil procedure.
/// </summary>
public void Fire(float magnitude)
{
float rnd = magnitude * UnityEngine.Random.value * magnitudeRandom;
magnitudeMlp = magnitude + rnd;
randomRotation = Quaternion.Euler(rotationRandom * UnityEngine.Random.value);
foreach (RecoilOffset offset in offsets)
{
offset.Start();
}
if (Time.time < endTime) blendWeight = 0f;
else blendWeight = 1f;
Keyframe[] keys = recoilWeight.keys;
length = keys[keys.Length - 1].time;
endTime = Time.time + length;
}
protected override void OnModifyOffset()
{
if (aimIK != null) aimIKAxis = aimIK.solver.axis;
if (!initiated && ik != null)
{
initiated = true;
if (headIK != null) headIK.enabled = false;
ik.solver.OnPostUpdate += AfterFBBIK;
if (aimIK != null) aimIK.solver.OnPostUpdate += AfterAimIK;
}
if (Time.time >= endTime)
{
rotationOffset = Quaternion.identity;
return;
}
blendTime = Mathf.Max(blendTime, 0f);
if (blendTime > 0f) blendWeight = Mathf.Min(blendWeight + Time.deltaTime * (1f / blendTime), 1f);
else blendWeight = 1f;
// Current weight of offset
float wTarget = recoilWeight.Evaluate(length - (endTime - Time.time)) * magnitudeMlp;
w = Mathf.Lerp(w, wTarget, blendWeight);
// Find the rotation space of the recoil
Quaternion lookRotation = aimIK != null && aimIK.solver.transform != null && !aimIKSolvedLast ? Quaternion.LookRotation(aimIK.solver.IKPosition - aimIK.solver.transform.position, ik.references.root.up) : ik.references.root.rotation;
lookRotation = randomRotation * lookRotation;
// Apply FBBIK effector positionOffsets
foreach (RecoilOffset offset in offsets)
{
offset.Apply(ik.solver, lookRotation, w, length, endTime - Time.time);
}
if (!handRotationsSet)
{
primaryHandRotation = primaryHand.rotation;
//if (twoHanded) secondaryHandRotation = secondaryHand.rotation;
}
handRotationsSet = false;
// Rotation offset of the primary hand
rotationOffset = Quaternion.Lerp(Quaternion.identity, Quaternion.Euler(randomRotation * primaryHandRotation * handRotationOffset), w);
handRotation = rotationOffset * primaryHandRotation;
// Fix the secondary hand relative to the primary hand
if (twoHanded)
{
Vector3 secondaryHandRelativePosition = Quaternion.Inverse(primaryHand.rotation) * (secondaryHand.position - primaryHand.position);
secondaryHandRelativeRotation = Quaternion.Inverse(primaryHand.rotation) * secondaryHand.rotation;
Vector3 primaryHandPosition = primaryHand.position + primaryHandEffector.positionOffset;
Vector3 secondaryHandPosition = primaryHandPosition + handRotation * secondaryHandRelativePosition;
secondaryHandEffector.positionOffset += secondaryHandPosition - (secondaryHand.position + secondaryHandEffector.positionOffset);
}
if (aimIK != null && aimIKSolvedLast) aimIK.solver.axis = Quaternion.Inverse(ik.references.root.rotation) * Quaternion.Inverse(rotationOffset) * aimIKAxis;
}
private void AfterFBBIK()
{
if (Time.time < endTime)
{
// Rotate the hand bones
primaryHand.rotation = handRotation;
if (twoHanded) secondaryHand.rotation = primaryHand.rotation * secondaryHandRelativeRotation;
}
if (!aimIKSolvedLast && headIK != null) headIK.solver.Update();
}
private void AfterAimIK()
{
if (aimIKSolvedLast) aimIK.solver.axis = aimIKAxis;
if (aimIKSolvedLast && headIK != null) headIK.solver.Update();
}
// Shortcuts
private IKEffector primaryHandEffector
{
get
{
if (handedness == Handedness.Right) return ik.solver.rightHandEffector;
return ik.solver.leftHandEffector;
}
}
private IKEffector secondaryHandEffector
{
get
{
if (handedness == Handedness.Right) return ik.solver.leftHandEffector;
return ik.solver.rightHandEffector;
}
}
private Transform primaryHand
{
get
{
return primaryHandEffector.bone;
}
}
private Transform secondaryHand
{
get
{
return secondaryHandEffector.bone;
}
}
protected override void OnDestroy()
{
base.OnDestroy();
if (ik != null && initiated)
{
ik.solver.OnPostUpdate -= AfterFBBIK;
if (aimIK != null) aimIK.solver.OnPostUpdate -= AfterAimIK;
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 2c613189034e145e5abecee53af1ce0e
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,77 @@
using UnityEngine;
using System.Collections;
using RootMotion.FinalIK;
namespace RootMotion.FinalIK {
/// <summary>
/// Shoulder rotator is a workaround for FBBIK not rotating the shoulder bones when pulled by hands.
/// It get's the job done if you need it, but will take 2 solving iterations.
/// </summary>
public class ShoulderRotator : MonoBehaviour {
[Tooltip("Weight of shoulder rotation")]
public float weight = 1.5f;
[Tooltip("The greater the offset, the sooner the shoulder will start rotating")]
public float offset = 0.2f;
private FullBodyBipedIK ik;
private bool skip;
void Start() {
ik = GetComponent<FullBodyBipedIK>();
// You can use just LateUpdate, but note that it doesn't work when you have animatePhysics turned on for the character.
ik.solver.OnPostUpdate += RotateShoulders;
}
private void RotateShoulders () {
if (ik == null) return;
if (ik.solver.IKPositionWeight <= 0f) return;
// Skipping the second update cycle
if (skip) {
skip = false;
return;
}
RotateShoulder(FullBodyBipedChain.LeftArm, weight, offset); // Rotate the left shoulder
RotateShoulder(FullBodyBipedChain.RightArm, weight, offset); // Rotate the right shoulder
skip = true;
ik.solver.Update(); // Update FBBIK again with the rotated shoulders
}
// Rotates a shoulder of a FBBIK character
private void RotateShoulder(FullBodyBipedChain chain, float weight, float offset) {
// Get FromToRotation from the current swing direction of the shoulder to the IK target direction
Quaternion fromTo = Quaternion.FromToRotation(GetParentBoneMap(chain).swingDirection, ik.solver.GetEndEffector(chain).position - GetParentBoneMap(chain).transform.position);
// Direction to the IK target
Vector3 toTarget = ik.solver.GetEndEffector(chain).position - ik.solver.GetLimbMapping(chain).bone1.position;
// Length of the limb
float limbLength = ik.solver.GetChain(chain).nodes[0].length + ik.solver.GetChain(chain).nodes[1].length;
// Divide IK Target direction magnitude by limb length to know how much the limb is being pulled
float delta = (toTarget.magnitude / limbLength) - 1f + offset;
delta = Mathf.Clamp(delta * weight, 0f, 1f);
// Calculate the rotation offset for the shoulder
Quaternion rotationOffset = Quaternion.Lerp(Quaternion.identity, fromTo, delta * ik.solver.GetEndEffector(chain).positionWeight * ik.solver.IKPositionWeight);
// Rotate the shoulder
ik.solver.GetLimbMapping(chain).parentBone.rotation = rotationOffset * ik.solver.GetLimbMapping(chain).parentBone.rotation;
}
// Get the shoulder BoneMap
private IKMapping.BoneMap GetParentBoneMap(FullBodyBipedChain chain) {
return ik.solver.GetLimbMapping(chain).GetBoneMap(IKMappingLimb.BoneMapType.Parent);
}
// Remove the delegate when destroyed
void OnDestroy() {
if (ik != null) ik.solver.OnPostUpdate -= RotateShoulders;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: a48af8ef2fd147446af3c65186a1cd9e
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 78f02360f31c1a547b937559d34b03b1
timeCreated: 1512733284
licenseType: Store
NativeFormatImporter:
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,636 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK
{
/// <summary>
/// Calibrates VRIK for the HMD and up to 5 additional trackers.
/// </summary>
public static class VRIKCalibrator
{
/// <summary>
/// The settings for VRIK tracker calibration.
/// </summary>
[System.Serializable]
public class Settings
{
/// <summary>
/// Multiplies character scale.
/// </summary>
[Tooltip("Multiplies character scale")]
public float scaleMlp = 1f;
/// <summary>
/// Local axis of the HMD facing forward.
/// </summary>
[Tooltip("Local axis of the HMD facing forward.")]
public Vector3 headTrackerForward = Vector3.forward;
/// <summary>
/// Local axis of the HMD facing up.
/// </summary>
[Tooltip("Local axis of the HMD facing up.")]
public Vector3 headTrackerUp = Vector3.up;
/// <summary>
/// Local axis of the hand trackers pointing from the wrist towards the palm.
/// </summary>
[Tooltip("Local axis of the hand trackers pointing from the wrist towards the palm.")]
public Vector3 handTrackerForward = Vector3.forward;
/// <summary>
/// Local axis of the hand trackers pointing in the direction of the surface normal of the back of the hand.
/// </summary>
[Tooltip("Local axis of the hand trackers pointing in the direction of the surface normal of the back of the hand.")]
public Vector3 handTrackerUp = Vector3.up;
/// <summary>
/// Local axis of the foot trackers towards the player's forward direction.
/// </summary>
[Tooltip("Local axis of the foot trackers towards the player's forward direction.")]
public Vector3 footTrackerForward = Vector3.forward;
/// <summary>
/// Local axis of the foot tracker towards the up direction.
/// </summary>
[Tooltip("Local axis of the foot tracker towards the up direction.")]
public Vector3 footTrackerUp = Vector3.up;
[Space(10f)]
/// <summary>
/// Offset of the head bone from the HMD in (headTrackerForward, headTrackerUp) space relative to the head tracker.
/// </summary>
[Tooltip("Offset of the head bone from the HMD in (headTrackerForward, headTrackerUp) space relative to the head tracker.")]
public Vector3 headOffset;
/// <summary>
/// Offset of the hand bones from the hand trackers in (handTrackerForward, handTrackerUp) space relative to the hand trackers.
/// </summary>
[Tooltip("Offset of the hand bones from the hand trackers in (handTrackerForward, handTrackerUp) space relative to the hand trackers.")]
public Vector3 handOffset;
/// <summary>
/// Forward offset of the foot bones from the foot trackers.
/// </summary>
[Tooltip("Forward offset of the foot bones from the foot trackers.")]
public float footForwardOffset;
/// <summary>
/// Inward offset of the foot bones from the foot trackers.
/// </summary>
[Tooltip("Inward offset of the foot bones from the foot trackers.")]
public float footInwardOffset;
/// <summary>
/// Used for adjusting foot heading relative to the foot trackers.
/// </summary>
[Tooltip("Used for adjusting foot heading relative to the foot trackers.")]
[Range(-180f, 180f)]
public float footHeadingOffset;
/// <summary>
/// Pelvis target position weight. If the body tracker is on the backpack or somewhere else not very close to the pelvis of the player, position weight needs to be reduced to allow some bending for the spine.
/// </summary>
[Range(0f, 1f)] public float pelvisPositionWeight = 1f;
/// <summary>
/// Pelvis target rotation weight. If the body tracker is on the backpack or somewhere else not very close to the pelvis of the player, rotation weight needs to be reduced to allow some bending for the spine.
/// </summary>
[Range(0f, 1f)] public float pelvisRotationWeight = 1f;
}
/// <summary>
/// Recalibrates only the avatar scale, updates CalibrationData to the new scale value
/// </summary>
public static void RecalibrateScale(VRIK ik, CalibrationData data, Settings settings)
{
RecalibrateScale(ik, data, settings.scaleMlp);
}
/// <summary>
/// Recalibrates only the avatar scale, updates CalibrationData to the new scale value
/// </summary>
public static void RecalibrateScale(VRIK ik, CalibrationData data, float scaleMlp)
{
CalibrateScale(ik, scaleMlp);
data.scale = ik.references.root.localScale.y;
}
/// <summary>
/// Calibrates only the avatar scale.
/// </summary>
private static void CalibrateScale(VRIK ik, Settings settings)
{
CalibrateScale(ik, settings.scaleMlp);
}
/// <summary>
/// Calibrates only the avatar scale.
/// </summary>
private static void CalibrateScale(VRIK ik, float scaleMlp = 1f)
{
float sizeF = (ik.solver.spine.headTarget.position.y - ik.references.root.position.y) / (ik.references.head.position.y - ik.references.root.position.y);
ik.references.root.localScale *= sizeF * scaleMlp;
}
/// <summary>
/// Calibrates VRIK to the specified trackers using the VRIKTrackerCalibrator.Settings.
/// </summary>
/// <param name="ik">Reference to the VRIK component.</param>
/// <param name="settings">Calibration settings.</param>
/// <param name="headTracker">The HMD.</param>
/// <param name="bodyTracker">(Optional) A tracker placed anywhere on the body of the player, preferrably close to the pelvis, on the belt area.</param>
/// <param name="leftHandTracker">(Optional) A tracker or hand controller device placed anywhere on or in the player's left hand.</param>
/// <param name="rightHandTracker">(Optional) A tracker or hand controller device placed anywhere on or in the player's right hand.</param>
/// <param name="leftFootTracker">(Optional) A tracker placed anywhere on the ankle or toes of the player's left leg.</param>
/// <param name="rightFootTracker">(Optional) A tracker placed anywhere on the ankle or toes of the player's right leg.</param>
public static CalibrationData Calibrate(VRIK ik, Settings settings, Transform headTracker, Transform bodyTracker = null, Transform leftHandTracker = null, Transform rightHandTracker = null, Transform leftFootTracker = null, Transform rightFootTracker = null)
{
if (!ik.solver.initiated)
{
Debug.LogError("Can not calibrate before VRIK has initiated.");
return null;
}
if (headTracker == null)
{
Debug.LogError("Can not calibrate VRIK without the head tracker.");
return null;
}
CalibrationData data = new CalibrationData();
ik.solver.FixTransforms();
// Root position and rotation
Vector3 headPos = headTracker.position + headTracker.rotation * Quaternion.LookRotation(settings.headTrackerForward, settings.headTrackerUp) * settings.headOffset;
ik.references.root.position = new Vector3(headPos.x, ik.references.root.position.y, headPos.z);
Vector3 headForward = headTracker.rotation * settings.headTrackerForward;
headForward.y = 0f;
ik.references.root.rotation = Quaternion.LookRotation(headForward);
// Head
Transform headTarget = ik.solver.spine.headTarget == null ? (new GameObject("Head Target")).transform : ik.solver.spine.headTarget;
headTarget.position = headPos;
headTarget.rotation = ik.references.head.rotation;
headTarget.parent = headTracker;
ik.solver.spine.headTarget = headTarget;
// Size
float sizeF = (headTarget.position.y - ik.references.root.position.y) / (ik.references.head.position.y - ik.references.root.position.y);
ik.references.root.localScale *= sizeF * settings.scaleMlp;
// Body
if (bodyTracker != null)
{
Transform pelvisTarget = ik.solver.spine.pelvisTarget == null ? (new GameObject("Pelvis Target")).transform : ik.solver.spine.pelvisTarget;
pelvisTarget.position = ik.references.pelvis.position;
pelvisTarget.rotation = ik.references.pelvis.rotation;
pelvisTarget.parent = bodyTracker;
ik.solver.spine.pelvisTarget = pelvisTarget;
ik.solver.spine.pelvisPositionWeight = settings.pelvisPositionWeight;
ik.solver.spine.pelvisRotationWeight = settings.pelvisRotationWeight;
ik.solver.plantFeet = false;
ik.solver.spine.maxRootAngle = 180f;
}
else if (leftFootTracker != null && rightFootTracker != null)
{
ik.solver.spine.maxRootAngle = 0f;
}
// Left Hand
if (leftHandTracker != null)
{
Transform leftHandTarget = ik.solver.leftArm.target == null ? (new GameObject("Left Hand Target")).transform : ik.solver.leftArm.target;
leftHandTarget.position = leftHandTracker.position + leftHandTracker.rotation * Quaternion.LookRotation(settings.handTrackerForward, settings.handTrackerUp) * settings.handOffset;
Vector3 leftHandUp = Vector3.Cross(ik.solver.leftArm.wristToPalmAxis, ik.solver.leftArm.palmToThumbAxis);
leftHandTarget.rotation = QuaTools.MatchRotation(leftHandTracker.rotation * Quaternion.LookRotation(settings.handTrackerForward, settings.handTrackerUp), settings.handTrackerForward, settings.handTrackerUp, ik.solver.leftArm.wristToPalmAxis, leftHandUp);
leftHandTarget.parent = leftHandTracker;
ik.solver.leftArm.target = leftHandTarget;
ik.solver.leftArm.positionWeight = 1f;
ik.solver.leftArm.rotationWeight = 1f;
}
else
{
ik.solver.leftArm.positionWeight = 0f;
ik.solver.leftArm.rotationWeight = 0f;
}
// Right Hand
if (rightHandTracker != null)
{
Transform rightHandTarget = ik.solver.rightArm.target == null ? (new GameObject("Right Hand Target")).transform : ik.solver.rightArm.target;
rightHandTarget.position = rightHandTracker.position + rightHandTracker.rotation * Quaternion.LookRotation(settings.handTrackerForward, settings.handTrackerUp) * settings.handOffset;
Vector3 rightHandUp = -Vector3.Cross(ik.solver.rightArm.wristToPalmAxis, ik.solver.rightArm.palmToThumbAxis);
rightHandTarget.rotation = QuaTools.MatchRotation(rightHandTracker.rotation * Quaternion.LookRotation(settings.handTrackerForward, settings.handTrackerUp), settings.handTrackerForward, settings.handTrackerUp, ik.solver.rightArm.wristToPalmAxis, rightHandUp);
rightHandTarget.parent = rightHandTracker;
ik.solver.rightArm.target = rightHandTarget;
ik.solver.rightArm.positionWeight = 1f;
ik.solver.rightArm.rotationWeight = 1f;
}
else
{
ik.solver.rightArm.positionWeight = 0f;
ik.solver.rightArm.rotationWeight = 0f;
}
// Legs
if (leftFootTracker != null) CalibrateLeg(settings, leftFootTracker, ik.solver.leftLeg, (ik.references.leftToes != null ? ik.references.leftToes : ik.references.leftFoot), ik.references.root.forward, true);
if (rightFootTracker != null) CalibrateLeg(settings, rightFootTracker, ik.solver.rightLeg, (ik.references.rightToes != null ? ik.references.rightToes : ik.references.rightFoot), ik.references.root.forward, false);
// Root controller
bool addRootController = bodyTracker != null || (leftFootTracker != null && rightFootTracker != null);
var rootController = ik.references.root.GetComponent<VRIKRootController>();
if (addRootController)
{
if (rootController == null) rootController = ik.references.root.gameObject.AddComponent<VRIKRootController>();
rootController.Calibrate();
}
else
{
if (rootController != null) GameObject.Destroy(rootController);
}
// Additional solver settings
ik.solver.spine.minHeadHeight = 0f;
ik.solver.locomotion.weight = bodyTracker == null && leftFootTracker == null && rightFootTracker == null ? 1f : 0f;
// Fill in Calibration Data
data.scale = ik.references.root.localScale.y;
data.head = new CalibrationData.Target(ik.solver.spine.headTarget);
data.pelvis = new CalibrationData.Target(ik.solver.spine.pelvisTarget);
data.leftHand = new CalibrationData.Target(ik.solver.leftArm.target);
data.rightHand = new CalibrationData.Target(ik.solver.rightArm.target);
data.leftFoot = new CalibrationData.Target(ik.solver.leftLeg.target);
data.rightFoot = new CalibrationData.Target(ik.solver.rightLeg.target);
data.leftLegGoal = new CalibrationData.Target(ik.solver.leftLeg.bendGoal);
data.rightLegGoal = new CalibrationData.Target(ik.solver.rightLeg.bendGoal);
data.pelvisTargetRight = rootController != null ? rootController.pelvisTargetRight : Vector3.zero;
data.pelvisPositionWeight = ik.solver.spine.pelvisPositionWeight;
data.pelvisRotationWeight = ik.solver.spine.pelvisRotationWeight;
return data;
}
private static void CalibrateLeg(Settings settings, Transform tracker, IKSolverVR.Leg leg, Transform lastBone, Vector3 rootForward, bool isLeft)
{
string name = isLeft ? "Left" : "Right";
Transform target = leg.target == null ? (new GameObject(name + " Foot Target")).transform : leg.target;
// Space of the tracker heading
Quaternion trackerSpace = tracker.rotation * Quaternion.LookRotation(settings.footTrackerForward, settings.footTrackerUp);
Vector3 f = trackerSpace * Vector3.forward;
f.y = 0f;
trackerSpace = Quaternion.LookRotation(f);
// Target position
float inwardOffset = isLeft ? settings.footInwardOffset : -settings.footInwardOffset;
target.position = tracker.position + trackerSpace * new Vector3(inwardOffset, 0f, settings.footForwardOffset);
target.position = new Vector3(target.position.x, lastBone.position.y, target.position.z);
// Target rotation
target.rotation = lastBone.rotation;
// Rotate target forward towards tracker forward
Vector3 footForward = AxisTools.GetAxisVectorToDirection(lastBone, rootForward);
if (Vector3.Dot(lastBone.rotation * footForward, rootForward) < 0f) footForward = -footForward;
Vector3 fLocal = Quaternion.Inverse(Quaternion.LookRotation(target.rotation * footForward)) * f;
float angle = Mathf.Atan2(fLocal.x, fLocal.z) * Mathf.Rad2Deg;
float headingOffset = isLeft ? settings.footHeadingOffset : -settings.footHeadingOffset;
target.rotation = Quaternion.AngleAxis(angle + headingOffset, Vector3.up) * target.rotation;
target.parent = tracker;
leg.target = target;
leg.positionWeight = 1f;
leg.rotationWeight = 1f;
// Bend goal
Transform bendGoal = leg.bendGoal == null ? (new GameObject(name + " Leg Bend Goal")).transform : leg.bendGoal;
bendGoal.position = lastBone.position + trackerSpace * Vector3.forward + trackerSpace * Vector3.up;// * 0.5f;
bendGoal.parent = tracker;
leg.bendGoal = bendGoal;
leg.bendGoalWeight = 1f;
}
/// <summary>
/// When VRIK is calibrated by calibration settings, will store CalibrationData that can be used to set up another character with the exact same calibration.
/// </summary>
[System.Serializable]
public class CalibrationData
{
[System.Serializable]
public class Target
{
public bool used;
public Vector3 localPosition;
public Quaternion localRotation;
public Target(Transform t)
{
this.used = t != null;
if (!this.used) return;
this.localPosition = t.localPosition;
this.localRotation = t.localRotation;
}
public void SetTo(Transform t)
{
if (!used) return;
t.localPosition = localPosition;
t.localRotation = localRotation;
}
}
public float scale;
public Target head, leftHand, rightHand, pelvis, leftFoot, rightFoot, leftLegGoal, rightLegGoal;
public Vector3 pelvisTargetRight;
public float pelvisPositionWeight;
public float pelvisRotationWeight;
}
/// <summary>
/// Calibrates VRIK to the specified trackers using CalibrationData from a previous calibration. Requires this character's bone orientations to match with the character's that was used in the previous calibration.
/// </summary>
/// <param name="ik">Reference to the VRIK component.</param>
/// <param name="data">Use calibration data from a previous calibration.</param>
/// <param name="headTracker">The HMD.</param>
/// <param name="bodyTracker">(Optional) A tracker placed anywhere on the body of the player, preferrably close to the pelvis, on the belt area.</param>
/// <param name="leftHandTracker">(Optional) A tracker or hand controller device placed anywhere on or in the player's left hand.</param>
/// <param name="rightHandTracker">(Optional) A tracker or hand controller device placed anywhere on or in the player's right hand.</param>
/// <param name="leftFootTracker">(Optional) A tracker placed anywhere on the ankle or toes of the player's left leg.</param>
/// <param name="rightFootTracker">(Optional) A tracker placed anywhere on the ankle or toes of the player's right leg.</param>
public static void Calibrate(VRIK ik, CalibrationData data, Transform headTracker, Transform bodyTracker = null, Transform leftHandTracker = null, Transform rightHandTracker = null, Transform leftFootTracker = null, Transform rightFootTracker = null)
{
if (!ik.solver.initiated)
{
Debug.LogError("Can not calibrate before VRIK has initiated.");
return;
}
if (headTracker == null)
{
Debug.LogError("Can not calibrate VRIK without the head tracker.");
return;
}
ik.solver.FixTransforms();
// Head
Transform headTarget = ik.solver.spine.headTarget == null ? (new GameObject("Head Target")).transform : ik.solver.spine.headTarget;
headTarget.parent = headTracker;
data.head.SetTo(headTarget);
ik.solver.spine.headTarget = headTarget;
// Size
ik.references.root.localScale = data.scale * Vector3.one;
// Body
if (bodyTracker != null && data.pelvis != null)
{
Transform pelvisTarget = ik.solver.spine.pelvisTarget == null ? (new GameObject("Pelvis Target")).transform : ik.solver.spine.pelvisTarget;
pelvisTarget.parent = bodyTracker;
data.pelvis.SetTo(pelvisTarget);
ik.solver.spine.pelvisTarget = pelvisTarget;
ik.solver.spine.pelvisPositionWeight = data.pelvisPositionWeight;
ik.solver.spine.pelvisRotationWeight = data.pelvisRotationWeight;
ik.solver.plantFeet = false;
ik.solver.spine.maxRootAngle = 180f;
}
else if (leftFootTracker != null && rightFootTracker != null)
{
ik.solver.spine.maxRootAngle = 0f;
}
// Left Hand
if (leftHandTracker != null)
{
Transform leftHandTarget = ik.solver.leftArm.target == null ? (new GameObject("Left Hand Target")).transform : ik.solver.leftArm.target;
leftHandTarget.parent = leftHandTracker;
data.leftHand.SetTo(leftHandTarget);
ik.solver.leftArm.target = leftHandTarget;
ik.solver.leftArm.positionWeight = 1f;
ik.solver.leftArm.rotationWeight = 1f;
}
else
{
ik.solver.leftArm.positionWeight = 0f;
ik.solver.leftArm.rotationWeight = 0f;
}
// Right Hand
if (rightHandTracker != null)
{
Transform rightHandTarget = ik.solver.rightArm.target == null ? (new GameObject("Right Hand Target")).transform : ik.solver.rightArm.target;
rightHandTarget.parent = rightHandTracker;
data.rightHand.SetTo(rightHandTarget);
ik.solver.rightArm.target = rightHandTarget;
ik.solver.rightArm.positionWeight = 1f;
ik.solver.rightArm.rotationWeight = 1f;
}
else
{
ik.solver.rightArm.positionWeight = 0f;
ik.solver.rightArm.rotationWeight = 0f;
}
// Legs
if (leftFootTracker != null) CalibrateLeg(data, leftFootTracker, ik.solver.leftLeg, (ik.references.leftToes != null ? ik.references.leftToes : ik.references.leftFoot), ik.references.root.forward, true);
if (rightFootTracker != null) CalibrateLeg(data, rightFootTracker, ik.solver.rightLeg, (ik.references.rightToes != null ? ik.references.rightToes : ik.references.rightFoot), ik.references.root.forward, false);
// Root controller
bool addRootController = bodyTracker != null || (leftFootTracker != null && rightFootTracker != null);
var rootController = ik.references.root.GetComponent<VRIKRootController>();
if (addRootController)
{
if (rootController == null) rootController = ik.references.root.gameObject.AddComponent<VRIKRootController>();
rootController.Calibrate(data);
}
else
{
if (rootController != null) GameObject.Destroy(rootController);
}
// Additional solver settings
ik.solver.spine.minHeadHeight = 0f;
ik.solver.locomotion.weight = bodyTracker == null && leftFootTracker == null && rightFootTracker == null ? 1f : 0f;
}
private static void CalibrateLeg(CalibrationData data, Transform tracker, IKSolverVR.Leg leg, Transform lastBone, Vector3 rootForward, bool isLeft)
{
if (isLeft && data.leftFoot == null) return;
if (!isLeft && data.rightFoot == null) return;
string name = isLeft ? "Left" : "Right";
Transform target = leg.target == null ? (new GameObject(name + " Foot Target")).transform : leg.target;
target.parent = tracker;
if (isLeft) data.leftFoot.SetTo(target);
else data.rightFoot.SetTo(target);
leg.target = target;
leg.positionWeight = 1f;
leg.rotationWeight = 1f;
// Bend goal
Transform bendGoal = leg.bendGoal == null ? (new GameObject(name + " Leg Bend Goal")).transform : leg.bendGoal;
bendGoal.parent = tracker;
if (isLeft) data.leftLegGoal.SetTo(bendGoal);
else data.rightLegGoal.SetTo(bendGoal);
leg.bendGoal = bendGoal;
leg.bendGoalWeight = 1f;
}
/// <summary>
/// Simple calibration to head and hands using predefined anchor position and rotation offsets.
/// </summary>
/// <param name="ik">The VRIK component.</param>
/// <param name="centerEyeAnchor">HMD.</param>
/// <param name="leftHandAnchor">Left hand controller.</param>
/// <param name="rightHandAnchor">Right hand controller.</param>
/// <param name="centerEyePositionOffset">Position offset of the camera from the head bone (root space).</param>
/// <param name="centerEyeRotationOffset">Rotation offset of the camera from the head bone (root space).</param>
/// <param name="handPositionOffset">Position offset of the hand controller from the hand bone (controller space).</param>
/// <param name="handRotationOffset">Rotation offset of the hand controller from the hand bone (controller space).</param>
/// <param name="scaleMlp">Multiplies the scale of the root.</param>
/// <returns></returns>
public static CalibrationData Calibrate(VRIK ik, Transform centerEyeAnchor, Transform leftHandAnchor, Transform rightHandAnchor, Vector3 centerEyePositionOffset, Vector3 centerEyeRotationOffset, Vector3 handPositionOffset, Vector3 handRotationOffset, float scaleMlp = 1f)
{
CalibrateHead(ik, centerEyeAnchor, centerEyePositionOffset, centerEyeRotationOffset);
CalibrateHands(ik, leftHandAnchor, rightHandAnchor, handPositionOffset, handRotationOffset);
CalibrateScale(ik, scaleMlp);
// Fill in Calibration Data
CalibrationData data = new CalibrationData();
data.scale = ik.references.root.localScale.y;
data.head = new CalibrationData.Target(ik.solver.spine.headTarget);
data.leftHand = new CalibrationData.Target(ik.solver.leftArm.target);
data.rightHand = new CalibrationData.Target(ik.solver.rightArm.target);
return data;
}
/// <summary>
/// Calibrates head IK target to specified anchor position and rotation offset independent of avatar bone orientations.
/// </summary>
public static void CalibrateHead(VRIK ik, Transform centerEyeAnchor, Vector3 anchorPositionOffset, Vector3 anchorRotationOffset)
{
if (ik.solver.spine.headTarget == null) ik.solver.spine.headTarget = new GameObject("Head IK Target").transform;
Vector3 forward = Quaternion.Inverse(ik.references.head.rotation) * ik.references.root.forward;
Vector3 up = Quaternion.Inverse(ik.references.head.rotation) * ik.references.root.up;
Quaternion headSpace = Quaternion.LookRotation(forward, up);
Vector3 anchorPos = ik.references.head.position + ik.references.head.rotation * headSpace * anchorPositionOffset;
Quaternion anchorRot = ik.references.head.rotation * headSpace * Quaternion.Euler(anchorRotationOffset);
Quaternion anchorRotInverse = Quaternion.Inverse(anchorRot);
ik.solver.spine.headTarget.parent = centerEyeAnchor;
ik.solver.spine.headTarget.localPosition = anchorRotInverse * (ik.references.head.position - anchorPos);
ik.solver.spine.headTarget.localRotation = anchorRotInverse * ik.references.head.rotation;
}
/// <summary>
/// Calibrates body target to avatar pelvis position and position/rotation offsets in character root space.
/// </summary>
public static void CalibrateBody(VRIK ik, Transform pelvisTracker, Vector3 trackerPositionOffset, Vector3 trackerRotationOffset)
{
if (ik.solver.spine.pelvisTarget == null) ik.solver.spine.pelvisTarget = new GameObject("Pelvis IK Target").transform;
ik.solver.spine.pelvisTarget.position = ik.references.pelvis.position + ik.references.root.rotation * trackerPositionOffset;
ik.solver.spine.pelvisTarget.rotation = ik.references.root.rotation * Quaternion.Euler(trackerRotationOffset);
ik.solver.spine.pelvisTarget.parent = pelvisTracker;
}
/// <summary>
/// Calibrates hand IK targets to specified anchor position and rotation offsets independent of avatar bone orientations.
/// </summary>
public static void CalibrateHands(VRIK ik, Transform leftHandAnchor, Transform rightHandAnchor, Vector3 anchorPositionOffset, Vector3 anchorRotationOffset)
{
if (ik.solver.leftArm.target == null) ik.solver.leftArm.target = new GameObject("Left Hand IK Target").transform;
if (ik.solver.rightArm.target == null) ik.solver.rightArm.target = new GameObject("Right Hand IK Target").transform;
CalibrateHand(ik, leftHandAnchor, anchorPositionOffset, anchorRotationOffset, true);
CalibrateHand(ik, rightHandAnchor, anchorPositionOffset, anchorRotationOffset, false);
}
private static void CalibrateHand(VRIK ik, Transform anchor, Vector3 positionOffset, Vector3 rotationOffset, bool isLeft)
{
if (isLeft)
{
positionOffset.x = -positionOffset.x;
rotationOffset.y = -rotationOffset.y;
rotationOffset.z = -rotationOffset.z;
}
var hand = isLeft ? ik.references.leftHand : ik.references.rightHand;
var forearm = isLeft ? ik.references.leftForearm : ik.references.rightForearm;
var target = isLeft ? ik.solver.leftArm.target : ik.solver.rightArm.target;
Vector3 forward = isLeft ? ik.solver.leftArm.wristToPalmAxis : ik.solver.rightArm.wristToPalmAxis;
if (forward == Vector3.zero) forward = VRIKCalibrator.GuessWristToPalmAxis(hand, forearm);
Vector3 up = isLeft ? ik.solver.leftArm.palmToThumbAxis : ik.solver.rightArm.palmToThumbAxis;
if (up == Vector3.zero) up = VRIKCalibrator.GuessPalmToThumbAxis(hand, forearm);
Quaternion handSpace = Quaternion.LookRotation(forward, up);
Vector3 anchorPos = hand.position + hand.rotation * handSpace * positionOffset;
Quaternion anchorRot = hand.rotation * handSpace * Quaternion.Euler(rotationOffset);
Quaternion anchorRotInverse = Quaternion.Inverse(anchorRot);
target.parent = anchor;
target.localPosition = anchorRotInverse * (hand.position - anchorPos);
target.localRotation = anchorRotInverse * hand.rotation;
}
public static Vector3 GuessWristToPalmAxis(Transform hand, Transform forearm)
{
Vector3 toForearm = forearm.position - hand.position;
Vector3 axis = AxisTools.ToVector3(AxisTools.GetAxisToDirection(hand, toForearm));
if (Vector3.Dot(toForearm, hand.rotation * axis) > 0f) axis = -axis;
return axis;
}
public static Vector3 GuessPalmToThumbAxis(Transform hand, Transform forearm)
{
if (hand.childCount == 0)
{
Debug.LogWarning("Hand " + hand.name + " does not have any fingers, VRIK can not guess the hand bone's orientation. Please assign 'Wrist To Palm Axis' and 'Palm To Thumb Axis' manually for both arms in VRIK settings.", hand);
return Vector3.zero;
}
float closestSqrMag = Mathf.Infinity;
int thumbIndex = 0;
for (int i = 0; i < hand.childCount; i++)
{
float sqrMag = Vector3.SqrMagnitude(hand.GetChild(i).position - hand.position);
if (sqrMag < closestSqrMag)
{
closestSqrMag = sqrMag;
thumbIndex = i;
}
}
Vector3 handNormal = Vector3.Cross(hand.position - forearm.position, hand.GetChild(thumbIndex).position - hand.position);
Vector3 toThumb = Vector3.Cross(handNormal, hand.position - forearm.position);
Vector3 axis = AxisTools.ToVector3(AxisTools.GetAxisToDirection(hand, toThumb));
if (Vector3.Dot(toThumb, hand.rotation * axis) < 0f) axis = -axis;
return axis;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: a19b811591057074d98d5ab94aaf496a
timeCreated: 1499778189
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,46 @@
using UnityEngine;
using System.Collections;
using RootMotion.FinalIK;
namespace RootMotion.FinalIK
{
/// <summary>
/// Simple VRIK LOD level controller based on renderer.isVisible and camera distance.
/// </summary>
public class VRIKLODController : MonoBehaviour
{
public Renderer LODRenderer;
public float LODDistance = 15f;
public bool allowCulled = true;
private VRIK ik;
void Start()
{
ik = GetComponent<VRIK>();
}
void Update()
{
ik.solver.LOD = GetLODLevel();
}
// Setting LOD level to 1 saves approximately 20% of solving time. LOD level 2 means IK is culled, with only root position and rotation updated if locomotion enabled.
private int GetLODLevel()
{
if (allowCulled)
{
if (LODRenderer == null) return 0;
if (!LODRenderer.isVisible) return 2;
}
// Set LOD to 1 if too far from camera
float sqrMag = (ik.transform.position - Camera.main.transform.position).sqrMagnitude;
if (sqrMag > LODDistance * LODDistance) return 1;
return 0;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1158802e47cf684439400e5c8e03cba9
timeCreated: 1549982641
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,80 @@
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK
{
public class VRIKRootController : MonoBehaviour
{
public Vector3 pelvisTargetRight { get; private set; }
private Transform pelvisTarget;
private Transform leftFootTarget;
private Transform rightFootTarget;
private VRIK ik;
void Awake()
{
ik = GetComponent<VRIK>();
ik.solver.OnPreUpdate += OnPreUpdate;
Calibrate();
}
public void Calibrate()
{
if (ik == null)
{
Debug.LogError("No VRIK found on VRIKRootController's GameObject.", transform);
return;
}
pelvisTarget = ik.solver.spine.pelvisTarget;
leftFootTarget = ik.solver.leftLeg.target;
rightFootTarget = ik.solver.rightLeg.target;
if (pelvisTarget != null) pelvisTargetRight = Quaternion.Inverse(pelvisTarget.rotation) * ik.references.root.right;
}
public void Calibrate(VRIKCalibrator.CalibrationData data)
{
if (ik == null)
{
Debug.LogError("No VRIK found on VRIKRootController's GameObject.", transform);
return;
}
pelvisTarget = ik.solver.spine.pelvisTarget;
leftFootTarget = ik.solver.leftLeg.target;
rightFootTarget = ik.solver.rightLeg.target;
if (pelvisTarget != null)
{
pelvisTargetRight = data.pelvisTargetRight;
}
}
void OnPreUpdate()
{
if (!enabled) return;
if (pelvisTarget != null)
{
ik.references.root.position = new Vector3(pelvisTarget.position.x, ik.references.root.position.y, pelvisTarget.position.z);
Vector3 f = Vector3.Cross(pelvisTarget.rotation * pelvisTargetRight, ik.references.root.up);
f.y = 0f;
ik.references.root.rotation = Quaternion.LookRotation(f);
ik.references.pelvis.position = Vector3.Lerp(ik.references.pelvis.position, pelvisTarget.position, ik.solver.spine.pelvisPositionWeight);
ik.references.pelvis.rotation = Quaternion.Slerp(ik.references.pelvis.rotation, pelvisTarget.rotation, ik.solver.spine.pelvisRotationWeight);
}
else if (leftFootTarget != null && rightFootTarget != null)
{
ik.references.root.position = Vector3.Lerp(leftFootTarget.position, rightFootTarget.position, 0.5f);
}
}
void OnDestroy()
{
if (ik != null) ik.solver.OnPreUpdate -= OnPreUpdate;
}
}
}

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 1d0d2eb6d6e88294dbaf39641e718d71
timeCreated: 1500635579
licenseType: Store
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: