using UnityEngine; using System.Collections; namespace RootMotion.FinalIK { /// /// The base abstract class for all %IK solvers /// [System.Serializable] public abstract class IKSolver { #region Main Interface /// /// Determines whether this instance is valid or not. /// public bool IsValid() { string message = string.Empty; return IsValid(ref message); } /// /// Determines whether this instance is valid or not. If returns false, also fills in an error message. /// public abstract bool IsValid(ref string message); /// /// Initiate the solver with specified root Transform. Use only if this %IKSolver is not a member of an %IK component. /// public void Initiate(Transform root) { if (OnPreInitiate != null) OnPreInitiate(); if (root == null) Debug.LogError("Initiating IKSolver with null root Transform."); this.root = root; initiated = false; string message = string.Empty; if (!IsValid(ref message)) { Warning.Log(message, root, false); return; } OnInitiate(); StoreDefaultLocalState(); initiated = true; firstInitiation = false; if (OnPostInitiate != null) OnPostInitiate(); } /// /// Updates the %IK solver. Use only if this %IKSolver is not a member of an %IK component or the %IK component has been disabled and you intend to manually control the updating. /// public void Update() { if (OnPreUpdate != null) OnPreUpdate(); if (firstInitiation) Initiate(root); // when the IK component has been disabled in Awake, this will initiate it. if (!initiated) return; OnUpdate(); if (OnPostUpdate != null) OnPostUpdate(); } /// /// The %IK position. /// [HideInInspector] public Vector3 IKPosition; [Tooltip("The positional or the master weight of the solver.")] /// /// The %IK position weight or the master weight of the solver. /// [Range(0f, 1f)] public float IKPositionWeight = 1f; /// /// Gets the %IK position. NOTE: You are welcome to read IKPosition directly, this method is here only to match the Unity's built in %IK API. /// public virtual Vector3 GetIKPosition() { return IKPosition; } /// /// Sets the %IK position. NOTE: You are welcome to set IKPosition directly, this method is here only to match the Unity's built in %IK API. /// public void SetIKPosition(Vector3 position) { IKPosition = position; } /// /// Gets the %IK position weight. NOTE: You are welcome to read IKPositionWeight directly, this method is here only to match the Unity's built in %IK API. /// public float GetIKPositionWeight() { return IKPositionWeight; } /// /// Sets the %IK position weight. NOTE: You are welcome to set IKPositionWeight directly, this method is here only to match the Unity's built in %IK API. /// public void SetIKPositionWeight(float weight) { IKPositionWeight = Mathf.Clamp(weight, 0f, 1f); } /// /// Gets the root Transform. /// public Transform GetRoot() { return root; } /// /// Gets a value indicating whether this has successfully initiated. /// public bool initiated { get; private set; } /// /// Gets all the points used by the solver. /// public abstract IKSolver.Point[] GetPoints(); /// /// Gets the point with the specified Transform. /// public abstract IKSolver.Point GetPoint(Transform transform); /// /// Fixes all the Transforms used by the solver to their initial state. /// public abstract void FixTransforms(); /// /// Stores the default local state for the bones used by the solver. /// public abstract void StoreDefaultLocalState(); /// /// The most basic element type in the %IK chain that all other types extend from. /// [System.Serializable] public class Point { /// /// The transform. /// public Transform transform; /// /// The weight of this bone in the solver. /// [Range(0f, 1f)] public float weight = 1f; /// /// Virtual position in the %IK solver. /// public Vector3 solverPosition; /// /// Virtual rotation in the %IK solver. /// public Quaternion solverRotation = Quaternion.identity; /// /// The default local position of the Transform. /// public Vector3 defaultLocalPosition; /// /// The default local rotation of the Transform. /// public Quaternion defaultLocalRotation; /// /// Stores the default local state of the point. /// public void StoreDefaultLocalState() { defaultLocalPosition = transform.localPosition; defaultLocalRotation = transform.localRotation; } /// /// Fixes the transform to it's default local state. /// public void FixTransform() { if (transform.localPosition != defaultLocalPosition) transform.localPosition = defaultLocalPosition; if (transform.localRotation != defaultLocalRotation) transform.localRotation = defaultLocalRotation; } /// /// Updates the solverPosition (in world space). /// public void UpdateSolverPosition() { solverPosition = transform.position; } /// /// Updates the solverPosition (in local space). /// public void UpdateSolverLocalPosition() { solverPosition = transform.localPosition; } /// /// Updates the solverPosition/Rotation (in world space). /// public void UpdateSolverState() { solverPosition = transform.position; solverRotation = transform.rotation; } /// /// Updates the solverPosition/Rotation (in local space). /// public void UpdateSolverLocalState() { solverPosition = transform.localPosition; solverRotation = transform.localRotation; } } /// /// %Bone type of element in the %IK chain. Used in the case of skeletal Transform hierarchies. /// [System.Serializable] public class Bone: Point { /// /// The length of the bone. /// public float length; /// /// The sqr mag of the bone. /// public float sqrMag; /// /// Local axis to target/child bone. /// public Vector3 axis = -Vector3.right; /// /// Gets the rotation limit component from the Transform if there is any. /// public RotationLimit rotationLimit { get { if (!isLimited) return null; if (_rotationLimit == null) _rotationLimit = transform.GetComponent(); isLimited = _rotationLimit != null; return _rotationLimit; } set { _rotationLimit = value; isLimited = value != null; } } /* * Swings the Transform's axis towards the swing target * */ public void Swing(Vector3 swingTarget, float weight = 1f) { if (weight <= 0f) return; Quaternion r = Quaternion.FromToRotation(transform.rotation * axis, swingTarget - transform.position); if (weight >= 1f) { transform.rotation = r * transform.rotation; return; } transform.rotation = Quaternion.Lerp(Quaternion.identity, r, weight) * transform.rotation; } public static void SolverSwing(Bone[] bones, int index, Vector3 swingTarget, float weight = 1f) { if (weight <= 0f) return; Quaternion r = Quaternion.FromToRotation(bones[index].solverRotation * bones[index].axis, swingTarget - bones[index].solverPosition); if (weight >= 1f) { for (int i = index; i < bones.Length; i++) { bones[i].solverRotation = r * bones[i].solverRotation; } return; } for (int i = index; i < bones.Length; i++) { bones[i].solverRotation = Quaternion.Lerp(Quaternion.identity, r, weight) * bones[i].solverRotation; } } /* * Swings the Transform's axis towards the swing target on the XY plane only * */ public void Swing2D(Vector3 swingTarget, float weight = 1f) { if (weight <= 0f) return; Vector3 from = transform.rotation * axis; Vector3 to = swingTarget - transform.position; float angleFrom = Mathf.Atan2(from.x, from.y) * Mathf.Rad2Deg; float angleTo = Mathf.Atan2(to.x, to.y) * Mathf.Rad2Deg; transform.rotation = Quaternion.AngleAxis(Mathf.DeltaAngle(angleFrom, angleTo) * weight, Vector3.back) * transform.rotation; } /* * Moves the bone to the solver position * */ public void SetToSolverPosition() { transform.position = solverPosition; } public Bone() {} public Bone (Transform transform) { this.transform = transform; } public Bone (Transform transform, float weight) { this.transform = transform; this.weight = weight; } private RotationLimit _rotationLimit; private bool isLimited = true; } /// /// %Node type of element in the %IK chain. Used in the case of mixed/non-hierarchical %IK systems /// [System.Serializable] public class Node: Point { /// /// Distance to child node. /// public float length; /// /// The effector position weight. /// public float effectorPositionWeight; /// /// The effector rotation weight. /// public float effectorRotationWeight; /// /// Position offset. /// public Vector3 offset; public Node() {} public Node (Transform transform) { this.transform = transform; } public Node (Transform transform, float weight) { this.transform = transform; this.weight = weight; } } /// /// Delegates solver update events. /// public delegate void UpdateDelegate(); /// /// Delegates solver iteration events. /// public delegate void IterationDelegate(int i); /// /// Called before initiating the solver. /// public UpdateDelegate OnPreInitiate; /// /// Called after initiating the solver. /// public UpdateDelegate OnPostInitiate; /// /// Called before updating. /// public UpdateDelegate OnPreUpdate; /// /// Called after writing the solved pose /// public UpdateDelegate OnPostUpdate; #endregion Main Interface protected abstract void OnInitiate(); protected abstract void OnUpdate(); protected bool firstInitiation = true; [SerializeField][HideInInspector] protected Transform root; protected void LogWarning(string message) { Warning.Log(message, root, true); } #region Class Methods /// /// Checks if an array of objects contains any duplicates. /// public static Transform ContainsDuplicateBone(Bone[] bones) { for (int i = 0; i < bones.Length; i++) { for (int i2 = 0; i2 < bones.Length; i2++) { if (i != i2 && bones[i].transform == bones[i2].transform) return bones[i].transform; } } return null; } /* * Make sure the bones are in valid Hierarchy * */ public static bool HierarchyIsValid(IKSolver.Bone[] bones) { for (int i = 1; i < bones.Length; i++) { // If parent bone is not an ancestor of bone, the hierarchy is invalid if (!Hierarchy.IsAncestor(bones[i].transform, bones[i - 1].transform)) { return false; } } return true; } // Calculates bone lengths and axes, returns the length of the entire chain protected static float PreSolveBones(ref Bone[] bones) { float length = 0; for (int i = 0; i < bones.Length; i++) { bones[i].solverPosition = bones[i].transform.position; bones[i].solverRotation = bones[i].transform.rotation; } for (int i = 0; i < bones.Length; i++) { if (i < bones.Length - 1) { bones[i].sqrMag = (bones[i + 1].solverPosition - bones[i].solverPosition).sqrMagnitude; bones[i].length = Mathf.Sqrt(bones[i].sqrMag); length += bones[i].length; bones[i].axis = Quaternion.Inverse(bones[i].solverRotation) * (bones[i + 1].solverPosition - bones[i].solverPosition); } else { bones[i].sqrMag = 0f; bones[i].length = 0f; } } return length; } #endregion Class Methods } }