You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
454 lines
13 KiB
C#
454 lines
13 KiB
C#
6 months ago
|
using UnityEngine;
|
||
|
using System.Collections;
|
||
|
|
||
|
namespace RootMotion.FinalIK {
|
||
|
|
||
|
/// <summary>
|
||
|
/// The base abstract class for all %IK solvers
|
||
|
/// </summary>
|
||
|
[System.Serializable]
|
||
|
public abstract class IKSolver {
|
||
|
|
||
|
#region Main Interface
|
||
|
|
||
|
[HideInInspector] public bool executedInEditor;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Determines whether this instance is valid or not.
|
||
|
/// </summary>
|
||
|
public bool IsValid() {
|
||
|
string message = string.Empty;
|
||
|
return IsValid(ref message);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Determines whether this instance is valid or not. If returns false, also fills in an error message.
|
||
|
/// </summary>
|
||
|
public abstract bool IsValid(ref string message);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Initiate the solver with specified root Transform. Use only if this %IKSolver is not a member of an %IK component.
|
||
|
/// </summary>
|
||
|
public void Initiate(Transform root) {
|
||
|
if (executedInEditor) return;
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
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();
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// The %IK position.
|
||
|
/// </summary>
|
||
|
[HideInInspector] public Vector3 IKPosition;
|
||
|
|
||
|
[Tooltip("The positional or the master weight of the solver.")]
|
||
|
/// <summary>
|
||
|
/// The %IK position weight or the master weight of the solver.
|
||
|
/// </summary>
|
||
|
[Range(0f, 1f)]
|
||
|
public float IKPositionWeight = 1f;
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
public virtual Vector3 GetIKPosition() {
|
||
|
return IKPosition;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
public void SetIKPosition(Vector3 position) {
|
||
|
IKPosition = position;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
public float GetIKPositionWeight() {
|
||
|
return IKPositionWeight;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// 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.
|
||
|
/// </summary>
|
||
|
public void SetIKPositionWeight(float weight) {
|
||
|
IKPositionWeight = Mathf.Clamp(weight, 0f, 1f);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the root Transform.
|
||
|
/// </summary>
|
||
|
public Transform GetRoot() {
|
||
|
return root;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets a value indicating whether this <see cref="IKSolver"/> has successfully initiated.
|
||
|
/// </summary>
|
||
|
public bool initiated { get; private set; }
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets all the points used by the solver.
|
||
|
/// </summary>
|
||
|
public abstract IKSolver.Point[] GetPoints();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the point with the specified Transform.
|
||
|
/// </summary>
|
||
|
public abstract IKSolver.Point GetPoint(Transform transform);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Fixes all the Transforms used by the solver to their initial state.
|
||
|
/// </summary>
|
||
|
public abstract void FixTransforms();
|
||
|
|
||
|
/// <summary>
|
||
|
/// Stores the default local state for the bones used by the solver.
|
||
|
/// </summary>
|
||
|
public abstract void StoreDefaultLocalState();
|
||
|
|
||
|
/// <summary>
|
||
|
/// The most basic element type in the %IK chain that all other types extend from.
|
||
|
/// </summary>
|
||
|
[System.Serializable]
|
||
|
public class Point {
|
||
|
|
||
|
/// <summary>
|
||
|
/// The transform.
|
||
|
/// </summary>
|
||
|
public Transform transform;
|
||
|
/// <summary>
|
||
|
/// The weight of this bone in the solver.
|
||
|
/// </summary>
|
||
|
[Range(0f, 1f)]
|
||
|
public float weight = 1f;
|
||
|
/// <summary>
|
||
|
/// Virtual position in the %IK solver.
|
||
|
/// </summary>
|
||
|
public Vector3 solverPosition;
|
||
|
/// <summary>
|
||
|
/// Virtual rotation in the %IK solver.
|
||
|
/// </summary>
|
||
|
public Quaternion solverRotation = Quaternion.identity;
|
||
|
/// <summary>
|
||
|
/// The default local position of the Transform.
|
||
|
/// </summary>
|
||
|
public Vector3 defaultLocalPosition;
|
||
|
/// <summary>
|
||
|
/// The default local rotation of the Transform.
|
||
|
/// </summary>
|
||
|
public Quaternion defaultLocalRotation;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Stores the default local state of the point.
|
||
|
/// </summary>
|
||
|
public void StoreDefaultLocalState() {
|
||
|
defaultLocalPosition = transform.localPosition;
|
||
|
defaultLocalRotation = transform.localRotation;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Fixes the transform to its default local state.
|
||
|
/// </summary>
|
||
|
public void FixTransform() {
|
||
|
if (transform.localPosition != defaultLocalPosition) transform.localPosition = defaultLocalPosition;
|
||
|
if (transform.localRotation != defaultLocalRotation) transform.localRotation = defaultLocalRotation;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Updates the solverPosition (in world space).
|
||
|
/// </summary>
|
||
|
public void UpdateSolverPosition() {
|
||
|
solverPosition = transform.position;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Updates the solverPosition (in local space).
|
||
|
/// </summary>
|
||
|
public void UpdateSolverLocalPosition() {
|
||
|
solverPosition = transform.localPosition;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Updates the solverPosition/Rotation (in world space).
|
||
|
/// </summary>
|
||
|
public void UpdateSolverState() {
|
||
|
solverPosition = transform.position;
|
||
|
solverRotation = transform.rotation;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Updates the solverPosition/Rotation (in local space).
|
||
|
/// </summary>
|
||
|
public void UpdateSolverLocalState() {
|
||
|
solverPosition = transform.localPosition;
|
||
|
solverRotation = transform.localRotation;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// %Bone type of element in the %IK chain. Used in the case of skeletal Transform hierarchies.
|
||
|
/// </summary>
|
||
|
[System.Serializable]
|
||
|
public class Bone: Point {
|
||
|
|
||
|
/// <summary>
|
||
|
/// The length of the bone.
|
||
|
/// </summary>
|
||
|
public float length;
|
||
|
/// <summary>
|
||
|
/// The sqr mag of the bone.
|
||
|
/// </summary>
|
||
|
public float sqrMag;
|
||
|
/// <summary>
|
||
|
/// Local axis to target/child bone.
|
||
|
/// </summary>
|
||
|
public Vector3 axis = -Vector3.right;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Gets the rotation limit component from the Transform if there is any.
|
||
|
/// </summary>
|
||
|
public RotationLimit rotationLimit {
|
||
|
get {
|
||
|
if (!isLimited) return null;
|
||
|
if (_rotationLimit == null) _rotationLimit = transform.GetComponent<RotationLimit>();
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// %Node type of element in the %IK chain. Used in the case of mixed/non-hierarchical %IK systems
|
||
|
/// </summary>
|
||
|
[System.Serializable]
|
||
|
public class Node: Point {
|
||
|
|
||
|
/// <summary>
|
||
|
/// Distance to child node.
|
||
|
/// </summary>
|
||
|
public float length;
|
||
|
/// <summary>
|
||
|
/// The effector position weight.
|
||
|
/// </summary>
|
||
|
public float effectorPositionWeight;
|
||
|
/// <summary>
|
||
|
/// The effector rotation weight.
|
||
|
/// </summary>
|
||
|
public float effectorRotationWeight;
|
||
|
/// <summary>
|
||
|
/// Position offset.
|
||
|
/// </summary>
|
||
|
public Vector3 offset;
|
||
|
|
||
|
public Node() {}
|
||
|
|
||
|
public Node (Transform transform) {
|
||
|
this.transform = transform;
|
||
|
}
|
||
|
|
||
|
public Node (Transform transform, float weight) {
|
||
|
this.transform = transform;
|
||
|
this.weight = weight;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Delegates solver update events.
|
||
|
/// </summary>
|
||
|
public delegate void UpdateDelegate();
|
||
|
/// <summary>
|
||
|
/// Delegates solver iteration events.
|
||
|
/// </summary>
|
||
|
public delegate void IterationDelegate(int i);
|
||
|
|
||
|
/// <summary>
|
||
|
/// Called before initiating the solver.
|
||
|
/// </summary>
|
||
|
public UpdateDelegate OnPreInitiate;
|
||
|
/// <summary>
|
||
|
/// Called after initiating the solver.
|
||
|
/// </summary>
|
||
|
public UpdateDelegate OnPostInitiate;
|
||
|
/// <summary>
|
||
|
/// Called before updating.
|
||
|
/// </summary>
|
||
|
public UpdateDelegate OnPreUpdate;
|
||
|
/// <summary>
|
||
|
/// Called after writing the solved pose
|
||
|
/// </summary>
|
||
|
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
|
||
|
|
||
|
/// <summary>
|
||
|
/// Checks if an array of objects contains any duplicates.
|
||
|
/// </summary>
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
|