using UnityEngine; using System.Collections; namespace RootMotion.FinalIK { /// /// Mapping a bone hierarchy to 2 triangles defined by the hip and chest planes. /// [System.Serializable] public class IKMappingSpine: IKMapping { #region Main Interface /// /// The spine bones. /// public Transform[] spineBones; /// /// The left upper arm bone. /// public Transform leftUpperArmBone; /// /// The right upper arm bone. /// public Transform rightUpperArmBone; /// /// The left thigh bone. /// public Transform leftThighBone; /// /// The right thigh bone. /// public Transform rightThighBone; /// /// The number of iterations of the %FABRIK algorithm. Not used if there are 2 bones assigned to Spine in the References. /// [Range(1, 3)] public int iterations = 3; /// /// The weight of twisting the spine bones gradually to the orientation of the chest triangle. Relatively expensive, so set this to 0 if there is not much spine twisting going on. /// [Range(0f, 1f)] public float twistWeight = 1f; /// /// Determines whether this IKMappingSpine is valid /// public override bool IsValid(IKSolver solver, ref string message) { if (!base.IsValid(solver, ref message)) return false; foreach (Transform spineBone in spineBones) if (spineBone == null) { message = "Spine bones contains a null reference."; return false; } int nodes = 0; for (int i = 0; i < spineBones.Length; i++) { if (solver.GetPoint(spineBones[i]) != null) nodes ++; } if (nodes == 0) { message = "IKMappingSpine does not contain any nodes."; return false; } if (leftUpperArmBone == null) { message = "IKMappingSpine is missing the left upper arm bone."; return false; } if (rightUpperArmBone == null) { message = "IKMappingSpine is missing the right upper arm bone."; return false; } if (leftThighBone == null) { message = "IKMappingSpine is missing the left thigh bone."; return false; } if (rightThighBone == null) { message = "IKMappingSpine is missing the right thigh bone."; return false; } if (solver.GetPoint(leftUpperArmBone) == null) { message = "Full Body IK is missing the left upper arm node."; return false; } if (solver.GetPoint(rightUpperArmBone) == null) { message = "Full Body IK is missing the right upper arm node."; return false; } if (solver.GetPoint(leftThighBone) == null) { message = "Full Body IK is missing the left thigh node."; return false; } if (solver.GetPoint(rightThighBone) == null) { message = "Full Body IK is missing the right thigh node."; return false; } return true; } #endregion Main Interface private int rootNodeIndex; private BoneMap[] spine = new BoneMap[0]; private BoneMap leftUpperArm = new BoneMap(), rightUpperArm = new BoneMap(), leftThigh = new BoneMap(), rightThigh = new BoneMap(); private bool useFABRIK; public IKMappingSpine() {} public IKMappingSpine(Transform[] spineBones, Transform leftUpperArmBone, Transform rightUpperArmBone, Transform leftThighBone, Transform rightThighBone) { SetBones(spineBones, leftUpperArmBone, rightUpperArmBone, leftThighBone, rightThighBone); } public void SetBones(Transform[] spineBones, Transform leftUpperArmBone, Transform rightUpperArmBone, Transform leftThighBone, Transform rightThighBone) { this.spineBones = spineBones; this.leftUpperArmBone = leftUpperArmBone; this.rightUpperArmBone = rightUpperArmBone; this.leftThighBone = leftThighBone; this.rightThighBone = rightThighBone; } public void StoreDefaultLocalState() { for (int i = 0; i < spine.Length; i++) { spine[i].StoreDefaultLocalState(); } } public void FixTransforms() { for (int i = 0; i < spine.Length; i++) { spine[i].FixTransform(i == 0 || i == spine.Length - 1); } } /* * Initiating and setting defaults * */ public override void Initiate(IKSolverFullBody solver) { if (iterations <= 0) iterations = 3; // Creating the bone maps if (spine == null || spine.Length != spineBones.Length) spine = new BoneMap[spineBones.Length]; rootNodeIndex = -1; for (int i = 0; i < spineBones.Length; i++) { if (spine[i] == null) spine[i] = new BoneMap(); spine[i].Initiate(spineBones[i], solver); // Finding the root node if (spine[i].isNodeBone) rootNodeIndex = i; } if (leftUpperArm == null) leftUpperArm = new BoneMap(); if (rightUpperArm == null) rightUpperArm = new BoneMap(); if (leftThigh == null) leftThigh = new BoneMap(); if (rightThigh == null) rightThigh = new BoneMap(); leftUpperArm.Initiate(leftUpperArmBone, solver); rightUpperArm.Initiate(rightUpperArmBone, solver); leftThigh.Initiate(leftThighBone, solver); rightThigh.Initiate(rightThighBone, solver); for (int i = 0; i < spine.Length; i++) spine[i].SetIKPosition(); // Defining the plane for the first bone spine[0].SetPlane(solver, spine[rootNodeIndex].transform, leftThigh.transform, rightThigh.transform); // Finding bone lengths and axes for (int i = 0; i < spine.Length - 1; i++) { spine[i].SetLength(spine[i + 1]); spine[i].SetLocalSwingAxis(spine[i + 1]); spine[i].SetLocalTwistAxis(leftUpperArm.transform.position - rightUpperArm.transform.position, spine[i + 1].transform.position - spine[i].transform.position); } // Defining the plane for the last bone spine[spine.Length - 1].SetPlane(solver, spine[rootNodeIndex].transform, leftUpperArm.transform, rightUpperArm.transform); spine[spine.Length - 1].SetLocalSwingAxis(leftUpperArm, rightUpperArm); useFABRIK = UseFABRIK(); } // Should the spine mapping use the FABRIK algorithm private bool UseFABRIK() { if (spine.Length > 3) return true; if (rootNodeIndex != 1) return true; return false; } /* * Updating the bone maps to the current animated state of the character * */ public void ReadPose() { spine[0].UpdatePlane(true, true); for (int i = 0; i < spine.Length - 1; i++) { spine[i].SetLength(spine[i + 1]); spine[i].SetLocalSwingAxis(spine[i + 1]); spine[i].SetLocalTwistAxis(leftUpperArm.transform.position - rightUpperArm.transform.position, spine[i + 1].transform.position - spine[i].transform.position); } spine[spine.Length - 1].UpdatePlane(true, true); spine[spine.Length - 1].SetLocalSwingAxis(leftUpperArm, rightUpperArm); } /* * Mapping the spine to the hip and chest planes * */ public void WritePose(IKSolverFullBody solver) { Vector3 firstPosition = spine[0].GetPlanePosition(solver); Vector3 rootPosition = solver.GetNode(spine[rootNodeIndex].chainIndex, spine[rootNodeIndex].nodeIndex).solverPosition; Vector3 lastPosition = spine[spine.Length - 1].GetPlanePosition(solver); // If we have more than 3 bones, use the FABRIK algorithm if (useFABRIK) { Vector3 offset = solver.GetNode(spine[rootNodeIndex].chainIndex, spine[rootNodeIndex].nodeIndex).solverPosition - spine[rootNodeIndex].transform.position; for (int i = 0; i < spine.Length; i++) { spine[i].ikPosition = spine[i].transform.position + offset; } // Iterating the FABRIK algorithm for (int i = 0; i < iterations; i++) { ForwardReach(lastPosition); BackwardReach(firstPosition); spine[rootNodeIndex].ikPosition = rootPosition; } } else { // When we have just 3 bones, we know their positions already spine[0].ikPosition = firstPosition; spine[rootNodeIndex].ikPosition = rootPosition; } spine[spine.Length - 1].ikPosition = lastPosition; // Mapping the spine bones to the solver MapToSolverPositions(solver); } /* * Stage 1 of the FABRIK algorithm. * */ public void ForwardReach(Vector3 position) { // Lerp last bone's ikPosition to position spine[spineBones.Length - 1].ikPosition = position; for (int i = spine.Length - 2; i > -1; i--) { // Finding joint positions spine[i].ikPosition = SolveFABRIKJoint(spine[i].ikPosition, spine[i + 1].ikPosition, spine[i].length); } } /* * Stage 2 of the FABRIK algorithm * */ private void BackwardReach(Vector3 position) { spine[0].ikPosition = position; // Finding joint positions for (int i = 1; i < spine.Length; i++) { spine[i].ikPosition = SolveFABRIKJoint(spine[i].ikPosition, spine[i - 1].ikPosition, spine[i - 1].length); } } /* * Positioning and rotating the spine bones to match the solver positions * */ private void MapToSolverPositions(IKSolverFullBody solver) { // Translating the first bone // Note: spine here also includes the pelvis spine[0].SetToIKPosition(); spine[0].RotateToPlane(solver, 1f); // Translating all the bones between the first and the last for (int i = 1; i < spine.Length - 1; i++) { spine[i].Swing(spine[i + 1].ikPosition, 1f); if (twistWeight > 0) { float bWeight = (float)i / ((float)spine.Length - 2); Vector3 s1 = solver.GetNode(leftUpperArm.chainIndex, leftUpperArm.nodeIndex).solverPosition; Vector3 s2 = solver.GetNode(rightUpperArm.chainIndex, rightUpperArm.nodeIndex).solverPosition; spine[i].Twist(s1 - s2, spine[i + 1].ikPosition - spine[i].transform.position, bWeight * twistWeight); } } // Translating the last bone spine[spine.Length - 1].SetToIKPosition(); spine[spine.Length - 1].RotateToPlane(solver, 1f); } } }