holopy3/Assets/Plugins/RootMotion/FinalIK/IK Solvers/IKSolverHeuristic.cs

236 lines
7.4 KiB
C#
Raw Normal View History

2020-12-10 14:25:12 +00:00
using UnityEngine;
using System.Collections;
using System;
namespace RootMotion.FinalIK {
/// <summary>
/// Contains methods common for all heuristic solvers.
/// </summary>
[System.Serializable]
public class IKSolverHeuristic: IKSolver {
#region Main Interface
/// <summary>
/// The target Transform. Solver IKPosition will be automatically set to the position of the target.
/// </summary>
public Transform target;
/// <summary>
/// Minimum distance from last reached position. Will stop solving if difference from previous reached position is less than tolerance. If tolerance is zero, will iterate until maxIterations.
/// </summary>
public float tolerance = 0f;
/// <summary>
/// Max iterations per frame
/// </summary>
public int maxIterations = 4;
/// <summary>
/// If true, rotation limits (if existing) will be applied on each iteration.
/// </summary>
public bool useRotationLimits = true;
/// <summary>
/// Solve in 2D?
/// </summary>
public bool XY;
/// <summary>
/// The hierarchy of bones.
/// </summary>
public Bone[] bones = new Bone[0];
/// <summary>
/// Rebuild the bone hierarcy and reinitiate the solver.
/// </summary>
/// <returns>
/// Returns true if the new chain is valid.
/// </returns>
public bool SetChain(Transform[] hierarchy, Transform root) {
if (bones == null || bones.Length != hierarchy.Length) bones = new Bone[hierarchy.Length];
for (int i = 0; i < hierarchy.Length; i++) {
if (bones[i] == null) bones[i] = new IKSolver.Bone();
bones[i].transform = hierarchy[i];
}
Initiate(root);
return initiated;
}
/// <summary>
/// Adds a bone to the chain.
/// </summary>
public void AddBone(Transform bone) {
Transform[] newBones = new Transform[bones.Length + 1];
for (int i = 0; i < bones.Length; i++) {
newBones[i] = bones[i].transform;
}
newBones[newBones.Length - 1] = bone;
SetChain(newBones, root);
}
public override void StoreDefaultLocalState() {
for (int i = 0; i < bones.Length; i++) bones[i].StoreDefaultLocalState();
}
public override void FixTransforms() {
if (!initiated) return;
if (IKPositionWeight <= 0f) return;
for (int i = 0; i < bones.Length; i++) bones[i].FixTransform();
}
public override bool IsValid(ref string message) {
if (bones.Length == 0) {
message = "IK chain has no Bones.";
return false;
}
if (bones.Length < minBones) {
message = "IK chain has less than " + minBones + " Bones.";
return false;
}
foreach (Bone bone in bones) {
if (bone.transform == null) {
message = "One of the Bones is null.";
return false;
}
}
Transform duplicate = ContainsDuplicateBone(bones);
if (duplicate != null) {
message = duplicate.name + " is represented multiple times in the Bones.";
return false;
}
if (!allowCommonParent && !HierarchyIsValid(bones)) {
message = "Invalid bone hierarchy detected. IK requires for it's bones to be parented to each other in descending order.";
return false;
}
if (!boneLengthCanBeZero) {
for (int i = 0; i < bones.Length - 1; i++) {
float l = (bones[i].transform.position - bones[i + 1].transform.position).magnitude;
if (l == 0) {
message = "Bone " + i + " length is zero.";
return false;
}
}
}
return true;
}
public override IKSolver.Point[] GetPoints() {
return bones as IKSolver.Point[];
}
public override IKSolver.Point GetPoint(Transform transform) {
for (int i = 0; i < bones.Length; i++) if (bones[i].transform == transform) return bones[i] as IKSolver.Point;
return null;
}
#endregion Main Interface
protected virtual int minBones { get { return 2; }}
protected virtual bool boneLengthCanBeZero { get { return true; }}
protected virtual bool allowCommonParent { get { return false; }}
protected override void OnInitiate() {}
protected override void OnUpdate() {}
protected Vector3 lastLocalDirection;
protected float chainLength;
/*
* Initiates all bones to match their current state
* */
protected void InitiateBones() {
chainLength = 0;
for (int i = 0; i < bones.Length; i++) {
// Find out which local axis is directed at child/target position
if (i < bones.Length - 1) {
bones[i].length = (bones[i].transform.position - bones[i + 1].transform.position).magnitude;
chainLength += bones[i].length;
Vector3 nextPosition = bones[i + 1].transform.position;
bones[i].axis = Quaternion.Inverse(bones[i].transform.rotation) * (nextPosition - bones[i].transform.position);
// Disable Rotation Limits from updating to take control of their execution order
if (bones[i].rotationLimit != null) {
if (XY) {
if (bones[i].rotationLimit is RotationLimitHinge) {
} else Warning.Log("Only Hinge Rotation Limits should be used on 2D IK solvers.", bones[i].transform);
}
bones[i].rotationLimit.Disable();
}
} else {
bones[i].axis = Quaternion.Inverse(bones[i].transform.rotation) * (bones[bones.Length - 1].transform.position - bones[0].transform.position);
}
}
}
#region Optimizations
/*
* Gets the direction from last bone to first bone in first bone's local space.
* */
protected virtual Vector3 localDirection {
get {
return bones[0].transform.InverseTransformDirection(bones[bones.Length - 1].transform.position - bones[0].transform.position);
}
}
/*
* Gets the offset from last position of the last bone to its current position.
* */
protected float positionOffset {
get {
return Vector3.SqrMagnitude(localDirection - lastLocalDirection);
}
}
#endregion Optimizations
/*
* Get target offset to break out of the linear singularity issue
* */
protected Vector3 GetSingularityOffset() {
if (!SingularityDetected()) return Vector3.zero;
Vector3 IKDirection = (IKPosition - bones[0].transform.position).normalized;
Vector3 secondaryDirection = new Vector3(IKDirection.y, IKDirection.z, IKDirection.x);
// Avoiding getting locked by the Hinge Rotation Limit
if (useRotationLimits && bones[bones.Length - 2].rotationLimit != null && bones[bones.Length - 2].rotationLimit is RotationLimitHinge) {
secondaryDirection = bones[bones.Length - 2].transform.rotation * bones[bones.Length - 2].rotationLimit.axis;
}
return Vector3.Cross(IKDirection, secondaryDirection) * bones[bones.Length - 2].length * 0.5f;
}
/*
* Detects linear singularity issue when the direction from first bone to IKPosition matches the direction from first bone to the last bone.
* */
private bool SingularityDetected() {
if (!initiated) return false;
Vector3 toLastBone = bones[bones.Length - 1].transform.position - bones[0].transform.position;
Vector3 toIKPosition = IKPosition - bones[0].transform.position;
float toLastBoneDistance = toLastBone.magnitude;
float toIKPositionDistance = toIKPosition.magnitude;
if (toLastBoneDistance < toIKPositionDistance) return false;
if (toLastBoneDistance < chainLength - (bones[bones.Length - 2].length * 0.1f)) return false;
if (toLastBoneDistance == 0) return false;
if (toIKPositionDistance == 0) return false;
if (toIKPositionDistance > toLastBoneDistance) return false;
float dot = Vector3.Dot(toLastBone / toLastBoneDistance, toIKPosition / toIKPositionDistance);
if (dot < 0.999f) return false;
return true;
}
}
}