using UnityEngine; using System.Collections; namespace RootMotion.FinalIK { /// /// Generic FBIK solver. In each solver update, %IKSolverFullBody first reads the character's pose, then solves the %IK and writes the solved pose back to the character via IKMapping. /// [System.Serializable] public class IKSolverFullBody : IKSolver { #region Main Interface /// /// Number of solver iterations. /// [Range(0, 10)] public int iterations = 4; /// /// The root node chain. /// public FBIKChain[] chain = new FBIKChain[0]; /// /// The effectors. /// public IKEffector[] effectors = new IKEffector[0]; /// /// Mapping spine bones to the solver. /// public IKMappingSpine spineMapping = new IKMappingSpine(); /// /// Mapping individual bones to the solver /// public IKMappingBone[] boneMappings = new IKMappingBone[0]; /// /// Mapping 3 segment limbs to the solver /// public IKMappingLimb[] limbMappings = new IKMappingLimb[0]; /// /// If false, will not solve a FABRIK pass and the arms/legs will not be able to pull the body. /// public bool FABRIKPass = true; /// /// Gets the effector of the specified Transform. /// public IKEffector GetEffector(Transform t) { for (int i = 0; i < effectors.Length; i++) if (effectors[i].bone == t) return effectors[i]; return null; } /// /// Gets the chain that contains the specified Transform. /// public FBIKChain GetChain(Transform transform) { int index = GetChainIndex(transform); if (index == -1) return null; return chain[index]; } /// /// Gets the index of the chain (in the IKSolverFullBody.chain array) that contains the specified Transform. /// public int GetChainIndex(Transform transform) { for (int i = 0; i < chain.Length; i++) { for (int n = 0; n < chain[i].nodes.Length; n++) if (chain[i].nodes[n].transform == transform) return i; } return -1; } public IKSolver.Node GetNode(int chainIndex, int nodeIndex) { return chain[chainIndex].nodes[nodeIndex]; } public void GetChainAndNodeIndexes(Transform transform, out int chainIndex, out int nodeIndex) { chainIndex = GetChainIndex(transform); if (chainIndex == -1) nodeIndex = -1; else nodeIndex = chain[chainIndex].GetNodeIndex(transform); } public override IKSolver.Point[] GetPoints() { int nodes = 0; for (int i = 0; i < chain.Length; i++) nodes += chain[i].nodes.Length; IKSolver.Point[] pointArray = new IKSolver.Point[nodes]; int added = 0; for (int i = 0; i < chain.Length; i++) { for (int n = 0; n < chain[i].nodes.Length; n++) { pointArray[added] = chain[i].nodes[n] as IKSolver.Node; added++; } } return pointArray; } public override IKSolver.Point GetPoint(Transform transform) { for (int i = 0; i < chain.Length; i++) { for (int n = 0; n < chain[i].nodes.Length; n++) if (chain[i].nodes[n].transform == transform) return chain[i].nodes[n] as IKSolver.Point; } return null; } public override bool IsValid(ref string message) { if (chain == null) { message = "FBIK chain is null, can't initiate solver."; return false; } if (chain.Length == 0) { message = "FBIK chain length is 0, can't initiate solver."; return false; } for (int i = 0; i < chain.Length; i++) { if (!chain[i].IsValid(ref message)) return false; } foreach (IKEffector e in effectors) if (!e.IsValid(this, ref message)) return false; if (!spineMapping.IsValid(this, ref message)) return false; foreach (IKMappingLimb l in limbMappings) if (!l.IsValid(this, ref message)) return false; foreach (IKMappingBone b in boneMappings) if (!b.IsValid(this, ref message)) return false; return true; } /// /// Called before reading the pose /// public UpdateDelegate OnPreRead; /// /// Called before solving. /// public UpdateDelegate OnPreSolve; /// /// Called before each iteration /// public IterationDelegate OnPreIteration; /// /// Called after each iteration /// public IterationDelegate OnPostIteration; /// /// Called before applying bend constraints. /// public UpdateDelegate OnPreBend; /// /// Called after updating the solver /// public UpdateDelegate OnPostSolve; /// /// Called when storing default local state (the state that FixTransforms will reset the hierarchy to). /// public UpdateDelegate OnStoreDefaultLocalState; /// /// Called when the bones used by the solver will reset to the default local state. /// public UpdateDelegate OnFixTransforms; #endregion Main Interface public override void StoreDefaultLocalState() { spineMapping.StoreDefaultLocalState(); for (int i = 0; i < limbMappings.Length; i++) limbMappings[i].StoreDefaultLocalState(); for (int i = 0; i < boneMappings.Length; i++) boneMappings[i].StoreDefaultLocalState(); if (OnStoreDefaultLocalState != null) OnStoreDefaultLocalState(); } public override void FixTransforms() { if (!initiated) return; if (IKPositionWeight <= 0f) return; spineMapping.FixTransforms(); for (int i = 0; i < limbMappings.Length; i++) limbMappings[i].FixTransforms(); for (int i = 0; i < boneMappings.Length; i++) boneMappings[i].FixTransforms(); if (OnFixTransforms != null) OnFixTransforms(); } protected override void OnInitiate() { // Initiate chain for (int i = 0; i < chain.Length; i++) { chain[i].Initiate(this); } // Initiate effectors foreach (IKEffector e in effectors) e.Initiate(this); // Initiate IK mapping spineMapping.Initiate(this); foreach (IKMappingBone boneMapping in boneMappings) boneMapping.Initiate(this); foreach (IKMappingLimb limbMapping in limbMappings) limbMapping.Initiate(this); } protected override void OnUpdate() { if (IKPositionWeight <= 0) { // clear effector positionOffsets so they would not accumulate for (int i = 0; i < effectors.Length; i++) effectors[i].positionOffset = Vector3.zero; return; } if (chain.Length == 0) return; IKPositionWeight = Mathf.Clamp(IKPositionWeight, 0f, 1f); if (OnPreRead != null) OnPreRead(); // Phase 1: Read the pose of the biped ReadPose(); if (OnPreSolve != null) OnPreSolve(); // Phase 2: Solve IK Solve(); if (OnPostSolve != null) OnPostSolve(); // Phase 3: Map biped to its solved state WritePose(); // Reset effector position offsets to Vector3.zero for (int i = 0; i < effectors.Length; i++) effectors[i].OnPostWrite(); } protected virtual void ReadPose() { // Making sure the limbs are not inverted for (int i = 0; i < chain.Length; i++) { if (chain[i].bendConstraint.initiated) chain[i].bendConstraint.LimitBend(IKPositionWeight, GetEffector(chain[i].nodes[2].transform).positionWeight); } // Presolve effectors, apply effector offset to the nodes for (int i = 0; i < effectors.Length; i++) effectors[i].ResetOffset(this); for (int i = 0; i < effectors.Length; i++) effectors[i].OnPreSolve(this); // Set solver positions to match the current bone positions of the biped for (int i = 0; i < chain.Length; i++) { chain[i].ReadPose(this, iterations > 0); } // IKMapping if (iterations > 0) { spineMapping.ReadPose(); for (int i = 0; i < boneMappings.Length; i++) boneMappings[i].ReadPose(); } for (int i = 0; i < limbMappings.Length; i++) limbMappings[i].ReadPose(); } protected virtual void Solve() { // Iterate solver if(iterations > 0) { for (int i = 0; i < (FABRIKPass? iterations: 1); i++) { if (OnPreIteration != null) OnPreIteration(i); // Apply end-effectors for (int e = 0; e < effectors.Length; e++) if (effectors[e].isEndEffector) effectors[e].Update(this); if (FABRIKPass) { // Reaching chain[0].Push(this); // Reaching if (FABRIKPass) chain[0].Reach(this); // Apply non end-effectors for (int e = 0; e < effectors.Length; e++) if (!effectors[e].isEndEffector) effectors[e].Update(this); } // Trigonometric pass to release push tension from the solver chain[0].SolveTrigonometric(this); if (FABRIKPass) { // Solving FABRIK forward chain[0].Stage1(this); // Apply non end-effectors again for (int e = 0; e < effectors.Length; e++) if (!effectors[e].isEndEffector) effectors[e].Update(this); // Solving FABRIK backwards chain[0].Stage2(this, chain[0].nodes[0].solverPosition); } if (OnPostIteration != null) OnPostIteration(i); } } // Before applying bend constraints (last chance to modify the bend direction) if (OnPreBend != null) OnPreBend(); // Final end-effector pass for (int i = 0; i < effectors.Length; i++) if (effectors[i].isEndEffector) effectors[i].Update(this); ApplyBendConstraints(); } protected virtual void ApplyBendConstraints() { // Solve bend constraints chain[0].SolveTrigonometric(this, true); } protected virtual void WritePose() { if (IKPositionWeight <= 0f) return; // Apply IK mapping if (iterations > 0) { spineMapping.WritePose(this); for (int i = 0; i < boneMappings.Length; i++) boneMappings[i].WritePose(IKPositionWeight); } for (int i = 0; i < limbMappings.Length; i++) limbMappings[i].WritePose(this, iterations > 0); } } }