This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.2D.IK.Editor")]

View File

@@ -0,0 +1,114 @@
using System.Collections.Generic;
using UnityEngine.Profiling;
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.IK
{
/// <summary>
/// Component for 2D Cyclic Coordinate Descent (CCD) IK.
/// </summary>
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
[Solver2DMenuAttribute("Chain (CCD)")]
public class CCDSolver2D : Solver2D
{
private const float kMinTolerance = 0.001f;
private const int kMinIterations = 1;
private const float kMinVelocity = 0.01f;
private const float kMaxVelocity = 1f;
[SerializeField]
private IKChain2D m_Chain = new IKChain2D();
[SerializeField][Range(kMinIterations, 50)]
private int m_Iterations = 10;
[SerializeField][Range(kMinTolerance, 0.1f)]
private float m_Tolerance = 0.01f;
[SerializeField][Range(0f, 1f)]
private float m_Velocity = 0.5f;
private Vector3[] m_Positions;
/// <summary>
/// Get and Set the solver's itegration count.
/// </summary>
public int iterations
{
get { return m_Iterations; }
set { m_Iterations = Mathf.Max(value, kMinIterations); }
}
/// <summary>
/// Get and Set target distance tolerance.
/// </summary>
public float tolerance
{
get { return m_Tolerance; }
set { m_Tolerance = Mathf.Max(value, kMinTolerance); }
}
/// <summary>
/// Get and Set the solver velocity.
/// </summary>
public float velocity
{
get { return m_Velocity; }
set { m_Velocity = Mathf.Clamp01(value); }
}
/// <summary>
/// Returns the number of chain in the solver.
/// </summary>
/// <returns>This always returns 1</returns>
protected override int GetChainCount()
{
return 1;
}
/// <summary>
/// Gets the chain in the solver by index.
/// </summary>
/// <param name="index">Chain index.</param>
/// <returns>Returns IKChain2D at the index.</returns>
public override IKChain2D GetChain(int index)
{
return m_Chain;
}
/// <summary>
/// DoPrepare override from base class.
/// </summary>
protected override void DoPrepare()
{
if (m_Positions == null || m_Positions.Length != m_Chain.transformCount)
m_Positions = new Vector3[m_Chain.transformCount];
for (int i = 0; i < m_Chain.transformCount; ++i)
m_Positions[i] = m_Chain.transforms[i].position;
}
/// <summary>
/// DoUpdateIK override from base class.
/// </summary>
/// <param name="effectorPositions">Target position for the chain.</param>
protected override void DoUpdateIK(List<Vector3> effectorPositions)
{
Profiler.BeginSample("CCDSolver2D.DoUpdateIK");
Vector3 effectorPosition = effectorPositions[0];
Vector2 effectorLocalPosition2D = m_Chain.transforms[0].InverseTransformPoint(effectorPosition);
effectorPosition = m_Chain.transforms[0].TransformPoint(effectorLocalPosition2D);
if (CCD2D.Solve(effectorPosition, GetPlaneRootTransform().forward, iterations, tolerance, Mathf.Lerp(kMinVelocity, kMaxVelocity, m_Velocity), ref m_Positions))
{
for (int i = 0; i < m_Chain.transformCount - 1; ++i)
{
Vector3 startLocalPosition = m_Chain.transforms[i + 1].localPosition;
Vector3 endLocalPosition = m_Chain.transforms[i].InverseTransformPoint(m_Positions[i + 1]);
m_Chain.transforms[i].localRotation *= Quaternion.FromToRotation(startLocalPosition, endLocalPosition);
}
}
Profiler.EndSample();
}
}
}

View File

@@ -0,0 +1,116 @@
using System.Collections.Generic;
using UnityEngine.Profiling;
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.IK
{
/// <summary>
/// Component for 2D Forward And Backward Reaching Inverse Kinematics (FABRIK) IK.
/// </summary>
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
[Solver2DMenu("Chain (FABRIK)")]
public class FabrikSolver2D : Solver2D
{
private const float kMinTolerance = 0.001f;
private const int kMinIterations = 1;
[SerializeField]
private IKChain2D m_Chain = new IKChain2D();
[SerializeField][Range(kMinIterations, 50)]
private int m_Iterations = 10;
[SerializeField][Range(kMinTolerance, 0.1f)]
private float m_Tolerance = 0.01f;
private float[] m_Lengths;
private Vector2[] m_Positions;
private Vector3[] m_WorldPositions;
/// <summary>
/// Get and Set the solver's itegration count.
/// </summary>
public int iterations
{
get { return m_Iterations; }
set { m_Iterations = Mathf.Max(value, kMinIterations); }
}
/// <summary>
/// Get and Set target distance tolerance.
/// </summary>
public float tolerance
{
get { return m_Tolerance; }
set { m_Tolerance = Mathf.Max(value, kMinTolerance); }
}
/// <summary>
/// Returns the number of chain in the solver.
/// </summary>
/// <returns>This always returns 1.</returns>
protected override int GetChainCount()
{
return 1;
}
/// <summary>
/// Gets the chain in the solver by index.
/// </summary>
/// <param name="index">Chain index.</param>
/// <returns>Returns IKChain2D at the index.</returns>
public override IKChain2D GetChain(int index)
{
return m_Chain;
}
/// <summary>
/// DoPrepare override from base class.
/// </summary>
protected override void DoPrepare()
{
if (m_Positions == null || m_Positions.Length != m_Chain.transformCount)
{
m_Positions = new Vector2[m_Chain.transformCount];
m_Lengths = new float[m_Chain.transformCount - 1];
m_WorldPositions = new Vector3[m_Chain.transformCount];
}
for (int i = 0; i < m_Chain.transformCount; ++i)
{
m_Positions[i] = GetPointOnSolverPlane(m_Chain.transforms[i].position);
}
for (int i = 0; i < m_Chain.transformCount - 1; ++i)
{
m_Lengths[i] = (m_Positions[i + 1] - m_Positions[i]).magnitude;
}
}
/// <summary>
/// DoUpdateIK override from base class.
/// </summary>
/// <param name="effectorPositions">Target position for the chain.</param>
protected override void DoUpdateIK(List<Vector3> effectorPositions)
{
Profiler.BeginSample("FABRIKSolver2D.DoUpdateIK");
Vector3 effectorPosition = effectorPositions[0];
effectorPosition = GetPointOnSolverPlane(effectorPosition);
if (FABRIK2D.Solve(effectorPosition, iterations, tolerance, m_Lengths, ref m_Positions))
{
// Convert all plane positions to world positions
for (int i = 0; i < m_Positions.Length; ++i)
{
m_WorldPositions[i] = GetWorldPositionFromSolverPlanePoint(m_Positions[i]);
}
for (int i = 0; i < m_Chain.transformCount - 1; ++i)
{
Vector3 startLocalPosition = m_Chain.transforms[i + 1].localPosition;
Vector3 endLocalPosition = m_Chain.transforms[i].InverseTransformPoint(m_WorldPositions[i + 1]);
m_Chain.transforms[i].localRotation *= Quaternion.FromToRotation(startLocalPosition, endLocalPosition);
}
}
Profiler.EndSample();
}
}
}

View File

@@ -0,0 +1,208 @@
using System;
using UnityEngine.Scripting.APIUpdating;
using UnityEngine.Serialization;
namespace UnityEngine.U2D.IK
{
/// <summary>
/// Class for storing data for a 2D IK Chain.
/// </summary>
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
[Serializable]
public class IKChain2D
{
[SerializeField][FormerlySerializedAs("m_Target")]
private Transform m_EffectorTransform;
[SerializeField][FormerlySerializedAs("m_Effector")]
private Transform m_TargetTransform;
[SerializeField]
private int m_TransformCount;
[SerializeField]
private Transform[] m_Transforms;
[SerializeField]
private Quaternion[] m_DefaultLocalRotations;
[SerializeField]
private Quaternion[] m_StoredLocalRotations;
protected float[] m_Lengths;
/// <summary>
/// Get Set the Unity Transform used as IK Effector.
/// </summary>
public Transform effector
{
get { return m_EffectorTransform; }
set { m_EffectorTransform = value; }
}
/// <summary>
/// Get Set the Unity Transform used as IK Target.
/// </summary>
public Transform target
{
get { return m_TargetTransform; }
set { m_TargetTransform = value; }
}
/// <summary>
/// Get the Unity Transforms that are in the IK Chain.
/// </summary>
public Transform[] transforms
{
get { return m_Transforms; }
}
/// <summary>
/// Get the root Unity Transform for the IK Chain.
/// </summary>
public Transform rootTransform
{
get
{
if (m_Transforms != null && transformCount > 0 && m_Transforms.Length == transformCount)
return m_Transforms[0];
return null;
}
}
private Transform lastTransform
{
get
{
if (m_Transforms != null && transformCount > 0 && m_Transforms.Length == transformCount)
return m_Transforms[transformCount - 1];
return null;
}
}
/// <summary>
/// Get and Set the number of Unity Transforms in the IK Chain.
/// </summary>
public int transformCount
{
get { return m_TransformCount; }
set { m_TransformCount = Mathf.Max(0, value); }
}
/// <summary>
/// Returns true if the IK Chain is valid. False otherwise.
/// </summary>
public bool isValid
{
get { return Validate(); }
}
/// <summary>
/// Gets the length of the IK Chain.
/// </summary>
public float[] lengths
{
get
{
if(isValid)
{
PrepareLengths();
return m_Lengths;
}
return null;
}
}
private bool Validate()
{
if (effector == null)
return false;
if (transformCount == 0)
return false;
if (m_Transforms == null || m_Transforms.Length != transformCount)
return false;
if (m_DefaultLocalRotations == null || m_DefaultLocalRotations.Length != transformCount)
return false;
if (m_StoredLocalRotations == null || m_StoredLocalRotations.Length != transformCount)
return false;
if (rootTransform == null)
return false;
if (lastTransform != effector)
return false;
if (target && IKUtility.IsDescendentOf(target, rootTransform))
return false;
return true;
}
/// <summary>
/// Initialize the IK Chain.
/// </summary>
public void Initialize()
{
if (effector == null || transformCount == 0 || IKUtility.GetAncestorCount(effector) < transformCount - 1)
return;
m_Transforms = new Transform[transformCount];
m_DefaultLocalRotations = new Quaternion[transformCount];
m_StoredLocalRotations = new Quaternion[transformCount];
var currentTransform = effector;
int index = transformCount - 1;
while (currentTransform && index >= 0)
{
m_Transforms[index] = currentTransform;
m_DefaultLocalRotations[index] = currentTransform.localRotation;
currentTransform = currentTransform.parent;
--index;
}
}
private void PrepareLengths()
{
var currentTransform = effector;
int index = transformCount - 1;
if (m_Lengths == null || m_Lengths.Length != transformCount - 1)
m_Lengths = new float[transformCount - 1];
while (currentTransform && index >= 0)
{
if (currentTransform.parent && index > 0)
m_Lengths[index - 1] = (currentTransform.position - currentTransform.parent.position).magnitude;
currentTransform = currentTransform.parent;
--index;
}
}
/// <summary>
/// Restores IK Chain to it's default pose.
/// </summary>
/// <param name="targetRotationIsConstrained">True to constrain the target rotation. False otherwise.</param>
public void RestoreDefaultPose(bool targetRotationIsConstrained)
{
var count = targetRotationIsConstrained ? transformCount : transformCount-1;
for (int i = 0; i < count; ++i)
m_Transforms[i].localRotation = m_DefaultLocalRotations[i];
}
/// <summary>
/// Explicitly stores the local rotation
/// </summary>
public void StoreLocalRotations()
{
for (int i = 0; i < m_Transforms.Length; ++i)
m_StoredLocalRotations[i] = m_Transforms[i].localRotation;
}
/// <summary>
/// Blend between Forward Kinematics and Inverse Kinematics.
/// </summary>
/// <param name="finalWeight">Weight for blend</param>
/// <param name="targetRotationIsConstrained">True to constrain target rotation. False otherwise.</param>
public void BlendFkToIk(float finalWeight, bool targetRotationIsConstrained)
{
var count = targetRotationIsConstrained ? transformCount : transformCount-1;
for (int i = 0; i < count; ++i)
m_Transforms[i].localRotation = Quaternion.Slerp(m_StoredLocalRotations[i], m_Transforms[i].localRotation, finalWeight);
}
}
}

View File

@@ -0,0 +1,31 @@
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Scripting.APIUpdating;
#if UNITY_EDITOR
namespace UnityEditor.U2D.IK
{
[DefaultExecutionOrder(-2)]
[ExecuteInEditMode]
[AddComponentMenu("")]
[MovedFrom("UnityEditor.Experimental.U2D.IK")]
internal class IKEditorManagerHelper : MonoBehaviour
{
public UnityEvent onLateUpdate = new UnityEvent();
void Start()
{
if(hideFlags != HideFlags.HideAndDontSave)
Debug.LogWarning("This is an internal IK Component. Please remove it from your GameObject", this.gameObject);
}
void LateUpdate()
{
if (Application.isPlaying)
return;
onLateUpdate.Invoke();
}
}
}
#endif

View File

@@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.IK
{
/// <summary>
/// Component to manager 2D IK Solvers.
/// </summary>
[DefaultExecutionOrder(-2)]
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
public partial class IKManager2D : MonoBehaviour
{
[SerializeField]
private List<Solver2D> m_Solvers = new List<Solver2D>();
[SerializeField][Range(0f, 1f)]
private float m_Weight = 1f;
/// <summary>
/// Get and Set the weight for solvers.
/// </summary>
public float weight
{
get { return m_Weight; }
set { m_Weight = Mathf.Clamp01(value); }
}
/// <summary>
/// Get the Solvers that are managed by this manager.
/// </summary>
public List<Solver2D> solvers
{
get { return m_Solvers; }
}
private void OnValidate()
{
m_Weight = Mathf.Clamp01(m_Weight);
OnEditorDataValidate();
}
private void OnEnable()
{
}
private void Reset()
{
FindChildSolvers();
OnEditorDataValidate();
}
private void FindChildSolvers()
{
m_Solvers.Clear();
List<Solver2D> solvers = new List<Solver2D>();
transform.GetComponentsInChildren<Solver2D>(true, solvers);
foreach (Solver2D solver in solvers)
{
if (solver.GetComponentInParent<IKManager2D>() == this)
AddSolver(solver);
}
}
/// <summary>
/// Add Solver to the manager.
/// </summary>
/// <param name="solver">Solver to add.</param>
public void AddSolver(Solver2D solver)
{
if (!m_Solvers.Contains(solver))
{
m_Solvers.Add(solver);
AddSolverEditorData();
}
}
/// <summary>
/// Remove Solver from the manager.
/// </summary>
/// <param name="solver">Solver to remove.</param>
public void RemoveSolver(Solver2D solver)
{
RemoveSolverEditorData(solver);
m_Solvers.Remove(solver);
}
/// <summary>
/// Updates the Solvers in this manager.
/// </summary>
public void UpdateManager()
{
foreach (var solver in m_Solvers)
{
if (solver == null || !solver.isActiveAndEnabled)
continue;
if (!solver.isValid)
solver.Initialize();
solver.UpdateIK(weight);
}
}
private void LateUpdate()
{
UpdateManager();
}
#if UNITY_EDITOR
internal static Events.UnityEvent onDrawGizmos = new Events.UnityEvent();
private void OnDrawGizmos() { onDrawGizmos.Invoke(); }
#endif
}
}

View File

@@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
namespace UnityEngine.U2D.IK
{
public partial class IKManager2D : MonoBehaviour
{
#if UNITY_EDITOR
[Serializable]
internal struct SolverEditorData
{
public Color color;
public bool showGizmo;
public static SolverEditorData defaultValue
{
get
{
return new SolverEditorData(){ color = Color.green, showGizmo = true};
}
}
}
[SerializeField]
private List<SolverEditorData> m_SolverEditorData = new List<SolverEditorData>();
void OnEditorDataValidate()
{
var solverDataLength = m_SolverEditorData.Count;
for (int i = solverDataLength; i < m_Solvers.Count; ++i)
{
AddSolverEditorData();
}
}
internal SolverEditorData GetSolverEditorData(Solver2D solver)
{
var index = m_Solvers.FindIndex(x => x == solver);
if (index >= 0)
{
if(index >= m_SolverEditorData.Count)
OnEditorDataValidate();
return m_SolverEditorData[index];
}
return SolverEditorData.defaultValue;
}
void AddSolverEditorData()
{
m_SolverEditorData.Add(new SolverEditorData()
{
color = Color.green,
showGizmo = true
});
}
void RemoveSolverEditorData(Solver2D solver)
{
var index = m_Solvers.FindIndex(x => x == solver);
if(index >= 0)
m_SolverEditorData.RemoveAt(index);
}
#else
void OnEditorDataValidate(){}
void AddSolverEditorData(){}
void RemoveSolverEditorData(Solver2D solver){}
#endif
}
}

View File

@@ -0,0 +1,70 @@
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.IK
{
/// <summary>
/// General utilities for 2D IK.
/// </summary>
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
public class IKUtility
{
/// <summary>
/// Check if a Unity Transform is a descendent of another Unity Transform.
/// </summary>
/// <param name="transform">Unity Transform to check.</param>
/// <param name="ancestor">Unity Transform ancestor.</param>
/// <returns>Returns true if the Unity Transform is a descendent. False otherwise.</returns>
public static bool IsDescendentOf(Transform transform, Transform ancestor)
{
Debug.Assert(transform != null, "Transform is null");
Transform currentParent = transform.parent;
while (currentParent)
{
if (currentParent == ancestor)
return true;
currentParent = currentParent.parent;
}
return false;
}
/// <summary>
/// Gets the hierarchy depth of a Unity Transform.
/// </summary>
/// <param name="transform">Unity Transform to check.</param>
/// <returns>Integer value for hierarchy depth.</returns>
public static int GetAncestorCount(Transform transform)
{
Debug.Assert(transform != null, "Transform is null");
int ancestorCount = 0;
while (transform.parent)
{
++ancestorCount;
transform = transform.parent;
}
return ancestorCount;
}
/// <summary>
/// Gets the maximum chain count for a IKChain2D.
/// </summary>
/// <param name="chain">IKChain2D to query.</param>
/// <returns>Integer value for the maximum chain count.</returns>
public static int GetMaxChainCount(IKChain2D chain)
{
int maxChainCount = 0;
if (chain.effector)
maxChainCount = GetAncestorCount(chain.effector) + 1;
return maxChainCount;
}
}
}

View File

@@ -0,0 +1,91 @@
using System.Collections.Generic;
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.IK
{
/// <summary>
/// Component for 2D Limb IK.
/// </summary>
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
[Solver2DMenuAttribute("Limb")]
public class LimbSolver2D : Solver2D
{
[SerializeField]
private IKChain2D m_Chain = new IKChain2D();
[SerializeField]
private bool m_Flip;
private Vector3[] m_Positions = new Vector3[3];
private float[] m_Lengths = new float[2];
private float[] m_Angles = new float[2];
/// <summary>
/// Get Set for flip property.
/// </summary>
public bool flip
{
get { return m_Flip; }
set { m_Flip = value; }
}
/// <summary>
/// Override base class DoInitialize.
/// </summary>
protected override void DoInitialize()
{
m_Chain.transformCount = m_Chain.effector == null || IKUtility.GetAncestorCount(m_Chain.effector) < 2 ? 0 : 3;
base.DoInitialize();
}
/// <summary>
/// Override base class GetChainCount.
/// </summary>
/// <returns>Always returns 1.</returns>
protected override int GetChainCount()
{
return 1;
}
/// <summary>
/// Override base class GetChain.
/// </summary>
/// <param name="index">Index to query.</param>
/// <returns>Returns IKChain2D for the Solver.</returns>
public override IKChain2D GetChain(int index)
{
return m_Chain;
}
/// <summary>
/// Override base class DoPrepare.
/// </summary>
protected override void DoPrepare()
{
var lengths = m_Chain.lengths;
m_Positions[0] = m_Chain.transforms[0].position;
m_Positions[1] = m_Chain.transforms[1].position;
m_Positions[2] = m_Chain.transforms[2].position;
m_Lengths[0] = lengths[0];
m_Lengths[1] = lengths[1];
}
/// <summary>
/// OVerride base class DoUpdateIK.
/// </summary>
/// <param name="effectorPositions">List of effector positions.</param>
protected override void DoUpdateIK(List<Vector3> effectorPositions)
{
Vector3 effectorPosition = effectorPositions[0];
Vector2 effectorLocalPosition2D = m_Chain.transforms[0].InverseTransformPoint(effectorPosition);
effectorPosition = m_Chain.transforms[0].TransformPoint(effectorLocalPosition2D);
if (effectorLocalPosition2D.sqrMagnitude > 0f && Limb.Solve(effectorPosition, m_Lengths, m_Positions, ref m_Angles))
{
float flipSign = flip ? -1f : 1f;
m_Chain.transforms[0].localRotation *= Quaternion.FromToRotation(Vector3.right, effectorLocalPosition2D) * Quaternion.FromToRotation(m_Chain.transforms[1].localPosition, Vector3.right);
m_Chain.transforms[0].localRotation *= Quaternion.AngleAxis(flipSign * m_Angles[0], Vector3.forward);
m_Chain.transforms[1].localRotation *= Quaternion.FromToRotation(Vector3.right, m_Chain.transforms[1].InverseTransformPoint(effectorPosition)) * Quaternion.FromToRotation(m_Chain.transforms[2].localPosition, Vector3.right);
}
}
}
}

View File

@@ -0,0 +1,297 @@
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]
private bool m_ConstrainRotation = true;
[FormerlySerializedAs("m_RestoreDefaultPose")]
[SerializeField]
private bool m_SolveFromDefaultPose = true;
[SerializeField][Range(0f, 1f)]
private float m_Weight = 1f;
private Plane m_Plane;
private List<Vector3> m_TargetPositions = new List<Vector3>();
/// <summary>
/// Returns the number of IKChain2D in the solver.
/// </summary>
public int chainCount
{
get { return GetChainCount(); }
}
/// <summary>
/// Get Set for rotation constrain property.
/// </summary>
public bool constrainRotation
{
get { return m_ConstrainRotation; }
set { m_ConstrainRotation = value; }
}
/// <summary>
/// Get Set for restoring default pose.
/// </summary>
public bool solveFromDefaultPose
{
get { return m_SolveFromDefaultPose; }
set { m_SolveFromDefaultPose = value; }
}
/// <summary>
/// Returns true if the Solver2D is in a valid state.
/// </summary>
public bool isValid
{
get { return Validate(); }
}
/// <summary>
/// Returns true if all chains in the Solver has a target.
/// </summary>
public bool allChainsHaveTargets
{
get { return HasTargets(); }
}
/// <summary>
/// Get and Set Solver weights.
/// </summary>
public float weight
{
get { return m_Weight; }
set { m_Weight = Mathf.Clamp01(value); }
}
private void OnEnable() {}
/// <summary>
/// Validate and initialize the Solver.
/// </summary>
protected virtual void OnValidate()
{
m_Weight = Mathf.Clamp01(m_Weight);
if (!isValid)
Initialize();
}
private bool Validate()
{
for (int i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
if (!chain.isValid)
return false;
}
return DoValidate();
}
private bool HasTargets()
{
for (int 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 (int i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
chain.Initialize();
}
}
private void Prepare()
{
var rootTransform = GetPlaneRootTransform();
if (rootTransform != null)
{
m_Plane.normal = rootTransform.forward;
m_Plane.distance = -Vector3.Dot(m_Plane.normal, rootTransform.position);
}
for (int i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
var constrainTargetRotation = constrainRotation && chain.target != null;
if (m_SolveFromDefaultPose)
chain.RestoreDefaultPose(constrainTargetRotation);
}
DoPrepare();
}
private void PrepareEffectorPositions()
{
m_TargetPositions.Clear();
for (int i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
if (chain.target)
m_TargetPositions.Add(chain.target.position);
}
}
/// <summary>
/// Perfom 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;
float finalWeight = globalWeight * weight;
if (finalWeight == 0f)
return;
if (!isValid)
return;
Prepare();
if (finalWeight < 1f)
StoreLocalRotations();
DoUpdateIK(positions);
if (constrainRotation)
{
for (int i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
if (chain.target)
chain.effector.rotation = chain.target.rotation;
}
}
if (finalWeight < 1f)
BlendFkToIk(finalWeight);
}
private void StoreLocalRotations()
{
for (int i = 0; i < GetChainCount(); ++i)
{
var chain = GetChain(i);
chain.StoreLocalRotations();
}
}
private void BlendFkToIk(float finalWeight)
{
for (int 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() { return 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()
{
if (chainCount > 0)
return GetChain(0).rootTransform;
return 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);
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using UnityEngine;
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.IK
{
/// <summary>
/// Attribute to add a menu item in IKManager2D to create the Solver.
/// </summary>
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
[AttributeUsage(AttributeTargets.Class)]
public sealed class Solver2DMenuAttribute : Attribute
{
string m_MenuPath;
/// <summary>
/// Menu path.
/// </summary>
public string menuPath
{
get { return m_MenuPath; }
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="_menuPath">Menu item path.</param>
public Solver2DMenuAttribute(string _menuPath)
{
m_MenuPath = _menuPath;
}
}
}

View File

@@ -0,0 +1,60 @@
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.IK
{
/// <summary>
/// Utility for 2D based Cyclic Coordinate Descent (CCD) IK Solver.
/// </summary>
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
public static class CCD2D
{
/// <summary>
/// Solve IK Chain based on CCD.
/// </summary>
/// <param name="targetPosition">Target position.</param>
/// <param name="forward">Forward vector for solver.</param>
/// <param name="solverLimit">Solver iteration count.</param>
/// <param name="tolerance">Target position's tolerance.</param>
/// <param name="velocity">Velocity towards target position.</param>
/// <param name="positions">Chain positions.</param>
/// <returns>Returns true if solver successfully completes within iteration limit. False otherwise.</returns>
public static bool Solve(Vector3 targetPosition, Vector3 forward, int solverLimit, float tolerance, float velocity, ref Vector3[] positions)
{
int last = positions.Length - 1;
int iterations = 0;
float sqrTolerance = tolerance * tolerance;
float sqrDistanceToTarget = (targetPosition - positions[last]).sqrMagnitude;
while (sqrDistanceToTarget > sqrTolerance)
{
DoIteration(targetPosition, forward, last, velocity, ref positions);
sqrDistanceToTarget = (targetPosition - positions[last]).sqrMagnitude;
if (++iterations >= solverLimit)
break;
}
return iterations != 0;
}
static void DoIteration(Vector3 targetPosition, Vector3 forward, int last, float velocity, ref Vector3[] positions)
{
for (int i = last - 1; i >= 0; --i)
{
Vector3 toTarget = targetPosition - positions[i];
Vector3 toLast = positions[last] - positions[i];
float angle = Vector3.SignedAngle(toLast, toTarget, forward);
angle = Mathf.Lerp(0f, angle, velocity);
Quaternion deltaRotation = Quaternion.AngleAxis(angle, forward);
for (int j = last; j > i; --j)
positions[j] = RotatePositionFrom(positions[j], positions[i], deltaRotation);
}
}
static Vector3 RotatePositionFrom(Vector3 position, Vector3 pivot, Quaternion rotation)
{
Vector3 v = position - pivot;
v = rotation * v;
return pivot + v;
}
}
}

View File

@@ -0,0 +1,171 @@
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.IK
{
/// <summary>
/// Structure to store FABRIK Chain data.
/// </summary>
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
public struct FABRIKChain2D
{
public Vector2 first
{
get { return positions[0]; }
}
public Vector2 last
{
get { return positions[positions.Length - 1]; }
}
public Vector2 origin;
public Vector2 target;
public float sqrTolerance;
public Vector2[] positions;
public float[] lengths;
public int[] subChainIndices;
public Vector3[] worldPositions;
}
/// <summary>
/// Utility for 2D Forward And Backward Reaching Inverse Kinematics (FABRIK) IK Solver.
/// </summary>
public static class FABRIK2D
{
/// <summary>
/// Solve IK based on FABRIK
/// </summary>
/// <param name="targetPosition">Target position.</param>
/// <param name="solverLimit">Solver iteration count.</param>
/// <param name="tolerance">Target position's tolerance.</param>
/// <param name="lengths">Length of the chains.</param>
/// <param name="positions">Chain positions.</param>
/// <returns>Returns true if solver successfully completes within iteration limit. False otherwise.</returns>
public static bool Solve(Vector2 targetPosition, int solverLimit, float tolerance, float[] lengths, ref Vector2[] positions)
{
int last = positions.Length - 1;
int iterations = 0;
float sqrTolerance = tolerance * tolerance;
float sqrDistanceToTarget = (targetPosition - positions[last]).sqrMagnitude;
Vector2 originPosition = positions[0];
while (sqrDistanceToTarget > sqrTolerance)
{
Forward(targetPosition, lengths, ref positions);
Backward(originPosition, lengths, ref positions);
sqrDistanceToTarget = (targetPosition - positions[last]).sqrMagnitude;
if (++iterations >= solverLimit)
break;
}
// Return whether positions have changed
return iterations != 0;
}
public static bool SolveChain(int solverLimit, ref FABRIKChain2D[] chains)
{
// Do a quick validation of the end points that it has not been solved
if (ValidateChain(chains))
return false;
// Validation failed, solve chain
for (int iterations = 0; iterations < solverLimit; ++iterations)
{
SolveForwardsChain(0, ref chains);
// Break if solution is solved
if (!SolveBackwardsChain(0, ref chains))
break;
}
return true;
}
static bool ValidateChain(FABRIKChain2D[] chains)
{
foreach (var chain in chains)
{
if (chain.subChainIndices.Length == 0 && (chain.target - chain.last).sqrMagnitude > chain.sqrTolerance)
return false;
}
return true;
}
static void SolveForwardsChain(int idx, ref FABRIKChain2D[] chains)
{
var target = chains[idx].target;
if (chains[idx].subChainIndices.Length > 0)
{
target = Vector2.zero;
for (int i = 0; i < chains[idx].subChainIndices.Length; ++i)
{
var childIdx = chains[idx].subChainIndices[i];
SolveForwardsChain(childIdx, ref chains);
target += chains[childIdx].first;
}
target = target / chains[idx].subChainIndices.Length;
}
Forward(target, chains[idx].lengths, ref chains[idx].positions);
}
static bool SolveBackwardsChain(int idx, ref FABRIKChain2D[] chains)
{
bool notSolved = false;
Backward(chains[idx].origin, chains[idx].lengths, ref chains[idx].positions);
for (int i = 0; i < chains[idx].subChainIndices.Length; ++i)
{
var childIdx = chains[idx].subChainIndices[i];
chains[childIdx].origin = chains[idx].last;
notSolved |= SolveBackwardsChain(childIdx, ref chains);
}
// Check if end point has reached the target
if (chains[idx].subChainIndices.Length == 0)
{
notSolved |= (chains[idx].target - chains[idx].last).sqrMagnitude > chains[idx].sqrTolerance;
}
return notSolved;
}
static void Forward(Vector2 targetPosition, float[] lengths, ref Vector2[] positions)
{
var last = positions.Length - 1;
positions[last] = targetPosition;
for (int i = last - 1; i >= 0; --i)
{
var r = positions[i + 1] - positions[i];
var l = lengths[i] / r.magnitude;
var position = (1f - l) * positions[i + 1] + l * positions[i];
positions[i] = position;
}
}
static void Backward(Vector2 originPosition, float[] lengths, ref Vector2[] positions)
{
positions[0] = originPosition;
var last = positions.Length - 1;
for (int i = 0; i < last; ++i)
{
var r = positions[i + 1] - positions[i];
var l = lengths[i] / r.magnitude;
var position = (1f - l) * positions[i] + l * positions[i + 1];
positions[i + 1] = position;
}
}
// For constraints
static Vector2 ValidateJoint(Vector2 endPosition, Vector2 startPosition, Vector2 right, float min, float max)
{
var localDifference = endPosition - startPosition;
var angle = Vector2.SignedAngle(right, localDifference);
var validatedPosition = endPosition;
if (angle < min)
{
var minRotation = Quaternion.Euler(0f, 0f, min);
validatedPosition = startPosition + (Vector2)(minRotation * right * localDifference.magnitude);
}
else if (angle > max)
{
var maxRotation = Quaternion.Euler(0f, 0f, max);
validatedPosition = startPosition + (Vector2)(maxRotation * right * localDifference.magnitude);
}
return validatedPosition;
}
}
}

View File

@@ -0,0 +1,46 @@
using UnityEngine.Scripting.APIUpdating;
namespace UnityEngine.U2D.IK
{
/// <summary>
/// Utility for 2D Limb IK Solver.
/// </summary>
[MovedFrom("UnityEngine.Experimental.U2D.IK")]
public static class Limb
{
/// <summary>
/// Solve based on Limb IK
/// </summary>
/// <param name="targetPosition">Target position.</param>
/// <param name="lengths">Length of the chains.</param>
/// <param name="positions">Chain positions.</param>
/// <param name="outAngles">Output angles for the chain's position.</param>
/// <returns>Always returns true.</returns>
public static bool Solve(Vector3 targetPosition, float[] lengths, Vector3[] positions, ref float[] outAngles)
{
outAngles[0] = 0f;
outAngles[1] = 0f;
if (lengths[0] == 0f || lengths[1] == 0f)
return false;
Vector3 startToEnd = targetPosition - positions[0];
float distanceMagnitude = startToEnd.magnitude;
float sqrDistance = startToEnd.sqrMagnitude;
float sqrParentLength = (lengths[0] * lengths[0]);
float sqrTargetLength = (lengths[1] * lengths[1]);
float angle0Cos = (sqrDistance + sqrParentLength - sqrTargetLength) / (2f * lengths[0] * distanceMagnitude);
float angle1Cos = (sqrDistance - sqrParentLength - sqrTargetLength) / (2f * lengths[0] * lengths[1]);
if ((angle0Cos >= -1f && angle0Cos <= 1f) && (angle1Cos >= -1f && angle1Cos <= 1f))
{
outAngles[0] = Mathf.Acos(angle0Cos) * Mathf.Rad2Deg;
outAngles[1] = Mathf.Acos(angle1Cos) * Mathf.Rad2Deg;
}
return true;
}
}
}

View File

@@ -0,0 +1,7 @@
{
"name": "Unity.2D.IK.Runtime",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [],
"excludePlatforms": []
}