using UnityEngine; using System.Collections; namespace RootMotion.FinalIK { /// /// Effector for manipulating node based %IK solvers. /// [System.Serializable] public class IKEffector { #region Main Interface /// /// Gets the main node. /// public IKSolver.Node GetNode(IKSolverFullBody solver) { return solver.chain[chainIndex].nodes[nodeIndex]; } /// /// The node transform used by this effector. /// public Transform bone; /// /// The target Transform (optional, you can use just the position and rotation instead). /// public Transform target; /// /// The position weight. /// [Range(0f, 1f)] public float positionWeight; /// /// The rotation weight. /// [Range(0f, 1f)] public float rotationWeight; /// /// The effector position in world space. /// public Vector3 position = Vector3.zero; /// /// The effector rotation relative to default rotation in world space. /// public Quaternion rotation = Quaternion.identity; /// /// The position offset in world space. positionOffset will be reset to Vector3.zero each frame after the solver is complete. /// public Vector3 positionOffset; /// /// Is this the last effector of a node chain? /// public bool isEndEffector { get; private set; } /// /// If false, child nodes will be ignored by this effector (if it has any). /// public bool effectChildNodes = true; /// /// Keeps the node position relative to the triangle defined by the plane bones (applies only to end-effectors). /// [Range(0f, 1f)] public float maintainRelativePositionWeight; /// /// Pins the effector to the animated position of its bone. /// public void PinToBone(float positionWeight, float rotationWeight) { position = bone.position; this.positionWeight = Mathf.Clamp(positionWeight, 0f, 1f); rotation = bone.rotation; this.rotationWeight = Mathf.Clamp(rotationWeight, 0f, 1f); } #endregion Main Interface public Transform[] childBones = new Transform[0]; // The optional list of other bones that positionOffset and position of this effector will be applied to. public Transform planeBone1; // The first bone defining the parent plane. public Transform planeBone2; // The second bone defining the parent plane. public Transform planeBone3; // The third bone defining the parent plane. public Quaternion planeRotationOffset = Quaternion.identity; // Used by Bend Constraints private float posW, rotW; private Vector3[] localPositions = new Vector3[0]; private bool usePlaneNodes; private Quaternion animatedPlaneRotation = Quaternion.identity; private Vector3 animatedPosition; private bool firstUpdate; private int chainIndex = -1; private int nodeIndex = -1; private int plane1ChainIndex; private int plane1NodeIndex = -1; private int plane2ChainIndex = -1; private int plane2NodeIndex = -1; private int plane3ChainIndex = -1; private int plane3NodeIndex = -1; private int[] childChainIndexes = new int[0]; private int[] childNodeIndexes = new int[0]; public IKEffector() {} public IKEffector (Transform bone, Transform[] childBones) { this.bone = bone; this.childBones = childBones; } /* * Determines whether this IKEffector is valid or not. * */ public bool IsValid(IKSolver solver, ref string message) { if (bone == null) { message = "IK Effector bone is null."; return false; } if (solver.GetPoint(bone) == null) { message = "IK Effector is referencing to a bone '" + bone.name + "' that does not excist in the Node Chain."; return false; } foreach (Transform b in childBones) { if (b == null) { message = "IK Effector contains a null reference."; return false; } } foreach (Transform b in childBones) { if (solver.GetPoint(b) == null) { message = "IK Effector is referencing to a bone '" + b.name + "' that does not excist in the Node Chain."; return false; } } if (planeBone1 != null && solver.GetPoint(planeBone1) == null) { message = "IK Effector is referencing to a bone '" + planeBone1.name + "' that does not excist in the Node Chain."; return false; } if (planeBone2 != null && solver.GetPoint(planeBone2) == null) { message = "IK Effector is referencing to a bone '" + planeBone2.name + "' that does not excist in the Node Chain."; return false; } if (planeBone3 != null && solver.GetPoint(planeBone3) == null) { message = "IK Effector is referencing to a bone '" + planeBone3.name + "' that does not excist in the Node Chain."; return false; } return true; } /* * Initiate the effector, set default values * */ public void Initiate(IKSolverFullBody solver) { position = bone.position; rotation = bone.rotation; animatedPlaneRotation = Quaternion.identity; // Getting the node solver.GetChainAndNodeIndexes(bone, out chainIndex, out nodeIndex); // Child nodes childChainIndexes = new int[childBones.Length]; childNodeIndexes = new int[childBones.Length]; for (int i = 0; i < childBones.Length; i++) { solver.GetChainAndNodeIndexes(childBones[i], out childChainIndexes[i], out childNodeIndexes[i]); } localPositions = new Vector3[childBones.Length]; // Plane nodes usePlaneNodes = false; if (planeBone1 != null) { solver.GetChainAndNodeIndexes(planeBone1, out plane1ChainIndex, out plane1NodeIndex); if (planeBone2 != null) { solver.GetChainAndNodeIndexes(planeBone2, out plane2ChainIndex, out plane2NodeIndex); if (planeBone3 != null) { solver.GetChainAndNodeIndexes(planeBone3, out plane3ChainIndex, out plane3NodeIndex); usePlaneNodes = true; } } isEndEffector = true; } else isEndEffector = false; } /* * Clear node offset * */ public void ResetOffset(IKSolverFullBody solver) { solver.GetNode(chainIndex, nodeIndex).offset = Vector3.zero; for (int i = 0; i < childChainIndexes.Length; i++) { solver.GetNode(childChainIndexes[i], childNodeIndexes[i]).offset = Vector3.zero; } } /* * Set the position and rotation to match the target * */ public void SetToTarget() { if (target == null) return; position = target.position; rotation = target.rotation; } /* * Presolving, applying offset * */ public void OnPreSolve(IKSolverFullBody solver) { positionWeight = Mathf.Clamp(positionWeight, 0f, 1f); rotationWeight = Mathf.Clamp(rotationWeight, 0f, 1f); maintainRelativePositionWeight = Mathf.Clamp(maintainRelativePositionWeight, 0f, 1f); // Calculating weights posW = positionWeight * solver.IKPositionWeight; rotW = rotationWeight * solver.IKPositionWeight; solver.GetNode(chainIndex, nodeIndex).effectorPositionWeight = posW; solver.GetNode(chainIndex, nodeIndex).effectorRotationWeight = rotW; solver.GetNode(chainIndex, nodeIndex).solverRotation = rotation; if (float.IsInfinity(positionOffset.x) || float.IsInfinity(positionOffset.y) || float.IsInfinity(positionOffset.z) ) Debug.LogError("Invalid IKEffector.positionOffset (contains Infinity)! Please make sure not to set IKEffector.positionOffset to infinite values.", bone); if (float.IsNaN(positionOffset.x) || float.IsNaN(positionOffset.y) || float.IsNaN(positionOffset.z) ) Debug.LogError("Invalid IKEffector.positionOffset (contains NaN)! Please make sure not to set IKEffector.positionOffset to NaN values.", bone); if (positionOffset.sqrMagnitude > 10000000000f) Debug.LogError("Additive effector positionOffset detected in Full Body IK (extremely large value). Make sure you are not circularily adding to effector positionOffset each frame.", bone); if (float.IsInfinity(position.x) || float.IsInfinity(position.y) || float.IsInfinity(position.z) ) Debug.LogError("Invalid IKEffector.position (contains Infinity)!"); solver.GetNode(chainIndex, nodeIndex).offset += positionOffset * solver.IKPositionWeight; if (effectChildNodes && solver.iterations > 0) { for (int i = 0; i < childBones.Length; i++) { localPositions[i] = childBones[i].transform.position - bone.transform.position; solver.GetNode(childChainIndexes[i], childNodeIndexes[i]).offset += positionOffset * solver.IKPositionWeight; } } // Relative to Plane if (usePlaneNodes && maintainRelativePositionWeight > 0f) { animatedPlaneRotation = Quaternion.LookRotation(planeBone2.position - planeBone1.position, planeBone3.position - planeBone1.position);; } firstUpdate = true; } /* * Called after writing the pose * */ public void OnPostWrite() { positionOffset = Vector3.zero; } /* * Rotation of plane nodes in the solver * */ private Quaternion GetPlaneRotation(IKSolverFullBody solver) { Vector3 p1 = solver.GetNode(plane1ChainIndex, plane1NodeIndex).solverPosition; Vector3 p2 = solver.GetNode(plane2ChainIndex, plane2NodeIndex).solverPosition; Vector3 p3 = solver.GetNode(plane3ChainIndex, plane3NodeIndex).solverPosition; Vector3 viewingVector = p2 - p1; Vector3 upVector = p3 - p1; if (viewingVector == Vector3.zero) { Warning.Log("Make sure you are not placing 2 or more FBBIK effectors of the same chain to exactly the same position.", bone); return Quaternion.identity; } return Quaternion.LookRotation(viewingVector, upVector); } /* * Manipulating node solverPosition * */ public void Update(IKSolverFullBody solver) { if (firstUpdate) { animatedPosition = bone.position + solver.GetNode(chainIndex, nodeIndex).offset; firstUpdate = false; } solver.GetNode(chainIndex, nodeIndex).solverPosition = Vector3.Lerp(GetPosition(solver, out planeRotationOffset), position, posW); // Child nodes if (!effectChildNodes) return; for (int i = 0; i < childBones.Length; i++) { solver.GetNode(childChainIndexes[i], childNodeIndexes[i]).solverPosition = Vector3.Lerp(solver.GetNode(childChainIndexes[i], childNodeIndexes[i]).solverPosition, solver.GetNode(chainIndex, nodeIndex).solverPosition + localPositions[i], posW); } } /* * Gets the starting position of the iteration * */ private Vector3 GetPosition(IKSolverFullBody solver, out Quaternion planeRotationOffset) { planeRotationOffset = Quaternion.identity; if (!isEndEffector) return solver.GetNode(chainIndex, nodeIndex).solverPosition; // non end-effectors are always free if (maintainRelativePositionWeight <= 0f) return animatedPosition; // Maintain relative position Vector3 p = bone.position; Vector3 dir = p - planeBone1.position; planeRotationOffset = GetPlaneRotation(solver) * Quaternion.Inverse(animatedPlaneRotation); p = solver.GetNode(plane1ChainIndex, plane1NodeIndex).solverPosition + planeRotationOffset * dir; // Interpolate the rotation offset planeRotationOffset = Quaternion.Lerp(Quaternion.identity, planeRotationOffset, maintainRelativePositionWeight); return Vector3.Lerp(animatedPosition, p + solver.GetNode(chainIndex, nodeIndex).offset, maintainRelativePositionWeight); } } }