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.

301 lines
9.5 KiB
C#

6 months ago
using UnityEngine;
using System.Collections;
namespace RootMotion.FinalIK {
/// <summary>
/// Mapping a bone hierarchy to 2 triangles defined by the hip and chest planes.
/// </summary>
[System.Serializable]
public class IKMappingSpine: IKMapping {
#region Main Interface
/// <summary>
/// The spine bones.
/// </summary>
public Transform[] spineBones;
/// <summary>
/// The left upper arm bone.
/// </summary>
public Transform leftUpperArmBone;
/// <summary>
/// The right upper arm bone.
/// </summary>
public Transform rightUpperArmBone;
/// <summary>
/// The left thigh bone.
/// </summary>
public Transform leftThighBone;
/// <summary>
/// The right thigh bone.
/// </summary>
public Transform rightThighBone;
/// <summary>
/// The number of iterations of the %FABRIK algorithm. Not used if there are 2 bones assigned to Spine in the References.
/// </summary>
[Range(1, 3)]
public int iterations = 3;
/// <summary>
/// The weight of twisting the spine bones gradually to the orientation of the chest triangle. Relatively expensive, so set this to 0 if there is not much spine twisting going on.
/// </summary>
[Range(0f, 1f)]
public float twistWeight = 1f;
/// <summary>
/// Determines whether this IKMappingSpine is valid
/// </summary>
public override bool IsValid(IKSolver solver, ref string message) {
if (!base.IsValid(solver, ref message)) return false;
foreach (Transform spineBone in spineBones) if (spineBone == null) {
message = "Spine bones contains a null reference.";
return false;
}
int nodes = 0;
for (int i = 0; i < spineBones.Length; i++) {
if (solver.GetPoint(spineBones[i]) != null) nodes ++;
}
if (nodes == 0) {
message = "IKMappingSpine does not contain any nodes.";
return false;
}
if (leftUpperArmBone == null) {
message = "IKMappingSpine is missing the left upper arm bone.";
return false;
}
if (rightUpperArmBone == null) {
message = "IKMappingSpine is missing the right upper arm bone.";
return false;
}
if (leftThighBone == null) {
message = "IKMappingSpine is missing the left thigh bone.";
return false;
}
if (rightThighBone == null) {
message = "IKMappingSpine is missing the right thigh bone.";
return false;
}
if (solver.GetPoint(leftUpperArmBone) == null) {
message = "Full Body IK is missing the left upper arm node.";
return false;
}
if (solver.GetPoint(rightUpperArmBone) == null) {
message = "Full Body IK is missing the right upper arm node.";
return false;
}
if (solver.GetPoint(leftThighBone) == null) {
message = "Full Body IK is missing the left thigh node.";
return false;
}
if (solver.GetPoint(rightThighBone) == null) {
message = "Full Body IK is missing the right thigh node.";
return false;
}
return true;
}
#endregion Main Interface
private int rootNodeIndex;
private BoneMap[] spine = new BoneMap[0];
private BoneMap leftUpperArm = new BoneMap(), rightUpperArm = new BoneMap(), leftThigh = new BoneMap(), rightThigh = new BoneMap();
private bool useFABRIK;
public IKMappingSpine() {}
public IKMappingSpine(Transform[] spineBones, Transform leftUpperArmBone, Transform rightUpperArmBone, Transform leftThighBone, Transform rightThighBone) {
SetBones(spineBones, leftUpperArmBone, rightUpperArmBone, leftThighBone, rightThighBone);
}
public void SetBones(Transform[] spineBones, Transform leftUpperArmBone, Transform rightUpperArmBone, Transform leftThighBone, Transform rightThighBone) {
this.spineBones = spineBones;
this.leftUpperArmBone = leftUpperArmBone;
this.rightUpperArmBone = rightUpperArmBone;
this.leftThighBone = leftThighBone;
this.rightThighBone = rightThighBone;
}
public void StoreDefaultLocalState() {
for (int i = 0; i < spine.Length; i++) {
spine[i].StoreDefaultLocalState();
}
}
public void FixTransforms() {
for (int i = 0; i < spine.Length; i++) {
spine[i].FixTransform(i == 0 || i == spine.Length - 1);
}
}
/*
* Initiating and setting defaults
* */
public override void Initiate(IKSolverFullBody solver) {
if (iterations <= 0) iterations = 3;
// Creating the bone maps
if (spine == null || spine.Length != spineBones.Length) spine = new BoneMap[spineBones.Length];
rootNodeIndex = -1;
for (int i = 0; i < spineBones.Length; i++) {
if (spine[i] == null) spine[i] = new BoneMap();
spine[i].Initiate(spineBones[i], solver);
// Finding the root node
if (spine[i].isNodeBone) rootNodeIndex = i;
}
if (leftUpperArm == null) leftUpperArm = new BoneMap();
if (rightUpperArm == null) rightUpperArm = new BoneMap();
if (leftThigh == null) leftThigh = new BoneMap();
if (rightThigh == null) rightThigh = new BoneMap();
leftUpperArm.Initiate(leftUpperArmBone, solver);
rightUpperArm.Initiate(rightUpperArmBone, solver);
leftThigh.Initiate(leftThighBone, solver);
rightThigh.Initiate(rightThighBone, solver);
for (int i = 0; i < spine.Length; i++) spine[i].SetIKPosition();
// Defining the plane for the first bone
spine[0].SetPlane(solver, spine[rootNodeIndex].transform, leftThigh.transform, rightThigh.transform);
// Finding bone lengths and axes
for (int i = 0; i < spine.Length - 1; i++) {
spine[i].SetLength(spine[i + 1]);
spine[i].SetLocalSwingAxis(spine[i + 1]);
spine[i].SetLocalTwistAxis(leftUpperArm.transform.position - rightUpperArm.transform.position, spine[i + 1].transform.position - spine[i].transform.position);
}
// Defining the plane for the last bone
spine[spine.Length - 1].SetPlane(solver, spine[rootNodeIndex].transform, leftUpperArm.transform, rightUpperArm.transform);
spine[spine.Length - 1].SetLocalSwingAxis(leftUpperArm, rightUpperArm);
useFABRIK = UseFABRIK();
}
// Should the spine mapping use the FABRIK algorithm
private bool UseFABRIK() {
if (spine.Length > 3) return true;
if (rootNodeIndex != 1) return true;
return false;
}
/*
* Updating the bone maps to the current animated state of the character
* */
public void ReadPose() {
spine[0].UpdatePlane(true, true);
for (int i = 0; i < spine.Length - 1; i++) {
spine[i].SetLength(spine[i + 1]);
spine[i].SetLocalSwingAxis(spine[i + 1]);
spine[i].SetLocalTwistAxis(leftUpperArm.transform.position - rightUpperArm.transform.position, spine[i + 1].transform.position - spine[i].transform.position);
}
spine[spine.Length - 1].UpdatePlane(true, true);
spine[spine.Length - 1].SetLocalSwingAxis(leftUpperArm, rightUpperArm);
}
/*
* Mapping the spine to the hip and chest planes
* */
public void WritePose(IKSolverFullBody solver) {
Vector3 firstPosition = spine[0].GetPlanePosition(solver);
Vector3 rootPosition = solver.GetNode(spine[rootNodeIndex].chainIndex, spine[rootNodeIndex].nodeIndex).solverPosition;
Vector3 lastPosition = spine[spine.Length - 1].GetPlanePosition(solver);
// If we have more than 3 bones, use the FABRIK algorithm
if (useFABRIK) {
Vector3 offset = solver.GetNode(spine[rootNodeIndex].chainIndex, spine[rootNodeIndex].nodeIndex).solverPosition - spine[rootNodeIndex].transform.position;
for (int i = 0; i < spine.Length; i++) {
spine[i].ikPosition = spine[i].transform.position + offset;
}
// Iterating the FABRIK algorithm
for (int i = 0; i < iterations; i++) {
ForwardReach(lastPosition);
BackwardReach(firstPosition);
spine[rootNodeIndex].ikPosition = rootPosition;
}
} else {
// When we have just 3 bones, we know their positions already
spine[0].ikPosition = firstPosition;
spine[rootNodeIndex].ikPosition = rootPosition;
}
spine[spine.Length - 1].ikPosition = lastPosition;
// Mapping the spine bones to the solver
MapToSolverPositions(solver);
}
/*
* Stage 1 of the FABRIK algorithm.
* */
public void ForwardReach(Vector3 position) {
// Lerp last bone's ikPosition to position
spine[spineBones.Length - 1].ikPosition = position;
for (int i = spine.Length - 2; i > -1; i--) {
// Finding joint positions
spine[i].ikPosition = SolveFABRIKJoint(spine[i].ikPosition, spine[i + 1].ikPosition, spine[i].length);
}
}
/*
* Stage 2 of the FABRIK algorithm
* */
private void BackwardReach(Vector3 position) {
spine[0].ikPosition = position;
// Finding joint positions
for (int i = 1; i < spine.Length; i++) {
spine[i].ikPosition = SolveFABRIKJoint(spine[i].ikPosition, spine[i - 1].ikPosition, spine[i - 1].length);
}
}
/*
* Positioning and rotating the spine bones to match the solver positions
* */
private void MapToSolverPositions(IKSolverFullBody solver) {
// Translating the first bone
// Note: spine here also includes the pelvis
spine[0].SetToIKPosition();
spine[0].RotateToPlane(solver, 1f);
// Translating all the bones between the first and the last
for (int i = 1; i < spine.Length - 1; i++) {
spine[i].Swing(spine[i + 1].ikPosition, 1f);
if (twistWeight > 0) {
float bWeight = (float)i / ((float)spine.Length - 2);
Vector3 s1 = solver.GetNode(leftUpperArm.chainIndex, leftUpperArm.nodeIndex).solverPosition;
Vector3 s2 = solver.GetNode(rightUpperArm.chainIndex, rightUpperArm.nodeIndex).solverPosition;
spine[i].Twist(s1 - s2, spine[i + 1].ikPosition - spine[i].transform.position, bWeight * twistWeight);
}
}
// Translating the last bone
spine[spine.Length - 1].SetToIKPosition();
spine[spine.Length - 1].RotateToPlane(solver, 1f);
}
}
}