using System; using System.Collections.Generic; using UnityEngine.Scripting.APIUpdating; using UnityEngine.Serialization; namespace UnityEngine.U2D.IK { /// <summary> /// Abstract class for implementing a 2D IK Solver. /// </summary> [MovedFrom("UnityEngine.Experimental.U2D.IK")] public abstract class Solver2D : MonoBehaviour { [SerializeField] bool m_ConstrainRotation = true; [FormerlySerializedAs("m_RestoreDefaultPose")] [SerializeField] bool m_SolveFromDefaultPose = true; [SerializeField] [Range(0f, 1f)] float m_Weight = 1f; Plane m_Plane; List<Vector3> m_TargetPositions = new List<Vector3>(); /// <summary> /// Used to evaluate if Solver2D needs to be updated. /// </summary> float m_LastFinalWeight; /// <summary> /// Returns the number of IKChain2D in the solver. /// </summary> public int chainCount => GetChainCount(); /// <summary> /// Get Set for rotation constrain property. /// </summary> public bool constrainRotation { get => m_ConstrainRotation; set => m_ConstrainRotation = value; } /// <summary> /// Get Set for restoring default pose. /// </summary> public bool solveFromDefaultPose { get => m_SolveFromDefaultPose; set => m_SolveFromDefaultPose = value; } /// <summary> /// Returns true if the Solver2D is in a valid state. /// </summary> public bool isValid => Validate(); /// <summary> /// Returns true if all chains in the Solver has a target. /// </summary> public bool allChainsHaveTargets => HasTargets(); /// <summary> /// Get and Set Solver weights. /// </summary> public float weight { get => m_Weight; set => m_Weight = Mathf.Clamp01(value); } /// <summary> /// Validate and initialize the Solver. /// </summary> protected virtual void OnValidate() { m_Weight = Mathf.Clamp01(m_Weight); if (!isValid) Initialize(); } bool Validate() { for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); if (!chain.isValid) return false; } return DoValidate(); } bool HasTargets() { for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); if (chain.target == null) return false; } return true; } /// <summary> /// Initializes the solver. /// </summary> public void Initialize() { DoInitialize(); for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); chain.Initialize(); } } void Prepare() { var rootTransform = GetPlaneRootTransform(); if (rootTransform != null) { m_Plane.normal = rootTransform.forward; m_Plane.distance = -Vector3.Dot(m_Plane.normal, rootTransform.position); } for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); var constrainTargetRotation = constrainRotation && chain.target != null; if (m_SolveFromDefaultPose) chain.RestoreDefaultPose(constrainTargetRotation); } DoPrepare(); } void PrepareEffectorPositions() { m_TargetPositions.Clear(); for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); if (chain.target) m_TargetPositions.Add(chain.target.position); } } /// <summary> /// Perform Solver IK update. /// </summary> /// <param name="globalWeight">Weight for position solving.</param> public void UpdateIK(float globalWeight) { if (allChainsHaveTargets) { PrepareEffectorPositions(); UpdateIK(m_TargetPositions, globalWeight); } } /// <summary> /// Perform Solver IK update. /// </summary> /// <param name="positions">Positions of chain.</param> /// <param name="globalWeight">Weight for position solving.</param> public void UpdateIK(List<Vector3> positions, float globalWeight) { if (positions.Count != chainCount) return; var finalWeight = globalWeight * weight; var weightValueChanged = Math.Abs(finalWeight - m_LastFinalWeight) > 0.0001f; m_LastFinalWeight = finalWeight; if (finalWeight == 0f && !weightValueChanged) return; if (!isValid) return; Prepare(); if (finalWeight < 1f) StoreLocalRotations(); DoUpdateIK(positions); if (constrainRotation) { for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); if (chain.target) chain.effector.rotation = chain.target.rotation; } } if (finalWeight < 1f) BlendFkToIk(finalWeight); } void StoreLocalRotations() { for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); chain.StoreLocalRotations(); } } void BlendFkToIk(float finalWeight) { for (var i = 0; i < GetChainCount(); ++i) { var chain = GetChain(i); var constrainTargetRotation = constrainRotation && chain.target != null; chain.BlendFkToIk(finalWeight, constrainTargetRotation); } } /// <summary> /// Override to return the IKChain2D at the given index. /// </summary> /// <param name="index">Index for IKChain2D.</param> /// <returns></returns> public abstract IKChain2D GetChain(int index); /// <summary> /// Override to return the number of chains in the Solver /// </summary> /// <returns>Integer represents IKChain2D count.</returns> protected abstract int GetChainCount(); /// <summary> /// Override to perform Solver IK update /// </summary> /// <param name="effectorPositions">Position of the effectors.</param> protected abstract void DoUpdateIK(List<Vector3> effectorPositions); /// <summary> /// Override to perform custom validation. /// </summary> /// <returns>Returns true if the Solver is in a valid state. False otherwise.</returns> protected virtual bool DoValidate() => true; /// <summary> /// Override to perform initialize the solver /// </summary> protected virtual void DoInitialize() { } /// <summary> /// Override to prepare the solver for update /// </summary> protected virtual void DoPrepare() { } /// <summary> /// Override to return the root Unity Transform of the Solver. The default implementation returns the root /// transform of the first chain. /// </summary> /// <returns>Unity Transform that represents the root.</returns> protected virtual Transform GetPlaneRootTransform() { return chainCount > 0 ? GetChain(0).rootTransform : null; } /// <summary> /// Convert a world position coordinate to the solver's plane space /// </summary> /// <param name="worldPosition">Vector3 representing world position</param> /// <returns>Converted position in solver's plane</returns> protected Vector3 GetPointOnSolverPlane(Vector3 worldPosition) { return GetPlaneRootTransform().InverseTransformPoint(m_Plane.ClosestPointOnPlane(worldPosition)); } /// <summary> /// Convert a position from solver's plane to world coordinate /// </summary> /// <param name="planePoint">Vector3 representing a position in the Solver's plane.</param> /// <returns>Converted position to world coordinate.</returns> protected Vector3 GetWorldPositionFromSolverPlanePoint(Vector2 planePoint) { return GetPlaneRootTransform().TransformPoint(planePoint); } } }