initial upload
This commit is contained in:
@ -0,0 +1,122 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Branch of FABRIK components in the FABRIKRoot hierarchy.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class FABRIKChain {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The FABRIK component.
|
||||
/// </summary>
|
||||
public FABRIK ik;
|
||||
/// <summary>
|
||||
/// Parent pull weight.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float pull = 1f;
|
||||
/// <summary>
|
||||
/// Resistance to being pulled by child chains.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float pin = 1f;
|
||||
/// <summary>
|
||||
/// The child chain indexes.
|
||||
/// </summary>
|
||||
public int[] children = new int[0];
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether this FABRIKChain is valid.
|
||||
/// </summary>
|
||||
public bool IsValid(ref string message) {
|
||||
if (ik == null) {
|
||||
message = "IK unassigned in FABRIKChain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ik.solver.IsValid(ref message)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
/*
|
||||
* Initiate the chain
|
||||
* */
|
||||
public void Initiate() {
|
||||
ik.enabled = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Solving stage 1 of the FABRIK algorithm from end effectors towards the root.
|
||||
* */
|
||||
public void Stage1(FABRIKChain[] chain) {
|
||||
// Solving children first
|
||||
for (int i = 0; i < children.Length; i++) chain[children[i]].Stage1(chain);
|
||||
|
||||
// The last chains
|
||||
if (children.Length == 0) {
|
||||
ik.solver.SolveForward(ik.solver.GetIKPosition());
|
||||
return;
|
||||
}
|
||||
|
||||
ik.solver.SolveForward(GetCentroid(chain));
|
||||
}
|
||||
|
||||
/*
|
||||
* Solving stage 2 of the FABRIK algoright from the root to the end effectors.
|
||||
* */
|
||||
public void Stage2(Vector3 rootPosition, FABRIKChain[] chain) {
|
||||
// Solve this chain backwards
|
||||
ik.solver.SolveBackward(rootPosition);
|
||||
|
||||
// Solve child chains
|
||||
for (int i = 0; i < children.Length; i++) {
|
||||
chain[children[i]].Stage2(ik.solver.bones[ik.solver.bones.Length - 1].transform.position, chain);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the centroid of child positions
|
||||
private Vector3 GetCentroid(FABRIKChain[] chain) {
|
||||
Vector3 position = ik.solver.GetIKPosition();
|
||||
|
||||
// The chain is pinned, ignore the children
|
||||
if (pin >= 1f) return position;
|
||||
|
||||
// Get the sum of the pull values of all the children
|
||||
float pullSum = 0f;
|
||||
for (int i = 0; i < children.Length; i++) pullSum += chain[children[i]].pull;
|
||||
|
||||
// All pull values are zero
|
||||
if (pullSum <= 0f) return position;
|
||||
|
||||
if (pullSum < 1f) pullSum = 1f;
|
||||
|
||||
// Calculating the centroid
|
||||
Vector3 centroid = position;
|
||||
|
||||
for (int i = 0; i < children.Length; i++) {
|
||||
// Vector from IKPosition to the first bone of the child
|
||||
Vector3 toChild = chain[children[i]].ik.solver.bones[0].solverPosition - position;
|
||||
|
||||
// Weight of the child
|
||||
float childWeight = chain[children[i]].pull / pullSum;
|
||||
|
||||
// Adding to the centroid
|
||||
centroid += toChild * childWeight;
|
||||
}
|
||||
|
||||
// No pinning
|
||||
if (pin <= 0f) return centroid;
|
||||
|
||||
// Pinning
|
||||
return centroid + (position - centroid) * pin;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9a2b10b17d604bb2a4d1bff880fc61a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,66 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using RootMotion.FinalIK;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Calculates bending direction and hand rotations for a FBBIK rig for VR hand controllers.
|
||||
/// </summary>
|
||||
public class FBBIKArmBending : MonoBehaviour {
|
||||
|
||||
public FullBodyBipedIK ik;
|
||||
|
||||
// Bend direction offset for the arms
|
||||
public Vector3 bendDirectionOffsetLeft;
|
||||
public Vector3 bendDirectionOffsetRight;
|
||||
|
||||
// Add some bend direction offset in character space
|
||||
public Vector3 characterSpaceBendOffsetLeft;
|
||||
public Vector3 characterSpaceBendOffsetRight;
|
||||
|
||||
private Quaternion leftHandTargetRotation;
|
||||
private Quaternion rightHandTargetRotation;
|
||||
private bool initiated;
|
||||
|
||||
void LateUpdate() {
|
||||
if (ik == null) return;
|
||||
|
||||
if (!initiated) {
|
||||
ik.solver.OnPostUpdate += OnPostFBBIK;
|
||||
initiated = true;
|
||||
}
|
||||
|
||||
// Left arm bend direction
|
||||
if (ik.solver.leftHandEffector.target != null) {
|
||||
Vector3 armAxisLeft = Vector3.left;
|
||||
ik.solver.leftArmChain.bendConstraint.direction = ik.solver.leftHandEffector.target.rotation * armAxisLeft + ik.solver.leftHandEffector.target.rotation * bendDirectionOffsetLeft + ik.transform.rotation * characterSpaceBendOffsetLeft;
|
||||
ik.solver.leftArmChain.bendConstraint.weight = 1f;
|
||||
}
|
||||
|
||||
// Right arm bend direction
|
||||
if (ik.solver.rightHandEffector.target != null) {
|
||||
Vector3 armAxisRight = Vector3.right;
|
||||
ik.solver.rightArmChain.bendConstraint.direction = ik.solver.rightHandEffector.target.rotation * armAxisRight + ik.solver.rightHandEffector.target.rotation * bendDirectionOffsetRight + ik.transform.rotation * characterSpaceBendOffsetRight;
|
||||
ik.solver.rightArmChain.bendConstraint.weight = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
void OnPostFBBIK() {
|
||||
if (ik == null) return;
|
||||
|
||||
// Rotate hand bones
|
||||
if (ik.solver.leftHandEffector.target != null) {
|
||||
ik.references.leftHand.rotation = ik.solver.leftHandEffector.target.rotation;
|
||||
}
|
||||
|
||||
if (ik.solver.rightHandEffector.target != null) {
|
||||
ik.references.rightHand.rotation = ik.solver.rightHandEffector.target.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy() {
|
||||
if (ik != null) ik.solver.OnPostUpdate -= OnPostFBBIK;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e6c4fa4d3ae33fb44b29d48945f7a129
|
||||
timeCreated: 1453857262
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,395 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Head effector for FBBIK.
|
||||
/// </summary>
|
||||
public class FBBIKHeadEffector : MonoBehaviour {
|
||||
|
||||
[Tooltip("Reference to the FBBIK component.")]
|
||||
public FullBodyBipedIK ik;
|
||||
|
||||
[LargeHeader("Position")]
|
||||
[Tooltip("Master weight for positioning the head.")]
|
||||
[Range(0f, 1f)] public float positionWeight = 1f;
|
||||
|
||||
[Tooltip("The weight of moving the body along with the head")]
|
||||
[Range(0f, 1f)] public float bodyWeight = 0.8f;
|
||||
|
||||
[Tooltip("The weight of moving the thighs along with the head")]
|
||||
[Range(0f, 1f)] public float thighWeight = 0.8f;
|
||||
|
||||
[Tooltip("If false, hands will not pull the head away if they are too far. Disabling this will improve performance significantly.")]
|
||||
public bool handsPullBody = true;
|
||||
|
||||
[LargeHeader("Rotation")]
|
||||
[Tooltip("The weight of rotating the head bone after solving")]
|
||||
[Range(0f, 1f)] public float rotationWeight = 0f;
|
||||
|
||||
[Tooltip("Clamping the rotation of the body")]
|
||||
[Range(0f, 1f)] public float bodyClampWeight = 0.5f;
|
||||
|
||||
[Tooltip("Clamping the rotation of the head")]
|
||||
[Range(0f, 1f)] public float headClampWeight = 0.5f;
|
||||
|
||||
[Tooltip("The master weight of bending/twisting the spine to the rotation of the head effector. This is similar to CCD, but uses the rotation of the head effector not the position.")]
|
||||
[Range(0f, 1f)] public float bendWeight = 1f;
|
||||
|
||||
[Tooltip("The bones to use for bending.")]
|
||||
public BendBone[] bendBones = new BendBone[0];
|
||||
|
||||
[System.Serializable]
|
||||
public class BendBone {
|
||||
|
||||
[Tooltip("Assign spine and/or neck bones.")]
|
||||
public Transform transform;
|
||||
|
||||
[Tooltip("The weight of rotating this bone.")]
|
||||
[Range(0f, 1f)] public float weight = 0.5f;
|
||||
|
||||
private Quaternion defaultLocalRotation = Quaternion.identity;
|
||||
|
||||
public BendBone() {}
|
||||
|
||||
public BendBone(Transform transform, float weight) {
|
||||
this.transform = transform;
|
||||
this.weight = weight;
|
||||
}
|
||||
|
||||
public void StoreDefaultLocalState() {
|
||||
defaultLocalRotation = transform.localRotation;
|
||||
}
|
||||
|
||||
public void FixTransforms() {
|
||||
transform.localRotation = defaultLocalRotation;
|
||||
}
|
||||
}
|
||||
|
||||
[LargeHeader("CCD")]
|
||||
[Tooltip("Optional. The master weight of the CCD (Cyclic Coordinate Descent) IK effect that bends the spine towards the head effector before FBBIK solves.")]
|
||||
[Range(0f, 1f)] public float CCDWeight = 1f;
|
||||
|
||||
[Tooltip("The weight of rolling the bones in towards the target")]
|
||||
[Range(0f, 1f)] public float roll = 0f;
|
||||
|
||||
[Tooltip("Smoothing the CCD effect.")]
|
||||
[Range(0f, 1000f)] public float damper = 500f;
|
||||
|
||||
[Tooltip("Bones to use for the CCD pass. Assign spine and/or neck bones.")]
|
||||
public Transform[] CCDBones = new Transform[0];
|
||||
|
||||
[LargeHeader("Stretching")]
|
||||
[Tooltip("Stretching the spine/neck to help reach the target. This is useful for making sure the head stays locked relative to the VR headset. NB! Stretching is done after FBBIK has solved so if you have the hand effectors pinned and spine bones included in the 'Stretch Bones', the hands might become offset from their target positions.")]
|
||||
[Range(0f, 1f)] public float postStretchWeight = 1f;
|
||||
|
||||
[Tooltip("Stretch magnitude limit.")]
|
||||
public float maxStretch = 0.1f;
|
||||
[Tooltip("If > 0, dampers the stretching effect.")]
|
||||
public float stretchDamper = 0f;
|
||||
[Tooltip("If true, will fix head position to this Transform no matter what. Good for making sure the head will not budge away from the VR headset")]
|
||||
public bool fixHead;
|
||||
[Tooltip("Bones to use for stretching. The more bones you add, the less noticable the effect.")]
|
||||
public Transform[] stretchBones = new Transform[0];
|
||||
|
||||
[LargeHeader("Chest Direction")]
|
||||
public Vector3 chestDirection = Vector3.forward;
|
||||
[Range(0f, 1f)] public float chestDirectionWeight = 1f;
|
||||
public Transform[] chestBones = new Transform[0];
|
||||
|
||||
public IKSolver.UpdateDelegate OnPostHeadEffectorFK;
|
||||
|
||||
private Vector3 offset, headToBody, shoulderCenterToHead, headToLeftThigh, headToRightThigh, leftShoulderPos, rightShoulderPos;
|
||||
private float shoulderDist, leftShoulderDist, rightShoulderDist;
|
||||
private Quaternion chestRotation;
|
||||
private Quaternion headRotationRelativeToRoot;
|
||||
private Quaternion[] ccdDefaultLocalRotations = new Quaternion[0];
|
||||
private Vector3 headLocalPosition;
|
||||
private Quaternion headLocalRotation;
|
||||
private Vector3[] stretchLocalPositions = new Vector3[0];
|
||||
private Quaternion[] stretchLocalRotations = new Quaternion[0];
|
||||
private Vector3[] chestLocalPositions = new Vector3[0];
|
||||
private Quaternion[] chestLocalRotations = new Quaternion[0];
|
||||
private int bendBonesCount;
|
||||
private int ccdBonesCount;
|
||||
private int stretchBonesCount;
|
||||
private int chestBonesCount;
|
||||
|
||||
// Register to get messages from the FBBIK
|
||||
void Start() {
|
||||
ik.solver.OnPreRead += OnPreRead;
|
||||
ik.solver.OnPreIteration += Iterate;
|
||||
ik.solver.OnPostUpdate += OnPostUpdate;
|
||||
ik.solver.OnStoreDefaultLocalState += OnStoreDefaultLocalState;
|
||||
ik.solver.OnFixTransforms += OnFixTransforms;
|
||||
|
||||
OnStoreDefaultLocalState();
|
||||
|
||||
headRotationRelativeToRoot = Quaternion.Inverse(ik.references.root.rotation) * ik.references.head.rotation;
|
||||
}
|
||||
|
||||
// Store the default local positions and rotations of the bones used by this head effector.
|
||||
private void OnStoreDefaultLocalState() {
|
||||
foreach (BendBone bendBone in bendBones) {
|
||||
if (bendBone != null) bendBone.StoreDefaultLocalState();
|
||||
}
|
||||
|
||||
ccdDefaultLocalRotations = new Quaternion[CCDBones.Length];
|
||||
for (int i = 0; i < CCDBones.Length; i++) {
|
||||
if (CCDBones[i] != null) ccdDefaultLocalRotations[i] = CCDBones[i].localRotation;
|
||||
}
|
||||
|
||||
headLocalPosition = ik.references.head.localPosition;
|
||||
headLocalRotation = ik.references.head.localRotation;
|
||||
|
||||
stretchLocalPositions = new Vector3[stretchBones.Length];
|
||||
stretchLocalRotations = new Quaternion[stretchBones.Length];
|
||||
for (int i = 0; i < stretchBones.Length; i++) {
|
||||
if (stretchBones[i] != null) {
|
||||
stretchLocalPositions[i] = stretchBones[i].localPosition;
|
||||
stretchLocalRotations[i] = stretchBones[i].localRotation;
|
||||
}
|
||||
}
|
||||
|
||||
chestLocalPositions = new Vector3[chestBones.Length];
|
||||
chestLocalRotations = new Quaternion[chestBones.Length];
|
||||
for (int i = 0; i < chestBones.Length; i++) {
|
||||
if (chestBones[i] != null) {
|
||||
chestLocalPositions[i] = chestBones[i].localPosition;
|
||||
chestLocalRotations[i] = chestBones[i].localRotation;
|
||||
}
|
||||
}
|
||||
|
||||
bendBonesCount = bendBones.Length;
|
||||
ccdBonesCount = CCDBones.Length;
|
||||
stretchBonesCount = stretchBones.Length;
|
||||
chestBonesCount = chestBones.Length;
|
||||
}
|
||||
|
||||
// Fix the bones used by this head effector to their default local state
|
||||
private void OnFixTransforms() {
|
||||
if (!enabled) return;
|
||||
|
||||
foreach (BendBone bendBone in bendBones) {
|
||||
if (bendBone != null) bendBone.FixTransforms();
|
||||
}
|
||||
|
||||
for (int i = 0; i < CCDBones.Length; i++) {
|
||||
if (CCDBones[i] != null) CCDBones[i].localRotation = ccdDefaultLocalRotations[i];
|
||||
}
|
||||
|
||||
ik.references.head.localPosition = headLocalPosition;
|
||||
ik.references.head.localRotation = headLocalRotation;
|
||||
|
||||
for (int i = 0; i < stretchBones.Length; i++) {
|
||||
if (stretchBones[i] != null) {
|
||||
stretchBones[i].localPosition = stretchLocalPositions[i];
|
||||
stretchBones[i].localRotation = stretchLocalRotations[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < chestBones.Length; i++) {
|
||||
if (chestBones[i] != null) {
|
||||
chestBones[i].localPosition = chestLocalPositions[i];
|
||||
chestBones[i].localRotation = chestLocalRotations[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called by the FBBIK each time before it reads the pose
|
||||
private void OnPreRead() {
|
||||
if (!enabled) return;
|
||||
if (!gameObject.activeInHierarchy) return;
|
||||
|
||||
if (ik.solver.iterations == 0) return;
|
||||
|
||||
ik.solver.FABRIKPass = handsPullBody;
|
||||
|
||||
if (bendBonesCount != bendBones.Length || ccdBonesCount != CCDBones.Length || stretchBonesCount != stretchBones.Length || chestBonesCount != chestBones.Length) OnStoreDefaultLocalState();
|
||||
|
||||
// Chest direction
|
||||
ChestDirection();
|
||||
|
||||
// Spine Bend
|
||||
SpineBend ();
|
||||
|
||||
// CCD
|
||||
CCDPass();
|
||||
|
||||
// Body
|
||||
offset = transform.position - ik.references.head.position;
|
||||
|
||||
shoulderDist = Vector3.Distance(ik.references.leftUpperArm.position, ik.references.rightUpperArm.position);
|
||||
leftShoulderDist = Vector3.Distance(ik.references.head.position, ik.references.leftUpperArm.position);
|
||||
rightShoulderDist = Vector3.Distance(ik.references.head.position, ik.references.rightUpperArm.position);
|
||||
|
||||
headToBody = ik.solver.rootNode.position - ik.references.head.position;
|
||||
headToLeftThigh = ik.references.leftThigh.position - ik.references.head.position;
|
||||
headToRightThigh = ik.references.rightThigh.position - ik.references.head.position;
|
||||
|
||||
leftShoulderPos = ik.references.leftUpperArm.position + offset * bodyWeight;
|
||||
rightShoulderPos = ik.references.rightUpperArm.position + offset * bodyWeight;
|
||||
|
||||
chestRotation = Quaternion.LookRotation(ik.references.head.position - ik.references.leftUpperArm.position, ik.references.rightUpperArm.position - ik.references.leftUpperArm.position);
|
||||
|
||||
if (OnPostHeadEffectorFK != null) OnPostHeadEffectorFK ();
|
||||
}
|
||||
|
||||
// Bending the spine to the head effector
|
||||
private void SpineBend() {
|
||||
float w = bendWeight * ik.solver.IKPositionWeight;
|
||||
|
||||
if (w <= 0f) return;
|
||||
if (bendBones.Length == 0) return;
|
||||
|
||||
Quaternion rotation = transform.rotation * Quaternion.Inverse(ik.references.root.rotation * headRotationRelativeToRoot);
|
||||
rotation = QuaTools.ClampRotation(rotation, bodyClampWeight, 2);
|
||||
|
||||
float step = 1f / bendBones.Length;
|
||||
|
||||
for (int i = 0; i < bendBones.Length; i++) {
|
||||
if (bendBones[i].transform != null) {
|
||||
bendBones[i].transform.rotation = Quaternion.Lerp(Quaternion.identity, rotation, step * bendBones[i].weight * w) * bendBones[i].transform.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Single CCD pass to make the spine less stiff
|
||||
private void CCDPass() {
|
||||
float w = CCDWeight * ik.solver.IKPositionWeight;
|
||||
|
||||
if (w <= 0f) return;
|
||||
|
||||
for (int i = CCDBones.Length - 1; i > -1; i--) {
|
||||
Quaternion r = Quaternion.FromToRotation(ik.references.head.position - CCDBones[i].position, transform.position - CCDBones[i].position) * CCDBones[i].rotation;
|
||||
float d = Mathf.Lerp((CCDBones.Length - i) / CCDBones.Length, 1f, roll);
|
||||
float a = Quaternion.Angle(Quaternion.identity, r);
|
||||
|
||||
a = Mathf.Lerp(0f, a, (damper - a) / damper);
|
||||
|
||||
CCDBones[i].rotation = Quaternion.RotateTowards(CCDBones[i].rotation, r, a * w * d);
|
||||
}
|
||||
}
|
||||
|
||||
//private float leftArmLength;
|
||||
|
||||
// Called by the FBBIK before each solver iteration
|
||||
private void Iterate(int iteration) {
|
||||
if (!enabled) return;
|
||||
if (!gameObject.activeInHierarchy) return;
|
||||
|
||||
if (ik.solver.iterations == 0) return;
|
||||
|
||||
// Shoulders
|
||||
leftShoulderPos = transform.position + (leftShoulderPos - transform.position).normalized * leftShoulderDist;
|
||||
rightShoulderPos = transform.position + (rightShoulderPos - transform.position).normalized * rightShoulderDist;
|
||||
|
||||
Solve (ref leftShoulderPos, ref rightShoulderPos, shoulderDist);
|
||||
|
||||
LerpSolverPosition(ik.solver.leftShoulderEffector, leftShoulderPos, positionWeight * ik.solver.IKPositionWeight, ik.solver.leftShoulderEffector.positionOffset);
|
||||
LerpSolverPosition(ik.solver.rightShoulderEffector, rightShoulderPos, positionWeight * ik.solver.IKPositionWeight, ik.solver.rightShoulderEffector.positionOffset);
|
||||
|
||||
// Body
|
||||
Quaternion chestRotationSolved = Quaternion.LookRotation(transform.position - leftShoulderPos, rightShoulderPos - leftShoulderPos);
|
||||
Quaternion rBody = QuaTools.FromToRotation(chestRotation, chestRotationSolved);
|
||||
|
||||
Vector3 headToBodySolved = rBody * headToBody;
|
||||
LerpSolverPosition(ik.solver.bodyEffector, transform.position + headToBodySolved, positionWeight * ik.solver.IKPositionWeight, ik.solver.bodyEffector.positionOffset - ik.solver.pullBodyOffset);
|
||||
|
||||
// Thighs
|
||||
Quaternion rThighs = Quaternion.Lerp(Quaternion.identity, rBody, thighWeight);
|
||||
|
||||
Vector3 headToLeftThighSolved = rThighs * headToLeftThigh;
|
||||
Vector3 headToRightThighSolved = rThighs * headToRightThigh;
|
||||
|
||||
|
||||
LerpSolverPosition(ik.solver.leftThighEffector, transform.position + headToLeftThighSolved, positionWeight * ik.solver.IKPositionWeight, (ik.solver.bodyEffector.positionOffset - ik.solver.pullBodyOffset) + ik.solver.leftThighEffector.positionOffset);
|
||||
LerpSolverPosition(ik.solver.rightThighEffector, transform.position + headToRightThighSolved, positionWeight * ik.solver.IKPositionWeight, (ik.solver.bodyEffector.positionOffset - ik.solver.pullBodyOffset) + ik.solver.rightThighEffector.positionOffset);
|
||||
}
|
||||
|
||||
// Called by the FBBIK each time it is finished updating
|
||||
private void OnPostUpdate() {
|
||||
if (!enabled) return;
|
||||
if (!gameObject.activeInHierarchy) return;
|
||||
|
||||
// Stretching the spine and neck
|
||||
PostStretching ();
|
||||
|
||||
// Rotate the head bone
|
||||
Quaternion headRotation = QuaTools.FromToRotation(ik.references.head.rotation, transform.rotation);
|
||||
headRotation = QuaTools.ClampRotation(headRotation, headClampWeight, 2);
|
||||
|
||||
ik.references.head.rotation = Quaternion.Lerp(Quaternion.identity, headRotation, rotationWeight * ik.solver.IKPositionWeight) * ik.references.head.rotation;
|
||||
}
|
||||
|
||||
private void ChestDirection() {
|
||||
float w = chestDirectionWeight * ik.solver.IKPositionWeight;
|
||||
if (w <= 0f) return;
|
||||
|
||||
bool changed = false;
|
||||
chestDirection = RootMotion.V3Tools.ClampDirection(chestDirection, ik.references.root.forward, 0.45f, 2, out changed);
|
||||
|
||||
if (chestDirection == Vector3.zero) return;
|
||||
|
||||
Quaternion q = Quaternion.FromToRotation (ik.references.root.forward, chestDirection);
|
||||
q = Quaternion.Lerp (Quaternion.identity, q, w * (1f / chestBones.Length));
|
||||
|
||||
foreach (Transform bone in chestBones) {
|
||||
bone.rotation = q * bone.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
// Stretching the spine/neck to help reach the target. This is most useful for making sure the head stays locked relative to the VR controller
|
||||
private void PostStretching() {
|
||||
float w = postStretchWeight * ik.solver.IKPositionWeight;
|
||||
|
||||
if (w > 0f) {
|
||||
Vector3 stretch = Vector3.ClampMagnitude(transform.position - ik.references.head.position, maxStretch);
|
||||
stretch *= w;
|
||||
|
||||
stretchDamper = Mathf.Max (stretchDamper, 0f);
|
||||
if (stretchDamper > 0f) stretch /= (1f + stretch.magnitude) * (1f + stretchDamper);
|
||||
|
||||
for (int i = 0; i < stretchBones.Length; i++) {
|
||||
if (stretchBones[i] != null) stretchBones[i].position += stretch / stretchBones.Length;
|
||||
}
|
||||
}
|
||||
if (fixHead && ik.solver.IKPositionWeight > 0f) ik.references.head.position = transform.position;
|
||||
}
|
||||
|
||||
// Interpolate the solver position of the effector
|
||||
private void LerpSolverPosition(IKEffector effector, Vector3 position, float weight, Vector3 offset) {
|
||||
effector.GetNode(ik.solver).solverPosition = Vector3.Lerp(effector.GetNode(ik.solver).solverPosition, position + offset, weight);
|
||||
}
|
||||
|
||||
// Solve a simple linear constraint
|
||||
private void Solve(ref Vector3 pos1, ref Vector3 pos2, float nominalDistance) {
|
||||
Vector3 direction = pos2 - pos1;
|
||||
|
||||
float distance = direction.magnitude;
|
||||
if (distance == nominalDistance) return;
|
||||
if (distance == 0f) return;
|
||||
|
||||
float force = 1f;
|
||||
|
||||
force *= 1f - nominalDistance / distance;
|
||||
|
||||
Vector3 offset = direction * force * 0.5f;
|
||||
|
||||
pos1 += offset;
|
||||
pos2 -= offset;
|
||||
}
|
||||
|
||||
// Clean up the delegates
|
||||
void OnDestroy() {
|
||||
if (ik != null) {
|
||||
ik.solver.OnPreRead -= OnPreRead;
|
||||
ik.solver.OnPreIteration -= Iterate;
|
||||
ik.solver.OnPostUpdate -= OnPostUpdate;
|
||||
ik.solver.OnStoreDefaultLocalState -= OnStoreDefaultLocalState;
|
||||
ik.solver.OnFixTransforms -= OnFixTransforms;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebbd066464934494f896947690872ad4
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,582 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// A chain of bones in IKSolverFullBody.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class FBIKChain {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// Linear constraint between child chains of a FBIKChain.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class ChildConstraint {
|
||||
|
||||
/// <summary>
|
||||
/// The push elasticity.
|
||||
/// </summary>
|
||||
public float pushElasticity = 0f;
|
||||
/// <summary>
|
||||
/// The pull elasticity.
|
||||
/// </summary>
|
||||
public float pullElasticity = 0f;
|
||||
/// <summary>
|
||||
/// The first bone.
|
||||
/// </summary>
|
||||
[SerializeField] private Transform bone1;
|
||||
/// <summary>
|
||||
/// The second bone.
|
||||
/// </summary>
|
||||
[SerializeField] private Transform bone2;
|
||||
|
||||
// Gets the nominal (animated) distance between the two bones.
|
||||
public float nominalDistance { get; private set; }
|
||||
|
||||
// The constraint is rigid if both push and pull elasticity are 0.
|
||||
public bool isRigid { get; private set; }
|
||||
|
||||
// The crossFade value between the connected chains
|
||||
private float crossFade, inverseCrossFade;
|
||||
|
||||
private int chain1Index;
|
||||
private int chain2Index;
|
||||
|
||||
/*
|
||||
* Constructor
|
||||
* */
|
||||
public ChildConstraint(Transform bone1, Transform bone2, float pushElasticity = 0f, float pullElasticity = 0f) {
|
||||
this.bone1 = bone1;
|
||||
this.bone2 = bone2;
|
||||
this.pushElasticity = pushElasticity;
|
||||
this.pullElasticity = pullElasticity;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiating the constraint
|
||||
* */
|
||||
public void Initiate(IKSolverFullBody solver) {
|
||||
chain1Index = solver.GetChainIndex(bone1);
|
||||
chain2Index = solver.GetChainIndex(bone2);
|
||||
|
||||
OnPreSolve(solver);
|
||||
}
|
||||
|
||||
/*
|
||||
* Updating nominal distance because it might have changed in the animation
|
||||
* */
|
||||
public void OnPreSolve(IKSolverFullBody solver) {
|
||||
nominalDistance = Vector3.Distance(solver.chain[chain1Index].nodes[0].transform.position, solver.chain[chain2Index].nodes[0].transform.position);
|
||||
|
||||
isRigid = pushElasticity <= 0 && pullElasticity <= 0;
|
||||
|
||||
// CrossFade
|
||||
if (isRigid) {
|
||||
float offset = solver.chain[chain1Index].pull - solver.chain[chain2Index].pull;
|
||||
crossFade = 1f - (0.5f + (offset * 0.5f));
|
||||
} else crossFade = 0.5f;
|
||||
|
||||
inverseCrossFade = 1f - crossFade;
|
||||
}
|
||||
|
||||
/*
|
||||
* Solving the constraint
|
||||
* */
|
||||
public void Solve(IKSolverFullBody solver) {
|
||||
if (pushElasticity >= 1 && pullElasticity >= 1) return;
|
||||
|
||||
Vector3 direction = solver.chain[chain2Index].nodes[0].solverPosition - solver.chain[chain1Index].nodes[0].solverPosition;
|
||||
|
||||
float distance = direction.magnitude;
|
||||
if (distance == nominalDistance) return;
|
||||
if (distance == 0f) return;
|
||||
|
||||
float force = 1f;
|
||||
|
||||
if (!isRigid) {
|
||||
float elasticity = distance > nominalDistance? pullElasticity: pushElasticity;
|
||||
force = 1f - elasticity;
|
||||
}
|
||||
|
||||
force *= 1f - nominalDistance / distance;
|
||||
|
||||
Vector3 offset = direction * force;
|
||||
|
||||
solver.chain[chain1Index].nodes[0].solverPosition += offset * crossFade;
|
||||
solver.chain[chain2Index].nodes[0].solverPosition -= offset * inverseCrossFade;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public enum Smoothing {
|
||||
None,
|
||||
Exponential,
|
||||
Cubic
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The pin weight. If closer to 1, the chain will be less influenced by child chains.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float pin;
|
||||
/// <summary>
|
||||
/// The weight of pulling the parent chain.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float pull = 1f;
|
||||
/// <summary>
|
||||
/// The weight of the end-effector pushing the shoulder/thigh when the end-effector is close to it.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float push;
|
||||
/// <summary>
|
||||
/// The amount of push force transferred to the parent (from hand or foot to the body).
|
||||
/// </summary>
|
||||
[Range(-1f, 1f)]
|
||||
public float pushParent;
|
||||
/// <summary>
|
||||
/// Only used in 3 segmented chains, pulls the first node closer to the third node.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float reach = 0.1f;
|
||||
/// <summary>
|
||||
/// Smoothing the effect of the Reach with the expense of some accuracy.
|
||||
/// </summary>
|
||||
public Smoothing reachSmoothing = Smoothing.Exponential;
|
||||
/// <summary>
|
||||
/// Smoothing the effect of the Push.
|
||||
/// </summary>
|
||||
public Smoothing pushSmoothing = Smoothing.Exponential;
|
||||
/// <summary>
|
||||
/// The nodes in this chain.
|
||||
/// </summary>
|
||||
public IKSolver.Node[] nodes = new IKSolver.Node[0];
|
||||
/// <summary>
|
||||
/// The child chains.
|
||||
/// </summary>
|
||||
public int[] children = new int[0];
|
||||
/// <summary>
|
||||
/// The child constraints are used for example for fixing the distance between left upper arm and right upper arm
|
||||
/// </summary>
|
||||
public ChildConstraint[] childConstraints = new ChildConstraint[0];
|
||||
/// <summary>
|
||||
/// Gets the bend constraint (if this chain has 3 segments).
|
||||
/// </summary>
|
||||
/// <value>The bend constraint.</value>
|
||||
public IKConstraintBend bendConstraint = new IKConstraintBend();
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
private float rootLength;
|
||||
private bool initiated;
|
||||
private float length;
|
||||
private float distance;
|
||||
private IKSolver.Point p;
|
||||
private float reachForce;
|
||||
private float pullParentSum;
|
||||
private float[] crossFades;
|
||||
private float sqrMag1, sqrMag2, sqrMagDif;
|
||||
private const float maxLimbLength = 0.99999f;
|
||||
|
||||
public FBIKChain() {}
|
||||
|
||||
public FBIKChain (float pin, float pull, params Transform[] nodeTransforms) {
|
||||
this.pin = pin;
|
||||
this.pull = pull;
|
||||
|
||||
SetNodes(nodeTransforms);
|
||||
|
||||
children = new int[0];
|
||||
}
|
||||
|
||||
/*
|
||||
* Set nodes to the following bone transforms.
|
||||
* */
|
||||
public void SetNodes(params Transform[] boneTransforms) {
|
||||
nodes = new IKSolver.Node[boneTransforms.Length];
|
||||
for (int i = 0; i < boneTransforms.Length; i++) {
|
||||
nodes[i] = new IKSolver.Node(boneTransforms[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public int GetNodeIndex(Transform boneTransform) {
|
||||
for (int i = 0; i < nodes.Length; i++) {
|
||||
if (nodes[i].transform == boneTransform) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if this chain is valid or not.
|
||||
* */
|
||||
public bool IsValid(ref string message) {
|
||||
if (nodes.Length == 0) {
|
||||
message = "FBIK chain contains no nodes.";
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (IKSolver.Node node in nodes) if (node.transform == null) {
|
||||
message = "Node transform is null in FBIK chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiating the chain.
|
||||
* */
|
||||
public void Initiate(IKSolverFullBody solver) {
|
||||
initiated = false;
|
||||
|
||||
foreach (IKSolver.Node node in nodes) {
|
||||
node.solverPosition = node.transform.position;
|
||||
}
|
||||
|
||||
// Calculating bone lengths
|
||||
CalculateBoneLengths(solver);
|
||||
|
||||
// Initiating child constraints
|
||||
foreach (ChildConstraint c in childConstraints) c.Initiate(solver as IKSolverFullBody);
|
||||
|
||||
// Initiating the bend constraint
|
||||
if (nodes.Length == 3) {
|
||||
bendConstraint.SetBones(nodes[0].transform, nodes[1].transform, nodes[2].transform);
|
||||
bendConstraint.Initiate(solver as IKSolverFullBody);
|
||||
}
|
||||
|
||||
crossFades = new float[children.Length];
|
||||
|
||||
initiated = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Before updating the chain
|
||||
* */
|
||||
public void ReadPose(IKSolverFullBody solver, bool fullBody) {
|
||||
if (!initiated) return;
|
||||
|
||||
for (int i = 0; i < nodes.Length; i++) {
|
||||
nodes[i].solverPosition = nodes[i].transform.position + nodes[i].offset;
|
||||
}
|
||||
|
||||
// Calculating bone lengths
|
||||
CalculateBoneLengths(solver);
|
||||
|
||||
if (fullBody) {
|
||||
// Pre-update child constraints
|
||||
for (int i = 0; i < childConstraints.Length; i++) childConstraints[i].OnPreSolve(solver);
|
||||
|
||||
if (children.Length > 0) {
|
||||
// PullSum
|
||||
float pullSum = nodes[nodes.Length - 1].effectorPositionWeight;
|
||||
for (int i = 0; i < children.Length; i++) pullSum += solver.chain[children[i]].nodes[0].effectorPositionWeight * solver.chain[children[i]].pull;
|
||||
pullSum = Mathf.Clamp(pullSum, 1f, Mathf.Infinity);
|
||||
|
||||
// CrossFades
|
||||
for (int i = 0; i < children.Length; i++) {
|
||||
crossFades[i] = (solver.chain[children[i]].nodes[0].effectorPositionWeight * solver.chain[children[i]].pull) / pullSum;
|
||||
}
|
||||
}
|
||||
|
||||
// Finding the total pull force by all child chains
|
||||
pullParentSum = 0f;
|
||||
for (int i = 0; i < children.Length; i++) pullParentSum += solver.chain[children[i]].pull;
|
||||
pullParentSum = Mathf.Clamp(pullParentSum, 1f, Mathf.Infinity);
|
||||
|
||||
// Reach force
|
||||
if (nodes.Length == 3) {
|
||||
reachForce = reach * Mathf.Clamp(nodes[2].effectorPositionWeight, 0f, 1f);
|
||||
} else reachForce = 0f;
|
||||
|
||||
if (push > 0f && nodes.Length > 1) distance = Vector3.Distance(nodes[0].transform.position, nodes[nodes.Length - 1].transform.position);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculates all bone lengths as well as lenghts between the chains
|
||||
private void CalculateBoneLengths(IKSolverFullBody solver) {
|
||||
// Calculating bone lengths
|
||||
length = 0f;
|
||||
|
||||
for (int i = 0; i < nodes.Length - 1; i++) {
|
||||
nodes[i].length = Vector3.Distance(nodes[i].transform.position, nodes[i + 1].transform.position);
|
||||
length += nodes[i].length;
|
||||
|
||||
if (nodes[i].length == 0) {
|
||||
Warning.Log("Bone " + nodes[i].transform.name + " - " + nodes[i + 1].transform.name + " length is zero, can not solve.", nodes[i].transform);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < children.Length; i++) {
|
||||
solver.chain[children[i]].rootLength = (solver.chain[children[i]].nodes[0].transform.position - nodes[nodes.Length - 1].transform.position).magnitude;
|
||||
|
||||
if (solver.chain[children[i]].rootLength == 0f) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (nodes.Length == 3) {
|
||||
// Square magnitude of the limb lengths
|
||||
sqrMag1 = nodes[0].length * nodes[0].length;
|
||||
sqrMag2 = nodes[1].length * nodes[1].length;
|
||||
sqrMagDif = sqrMag1 - sqrMag2;
|
||||
}
|
||||
}
|
||||
|
||||
#region Recursive Methods
|
||||
|
||||
/*
|
||||
* Reaching limbs
|
||||
* */
|
||||
public void Reach(IKSolverFullBody solver) {
|
||||
if (!initiated) return;
|
||||
|
||||
// Solve children first
|
||||
for (int i = 0; i < children.Length; i++) solver.chain[children[i]].Reach(solver);
|
||||
|
||||
if (reachForce <= 0f) return;
|
||||
|
||||
Vector3 solverDirection = nodes[2].solverPosition - nodes[0].solverPosition;
|
||||
if (solverDirection == Vector3.zero) return;
|
||||
|
||||
float solverLength = solverDirection.magnitude;
|
||||
|
||||
//Reaching
|
||||
Vector3 straight = (solverDirection / solverLength) * length;
|
||||
|
||||
float delta = Mathf.Clamp(solverLength / length, 1 - reachForce, 1 + reachForce) - 1f;
|
||||
delta = Mathf.Clamp(delta + reachForce, -1f, 1f);
|
||||
|
||||
// Smoothing the effect of Reach with the expense of some accuracy
|
||||
switch (reachSmoothing) {
|
||||
case Smoothing.Exponential:
|
||||
delta *= delta;
|
||||
break;
|
||||
case Smoothing.Cubic:
|
||||
delta *= delta * delta;
|
||||
break;
|
||||
}
|
||||
|
||||
Vector3 offset = straight * Mathf.Clamp(delta, 0f, solverLength);
|
||||
nodes[0].solverPosition += offset * (1f - nodes[0].effectorPositionWeight);
|
||||
nodes[2].solverPosition += offset;
|
||||
}
|
||||
|
||||
/*
|
||||
* End-effectors pushing the first nodes
|
||||
* */
|
||||
public Vector3 Push(IKSolverFullBody solver) {
|
||||
Vector3 sum = Vector3.zero;
|
||||
|
||||
// Get the push from the children
|
||||
for (int i = 0; i < children.Length; i++) {
|
||||
sum += solver.chain[children[i]].Push(solver) * solver.chain[children[i]].pushParent;
|
||||
}
|
||||
|
||||
// Apply the push from a child
|
||||
nodes[nodes.Length - 1].solverPosition += sum;
|
||||
|
||||
// Calculating the push of THIS chain (passed on to the parent as we're in a recursive method)
|
||||
if (nodes.Length < 2) return Vector3.zero;
|
||||
if (push <= 0f) return Vector3.zero;
|
||||
|
||||
Vector3 solverDirection = nodes[2].solverPosition - nodes[0].solverPosition;
|
||||
float solverLength = solverDirection.magnitude;
|
||||
if (solverLength == 0f) return Vector3.zero;
|
||||
|
||||
// Get the push force factor
|
||||
float f = 1f - (solverLength / distance);
|
||||
if (f <= 0f) return Vector3.zero;
|
||||
|
||||
// Push smoothing
|
||||
switch (pushSmoothing) {
|
||||
case Smoothing.Exponential:
|
||||
f *= f;
|
||||
break;
|
||||
case Smoothing.Cubic:
|
||||
f *= f * f;
|
||||
break;
|
||||
}
|
||||
|
||||
// The final push force
|
||||
Vector3 p = -solverDirection * f * push;
|
||||
|
||||
nodes[0].solverPosition += p;
|
||||
return p;
|
||||
}
|
||||
|
||||
/*
|
||||
* Applying trigonometric IK solver on the 3 segmented chains to relieve tension from the solver and increase accuracy.
|
||||
* */
|
||||
public void SolveTrigonometric(IKSolverFullBody solver, bool calculateBendDirection = false) {
|
||||
if (!initiated) return;
|
||||
|
||||
// Solve children first
|
||||
for (int i = 0; i < children.Length; i++) solver.chain[children[i]].SolveTrigonometric(solver, calculateBendDirection);
|
||||
|
||||
if (nodes.Length != 3) return;
|
||||
|
||||
// Direction of the limb in solver
|
||||
Vector3 solverDirection = nodes[2].solverPosition - nodes[0].solverPosition;
|
||||
|
||||
// Distance between the first and the last node solver positions
|
||||
float solverLength = solverDirection.magnitude;
|
||||
if (solverLength == 0f) return;
|
||||
|
||||
// Maximim stretch of the limb
|
||||
float maxMag = Mathf.Clamp(solverLength, 0f, length * maxLimbLength);
|
||||
Vector3 direction = (solverDirection / solverLength) * maxMag;
|
||||
|
||||
// Get the general world space bending direction
|
||||
Vector3 bendDirection = calculateBendDirection && bendConstraint.initiated? bendConstraint.GetDir(solver): nodes[1].solverPosition - nodes[0].solverPosition;
|
||||
|
||||
// Get the direction to the trigonometrically solved position of the second node
|
||||
Vector3 toBendPoint = GetDirToBendPoint(direction, bendDirection, maxMag);
|
||||
|
||||
// Position the second node
|
||||
nodes[1].solverPosition = nodes[0].solverPosition + toBendPoint;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stage 1 of the FABRIK algorithm
|
||||
* */
|
||||
public void Stage1(IKSolverFullBody solver) {
|
||||
// Stage 1
|
||||
for (int i = 0; i < children.Length; i++) solver.chain[children[i]].Stage1(solver);
|
||||
|
||||
// If is the last chain in this hierarchy, solve immediatelly and return
|
||||
if (children.Length == 0) {
|
||||
ForwardReach(nodes[nodes.Length - 1].solverPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 centroid = nodes[nodes.Length - 1].solverPosition;
|
||||
|
||||
// Satisfying child constraints
|
||||
SolveChildConstraints(solver);
|
||||
|
||||
// Finding the centroid position of all child chains according to their individual pull weights
|
||||
for (int i = 0; i < children.Length; i++) {
|
||||
Vector3 childPosition = solver.chain[children[i]].nodes[0].solverPosition;
|
||||
|
||||
if (solver.chain[children[i]].rootLength > 0) {
|
||||
childPosition = SolveFABRIKJoint(nodes[nodes.Length - 1].solverPosition, solver.chain[children[i]].nodes[0].solverPosition, solver.chain[children[i]].rootLength);
|
||||
}
|
||||
|
||||
if (pullParentSum > 0) centroid += (childPosition - nodes[nodes.Length - 1].solverPosition) * (solver.chain[children[i]].pull / pullParentSum);
|
||||
}
|
||||
|
||||
// Forward reach to the centroid (unless pinned)
|
||||
ForwardReach(Vector3.Lerp(centroid, nodes[nodes.Length - 1].solverPosition, pin));
|
||||
}
|
||||
|
||||
/*
|
||||
* Stage 2 of the FABRIK algorithm.
|
||||
* */
|
||||
public void Stage2(IKSolverFullBody solver, Vector3 position) {
|
||||
// Stage 2
|
||||
BackwardReach(position);
|
||||
|
||||
int it = Mathf.Clamp(solver.iterations, 2, 4);
|
||||
|
||||
// Iterating child constraints and child chains to make sure they are not conflicting
|
||||
if (childConstraints.Length > 0) {
|
||||
for (int i = 0; i < it; i++) SolveConstraintSystems(solver);
|
||||
}
|
||||
|
||||
// Stage 2 for the children
|
||||
for (int i = 0; i < children.Length; i++) solver.chain[children[i]].Stage2(solver, nodes[nodes.Length - 1].solverPosition);
|
||||
}
|
||||
|
||||
/*
|
||||
* Iterating child constraints and child chains to make sure they are not conflicting
|
||||
* */
|
||||
public void SolveConstraintSystems(IKSolverFullBody solver) {
|
||||
// Satisfy child constraints
|
||||
SolveChildConstraints(solver);
|
||||
|
||||
for (int i = 0; i < children.Length; i++) {
|
||||
SolveLinearConstraint(nodes[nodes.Length - 1], solver.chain[children[i]].nodes[0], crossFades[i], solver.chain[children[i]].rootLength);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Recursive Methods
|
||||
|
||||
/*
|
||||
* Interpolates the joint position to match the bone's length
|
||||
*/
|
||||
private Vector3 SolveFABRIKJoint(Vector3 pos1, Vector3 pos2, float length) {
|
||||
return pos2 + (pos1 - pos2).normalized * length;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculates the bend direction based on the law of cosines (from IKSolverTrigonometric).
|
||||
* */
|
||||
protected Vector3 GetDirToBendPoint(Vector3 direction, Vector3 bendDirection, float directionMagnitude) {
|
||||
float x = ((directionMagnitude * directionMagnitude) + sqrMagDif) / 2f / directionMagnitude;
|
||||
float y = (float)Math.Sqrt(Mathf.Clamp(sqrMag1 - x * x, 0, Mathf.Infinity));
|
||||
|
||||
if (direction == Vector3.zero) return Vector3.zero;
|
||||
return Quaternion.LookRotation(direction, bendDirection) * new Vector3(0f, y, x);
|
||||
}
|
||||
|
||||
/*
|
||||
* Satisfying child constraints
|
||||
* */
|
||||
private void SolveChildConstraints(IKSolverFullBody solver) {
|
||||
for (int i = 0; i < childConstraints.Length; i++) {
|
||||
childConstraints[i].Solve(solver);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Solve simple linear constraint
|
||||
* */
|
||||
private void SolveLinearConstraint(IKSolver.Node node1, IKSolver.Node node2, float crossFade, float distance) {
|
||||
Vector3 dir = node2.solverPosition - node1.solverPosition;
|
||||
|
||||
float mag = dir.magnitude;
|
||||
|
||||
if (distance == mag) return;
|
||||
if (mag == 0f) return;
|
||||
|
||||
Vector3 offset = dir * (1f - distance / mag);
|
||||
|
||||
node1.solverPosition += offset * crossFade;
|
||||
node2.solverPosition -= offset * (1f - crossFade);
|
||||
}
|
||||
|
||||
/*
|
||||
* FABRIK Forward reach
|
||||
* */
|
||||
public void ForwardReach(Vector3 position) {
|
||||
// Lerp last node's solverPosition to position
|
||||
nodes[nodes.Length - 1].solverPosition = position;
|
||||
|
||||
for (int i = nodes.Length - 2; i > -1; i--) {
|
||||
// Finding joint positions
|
||||
nodes[i].solverPosition = SolveFABRIKJoint(nodes[i].solverPosition, nodes[i + 1].solverPosition, nodes[i].length);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* FABRIK Backward reach
|
||||
* */
|
||||
private void BackwardReach(Vector3 position) {
|
||||
// Solve forst node only if it already hasn't been solved in SolveConstraintSystems
|
||||
if (rootLength > 0) position = SolveFABRIKJoint(nodes[0].solverPosition, position, rootLength);
|
||||
nodes[0].solverPosition = position;
|
||||
|
||||
// Finding joint positions
|
||||
for (int i = 1; i < nodes.Length; i++) {
|
||||
nodes[i].solverPosition = SolveFABRIKJoint(nodes[i].solverPosition, nodes[i - 1].solverPosition, nodes[i - 1].length);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d18c3d1b5f0274264811ca0b290c1c26
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,232 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// %Constraint used for fixing bend direction of 3-segment node chains in a node based %IK solver.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKConstraintBend {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The first bone.
|
||||
/// </summary>
|
||||
public Transform bone1;
|
||||
/// <summary>
|
||||
/// The second (bend) bone.
|
||||
/// </summary>
|
||||
public Transform bone2;
|
||||
/// <summary>
|
||||
/// The third bone.
|
||||
/// </summary>
|
||||
public Transform bone3;
|
||||
/// <summary>
|
||||
/// The bend goal Transform.
|
||||
/// </summary>
|
||||
public Transform bendGoal;
|
||||
|
||||
/// <summary>
|
||||
/// The bend direction.
|
||||
/// </summary>
|
||||
public Vector3 direction = Vector3.right;
|
||||
|
||||
/// <summary>
|
||||
/// The bend rotation offset.
|
||||
/// </summary>
|
||||
public Quaternion rotationOffset;
|
||||
|
||||
/// <summary>
|
||||
/// The weight. If weight is 1, will override effector rotation and the joint will be rotated at the direction. This enables for direct manipulation of the bend direction independent of effector rotation.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float weight = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this IKConstraintBend is valid.
|
||||
/// </summary>
|
||||
public bool IsValid(IKSolverFullBody solver, Warning.Logger logger) {
|
||||
if (bone1 == null || bone2 == null || bone3 == null) {
|
||||
if (logger != null) logger("Bend Constraint contains a null reference.");
|
||||
return false;
|
||||
}
|
||||
if (solver.GetPoint(bone1) == null) {
|
||||
if (logger != null) logger("Bend Constraint is referencing to a bone '" + bone1.name + "' that does not excist in the Node Chain.");
|
||||
return false;
|
||||
}
|
||||
if (solver.GetPoint(bone2) == null) {
|
||||
if (logger != null) logger("Bend Constraint is referencing to a bone '" + bone2.name + "' that does not excist in the Node Chain.");
|
||||
return false;
|
||||
}
|
||||
if (solver.GetPoint(bone3) == null) {
|
||||
if (logger != null) logger("Bend Constraint is referencing to a bone '" + bone3.name + "' that does not excist in the Node Chain.");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
public Vector3 defaultLocalDirection, defaultChildDirection;
|
||||
[System.NonSerializedAttribute] public float clampF = 0.505f;
|
||||
|
||||
//private IKSolver.Node node1, node2, node3;
|
||||
private int chainIndex1;
|
||||
private int nodeIndex1;
|
||||
private int chainIndex2;
|
||||
private int nodeIndex2;
|
||||
private int chainIndex3;
|
||||
private int nodeIndex3;
|
||||
|
||||
public bool initiated { get; private set; }
|
||||
private bool limbOrientationsSet;
|
||||
|
||||
public IKConstraintBend() {}
|
||||
|
||||
public IKConstraintBend(Transform bone1, Transform bone2, Transform bone3) {
|
||||
SetBones(bone1, bone2, bone3);
|
||||
}
|
||||
|
||||
public void SetBones(Transform bone1, Transform bone2, Transform bone3) {
|
||||
this.bone1 = bone1;
|
||||
this.bone2 = bone2;
|
||||
this.bone3 = bone3;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiate the constraint and set defaults
|
||||
* */
|
||||
public void Initiate(IKSolverFullBody solver) {
|
||||
solver.GetChainAndNodeIndexes(bone1, out chainIndex1, out nodeIndex1);
|
||||
solver.GetChainAndNodeIndexes(bone2, out chainIndex2, out nodeIndex2);
|
||||
solver.GetChainAndNodeIndexes(bone3, out chainIndex3, out nodeIndex3);
|
||||
|
||||
// Find the default bend direction orthogonal to the chain direction
|
||||
direction = OrthoToBone1(solver, OrthoToLimb(solver, bone2.position - bone1.position));
|
||||
|
||||
if (!limbOrientationsSet) {
|
||||
// Default bend direction relative to the first node
|
||||
defaultLocalDirection = Quaternion.Inverse(bone1.rotation) * direction;
|
||||
|
||||
// Default plane normal
|
||||
Vector3 defaultNormal = Vector3.Cross((bone3.position - bone1.position).normalized, direction);
|
||||
|
||||
// Default plane normal relative to the third node
|
||||
defaultChildDirection = Quaternion.Inverse(bone3.rotation) * defaultNormal;
|
||||
}
|
||||
|
||||
initiated = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make the limb bend towards the specified local directions of the bones
|
||||
* */
|
||||
public void SetLimbOrientation(Vector3 upper, Vector3 lower, Vector3 last) {
|
||||
if (upper == Vector3.zero) Debug.LogError("Attempting to set limb orientation to Vector3.zero axis");
|
||||
if (lower == Vector3.zero) Debug.LogError("Attempting to set limb orientation to Vector3.zero axis");
|
||||
if (last == Vector3.zero) Debug.LogError("Attempting to set limb orientation to Vector3.zero axis");
|
||||
|
||||
// Default bend direction relative to the first node
|
||||
defaultLocalDirection = upper.normalized;
|
||||
defaultChildDirection = last.normalized;
|
||||
|
||||
limbOrientationsSet = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Limits the bending joint of the limb to 90 degrees from the default 90 degrees of bend direction
|
||||
* */
|
||||
public void LimitBend(float solverWeight, float positionWeight) {
|
||||
if (!initiated) return;
|
||||
|
||||
Vector3 normalDirection = bone1.rotation * -defaultLocalDirection;
|
||||
|
||||
Vector3 axis2 = bone3.position - bone2.position;
|
||||
|
||||
// Clamp the direction from knee/elbow to foot/hand to valid range (90 degrees from right-angledly bent limb)
|
||||
bool changed = false;
|
||||
Vector3 clampedAxis2 = V3Tools.ClampDirection(axis2, normalDirection, clampF * solverWeight, 0, out changed);
|
||||
|
||||
Quaternion bone3Rotation = bone3.rotation;
|
||||
|
||||
if (changed) {
|
||||
Quaternion f = Quaternion.FromToRotation(axis2, clampedAxis2);
|
||||
bone2.rotation = f * bone2.rotation;
|
||||
}
|
||||
|
||||
// Rotating bend direction to normal when the limb is stretched out
|
||||
if (positionWeight > 0f) {
|
||||
Vector3 normal = bone2.position - bone1.position;
|
||||
Vector3 tangent = bone3.position - bone2.position;
|
||||
|
||||
Vector3.OrthoNormalize(ref normal, ref tangent);
|
||||
Quaternion q = Quaternion.FromToRotation(tangent, normalDirection);
|
||||
|
||||
bone2.rotation = Quaternion.Lerp(bone2.rotation, q * bone2.rotation, positionWeight * solverWeight);
|
||||
}
|
||||
|
||||
if (changed || positionWeight > 0f) bone3.rotation = bone3Rotation;
|
||||
}
|
||||
|
||||
/*
|
||||
* Computes the direction from the first node to the second node
|
||||
* */
|
||||
public Vector3 GetDir(IKSolverFullBody solver) {
|
||||
if (!initiated) return Vector3.zero;
|
||||
|
||||
float w = weight * solver.IKPositionWeight;
|
||||
|
||||
// Apply the bend goal
|
||||
if (bendGoal != null) {
|
||||
Vector3 b = bendGoal.position - solver.GetNode(chainIndex1, nodeIndex1).solverPosition;
|
||||
if (b != Vector3.zero) direction = b;
|
||||
}
|
||||
|
||||
if (w >= 1f) return direction.normalized;
|
||||
|
||||
Vector3 solverDirection = solver.GetNode(chainIndex3, nodeIndex3).solverPosition - solver.GetNode(chainIndex1, nodeIndex1).solverPosition;
|
||||
|
||||
// Get rotation from animated limb direction to solver limb direction
|
||||
Quaternion f = Quaternion.FromToRotation(bone3.position - bone1.position, solverDirection);
|
||||
|
||||
// Rotate the default bend direction by f
|
||||
Vector3 dir = f * (bone2.position - bone1.position);
|
||||
|
||||
// Effector rotation
|
||||
if (solver.GetNode(chainIndex3, nodeIndex3).effectorRotationWeight > 0f) {
|
||||
// Bend direction according to the effector rotation
|
||||
Vector3 effectorDirection = -Vector3.Cross(solverDirection, solver.GetNode(chainIndex3, nodeIndex3).solverRotation * defaultChildDirection);
|
||||
dir = Vector3.Lerp(dir, effectorDirection, solver.GetNode(chainIndex3, nodeIndex3).effectorRotationWeight);
|
||||
}
|
||||
|
||||
// Rotation Offset
|
||||
if (rotationOffset != Quaternion.identity) {
|
||||
Quaternion toOrtho = Quaternion.FromToRotation(rotationOffset * solverDirection, solverDirection);
|
||||
dir = toOrtho * rotationOffset * dir;
|
||||
}
|
||||
|
||||
if (w <= 0f) return dir;
|
||||
return Vector3.Lerp(dir, direction.normalized, w);
|
||||
}
|
||||
|
||||
/*
|
||||
* Ortho-Normalize a vector to the chain direction
|
||||
* */
|
||||
private Vector3 OrthoToLimb(IKSolverFullBody solver, Vector3 tangent) {
|
||||
Vector3 normal = solver.GetNode(chainIndex3, nodeIndex3).solverPosition - solver.GetNode(chainIndex1, nodeIndex1).solverPosition;
|
||||
Vector3.OrthoNormalize(ref normal, ref tangent);
|
||||
return tangent;
|
||||
}
|
||||
|
||||
/*
|
||||
* Ortho-Normalize a vector to the first bone direction
|
||||
* */
|
||||
private Vector3 OrthoToBone1(IKSolverFullBody solver, Vector3 tangent) {
|
||||
Vector3 normal = solver.GetNode(chainIndex2, nodeIndex2).solverPosition - solver.GetNode(chainIndex1, nodeIndex1).solverPosition;
|
||||
Vector3.OrthoNormalize(ref normal, ref tangent);
|
||||
return tangent;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89a2409fbbfb040b8b611279d6bba392
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,336 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Effector for manipulating node based %IK solvers.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKEffector {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// Gets the main node.
|
||||
/// </summary>
|
||||
public IKSolver.Node GetNode(IKSolverFullBody solver) {
|
||||
return solver.chain[chainIndex].nodes[nodeIndex];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The node transform used by this effector.
|
||||
/// </summary>
|
||||
public Transform bone;
|
||||
/// <summary>
|
||||
/// The target Transform (optional, you can use just the position and rotation instead).
|
||||
/// </summary>
|
||||
public Transform target;
|
||||
/// <summary>
|
||||
/// The position weight.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float positionWeight;
|
||||
/// <summary>
|
||||
/// The rotation weight.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float rotationWeight;
|
||||
/// <summary>
|
||||
/// The effector position in world space.
|
||||
/// </summary>
|
||||
public Vector3 position = Vector3.zero;
|
||||
/// <summary>
|
||||
/// The effector rotation relative to default rotation in world space.
|
||||
/// </summary>
|
||||
public Quaternion rotation = Quaternion.identity;
|
||||
/// <summary>
|
||||
/// The position offset in world space. positionOffset will be reset to Vector3.zero each frame after the solver is complete.
|
||||
/// </summary>
|
||||
public Vector3 positionOffset;
|
||||
/// <summary>
|
||||
/// Is this the last effector of a node chain?
|
||||
/// </summary>
|
||||
public bool isEndEffector { get; private set; }
|
||||
/// <summary>
|
||||
/// If false, child nodes will be ignored by this effector (if it has any).
|
||||
/// </summary>
|
||||
public bool effectChildNodes = true;
|
||||
/// <summary>
|
||||
/// Keeps the node position relative to the triangle defined by the plane bones (applies only to end-effectors).
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float maintainRelativePositionWeight;
|
||||
|
||||
/// <summary>
|
||||
/// Pins the effector to the animated position of its bone.
|
||||
/// </summary>
|
||||
public void PinToBone(float positionWeight, float rotationWeight) {
|
||||
position = bone.position;
|
||||
this.positionWeight = Mathf.Clamp(positionWeight, 0f, 1f);
|
||||
|
||||
rotation = bone.rotation;
|
||||
this.rotationWeight = Mathf.Clamp(rotationWeight, 0f, 1f);
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
public Transform[] childBones = new Transform[0]; // The optional list of other bones that positionOffset and position of this effector will be applied to.
|
||||
public Transform planeBone1; // The first bone defining the parent plane.
|
||||
public Transform planeBone2; // The second bone defining the parent plane.
|
||||
public Transform planeBone3; // The third bone defining the parent plane.
|
||||
public Quaternion planeRotationOffset = Quaternion.identity; // Used by Bend Constraints
|
||||
|
||||
private float posW, rotW;
|
||||
private Vector3[] localPositions = new Vector3[0];
|
||||
private bool usePlaneNodes;
|
||||
private Quaternion animatedPlaneRotation = Quaternion.identity;
|
||||
private Vector3 animatedPosition;
|
||||
private bool firstUpdate;
|
||||
private int chainIndex = -1;
|
||||
private int nodeIndex = -1;
|
||||
private int plane1ChainIndex;
|
||||
private int plane1NodeIndex = -1;
|
||||
private int plane2ChainIndex = -1;
|
||||
private int plane2NodeIndex = -1;
|
||||
private int plane3ChainIndex = -1;
|
||||
private int plane3NodeIndex = -1;
|
||||
private int[] childChainIndexes = new int[0];
|
||||
private int[] childNodeIndexes = new int[0];
|
||||
|
||||
public IKEffector() {}
|
||||
|
||||
public IKEffector (Transform bone, Transform[] childBones) {
|
||||
this.bone = bone;
|
||||
this.childBones = childBones;
|
||||
}
|
||||
|
||||
/*
|
||||
* Determines whether this IKEffector is valid or not.
|
||||
* */
|
||||
public bool IsValid(IKSolver solver, ref string message) {
|
||||
|
||||
if (bone == null) {
|
||||
message = "IK Effector bone is null.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (solver.GetPoint(bone) == null) {
|
||||
message = "IK Effector is referencing to a bone '" + bone.name + "' that does not excist in the Node Chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (Transform b in childBones) {
|
||||
if (b == null) {
|
||||
message = "IK Effector contains a null reference.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Transform b in childBones) {
|
||||
if (solver.GetPoint(b) == null) {
|
||||
message = "IK Effector is referencing to a bone '" + b.name + "' that does not excist in the Node Chain.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (planeBone1 != null && solver.GetPoint(planeBone1) == null) {
|
||||
message = "IK Effector is referencing to a bone '" + planeBone1.name + "' that does not excist in the Node Chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (planeBone2 != null && solver.GetPoint(planeBone2) == null) {
|
||||
message = "IK Effector is referencing to a bone '" + planeBone2.name + "' that does not excist in the Node Chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (planeBone3 != null && solver.GetPoint(planeBone3) == null) {
|
||||
message = "IK Effector is referencing to a bone '" + planeBone3.name + "' that does not excist in the Node Chain.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiate the effector, set default values
|
||||
* */
|
||||
public void Initiate(IKSolverFullBody solver) {
|
||||
position = bone.position;
|
||||
rotation = bone.rotation;
|
||||
animatedPlaneRotation = Quaternion.identity;
|
||||
|
||||
// Getting the node
|
||||
solver.GetChainAndNodeIndexes(bone, out chainIndex, out nodeIndex);
|
||||
|
||||
// Child nodes
|
||||
childChainIndexes = new int[childBones.Length];
|
||||
childNodeIndexes = new int[childBones.Length];
|
||||
|
||||
for (int i = 0; i < childBones.Length; i++) {
|
||||
solver.GetChainAndNodeIndexes(childBones[i], out childChainIndexes[i], out childNodeIndexes[i]);
|
||||
}
|
||||
|
||||
localPositions = new Vector3[childBones.Length];
|
||||
|
||||
// Plane nodes
|
||||
usePlaneNodes = false;
|
||||
|
||||
if (planeBone1 != null) {
|
||||
solver.GetChainAndNodeIndexes(planeBone1, out plane1ChainIndex, out plane1NodeIndex);
|
||||
|
||||
if (planeBone2 != null) {
|
||||
solver.GetChainAndNodeIndexes(planeBone2, out plane2ChainIndex, out plane2NodeIndex);
|
||||
|
||||
if (planeBone3 != null) {
|
||||
solver.GetChainAndNodeIndexes(planeBone3, out plane3ChainIndex, out plane3NodeIndex);
|
||||
|
||||
usePlaneNodes = true;
|
||||
}
|
||||
}
|
||||
|
||||
isEndEffector = true;
|
||||
} else isEndEffector = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Clear node offset
|
||||
* */
|
||||
public void ResetOffset(IKSolverFullBody solver) {
|
||||
solver.GetNode(chainIndex, nodeIndex).offset = Vector3.zero;
|
||||
|
||||
for (int i = 0; i < childChainIndexes.Length; i++) {
|
||||
solver.GetNode(childChainIndexes[i], childNodeIndexes[i]).offset = Vector3.zero;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the position and rotation to match the target
|
||||
* */
|
||||
public void SetToTarget() {
|
||||
if (target == null) return;
|
||||
position = target.position;
|
||||
rotation = target.rotation;
|
||||
}
|
||||
|
||||
/*
|
||||
* Presolving, applying offset
|
||||
* */
|
||||
public void OnPreSolve(IKSolverFullBody solver) {
|
||||
positionWeight = Mathf.Clamp(positionWeight, 0f, 1f);
|
||||
rotationWeight = Mathf.Clamp(rotationWeight, 0f, 1f);
|
||||
maintainRelativePositionWeight = Mathf.Clamp(maintainRelativePositionWeight, 0f, 1f);
|
||||
|
||||
// Calculating weights
|
||||
posW = positionWeight * solver.IKPositionWeight;
|
||||
rotW = rotationWeight * solver.IKPositionWeight;
|
||||
|
||||
solver.GetNode(chainIndex, nodeIndex).effectorPositionWeight = posW;
|
||||
solver.GetNode(chainIndex, nodeIndex).effectorRotationWeight = rotW;
|
||||
|
||||
solver.GetNode(chainIndex, nodeIndex).solverRotation = rotation;
|
||||
|
||||
if (float.IsInfinity(positionOffset.x) ||
|
||||
float.IsInfinity(positionOffset.y) ||
|
||||
float.IsInfinity(positionOffset.z)
|
||||
) Debug.LogError("Invalid IKEffector.positionOffset (contains Infinity)! Please make sure not to set IKEffector.positionOffset to infinite values.", bone);
|
||||
|
||||
if (float.IsNaN(positionOffset.x) ||
|
||||
float.IsNaN(positionOffset.y) ||
|
||||
float.IsNaN(positionOffset.z)
|
||||
) Debug.LogError("Invalid IKEffector.positionOffset (contains NaN)! Please make sure not to set IKEffector.positionOffset to NaN values.", bone);
|
||||
|
||||
if (positionOffset.sqrMagnitude > 10000000000f) Debug.LogError("Additive effector positionOffset detected in Full Body IK (extremely large value). Make sure you are not circularily adding to effector positionOffset each frame.", bone);
|
||||
|
||||
if (float.IsInfinity(position.x) ||
|
||||
float.IsInfinity(position.y) ||
|
||||
float.IsInfinity(position.z)
|
||||
) Debug.LogError("Invalid IKEffector.position (contains Infinity)!");
|
||||
|
||||
solver.GetNode(chainIndex, nodeIndex).offset += positionOffset * solver.IKPositionWeight;
|
||||
|
||||
if (effectChildNodes && solver.iterations > 0) {
|
||||
for (int i = 0; i < childBones.Length; i++) {
|
||||
localPositions[i] = childBones[i].transform.position - bone.transform.position;
|
||||
|
||||
solver.GetNode(childChainIndexes[i], childNodeIndexes[i]).offset += positionOffset * solver.IKPositionWeight;
|
||||
}
|
||||
}
|
||||
|
||||
// Relative to Plane
|
||||
if (usePlaneNodes && maintainRelativePositionWeight > 0f) {
|
||||
animatedPlaneRotation = Quaternion.LookRotation(planeBone2.position - planeBone1.position, planeBone3.position - planeBone1.position);;
|
||||
}
|
||||
|
||||
firstUpdate = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called after writing the pose
|
||||
* */
|
||||
public void OnPostWrite() {
|
||||
positionOffset = Vector3.zero;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotation of plane nodes in the solver
|
||||
* */
|
||||
private Quaternion GetPlaneRotation(IKSolverFullBody solver) {
|
||||
Vector3 p1 = solver.GetNode(plane1ChainIndex, plane1NodeIndex).solverPosition;
|
||||
Vector3 p2 = solver.GetNode(plane2ChainIndex, plane2NodeIndex).solverPosition;
|
||||
Vector3 p3 = solver.GetNode(plane3ChainIndex, plane3NodeIndex).solverPosition;
|
||||
|
||||
Vector3 viewingVector = p2 - p1;
|
||||
Vector3 upVector = p3 - p1;
|
||||
|
||||
if (viewingVector == Vector3.zero) {
|
||||
Warning.Log("Make sure you are not placing 2 or more FBBIK effectors of the same chain to exactly the same position.", bone);
|
||||
return Quaternion.identity;
|
||||
}
|
||||
|
||||
return Quaternion.LookRotation(viewingVector, upVector);
|
||||
}
|
||||
|
||||
/*
|
||||
* Manipulating node solverPosition
|
||||
* */
|
||||
public void Update(IKSolverFullBody solver) {
|
||||
if (firstUpdate) {
|
||||
animatedPosition = bone.position + solver.GetNode(chainIndex, nodeIndex).offset;
|
||||
firstUpdate = false;
|
||||
}
|
||||
|
||||
solver.GetNode(chainIndex, nodeIndex).solverPosition = Vector3.Lerp(GetPosition(solver, out planeRotationOffset), position, posW);
|
||||
|
||||
// Child nodes
|
||||
if (!effectChildNodes) return;
|
||||
|
||||
for (int i = 0; i < childBones.Length; i++) {
|
||||
solver.GetNode(childChainIndexes[i], childNodeIndexes[i]).solverPosition = Vector3.Lerp(solver.GetNode(childChainIndexes[i], childNodeIndexes[i]).solverPosition, solver.GetNode(chainIndex, nodeIndex).solverPosition + localPositions[i], posW);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the starting position of the iteration
|
||||
* */
|
||||
private Vector3 GetPosition(IKSolverFullBody solver, out Quaternion planeRotationOffset) {
|
||||
planeRotationOffset = Quaternion.identity;
|
||||
if (!isEndEffector) return solver.GetNode(chainIndex, nodeIndex).solverPosition; // non end-effectors are always free
|
||||
|
||||
if (maintainRelativePositionWeight <= 0f) return animatedPosition;
|
||||
|
||||
// Maintain relative position
|
||||
Vector3 p = bone.position;
|
||||
Vector3 dir = p - planeBone1.position;
|
||||
|
||||
planeRotationOffset = GetPlaneRotation(solver) * Quaternion.Inverse(animatedPlaneRotation);
|
||||
|
||||
p = solver.GetNode(plane1ChainIndex, plane1NodeIndex).solverPosition + planeRotationOffset * dir;
|
||||
|
||||
// Interpolate the rotation offset
|
||||
planeRotationOffset = Quaternion.Lerp(Quaternion.identity, planeRotationOffset, maintainRelativePositionWeight);
|
||||
|
||||
return Vector3.Lerp(animatedPosition, p + solver.GetNode(chainIndex, nodeIndex).offset, maintainRelativePositionWeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02c7adf75b099410fa394db3cdbd7137
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,336 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Maps a bone or a collection of bones to a node based %IK solver
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKMapping {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// Contains mapping information of a single bone
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class BoneMap {
|
||||
/// <summary>
|
||||
/// The transform.
|
||||
/// </summary>
|
||||
public Transform transform;
|
||||
/// <summary>
|
||||
/// The node in %IK Solver.
|
||||
/// </summary>
|
||||
//public IKSolver.Node node;
|
||||
|
||||
public int chainIndex = -1;
|
||||
public int nodeIndex = -1;
|
||||
|
||||
public Vector3 defaultLocalPosition;
|
||||
public Quaternion defaultLocalRotation;
|
||||
public Vector3 localSwingAxis, localTwistAxis, planePosition, ikPosition;
|
||||
public Quaternion defaultLocalTargetRotation;
|
||||
private Quaternion maintainRotation;
|
||||
public float length;
|
||||
public Quaternion animatedRotation;
|
||||
|
||||
private Transform planeBone1, planeBone2, planeBone3;
|
||||
private int plane1ChainIndex = -1;
|
||||
private int plane1NodeIndex = -1;
|
||||
private int plane2ChainIndex = -1;
|
||||
private int plane2NodeIndex = -1;
|
||||
private int plane3ChainIndex = -1;
|
||||
private int plane3NodeIndex = -1;
|
||||
|
||||
//private IKSolver.Node planeNode1, planeNode2, planeNode3;
|
||||
|
||||
public void Initiate(Transform transform, IKSolverFullBody solver) {
|
||||
this.transform = transform;
|
||||
|
||||
solver.GetChainAndNodeIndexes(transform, out chainIndex, out nodeIndex);
|
||||
//IKSolver.Point point = solver.GetPoint(transform);
|
||||
//this.node = point as IKSolver.Node;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current swing direction of the bone in world space.
|
||||
/// </summary>
|
||||
public Vector3 swingDirection {
|
||||
get {
|
||||
return transform.rotation * localSwingAxis;
|
||||
}
|
||||
}
|
||||
|
||||
public void StoreDefaultLocalState() {
|
||||
defaultLocalPosition = transform.localPosition;
|
||||
defaultLocalRotation = transform.localRotation;
|
||||
}
|
||||
|
||||
public void FixTransform(bool position) {
|
||||
if (position) transform.localPosition = defaultLocalPosition;
|
||||
transform.localRotation = defaultLocalRotation;
|
||||
}
|
||||
|
||||
#region Reading
|
||||
|
||||
/*
|
||||
* Does this bone have a node in the IK Solver?
|
||||
* */
|
||||
public bool isNodeBone {
|
||||
get {
|
||||
return nodeIndex != -1;
|
||||
//return node != null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate length of the bone
|
||||
* */
|
||||
public void SetLength(BoneMap nextBone) {
|
||||
length = Vector3.Distance(transform.position, nextBone.transform.position);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the direction to the swing target in local space
|
||||
* */
|
||||
public void SetLocalSwingAxis(BoneMap swingTarget) {
|
||||
SetLocalSwingAxis(swingTarget, this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the direction to the swing target in local space
|
||||
* */
|
||||
public void SetLocalSwingAxis(BoneMap bone1, BoneMap bone2) {
|
||||
localSwingAxis = Quaternion.Inverse(transform.rotation) * (bone1.transform.position - bone2.transform.position);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the direction to the twist target in local space
|
||||
* */
|
||||
public void SetLocalTwistAxis(Vector3 twistDirection, Vector3 normalDirection) {
|
||||
Vector3.OrthoNormalize(ref normalDirection, ref twistDirection);
|
||||
localTwistAxis = Quaternion.Inverse(transform.rotation) * twistDirection;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the 3 points defining a plane for this bone
|
||||
* */
|
||||
public void SetPlane(IKSolverFullBody solver, Transform planeBone1, Transform planeBone2, Transform planeBone3) {
|
||||
this.planeBone1 = planeBone1;
|
||||
this.planeBone2 = planeBone2;
|
||||
this.planeBone3 = planeBone3;
|
||||
|
||||
solver.GetChainAndNodeIndexes(planeBone1, out plane1ChainIndex, out plane1NodeIndex);
|
||||
solver.GetChainAndNodeIndexes(planeBone2, out plane2ChainIndex, out plane2NodeIndex);
|
||||
solver.GetChainAndNodeIndexes(planeBone3, out plane3ChainIndex, out plane3NodeIndex);
|
||||
|
||||
//this.planeNode1 = planeNode1;
|
||||
//this.planeNode2 = planeNode2;
|
||||
//this.planeNode3 = planeNode3;
|
||||
|
||||
UpdatePlane(true, true);
|
||||
}
|
||||
|
||||
/*
|
||||
* Updates the 3 plane points
|
||||
* */
|
||||
public void UpdatePlane(bool rotation, bool position) {
|
||||
Quaternion t = lastAnimatedTargetRotation;
|
||||
|
||||
if (rotation) defaultLocalTargetRotation = QuaTools.RotationToLocalSpace(transform.rotation, t);
|
||||
if (position) planePosition = Quaternion.Inverse(t) * (transform.position - planeBone1.position);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sets the virtual position for this bone
|
||||
* */
|
||||
public void SetIKPosition() {
|
||||
ikPosition = transform.position;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stores the current rotation for later use.
|
||||
* */
|
||||
public void MaintainRotation() {
|
||||
maintainRotation = transform.rotation;
|
||||
}
|
||||
|
||||
#endregion Reading
|
||||
|
||||
#region Writing
|
||||
|
||||
/*
|
||||
* Moves the bone to its virtual position
|
||||
* */
|
||||
public void SetToIKPosition() {
|
||||
transform.position = ikPosition;
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the bone to the solver position of its node
|
||||
* */
|
||||
public void FixToNode(IKSolverFullBody solver, float weight, IKSolver.Node fixNode = null) {
|
||||
if (fixNode == null) fixNode = solver.GetNode(chainIndex, nodeIndex);
|
||||
|
||||
if (weight >= 1f) {
|
||||
transform.position = fixNode.solverPosition;
|
||||
return;
|
||||
}
|
||||
|
||||
transform.position = Vector3.Lerp(transform.position, fixNode.solverPosition, weight);
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the bone's position relative to its 3 plane nodes
|
||||
* */
|
||||
public Vector3 GetPlanePosition(IKSolverFullBody solver) {
|
||||
return solver.GetNode(plane1ChainIndex, plane1NodeIndex).solverPosition + (GetTargetRotation(solver) * planePosition);
|
||||
//return planeNode1.solverPosition + (targetRotation * planePosition);
|
||||
}
|
||||
|
||||
/*
|
||||
* Positions the bone relative to its 3 plane nodes
|
||||
* */
|
||||
public void PositionToPlane(IKSolverFullBody solver) {
|
||||
transform.position = GetPlanePosition(solver);
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotates the bone relative to its 3 plane nodes
|
||||
* */
|
||||
public void RotateToPlane(IKSolverFullBody solver, float weight) {
|
||||
Quaternion r = GetTargetRotation(solver) * defaultLocalTargetRotation;
|
||||
|
||||
if (weight >= 1f) {
|
||||
transform.rotation = r;
|
||||
return;
|
||||
}
|
||||
|
||||
transform.rotation = Quaternion.Lerp(transform.rotation, r, weight);
|
||||
}
|
||||
|
||||
/*
|
||||
* Swings to the swing target
|
||||
* */
|
||||
public void Swing(Vector3 swingTarget, float weight) {
|
||||
Swing(swingTarget, transform.position, weight);
|
||||
}
|
||||
|
||||
/*
|
||||
* Swings to a direction from pos2 to pos1
|
||||
* */
|
||||
public void Swing(Vector3 pos1, Vector3 pos2, float weight) {
|
||||
Quaternion r = Quaternion.FromToRotation(transform.rotation * localSwingAxis, pos1 - pos2) * transform.rotation;
|
||||
|
||||
if (weight >= 1f) {
|
||||
transform.rotation = r;
|
||||
return;
|
||||
}
|
||||
|
||||
transform.rotation = Quaternion.Lerp(transform.rotation, r, weight);
|
||||
}
|
||||
|
||||
/*
|
||||
* Twists to the twist target
|
||||
* */
|
||||
public void Twist(Vector3 twistDirection, Vector3 normalDirection, float weight) {
|
||||
Vector3.OrthoNormalize(ref normalDirection, ref twistDirection);
|
||||
|
||||
Quaternion r = Quaternion.FromToRotation(transform.rotation * localTwistAxis, twistDirection) * transform.rotation;
|
||||
|
||||
if (weight >= 1f) {
|
||||
transform.rotation = r;
|
||||
return;
|
||||
}
|
||||
|
||||
transform.rotation = Quaternion.Lerp(transform.rotation, r, weight);
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotates back to the last animated local rotation
|
||||
* */
|
||||
public void RotateToMaintain(float weight) {
|
||||
if (weight <= 0f) return;
|
||||
|
||||
transform.rotation = Quaternion.Lerp(transform.rotation, maintainRotation, weight);
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotates to match the effector rotation
|
||||
* */
|
||||
public void RotateToEffector(IKSolverFullBody solver, float weight) {
|
||||
if (!isNodeBone) return;
|
||||
float w = weight * solver.GetNode(chainIndex, nodeIndex).effectorRotationWeight;
|
||||
if (w <= 0f) return;
|
||||
|
||||
if (w >= 1f) {
|
||||
transform.rotation = solver.GetNode(chainIndex, nodeIndex).solverRotation;
|
||||
return;
|
||||
}
|
||||
|
||||
transform.rotation = Quaternion.Lerp(transform.rotation, solver.GetNode(chainIndex, nodeIndex).solverRotation, w);
|
||||
}
|
||||
|
||||
#endregion Writing
|
||||
|
||||
/*
|
||||
* Rotation of plane nodes in the solver
|
||||
* */
|
||||
private Quaternion GetTargetRotation(IKSolverFullBody solver) {
|
||||
Vector3 p1 = solver.GetNode(plane1ChainIndex, plane1NodeIndex).solverPosition;
|
||||
Vector3 p2 = solver.GetNode(plane2ChainIndex, plane2NodeIndex).solverPosition;
|
||||
Vector3 p3 = solver.GetNode(plane3ChainIndex, plane3NodeIndex).solverPosition;
|
||||
|
||||
if (p1 == p3) return Quaternion.identity;
|
||||
return Quaternion.LookRotation(p2 - p1, p3 - p1);
|
||||
|
||||
//if (planeNode1.solverPosition == planeNode3.solverPosition) return Quaternion.identity;
|
||||
//return Quaternion.LookRotation(planeNode2.solverPosition - planeNode1.solverPosition, planeNode3.solverPosition - planeNode1.solverPosition);
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotation of plane nodes in the animation
|
||||
* */
|
||||
private Quaternion lastAnimatedTargetRotation {
|
||||
get {
|
||||
if (planeBone1.position == planeBone3.position) return Quaternion.identity;
|
||||
return Quaternion.LookRotation(planeBone2.position - planeBone1.position, planeBone3.position - planeBone1.position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this IKMapping is valid.
|
||||
/// </summary>
|
||||
public virtual bool IsValid(IKSolver solver, ref string message) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
public virtual void Initiate(IKSolverFullBody solver) {}
|
||||
|
||||
protected bool BoneIsValid(Transform bone, IKSolver solver, ref string message, Warning.Logger logger = null) {
|
||||
if (bone == null) {
|
||||
message = "IKMappingLimb contains a null reference.";
|
||||
if (logger != null) logger(message);
|
||||
return false;
|
||||
}
|
||||
if (solver.GetPoint(bone) == null) {
|
||||
message = "IKMappingLimb is referencing to a bone '" + bone.name + "' that does not excist in the Node Chain.";
|
||||
if (logger != null) logger(message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Interpolates the joint position to match the bone's length
|
||||
*/
|
||||
protected Vector3 SolveFABRIKJoint(Vector3 pos1, Vector3 pos2, float length) {
|
||||
return pos2 + (pos1 - pos2).normalized * length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea73ef4abb3e747839d91973dd0bb3cb
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,78 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Maps a single bone to a node in %IK Solver
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKMappingBone: IKMapping {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The bone transform.
|
||||
/// </summary>
|
||||
public Transform bone;
|
||||
|
||||
/// <summary>
|
||||
/// The weight of maintaining the bone's rotation after solver has finished.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float maintainRotationWeight = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this IKMappingBone is valid.
|
||||
/// </summary>
|
||||
public override bool IsValid(IKSolver solver, ref string message) {
|
||||
if (!base.IsValid(solver, ref message)) return false;
|
||||
|
||||
if (bone == null) {
|
||||
message = "IKMappingBone's bone is null.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
private BoneMap boneMap = new BoneMap();
|
||||
|
||||
public IKMappingBone() {}
|
||||
|
||||
public IKMappingBone(Transform bone) {
|
||||
this.bone = bone;
|
||||
}
|
||||
|
||||
public void StoreDefaultLocalState() {
|
||||
boneMap.StoreDefaultLocalState();
|
||||
}
|
||||
|
||||
public void FixTransforms() {
|
||||
boneMap.FixTransform(false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiating and setting defaults
|
||||
* */
|
||||
public override void Initiate(IKSolverFullBody solver) {
|
||||
if (boneMap == null) boneMap = new BoneMap();
|
||||
|
||||
boneMap.Initiate(bone, solver);
|
||||
}
|
||||
|
||||
/*
|
||||
* Pre-solving
|
||||
* */
|
||||
public void ReadPose() {
|
||||
boneMap.MaintainRotation();
|
||||
}
|
||||
|
||||
public void WritePose(float solverWeight) {
|
||||
// Rotating back to the last maintained rotation
|
||||
boneMap.RotateToMaintain(solverWeight * maintainRotationWeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b454b4eecba84e17a9a861f36967230
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,184 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Maps a 3-segmented bone hierarchy to a node chain of an %IK Solver
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKMappingLimb: IKMapping {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// Limb Bone Map type
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public enum BoneMapType {
|
||||
Parent,
|
||||
Bone1,
|
||||
Bone2,
|
||||
Bone3
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The optional parent bone (clavicle).
|
||||
/// </summary>
|
||||
public Transform parentBone;
|
||||
/// <summary>
|
||||
/// The first bone (upper arm or thigh).
|
||||
/// </summary>
|
||||
public Transform bone1;
|
||||
/// <summary>
|
||||
/// The second bone (forearm or calf).
|
||||
/// </summary>
|
||||
public Transform bone2;
|
||||
/// <summary>
|
||||
/// The third bone (hand or foot).
|
||||
/// </summary>
|
||||
public Transform bone3;
|
||||
/// <summary>
|
||||
/// The weight of maintaining the third bone's rotation as it was in the animation
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float maintainRotationWeight;
|
||||
/// <summary>
|
||||
/// The weight of mapping the limb to its IK pose. This can be useful if you want to disable the effect of IK for the limb.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float weight = 1f; // Added in 0.2
|
||||
/// <summary>
|
||||
/// Disable this to maintain original sampled rotations of the limb bones relative to each other.
|
||||
/// </summary>
|
||||
[System.NonSerializedAttribute] public bool updatePlaneRotations = true;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether this IKMappingLimb is valid
|
||||
/// </summary>
|
||||
public override bool IsValid(IKSolver solver, ref string message) {
|
||||
if (!base.IsValid(solver, ref message)) return false;
|
||||
|
||||
if (!BoneIsValid(bone1, solver, ref message)) return false;
|
||||
if (!BoneIsValid(bone2, solver, ref message)) return false;
|
||||
if (!BoneIsValid(bone3, solver, ref message)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bone map of the specified bone.
|
||||
/// </summary>
|
||||
public BoneMap GetBoneMap(BoneMapType boneMap) {
|
||||
switch(boneMap) {
|
||||
case BoneMapType.Parent:
|
||||
if (parentBone == null) Warning.Log("This limb does not have a parent (shoulder) bone", bone1);
|
||||
return boneMapParent;
|
||||
case BoneMapType.Bone1: return boneMap1;
|
||||
case BoneMapType.Bone2: return boneMap2;
|
||||
default: return boneMap3;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes the limb mapped to the specific local directions of the bones. Added in 0.3
|
||||
/// </summary>
|
||||
public void SetLimbOrientation(Vector3 upper, Vector3 lower) {
|
||||
boneMap1.defaultLocalTargetRotation = Quaternion.Inverse(Quaternion.Inverse(bone1.rotation) * Quaternion.LookRotation(bone2.position - bone1.position, bone1.rotation * -upper));
|
||||
boneMap2.defaultLocalTargetRotation = Quaternion.Inverse(Quaternion.Inverse(bone2.rotation) * Quaternion.LookRotation(bone3.position - bone2.position, bone2.rotation * -lower));
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
private BoneMap boneMapParent = new BoneMap(), boneMap1 = new BoneMap(), boneMap2 = new BoneMap(), boneMap3 = new BoneMap();
|
||||
|
||||
public IKMappingLimb() {}
|
||||
|
||||
public IKMappingLimb(Transform bone1, Transform bone2, Transform bone3, Transform parentBone = null) {
|
||||
SetBones(bone1, bone2, bone3, parentBone);
|
||||
}
|
||||
|
||||
public void SetBones(Transform bone1, Transform bone2, Transform bone3, Transform parentBone = null) {
|
||||
this.bone1 = bone1;
|
||||
this.bone2 = bone2;
|
||||
this.bone3 = bone3;
|
||||
this.parentBone = parentBone;
|
||||
}
|
||||
|
||||
public void StoreDefaultLocalState() {
|
||||
if (parentBone != null) boneMapParent.StoreDefaultLocalState();
|
||||
boneMap1.StoreDefaultLocalState();
|
||||
boneMap2.StoreDefaultLocalState();
|
||||
boneMap3.StoreDefaultLocalState();
|
||||
}
|
||||
|
||||
public void FixTransforms() {
|
||||
if (parentBone != null) boneMapParent.FixTransform(false);
|
||||
boneMap1.FixTransform(true);
|
||||
boneMap2.FixTransform(false);
|
||||
boneMap3.FixTransform(false);
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiating and setting defaults
|
||||
* */
|
||||
public override void Initiate(IKSolverFullBody solver) {
|
||||
if (boneMapParent == null) boneMapParent = new BoneMap();
|
||||
if (boneMap1 == null) boneMap1 = new BoneMap();
|
||||
if (boneMap2 == null) boneMap2 = new BoneMap();
|
||||
if (boneMap3 == null) boneMap3 = new BoneMap();
|
||||
|
||||
// Finding the nodes
|
||||
if (parentBone != null) boneMapParent.Initiate(parentBone, solver);
|
||||
boneMap1.Initiate(bone1, solver);
|
||||
boneMap2.Initiate(bone2, solver);
|
||||
boneMap3.Initiate(bone3, solver);
|
||||
|
||||
// Define plane points for the bone maps
|
||||
boneMap1.SetPlane(solver, boneMap1.transform, boneMap2.transform, boneMap3.transform);
|
||||
boneMap2.SetPlane(solver, boneMap2.transform, boneMap3.transform, boneMap1.transform);
|
||||
|
||||
// Find the swing axis for the parent bone
|
||||
if (parentBone != null) boneMapParent.SetLocalSwingAxis(boneMap1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Presolving the bones and maintaining rotation
|
||||
* */
|
||||
public void ReadPose() {
|
||||
boneMap1.UpdatePlane(updatePlaneRotations, true);
|
||||
boneMap2.UpdatePlane(updatePlaneRotations, false);
|
||||
|
||||
// Clamping weights
|
||||
weight = Mathf.Clamp(weight, 0f, 1f);
|
||||
|
||||
// Define plane points for the bone maps
|
||||
boneMap3.MaintainRotation();
|
||||
}
|
||||
|
||||
public void WritePose(IKSolverFullBody solver, bool fullBody) {
|
||||
if (weight <= 0f) return;
|
||||
|
||||
// Swing the parent bone to look at the first node's position
|
||||
if (fullBody) {
|
||||
if (parentBone != null) {
|
||||
boneMapParent.Swing(solver.GetNode(boneMap1.chainIndex, boneMap1.nodeIndex).solverPosition, weight);
|
||||
//boneMapParent.Swing(boneMap1.node.solverPosition, weight);
|
||||
}
|
||||
|
||||
// Fix the first bone to its node
|
||||
boneMap1.FixToNode(solver, weight);
|
||||
}
|
||||
|
||||
// Rotate the 2 first bones to the plane points
|
||||
boneMap1.RotateToPlane(solver, weight);
|
||||
boneMap2.RotateToPlane(solver, weight);
|
||||
|
||||
// Rotate the third bone to the rotation it had before solving
|
||||
boneMap3.RotateToMaintain(maintainRotationWeight * weight * solver.IKPositionWeight);
|
||||
|
||||
// Rotate the third bone to the effector rotation
|
||||
boneMap3.RotateToEffector(solver, weight);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ef3f7e989de854a09aaf17ab4f391645
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,300 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d268325743f394a8a8e524836dceecf1
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,453 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2613038c163cb4eb4b8ca6f6b9021588
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,251 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Rotates a hierarchy of bones to make a Transform aim at a target.
|
||||
/// If there are problems with continuity and the solver get's jumpy, make sure to keep IKPosition at a safe distance from the transform and try decreasing solver and bone weights.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKSolverAim : IKSolverHeuristic {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The transform that we want to aim at IKPosition.
|
||||
/// </summary>
|
||||
public Transform transform;
|
||||
/// <summary>
|
||||
/// The local axis of the Transform that you want to be aimed at IKPosition.
|
||||
/// </summary>
|
||||
public Vector3 axis = Vector3.forward;
|
||||
/// <summary>
|
||||
/// Keeps that axis of the Aim Transform directed at the polePosition.
|
||||
/// </summary>
|
||||
public Vector3 poleAxis = Vector3.up;
|
||||
/// <summary>
|
||||
/// The position in world space to keep the pole axis of the Aim Transform directed at.
|
||||
/// </summary>
|
||||
public Vector3 polePosition;
|
||||
/// <summary>
|
||||
/// The weight of the Pole.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float poleWeight;
|
||||
/// <summary>
|
||||
/// If assigned, will automatically set polePosition to the position of this Transform.
|
||||
/// </summary>
|
||||
public Transform poleTarget;
|
||||
/// <summary>
|
||||
/// Clamping rotation of the solver. 0 is free rotation, 1 is completely clamped to transform axis.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float clampWeight = 0.1f;
|
||||
/// <summary>
|
||||
/// Number of sine smoothing iterations applied to clamping to make it smoother.
|
||||
/// </summary>
|
||||
[Range(0, 2)]
|
||||
public int clampSmoothing = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the angular offset.
|
||||
/// </summary>
|
||||
public float GetAngle() {
|
||||
return Vector3.Angle(transformAxis, IKPosition - transform.position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Axis of the AimTransform is world space.
|
||||
/// </summary>
|
||||
public Vector3 transformAxis {
|
||||
get {
|
||||
return transform.rotation * axis;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Pole Axis of the AimTransform is world space.
|
||||
/// </summary>
|
||||
public Vector3 transformPoleAxis {
|
||||
get {
|
||||
return transform.rotation * poleAxis;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before each iteration of the solver.
|
||||
/// </summary>
|
||||
public IterationDelegate OnPreIteration;
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
protected override void OnInitiate() {
|
||||
if ((firstInitiation || !Application.isPlaying) && transform != null) {
|
||||
IKPosition = transform.position + transformAxis * 3f;
|
||||
polePosition = transform.position + transformPoleAxis * 3f;
|
||||
}
|
||||
|
||||
// Disable Rotation Limits from updating to take control of their execution order
|
||||
for (int i = 0; i < bones.Length; i++) {
|
||||
if (bones[i].rotationLimit != null) bones[i].rotationLimit.Disable();
|
||||
}
|
||||
|
||||
step = 1f / (float)bones.Length;
|
||||
if (Application.isPlaying) axis = axis.normalized;
|
||||
}
|
||||
|
||||
protected override void OnUpdate() {
|
||||
if (axis == Vector3.zero) {
|
||||
if (!Warning.logged) LogWarning("IKSolverAim axis is Vector3.zero.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (poleAxis == Vector3.zero && poleWeight > 0f) {
|
||||
if (!Warning.logged) LogWarning("IKSolverAim poleAxis is Vector3.zero.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (target != null) IKPosition = target.position;
|
||||
if (poleTarget != null) polePosition = poleTarget.position;
|
||||
|
||||
if (XY) IKPosition.z = bones[0].transform.position.z;
|
||||
|
||||
// Clamping weights
|
||||
if (IKPositionWeight <= 0) return;
|
||||
IKPositionWeight = Mathf.Clamp(IKPositionWeight, 0f, 1f);
|
||||
|
||||
// Rotation Limit on the Aim Transform
|
||||
if (transform != lastTransform) {
|
||||
transformLimit = transform.GetComponent<RotationLimit>();
|
||||
if (transformLimit != null) transformLimit.enabled = false;
|
||||
lastTransform = transform;
|
||||
}
|
||||
|
||||
if (transformLimit != null) transformLimit.Apply();
|
||||
|
||||
// In case transform becomes unassigned in runtime
|
||||
if (transform == null) {
|
||||
if (!Warning.logged) LogWarning("Aim Transform unassigned in Aim IK solver. Please Assign a Transform (lineal descendant to the last bone in the spine) that you want to be aimed at IKPosition");
|
||||
return;
|
||||
}
|
||||
|
||||
clampWeight = Mathf.Clamp(clampWeight, 0f, 1f);
|
||||
clampedIKPosition = GetClampedIKPosition();
|
||||
|
||||
Vector3 dir = clampedIKPosition - transform.position;
|
||||
dir = Vector3.Slerp(transformAxis * dir.magnitude, dir, IKPositionWeight);
|
||||
clampedIKPosition = transform.position + dir;
|
||||
|
||||
// Iterating the solver
|
||||
for (int i = 0; i < maxIterations; i++) {
|
||||
|
||||
// Optimizations
|
||||
if (i >= 1 && tolerance > 0 && GetAngle() < tolerance) break;
|
||||
lastLocalDirection = localDirection;
|
||||
|
||||
if (OnPreIteration != null) OnPreIteration(i);
|
||||
|
||||
Solve();
|
||||
}
|
||||
|
||||
lastLocalDirection = localDirection;
|
||||
}
|
||||
|
||||
protected override int minBones { get { return 1; }}
|
||||
|
||||
private float step;
|
||||
private Vector3 clampedIKPosition;
|
||||
private RotationLimit transformLimit;
|
||||
private Transform lastTransform;
|
||||
|
||||
/*
|
||||
* Solving the hierarchy
|
||||
* */
|
||||
private void Solve() {
|
||||
// Rotating bones to get closer to target.
|
||||
for (int i = 0; i < bones.Length - 1; i++) RotateToTarget(clampedIKPosition, bones[i], step * (i + 1) * IKPositionWeight * bones[i].weight);
|
||||
RotateToTarget(clampedIKPosition, bones[bones.Length - 1], IKPositionWeight * bones[bones.Length - 1].weight);
|
||||
}
|
||||
|
||||
/*
|
||||
* Clamping the IKPosition to legal range
|
||||
* */
|
||||
private Vector3 GetClampedIKPosition() {
|
||||
if (clampWeight <= 0f) return IKPosition;
|
||||
if (clampWeight >= 1f) return transform.position + transformAxis * (IKPosition - transform.position).magnitude;
|
||||
|
||||
// Getting the dot product of IK direction and transformAxis
|
||||
//float dot = (Vector3.Dot(transformAxis, (IKPosition - transform.position).normalized) + 1) * 0.5f;
|
||||
float angle = Vector3.Angle(transformAxis, (IKPosition - transform.position));
|
||||
float dot = 1f - (angle / 180f);
|
||||
|
||||
// Clamping the target
|
||||
float targetClampMlp = clampWeight > 0? Mathf.Clamp(1f - ((clampWeight - dot) / (1f - dot)), 0f, 1f): 1f;
|
||||
|
||||
// Calculating the clamp multiplier
|
||||
float clampMlp = clampWeight > 0? Mathf.Clamp(dot / clampWeight, 0f, 1f): 1f;
|
||||
|
||||
for (int i = 0; i < clampSmoothing; i++) {
|
||||
float sinF = clampMlp * Mathf.PI * 0.5f;
|
||||
clampMlp = Mathf.Sin(sinF);
|
||||
}
|
||||
|
||||
// Slerping the IK direction (don't use Lerp here, it breaks it)
|
||||
return transform.position + Vector3.Slerp(transformAxis * 10f, IKPosition - transform.position, clampMlp * targetClampMlp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotating bone to get transform aim closer to target
|
||||
* */
|
||||
private void RotateToTarget(Vector3 targetPosition, IKSolver.Bone bone, float weight) {
|
||||
// Swing
|
||||
if (XY) {
|
||||
if (weight >= 0f) {
|
||||
Vector3 dir = transformAxis;
|
||||
Vector3 targetDir = targetPosition - transform.position;
|
||||
|
||||
float angleDir = Mathf.Atan2(dir.x, dir.y) * Mathf.Rad2Deg;
|
||||
float angleTarget = Mathf.Atan2(targetDir.x, targetDir.y) * Mathf.Rad2Deg;
|
||||
|
||||
bone.transform.rotation = Quaternion.AngleAxis(Mathf.DeltaAngle(angleDir, angleTarget), Vector3.back) * bone.transform.rotation;
|
||||
}
|
||||
} else {
|
||||
if (weight >= 0f) {
|
||||
Quaternion rotationOffset = Quaternion.FromToRotation(transformAxis, targetPosition - transform.position);
|
||||
|
||||
if (weight >= 1f) {
|
||||
bone.transform.rotation = rotationOffset * bone.transform.rotation;
|
||||
} else {
|
||||
bone.transform.rotation = Quaternion.Lerp(Quaternion.identity, rotationOffset, weight) * bone.transform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
// Pole
|
||||
if (poleWeight > 0f) {
|
||||
Vector3 poleDirection = polePosition - transform.position;
|
||||
|
||||
// Ortho-normalize to transform axis to make this a twisting only operation
|
||||
Vector3 poleDirOrtho = poleDirection;
|
||||
Vector3 normal = transformAxis;
|
||||
Vector3.OrthoNormalize(ref normal, ref poleDirOrtho);
|
||||
|
||||
Quaternion toPole = Quaternion.FromToRotation(transformPoleAxis, poleDirOrtho);
|
||||
bone.transform.rotation = Quaternion.Lerp(Quaternion.identity, toPole, weight * poleWeight) * bone.transform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
if (useRotationLimits && bone.rotationLimit != null) bone.rotationLimit.Apply();
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the direction from last bone's forward in first bone's local space.
|
||||
* */
|
||||
protected override Vector3 localDirection {
|
||||
get {
|
||||
return bones[0].transform.InverseTransformDirection(bones[bones.Length - 1].transform.forward);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b9d7f152c7074f6ba84fdb7c6ee721c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,156 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper for making IKSolverVRArm work with other IK components.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKSolverArm : IKSolver {
|
||||
|
||||
[Range(0f, 1f)]
|
||||
public float IKRotationWeight = 1f;
|
||||
/// <summary>
|
||||
/// The %IK rotation target.
|
||||
/// </summary>
|
||||
public Quaternion IKRotation = Quaternion.identity;
|
||||
|
||||
public IKSolver.Point chest = new IKSolver.Point();
|
||||
public IKSolver.Point shoulder = new IKSolver.Point();
|
||||
public IKSolver.Point upperArm = new IKSolver.Point();
|
||||
public IKSolver.Point forearm = new IKSolver.Point();
|
||||
public IKSolver.Point hand = new IKSolver.Point();
|
||||
|
||||
public bool isLeft;
|
||||
|
||||
public IKSolverVR.Arm arm = new IKSolverVR.Arm();
|
||||
|
||||
private Vector3[] positions = new Vector3[6];
|
||||
private Quaternion[] rotations = new Quaternion[6];
|
||||
|
||||
public override bool IsValid(ref string message) {
|
||||
if (chest.transform == null || shoulder.transform == null || upperArm.transform == null || forearm.transform == null || hand.transform == null) {
|
||||
message = "Please assign all bone slots of the Arm IK solver.";
|
||||
return false;
|
||||
}
|
||||
|
||||
Transform duplicate = (Transform)Hierarchy.ContainsDuplicate(new Transform[5] { chest.transform, shoulder.transform, upperArm.transform, forearm.transform, hand.transform });
|
||||
if (duplicate != null) {
|
||||
message = duplicate.name + " is represented multiple times in the ArmIK.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set IK rotation weight for the arm.
|
||||
/// </summary>
|
||||
public void SetRotationWeight(float weight)
|
||||
{
|
||||
IKRotationWeight = weight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reinitiate the solver with new bone Transforms.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns true if the new chain is valid.
|
||||
/// </returns>
|
||||
public bool SetChain(Transform chest, Transform shoulder, Transform upperArm, Transform forearm, Transform hand, Transform root) {
|
||||
this.chest.transform = chest;
|
||||
this.shoulder.transform = shoulder;
|
||||
this.upperArm.transform = upperArm;
|
||||
this.forearm.transform = forearm;
|
||||
this.hand.transform = hand;
|
||||
|
||||
Initiate(root);
|
||||
return initiated;
|
||||
}
|
||||
|
||||
public override IKSolver.Point[] GetPoints() {
|
||||
return new IKSolver.Point[5] { (IKSolver.Point)chest, (IKSolver.Point)shoulder, (IKSolver.Point)upperArm, (IKSolver.Point)forearm, (IKSolver.Point)hand };
|
||||
}
|
||||
|
||||
public override IKSolver.Point GetPoint(Transform transform) {
|
||||
if (chest.transform == transform) return (IKSolver.Point)chest;
|
||||
if (shoulder.transform == transform) return (IKSolver.Point)shoulder;
|
||||
if (upperArm.transform == transform) return (IKSolver.Point)upperArm;
|
||||
if (forearm.transform == transform) return (IKSolver.Point)forearm;
|
||||
if (hand.transform == transform) return (IKSolver.Point)hand;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void StoreDefaultLocalState() {
|
||||
shoulder.StoreDefaultLocalState();
|
||||
upperArm.StoreDefaultLocalState();
|
||||
forearm.StoreDefaultLocalState();
|
||||
hand.StoreDefaultLocalState();
|
||||
}
|
||||
|
||||
public override void FixTransforms() {
|
||||
if (!initiated) return;
|
||||
|
||||
shoulder.FixTransform();
|
||||
upperArm.FixTransform();
|
||||
forearm.FixTransform();
|
||||
hand.FixTransform();
|
||||
}
|
||||
|
||||
protected override void OnInitiate() {
|
||||
IKPosition = hand.transform.position;
|
||||
IKRotation = hand.transform.rotation;
|
||||
|
||||
Read ();
|
||||
}
|
||||
|
||||
protected override void OnUpdate() {
|
||||
Read ();
|
||||
Solve ();
|
||||
Write ();
|
||||
}
|
||||
|
||||
private void Solve() {
|
||||
arm.PreSolve (1f);
|
||||
arm.ApplyOffsets(1f);
|
||||
arm.Solve (isLeft);
|
||||
arm.ResetOffsets ();
|
||||
}
|
||||
|
||||
private void Read() {
|
||||
arm.IKPosition = IKPosition;
|
||||
arm.positionWeight = IKPositionWeight;
|
||||
arm.IKRotation = IKRotation;
|
||||
arm.rotationWeight = IKRotationWeight;
|
||||
|
||||
positions [0] = root.position;
|
||||
positions [1] = chest.transform.position;
|
||||
positions [2] = shoulder.transform.position;
|
||||
positions [3] = upperArm.transform.position;
|
||||
positions [4] = forearm.transform.position;
|
||||
positions [5] = hand.transform.position;
|
||||
|
||||
rotations [0] = root.rotation;
|
||||
rotations [1] = chest.transform.rotation;
|
||||
rotations [2] = shoulder.transform.rotation;
|
||||
rotations [3] = upperArm.transform.rotation;
|
||||
rotations [4] = forearm.transform.rotation;
|
||||
rotations [5] = hand.transform.rotation;
|
||||
|
||||
arm.Read(positions, rotations, false, false, true, false, false, 1, 2);
|
||||
}
|
||||
|
||||
private void Write() {
|
||||
arm.Write (ref positions, ref rotations);
|
||||
|
||||
shoulder.transform.rotation = rotations [2];
|
||||
upperArm.transform.rotation = rotations [3];
|
||||
forearm.transform.rotation = rotations [4];
|
||||
hand.transform.rotation = rotations [5];
|
||||
|
||||
forearm.transform.position = positions[4];
|
||||
hand.transform.position = positions[5];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3c8b68f5bcd81b41913fb93e0f5526d
|
||||
timeCreated: 1528378875
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,114 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// CCD (Cyclic Coordinate Descent) constrainable heuristic inverse kinematics algorithm.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKSolverCCD : IKSolverHeuristic {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// CCD tends to overemphasise the rotations of the bones closer to the target position. Reducing bone weight down the hierarchy will compensate for this effect.
|
||||
/// </summary>
|
||||
public void FadeOutBoneWeights() {
|
||||
if (bones.Length < 2) return;
|
||||
|
||||
bones[0].weight = 1f;
|
||||
float step = 1f / (bones.Length - 1);
|
||||
|
||||
for (int i = 1; i < bones.Length; i++) {
|
||||
bones[i].weight = step * (bones.Length - 1 - i);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before each iteration of the solver.
|
||||
/// </summary>
|
||||
public IterationDelegate OnPreIteration;
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
protected override void OnInitiate() {
|
||||
if (firstInitiation || !Application.isPlaying) IKPosition = bones[bones.Length - 1].transform.position;
|
||||
|
||||
InitiateBones();
|
||||
}
|
||||
|
||||
protected override void OnUpdate() {
|
||||
if (IKPositionWeight <= 0) return;
|
||||
IKPositionWeight = Mathf.Clamp(IKPositionWeight, 0f, 1f);
|
||||
|
||||
if (target != null) IKPosition = target.position;
|
||||
if (XY) IKPosition.z = bones[0].transform.position.z;
|
||||
|
||||
Vector3 singularityOffset = maxIterations > 1? GetSingularityOffset(): Vector3.zero;
|
||||
|
||||
// Iterating the solver
|
||||
for (int i = 0; i < maxIterations; i++) {
|
||||
|
||||
// Optimizations
|
||||
if (singularityOffset == Vector3.zero && i >= 1 && tolerance > 0 && positionOffset < tolerance * tolerance) break;
|
||||
lastLocalDirection = localDirection;
|
||||
|
||||
if (OnPreIteration != null) OnPreIteration(i);
|
||||
|
||||
Solve(IKPosition + (i == 0? singularityOffset: Vector3.zero));
|
||||
}
|
||||
|
||||
lastLocalDirection = localDirection;
|
||||
}
|
||||
|
||||
/*
|
||||
* Solve the CCD algorithm
|
||||
* */
|
||||
protected void Solve(Vector3 targetPosition) {
|
||||
// 2D
|
||||
if (XY) {
|
||||
for (int i = bones.Length - 2; i > -1; i--) {
|
||||
//CCD tends to overemphasise the rotations of the bones closer to the target position. Reducing bone weight down the hierarchy will compensate for this effect.
|
||||
float w = bones[i].weight * IKPositionWeight;
|
||||
|
||||
if (w > 0f) {
|
||||
Vector3 toLastBone = bones[bones.Length - 1].transform.position - bones[i].transform.position;
|
||||
Vector3 toTarget = targetPosition - bones[i].transform.position;
|
||||
|
||||
float angleToLastBone = Mathf.Atan2(toLastBone.x, toLastBone.y) * Mathf.Rad2Deg;
|
||||
float angleToTarget = Mathf.Atan2(toTarget.x, toTarget.y) * Mathf.Rad2Deg;
|
||||
|
||||
// Rotation to direct the last bone to the target
|
||||
bones[i].transform.rotation = Quaternion.AngleAxis(Mathf.DeltaAngle(angleToLastBone, angleToTarget) * w, Vector3.back) * bones[i].transform.rotation;
|
||||
}
|
||||
|
||||
// Rotation Constraints
|
||||
if (useRotationLimits && bones[i].rotationLimit != null) bones[i].rotationLimit.Apply();
|
||||
}
|
||||
// 3D
|
||||
} else {
|
||||
for (int i = bones.Length - 2; i > -1; i--) {
|
||||
// Slerp if weight is < 0
|
||||
//CCD tends to overemphasise the rotations of the bones closer to the target position. Reducing bone weight down the hierarchy will compensate for this effect.
|
||||
float w = bones[i].weight * IKPositionWeight;
|
||||
|
||||
if (w > 0f) {
|
||||
Vector3 toLastBone = bones[bones.Length - 1].transform.position - bones[i].transform.position;
|
||||
Vector3 toTarget = targetPosition - bones[i].transform.position;
|
||||
|
||||
// Get the rotation to direct the last bone to the target
|
||||
Quaternion targetRotation = Quaternion.FromToRotation(toLastBone, toTarget) * bones[i].transform.rotation;
|
||||
|
||||
if (w >= 1) bones[i].transform.rotation = targetRotation;
|
||||
else bones[i].transform.rotation = Quaternion.Lerp(bones[i].transform.rotation, targetRotation, w);
|
||||
}
|
||||
|
||||
// Rotation Constraints
|
||||
if (useRotationLimits && bones[i].rotationLimit != null) bones[i].rotationLimit.Apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6380e07d356e34385a1e05bae6967fcc
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,359 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Forward and Backward Reaching Inverse Kinematics solver.
|
||||
///
|
||||
/// This class is based on the "FABRIK: A fast, iterative solver for the inverse kinematics problem." paper by Aristidou, A., Lasenby, J.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKSolverFABRIK : IKSolverHeuristic {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// Solving stage 1 of the %FABRIK algorithm.
|
||||
/// </summary>
|
||||
public void SolveForward(Vector3 position) {
|
||||
if (!initiated) {
|
||||
if (!Warning.logged) LogWarning("Trying to solve uninitiated FABRIK chain.");
|
||||
return;
|
||||
}
|
||||
|
||||
OnPreSolve();
|
||||
|
||||
ForwardReach(position);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Solving stage 2 of the %FABRIK algorithm.
|
||||
/// </summary>
|
||||
public void SolveBackward(Vector3 position) {
|
||||
if (!initiated) {
|
||||
if (!Warning.logged) LogWarning("Trying to solve uninitiated FABRIK chain.");
|
||||
return;
|
||||
}
|
||||
|
||||
BackwardReach(position);
|
||||
|
||||
OnPostSolve();
|
||||
}
|
||||
|
||||
public override Vector3 GetIKPosition() {
|
||||
if (target != null) return target.position;
|
||||
return IKPosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before each iteration of the solver.
|
||||
/// </summary>
|
||||
public IterationDelegate OnPreIteration;
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
private bool[] limitedBones = new bool[0];
|
||||
private Vector3[] solverLocalPositions = new Vector3[0];
|
||||
|
||||
protected override void OnInitiate() {
|
||||
if (firstInitiation || !Application.isPlaying) IKPosition = bones[bones.Length - 1].transform.position;
|
||||
|
||||
for (int i = 0; i < bones.Length; i++) {
|
||||
bones[i].solverPosition = bones[i].transform.position;
|
||||
bones[i].solverRotation = bones[i].transform.rotation;
|
||||
}
|
||||
|
||||
limitedBones = new bool[bones.Length];
|
||||
solverLocalPositions = new Vector3[bones.Length];
|
||||
|
||||
InitiateBones();
|
||||
|
||||
for (int i = 0; i < bones.Length; i++) {
|
||||
solverLocalPositions[i] = Quaternion.Inverse(GetParentSolverRotation(i)) * (bones[i].transform.position - GetParentSolverPosition(i));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnUpdate() {
|
||||
if (IKPositionWeight <= 0) return;
|
||||
IKPositionWeight = Mathf.Clamp(IKPositionWeight, 0f, 1f);
|
||||
|
||||
OnPreSolve();
|
||||
|
||||
if (target != null) IKPosition = target.position;
|
||||
if (XY) IKPosition.z = bones[0].transform.position.z;
|
||||
|
||||
Vector3 singularityOffset = maxIterations > 1? GetSingularityOffset(): Vector3.zero;
|
||||
|
||||
// Iterating the solver
|
||||
for (int i = 0; i < maxIterations; i++) {
|
||||
// Optimizations
|
||||
if (singularityOffset == Vector3.zero && i >= 1 && tolerance > 0 && positionOffset < tolerance * tolerance) break;
|
||||
lastLocalDirection = localDirection;
|
||||
|
||||
if (OnPreIteration != null) OnPreIteration(i);
|
||||
|
||||
Solve(IKPosition + (i == 0? singularityOffset: Vector3.zero));
|
||||
}
|
||||
|
||||
OnPostSolve();
|
||||
}
|
||||
|
||||
/*
|
||||
* If true, the solver will work with 0 length bones
|
||||
* */
|
||||
protected override bool boneLengthCanBeZero { get { return false; }} // Returning false here also ensures that the bone lengths will be calculated
|
||||
|
||||
/*
|
||||
* Interpolates the joint position to match the bone's length
|
||||
*/
|
||||
private Vector3 SolveJoint(Vector3 pos1, Vector3 pos2, float length) {
|
||||
if (XY) pos1.z = pos2.z;
|
||||
|
||||
return pos2 + (pos1 - pos2).normalized * length;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if bones have moved from last solved positions
|
||||
* */
|
||||
private void OnPreSolve() {
|
||||
chainLength = 0;
|
||||
|
||||
for (int i = 0; i < bones.Length; i++) {
|
||||
bones[i].solverPosition = bones[i].transform.position;
|
||||
bones[i].solverRotation = bones[i].transform.rotation;
|
||||
|
||||
if (i < bones.Length - 1) {
|
||||
bones[i].length = (bones[i].transform.position - bones[i + 1].transform.position).magnitude;
|
||||
bones[i].axis = Quaternion.Inverse(bones[i].transform.rotation) * (bones[i + 1].transform.position - bones[i].transform.position);
|
||||
|
||||
chainLength += bones[i].length;
|
||||
}
|
||||
|
||||
if (useRotationLimits) solverLocalPositions[i] = Quaternion.Inverse(GetParentSolverRotation(i)) * (bones[i].transform.position - GetParentSolverPosition(i));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* After solving the chain
|
||||
* */
|
||||
private void OnPostSolve() {
|
||||
// Rotating bones to match the solver positions
|
||||
if (!useRotationLimits) MapToSolverPositions();
|
||||
else MapToSolverPositionsLimited();
|
||||
|
||||
lastLocalDirection = localDirection;
|
||||
}
|
||||
|
||||
private void Solve(Vector3 targetPosition) {
|
||||
// Forward reaching
|
||||
ForwardReach(targetPosition);
|
||||
|
||||
// Backward reaching
|
||||
BackwardReach(bones[0].transform.position);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stage 1 of FABRIK algorithm
|
||||
* */
|
||||
private void ForwardReach(Vector3 position) {
|
||||
// Lerp last bone's solverPosition to position
|
||||
bones[bones.Length - 1].solverPosition = Vector3.Lerp(bones[bones.Length - 1].solverPosition, position, IKPositionWeight);
|
||||
|
||||
for (int i = 0; i < limitedBones.Length; i++) limitedBones[i] = false;
|
||||
|
||||
for (int i = bones.Length - 2; i > -1; i--) {
|
||||
// Finding joint positions
|
||||
bones[i].solverPosition = SolveJoint(bones[i].solverPosition, bones[i + 1].solverPosition, bones[i].length);
|
||||
|
||||
// Limiting bone rotation forward
|
||||
LimitForward(i, i + 1);
|
||||
}
|
||||
|
||||
// Limiting the first bone's rotation
|
||||
LimitForward(0, 0);
|
||||
}
|
||||
|
||||
private void SolverMove(int index, Vector3 offset) {
|
||||
for (int i = index; i < bones.Length; i++) {
|
||||
bones[i].solverPosition += offset;
|
||||
}
|
||||
}
|
||||
|
||||
private void SolverRotate(int index, Quaternion rotation, bool recursive) {
|
||||
for (int i = index; i < bones.Length; i++) {
|
||||
bones[i].solverRotation = rotation * bones[i].solverRotation;
|
||||
|
||||
if (!recursive) return;
|
||||
}
|
||||
}
|
||||
|
||||
private void SolverRotateChildren(int index, Quaternion rotation) {
|
||||
for (int i = index + 1; i < bones.Length; i++) {
|
||||
bones[i].solverRotation = rotation * bones[i].solverRotation;
|
||||
}
|
||||
}
|
||||
|
||||
private void SolverMoveChildrenAroundPoint(int index, Quaternion rotation) {
|
||||
for (int i = index + 1; i < bones.Length; i++) {
|
||||
Vector3 dir = bones[i].solverPosition - bones[index].solverPosition;
|
||||
bones[i].solverPosition = bones[index].solverPosition + rotation * dir;
|
||||
}
|
||||
}
|
||||
|
||||
private Quaternion GetParentSolverRotation(int index) {
|
||||
if (index > 0) return bones[index - 1].solverRotation;
|
||||
if (bones[0].transform.parent == null) return Quaternion.identity;
|
||||
return bones[0].transform.parent.rotation;
|
||||
}
|
||||
|
||||
private Vector3 GetParentSolverPosition(int index) {
|
||||
if (index > 0) return bones[index - 1].solverPosition;
|
||||
if (bones[0].transform.parent == null) return Vector3.zero;
|
||||
return bones[0].transform.parent.position;
|
||||
}
|
||||
|
||||
private Quaternion GetLimitedRotation(int index, Quaternion q, out bool changed) {
|
||||
changed = false;
|
||||
|
||||
Quaternion parentRotation = GetParentSolverRotation(index);
|
||||
Quaternion localRotation = Quaternion.Inverse(parentRotation) * q;
|
||||
|
||||
Quaternion limitedLocalRotation = bones[index].rotationLimit.GetLimitedLocalRotation(localRotation, out changed);
|
||||
|
||||
if (!changed) return q;
|
||||
|
||||
return parentRotation * limitedLocalRotation;
|
||||
}
|
||||
|
||||
/*
|
||||
* Applying rotation limit to a bone in stage 1 in a more stable way
|
||||
* */
|
||||
private void LimitForward(int rotateBone, int limitBone) {
|
||||
if (!useRotationLimits) return;
|
||||
if (bones[limitBone].rotationLimit == null) return;
|
||||
|
||||
// Storing last bone's position before applying the limit
|
||||
Vector3 lastBoneBeforeLimit = bones[bones.Length - 1].solverPosition;
|
||||
|
||||
// Moving and rotating this bone and all its children to their solver positions
|
||||
for (int i = rotateBone; i < bones.Length - 1; i++) {
|
||||
if (limitedBones[i]) break;
|
||||
|
||||
Quaternion fromTo = Quaternion.FromToRotation(bones[i].solverRotation * bones[i].axis, bones[i + 1].solverPosition - bones[i].solverPosition);
|
||||
SolverRotate(i, fromTo, false);
|
||||
}
|
||||
|
||||
// Limit the bone's rotation
|
||||
bool changed = false;
|
||||
Quaternion afterLimit = GetLimitedRotation(limitBone, bones[limitBone].solverRotation, out changed);
|
||||
|
||||
if (changed) {
|
||||
// Rotating and positioning the hierarchy so that the last bone's position is maintained
|
||||
if (limitBone < bones.Length - 1) {
|
||||
Quaternion change = QuaTools.FromToRotation(bones[limitBone].solverRotation, afterLimit);
|
||||
bones[limitBone].solverRotation = afterLimit;
|
||||
SolverRotateChildren(limitBone, change);
|
||||
SolverMoveChildrenAroundPoint(limitBone, change);
|
||||
|
||||
// Rotating to compensate for the limit
|
||||
Quaternion fromTo = Quaternion.FromToRotation(bones[bones.Length - 1].solverPosition - bones[rotateBone].solverPosition, lastBoneBeforeLimit - bones[rotateBone].solverPosition);
|
||||
|
||||
SolverRotate(rotateBone, fromTo, true);
|
||||
SolverMoveChildrenAroundPoint(rotateBone, fromTo);
|
||||
|
||||
// Moving the bone so that last bone maintains its initial position
|
||||
SolverMove(rotateBone, lastBoneBeforeLimit - bones[bones.Length - 1].solverPosition);
|
||||
} else {
|
||||
// last bone
|
||||
bones[limitBone].solverRotation = afterLimit;
|
||||
}
|
||||
}
|
||||
|
||||
limitedBones[limitBone] = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Stage 2 of FABRIK algorithm
|
||||
* */
|
||||
private void BackwardReach(Vector3 position) {
|
||||
if (useRotationLimits) BackwardReachLimited(position);
|
||||
else BackwardReachUnlimited(position);
|
||||
}
|
||||
|
||||
/*
|
||||
* Stage 2 of FABRIK algorithm without rotation limits
|
||||
* */
|
||||
private void BackwardReachUnlimited(Vector3 position) {
|
||||
// Move first bone to position
|
||||
bones[0].solverPosition = position;
|
||||
|
||||
// Finding joint positions
|
||||
for (int i = 1; i < bones.Length; i++) {
|
||||
bones[i].solverPosition = SolveJoint(bones[i].solverPosition, bones[i - 1].solverPosition, bones[i - 1].length);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Stage 2 of FABRIK algorithm with limited rotations
|
||||
* */
|
||||
private void BackwardReachLimited(Vector3 position) {
|
||||
// Move first bone to position
|
||||
bones[0].solverPosition = position;
|
||||
|
||||
// Applying rotation limits bone by bone
|
||||
for (int i = 0; i < bones.Length - 1; i++) {
|
||||
// Rotating bone to look at the solved joint position
|
||||
Vector3 nextPosition = SolveJoint(bones[i + 1].solverPosition, bones[i].solverPosition, bones[i].length);
|
||||
|
||||
Quaternion swing = Quaternion.FromToRotation(bones[i].solverRotation * bones[i].axis, nextPosition - bones[i].solverPosition);
|
||||
Quaternion targetRotation = swing * bones[i].solverRotation;
|
||||
|
||||
// Rotation Constraints
|
||||
if (bones[i].rotationLimit != null) {
|
||||
bool changed = false;
|
||||
targetRotation = GetLimitedRotation(i, targetRotation, out changed);
|
||||
}
|
||||
|
||||
Quaternion fromTo = QuaTools.FromToRotation(bones[i].solverRotation, targetRotation);
|
||||
bones[i].solverRotation = targetRotation;
|
||||
SolverRotateChildren(i, fromTo);
|
||||
|
||||
// Positioning the next bone to its default local position
|
||||
bones[i + 1].solverPosition = bones[i].solverPosition + bones[i].solverRotation * solverLocalPositions[i + 1];
|
||||
}
|
||||
|
||||
// Reconstruct solver rotations to protect from invalid Quaternions
|
||||
for (int i = 0; i < bones.Length; i++) {
|
||||
bones[i].solverRotation = Quaternion.LookRotation(bones[i].solverRotation * Vector3.forward, bones[i].solverRotation * Vector3.up);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotate bones to match the solver positions when not using Rotation Limits
|
||||
* */
|
||||
private void MapToSolverPositions() {
|
||||
bones[0].transform.position = bones[0].solverPosition;
|
||||
|
||||
for (int i = 0; i < bones.Length - 1; i++) {
|
||||
if (XY) {
|
||||
bones[i].Swing2D(bones[i + 1].solverPosition);
|
||||
} else {
|
||||
bones[i].Swing(bones[i + 1].solverPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotate bones to match the solver positions when using Rotation Limits
|
||||
* */
|
||||
private void MapToSolverPositionsLimited() {
|
||||
bones[0].transform.position = bones[0].solverPosition;
|
||||
|
||||
for (int i = 0; i < bones.Length; i++) {
|
||||
if (i < bones.Length - 1) bones[i].transform.rotation = bones[i].solverRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ebaef965ad4344c66afa96b93bc53c7a
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,210 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// %IK system for multiple branched %FABRIK chains.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKSolverFABRIKRoot : IKSolver {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// Solver iterations.
|
||||
/// </summary>
|
||||
public int iterations = 4;
|
||||
/// <summary>
|
||||
/// The weight of all chains being pinned to root position.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float rootPin = 0f;
|
||||
/// <summary>
|
||||
/// The %FABRIK chains.
|
||||
/// </summary>
|
||||
public FABRIKChain[] chains = new FABRIKChain[0];
|
||||
|
||||
public override bool IsValid(ref string message) {
|
||||
if (chains.Length == 0) {
|
||||
message = "IKSolverFABRIKRoot contains no chains.";
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (FABRIKChain chain in chains) {
|
||||
if (!chain.IsValid(ref message)) return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < chains.Length; i++) {
|
||||
for (int c = 0; c < chains.Length; c++) {
|
||||
if (i != c && chains[i].ik == chains[c].ik) {
|
||||
message = chains[i].ik.name + " is represented more than once in IKSolverFABRIKRoot chain.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the children
|
||||
for (int i = 0; i < chains.Length; i++) {
|
||||
for (int c = 0; c < chains[i].children.Length; c++) {
|
||||
int childIndex = chains[i].children[c];
|
||||
|
||||
if (childIndex < 0) {
|
||||
message = chains[i].ik.name + "IKSolverFABRIKRoot chain at index " + i + " has invalid children array. Child index is < 0.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (childIndex == i) {
|
||||
message = chains[i].ik.name + "IKSolverFABRIKRoot chain at index " + i + " has invalid children array. Child index is referencing to itself.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (childIndex >= chains.Length) {
|
||||
message = chains[i].ik.name + "IKSolverFABRIKRoot chain at index " + i + " has invalid children array. Child index > number of chains";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the child chain doesn't have this chain among its children
|
||||
for (int o = 0; o < chains.Length; o++) {
|
||||
if (childIndex == o) {
|
||||
for (int n = 0; n < chains[o].children.Length; n++) {
|
||||
if (chains[o].children[n] == i) {
|
||||
message = "Circular parenting. " + chains[o].ik.name + " already has " + chains[i].ik.name + " listed as its child.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for duplicates
|
||||
for (int n = 0; n < chains[i].children.Length; n++) {
|
||||
if (c != n && chains[i].children[n] == childIndex) {
|
||||
message = "Chain number " + childIndex + " is represented more than once in the children of " + chains[i].ik.name;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void StoreDefaultLocalState() {
|
||||
rootDefaultPosition = root.localPosition;
|
||||
for (int i = 0; i < chains.Length; i++) chains[i].ik.solver.StoreDefaultLocalState();
|
||||
}
|
||||
|
||||
public override void FixTransforms() {
|
||||
if (!initiated) return;
|
||||
|
||||
root.localPosition = rootDefaultPosition;
|
||||
for (int i = 0; i < chains.Length; i++) chains[i].ik.solver.FixTransforms();
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
private bool zeroWeightApplied;
|
||||
private bool[] isRoot;
|
||||
private Vector3 rootDefaultPosition;
|
||||
|
||||
protected override void OnInitiate() {
|
||||
for (int i = 0; i < chains.Length; i++) chains[i].Initiate();
|
||||
|
||||
isRoot = new bool[chains.Length];
|
||||
for (int i = 0; i < chains.Length; i++) {
|
||||
isRoot[i] = IsRoot(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Is the chain at index a root chain (not parented by any other chains)?
|
||||
private bool IsRoot(int index) {
|
||||
for (int i = 0; i < chains.Length; i++) {
|
||||
for (int c = 0; c < chains[i].children.Length; c++) {
|
||||
if (chains[i].children[c] == index) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnUpdate() {
|
||||
if (IKPositionWeight <= 0 && zeroWeightApplied) return;
|
||||
IKPositionWeight = Mathf.Clamp(IKPositionWeight, 0f, 1f);
|
||||
|
||||
// Set weight of all IK solvers
|
||||
for (int i = 0; i < chains.Length; i++) chains[i].ik.solver.IKPositionWeight = IKPositionWeight;
|
||||
|
||||
if (IKPositionWeight <= 0) {
|
||||
zeroWeightApplied = true;
|
||||
return;
|
||||
}
|
||||
|
||||
zeroWeightApplied = false;
|
||||
|
||||
for (int i = 0; i < iterations; i++) {
|
||||
// Solve trees from their targets
|
||||
for (int c = 0; c < chains.Length; c++) {
|
||||
if (isRoot[c]) chains[c].Stage1(chains);
|
||||
|
||||
}
|
||||
|
||||
// Get centroid of all tree roots
|
||||
Vector3 centroid = GetCentroid();
|
||||
root.position = centroid;
|
||||
|
||||
// Start all trees from the centroid
|
||||
for (int c = 0; c < chains.Length; c++) {
|
||||
if (isRoot[c]) chains[c].Stage2(centroid, chains);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override IKSolver.Point[] GetPoints() {
|
||||
IKSolver.Point[] array = new IKSolver.Point[0];
|
||||
for (int i = 0; i < chains.Length; i++) AddPointsToArray(ref array, chains[i]);
|
||||
return array;
|
||||
}
|
||||
|
||||
public override IKSolver.Point GetPoint(Transform transform) {
|
||||
IKSolver.Point p = null;
|
||||
for (int i = 0; i < chains.Length; i++) {
|
||||
p = chains[i].ik.solver.GetPoint(transform);
|
||||
if (p != null) return p;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void AddPointsToArray(ref IKSolver.Point[] array, FABRIKChain chain) {
|
||||
IKSolver.Point[] chainArray = chain.ik.solver.GetPoints();
|
||||
Array.Resize(ref array, array.Length + chainArray.Length);
|
||||
|
||||
int a = 0;
|
||||
for (int i = array.Length - chainArray.Length; i < array.Length; i++) {
|
||||
array[i] = chainArray[a];
|
||||
a++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the centroid position of all chains respective of their pull weights
|
||||
* */
|
||||
private Vector3 GetCentroid() {
|
||||
Vector3 centroid = root.position;
|
||||
if (rootPin >= 1) return centroid;
|
||||
|
||||
float pullSum = 0f;
|
||||
for (int i = 0; i < chains.Length; i++) {
|
||||
if (isRoot[i]) pullSum += chains[i].pull;
|
||||
}
|
||||
|
||||
for (int i = 0; i < chains.Length; i++) {
|
||||
if (isRoot[i] && pullSum > 0) centroid += (chains[i].ik.solver.bones[0].solverPosition - root.position) * (chains[i].pull / Mathf.Clamp(pullSum, 1f, pullSum));
|
||||
}
|
||||
|
||||
return Vector3.Lerp(centroid, root.position, rootPin);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3832891e51da482f8856a7689ca7c1b
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,317 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Generic FBIK solver. In each solver update, %IKSolverFullBody first reads the character's pose, then solves the %IK and writes the solved pose back to the character via IKMapping.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKSolverFullBody : IKSolver {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// Number of solver iterations.
|
||||
/// </summary>
|
||||
[Range(0, 10)]
|
||||
public int iterations = 4;
|
||||
/// <summary>
|
||||
/// The root node chain.
|
||||
/// </summary>
|
||||
public FBIKChain[] chain = new FBIKChain[0];
|
||||
/// <summary>
|
||||
/// The effectors.
|
||||
/// </summary>
|
||||
public IKEffector[] effectors = new IKEffector[0];
|
||||
/// <summary>
|
||||
/// Mapping spine bones to the solver.
|
||||
/// </summary>
|
||||
public IKMappingSpine spineMapping = new IKMappingSpine();
|
||||
/// <summary>
|
||||
/// Mapping individual bones to the solver
|
||||
/// </summary>
|
||||
public IKMappingBone[] boneMappings = new IKMappingBone[0];
|
||||
/// <summary>
|
||||
/// Mapping 3 segment limbs to the solver
|
||||
/// </summary>
|
||||
public IKMappingLimb[] limbMappings = new IKMappingLimb[0];
|
||||
/// <summary>
|
||||
/// If false, will not solve a FABRIK pass and the arms/legs will not be able to pull the body.
|
||||
/// </summary>
|
||||
public bool FABRIKPass = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the effector of the specified Transform.
|
||||
/// </summary>
|
||||
public IKEffector GetEffector(Transform t) {
|
||||
for (int i = 0; i < effectors.Length; i++) if (effectors[i].bone == t) return effectors[i];
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chain that contains the specified Transform.
|
||||
/// </summary>
|
||||
public FBIKChain GetChain(Transform transform) {
|
||||
int index = GetChainIndex(transform);
|
||||
if (index == -1) return null;
|
||||
return chain[index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of the chain (in the IKSolverFullBody.chain array) that contains the specified Transform.
|
||||
/// </summary>
|
||||
public int GetChainIndex(Transform transform) {
|
||||
for (int i = 0; i < chain.Length; i++) {
|
||||
for (int n = 0; n < chain[i].nodes.Length; n++) if (chain[i].nodes[n].transform == transform) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public IKSolver.Node GetNode(int chainIndex, int nodeIndex) {
|
||||
return chain[chainIndex].nodes[nodeIndex];
|
||||
}
|
||||
|
||||
public void GetChainAndNodeIndexes(Transform transform, out int chainIndex, out int nodeIndex) {
|
||||
chainIndex = GetChainIndex(transform);
|
||||
if (chainIndex == -1) nodeIndex = -1;
|
||||
else nodeIndex = chain[chainIndex].GetNodeIndex(transform);
|
||||
}
|
||||
|
||||
public override IKSolver.Point[] GetPoints() {
|
||||
int nodes = 0;
|
||||
for (int i = 0; i < chain.Length; i++) nodes += chain[i].nodes.Length;
|
||||
|
||||
IKSolver.Point[] pointArray = new IKSolver.Point[nodes];
|
||||
|
||||
int added = 0;
|
||||
for (int i = 0; i < chain.Length; i++) {
|
||||
for (int n = 0; n < chain[i].nodes.Length; n++) {
|
||||
pointArray[added] = chain[i].nodes[n] as IKSolver.Node;
|
||||
added++;
|
||||
}
|
||||
}
|
||||
|
||||
return pointArray;
|
||||
}
|
||||
|
||||
public override IKSolver.Point GetPoint(Transform transform) {
|
||||
for (int i = 0; i < chain.Length; i++) {
|
||||
for (int n = 0; n < chain[i].nodes.Length; n++) if (chain[i].nodes[n].transform == transform) return chain[i].nodes[n] as IKSolver.Point;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool IsValid(ref string message) {
|
||||
if (chain == null) {
|
||||
message = "FBIK chain is null, can't initiate solver.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chain.Length == 0) {
|
||||
message = "FBIK chain length is 0, can't initiate solver.";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int i = 0; i < chain.Length; i++) {
|
||||
if (!chain[i].IsValid(ref message)) return false;
|
||||
}
|
||||
|
||||
foreach (IKEffector e in effectors) if (!e.IsValid(this, ref message)) return false;
|
||||
|
||||
if (!spineMapping.IsValid(this, ref message)) return false;
|
||||
foreach (IKMappingLimb l in limbMappings) if (!l.IsValid(this, ref message)) return false;
|
||||
foreach (IKMappingBone b in boneMappings) if (!b.IsValid(this, ref message)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before reading the pose
|
||||
/// </summary>
|
||||
public UpdateDelegate OnPreRead;
|
||||
/// <summary>
|
||||
/// Called before solving.
|
||||
/// </summary>
|
||||
public UpdateDelegate OnPreSolve;
|
||||
/// <summary>
|
||||
/// Called before each iteration
|
||||
/// </summary>
|
||||
public IterationDelegate OnPreIteration;
|
||||
/// <summary>
|
||||
/// Called after each iteration
|
||||
/// </summary>
|
||||
public IterationDelegate OnPostIteration;
|
||||
/// <summary>
|
||||
/// Called before applying bend constraints.
|
||||
/// </summary>
|
||||
public UpdateDelegate OnPreBend;
|
||||
/// <summary>
|
||||
/// Called after updating the solver
|
||||
/// </summary>
|
||||
public UpdateDelegate OnPostSolve;
|
||||
/// <summary>
|
||||
/// Called when storing default local state (the state that FixTransforms will reset the hierarchy to).
|
||||
/// </summary>
|
||||
public UpdateDelegate OnStoreDefaultLocalState;
|
||||
/// <summary>
|
||||
/// Called when the bones used by the solver will reset to the default local state.
|
||||
/// </summary>
|
||||
public UpdateDelegate OnFixTransforms;
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
public override void StoreDefaultLocalState() {
|
||||
spineMapping.StoreDefaultLocalState();
|
||||
for (int i = 0; i < limbMappings.Length; i++) limbMappings[i].StoreDefaultLocalState();
|
||||
for (int i = 0; i < boneMappings.Length; i++) boneMappings[i].StoreDefaultLocalState();
|
||||
|
||||
if (OnStoreDefaultLocalState != null) OnStoreDefaultLocalState();
|
||||
}
|
||||
|
||||
public override void FixTransforms() {
|
||||
if (!initiated) return;
|
||||
if (IKPositionWeight <= 0f) return;
|
||||
|
||||
spineMapping.FixTransforms();
|
||||
for (int i = 0; i < limbMappings.Length; i++) limbMappings[i].FixTransforms();
|
||||
for (int i = 0; i < boneMappings.Length; i++) boneMappings[i].FixTransforms();
|
||||
|
||||
if (OnFixTransforms != null) OnFixTransforms();
|
||||
}
|
||||
|
||||
protected override void OnInitiate() {
|
||||
// Initiate chain
|
||||
for (int i = 0; i < chain.Length; i++) {
|
||||
chain[i].Initiate(this);
|
||||
}
|
||||
|
||||
// Initiate effectors
|
||||
foreach (IKEffector e in effectors) e.Initiate(this);
|
||||
|
||||
// Initiate IK mapping
|
||||
spineMapping.Initiate(this);
|
||||
foreach (IKMappingBone boneMapping in boneMappings) boneMapping.Initiate(this);
|
||||
foreach (IKMappingLimb limbMapping in limbMappings) limbMapping.Initiate(this);
|
||||
}
|
||||
|
||||
protected override void OnUpdate() {
|
||||
if (IKPositionWeight <= 0) {
|
||||
// clear effector positionOffsets so they would not accumulate
|
||||
for (int i = 0; i < effectors.Length; i++) effectors[i].positionOffset = Vector3.zero;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (chain.Length == 0) return;
|
||||
|
||||
IKPositionWeight = Mathf.Clamp(IKPositionWeight, 0f, 1f);
|
||||
|
||||
if (OnPreRead != null) OnPreRead();
|
||||
|
||||
// Phase 1: Read the pose of the biped
|
||||
ReadPose();
|
||||
|
||||
if (OnPreSolve != null) OnPreSolve();
|
||||
|
||||
// Phase 2: Solve IK
|
||||
Solve();
|
||||
|
||||
if (OnPostSolve != null) OnPostSolve();
|
||||
|
||||
// Phase 3: Map biped to its solved state
|
||||
WritePose();
|
||||
|
||||
// Reset effector position offsets to Vector3.zero
|
||||
for (int i = 0; i < effectors.Length; i++) effectors[i].OnPostWrite();
|
||||
}
|
||||
|
||||
protected virtual void ReadPose() {
|
||||
// Making sure the limbs are not inverted
|
||||
for (int i = 0; i < chain.Length; i++) {
|
||||
if (chain[i].bendConstraint.initiated) chain[i].bendConstraint.LimitBend(IKPositionWeight, GetEffector(chain[i].nodes[2].transform).positionWeight);
|
||||
}
|
||||
|
||||
// Presolve effectors, apply effector offset to the nodes
|
||||
for (int i = 0; i < effectors.Length; i++) effectors[i].ResetOffset(this);
|
||||
for (int i = 0; i < effectors.Length; i++) effectors[i].OnPreSolve(this);
|
||||
|
||||
// Set solver positions to match the current bone positions of the biped
|
||||
for (int i = 0; i < chain.Length; i++) {
|
||||
chain[i].ReadPose(this, iterations > 0);
|
||||
}
|
||||
|
||||
// IKMapping
|
||||
if (iterations > 0) {
|
||||
spineMapping.ReadPose();
|
||||
for (int i = 0; i < boneMappings.Length; i++) boneMappings[i].ReadPose();
|
||||
}
|
||||
|
||||
for (int i = 0; i < limbMappings.Length; i++) limbMappings[i].ReadPose();
|
||||
}
|
||||
|
||||
protected virtual void Solve() {
|
||||
// Iterate solver
|
||||
if(iterations > 0) {
|
||||
for (int i = 0; i < (FABRIKPass? iterations: 1); i++) {
|
||||
if (OnPreIteration != null) OnPreIteration(i);
|
||||
|
||||
// Apply end-effectors
|
||||
for (int e = 0; e < effectors.Length; e++) if (effectors[e].isEndEffector) effectors[e].Update(this);
|
||||
|
||||
if (FABRIKPass) {
|
||||
// Reaching
|
||||
chain[0].Push(this);
|
||||
|
||||
// Reaching
|
||||
if (FABRIKPass) chain[0].Reach(this);
|
||||
|
||||
// Apply non end-effectors
|
||||
for (int e = 0; e < effectors.Length; e++) if (!effectors[e].isEndEffector) effectors[e].Update(this);
|
||||
}
|
||||
|
||||
// Trigonometric pass to release push tension from the solver
|
||||
chain[0].SolveTrigonometric(this);
|
||||
|
||||
if (FABRIKPass) {
|
||||
// Solving FABRIK forward
|
||||
chain[0].Stage1(this);
|
||||
|
||||
// Apply non end-effectors again
|
||||
for (int e = 0; e < effectors.Length; e++) if (!effectors[e].isEndEffector) effectors[e].Update(this);
|
||||
|
||||
// Solving FABRIK backwards
|
||||
chain[0].Stage2(this, chain[0].nodes[0].solverPosition);
|
||||
}
|
||||
|
||||
if (OnPostIteration != null) OnPostIteration(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Before applying bend constraints (last chance to modify the bend direction)
|
||||
if (OnPreBend != null) OnPreBend();
|
||||
|
||||
// Final end-effector pass
|
||||
for (int i = 0; i < effectors.Length; i++) if (effectors[i].isEndEffector) effectors[i].Update(this);
|
||||
|
||||
ApplyBendConstraints();
|
||||
}
|
||||
|
||||
protected virtual void ApplyBendConstraints() {
|
||||
// Solve bend constraints
|
||||
chain[0].SolveTrigonometric(this, true);
|
||||
}
|
||||
|
||||
protected virtual void WritePose() {
|
||||
if (IKPositionWeight <= 0f) return;
|
||||
|
||||
// Apply IK mapping
|
||||
if (iterations > 0) {
|
||||
spineMapping.WritePose(this);
|
||||
for (int i = 0; i < boneMappings.Length; i++) boneMappings[i].WritePose(IKPositionWeight);
|
||||
}
|
||||
|
||||
for (int i = 0; i < limbMappings.Length; i++) limbMappings[i].WritePose(this, iterations > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a0f2e3f14c5d4b7b8ed722e50626211
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,580 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Full body biped IK effector types.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public enum FullBodyBipedEffector {
|
||||
Body,
|
||||
LeftShoulder,
|
||||
RightShoulder,
|
||||
LeftThigh,
|
||||
RightThigh,
|
||||
LeftHand,
|
||||
RightHand,
|
||||
LeftFoot,
|
||||
RightFoot
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Full body biped IK chain types.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public enum FullBodyBipedChain {
|
||||
LeftArm,
|
||||
RightArm,
|
||||
LeftLeg,
|
||||
RightLeg
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FBIK solver specialized to biped characters.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKSolverFullBodyBiped : IKSolverFullBody {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The central root node (body).
|
||||
/// </summary>
|
||||
public Transform rootNode;
|
||||
/// <summary>
|
||||
/// The stiffness of spine constraints.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float spineStiffness = 0.5f;
|
||||
/// <summary>
|
||||
/// Weight of hand effectors pulling the body vertically (relative to root rotation).
|
||||
/// </summary>
|
||||
[Range(-1f, 1f)]
|
||||
public float pullBodyVertical = 0.5f;
|
||||
/// <summary>
|
||||
/// Weight of hand effectors pulling the body horizontally (relative to root rotation).
|
||||
/// </summary>
|
||||
[Range(-1f, 1f)]
|
||||
public float pullBodyHorizontal = 0f;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the body effector.
|
||||
/// </summary>
|
||||
public IKEffector bodyEffector { get { return GetEffector(FullBodyBipedEffector.Body); }}
|
||||
/// <summary>
|
||||
/// Gets the left shoulder effector.
|
||||
/// </summary>
|
||||
public IKEffector leftShoulderEffector { get { return GetEffector(FullBodyBipedEffector.LeftShoulder); }}
|
||||
/// <summary>
|
||||
/// Gets the right shoulder effector.
|
||||
/// </summary>
|
||||
public IKEffector rightShoulderEffector { get { return GetEffector(FullBodyBipedEffector.RightShoulder); }}
|
||||
/// <summary>
|
||||
/// Gets the left thigh effector.
|
||||
/// </summary>
|
||||
public IKEffector leftThighEffector { get { return GetEffector(FullBodyBipedEffector.LeftThigh); }}
|
||||
/// <summary>
|
||||
/// Gets the right thigh effector.
|
||||
/// </summary>
|
||||
public IKEffector rightThighEffector { get { return GetEffector(FullBodyBipedEffector.RightThigh); }}
|
||||
/// <summary>
|
||||
/// Gets the left hand effector.
|
||||
/// </summary>
|
||||
public IKEffector leftHandEffector { get { return GetEffector(FullBodyBipedEffector.LeftHand); }}
|
||||
/// <summary>
|
||||
/// Gets the right hand effector.
|
||||
/// </summary>
|
||||
public IKEffector rightHandEffector { get { return GetEffector(FullBodyBipedEffector.RightHand); }}
|
||||
/// <summary>
|
||||
/// Gets the left foot effector.
|
||||
/// </summary>
|
||||
public IKEffector leftFootEffector { get { return GetEffector(FullBodyBipedEffector.LeftFoot); }}
|
||||
/// <summary>
|
||||
/// Gets the right foot effector.
|
||||
/// </summary>
|
||||
public IKEffector rightFootEffector { get { return GetEffector(FullBodyBipedEffector.RightFoot); }}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the left arm chain.
|
||||
/// </summary>
|
||||
public FBIKChain leftArmChain { get { return chain[1]; }}
|
||||
/// <summary>
|
||||
/// Gets the right arm chain.
|
||||
/// </summary>
|
||||
public FBIKChain rightArmChain { get { return chain[2]; }}
|
||||
/// <summary>
|
||||
/// Gets the left leg chain.
|
||||
/// </summary>
|
||||
public FBIKChain leftLegChain { get { return chain[3]; }}
|
||||
/// <summary>
|
||||
/// Gets the right leg chain.
|
||||
/// </summary>
|
||||
public FBIKChain rightLegChain { get { return chain[4]; }}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the left arm IK mapping.
|
||||
/// </summary>
|
||||
public IKMappingLimb leftArmMapping { get { return limbMappings[0]; }}
|
||||
/// <summary>
|
||||
/// Gets the right arm IK mapping.
|
||||
/// </summary>
|
||||
public IKMappingLimb rightArmMapping { get { return limbMappings[1]; }}
|
||||
/// <summary>
|
||||
/// Gets the left leg IK mapping.
|
||||
/// </summary>
|
||||
public IKMappingLimb leftLegMapping { get { return limbMappings[2]; }}
|
||||
/// <summary>
|
||||
/// Gets the right leg IK mapping.
|
||||
/// </summary>
|
||||
public IKMappingLimb rightLegMapping { get { return limbMappings[3]; }}
|
||||
/// <summary>
|
||||
/// Gets the head IK mapping.
|
||||
/// </summary>
|
||||
public IKMappingBone headMapping { get { return boneMappings[0]; }}
|
||||
|
||||
/// <summary>
|
||||
/// Sets chain weights for the specified chain.
|
||||
/// </summary>
|
||||
public void SetChainWeights(FullBodyBipedChain c, float pull, float reach = 0f) {
|
||||
GetChain(c).pull = pull;
|
||||
GetChain(c).reach = reach;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets effector weights for the specified effector.
|
||||
/// </summary>
|
||||
public void SetEffectorWeights(FullBodyBipedEffector effector, float positionWeight, float rotationWeight) {
|
||||
GetEffector(effector).positionWeight = Mathf.Clamp(positionWeight, 0f, 1f);
|
||||
GetEffector(effector).rotationWeight = Mathf.Clamp(rotationWeight, 0f, 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chain of a limb.
|
||||
/// </summary>
|
||||
public FBIKChain GetChain(FullBodyBipedChain c) {
|
||||
switch(c) {
|
||||
case FullBodyBipedChain.LeftArm: return chain[1];
|
||||
case FullBodyBipedChain.RightArm: return chain[2];
|
||||
case FullBodyBipedChain.LeftLeg: return chain[3];
|
||||
case FullBodyBipedChain.RightLeg: return chain[4];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chain of the specified effector.
|
||||
/// </summary>
|
||||
public FBIKChain GetChain(FullBodyBipedEffector effector) {
|
||||
switch(effector) {
|
||||
case FullBodyBipedEffector.Body: return chain[0];
|
||||
case FullBodyBipedEffector.LeftShoulder: return chain[1];
|
||||
case FullBodyBipedEffector.RightShoulder: return chain[2];
|
||||
case FullBodyBipedEffector.LeftThigh: return chain[3];
|
||||
case FullBodyBipedEffector.RightThigh: return chain[4];
|
||||
case FullBodyBipedEffector.LeftHand: return chain[1];
|
||||
case FullBodyBipedEffector.RightHand: return chain[2];
|
||||
case FullBodyBipedEffector.LeftFoot: return chain[3];
|
||||
case FullBodyBipedEffector.RightFoot: return chain[4];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the effector of type.
|
||||
/// </summary>
|
||||
public IKEffector GetEffector(FullBodyBipedEffector effector) {
|
||||
switch(effector) {
|
||||
case FullBodyBipedEffector.Body: return effectors[0];
|
||||
case FullBodyBipedEffector.LeftShoulder: return effectors[1];
|
||||
case FullBodyBipedEffector.RightShoulder: return effectors[2];
|
||||
case FullBodyBipedEffector.LeftThigh: return effectors[3];
|
||||
case FullBodyBipedEffector.RightThigh: return effectors[4];
|
||||
case FullBodyBipedEffector.LeftHand: return effectors[5];
|
||||
case FullBodyBipedEffector.RightHand: return effectors[6];
|
||||
case FullBodyBipedEffector.LeftFoot: return effectors[7];
|
||||
case FullBodyBipedEffector.RightFoot: return effectors[8];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the effector of type.
|
||||
/// </summary>
|
||||
public IKEffector GetEndEffector(FullBodyBipedChain c) {
|
||||
switch(c) {
|
||||
case FullBodyBipedChain.LeftArm: return effectors[5];
|
||||
case FullBodyBipedChain.RightArm: return effectors[6];
|
||||
case FullBodyBipedChain.LeftLeg: return effectors[7];
|
||||
case FullBodyBipedChain.RightLeg: return effectors[8];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the limb mapping for the limb.
|
||||
/// </summary>
|
||||
public IKMappingLimb GetLimbMapping(FullBodyBipedChain chain) {
|
||||
switch(chain) {
|
||||
case FullBodyBipedChain.LeftArm: return limbMappings[0];
|
||||
case FullBodyBipedChain.RightArm: return limbMappings[1];
|
||||
case FullBodyBipedChain.LeftLeg: return limbMappings[2];
|
||||
case FullBodyBipedChain.RightLeg: return limbMappings[3];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the limb mapping for the effector type.
|
||||
/// </summary>
|
||||
public IKMappingLimb GetLimbMapping(FullBodyBipedEffector effector) {
|
||||
switch(effector) {
|
||||
case FullBodyBipedEffector.LeftShoulder: return limbMappings[0];
|
||||
case FullBodyBipedEffector.RightShoulder: return limbMappings[1];
|
||||
case FullBodyBipedEffector.LeftThigh: return limbMappings[2];
|
||||
case FullBodyBipedEffector.RightThigh: return limbMappings[3];
|
||||
case FullBodyBipedEffector.LeftHand: return limbMappings[0];
|
||||
case FullBodyBipedEffector.RightHand: return limbMappings[1];
|
||||
case FullBodyBipedEffector.LeftFoot: return limbMappings[2];
|
||||
case FullBodyBipedEffector.RightFoot: return limbMappings[3];
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the spine mapping.
|
||||
/// </summary>
|
||||
public IKMappingSpine GetSpineMapping() {
|
||||
return spineMapping;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the head mapping.
|
||||
/// </summary>
|
||||
public IKMappingBone GetHeadMapping() {
|
||||
return boneMappings[0];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the bend constraint of a limb.
|
||||
/// </summary>
|
||||
public IKConstraintBend GetBendConstraint(FullBodyBipedChain limb) {
|
||||
switch(limb) {
|
||||
case FullBodyBipedChain.LeftArm: return chain[1].bendConstraint;
|
||||
case FullBodyBipedChain.RightArm: return chain[2].bendConstraint;
|
||||
case FullBodyBipedChain.LeftLeg: return chain[3].bendConstraint;
|
||||
case FullBodyBipedChain.RightLeg: return chain[4].bendConstraint;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool IsValid(ref string message) {
|
||||
if (!base.IsValid(ref message)) return false;
|
||||
|
||||
if (rootNode == null) {
|
||||
message = "Root Node bone is null. FBBIK will not initiate.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (chain.Length != 5 ||
|
||||
chain[0].nodes.Length != 1 ||
|
||||
chain[1].nodes.Length != 3 ||
|
||||
chain[2].nodes.Length != 3 ||
|
||||
chain[3].nodes.Length != 3 ||
|
||||
chain[4].nodes.Length != 3 ||
|
||||
effectors.Length != 9 ||
|
||||
limbMappings.Length != 4
|
||||
) {
|
||||
message = "Invalid FBBIK setup. Please right-click on the component header and select 'Reinitiate'.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets up the solver to BipedReferences and reinitiates (if in runtime).
|
||||
/// </summary>
|
||||
/// <param name="references">Biped references.</param>
|
||||
/// <param name="rootNode">Root node (optional). if null, will try to detect the root node bone automatically. </param>
|
||||
public void SetToReferences(BipedReferences references, Transform rootNode = null) {
|
||||
root = references.root;
|
||||
|
||||
if (rootNode == null) rootNode = DetectRootNodeBone(references);
|
||||
this.rootNode = rootNode;
|
||||
|
||||
// Root Node
|
||||
if (chain == null || chain.Length != 5) chain = new FBIKChain[5];
|
||||
for (int i = 0; i < chain.Length; i++) {
|
||||
if (chain[i] == null) {
|
||||
chain[i] = new FBIKChain();
|
||||
}
|
||||
}
|
||||
|
||||
chain[0].pin = 0f;
|
||||
chain[0].SetNodes(rootNode);
|
||||
chain[0].children = new int[4] { 1, 2, 3, 4 };
|
||||
|
||||
// Left Arm
|
||||
chain[1].SetNodes(references.leftUpperArm, references.leftForearm, references.leftHand);
|
||||
|
||||
// Right Arm
|
||||
chain[2].SetNodes(references.rightUpperArm, references.rightForearm, references.rightHand);
|
||||
|
||||
// Left Leg
|
||||
chain[3].SetNodes(references.leftThigh, references.leftCalf, references.leftFoot);
|
||||
|
||||
// Right Leg
|
||||
chain[4].SetNodes(references.rightThigh, references.rightCalf, references.rightFoot);
|
||||
|
||||
// Effectors
|
||||
if (effectors.Length != 9) effectors = new IKEffector[9] {
|
||||
new IKEffector(), new IKEffector(), new IKEffector(), new IKEffector(), new IKEffector(), new IKEffector(), new IKEffector(), new IKEffector(), new IKEffector()
|
||||
};
|
||||
|
||||
effectors[0].bone = rootNode;
|
||||
effectors[0].childBones = new Transform[2] { references.leftThigh, references.rightThigh };
|
||||
|
||||
effectors[1].bone = references.leftUpperArm;
|
||||
effectors[2].bone = references.rightUpperArm;
|
||||
effectors[3].bone = references.leftThigh;
|
||||
effectors[4].bone = references.rightThigh;
|
||||
effectors[5].bone = references.leftHand;
|
||||
effectors[6].bone = references.rightHand;
|
||||
effectors[7].bone = references.leftFoot;
|
||||
effectors[8].bone = references.rightFoot;
|
||||
|
||||
effectors[5].planeBone1 = references.leftUpperArm;
|
||||
effectors[5].planeBone2 = references.rightUpperArm;
|
||||
effectors[5].planeBone3 = rootNode;
|
||||
|
||||
effectors[6].planeBone1 = references.rightUpperArm;
|
||||
effectors[6].planeBone2 = references.leftUpperArm;
|
||||
effectors[6].planeBone3 = rootNode;
|
||||
|
||||
effectors[7].planeBone1 = references.leftThigh;
|
||||
effectors[7].planeBone2 = references.rightThigh;
|
||||
effectors[7].planeBone3 = rootNode;
|
||||
|
||||
effectors[8].planeBone1 = references.rightThigh;
|
||||
effectors[8].planeBone2 = references.leftThigh;
|
||||
effectors[8].planeBone3 = rootNode;
|
||||
|
||||
// Child Constraints
|
||||
chain[0].childConstraints = new FBIKChain.ChildConstraint[4] {
|
||||
new FBIKChain.ChildConstraint(references.leftUpperArm, references.rightThigh, 0f, 1f),
|
||||
new FBIKChain.ChildConstraint(references.rightUpperArm, references.leftThigh, 0f, 1f),
|
||||
new FBIKChain.ChildConstraint(references.leftUpperArm, references.rightUpperArm),
|
||||
new FBIKChain.ChildConstraint(references.leftThigh, references.rightThigh)
|
||||
|
||||
};
|
||||
|
||||
// IKMappingSpine
|
||||
Transform[] spineBones = new Transform[references.spine.Length + 1];
|
||||
spineBones[0] = references.pelvis;
|
||||
for (int i = 0; i < references.spine.Length; i++) {
|
||||
spineBones[i + 1] = references.spine[i];
|
||||
}
|
||||
|
||||
if (spineMapping == null) {
|
||||
spineMapping = new IKMappingSpine();
|
||||
spineMapping.iterations = 3;
|
||||
}
|
||||
spineMapping.SetBones(spineBones, references.leftUpperArm, references.rightUpperArm, references.leftThigh, references.rightThigh);
|
||||
|
||||
// IKMappingBone
|
||||
int boneMappingsCount = references.head != null? 1: 0;
|
||||
|
||||
if (boneMappings.Length != boneMappingsCount) {
|
||||
boneMappings = new IKMappingBone[boneMappingsCount];
|
||||
for (int i = 0; i < boneMappings.Length; i++) {
|
||||
boneMappings[i] = new IKMappingBone();
|
||||
}
|
||||
if (boneMappingsCount == 1) boneMappings[0].maintainRotationWeight = 0f;
|
||||
}
|
||||
|
||||
if (boneMappings.Length > 0) boneMappings[0].bone = references.head;
|
||||
|
||||
// IKMappingLimb
|
||||
if (limbMappings.Length != 4) {
|
||||
limbMappings = new IKMappingLimb[4] {
|
||||
new IKMappingLimb(), new IKMappingLimb(), new IKMappingLimb(), new IKMappingLimb()
|
||||
};
|
||||
|
||||
limbMappings[2].maintainRotationWeight = 1f;
|
||||
limbMappings[3].maintainRotationWeight = 1f;
|
||||
}
|
||||
|
||||
limbMappings[0].SetBones(references.leftUpperArm, references.leftForearm, references.leftHand, GetLeftClavicle(references));
|
||||
limbMappings[1].SetBones(references.rightUpperArm, references.rightForearm, references.rightHand, GetRightClavicle(references));
|
||||
limbMappings[2].SetBones(references.leftThigh, references.leftCalf, references.leftFoot);
|
||||
limbMappings[3].SetBones(references.rightThigh, references.rightCalf, references.rightFoot);
|
||||
|
||||
if (Application.isPlaying) Initiate(references.root);
|
||||
}
|
||||
|
||||
/*
|
||||
* Tries to guess which bone should be the root node
|
||||
* */
|
||||
public static Transform DetectRootNodeBone(BipedReferences references) {
|
||||
if (!references.isFilled) return null;
|
||||
if (references.spine.Length < 1) return null;
|
||||
|
||||
int spineLength = references.spine.Length;
|
||||
if (spineLength == 1) return references.spine[0];
|
||||
|
||||
Vector3 hip = Vector3.Lerp(references.leftThigh.position, references.rightThigh.position, 0.5f);
|
||||
Vector3 neck = Vector3.Lerp(references.leftUpperArm.position, references.rightUpperArm.position, 0.5f);
|
||||
Vector3 toNeck = neck - hip;
|
||||
float toNeckMag = toNeck.magnitude;
|
||||
|
||||
if (references.spine.Length < 2) return references.spine[0];
|
||||
|
||||
int rootNodeBone = 0;
|
||||
|
||||
for (int i = 1; i < spineLength; i++) {
|
||||
Vector3 hipToBone = references.spine[i].position - hip;
|
||||
Vector3 projection = Vector3.Project(hipToBone, toNeck);
|
||||
|
||||
float dot = Vector3.Dot(projection.normalized, toNeck.normalized);
|
||||
if (dot > 0) {
|
||||
float mag = projection.magnitude / toNeckMag;
|
||||
if (mag < 0.5f) rootNodeBone = i;
|
||||
}
|
||||
}
|
||||
|
||||
return references.spine[rootNodeBone];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the bend directions of the limbs to the local axes specified by BipedLimbOrientations.
|
||||
/// </summary>
|
||||
public void SetLimbOrientations(BipedLimbOrientations o) {
|
||||
SetLimbOrientation(FullBodyBipedChain.LeftArm, o.leftArm);
|
||||
SetLimbOrientation(FullBodyBipedChain.RightArm, o.rightArm);
|
||||
SetLimbOrientation(FullBodyBipedChain.LeftLeg, o.leftLeg);
|
||||
SetLimbOrientation(FullBodyBipedChain.RightLeg, o.rightLeg);
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
// Offset applied to the body effector by PullBody
|
||||
public Vector3 pullBodyOffset { get; private set; }
|
||||
|
||||
/*
|
||||
* Sets the bend direction of a limb to the local axes specified by the LimbOrientation.
|
||||
* */
|
||||
private void SetLimbOrientation(FullBodyBipedChain chain, BipedLimbOrientations.LimbOrientation limbOrientation) {
|
||||
bool inverse = chain == FullBodyBipedChain.LeftArm || chain == FullBodyBipedChain.RightArm;
|
||||
|
||||
if (inverse) {
|
||||
GetBendConstraint(chain).SetLimbOrientation(-limbOrientation.upperBoneForwardAxis, -limbOrientation.lowerBoneForwardAxis, -limbOrientation.lastBoneLeftAxis);
|
||||
GetLimbMapping(chain).SetLimbOrientation(-limbOrientation.upperBoneForwardAxis, -limbOrientation.lowerBoneForwardAxis);
|
||||
} else {
|
||||
GetBendConstraint(chain).SetLimbOrientation(limbOrientation.upperBoneForwardAxis, limbOrientation.lowerBoneForwardAxis, limbOrientation.lastBoneLeftAxis);
|
||||
GetLimbMapping(chain).SetLimbOrientation(limbOrientation.upperBoneForwardAxis, limbOrientation.lowerBoneForwardAxis);
|
||||
}
|
||||
}
|
||||
|
||||
private static Transform GetLeftClavicle(BipedReferences references) {
|
||||
if (references.leftUpperArm == null) return null;
|
||||
if (!Contains(references.spine, references.leftUpperArm.parent)) return references.leftUpperArm.parent;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Transform GetRightClavicle(BipedReferences references) {
|
||||
if (references.rightUpperArm == null) return null;
|
||||
if (!Contains(references.spine, references.rightUpperArm.parent)) return references.rightUpperArm.parent;
|
||||
return null;
|
||||
}
|
||||
|
||||
private static bool Contains(Transform[] array, Transform transform) {
|
||||
foreach (Transform t in array) if (t == transform) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void ReadPose() {
|
||||
// Set effectors to their targets
|
||||
for (int i = 0; i < effectors.Length; i++) effectors[i].SetToTarget();
|
||||
|
||||
// Pulling the body with the hands
|
||||
PullBody();
|
||||
|
||||
// Spine stiffness
|
||||
float s = Mathf.Clamp(1f - spineStiffness, 0f, 1f);
|
||||
chain[0].childConstraints[0].pushElasticity = s;
|
||||
chain[0].childConstraints[1].pushElasticity = s;
|
||||
|
||||
base.ReadPose();
|
||||
}
|
||||
|
||||
/*
|
||||
* Pulling the body with the hands
|
||||
* */
|
||||
private void PullBody() {
|
||||
if (iterations < 1) return;
|
||||
|
||||
// Getting the body positionOffset
|
||||
if (pullBodyVertical != 0f || pullBodyHorizontal != 0f) {
|
||||
Vector3 offset = GetBodyOffset();
|
||||
|
||||
pullBodyOffset = V3Tools.ExtractVertical(offset, root.up, pullBodyVertical) + V3Tools.ExtractHorizontal(offset, root.up, pullBodyHorizontal);
|
||||
bodyEffector.positionOffset += pullBodyOffset;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Get pull offset of the body effector.
|
||||
* */
|
||||
private Vector3 GetBodyOffset() {
|
||||
Vector3 offset = Vector3.zero + GetHandBodyPull(leftHandEffector, leftArmChain, Vector3.zero) * Mathf.Clamp(leftHandEffector.positionWeight, 0f, 1f);
|
||||
return offset + GetHandBodyPull(rightHandEffector, rightArmChain, offset) * Mathf.Clamp(rightHandEffector.positionWeight, 0f, 1f);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get pull offset of a hand
|
||||
* */
|
||||
private Vector3 GetHandBodyPull(IKEffector effector, FBIKChain arm, Vector3 offset) {
|
||||
// Get the vector from shoulder to hand effector
|
||||
Vector3 direction = effector.position - (arm.nodes[0].transform.position + offset);
|
||||
float armLength = arm.nodes[0].length + arm.nodes[1].length;
|
||||
|
||||
// Find delta of effector distance and arm length
|
||||
float dirMag = direction.magnitude;
|
||||
|
||||
if (dirMag < armLength) return Vector3.zero;
|
||||
float x = dirMag - armLength;
|
||||
|
||||
return (direction / dirMag) * x;
|
||||
}
|
||||
|
||||
private Vector3 offset;
|
||||
|
||||
protected override void ApplyBendConstraints() {
|
||||
if (iterations > 0) {
|
||||
chain[1].bendConstraint.rotationOffset = leftHandEffector.planeRotationOffset;
|
||||
chain[2].bendConstraint.rotationOffset = rightHandEffector.planeRotationOffset;
|
||||
chain[3].bendConstraint.rotationOffset = leftFootEffector.planeRotationOffset;
|
||||
chain[4].bendConstraint.rotationOffset = rightFootEffector.planeRotationOffset;
|
||||
} else {
|
||||
offset = Vector3.Lerp(effectors[0].positionOffset, effectors[0].position - (effectors[0].bone.position + effectors[0].positionOffset), effectors[0].positionWeight);
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
effectors[i].GetNode(this).solverPosition += offset;
|
||||
}
|
||||
}
|
||||
|
||||
base.ApplyBendConstraints();
|
||||
}
|
||||
|
||||
protected override void WritePose() {
|
||||
if (iterations == 0) {
|
||||
spineMapping.spineBones[0].position += offset;
|
||||
}
|
||||
|
||||
base.WritePose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b06331060dc2744f495261b5eccb9ad9
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,235 @@
|
||||
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 its 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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0ffa2c48532b04a7388e16278fe595ad
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,157 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// A wrapper for making IKSolverVRLeg work with other IK components and the Grounder.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKSolverLeg : IKSolver {
|
||||
|
||||
[Range(0f, 1f)]
|
||||
public float IKRotationWeight = 1f;
|
||||
/// <summary>
|
||||
/// The %IK rotation target.
|
||||
/// </summary>
|
||||
public Quaternion IKRotation = Quaternion.identity;
|
||||
|
||||
public IKSolver.Point pelvis = new IKSolver.Point();
|
||||
public IKSolver.Point thigh = new IKSolver.Point();
|
||||
public IKSolver.Point calf = new IKSolver.Point();
|
||||
public IKSolver.Point foot = new IKSolver.Point();
|
||||
public IKSolver.Point toe = new IKSolver.Point();
|
||||
|
||||
public IKSolverVR.Leg leg = new IKSolverVR.Leg();
|
||||
public Vector3 heelOffset;
|
||||
|
||||
private Vector3[] positions = new Vector3[6];
|
||||
private Quaternion[] rotations = new Quaternion[6];
|
||||
|
||||
public override bool IsValid(ref string message) {
|
||||
if (pelvis.transform == null || thigh.transform == null || calf.transform == null || foot.transform == null || toe.transform == null) {
|
||||
message = "Please assign all bone slots of the Leg IK solver.";
|
||||
return false;
|
||||
}
|
||||
|
||||
Transform duplicate = (Transform)Hierarchy.ContainsDuplicate(new Transform[5] { pelvis.transform, thigh.transform, calf.transform, foot.transform, toe.transform });
|
||||
if (duplicate != null) {
|
||||
message = duplicate.name + " is represented multiple times in the LegIK.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set IK rotation weight for the leg.
|
||||
/// </summary>
|
||||
public void SetRotationWeight(float weight)
|
||||
{
|
||||
IKRotationWeight = weight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reinitiate the solver with new bone Transforms.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns true if the new chain is valid.
|
||||
/// </returns>
|
||||
public bool SetChain(Transform pelvis, Transform thigh, Transform calf, Transform foot, Transform toe, Transform root) {
|
||||
this.pelvis.transform = pelvis;
|
||||
this.thigh.transform = thigh;
|
||||
this.calf.transform = calf;
|
||||
this.foot.transform = foot;
|
||||
this.toe.transform = toe;
|
||||
|
||||
Initiate(root);
|
||||
return initiated;
|
||||
}
|
||||
|
||||
public override IKSolver.Point[] GetPoints() {
|
||||
return new IKSolver.Point[5] { (IKSolver.Point)pelvis, (IKSolver.Point)thigh, (IKSolver.Point)calf, (IKSolver.Point)foot, (IKSolver.Point)toe };
|
||||
}
|
||||
|
||||
public override IKSolver.Point GetPoint(Transform transform) {
|
||||
if (pelvis.transform == transform) return (IKSolver.Point)pelvis;
|
||||
if (thigh.transform == transform) return (IKSolver.Point)thigh;
|
||||
if (calf.transform == transform) return (IKSolver.Point)calf;
|
||||
if (foot.transform == transform) return (IKSolver.Point)foot;
|
||||
if (toe.transform == transform) return (IKSolver.Point)toe;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void StoreDefaultLocalState() {
|
||||
thigh.StoreDefaultLocalState();
|
||||
calf.StoreDefaultLocalState();
|
||||
foot.StoreDefaultLocalState();
|
||||
toe.StoreDefaultLocalState();
|
||||
}
|
||||
|
||||
public override void FixTransforms() {
|
||||
if (!initiated) return;
|
||||
|
||||
thigh.FixTransform();
|
||||
calf.FixTransform();
|
||||
foot.FixTransform();
|
||||
toe.FixTransform();
|
||||
}
|
||||
|
||||
protected override void OnInitiate() {
|
||||
IKPosition = toe.transform.position;
|
||||
IKRotation = toe.transform.rotation;
|
||||
|
||||
Read ();
|
||||
}
|
||||
|
||||
protected override void OnUpdate() {
|
||||
Read ();
|
||||
Solve ();
|
||||
Write ();
|
||||
}
|
||||
|
||||
private void Solve() {
|
||||
leg.heelPositionOffset += heelOffset;
|
||||
|
||||
leg.PreSolve (1f);
|
||||
leg.ApplyOffsets(1f);
|
||||
leg.Solve (true);
|
||||
leg.ResetOffsets ();
|
||||
}
|
||||
|
||||
private void Read() {
|
||||
leg.IKPosition = IKPosition;
|
||||
leg.positionWeight = IKPositionWeight;
|
||||
leg.IKRotation = IKRotation;
|
||||
leg.rotationWeight = IKRotationWeight;
|
||||
|
||||
positions [0] = root.position;
|
||||
positions [1] = pelvis.transform.position;
|
||||
positions [2] = thigh.transform.position;
|
||||
positions [3] = calf.transform.position;
|
||||
positions [4] = foot.transform.position;
|
||||
positions [5] = toe.transform.position;
|
||||
|
||||
rotations [0] = root.rotation;
|
||||
rotations [1] = pelvis.transform.rotation;
|
||||
rotations [2] = thigh.transform.rotation;
|
||||
rotations [3] = calf.transform.rotation;
|
||||
rotations [4] = foot.transform.rotation;
|
||||
rotations [5] = toe.transform.rotation;
|
||||
|
||||
leg.Read(positions, rotations, false, false, false, true, true, 1, 2);
|
||||
}
|
||||
|
||||
private void Write() {
|
||||
leg.Write (ref positions, ref rotations);
|
||||
|
||||
thigh.transform.rotation = rotations [2];
|
||||
calf.transform.rotation = rotations [3];
|
||||
foot.transform.rotation = rotations [4];
|
||||
toe.transform.rotation = rotations [5];
|
||||
|
||||
calf.transform.position = positions[3];
|
||||
foot.transform.position = positions[4];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 833438e3b67258a4b8aec1195ac1284a
|
||||
timeCreated: 1487058641
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,264 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Extends IKSolverTrigonometric to add automatic bend and rotation modes.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKSolverLimb : IKSolverTrigonometric {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The AvatarIKGoal of this solver.
|
||||
/// </summary>
|
||||
public AvatarIKGoal goal;
|
||||
/// <summary>
|
||||
/// Bend normal modifier.
|
||||
/// </summary>
|
||||
public BendModifier bendModifier;
|
||||
/// <summary>
|
||||
/// Weight of maintaining the rotation of the third bone as it was before solving.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float maintainRotationWeight;
|
||||
/// <summary>
|
||||
/// Weight of bend normal modifier.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float bendModifierWeight = 1f;
|
||||
/// <summary>
|
||||
/// The bend goal Transform.
|
||||
/// </summary>
|
||||
public Transform bendGoal;
|
||||
|
||||
/// <summary>
|
||||
/// Used to record rotation of the last bone for one frame.
|
||||
/// If MaintainRotation is not called and maintainRotationWeight > 0, the solver will maintain the rotation of the last bone as it was before solving the %IK.
|
||||
/// You will probably need this if you wanted to maintain the animated rotation of a foot despite of any other %IK solver that manipulates its parents' rotation.
|
||||
/// So you would call %MaintainRotation() in LateUpdate() after animation and before updating the Spine %IK solver that would change the foot's rotation.
|
||||
/// </summary>
|
||||
public void MaintainRotation() {
|
||||
if (!initiated) return;
|
||||
|
||||
maintainRotation = bone3.transform.rotation;
|
||||
maintainRotationFor1Frame = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If Auto Bend is on "Animation', %MaintainBend() can be used to set the bend axis relative to the first bone's rotation.
|
||||
/// </summary>
|
||||
public void MaintainBend() {
|
||||
if (!initiated) return;
|
||||
|
||||
animationNormal = bone1.GetBendNormalFromCurrentRotation();
|
||||
|
||||
maintainBendFor1Frame = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Automatic bend modes.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public enum BendModifier {
|
||||
Animation, // Bending relative to the animated rotation of the first bone
|
||||
Target, // Bending relative to IKRotation
|
||||
Parent, // Bending relative to parentBone
|
||||
Arm, // Arm modifier tries to find the most biometrically natural and relaxed arm bend plane
|
||||
Goal // Use the bend goal Transform
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
/*
|
||||
* Override before Initiate
|
||||
* */
|
||||
protected override void OnInitiateVirtual() {
|
||||
defaultRootRotation = root.rotation;
|
||||
|
||||
if (bone1.transform.parent != null) {
|
||||
parentDefaultRotation = Quaternion.Inverse(defaultRootRotation) * bone1.transform.parent.rotation;
|
||||
}
|
||||
|
||||
if (bone3.rotationLimit != null) bone3.rotationLimit.Disable();
|
||||
bone3DefaultRotation = bone3.transform.rotation;
|
||||
|
||||
// Set bend plane to current (cant use the public SetBendPlaneToCurrent() method here because the solver has not initiated yet)
|
||||
Vector3 normal = Vector3.Cross(bone2.transform.position - bone1.transform.position, bone3.transform.position - bone2.transform.position);
|
||||
if (normal != Vector3.zero) bendNormal = normal;
|
||||
|
||||
animationNormal = bendNormal;
|
||||
|
||||
StoreAxisDirections(ref axisDirectionsLeft);
|
||||
StoreAxisDirections(ref axisDirectionsRight);
|
||||
}
|
||||
|
||||
/*
|
||||
* Changing stuff in Update() before solving
|
||||
* */
|
||||
protected override void OnUpdateVirtual() {
|
||||
if (IKPositionWeight > 0) {
|
||||
// Clamping weights
|
||||
bendModifierWeight = Mathf.Clamp(bendModifierWeight, 0f, 1f);
|
||||
maintainRotationWeight = Mathf.Clamp(maintainRotationWeight, 0f, 1f);
|
||||
|
||||
// Storing the bendNormal for reverting after solving
|
||||
_bendNormal = bendNormal;
|
||||
|
||||
// Modifying bendNormal
|
||||
bendNormal = GetModifiedBendNormal();
|
||||
}
|
||||
|
||||
if (maintainRotationWeight * IKPositionWeight > 0) {
|
||||
// Storing bone3 rotation
|
||||
bone3RotationBeforeSolve = maintainRotationFor1Frame? maintainRotation : bone3.transform.rotation;
|
||||
maintainRotationFor1Frame = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Changing stuff in Update() after solving
|
||||
* */
|
||||
protected override void OnPostSolveVirtual() {
|
||||
// Revert bendNormal to what it was before solving
|
||||
if (IKPositionWeight > 0) bendNormal = _bendNormal;
|
||||
|
||||
// Auto rotation modes
|
||||
if (maintainRotationWeight * IKPositionWeight > 0) {
|
||||
bone3.transform.rotation = Quaternion.Slerp(bone3.transform.rotation, bone3RotationBeforeSolve, maintainRotationWeight * IKPositionWeight);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Axis direction contains an arm bend axis for a specific IKPosition direction from the first bone. Used in Arm BendModifier mode.
|
||||
* */
|
||||
[System.Serializable]
|
||||
public struct AxisDirection {
|
||||
public Vector3 direction;
|
||||
public Vector3 axis;
|
||||
public float dot;
|
||||
|
||||
public AxisDirection(Vector3 direction, Vector3 axis) {
|
||||
this.direction = direction.normalized;
|
||||
this.axis = axis.normalized;
|
||||
this.dot = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public IKSolverLimb() {}
|
||||
|
||||
public IKSolverLimb(AvatarIKGoal goal) {
|
||||
this.goal = goal;
|
||||
}
|
||||
|
||||
private bool maintainBendFor1Frame, maintainRotationFor1Frame;
|
||||
private Quaternion defaultRootRotation, parentDefaultRotation, bone3RotationBeforeSolve, maintainRotation, bone3DefaultRotation;
|
||||
private Vector3 _bendNormal, animationNormal;
|
||||
private AxisDirection[] axisDirectionsLeft = new AxisDirection[4];
|
||||
private AxisDirection[] axisDirectionsRight = new AxisDirection[4];
|
||||
private AxisDirection[] axisDirections {
|
||||
get {
|
||||
if (goal == AvatarIKGoal.LeftHand) return axisDirectionsLeft;
|
||||
return axisDirectionsRight;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Storing Bend axes for arm directions. The directions and axes here were found empirically, feel free to edit, add or remove.
|
||||
* All vectors are in default character rotation space, where x is right and z is forward.
|
||||
* */
|
||||
private void StoreAxisDirections(ref AxisDirection[] axisDirections) {
|
||||
axisDirections[0] = new AxisDirection(Vector3.zero, new Vector3(-1f, 0f, 0f)); // default
|
||||
axisDirections[1] = new AxisDirection(new Vector3(0.5f, 0f, -0.2f), new Vector3(-0.5f, -1f, 1f)); // behind head
|
||||
axisDirections[2] = new AxisDirection(new Vector3(-0.5f, -1f, -0.2f), new Vector3(0f, 0.5f, -1f)); // arm twist
|
||||
axisDirections[3] = new AxisDirection(new Vector3(-0.5f, -0.5f, 1f), new Vector3(-1f, -1f, -1f)); // cross heart
|
||||
}
|
||||
|
||||
/*
|
||||
* Modifying bendNormal
|
||||
* */
|
||||
private Vector3 GetModifiedBendNormal() {
|
||||
float weight = bendModifierWeight;
|
||||
if (weight <= 0) return bendNormal;
|
||||
|
||||
switch(bendModifier) {
|
||||
// Animation Bend Mode attempts to maintain the bend axis as it is in the animation
|
||||
case BendModifier.Animation:
|
||||
if (!maintainBendFor1Frame) MaintainBend();
|
||||
maintainBendFor1Frame = false;
|
||||
return Vector3.Lerp(bendNormal, animationNormal, weight);
|
||||
|
||||
// Bending relative to the parent of the first bone
|
||||
case BendModifier.Parent:
|
||||
if (bone1.transform.parent == null) return bendNormal;
|
||||
|
||||
Quaternion parentRotation = bone1.transform.parent.rotation * Quaternion.Inverse(parentDefaultRotation);
|
||||
return Quaternion.Slerp(Quaternion.identity, parentRotation * Quaternion.Inverse(defaultRootRotation), weight) * bendNormal;
|
||||
|
||||
// Bending relative to IKRotation
|
||||
case BendModifier.Target:
|
||||
Quaternion targetRotation = IKRotation * Quaternion.Inverse(bone3DefaultRotation);
|
||||
return Quaternion.Slerp(Quaternion.identity, targetRotation, weight) * bendNormal;
|
||||
|
||||
// Anatomic Arm
|
||||
case BendModifier.Arm:
|
||||
if (bone1.transform.parent == null) return bendNormal;
|
||||
|
||||
// Disabling this for legs
|
||||
if (goal == AvatarIKGoal.LeftFoot || goal == AvatarIKGoal.RightFoot) {
|
||||
if (!Warning.logged) LogWarning("Trying to use the 'Arm' bend modifier on a leg.");
|
||||
return bendNormal;
|
||||
}
|
||||
|
||||
Vector3 direction = (IKPosition - bone1.transform.position).normalized;
|
||||
|
||||
// Converting direction to default world space
|
||||
direction = Quaternion.Inverse(bone1.transform.parent.rotation * Quaternion.Inverse(parentDefaultRotation)) * direction;
|
||||
|
||||
// Inverting direction for left hand
|
||||
if (goal == AvatarIKGoal.LeftHand) direction.x = -direction.x;
|
||||
|
||||
// Calculating dot products for all AxisDirections
|
||||
for (int i = 1; i < axisDirections.Length; i++) {
|
||||
axisDirections[i].dot = Mathf.Clamp(Vector3.Dot(axisDirections[i].direction, direction), 0f, 1f);
|
||||
axisDirections[i].dot = Interp.Float(axisDirections[i].dot, InterpolationMode.InOutQuintic);
|
||||
}
|
||||
|
||||
// Summing up the arm bend axis
|
||||
Vector3 sum = axisDirections[0].axis;
|
||||
|
||||
//for (int i = 1; i < axisDirections.Length; i++) sum = Vector3.Lerp(sum, axisDirections[i].axis, axisDirections[i].dot);
|
||||
for (int i = 1; i < axisDirections.Length; i++) sum = Vector3.Slerp(sum, axisDirections[i].axis, axisDirections[i].dot);
|
||||
|
||||
// Inverting sum for left hand
|
||||
if (goal == AvatarIKGoal.LeftHand) {
|
||||
sum.x = -sum.x;
|
||||
sum = -sum;
|
||||
}
|
||||
|
||||
// Converting sum back to parent space
|
||||
Vector3 armBendNormal = bone1.transform.parent.rotation * Quaternion.Inverse(parentDefaultRotation) * sum;
|
||||
|
||||
if (weight >= 1) return armBendNormal;
|
||||
return Vector3.Lerp(bendNormal, armBendNormal, weight);
|
||||
|
||||
// Bending towards the bend goal Transform
|
||||
case BendModifier.Goal:
|
||||
if (bendGoal == null) {
|
||||
if (!Warning.logged) LogWarning("Trying to use the 'Goal' Bend Modifier, but the Bend Goal is unassigned.");
|
||||
return bendNormal;
|
||||
}
|
||||
|
||||
Vector3 normal = Vector3.Cross(bendGoal.position - bone1.transform.position, IKPosition - bone1.transform.position);
|
||||
|
||||
if (normal == Vector3.zero) return bendNormal;
|
||||
|
||||
if (weight >= 1f) return normal;
|
||||
return Vector3.Lerp(bendNormal, normal, weight);
|
||||
default: return bendNormal;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 579cdedbac97d4a8fa6c00c94404c86c
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,462 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Rotates a hierarchy of bones to face a target.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKSolverLookAt : IKSolver {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The target Transform.
|
||||
/// </summary>
|
||||
public Transform target;
|
||||
/// <summary>
|
||||
/// The spine hierarchy.
|
||||
/// </summary>
|
||||
public LookAtBone[] spine = new LookAtBone[0];
|
||||
/// <summary>
|
||||
/// The head bone.
|
||||
/// </summary>
|
||||
public LookAtBone head = new LookAtBone();
|
||||
/// <summary>
|
||||
/// The eye bones.
|
||||
/// </summary>
|
||||
public LookAtBone[] eyes = new LookAtBone[0];
|
||||
/// <summary>
|
||||
/// The body weight.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float bodyWeight = 0.5f;
|
||||
/// <summary>
|
||||
/// The head weight.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float headWeight = 0.5f;
|
||||
/// <summary>
|
||||
/// The eyes weight.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float eyesWeight = 1f;
|
||||
/// <summary>
|
||||
/// Clamp weight for the body.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float clampWeight = 0.5f;
|
||||
/// <summary>
|
||||
/// Clamp weight for the head.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float clampWeightHead = 0.5f;
|
||||
/// <summary>
|
||||
/// Clamp weight for the eyes.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float clampWeightEyes = 0.5f;
|
||||
/// <summary>
|
||||
/// Number of sine smoothing iterations applied on clamping to make the clamping point smoother.
|
||||
/// </summary>
|
||||
[Range(0, 2)]
|
||||
public int clampSmoothing = 2;
|
||||
/// <summary>
|
||||
/// Weight distribution between the spine bones.
|
||||
/// </summary>
|
||||
public AnimationCurve spineWeightCurve = new AnimationCurve(new Keyframe[2] { new Keyframe(0f, 0.3f), new Keyframe(1f, 1f) });
|
||||
|
||||
/// <summary>
|
||||
/// Offset for the spine target in world space..
|
||||
/// </summary>
|
||||
public Vector3 spineTargetOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the look at weight. NOTE: You are welcome edit the weights directly, this method is here only to match the Unity's built in %IK API.
|
||||
/// </summary>
|
||||
public void SetLookAtWeight(float weight) {
|
||||
this.IKPositionWeight = Mathf.Clamp(weight, 0f, 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the look at weight. NOTE: You are welcome to edit the weights directly, this method is here only to match the Unity's built in %IK API.
|
||||
/// </summary>
|
||||
public void SetLookAtWeight(float weight, float bodyWeight) {
|
||||
this.IKPositionWeight = Mathf.Clamp(weight, 0f, 1f);
|
||||
this.bodyWeight = Mathf.Clamp(bodyWeight, 0f, 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the look at weight. NOTE: You are welcome to edit the weights directly, this method is here only to match the Unity's built in %IK API.
|
||||
/// </summary>
|
||||
public void SetLookAtWeight(float weight, float bodyWeight, float headWeight) {
|
||||
this.IKPositionWeight = Mathf.Clamp(weight, 0f, 1f);
|
||||
this.bodyWeight = Mathf.Clamp(bodyWeight, 0f, 1f);
|
||||
this.headWeight = Mathf.Clamp(headWeight, 0f, 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the look at weight. NOTE: You are welcome to edit the weights directly, this method is here only to match the Unity's built in %IK API.
|
||||
/// </summary>
|
||||
public void SetLookAtWeight(float weight, float bodyWeight, float headWeight, float eyesWeight) {
|
||||
this.IKPositionWeight = Mathf.Clamp(weight, 0f, 1f);
|
||||
this.bodyWeight = Mathf.Clamp(bodyWeight, 0f, 1f);
|
||||
this.headWeight = Mathf.Clamp(headWeight, 0f, 1f);
|
||||
this.eyesWeight = Mathf.Clamp(eyesWeight, 0f, 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the look at weight. NOTE: You are welcome to edit the weights directly, this method is here only to match the Unity's built in %IK API.
|
||||
/// </summary>
|
||||
public void SetLookAtWeight(float weight, float bodyWeight, float headWeight, float eyesWeight, float clampWeight) {
|
||||
this.IKPositionWeight = Mathf.Clamp(weight, 0f, 1f);
|
||||
this.bodyWeight = Mathf.Clamp(bodyWeight, 0f, 1f);
|
||||
this.headWeight = Mathf.Clamp(headWeight, 0f, 1f);
|
||||
this.eyesWeight = Mathf.Clamp(eyesWeight, 0f, 1f);
|
||||
this.clampWeight = Mathf.Clamp(clampWeight, 0f, 1f);
|
||||
this.clampWeightHead = this.clampWeight;
|
||||
this.clampWeightEyes = this.clampWeight;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the look at weight. NOTE: You are welcome to edit the weights directly, this method is here only to match the Unity's built in %IK API.
|
||||
/// </summary>
|
||||
public void SetLookAtWeight(float weight, float bodyWeight = 0f, float headWeight = 1f, float eyesWeight = 0.5f, float clampWeight = 0.5f, float clampWeightHead = 0.5f, float clampWeightEyes = 0.3f) {
|
||||
this.IKPositionWeight = Mathf.Clamp(weight, 0f, 1f);
|
||||
this.bodyWeight = Mathf.Clamp(bodyWeight, 0f, 1f);
|
||||
this.headWeight = Mathf.Clamp(headWeight, 0f, 1f);
|
||||
this.eyesWeight = Mathf.Clamp(eyesWeight, 0f, 1f);
|
||||
this.clampWeight = Mathf.Clamp(clampWeight, 0f, 1f);
|
||||
this.clampWeightHead = Mathf.Clamp(clampWeightHead, 0f, 1f);
|
||||
this.clampWeightEyes = Mathf.Clamp(clampWeightEyes, 0f, 1f);
|
||||
}
|
||||
|
||||
public override void StoreDefaultLocalState() {
|
||||
for (int i = 0; i < spine.Length; i++) spine[i].StoreDefaultLocalState();
|
||||
for (int i = 0; i < eyes.Length; i++) eyes[i].StoreDefaultLocalState();
|
||||
if (head != null && head.transform != null) head.StoreDefaultLocalState();
|
||||
}
|
||||
|
||||
// Flag for Fix Transforms.
|
||||
public void SetDirty()
|
||||
{
|
||||
isDirty = true;
|
||||
}
|
||||
|
||||
public override void FixTransforms() {
|
||||
if (!initiated) return;
|
||||
if (IKPositionWeight <= 0f && !isDirty) return;
|
||||
|
||||
for (int i = 0; i < spine.Length; i++) spine[i].FixTransform();
|
||||
for (int i = 0; i < eyes.Length; i++) eyes[i].FixTransform();
|
||||
if (head != null && head.transform != null) head.FixTransform();
|
||||
|
||||
isDirty = false;
|
||||
}
|
||||
|
||||
public override bool IsValid (ref string message) {
|
||||
if (!spineIsValid) {
|
||||
message = "IKSolverLookAt spine setup is invalid. Can't initiate solver.";
|
||||
return false;
|
||||
}
|
||||
if (!headIsValid) {
|
||||
message = "IKSolverLookAt head transform is null. Can't initiate solver.";
|
||||
return false;
|
||||
}
|
||||
if (!eyesIsValid) {
|
||||
message = "IKSolverLookAt eyes setup is invalid. Can't initiate solver.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (spineIsEmpty && headIsEmpty && eyesIsEmpty) {
|
||||
message = "IKSolverLookAt eyes setup is invalid. Can't initiate solver.";
|
||||
return false;
|
||||
}
|
||||
|
||||
Transform spineDuplicate = ContainsDuplicateBone(spine);
|
||||
if (spineDuplicate != null) {
|
||||
message = spineDuplicate.name + " is represented multiple times in a single IK chain. Can't initiate solver.";
|
||||
return false;
|
||||
}
|
||||
Transform eyeDuplicate = ContainsDuplicateBone(eyes);
|
||||
if (eyeDuplicate != null) {
|
||||
message = eyeDuplicate.name + " is represented multiple times in a single IK chain. Can't initiate solver.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public override IKSolver.Point[] GetPoints() {
|
||||
IKSolver.Point[] allPoints = new IKSolver.Point[spine.Length + eyes.Length + (head.transform != null? 1: 0)];
|
||||
for (int i = 0; i < spine.Length; i++) allPoints[i] = spine[i] as IKSolver.Point;
|
||||
|
||||
int eye = 0;
|
||||
for (int i = spine.Length; i < spine.Length + eyes.Length; i++)
|
||||
{
|
||||
allPoints[i] = eyes[eye] as IKSolver.Point;
|
||||
eye++;
|
||||
}
|
||||
|
||||
if (head.transform != null) allPoints[allPoints.Length - 1] = head as IKSolver.Point;
|
||||
return allPoints;
|
||||
}
|
||||
|
||||
public override IKSolver.Point GetPoint(Transform transform) {
|
||||
foreach (IKSolverLookAt.LookAtBone b in spine) if (b.transform == transform) return b as IKSolver.Point;
|
||||
foreach (IKSolverLookAt.LookAtBone b in eyes) if (b.transform == transform) return b as IKSolver.Point;
|
||||
if (head.transform == transform) return head as IKSolver.Point;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Look At bone class.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class LookAtBone: IKSolver.Bone {
|
||||
|
||||
#region Public methods
|
||||
|
||||
public Vector3 baseForwardOffsetEuler;
|
||||
|
||||
public LookAtBone() {}
|
||||
|
||||
/*
|
||||
* Custom constructor
|
||||
* */
|
||||
public LookAtBone(Transform transform) {
|
||||
this.transform = transform;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initiates the bone, precalculates values.
|
||||
* */
|
||||
public void Initiate(Transform root) {
|
||||
if (transform == null) return;
|
||||
|
||||
axis = Quaternion.Inverse(transform.rotation) * root.forward;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rotates the bone to look at a world direction.
|
||||
* */
|
||||
public void LookAt(Vector3 direction, float weight) {
|
||||
Quaternion fromTo = Quaternion.FromToRotation(forward, direction);
|
||||
Quaternion r = transform.rotation;
|
||||
transform.rotation = Quaternion.Lerp(r, fromTo * r, weight);
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the local axis to goal in world space.
|
||||
* */
|
||||
public Vector3 forward {
|
||||
get {
|
||||
return transform.rotation * axis;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Public methods
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reinitiate the solver with new bone Transforms.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns true if the new chain is valid.
|
||||
/// </returns>
|
||||
public bool SetChain(Transform[] spine, Transform head, Transform[] eyes, Transform root) {
|
||||
// Spine
|
||||
SetBones(spine, ref this.spine);
|
||||
|
||||
// Head
|
||||
this.head = new LookAtBone(head);
|
||||
|
||||
// Eyes
|
||||
SetBones(eyes, ref this.eyes);
|
||||
|
||||
Initiate(root);
|
||||
return initiated;
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
protected Vector3[] spineForwards = new Vector3[0];
|
||||
protected Vector3[] headForwards = new Vector3[1];
|
||||
protected Vector3[] eyeForward = new Vector3[1];
|
||||
private bool isDirty;
|
||||
|
||||
protected override void OnInitiate() {
|
||||
// Set IKPosition to default value
|
||||
if (firstInitiation || !Application.isPlaying) {
|
||||
if (spine.Length > 0) IKPosition = spine[spine.Length - 1].transform.position + root.forward * 3f;
|
||||
else if (head.transform != null) IKPosition = head.transform.position + root.forward * 3f;
|
||||
else if (eyes.Length > 0 && eyes[0].transform != null) IKPosition = eyes[0].transform.position + root.forward * 3f;
|
||||
}
|
||||
|
||||
// Initiating the bones
|
||||
foreach (LookAtBone s in spine) s.Initiate(root);
|
||||
if (head != null) head.Initiate(root);
|
||||
foreach (LookAtBone eye in eyes) eye.Initiate(root);
|
||||
|
||||
if (spineForwards == null || spineForwards.Length != spine.Length) spineForwards = new Vector3[spine.Length];
|
||||
if (headForwards == null) headForwards = new Vector3[1];
|
||||
if (eyeForward == null) eyeForward = new Vector3[1];
|
||||
}
|
||||
|
||||
protected override void OnUpdate() {
|
||||
if (IKPositionWeight <= 0) return;
|
||||
IKPositionWeight = Mathf.Clamp(IKPositionWeight, 0f, 1f);
|
||||
|
||||
if (target != null) IKPosition = target.position;
|
||||
|
||||
// Solving the hierarchies
|
||||
SolveSpine();
|
||||
SolveHead();
|
||||
SolveEyes();
|
||||
}
|
||||
|
||||
protected bool spineIsValid {
|
||||
get {
|
||||
if (spine == null) return false;
|
||||
if (spine.Length == 0) return true;
|
||||
|
||||
for (int i = 0; i < spine.Length; i++) if (spine[i] == null || spine[i].transform == null) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool spineIsEmpty { get { return spine.Length == 0; }}
|
||||
|
||||
// Solving the spine hierarchy
|
||||
protected void SolveSpine() {
|
||||
if (bodyWeight <= 0) return;
|
||||
if (spineIsEmpty) return;
|
||||
|
||||
// Get the look at vectors for each bone
|
||||
//Vector3 targetForward = Vector3.Lerp(spine[0].forward, (IKPosition - spine[spine.Length - 1].transform.position).normalized, bodyWeight * IKPositionWeight).normalized;
|
||||
Vector3 targetForward = (IKPosition + spineTargetOffset - spine[spine.Length - 1].transform.position).normalized;
|
||||
|
||||
GetForwards(ref spineForwards, spine[0].forward, targetForward, spine.Length, clampWeight);
|
||||
|
||||
// Rotate each bone to face their look at vectors
|
||||
for (int i = 0; i < spine.Length; i++) {
|
||||
spine[i].LookAt(spineForwards[i], bodyWeight * IKPositionWeight);
|
||||
}
|
||||
}
|
||||
|
||||
protected bool headIsValid {
|
||||
get {
|
||||
if (head == null) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool headIsEmpty { get { return head.transform == null; }}
|
||||
|
||||
// Solving the head rotation
|
||||
protected void SolveHead() {
|
||||
if (headWeight <= 0) return;
|
||||
if (headIsEmpty) return;
|
||||
|
||||
// Get the look at vector for the head
|
||||
Vector3 baseForward = spine.Length > 0 && spine[spine.Length - 1].transform != null? spine[spine.Length - 1].forward: head.forward;
|
||||
|
||||
Vector3 targetForward = Vector3.Lerp(baseForward, (IKPosition - head.transform.position).normalized, headWeight * IKPositionWeight).normalized;
|
||||
GetForwards(ref headForwards, baseForward, targetForward, 1, clampWeightHead);
|
||||
|
||||
// Rotate the head to face its look at vector
|
||||
head.LookAt(headForwards[0], headWeight * IKPositionWeight);
|
||||
}
|
||||
|
||||
protected bool eyesIsValid {
|
||||
get {
|
||||
if (eyes == null) return false;
|
||||
if (eyes.Length == 0) return true;
|
||||
|
||||
for (int i = 0; i < eyes.Length; i++) if (eyes[i] == null || eyes[i].transform == null) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
protected bool eyesIsEmpty { get { return eyes.Length == 0; }}
|
||||
|
||||
// Solving the eye rotations
|
||||
protected void SolveEyes() {
|
||||
if (eyesWeight <= 0) return;
|
||||
if (eyesIsEmpty) return;
|
||||
|
||||
for (int i = 0; i < eyes.Length; i++) {
|
||||
// Get the look at vector for the eye
|
||||
Quaternion baseRotation = head.transform != null ? head.transform.rotation : spine.Length > 0? spine[spine.Length - 1].transform.rotation: root.rotation;
|
||||
Vector3 baseAxis = head.transform != null ? head.axis : spine.Length > 0 ? spine[spine.Length - 1].axis : root.forward;
|
||||
|
||||
if (eyes[i].baseForwardOffsetEuler != Vector3.zero) baseRotation *= Quaternion.Euler(eyes[i].baseForwardOffsetEuler);
|
||||
|
||||
Vector3 baseForward = baseRotation * baseAxis;
|
||||
|
||||
GetForwards(ref eyeForward, baseForward, (IKPosition - eyes[i].transform.position).normalized, 1, clampWeightEyes);
|
||||
|
||||
// Rotate the eye to face its look at vector
|
||||
eyes[i].LookAt(eyeForward[0], eyesWeight * IKPositionWeight);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns forwards for a number of bones rotating from baseForward to targetForward.
|
||||
* NB! Make sure baseForward and targetForward are normalized.
|
||||
* */
|
||||
protected Vector3[] GetForwards(ref Vector3[] forwards, Vector3 baseForward, Vector3 targetForward, int bones, float clamp) {
|
||||
// If clamp >= 1 make all the forwards match the base
|
||||
if (clamp >= 1 || IKPositionWeight <= 0) {
|
||||
for (int i = 0; i < forwards.Length; i++) forwards[i] = baseForward;
|
||||
return forwards;
|
||||
}
|
||||
|
||||
// Get normalized dot product.
|
||||
float angle = Vector3.Angle(baseForward, targetForward);
|
||||
float dot = 1f - (angle / 180f);
|
||||
|
||||
// Clamping the targetForward so it doesn't exceed clamp
|
||||
float targetClampMlp = clamp > 0? Mathf.Clamp(1f - ((clamp - dot) / (1f - dot)), 0f, 1f): 1f;
|
||||
|
||||
// Calculating the clamp multiplier
|
||||
float clampMlp = clamp > 0? Mathf.Clamp(dot / clamp, 0f, 1f): 1f;
|
||||
|
||||
for (int i = 0; i < clampSmoothing; i++) {
|
||||
float sinF = clampMlp * Mathf.PI * 0.5f;
|
||||
clampMlp = Mathf.Sin(sinF);
|
||||
}
|
||||
|
||||
// Rotation amount for 1 bone
|
||||
if (forwards.Length == 1) {
|
||||
forwards[0] = Vector3.Slerp(baseForward, targetForward, clampMlp * targetClampMlp);
|
||||
} else {
|
||||
float step = 1f / (float)(forwards.Length - 1);
|
||||
|
||||
// Calculate the forward for each bone
|
||||
for (int i = 0; i < forwards.Length; i++) {
|
||||
forwards[i] = Vector3.Slerp(baseForward, targetForward, spineWeightCurve.Evaluate(step * i) * clampMlp * targetClampMlp);
|
||||
}
|
||||
}
|
||||
|
||||
return forwards;
|
||||
}
|
||||
|
||||
/*
|
||||
* Build LookAtBone[] array of a Transform array
|
||||
* */
|
||||
protected void SetBones(Transform[] array, ref LookAtBone[] bones) {
|
||||
if (array == null) {
|
||||
bones = new LookAtBone[0];
|
||||
return;
|
||||
}
|
||||
|
||||
if (bones.Length != array.Length) bones = new LookAtBone[array.Length];
|
||||
|
||||
for (int i = 0; i < array.Length; i++) {
|
||||
if (bones[i] == null) bones[i] = new LookAtBone(array[i]);
|
||||
else bones[i].transform = array[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94d39c9f4eb4440c3b87777aec05466f
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,363 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Analytic %IK solver based on the Law of Cosines.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IKSolverTrigonometric: IKSolver {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The target Transform.
|
||||
/// </summary>
|
||||
public Transform target;
|
||||
/// <summary>
|
||||
/// The %IK rotation weight (rotation of the last bone).
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float IKRotationWeight = 1f;
|
||||
/// <summary>
|
||||
/// The %IK rotation target.
|
||||
/// </summary>
|
||||
public Quaternion IKRotation = Quaternion.identity;
|
||||
/// <summary>
|
||||
/// The bend plane normal.
|
||||
/// </summary>
|
||||
public Vector3 bendNormal = Vector3.right;
|
||||
/// <summary>
|
||||
/// The first bone (upper arm or thigh).
|
||||
/// </summary>
|
||||
public TrigonometricBone bone1 = new TrigonometricBone();
|
||||
/// <summary>
|
||||
/// The second bone (forearm or calf).
|
||||
/// </summary>
|
||||
public TrigonometricBone bone2 = new TrigonometricBone();
|
||||
/// <summary>
|
||||
/// The third bone (hand or foot).
|
||||
/// </summary>
|
||||
public TrigonometricBone bone3 = new TrigonometricBone();
|
||||
|
||||
/// <summary>
|
||||
/// Sets the bend goal position.
|
||||
/// </summary>
|
||||
/// <param name='goalPosition'>
|
||||
/// Goal position.
|
||||
/// </param>
|
||||
public void SetBendGoalPosition(Vector3 goalPosition, float weight) {
|
||||
if (!initiated) return;
|
||||
if (weight <= 0f) return;
|
||||
|
||||
Vector3 normal = Vector3.Cross(goalPosition - bone1.transform.position, IKPosition - bone1.transform.position);
|
||||
if (normal != Vector3.zero) {
|
||||
if (weight >= 1f) {
|
||||
bendNormal = normal;
|
||||
return;
|
||||
}
|
||||
|
||||
bendNormal = Vector3.Lerp(bendNormal, normal, weight);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the bend plane to match current bone rotations.
|
||||
/// </summary>
|
||||
public void SetBendPlaneToCurrent() {
|
||||
if (!initiated) return;
|
||||
|
||||
Vector3 normal = Vector3.Cross(bone2.transform.position - bone1.transform.position, bone3.transform.position - bone2.transform.position);
|
||||
if (normal != Vector3.zero) bendNormal = normal;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the %IK rotation.
|
||||
/// </summary>
|
||||
public void SetIKRotation(Quaternion rotation) {
|
||||
IKRotation = rotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the %IK rotation weight.
|
||||
/// </summary>
|
||||
public void SetIKRotationWeight(float weight) {
|
||||
IKRotationWeight = Mathf.Clamp(weight, 0f, 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the %IK rotation.
|
||||
/// </summary>
|
||||
public Quaternion GetIKRotation() {
|
||||
return IKRotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the %IK rotation weight.
|
||||
/// </summary>
|
||||
public float GetIKRotationWeight() {
|
||||
return IKRotationWeight;
|
||||
}
|
||||
|
||||
public override IKSolver.Point[] GetPoints() {
|
||||
return new IKSolver.Point[3] { (IKSolver.Point)bone1, (IKSolver.Point)bone2, (IKSolver.Point)bone3 };
|
||||
}
|
||||
|
||||
public override IKSolver.Point GetPoint(Transform transform) {
|
||||
if (bone1.transform == transform) return (IKSolver.Point)bone1;
|
||||
if (bone2.transform == transform) return (IKSolver.Point)bone2;
|
||||
if (bone3.transform == transform) return (IKSolver.Point)bone3;
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void StoreDefaultLocalState() {
|
||||
bone1.StoreDefaultLocalState();
|
||||
bone2.StoreDefaultLocalState();
|
||||
bone3.StoreDefaultLocalState();
|
||||
}
|
||||
|
||||
public override void FixTransforms() {
|
||||
if (!initiated) return;
|
||||
|
||||
bone1.FixTransform();
|
||||
bone2.FixTransform();
|
||||
bone3.FixTransform();
|
||||
}
|
||||
|
||||
public override bool IsValid(ref string message) {
|
||||
if (bone1.transform == null || bone2.transform == null || bone3.transform == null) {
|
||||
message = "Please assign all Bones to the IK solver.";
|
||||
return false;
|
||||
}
|
||||
|
||||
Transform duplicate = (Transform)Hierarchy.ContainsDuplicate(new Transform[3] { bone1.transform, bone2.transform, bone3.transform });
|
||||
if (duplicate != null) {
|
||||
message = duplicate.name + " is represented multiple times in the Bones.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bone1.transform.position == bone2.transform.position) {
|
||||
message = "first bone position is the same as second bone position.";
|
||||
return false;
|
||||
}
|
||||
if (bone2.transform.position == bone3.transform.position) {
|
||||
message = "second bone position is the same as third bone position.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Bone type used by IKSolverTrigonometric.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class TrigonometricBone: IKSolver.Bone {
|
||||
|
||||
private Quaternion targetToLocalSpace;
|
||||
private Vector3 defaultLocalBendNormal;
|
||||
|
||||
#region Public methods
|
||||
|
||||
/*
|
||||
* Initiates the bone, precalculates values.
|
||||
* */
|
||||
public void Initiate(Vector3 childPosition, Vector3 bendNormal) {
|
||||
// Get default target rotation that looks at child position with bendNormal as up
|
||||
Quaternion defaultTargetRotation = Quaternion.LookRotation(childPosition - transform.position, bendNormal);
|
||||
|
||||
// Covert default target rotation to local space
|
||||
targetToLocalSpace = QuaTools.RotationToLocalSpace(transform.rotation, defaultTargetRotation);
|
||||
|
||||
defaultLocalBendNormal = Quaternion.Inverse(transform.rotation) * bendNormal;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculates the rotation of this bone to targetPosition.
|
||||
* */
|
||||
public Quaternion GetRotation(Vector3 direction, Vector3 bendNormal) {
|
||||
return Quaternion.LookRotation(direction, bendNormal) * targetToLocalSpace;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets the bend normal from current bone rotation.
|
||||
* */
|
||||
public Vector3 GetBendNormalFromCurrentRotation() {
|
||||
return transform.rotation * defaultLocalBendNormal;
|
||||
}
|
||||
|
||||
#endregion Public methods
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reinitiate the solver with new bone Transforms.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns true if the new chain is valid.
|
||||
/// </returns>
|
||||
public bool SetChain(Transform bone1, Transform bone2, Transform bone3, Transform root) {
|
||||
this.bone1.transform = bone1;
|
||||
this.bone2.transform = bone2;
|
||||
this.bone3.transform = bone3;
|
||||
|
||||
Initiate(root);
|
||||
return initiated;
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
#region Class Methods
|
||||
|
||||
/// <summary>
|
||||
/// Solve the bone chain.
|
||||
/// </summary>
|
||||
public static void Solve(Transform bone1, Transform bone2, Transform bone3, Vector3 targetPosition, Vector3 bendNormal, float weight) {
|
||||
if (weight <= 0f) return;
|
||||
|
||||
// Direction of the limb in solver
|
||||
targetPosition = Vector3.Lerp(bone3.position, targetPosition, weight);
|
||||
|
||||
Vector3 dir = targetPosition - bone1.position;
|
||||
|
||||
// Distance between the first and the last node solver positions
|
||||
float length = dir.magnitude;
|
||||
if (length == 0f) return;
|
||||
|
||||
float sqrMag1 = (bone2.position - bone1.position).sqrMagnitude;
|
||||
float sqrMag2 = (bone3.position - bone2.position).sqrMagnitude;
|
||||
|
||||
// Get the general world space bending direction
|
||||
Vector3 bendDir = Vector3.Cross(dir, bendNormal);
|
||||
|
||||
// Get the direction to the trigonometrically solved position of the second node
|
||||
Vector3 toBendPoint = GetDirectionToBendPoint(dir, length, bendDir, sqrMag1, sqrMag2);
|
||||
|
||||
// Position the second node
|
||||
Quaternion q1 = Quaternion.FromToRotation(bone2.position - bone1.position, toBendPoint);
|
||||
if (weight < 1f) q1 = Quaternion.Lerp(Quaternion.identity, q1, weight);
|
||||
|
||||
bone1.rotation = q1 * bone1.rotation;
|
||||
|
||||
Quaternion q2 = Quaternion.FromToRotation(bone3.position - bone2.position, targetPosition - bone2.position);
|
||||
if (weight < 1f) q2 = Quaternion.Lerp(Quaternion.identity, q2, weight);
|
||||
|
||||
bone2.rotation = q2 * bone2.rotation;
|
||||
}
|
||||
|
||||
//Calculates the bend direction based on the law of cosines. NB! Magnitude of the returned vector does not equal to the length of the first bone!
|
||||
private static Vector3 GetDirectionToBendPoint(Vector3 direction, float directionMag, Vector3 bendDirection, float sqrMag1, float sqrMag2) {
|
||||
float x = ((directionMag * directionMag) + (sqrMag1 - sqrMag2)) / 2f / directionMag;
|
||||
float y = (float)Math.Sqrt(Mathf.Clamp(sqrMag1 - x * x, 0, Mathf.Infinity));
|
||||
|
||||
if (direction == Vector3.zero) return Vector3.zero;
|
||||
return Quaternion.LookRotation(direction, bendDirection) * new Vector3(0f, y, x);
|
||||
}
|
||||
|
||||
#endregion Class Methods
|
||||
|
||||
protected override void OnInitiate() {
|
||||
if (bendNormal == Vector3.zero) bendNormal = Vector3.right;
|
||||
|
||||
OnInitiateVirtual();
|
||||
|
||||
IKPosition = bone3.transform.position;
|
||||
IKRotation = bone3.transform.rotation;
|
||||
|
||||
// Initiating bones
|
||||
InitiateBones();
|
||||
|
||||
directHierarchy = IsDirectHierarchy();
|
||||
}
|
||||
|
||||
// Are the bones parented directly to each other?
|
||||
private bool IsDirectHierarchy() {
|
||||
if (bone3.transform.parent != bone2.transform) return false;
|
||||
if (bone2.transform.parent != bone1.transform) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set the defaults for the bones
|
||||
public void InitiateBones() {
|
||||
bone1.Initiate(bone2.transform.position, bendNormal);
|
||||
bone2.Initiate(bone3.transform.position, bendNormal);
|
||||
|
||||
SetBendPlaneToCurrent();
|
||||
}
|
||||
|
||||
protected override void OnUpdate() {
|
||||
IKPositionWeight = Mathf.Clamp(IKPositionWeight, 0f, 1f);
|
||||
IKRotationWeight = Mathf.Clamp(IKRotationWeight, 0f, 1f);
|
||||
|
||||
if (target != null) {
|
||||
IKPosition = target.position;
|
||||
IKRotation = target.rotation;
|
||||
}
|
||||
|
||||
OnUpdateVirtual();
|
||||
|
||||
if (IKPositionWeight > 0) {
|
||||
|
||||
// Reinitiating the bones when the hierarchy is not direct. This allows for skipping animated bones in the hierarchy.
|
||||
if (!directHierarchy) {
|
||||
bone1.Initiate(bone2.transform.position, bendNormal);
|
||||
bone2.Initiate(bone3.transform.position, bendNormal);
|
||||
}
|
||||
|
||||
// Find out if bone lengths should be updated
|
||||
bone1.sqrMag = (bone2.transform.position - bone1.transform.position).sqrMagnitude;
|
||||
bone2.sqrMag = (bone3.transform.position - bone2.transform.position).sqrMagnitude;
|
||||
|
||||
if (bendNormal == Vector3.zero && !Warning.logged) LogWarning("IKSolverTrigonometric Bend Normal is Vector3.zero.");
|
||||
|
||||
weightIKPosition = Vector3.Lerp(bone3.transform.position, IKPosition, IKPositionWeight);
|
||||
|
||||
// Interpolating bend normal
|
||||
Vector3 currentBendNormal = Vector3.Lerp(bone1.GetBendNormalFromCurrentRotation(), bendNormal, IKPositionWeight);
|
||||
|
||||
// Calculating and interpolating bend direction
|
||||
Vector3 bendDirection = Vector3.Lerp(bone2.transform.position - bone1.transform.position, GetBendDirection(weightIKPosition, currentBendNormal), IKPositionWeight);
|
||||
|
||||
if (bendDirection == Vector3.zero) bendDirection = bone2.transform.position - bone1.transform.position;
|
||||
|
||||
// Rotating bone1
|
||||
bone1.transform.rotation = bone1.GetRotation(bendDirection, currentBendNormal);
|
||||
|
||||
// Rotating bone 2
|
||||
bone2.transform.rotation = bone2.GetRotation(weightIKPosition - bone2.transform.position, bone2.GetBendNormalFromCurrentRotation());
|
||||
}
|
||||
|
||||
// Rotating bone3
|
||||
if (IKRotationWeight > 0) {
|
||||
bone3.transform.rotation = Quaternion.Slerp(bone3.transform.rotation, IKRotation, IKRotationWeight);
|
||||
}
|
||||
|
||||
OnPostSolveVirtual();
|
||||
}
|
||||
|
||||
protected Vector3 weightIKPosition;
|
||||
protected virtual void OnInitiateVirtual() {}
|
||||
protected virtual void OnUpdateVirtual() {}
|
||||
protected virtual void OnPostSolveVirtual() {}
|
||||
protected bool directHierarchy = true;
|
||||
|
||||
/*
|
||||
* Calculates the bend direction based on the Law of Cosines.
|
||||
* */
|
||||
protected Vector3 GetBendDirection(Vector3 IKPosition, Vector3 bendNormal) {
|
||||
Vector3 direction = IKPosition - bone1.transform.position;
|
||||
if (direction == Vector3.zero) return Vector3.zero;
|
||||
|
||||
float directionSqrMag = direction.sqrMagnitude;
|
||||
float directionMagnitude = (float)Math.Sqrt(directionSqrMag);
|
||||
|
||||
float x = (directionSqrMag + bone1.sqrMag - bone2.sqrMag) / 2f / directionMagnitude;
|
||||
float y = (float)Math.Sqrt(Mathf.Clamp(bone1.sqrMag - x * x, 0, Mathf.Infinity));
|
||||
|
||||
Vector3 yDirection = Vector3.Cross(direction / directionMagnitude, bendNormal);
|
||||
return Quaternion.LookRotation(direction, yDirection) * new Vector3(0f, y, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23a7ead232727430e926213345415822
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,732 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
using RootMotion;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Hybrid %IK solver designed for mapping a character to a VR headset and 2 hand controllers
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public partial class IKSolverVR: IKSolver {
|
||||
|
||||
#region Wrapper
|
||||
|
||||
public Animator animator { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets this VRIK up to the specified bone references.
|
||||
/// </summary>
|
||||
public void SetToReferences(VRIK.References references) {
|
||||
if (!references.isFilled) {
|
||||
Debug.LogError("Invalid references, one or more Transforms are missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
animator = references.root.GetComponent<Animator>();
|
||||
|
||||
solverTransforms = references.GetTransforms();
|
||||
|
||||
hasChest = solverTransforms [3] != null;
|
||||
hasNeck = solverTransforms[4] != null;
|
||||
hasShoulders = solverTransforms[6] != null && solverTransforms[10] != null;
|
||||
hasToes = solverTransforms[17] != null && solverTransforms[21] != null;
|
||||
hasLegs = solverTransforms[14] != null;
|
||||
hasArms = solverTransforms[7] != null;
|
||||
|
||||
readPositions = new Vector3[solverTransforms.Length];
|
||||
readRotations = new Quaternion[solverTransforms.Length];
|
||||
|
||||
DefaultAnimationCurves();
|
||||
GuessHandOrientations(references, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Guesses the hand bones orientations ('Wrist To Palm Axis' and "Palm To Thumb Axis" of the arms) based on the provided references. if onlyIfZero is true, will only guess an orientation axis if it is Vector3.zero.
|
||||
/// </summary>
|
||||
public void GuessHandOrientations(VRIK.References references, bool onlyIfZero) {
|
||||
if (!references.isFilled) {
|
||||
Debug.LogError("VRIK References are not filled in, can not guess hand orientations. Right-click on VRIK header and slect 'Guess Hand Orientations' when you have filled in the References.", references.root);
|
||||
return;
|
||||
}
|
||||
|
||||
if (leftArm.wristToPalmAxis == Vector3.zero || !onlyIfZero) {
|
||||
leftArm.wristToPalmAxis = VRIKCalibrator.GuessWristToPalmAxis(references.leftHand, references.leftForearm);
|
||||
}
|
||||
|
||||
if (leftArm.palmToThumbAxis == Vector3.zero || !onlyIfZero) {
|
||||
leftArm.palmToThumbAxis = VRIKCalibrator.GuessPalmToThumbAxis(references.leftHand, references.leftForearm);
|
||||
}
|
||||
|
||||
if (rightArm.wristToPalmAxis == Vector3.zero || !onlyIfZero) {
|
||||
rightArm.wristToPalmAxis = VRIKCalibrator.GuessWristToPalmAxis(references.rightHand, references.rightForearm);
|
||||
}
|
||||
|
||||
if (rightArm.palmToThumbAxis == Vector3.zero || !onlyIfZero) {
|
||||
rightArm.palmToThumbAxis = VRIKCalibrator.GuessPalmToThumbAxis(references.rightHand, references.rightForearm);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set default values for the animation curves if they have no keys.
|
||||
/// </summary>
|
||||
public void DefaultAnimationCurves() {
|
||||
if (locomotion.stepHeight == null) locomotion.stepHeight = new AnimationCurve();
|
||||
if (locomotion.heelHeight == null) locomotion.heelHeight = new AnimationCurve ();
|
||||
|
||||
if (locomotion.stepHeight.keys.Length == 0) {
|
||||
locomotion.stepHeight.keys = GetSineKeyframes(0.03f);
|
||||
}
|
||||
|
||||
if (locomotion.heelHeight.keys.Length == 0) {
|
||||
locomotion.heelHeight.keys = GetSineKeyframes(0.03f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds position offset to a body part. Position offsets add to the targets in VRIK.
|
||||
/// </summary>
|
||||
public void AddPositionOffset(PositionOffset positionOffset, Vector3 value) {
|
||||
switch(positionOffset) {
|
||||
case PositionOffset.Pelvis: spine.pelvisPositionOffset += value; return;
|
||||
case PositionOffset.Chest: spine.chestPositionOffset += value; return;
|
||||
case PositionOffset.Head: spine.headPositionOffset += value; return;
|
||||
case PositionOffset.LeftHand: leftArm.handPositionOffset += value; return;
|
||||
case PositionOffset.RightHand: rightArm.handPositionOffset += value; return;
|
||||
case PositionOffset.LeftFoot: leftLeg.footPositionOffset += value; return;
|
||||
case PositionOffset.RightFoot: rightLeg.footPositionOffset += value; return;
|
||||
case PositionOffset.LeftHeel: leftLeg.heelPositionOffset += value; return;
|
||||
case PositionOffset.RightHeel: rightLeg.heelPositionOffset += value; return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds rotation offset to a body part. Rotation offsets add to the targets in VRIK
|
||||
/// </summary>
|
||||
public void AddRotationOffset(RotationOffset rotationOffset, Vector3 value) {
|
||||
AddRotationOffset(rotationOffset, Quaternion.Euler(value));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds rotation offset to a body part. Rotation offsets add to the targets in VRIK
|
||||
/// </summary>
|
||||
public void AddRotationOffset(RotationOffset rotationOffset, Quaternion value) {
|
||||
switch(rotationOffset) {
|
||||
case RotationOffset.Pelvis: spine.pelvisRotationOffset = value * spine.pelvisRotationOffset; return;
|
||||
case RotationOffset.Chest: spine.chestRotationOffset = value * spine.chestRotationOffset; return;
|
||||
case RotationOffset.Head: spine.headRotationOffset = value * spine.headRotationOffset; return;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Call this in each Update if your avatar is standing on a moving platform
|
||||
/// </summary>
|
||||
public void AddPlatformMotion(Vector3 deltaPosition, Quaternion deltaRotation, Vector3 platformPivot) {
|
||||
locomotion.AddDeltaPosition (deltaPosition);
|
||||
raycastOriginPelvis += deltaPosition;
|
||||
|
||||
locomotion.AddDeltaRotation (deltaRotation, platformPivot);
|
||||
spine.faceDirection = deltaRotation * spine.faceDirection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets all tweens, blendings and lerps. Call this after you have teleported the character.
|
||||
/// </summary>
|
||||
public void Reset() {
|
||||
if (!initiated) return;
|
||||
|
||||
UpdateSolverTransforms();
|
||||
Read(readPositions, readRotations, hasChest, hasNeck, hasShoulders, hasToes, hasLegs, hasArms);
|
||||
|
||||
spine.faceDirection = rootBone.readRotation * Vector3.forward;
|
||||
|
||||
if (hasLegs)
|
||||
{
|
||||
locomotion.Reset(readPositions, readRotations);
|
||||
raycastOriginPelvis = spine.pelvis.readPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public override void StoreDefaultLocalState() {
|
||||
for (int i = 1; i < solverTransforms.Length; i++) {
|
||||
if (solverTransforms[i] != null) {
|
||||
defaultLocalPositions[i - 1] = solverTransforms[i].localPosition;
|
||||
defaultLocalRotations[i - 1] = solverTransforms[i].localRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void FixTransforms() {
|
||||
if (!initiated) return;
|
||||
if (LOD >= 2) return;
|
||||
|
||||
for (int i = 1; i < solverTransforms.Length; i++) {
|
||||
if (solverTransforms[i] != null) {
|
||||
bool isPelvis = i == 1;
|
||||
|
||||
bool isArmStretchable = i == 8 || i == 9 || i == 12 || i == 13;
|
||||
bool isLegStretchable = (i >= 15 && i <= 17) || (i >= 19 && i <= 21);
|
||||
|
||||
if (isPelvis || isArmStretchable || isLegStretchable) {
|
||||
solverTransforms[i].localPosition = defaultLocalPositions[i - 1];
|
||||
}
|
||||
solverTransforms[i].localRotation = defaultLocalRotations[i - 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override IKSolver.Point[] GetPoints() {
|
||||
Debug.LogError("GetPoints() is not applicable to IKSolverVR.");
|
||||
return null;
|
||||
}
|
||||
|
||||
public override IKSolver.Point GetPoint(Transform transform) {
|
||||
Debug.LogError("GetPoint is not applicable to IKSolverVR.");
|
||||
return null;
|
||||
}
|
||||
|
||||
public override bool IsValid(ref string message) {
|
||||
if (solverTransforms == null || solverTransforms.Length == 0) {
|
||||
message = "Trying to initiate IKSolverVR with invalid bone references.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (leftArm.wristToPalmAxis == Vector3.zero) {
|
||||
message = "Left arm 'Wrist To Palm Axis' needs to be set in VRIK. Please select the hand bone, set it to the axis that points from the wrist towards the palm. If the arrow points away from the palm, axis must be negative.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rightArm.wristToPalmAxis == Vector3.zero) {
|
||||
message = "Right arm 'Wrist To Palm Axis' needs to be set in VRIK. Please select the hand bone, set it to the axis that points from the wrist towards the palm. If the arrow points away from the palm, axis must be negative.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (leftArm.palmToThumbAxis == Vector3.zero) {
|
||||
message = "Left arm 'Palm To Thumb Axis' needs to be set in VRIK. Please select the hand bone, set it to the axis that points from the palm towards the thumb. If the arrow points away from the thumb, axis must be negative.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (rightArm.palmToThumbAxis == Vector3.zero) {
|
||||
message = "Right arm 'Palm To Thumb Axis' needs to be set in VRIK. Please select the hand bone, set it to the axis that points from the palm towards the thumb. If the arrow points away from the thumb, axis must be negative.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private Transform[] solverTransforms = new Transform[0];
|
||||
private bool hasChest, hasNeck, hasShoulders, hasToes, hasLegs, hasArms;
|
||||
private Vector3[] readPositions = new Vector3[0];
|
||||
private Quaternion[] readRotations = new Quaternion[0];
|
||||
private Vector3[] solvedPositions = new Vector3[22];
|
||||
private Quaternion[] solvedRotations = new Quaternion[22];
|
||||
//private Vector3 defaultPelvisLocalPosition;
|
||||
private Quaternion[] defaultLocalRotations = new Quaternion[21];
|
||||
private Vector3[] defaultLocalPositions = new Vector3[21];
|
||||
|
||||
private Vector3 GetNormal(Transform[] transforms) {
|
||||
Vector3 normal = Vector3.zero;
|
||||
|
||||
Vector3 centroid = Vector3.zero;
|
||||
for (int i = 0; i < transforms.Length; i++) {
|
||||
centroid += transforms[i].position;
|
||||
}
|
||||
centroid /= transforms.Length;
|
||||
|
||||
for (int i = 0; i < transforms.Length - 1; i++) {
|
||||
normal += Vector3.Cross(transforms[i].position - centroid, transforms[i + 1].position - centroid).normalized;
|
||||
}
|
||||
|
||||
return normal;
|
||||
}
|
||||
|
||||
private static Keyframe[] GetSineKeyframes(float mag) {
|
||||
Keyframe[] keys = new Keyframe[3];
|
||||
keys[0].time = 0f;
|
||||
keys[0].value = 0f;
|
||||
keys[1].time = 0.5f;
|
||||
keys[1].value = mag;
|
||||
keys[2].time = 1f;
|
||||
keys[2].value = 0f;
|
||||
return keys;
|
||||
}
|
||||
|
||||
private void UpdateSolverTransforms() {
|
||||
for (int i = 0; i < solverTransforms.Length; i++) {
|
||||
if (solverTransforms[i] != null) {
|
||||
readPositions[i] = solverTransforms[i].position;
|
||||
readRotations[i] = solverTransforms[i].rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnInitiate() {
|
||||
UpdateSolverTransforms();
|
||||
Read(readPositions, readRotations, hasChest, hasNeck, hasShoulders, hasToes, hasLegs, hasArms);
|
||||
}
|
||||
|
||||
protected override void OnUpdate() {
|
||||
if (IKPositionWeight > 0f) {
|
||||
if (LOD < 2)
|
||||
{
|
||||
bool read = false;
|
||||
|
||||
if (lastLOD != LOD)
|
||||
{
|
||||
if (lastLOD == 2)
|
||||
{
|
||||
spine.faceDirection = rootBone.readRotation * Vector3.forward;
|
||||
|
||||
if (hasLegs)
|
||||
{
|
||||
// Teleport to the current position/rotation if resuming from culled LOD with locomotion enabled
|
||||
if (locomotion.weight > 0f)
|
||||
{
|
||||
root.position = new Vector3(spine.headTarget.position.x, root.position.y, spine.headTarget.position.z);
|
||||
Vector3 forward = spine.faceDirection;
|
||||
forward.y = 0f;
|
||||
root.rotation = Quaternion.LookRotation(forward, root.up);
|
||||
|
||||
UpdateSolverTransforms();
|
||||
Read(readPositions, readRotations, hasChest, hasNeck, hasShoulders, hasToes, hasLegs, hasArms);
|
||||
read = true;
|
||||
|
||||
locomotion.Reset(readPositions, readRotations);
|
||||
}
|
||||
|
||||
raycastOriginPelvis = spine.pelvis.readPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!read)
|
||||
{
|
||||
UpdateSolverTransforms();
|
||||
Read(readPositions, readRotations, hasChest, hasNeck, hasShoulders, hasToes, hasLegs, hasArms);
|
||||
}
|
||||
|
||||
Solve();
|
||||
Write();
|
||||
|
||||
WriteTransforms();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Culled
|
||||
if (locomotion.weight > 0f)
|
||||
{
|
||||
root.position = new Vector3(spine.headTarget.position.x, root.position.y, spine.headTarget.position.z);
|
||||
Vector3 forward = spine.headTarget.rotation * spine.anchorRelativeToHead * Vector3.forward;
|
||||
forward.y = 0f;
|
||||
root.rotation = Quaternion.LookRotation(forward, root.up);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastLOD = LOD;
|
||||
}
|
||||
|
||||
private void WriteTransforms() {
|
||||
for (int i = 0; i < solverTransforms.Length; i++) {
|
||||
if (solverTransforms[i] != null) {
|
||||
bool isRootOrPelvis = i < 2;
|
||||
bool isArmStretchable = i == 8 || i == 9 || i == 12 || i == 13;
|
||||
bool isLegStretchable = (i >= 15 && i <= 17) || (i >= 19 && i <= 21);
|
||||
|
||||
if (LOD > 0)
|
||||
{
|
||||
isArmStretchable = false;
|
||||
isLegStretchable = false;
|
||||
}
|
||||
|
||||
if (isRootOrPelvis) {
|
||||
solverTransforms[i].position = V3Tools.Lerp(solverTransforms[i].position, GetPosition(i), IKPositionWeight);
|
||||
}
|
||||
|
||||
if (isArmStretchable || isLegStretchable) {
|
||||
if (IKPositionWeight < 1f) {
|
||||
Vector3 localPosition = solverTransforms[i].localPosition;
|
||||
solverTransforms[i].position = V3Tools.Lerp(solverTransforms[i].position, GetPosition(i), IKPositionWeight);
|
||||
solverTransforms[i].localPosition = Vector3.Project(solverTransforms[i].localPosition, localPosition);
|
||||
} else
|
||||
{
|
||||
solverTransforms[i].position = V3Tools.Lerp(solverTransforms[i].position, GetPosition(i), IKPositionWeight);
|
||||
}
|
||||
}
|
||||
|
||||
solverTransforms[i].rotation = QuaTools.Lerp(solverTransforms[i].rotation, GetRotation(i), IKPositionWeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Wrapper
|
||||
|
||||
#region Generic API
|
||||
|
||||
private Vector3 rootV;
|
||||
private Vector3 rootVelocity;
|
||||
private Vector3 bodyOffset;
|
||||
private int supportLegIndex;
|
||||
private int lastLOD;
|
||||
|
||||
private void Read(Vector3[] positions, Quaternion[] rotations, bool hasChest, bool hasNeck, bool hasShoulders, bool hasToes, bool hasLegs, bool hasArms) {
|
||||
if (rootBone == null) {
|
||||
rootBone = new VirtualBone (positions [0], rotations [0]);
|
||||
} else {
|
||||
rootBone.Read (positions [0], rotations [0]);
|
||||
}
|
||||
|
||||
spine.Read(positions, rotations, hasChest, hasNeck, hasShoulders, hasToes, hasLegs, 0, 1);
|
||||
|
||||
if (hasArms)
|
||||
{
|
||||
leftArm.Read(positions, rotations, hasChest, hasNeck, hasShoulders, hasToes, hasLegs, hasChest ? 3 : 2, 6);
|
||||
rightArm.Read(positions, rotations, hasChest, hasNeck, hasShoulders, hasToes, hasLegs, hasChest ? 3 : 2, 10);
|
||||
}
|
||||
|
||||
if (hasLegs) {
|
||||
leftLeg.Read(positions, rotations, hasChest, hasNeck, hasShoulders, hasToes, hasLegs, 1, 14);
|
||||
rightLeg.Read(positions, rotations, hasChest, hasNeck, hasShoulders, hasToes, hasLegs, 1, 18);
|
||||
}
|
||||
|
||||
for (int i = 0; i < rotations.Length; i++) {
|
||||
this.solvedPositions[i] = positions[i];
|
||||
this.solvedRotations[i] = rotations[i];
|
||||
}
|
||||
|
||||
if (!initiated) {
|
||||
if (hasLegs) legs = new Leg[2] { leftLeg, rightLeg };
|
||||
if (hasArms) arms = new Arm[2] { leftArm, rightArm };
|
||||
|
||||
if (hasLegs) locomotion.Initiate(animator, positions, rotations, hasToes, scale);
|
||||
raycastOriginPelvis = spine.pelvis.readPosition;
|
||||
spine.faceDirection = readRotations[0] * Vector3.forward;
|
||||
}
|
||||
}
|
||||
|
||||
private void Solve() {
|
||||
if (scale <= 0f)
|
||||
{
|
||||
Debug.LogError("VRIK solver scale <= 0, can not solve!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasLegs && lastLocomotionWeight <= 0f && locomotion.weight > 0f) locomotion.Reset(readPositions, readRotations);
|
||||
|
||||
spine.SetLOD(LOD);
|
||||
if (hasArms) foreach (Arm arm in arms) arm.SetLOD(LOD);
|
||||
if (hasLegs) foreach (Leg leg in legs) leg.SetLOD(LOD);
|
||||
|
||||
// Pre-Solving
|
||||
spine.PreSolve(scale);
|
||||
if (hasArms) foreach (Arm arm in arms) arm.PreSolve(scale);
|
||||
if (hasLegs) foreach (Leg leg in legs) leg.PreSolve(scale);
|
||||
|
||||
// Applying spine and arm offsets
|
||||
if (hasArms) foreach (Arm arm in arms) arm.ApplyOffsets(scale);
|
||||
spine.ApplyOffsets(scale);
|
||||
|
||||
// Spine
|
||||
spine.Solve(animator, rootBone, legs, arms, scale);
|
||||
|
||||
if (hasLegs && spine.pelvisPositionWeight > 0f && plantFeet) {
|
||||
Warning.Log("If VRIK 'Pelvis Position Weight' is > 0, 'Plant Feet' should be disabled to improve performance and stability.", root);
|
||||
}
|
||||
|
||||
float deltaTime = Time.deltaTime;
|
||||
|
||||
// Locomotion
|
||||
if (hasLegs) {
|
||||
if (locomotion.weight > 0f)
|
||||
{
|
||||
switch (locomotion.mode)
|
||||
{
|
||||
case Locomotion.Mode.Procedural:
|
||||
Vector3 leftFootPosition = Vector3.zero;
|
||||
Vector3 rightFootPosition = Vector3.zero;
|
||||
Quaternion leftFootRotation = Quaternion.identity;
|
||||
Quaternion rightFootRotation = Quaternion.identity;
|
||||
float leftFootOffset = 0f;
|
||||
float rightFootOffset = 0f;
|
||||
float leftHeelOffset = 0f;
|
||||
float rightHeelOffset = 0f;
|
||||
|
||||
locomotion.Solve_Procedural(rootBone, spine, leftLeg, rightLeg, leftArm, rightArm, supportLegIndex, out leftFootPosition, out rightFootPosition, out leftFootRotation, out rightFootRotation, out leftFootOffset, out rightFootOffset, out leftHeelOffset, out rightHeelOffset, scale, deltaTime);
|
||||
|
||||
leftFootPosition += root.up * leftFootOffset;
|
||||
rightFootPosition += root.up * rightFootOffset;
|
||||
|
||||
leftLeg.footPositionOffset += (leftFootPosition - leftLeg.lastBone.solverPosition) * IKPositionWeight * (1f - leftLeg.positionWeight) * locomotion.weight;
|
||||
rightLeg.footPositionOffset += (rightFootPosition - rightLeg.lastBone.solverPosition) * IKPositionWeight * (1f - rightLeg.positionWeight) * locomotion.weight;
|
||||
|
||||
leftLeg.heelPositionOffset += root.up * leftHeelOffset * locomotion.weight;
|
||||
rightLeg.heelPositionOffset += root.up * rightHeelOffset * locomotion.weight;
|
||||
|
||||
Quaternion rotationOffsetLeft = QuaTools.FromToRotation(leftLeg.lastBone.solverRotation, leftFootRotation);
|
||||
Quaternion rotationOffsetRight = QuaTools.FromToRotation(rightLeg.lastBone.solverRotation, rightFootRotation);
|
||||
|
||||
rotationOffsetLeft = Quaternion.Lerp(Quaternion.identity, rotationOffsetLeft, IKPositionWeight * (1f - leftLeg.rotationWeight) * locomotion.weight);
|
||||
rotationOffsetRight = Quaternion.Lerp(Quaternion.identity, rotationOffsetRight, IKPositionWeight * (1f - rightLeg.rotationWeight) * locomotion.weight);
|
||||
|
||||
leftLeg.footRotationOffset = rotationOffsetLeft * leftLeg.footRotationOffset;
|
||||
rightLeg.footRotationOffset = rotationOffsetRight * rightLeg.footRotationOffset;
|
||||
|
||||
Vector3 footPositionC = Vector3.Lerp(leftLeg.position + leftLeg.footPositionOffset, rightLeg.position + rightLeg.footPositionOffset, 0.5f);
|
||||
footPositionC = V3Tools.PointToPlane(footPositionC, rootBone.solverPosition, root.up);
|
||||
|
||||
Vector3 p = rootBone.solverPosition + rootVelocity * deltaTime * 2f * locomotion.weight;
|
||||
p = Vector3.Lerp(p, footPositionC, deltaTime * locomotion.rootSpeed * locomotion.weight);
|
||||
rootBone.solverPosition = p;
|
||||
|
||||
rootVelocity += (footPositionC - rootBone.solverPosition) * deltaTime * 10f;
|
||||
Vector3 rootVelocityV = V3Tools.ExtractVertical(rootVelocity, root.up, 1f);
|
||||
rootVelocity -= rootVelocityV;
|
||||
|
||||
float bodyYOffset = Mathf.Min(leftFootOffset + rightFootOffset, locomotion.maxBodyYOffset * scale);
|
||||
bodyOffset = Vector3.Lerp(bodyOffset, root.up * bodyYOffset, deltaTime * 3f);
|
||||
bodyOffset = Vector3.Lerp(Vector3.zero, bodyOffset, locomotion.weight);
|
||||
|
||||
break;
|
||||
case Locomotion.Mode.Animated:
|
||||
if (lastLocomotionWeight <= 0f) locomotion.Reset_Animated(readPositions);
|
||||
locomotion.Solve_Animated(this, scale, deltaTime);
|
||||
break;
|
||||
}
|
||||
} else
|
||||
{
|
||||
if (lastLocomotionWeight > 0f) locomotion.Reset_Animated(readPositions);
|
||||
}
|
||||
}
|
||||
|
||||
lastLocomotionWeight = locomotion.weight;
|
||||
|
||||
// Legs
|
||||
if (hasLegs)
|
||||
{
|
||||
foreach (Leg leg in legs)
|
||||
{
|
||||
leg.ApplyOffsets(scale);
|
||||
}
|
||||
if (!plantFeet || LOD > 0)
|
||||
{
|
||||
spine.InverseTranslateToHead(legs, false, false, bodyOffset, 1f);
|
||||
|
||||
foreach (Leg leg in legs) leg.TranslateRoot(spine.pelvis.solverPosition, spine.pelvis.solverRotation);
|
||||
foreach (Leg leg in legs)
|
||||
{
|
||||
leg.Solve(true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
spine.InverseTranslateToHead(legs, true, true, bodyOffset, 1f);
|
||||
|
||||
foreach (Leg leg in legs) leg.TranslateRoot(spine.pelvis.solverPosition, spine.pelvis.solverRotation);
|
||||
foreach (Leg leg in legs)
|
||||
{
|
||||
leg.Solve(i == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else
|
||||
{
|
||||
spine.InverseTranslateToHead(legs, false, false, bodyOffset, 1f);
|
||||
}
|
||||
|
||||
// Arms
|
||||
if (hasArms)
|
||||
{
|
||||
for (int i = 0; i < arms.Length; i++)
|
||||
{
|
||||
arms[i].TranslateRoot(spine.chest.solverPosition, spine.chest.solverRotation);
|
||||
}
|
||||
|
||||
for (int i = 0; i < arms.Length; i++)
|
||||
{
|
||||
arms[i].Solve(i == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset offsets
|
||||
spine.ResetOffsets();
|
||||
if (hasLegs) foreach (Leg leg in legs) leg.ResetOffsets();
|
||||
if (hasArms) foreach (Arm arm in arms) arm.ResetOffsets();
|
||||
|
||||
if (hasLegs)
|
||||
{
|
||||
spine.pelvisPositionOffset += GetPelvisOffset(deltaTime);
|
||||
spine.chestPositionOffset += spine.pelvisPositionOffset;
|
||||
//spine.headPositionOffset += spine.pelvisPositionOffset;
|
||||
}
|
||||
|
||||
Write();
|
||||
|
||||
// Find the support leg
|
||||
if (hasLegs)
|
||||
{
|
||||
supportLegIndex = -1;
|
||||
float shortestMag = Mathf.Infinity;
|
||||
for (int i = 0; i < legs.Length; i++)
|
||||
{
|
||||
float mag = Vector3.SqrMagnitude(legs[i].lastBone.solverPosition - legs[i].bones[0].solverPosition);
|
||||
if (mag < shortestMag)
|
||||
{
|
||||
supportLegIndex = i;
|
||||
shortestMag = mag;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float lastLocomotionWeight;
|
||||
|
||||
private Vector3 GetPosition(int index) {
|
||||
return solvedPositions[index];
|
||||
}
|
||||
|
||||
private Quaternion GetRotation(int index) {
|
||||
return solvedRotations[index];
|
||||
}
|
||||
|
||||
#endregion Generic API
|
||||
|
||||
[Tooltip("LOD 0: Full quality solving. LOD 1: Shoulder solving, stretching plant feet disabled, spine solving quality reduced. This provides about 30% of performance gain. LOD 2: Culled, but updating root position and rotation if locomotion is enabled.")]
|
||||
/// <summary>
|
||||
/// LOD 0: Full quality solving. LOD 1: Shoulder solving, stretching plant feet disabled, spine solving quality reduced. This provides about 30% of performance gain. LOD 2: Culled, but updating root position and rotation if locomotion is enabled.
|
||||
/// </summary>
|
||||
[Range(0, 2)] public int LOD = 0;
|
||||
|
||||
[Tooltip("Scale of the character. Value of 1 means normal adult human size.")]
|
||||
/// <summary>
|
||||
/// Scale of the character. Value of 1 means normal adult human size.
|
||||
/// </summary>
|
||||
public float scale = 1f;
|
||||
|
||||
[Tooltip("If true, will keep the toes planted even if head target is out of reach, so this can cause the camera to exit the head if it is too high for the model to reach. Enabling this increases the cost of the solver as the legs will have to be solved multiple times.")]
|
||||
/// <summary>
|
||||
/// If true, will keep the toes planted even if head target is out of reach, so this can cause the camera to exit the head if it is too high for the model to reach. Enabling this increases the cost of the solver as the legs will have to be solved multiple times.
|
||||
/// </summary>
|
||||
public bool plantFeet = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the root bone.
|
||||
/// </summary>
|
||||
[HideInInspector] public VirtualBone rootBone { get; private set; }
|
||||
|
||||
[Tooltip("The spine solver.")]
|
||||
/// <summary>
|
||||
/// The spine solver.
|
||||
/// </summary>
|
||||
public Spine spine = new Spine();
|
||||
|
||||
[Tooltip("The left arm solver.")]
|
||||
/// <summary>
|
||||
/// The left arm solver.
|
||||
/// </summary>
|
||||
public Arm leftArm = new Arm();
|
||||
|
||||
[Tooltip("The right arm solver.")]
|
||||
/// <summary>
|
||||
/// The right arm solver.
|
||||
/// </summary>
|
||||
public Arm rightArm = new Arm();
|
||||
|
||||
[Tooltip("The left leg solver.")]
|
||||
/// <summary>
|
||||
/// The left leg solver.
|
||||
/// </summary>
|
||||
public Leg leftLeg = new Leg();
|
||||
|
||||
[Tooltip("The right leg solver.")]
|
||||
/// <summary>
|
||||
/// The right leg solver.
|
||||
/// </summary>
|
||||
public Leg rightLeg = new Leg();
|
||||
|
||||
[Tooltip("Procedural leg shuffling for stationary VR games. Not designed for roomscale and thumbstick locomotion. For those it would be better to use a strafing locomotion blend tree to make the character follow the horizontal direction towards the HMD by root motion or script.")]
|
||||
/// <summary>
|
||||
/// Procedural leg shuffling for stationary VR games. Not designed for roomscale and thumbstick locomotion. For those it would be better to use a strafing locomotion blend tree to make the character follow the horizontal direction towards the HMD by root motion or script.
|
||||
/// </summary>
|
||||
public Locomotion locomotion = new Locomotion();
|
||||
|
||||
private Leg[] legs = new Leg[2];
|
||||
private Arm[] arms = new Arm[2];
|
||||
private Vector3 headPosition;
|
||||
private Vector3 headDeltaPosition;
|
||||
private Vector3 raycastOriginPelvis;
|
||||
private Vector3 lastOffset;
|
||||
private Vector3 debugPos1;
|
||||
private Vector3 debugPos2;
|
||||
private Vector3 debugPos3;
|
||||
private Vector3 debugPos4;
|
||||
|
||||
private void Write() {
|
||||
solvedPositions[0] = rootBone.solverPosition;
|
||||
solvedRotations[0] = rootBone.solverRotation;
|
||||
spine.Write(ref solvedPositions, ref solvedRotations);
|
||||
|
||||
if (hasLegs)
|
||||
{
|
||||
foreach (Leg leg in legs) leg.Write(ref solvedPositions, ref solvedRotations);
|
||||
}
|
||||
if (hasArms)
|
||||
{
|
||||
foreach (Arm arm in arms) arm.Write(ref solvedPositions, ref solvedRotations);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 GetPelvisOffset(float deltaTime) {
|
||||
if (locomotion.weight <= 0f) return Vector3.zero;
|
||||
if (locomotion.blockingLayers == -1) return Vector3.zero;
|
||||
|
||||
// Origin to pelvis transform position
|
||||
Vector3 sampledOrigin = raycastOriginPelvis;
|
||||
sampledOrigin.y = spine.pelvis.solverPosition.y;
|
||||
Vector3 origin = spine.pelvis.readPosition;
|
||||
origin.y = spine.pelvis.solverPosition.y;
|
||||
Vector3 direction = origin - sampledOrigin;
|
||||
RaycastHit hit;
|
||||
|
||||
//debugPos4 = sampledOrigin;
|
||||
|
||||
if (locomotion.raycastRadius <= 0f) {
|
||||
if (Physics.Raycast(sampledOrigin, direction, out hit, direction.magnitude * 1.1f, locomotion.blockingLayers)) {
|
||||
origin = hit.point;
|
||||
}
|
||||
} else {
|
||||
if (Physics.SphereCast(sampledOrigin, locomotion.raycastRadius * 1.1f, direction, out hit, direction.magnitude, locomotion.blockingLayers)) {
|
||||
origin = sampledOrigin + direction.normalized * hit.distance / 1.1f;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 position = spine.pelvis.solverPosition;
|
||||
direction = position - origin;
|
||||
|
||||
//debugPos1 = origin;
|
||||
//debugPos2 = position;
|
||||
|
||||
if (locomotion.raycastRadius <= 0f) {
|
||||
if (Physics.Raycast(origin, direction, out hit, direction.magnitude, locomotion.blockingLayers)) {
|
||||
position = hit.point;
|
||||
}
|
||||
|
||||
} else {
|
||||
if (Physics.SphereCast(origin, locomotion.raycastRadius, direction, out hit, direction.magnitude, locomotion.blockingLayers)) {
|
||||
position = origin + direction.normalized * hit.distance;
|
||||
}
|
||||
}
|
||||
|
||||
lastOffset = Vector3.Lerp(lastOffset, Vector3.zero, deltaTime * 3f);
|
||||
position += Vector3.ClampMagnitude(lastOffset, 0.75f);
|
||||
position.y = spine.pelvis.solverPosition.y;
|
||||
|
||||
//debugPos3 = position;
|
||||
|
||||
lastOffset = Vector3.Lerp(lastOffset, position - spine.pelvis.solverPosition, deltaTime * 15f);
|
||||
return lastOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb2326f82d9db4d968a05d743aeea69d
|
||||
timeCreated: 1456729495
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,548 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
using RootMotion;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Hybrid %IK solver designed for mapping a character to a VR headset and 2 hand controllers
|
||||
/// </summary>
|
||||
public partial class IKSolverVR : IKSolver
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 4-segmented analytic arm chain.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Arm : BodyPart
|
||||
{
|
||||
|
||||
[System.Serializable]
|
||||
public enum ShoulderRotationMode
|
||||
{
|
||||
YawPitch,
|
||||
FromTo
|
||||
}
|
||||
|
||||
[LargeHeader("Hand")]
|
||||
|
||||
[Tooltip("The hand target. This should not be the hand controller itself, but a child GameObject parented to it so you could adjust its position/rotation to match the orientation of the hand bone. The best practice for setup would be to move the hand controller to the avatar's hand as it it was held by the avatar, duplicate the avatar's hand bone and parent it to the hand controller. Then assign the duplicate to this slot.")]
|
||||
/// <summary>
|
||||
/// The hand target. This should not be the hand controller itself, but a child GameObject parented to it so you could adjust its position/rotation to match the orientation of the hand bone. The best practice for setup would be to move the hand controller to the avatar's hand as it it was held by the avatar, duplicate the avatar's hand bone and parent it to the hand controller. Then assign the duplicate to this slot.
|
||||
/// </summary>
|
||||
public Transform target;
|
||||
|
||||
[Tooltip("Positional weight of the hand target. Note that if you have nulled the target, the hand will still be pulled to the last position of the target until you set this value to 0.")]
|
||||
/// <summary>
|
||||
/// Positional weight of the hand target. Note that if you have nulled the target, the hand will still be pulled to the last position of the target until you set this value to 0.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float positionWeight = 1f;
|
||||
|
||||
[Tooltip("Rotational weight of the hand target. Note that if you have nulled the target, the hand will still be rotated to the last rotation of the target until you set this value to 0.")]
|
||||
/// <summary>
|
||||
/// Rotational weight of the hand target. Note that if you have nulled the target, the hand will still be rotated to the last rotation of the target until you set this value to 0.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float rotationWeight = 1f;
|
||||
|
||||
[LargeHeader("Shoulder")]
|
||||
|
||||
[Tooltip("The weight of shoulder rotation")]
|
||||
/// <summary>
|
||||
/// The weight of shoulder rotation.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float shoulderRotationWeight = 1f;
|
||||
|
||||
[Tooltip("Different techniques for shoulder bone rotation.")]
|
||||
[ShowIf("shoulderRotationWeight", 0f, Mathf.Infinity)]
|
||||
/// <summary>
|
||||
/// Different techniques for shoulder bone rotation.
|
||||
/// </summary>
|
||||
public ShoulderRotationMode shoulderRotationMode = ShoulderRotationMode.YawPitch;
|
||||
|
||||
[Tooltip("The weight of twisting the shoulders backwards when arms are lifted up.")]
|
||||
[ShowRangeIf(0f, 1f, "shoulderRotationWeight", 0f, Mathf.Infinity)]
|
||||
/// <summary>
|
||||
/// The weight of twisting the shoulders backwards when arms are lifted up.
|
||||
/// </summary>
|
||||
public float shoulderTwistWeight = 1f;
|
||||
|
||||
[Tooltip("Tweak this value to adjust shoulder rotation around the yaw (up) axis.")]
|
||||
[ShowIf("shoulderRotationWeight", 0f, Mathf.Infinity)]
|
||||
/// <summary>
|
||||
/// Tweak this value to adjust shoulder rotation around the yaw (up) axis.
|
||||
/// </summary>
|
||||
public float shoulderYawOffset = 45f;
|
||||
|
||||
[Tooltip("Tweak this value to adjust shoulder rotation around the pitch (forward) axis.")]
|
||||
[ShowIf("shoulderRotationWeight", 0f, Mathf.Infinity)]
|
||||
/// <summary>
|
||||
/// Tweak this value to adjust shoulder rotation around the pitch (forward) axis.
|
||||
/// </summary>
|
||||
public float shoulderPitchOffset = -30f;
|
||||
|
||||
[LargeHeader("Bending")]
|
||||
[Tooltip("The elbow will be bent towards this Transform if 'Bend Goal Weight' > 0.")]
|
||||
/// <summary>
|
||||
/// The elbow will be bent towards this Transform if 'Bend Goal Weight' > 0.
|
||||
/// </summary>
|
||||
public Transform bendGoal;
|
||||
|
||||
[Tooltip("If greater than 0, will bend the elbow towards the 'Bend Goal' Transform.")]
|
||||
/// <summary>
|
||||
/// If greater than 0, will bend the elbow towards the 'Bend Goal' Transform.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float bendGoalWeight;
|
||||
|
||||
[Tooltip("Angular offset of the elbow bending direction.")]
|
||||
/// <summary>
|
||||
/// Angular offset of the elbow bending direction.
|
||||
/// </summary>
|
||||
[Range(-180f, 180f)] public float swivelOffset;
|
||||
|
||||
[Tooltip("Local axis of the hand bone that points from the wrist towards the palm. Used for defining hand bone orientation. If you have copied VRIK component from another avatar that has different bone orientations, right-click on VRIK header and select 'Guess Hand Orientations' from the context menu.")]
|
||||
/// <summary>
|
||||
/// Local axis of the hand bone that points from the wrist towards the palm. Used for defining hand bone orientation. If you have copied VRIK component from another avatar that has different bone orientations, right-click on VRIK header and select 'Guess Hand Orientations' from the context menu.
|
||||
/// </summary>
|
||||
public Vector3 wristToPalmAxis = Vector3.zero;
|
||||
|
||||
[Tooltip("Local axis of the hand bone that points from the palm towards the thumb. Used for defining hand bone orientation. If you have copied VRIK component from another avatar that has different bone orientations, right-click on VRIK header and select 'Guess Hand Orientations' from the context menu.")]
|
||||
/// <summary>
|
||||
/// Local axis of the hand bone that points from the palm towards the thumb. Used for defining hand bone orientation If you have copied VRIK component from another avatar that has different bone orientations, right-click on VRIK header and select 'Guess Hand Orientations' from the context menu..
|
||||
/// </summary>
|
||||
public Vector3 palmToThumbAxis = Vector3.zero;
|
||||
|
||||
[LargeHeader("Stretching")]
|
||||
|
||||
[Tooltip("Use this to make the arm shorter/longer. Works by displacement of hand and forearm localPosition.")]
|
||||
/// <summary>
|
||||
/// Use this to make the arm shorter/longer. Works by displacement of hand and forearm localPosition.
|
||||
/// </summary>
|
||||
[Range(0.01f, 2f)]
|
||||
public float armLengthMlp = 1f;
|
||||
|
||||
[Tooltip("'Time' represents (target distance / arm length) and 'value' represents the amount of stretching. So value at time 1 represents stretching amount at the point where distance to the target is equal to arm length. Value at time 2 represents stretching amount at the point where distance to the target is double the arm length. Linear stretching would be achieved with a linear curve going up by 45 degrees. Increase the range of stretching by moving the last key up and right by the same amount. Smoothing in the curve can help reduce elbow snapping (start stretching the arm slightly before target distance reaches arm length). To get a good optimal value for this curve, please go to the 'VRIK (Basic)' demo scene and copy the stretch curve over from the Pilot character.")]
|
||||
/// <summary>
|
||||
/// 'Time' represents (target distance / arm length) and 'value' represents the amount of stretching. So value at time 1 represents stretching amount at the point where distance to the target is equal to arm length. Value at time 2 represents stretching amount at the point where distance to the target is double the arm length. Linear stretching would be achieved with a linear curve going up by 45 degrees. Increase the range of stretching by moving the last key up and right by the same amount. Smoothing in the curve can help reduce elbow snapping (start stretching the arm slightly before target distance reaches arm length). To get a good optimal value for this curve, please go to the 'VRIK (Basic)' demo scene and copy the stretch curve over from the Pilot character.
|
||||
/// </summary>
|
||||
public AnimationCurve stretchCurve = new AnimationCurve();
|
||||
|
||||
/// <summary>
|
||||
/// Target position of the hand. Will be overwritten if target is assigned.
|
||||
/// </summary>
|
||||
[NonSerialized] [HideInInspector] public Vector3 IKPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Target rotation of the hand. Will be overwritten if target is assigned.
|
||||
/// </summary>
|
||||
[NonSerialized] [HideInInspector] public Quaternion IKRotation = Quaternion.identity;
|
||||
|
||||
/// <summary>
|
||||
/// The bending direction of the limb. Will be used if bendGoalWeight is greater than 0. Will be overwritten if bendGoal is assigned.
|
||||
/// </summary>
|
||||
[NonSerialized] [HideInInspector] public Vector3 bendDirection = Vector3.back;
|
||||
|
||||
/// <summary>
|
||||
/// Position offset of the hand. Will be applied on top of hand target position and reset to Vector3.zero after each update.
|
||||
/// </summary>
|
||||
[NonSerialized] [HideInInspector] public Vector3 handPositionOffset;
|
||||
|
||||
// Gets the target position of the hand.
|
||||
public Vector3 position { get; private set; }
|
||||
|
||||
// Gets the target rotation of the hand
|
||||
public Quaternion rotation { get; private set; }
|
||||
|
||||
private bool hasShoulder;
|
||||
private VirtualBone shoulder { get { return bones[0]; } }
|
||||
private VirtualBone upperArm
|
||||
{
|
||||
get
|
||||
{
|
||||
return bones[hasShoulder ? 1 : 0];
|
||||
}
|
||||
}
|
||||
private VirtualBone forearm
|
||||
{
|
||||
get
|
||||
{
|
||||
return bones[hasShoulder ? 2 : 1];
|
||||
}
|
||||
}
|
||||
private VirtualBone hand
|
||||
{
|
||||
get
|
||||
{
|
||||
return bones[hasShoulder ? 3 : 2];
|
||||
}
|
||||
}
|
||||
private Vector3 chestForwardAxis;
|
||||
private Vector3 chestUpAxis;
|
||||
private Quaternion chestRotation = Quaternion.identity;
|
||||
private Vector3 chestForward;
|
||||
private Vector3 chestUp;
|
||||
private Quaternion forearmRelToUpperArm = Quaternion.identity;
|
||||
private Vector3 upperArmBendAxis;
|
||||
|
||||
protected override void OnRead(Vector3[] positions, Quaternion[] rotations, bool hasChest, bool hasNeck, bool hasShoulders, bool hasToes, bool hasLegs, int rootIndex, int index)
|
||||
{
|
||||
Vector3 shoulderPosition = positions[index];
|
||||
Quaternion shoulderRotation = rotations[index];
|
||||
Vector3 upperArmPosition = positions[index + 1];
|
||||
Quaternion upperArmRotation = rotations[index + 1];
|
||||
Vector3 forearmPosition = positions[index + 2];
|
||||
Quaternion forearmRotation = rotations[index + 2];
|
||||
Vector3 handPosition = positions[index + 3];
|
||||
Quaternion handRotation = rotations[index + 3];
|
||||
|
||||
if (!initiated)
|
||||
{
|
||||
IKPosition = handPosition;
|
||||
IKRotation = handRotation;
|
||||
rotation = IKRotation;
|
||||
|
||||
this.hasShoulder = hasShoulders;
|
||||
|
||||
bones = new VirtualBone[hasShoulder ? 4 : 3];
|
||||
|
||||
if (hasShoulder)
|
||||
{
|
||||
bones[0] = new VirtualBone(shoulderPosition, shoulderRotation);
|
||||
bones[1] = new VirtualBone(upperArmPosition, upperArmRotation);
|
||||
bones[2] = new VirtualBone(forearmPosition, forearmRotation);
|
||||
bones[3] = new VirtualBone(handPosition, handRotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.bones[0] = new VirtualBone(upperArmPosition, upperArmRotation);
|
||||
this.bones[1] = new VirtualBone(forearmPosition, forearmRotation);
|
||||
this.bones[2] = new VirtualBone(handPosition, handRotation);
|
||||
}
|
||||
|
||||
Vector3 rootForward = rotations[0] * Vector3.forward;
|
||||
chestForwardAxis = Quaternion.Inverse(rootRotation) * rootForward;
|
||||
chestUpAxis = Quaternion.Inverse(rootRotation) * (rotations[0] * Vector3.up);
|
||||
|
||||
// Get the local axis of the upper arm pointing towards the bend normal
|
||||
Vector3 upperArmForwardAxis = AxisTools.GetAxisVectorToDirection(upperArmRotation, rootForward);
|
||||
if (Vector3.Dot(upperArmRotation * upperArmForwardAxis, rootForward) < 0f) upperArmForwardAxis = -upperArmForwardAxis;
|
||||
upperArmBendAxis = Vector3.Cross(Quaternion.Inverse(upperArmRotation) * (forearmPosition - upperArmPosition), upperArmForwardAxis);
|
||||
if (upperArmBendAxis == Vector3.zero)
|
||||
{
|
||||
Debug.LogError("VRIK can not calculate which way to bend the arms because the arms are perfectly straight. Please rotate the elbow bones slightly in their natural bending direction in the Editor.");
|
||||
}
|
||||
}
|
||||
|
||||
if (hasShoulder)
|
||||
{
|
||||
bones[0].Read(shoulderPosition, shoulderRotation);
|
||||
bones[1].Read(upperArmPosition, upperArmRotation);
|
||||
bones[2].Read(forearmPosition, forearmRotation);
|
||||
bones[3].Read(handPosition, handRotation);
|
||||
}
|
||||
else
|
||||
{
|
||||
bones[0].Read(upperArmPosition, upperArmRotation);
|
||||
bones[1].Read(forearmPosition, forearmRotation);
|
||||
bones[2].Read(handPosition, handRotation);
|
||||
}
|
||||
}
|
||||
|
||||
public override void PreSolve(float scale)
|
||||
{
|
||||
if (target != null)
|
||||
{
|
||||
IKPosition = target.position;
|
||||
IKRotation = target.rotation;
|
||||
}
|
||||
|
||||
position = V3Tools.Lerp(hand.solverPosition, IKPosition, positionWeight);
|
||||
rotation = QuaTools.Lerp(hand.solverRotation, IKRotation, rotationWeight);
|
||||
|
||||
shoulder.axis = shoulder.axis.normalized;
|
||||
forearmRelToUpperArm = Quaternion.Inverse(upperArm.solverRotation) * forearm.solverRotation;
|
||||
}
|
||||
|
||||
public override void ApplyOffsets(float scale)
|
||||
{
|
||||
position += handPositionOffset;
|
||||
}
|
||||
|
||||
private void Stretching()
|
||||
{
|
||||
// Adjusting arm length
|
||||
float armLength = upperArm.length + forearm.length;
|
||||
Vector3 elbowAdd = Vector3.zero;
|
||||
Vector3 handAdd = Vector3.zero;
|
||||
|
||||
if (armLengthMlp != 1f)
|
||||
{
|
||||
armLength *= armLengthMlp;
|
||||
elbowAdd = (forearm.solverPosition - upperArm.solverPosition) * (armLengthMlp - 1f);
|
||||
handAdd = (hand.solverPosition - forearm.solverPosition) * (armLengthMlp - 1f);
|
||||
forearm.solverPosition += elbowAdd;
|
||||
hand.solverPosition += elbowAdd + handAdd;
|
||||
}
|
||||
|
||||
// Stretching
|
||||
float distanceToTarget = Vector3.Distance(upperArm.solverPosition, position);
|
||||
float stretchF = distanceToTarget / armLength;
|
||||
|
||||
float m = stretchCurve.Evaluate(stretchF);
|
||||
m *= positionWeight;
|
||||
|
||||
elbowAdd = (forearm.solverPosition - upperArm.solverPosition) * m;
|
||||
handAdd = (hand.solverPosition - forearm.solverPosition) * m;
|
||||
|
||||
forearm.solverPosition += elbowAdd;
|
||||
hand.solverPosition += elbowAdd + handAdd;
|
||||
}
|
||||
|
||||
public void Solve(bool isLeft)
|
||||
{
|
||||
chestRotation = Quaternion.LookRotation(rootRotation * chestForwardAxis, rootRotation * chestUpAxis);
|
||||
chestForward = chestRotation * Vector3.forward;
|
||||
chestUp = chestRotation * Vector3.up;
|
||||
|
||||
//Debug.DrawRay (Vector3.up * 2f, chestForward);
|
||||
//Debug.DrawRay (Vector3.up * 2f, chestUp);
|
||||
|
||||
Vector3 bendNormal = Vector3.zero;
|
||||
|
||||
if (hasShoulder && shoulderRotationWeight > 0f && LOD < 1)
|
||||
{
|
||||
switch (shoulderRotationMode)
|
||||
{
|
||||
case ShoulderRotationMode.YawPitch:
|
||||
Vector3 sDir = position - shoulder.solverPosition;
|
||||
sDir = sDir.normalized;
|
||||
|
||||
// Shoulder Yaw
|
||||
float yOA = isLeft ? shoulderYawOffset : -shoulderYawOffset;
|
||||
Quaternion yawOffset = Quaternion.AngleAxis((isLeft ? -90f : 90f) + yOA, chestUp);
|
||||
Quaternion workingSpace = yawOffset * chestRotation;
|
||||
|
||||
//Debug.DrawRay(Vector3.up * 2f, workingSpace * Vector3.forward);
|
||||
//Debug.DrawRay(Vector3.up * 2f, workingSpace * Vector3.up);
|
||||
|
||||
Vector3 sDirWorking = Quaternion.Inverse(workingSpace) * sDir;
|
||||
|
||||
//Debug.DrawRay(Vector3.up * 2f, sDirWorking);
|
||||
|
||||
float yaw = Mathf.Atan2(sDirWorking.x, sDirWorking.z) * Mathf.Rad2Deg;
|
||||
|
||||
float dotY = Vector3.Dot(sDirWorking, Vector3.up);
|
||||
dotY = 1f - Mathf.Abs(dotY);
|
||||
yaw *= dotY;
|
||||
|
||||
yaw -= yOA;
|
||||
float yawLimitMin = isLeft ? -20f : -50f;
|
||||
float yawLimitMax = isLeft ? 50f : 20f;
|
||||
yaw = DamperValue(yaw, yawLimitMin - yOA, yawLimitMax - yOA, 0.7f); // back, forward
|
||||
|
||||
Vector3 f = shoulder.solverRotation * shoulder.axis;
|
||||
Vector3 t = workingSpace * (Quaternion.AngleAxis(yaw, Vector3.up) * Vector3.forward);
|
||||
Quaternion yawRotation = Quaternion.FromToRotation(f, t);
|
||||
|
||||
//Debug.DrawRay(Vector3.up * 2f, f, Color.red);
|
||||
//Debug.DrawRay(Vector3.up * 2f, t, Color.green);
|
||||
|
||||
//Debug.DrawRay(Vector3.up * 2f, yawRotation * Vector3.forward, Color.blue);
|
||||
//Debug.DrawRay(Vector3.up * 2f, yawRotation * Vector3.up, Color.green);
|
||||
//Debug.DrawRay(Vector3.up * 2f, yawRotation * Vector3.right, Color.red);
|
||||
|
||||
// Shoulder Pitch
|
||||
Quaternion pitchOffset = Quaternion.AngleAxis(isLeft ? -90f : 90f, chestUp);
|
||||
workingSpace = pitchOffset * chestRotation;
|
||||
workingSpace = Quaternion.AngleAxis(isLeft ? shoulderPitchOffset : -shoulderPitchOffset, chestForward) * workingSpace;
|
||||
|
||||
//Debug.DrawRay(Vector3.up * 2f, workingSpace * Vector3.forward);
|
||||
//Debug.DrawRay(Vector3.up * 2f, workingSpace * Vector3.up);
|
||||
|
||||
sDir = position - (shoulder.solverPosition + chestRotation * (isLeft ? Vector3.right : Vector3.left) * mag);
|
||||
sDirWorking = Quaternion.Inverse(workingSpace) * sDir;
|
||||
|
||||
//Debug.DrawRay(Vector3.up * 2f, sDirWorking);
|
||||
|
||||
float pitch = Mathf.Atan2(sDirWorking.y, sDirWorking.z) * Mathf.Rad2Deg;
|
||||
|
||||
pitch -= shoulderPitchOffset;
|
||||
pitch = DamperValue(pitch, -45f - shoulderPitchOffset, 45f - shoulderPitchOffset);
|
||||
|
||||
Quaternion pitchRotation = Quaternion.AngleAxis(-pitch, workingSpace * Vector3.right);
|
||||
|
||||
//Debug.DrawRay(Vector3.up * 2f, pitchRotation * Vector3.forward, Color.green);
|
||||
//Debug.DrawRay(Vector3.up * 2f, pitchRotation * Vector3.up, Color.green);
|
||||
|
||||
// Rotate bones
|
||||
Quaternion sR = pitchRotation * yawRotation;
|
||||
if (shoulderRotationWeight * positionWeight < 1f) sR = Quaternion.Lerp(Quaternion.identity, sR, shoulderRotationWeight * positionWeight);
|
||||
VirtualBone.RotateBy(bones, sR);
|
||||
|
||||
Stretching();
|
||||
|
||||
// Solve trigonometric
|
||||
bendNormal = GetBendNormal(position - upperArm.solverPosition);
|
||||
VirtualBone.SolveTrigonometric(bones, 1, 2, 3, position, bendNormal, positionWeight);
|
||||
|
||||
float p = Mathf.Clamp(pitch * positionWeight * shoulderRotationWeight * shoulderTwistWeight * 2f, 0f, 180f);
|
||||
shoulder.solverRotation = Quaternion.AngleAxis(p, shoulder.solverRotation * (isLeft ? shoulder.axis : -shoulder.axis)) * shoulder.solverRotation;
|
||||
upperArm.solverRotation = Quaternion.AngleAxis(p, upperArm.solverRotation * (isLeft ? upperArm.axis : -upperArm.axis)) * upperArm.solverRotation;
|
||||
|
||||
// Additional pass to reach with the shoulders
|
||||
//VirtualBone.SolveTrigonometric(bones, 0, 1, 3, position, Vector3.Cross(upperArm.solverPosition - shoulder.solverPosition, hand.solverPosition - shoulder.solverPosition), positionWeight * 0.5f);
|
||||
break;
|
||||
case ShoulderRotationMode.FromTo:
|
||||
Quaternion shoulderRotation = shoulder.solverRotation;
|
||||
|
||||
Quaternion r = Quaternion.FromToRotation((upperArm.solverPosition - shoulder.solverPosition).normalized + chestForward, position - shoulder.solverPosition);
|
||||
r = Quaternion.Slerp(Quaternion.identity, r, 0.5f * shoulderRotationWeight * positionWeight);
|
||||
VirtualBone.RotateBy(bones, r);
|
||||
|
||||
Stretching();
|
||||
|
||||
VirtualBone.SolveTrigonometric(bones, 0, 2, 3, position, Vector3.Cross(forearm.solverPosition - shoulder.solverPosition, hand.solverPosition - shoulder.solverPosition), 0.5f * shoulderRotationWeight * positionWeight);
|
||||
bendNormal = GetBendNormal(position - upperArm.solverPosition);
|
||||
VirtualBone.SolveTrigonometric(bones, 1, 2, 3, position, bendNormal, positionWeight);
|
||||
|
||||
// Twist shoulder and upper arm bones when holding hands up
|
||||
Quaternion q = Quaternion.Inverse(Quaternion.LookRotation(chestUp, chestForward));
|
||||
Vector3 vBefore = q * (shoulderRotation * shoulder.axis);
|
||||
Vector3 vAfter = q * (shoulder.solverRotation * shoulder.axis);
|
||||
float angleBefore = Mathf.Atan2(vBefore.x, vBefore.z) * Mathf.Rad2Deg;
|
||||
float angleAfter = Mathf.Atan2(vAfter.x, vAfter.z) * Mathf.Rad2Deg;
|
||||
float pitchAngle = Mathf.DeltaAngle(angleBefore, angleAfter);
|
||||
if (isLeft) pitchAngle = -pitchAngle;
|
||||
pitchAngle = Mathf.Clamp(pitchAngle * shoulderRotationWeight * shoulderTwistWeight * 2f * positionWeight, 0f, 180f);
|
||||
|
||||
shoulder.solverRotation = Quaternion.AngleAxis(pitchAngle, shoulder.solverRotation * (isLeft ? shoulder.axis : -shoulder.axis)) * shoulder.solverRotation;
|
||||
upperArm.solverRotation = Quaternion.AngleAxis(pitchAngle, upperArm.solverRotation * (isLeft ? upperArm.axis : -upperArm.axis)) * upperArm.solverRotation;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LOD < 1) Stretching();
|
||||
|
||||
bendNormal = GetBendNormal(position - upperArm.solverPosition);
|
||||
// Solve arm trigonometric
|
||||
if (hasShoulder)
|
||||
{
|
||||
VirtualBone.SolveTrigonometric(bones, 1, 2, 3, position, bendNormal, positionWeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
VirtualBone.SolveTrigonometric(bones, 0, 1, 2, position, bendNormal, positionWeight);
|
||||
}
|
||||
}
|
||||
|
||||
if (LOD < 1 && positionWeight > 0f)
|
||||
{
|
||||
// Fix upperarm twist relative to bend normal
|
||||
Quaternion space = Quaternion.LookRotation(upperArm.solverRotation * upperArmBendAxis, forearm.solverPosition - upperArm.solverPosition);
|
||||
Vector3 upperArmTwist = Quaternion.Inverse(space) * bendNormal;
|
||||
float angle = Mathf.Atan2(upperArmTwist.x, upperArmTwist.z) * Mathf.Rad2Deg;
|
||||
upperArm.solverRotation = Quaternion.AngleAxis(angle * positionWeight, forearm.solverPosition - upperArm.solverPosition) * upperArm.solverRotation;
|
||||
|
||||
// Fix forearm twist relative to upper arm
|
||||
Quaternion forearmFixed = upperArm.solverRotation * forearmRelToUpperArm;
|
||||
Quaternion fromTo = Quaternion.FromToRotation(forearmFixed * forearm.axis, hand.solverPosition - forearm.solverPosition);
|
||||
RotateTo(forearm, fromTo * forearmFixed, positionWeight);
|
||||
}
|
||||
|
||||
// Set hand rotation
|
||||
if (rotationWeight >= 1f)
|
||||
{
|
||||
hand.solverRotation = rotation;
|
||||
}
|
||||
else if (rotationWeight > 0f)
|
||||
{
|
||||
hand.solverRotation = Quaternion.Lerp(hand.solverRotation, rotation, rotationWeight);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ResetOffsets()
|
||||
{
|
||||
handPositionOffset = Vector3.zero;
|
||||
}
|
||||
|
||||
public override void Write(ref Vector3[] solvedPositions, ref Quaternion[] solvedRotations)
|
||||
{
|
||||
if (hasShoulder)
|
||||
{
|
||||
solvedPositions[index] = shoulder.solverPosition;
|
||||
solvedRotations[index] = shoulder.solverRotation;
|
||||
}
|
||||
|
||||
solvedPositions[index + 1] = upperArm.solverPosition;
|
||||
solvedPositions[index + 2] = forearm.solverPosition;
|
||||
solvedPositions[index + 3] = hand.solverPosition;
|
||||
|
||||
solvedRotations[index + 1] = upperArm.solverRotation;
|
||||
solvedRotations[index + 2] = forearm.solverRotation;
|
||||
solvedRotations[index + 3] = hand.solverRotation;
|
||||
}
|
||||
|
||||
private float DamperValue(float value, float min, float max, float weight = 1f)
|
||||
{
|
||||
float range = max - min;
|
||||
|
||||
if (weight < 1f)
|
||||
{
|
||||
float mid = max - range * 0.5f;
|
||||
float v = value - mid;
|
||||
v *= 0.5f;
|
||||
value = mid + v;
|
||||
}
|
||||
|
||||
value -= min;
|
||||
|
||||
float t = Mathf.Clamp(value / range, 0f, 1f);
|
||||
float tEased = RootMotion.Interp.Float(t, InterpolationMode.InOutQuintic);
|
||||
return Mathf.Lerp(min, max, tEased);
|
||||
}
|
||||
|
||||
private Vector3 GetBendNormal(Vector3 dir)
|
||||
{
|
||||
if (bendGoal != null) bendDirection = bendGoal.position - bones[1].solverPosition;
|
||||
|
||||
Vector3 armDir = bones[0].solverRotation * bones[0].axis;
|
||||
|
||||
Vector3 f = Vector3.down;
|
||||
Vector3 t = Quaternion.Inverse(chestRotation) * dir.normalized + Vector3.forward;
|
||||
Quaternion q = Quaternion.FromToRotation(f, t);
|
||||
|
||||
Vector3 b = q * Vector3.back;
|
||||
|
||||
f = Quaternion.Inverse(chestRotation) * armDir;
|
||||
t = Quaternion.Inverse(chestRotation) * dir;
|
||||
q = Quaternion.FromToRotation(f, t);
|
||||
b = q * b;
|
||||
|
||||
b = chestRotation * b;
|
||||
|
||||
b += armDir;
|
||||
b -= rotation * wristToPalmAxis;
|
||||
b -= rotation * palmToThumbAxis * 0.5f;
|
||||
|
||||
if (bendGoalWeight > 0f)
|
||||
{
|
||||
b = Vector3.Slerp(b, bendDirection, bendGoalWeight);
|
||||
}
|
||||
|
||||
if (swivelOffset != 0f) b = Quaternion.AngleAxis(swivelOffset, -dir) * b;
|
||||
|
||||
return Vector3.Cross(b, dir);
|
||||
}
|
||||
|
||||
private void Visualize(VirtualBone bone1, VirtualBone bone2, VirtualBone bone3, Color color)
|
||||
{
|
||||
Debug.DrawLine(bone1.solverPosition, bone2.solverPosition, color);
|
||||
Debug.DrawLine(bone2.solverPosition, bone3.solverPosition, color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f490a683bef204445a10e96a5f719bad
|
||||
timeCreated: 1456910094
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,105 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
using RootMotion;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Hybrid %IK solver designed for mapping a character to a VR headset and 2 hand controllers.
|
||||
/// </summary>
|
||||
public partial class IKSolverVR: IKSolver {
|
||||
|
||||
/// <summary>
|
||||
/// A base class for all IKSolverVR body parts.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public abstract class BodyPart {
|
||||
|
||||
protected abstract void OnRead(Vector3[] positions, Quaternion[] rotations, bool hasChest, bool hasNeck, bool hasShoulders, bool hasToes, bool hasLegs, int rootIndex, int index);
|
||||
public abstract void PreSolve(float scale);
|
||||
public abstract void Write(ref Vector3[] solvedPositions, ref Quaternion[] solvedRotations);
|
||||
public abstract void ApplyOffsets(float scale);
|
||||
public abstract void ResetOffsets();
|
||||
|
||||
public float sqrMag { get; private set; }
|
||||
public float mag { get; private set; }
|
||||
|
||||
[HideInInspector] public VirtualBone[] bones = new VirtualBone[0];
|
||||
protected bool initiated;
|
||||
protected Vector3 rootPosition;
|
||||
protected Quaternion rootRotation = Quaternion.identity;
|
||||
protected int index = -1;
|
||||
protected int LOD;
|
||||
|
||||
public void SetLOD(int LOD)
|
||||
{
|
||||
this.LOD = LOD;
|
||||
}
|
||||
|
||||
public void Read(Vector3[] positions, Quaternion[] rotations, bool hasChest, bool hasNeck, bool hasShoulders, bool hasToes, bool hasLegs, int rootIndex, int index) {
|
||||
this.index = index;
|
||||
|
||||
rootPosition = positions[rootIndex];
|
||||
rootRotation = rotations[rootIndex];
|
||||
|
||||
OnRead(positions, rotations, hasChest, hasNeck, hasShoulders, hasToes, hasLegs, rootIndex, index);
|
||||
|
||||
mag = VirtualBone.PreSolve(ref bones);
|
||||
sqrMag = mag * mag;
|
||||
|
||||
initiated = true;
|
||||
}
|
||||
|
||||
public void MovePosition(Vector3 position) {
|
||||
Vector3 delta = position - bones[0].solverPosition;
|
||||
foreach (VirtualBone bone in bones) bone.solverPosition += delta;
|
||||
}
|
||||
|
||||
public void MoveRotation(Quaternion rotation) {
|
||||
Quaternion delta = QuaTools.FromToRotation(bones[0].solverRotation, rotation);
|
||||
VirtualBone.RotateAroundPoint(bones, 0, bones[0].solverPosition, delta);
|
||||
}
|
||||
|
||||
public void Translate(Vector3 position, Quaternion rotation) {
|
||||
MovePosition(position);
|
||||
MoveRotation(rotation);
|
||||
}
|
||||
|
||||
public void TranslateRoot(Vector3 newRootPos, Quaternion newRootRot) {
|
||||
Vector3 deltaPosition = newRootPos - rootPosition;
|
||||
rootPosition = newRootPos;
|
||||
foreach (VirtualBone bone in bones) bone.solverPosition += deltaPosition;
|
||||
|
||||
Quaternion deltaRotation = QuaTools.FromToRotation(rootRotation, newRootRot);
|
||||
rootRotation = newRootRot;
|
||||
VirtualBone.RotateAroundPoint(bones, 0, newRootPos, deltaRotation);
|
||||
}
|
||||
|
||||
public void RotateTo(VirtualBone bone, Quaternion rotation, float weight = 1f) {
|
||||
if (weight <= 0f) return;
|
||||
|
||||
Quaternion q = QuaTools.FromToRotation(bone.solverRotation, rotation);
|
||||
|
||||
if (weight < 1f) q = Quaternion.Slerp(Quaternion.identity, q, weight);
|
||||
|
||||
for (int i = 0; i < bones.Length; i++) {
|
||||
if (bones[i] == bone) {
|
||||
VirtualBone.RotateAroundPoint(bones, i, bones[i].solverPosition, q);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Visualize(Color color) {
|
||||
for (int i = 0; i < bones.Length - 1; i++) {
|
||||
Debug.DrawLine(bones[i].solverPosition, bones[i + 1].solverPosition, color);
|
||||
}
|
||||
}
|
||||
|
||||
public void Visualize() {
|
||||
Visualize(Color.white);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf16693a882bc4380a0b4cdaa370777e
|
||||
timeCreated: 1457005784
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,96 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
public partial class IKSolverVR: IKSolver {
|
||||
|
||||
[System.Serializable]
|
||||
public class Footstep {
|
||||
|
||||
public float stepSpeed = 3f;
|
||||
public Vector3 characterSpaceOffset;
|
||||
|
||||
public Vector3 position;
|
||||
public Quaternion rotation = Quaternion.identity;
|
||||
public Quaternion stepToRootRot = Quaternion.identity;
|
||||
public bool isStepping { get { return stepProgress < 1f; }}
|
||||
public bool isSupportLeg;
|
||||
public bool relaxFlag;
|
||||
|
||||
public float stepProgress { get; private set; }
|
||||
public Vector3 stepFrom;
|
||||
public Vector3 stepTo;
|
||||
public Quaternion stepFromRot = Quaternion.identity;
|
||||
public Quaternion stepToRot = Quaternion.identity;
|
||||
private Quaternion footRelativeToRoot = Quaternion.identity;
|
||||
private float supportLegW;
|
||||
private float supportLegWV;
|
||||
|
||||
public Footstep (Quaternion rootRotation, Vector3 footPosition, Quaternion footRotation, Vector3 characterSpaceOffset) {
|
||||
this.characterSpaceOffset = characterSpaceOffset;
|
||||
Reset(rootRotation, footPosition, footRotation);
|
||||
footRelativeToRoot = Quaternion.Inverse(rootRotation) * rotation;
|
||||
}
|
||||
|
||||
public void Reset(Quaternion rootRotation, Vector3 footPosition, Quaternion footRotation) {
|
||||
position = footPosition;
|
||||
rotation = footRotation;
|
||||
stepFrom = position;
|
||||
stepTo = position;
|
||||
stepFromRot = rotation;
|
||||
stepToRot = rotation;
|
||||
stepToRootRot = rootRotation;
|
||||
stepProgress = 1f;
|
||||
}
|
||||
|
||||
public void StepTo(Vector3 p, Quaternion rootRotation, float stepThreshold) {
|
||||
if (relaxFlag)
|
||||
{
|
||||
stepThreshold = 0f;
|
||||
relaxFlag = false;
|
||||
}
|
||||
|
||||
if (Vector3.Magnitude(p - stepTo) < stepThreshold && Quaternion.Angle(rootRotation, stepToRootRot) < 25f) return;
|
||||
stepFrom = position;
|
||||
stepTo = p;
|
||||
stepFromRot = rotation;
|
||||
stepToRootRot = rootRotation;
|
||||
stepToRot = rootRotation * footRelativeToRoot;
|
||||
stepProgress = 0f;
|
||||
}
|
||||
|
||||
public void UpdateStepping(Vector3 p, Quaternion rootRotation, float speed, float deltaTime) {
|
||||
stepTo = Vector3.Lerp (stepTo, p, deltaTime * speed);
|
||||
stepToRot = Quaternion.Lerp (stepToRot, rootRotation * footRelativeToRoot, deltaTime * speed);
|
||||
|
||||
stepToRootRot = stepToRot * Quaternion.Inverse(footRelativeToRoot);
|
||||
}
|
||||
|
||||
public void UpdateStanding(Quaternion rootRotation, float minAngle, float speed, float deltaTime) {
|
||||
if (speed <= 0f || minAngle >= 180f) return;
|
||||
|
||||
Quaternion r = rootRotation * footRelativeToRoot;
|
||||
float angle = Quaternion.Angle (rotation, r);
|
||||
if (angle > minAngle) rotation = Quaternion.RotateTowards (rotation, r, Mathf.Min (deltaTime * speed * (1f - supportLegW), angle -minAngle));
|
||||
}
|
||||
|
||||
public void Update(InterpolationMode interpolation, UnityEvent onStep, float deltaTime) {
|
||||
float supportLegWTarget = isSupportLeg ? 1f : 0f;
|
||||
supportLegW = Mathf.SmoothDamp (supportLegW, supportLegWTarget, ref supportLegWV, 0.2f);
|
||||
|
||||
if (!isStepping) return;
|
||||
|
||||
stepProgress = Mathf.MoveTowards(stepProgress, 1f, deltaTime * stepSpeed);
|
||||
|
||||
if (stepProgress >= 1f) onStep.Invoke ();
|
||||
|
||||
float stepProgressSmooth = RootMotion.Interp.Float(stepProgress, interpolation);
|
||||
|
||||
position = Vector3.Lerp(stepFrom, stepTo, stepProgressSmooth);
|
||||
rotation = Quaternion.Lerp(stepFromRot, stepToRot, stepProgressSmooth);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20a3b52b2557ad34794c80cfe364e330
|
||||
timeCreated: 1462272654
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,387 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
using RootMotion;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Hybrid %IK solver designed for mapping a character to a VR headset and 2 hand controllers
|
||||
/// </summary>
|
||||
public partial class IKSolverVR: IKSolver {
|
||||
|
||||
/// <summary>
|
||||
/// 4-segmented analytic leg chain.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Leg: BodyPart {
|
||||
|
||||
[LargeHeader("Foot/Toe")]
|
||||
|
||||
[Tooltip("The foot/toe target. This should not be the foot tracker itself, but a child GameObject parented to it so you could adjust its position/rotation to match the orientation of the foot/toe bone. If a toe bone is assigned in the References, the solver will match the toe bone to this target. If no toe bone assigned, foot bone will be used instead.")]
|
||||
/// <summary>
|
||||
/// The foot/toe target. This should not be the foot tracker itself, but a child GameObject parented to it so you could adjust its position/rotation to match the orientation of the foot/toe bone. If a toe bone is assigned in the References, the solver will match the toe bone to this target. If no toe bone assigned, foot bone will be used instead.
|
||||
/// </summary>
|
||||
public Transform target;
|
||||
|
||||
[Tooltip("Positional weight of the toe/foot target. Note that if you have nulled the target, the foot will still be pulled to the last position of the target until you set this value to 0.")]
|
||||
/// <summary>
|
||||
/// Positional weight of the toe/foot target. Note that if you have nulled the target, the foot will still be pulled to the last position of the target until you set this value to 0.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float positionWeight;
|
||||
|
||||
[Tooltip("Rotational weight of the toe/foot target. Note that if you have nulled the target, the foot will still be rotated to the last rotation of the target until you set this value to 0.")]
|
||||
/// <summary>
|
||||
/// Rotational weight of the toe/foot target. Note that if you have nulled the target, the foot will still be rotated to the last rotation of the target until you set this value to 0.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float rotationWeight;
|
||||
|
||||
[LargeHeader("Bending")]
|
||||
[Tooltip("The knee will be bent towards this Transform if 'Bend Goal Weight' > 0.")]
|
||||
/// <summary>
|
||||
/// The knee will be bent towards this Transform if 'Bend Goal Weight' > 0.
|
||||
/// </summary>
|
||||
public Transform bendGoal;
|
||||
|
||||
[Tooltip("If greater than 0, will bend the knee towards the 'Bend Goal' Transform.")]
|
||||
/// <summary>
|
||||
/// If greater than 0, will bend the knee towards the 'Bend Goal' Transform.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float bendGoalWeight;
|
||||
|
||||
[Tooltip("Angular offset of knee bending direction.")]
|
||||
/// <summary>
|
||||
/// Angular offset of knee bending direction.
|
||||
/// </summary>
|
||||
[Range(-180f, 180f)] public float swivelOffset;
|
||||
|
||||
[Tooltip("If 0, the bend plane will be locked to the rotation of the pelvis and rotating the foot will have no effect on the knee direction. If 1, to the target rotation of the leg so that the knee will bend towards the forward axis of the foot. Values in between will be slerped between the two.")]
|
||||
/// <summary>
|
||||
/// If 0, the bend plane will be locked to the rotation of the pelvis and rotating the foot will have no effect on the knee direction. If 1, to the target rotation of the leg so that the knee will bend towards the forward axis of the foot. Values in between will be slerped between the two.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float bendToTargetWeight = 0.5f;
|
||||
|
||||
[LargeHeader("Stretching")]
|
||||
|
||||
[Tooltip("Use this to make the leg shorter/longer. Works by displacement of foot and calf localPosition.")]
|
||||
/// <summary>
|
||||
/// Use this to make the leg shorter/longer. Works by displacement of foot and calf localPosition.
|
||||
/// </summary>
|
||||
[Range(0.01f, 2f)]
|
||||
public float legLengthMlp = 1f;
|
||||
|
||||
[Tooltip("Evaluates stretching of the leg by target distance relative to leg length. Value at time 1 represents stretching amount at the point where distance to the target is equal to leg length. Value at time 1 represents stretching amount at the point where distance to the target is double the leg length. Value represents the amount of stretching. Linear stretching would be achieved with a linear curve going up by 45 degrees. Increase the range of stretching by moving the last key up and right at the same amount. Smoothing in the curve can help reduce knee snapping (start stretching the arm slightly before target distance reaches leg length). To get a good optimal value for this curve, please go to the 'VRIK (Basic)' demo scene and copy the stretch curve over from the Pilot character.")]
|
||||
/// <summary>
|
||||
/// Evaluates stretching of the leg by target distance relative to leg length. Value at time 1 represents stretching amount at the point where distance to the target is equal to leg length. Value at time 1 represents stretching amount at the point where distance to the target is double the leg length. Value represents the amount of stretching. Linear stretching would be achieved with a linear curve going up by 45 degrees. Increase the range of stretching by moving the last key up and right at the same amount. Smoothing in the curve can help reduce knee snapping (start stretching the arm slightly before target distance reaches leg length). To get a good optimal value for this curve, please go to the 'VRIK (Basic)' demo scene and copy the stretch curve over from the Pilot character.
|
||||
/// </summary>
|
||||
public AnimationCurve stretchCurve = new AnimationCurve();
|
||||
|
||||
/// <summary>
|
||||
/// Target position of the toe/foot. Will be overwritten if target is assigned.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Vector3 IKPosition;
|
||||
|
||||
/// <summary>
|
||||
/// Target rotation of the toe/foot. Will be overwritten if target is assigned.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Quaternion IKRotation = Quaternion.identity;
|
||||
|
||||
/// <summary>
|
||||
/// Position offset of the toe/foot. Will be applied on top of target position and reset to Vector3.zero after each update.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Vector3 footPositionOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Position offset of the heel. Will be reset to Vector3.zero after each update.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Vector3 heelPositionOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Rotation offset of the toe/foot. Will be reset to Quaternion.identity after each update.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Quaternion footRotationOffset = Quaternion.identity;
|
||||
|
||||
/// <summary>
|
||||
/// The length of the leg (calculated in last read).
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public float currentMag;
|
||||
|
||||
/// <summary>
|
||||
/// If true, will sample the leg bend angle each frame from the animation.
|
||||
/// </summary>
|
||||
[HideInInspector] public bool useAnimatedBendNormal;
|
||||
|
||||
public Vector3 position { get; private set; }
|
||||
public Quaternion rotation { get; private set; }
|
||||
public bool hasToes { get; private set; }
|
||||
public VirtualBone thigh { get { return bones[0]; }}
|
||||
private VirtualBone calf { get { return bones[1]; }}
|
||||
private VirtualBone foot { get { return bones[2]; }}
|
||||
private VirtualBone toes { get { return bones[3]; }}
|
||||
public VirtualBone lastBone { get { return bones[bones.Length - 1]; }}
|
||||
public Vector3 thighRelativeToPelvis { get; private set; }
|
||||
|
||||
private Vector3 footPosition;
|
||||
private Quaternion footRotation = Quaternion.identity;
|
||||
private Vector3 bendNormal;
|
||||
private Quaternion calfRelToThigh = Quaternion.identity;
|
||||
private Quaternion thighRelToFoot = Quaternion.identity;
|
||||
public Vector3 bendNormalRelToPelvis { get; set; }
|
||||
public Vector3 bendNormalRelToTarget { get; set; }
|
||||
|
||||
protected override void OnRead(Vector3[] positions, Quaternion[] rotations, bool hasChest, bool hasNeck, bool hasShoulders, bool hasToes, bool hasLegs, int rootIndex, int index) {
|
||||
Vector3 thighPos = positions[index];
|
||||
Quaternion thighRot = rotations[index];
|
||||
Vector3 calfPos = positions[index + 1];
|
||||
Quaternion calfRot = rotations[index + 1];
|
||||
Vector3 footPos = positions[index + 2];
|
||||
Quaternion footRot = rotations[index + 2];
|
||||
Vector3 toePos = positions[index + 3];
|
||||
Quaternion toeRot = rotations[index + 3];
|
||||
|
||||
if (!initiated) {
|
||||
this.hasToes = hasToes;
|
||||
bones = new VirtualBone[hasToes? 4: 3];
|
||||
|
||||
if (hasToes) {
|
||||
bones[0] = new VirtualBone(thighPos, thighRot);
|
||||
bones[1] = new VirtualBone(calfPos, calfRot);
|
||||
bones[2] = new VirtualBone(footPos, footRot);
|
||||
bones[3] = new VirtualBone(toePos, toeRot);
|
||||
|
||||
IKPosition = toePos;
|
||||
IKRotation = toeRot;
|
||||
} else {
|
||||
bones[0] = new VirtualBone(thighPos, thighRot);
|
||||
bones[1] = new VirtualBone(calfPos, calfRot);
|
||||
bones[2] = new VirtualBone(footPos, footRot);
|
||||
|
||||
IKPosition = footPos;
|
||||
IKRotation = footRot;
|
||||
}
|
||||
|
||||
bendNormal = Vector3.Cross(calfPos - thighPos, footPos - calfPos);
|
||||
//bendNormal = rotations[0] * Vector3.right; // Use this to make the knees bend towards root.forward
|
||||
|
||||
bendNormalRelToPelvis = Quaternion.Inverse(rootRotation) * bendNormal;
|
||||
bendNormalRelToTarget = Quaternion.Inverse(IKRotation) * bendNormal;
|
||||
|
||||
rotation = IKRotation;
|
||||
}
|
||||
|
||||
if (hasToes) {
|
||||
bones[0].Read(thighPos, thighRot);
|
||||
bones[1].Read(calfPos, calfRot);
|
||||
bones[2].Read(footPos, footRot);
|
||||
bones[3].Read(toePos, toeRot);
|
||||
} else {
|
||||
bones[0].Read(thighPos, thighRot);
|
||||
bones[1].Read(calfPos, calfRot);
|
||||
bones[2].Read(footPos, footRot);
|
||||
}
|
||||
}
|
||||
|
||||
public override void PreSolve(float scale) {
|
||||
if (target != null) {
|
||||
IKPosition = target.position;
|
||||
IKRotation = target.rotation;
|
||||
}
|
||||
|
||||
footPosition = foot.solverPosition;
|
||||
footRotation = foot.solverRotation;
|
||||
|
||||
position = lastBone.solverPosition;
|
||||
rotation = lastBone.solverRotation;
|
||||
|
||||
if (rotationWeight > 0f) {
|
||||
ApplyRotationOffset(RootMotion.QuaTools.FromToRotation(rotation, IKRotation), rotationWeight);
|
||||
}
|
||||
|
||||
if (positionWeight > 0f) {
|
||||
ApplyPositionOffset(IKPosition - position, positionWeight);
|
||||
}
|
||||
|
||||
thighRelativeToPelvis = Quaternion.Inverse(rootRotation) * (thigh.solverPosition - rootPosition);
|
||||
calfRelToThigh = Quaternion.Inverse(thigh.solverRotation) * calf.solverRotation;
|
||||
thighRelToFoot = Quaternion.Inverse(lastBone.solverRotation) * thigh.solverRotation;
|
||||
|
||||
// Calculate bend plane normal
|
||||
if (useAnimatedBendNormal)
|
||||
{
|
||||
// This was used before version 1.8
|
||||
bendNormal = Vector3.Cross(calf.solverPosition - thigh.solverPosition, foot.solverPosition - calf.solverPosition);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (bendToTargetWeight <= 0f)
|
||||
{
|
||||
bendNormal = rootRotation * bendNormalRelToPelvis;
|
||||
}
|
||||
else if (bendToTargetWeight >= 1f)
|
||||
{
|
||||
bendNormal = rotation * bendNormalRelToTarget;
|
||||
}
|
||||
else
|
||||
{
|
||||
bendNormal = Vector3.Slerp(rootRotation * bendNormalRelToPelvis, rotation * bendNormalRelToTarget, bendToTargetWeight);
|
||||
}
|
||||
}
|
||||
|
||||
bendNormal = bendNormal.normalized;
|
||||
}
|
||||
|
||||
public override void ApplyOffsets(float scale) {
|
||||
ApplyPositionOffset(footPositionOffset, 1f);
|
||||
ApplyRotationOffset(footRotationOffset, 1f);
|
||||
|
||||
// Heel position offset
|
||||
Quaternion fromTo = Quaternion.FromToRotation(footPosition - position, footPosition + heelPositionOffset - position);
|
||||
footPosition = position + fromTo * (footPosition - position);
|
||||
footRotation = fromTo * footRotation;
|
||||
|
||||
// Bend normal offset
|
||||
float bAngle = 0f;
|
||||
|
||||
if (bendGoal != null && bendGoalWeight > 0f) {
|
||||
Vector3 b = Vector3.Cross(bendGoal.position - thigh.solverPosition, position - thigh.solverPosition);
|
||||
Quaternion l = Quaternion.LookRotation(bendNormal, thigh.solverPosition - foot.solverPosition);
|
||||
Vector3 bRelative = Quaternion.Inverse(l) * b;
|
||||
bAngle = Mathf.Atan2(bRelative.x, bRelative.z) * Mathf.Rad2Deg * bendGoalWeight;
|
||||
}
|
||||
|
||||
float sO = swivelOffset + bAngle;
|
||||
|
||||
if (sO != 0f) {
|
||||
bendNormal = Quaternion.AngleAxis(sO, thigh.solverPosition - lastBone.solverPosition) * bendNormal;
|
||||
thigh.solverRotation = Quaternion.AngleAxis(-sO, thigh.solverRotation * thigh.axis) * thigh.solverRotation;
|
||||
}
|
||||
}
|
||||
|
||||
// Foot position offset
|
||||
private void ApplyPositionOffset(Vector3 offset, float weight) {
|
||||
if (weight <= 0f) return;
|
||||
offset *= weight;
|
||||
|
||||
// Foot position offset
|
||||
footPosition += offset;
|
||||
position += offset;
|
||||
}
|
||||
|
||||
// Foot rotation offset
|
||||
private void ApplyRotationOffset(Quaternion offset, float weight) {
|
||||
if (weight <= 0f) return;
|
||||
if (weight < 1f) {
|
||||
offset = Quaternion.Lerp(Quaternion.identity, offset, weight);
|
||||
}
|
||||
|
||||
footRotation = offset * footRotation;
|
||||
rotation = offset * rotation;
|
||||
bendNormal = offset * bendNormal;
|
||||
footPosition = position + offset * (footPosition - position);
|
||||
}
|
||||
|
||||
public void Solve(bool stretch) {
|
||||
if (stretch && LOD < 1) Stretching();
|
||||
|
||||
// Foot pass
|
||||
VirtualBone.SolveTrigonometric(bones, 0, 1, 2, footPosition, bendNormal, 1f);
|
||||
|
||||
// Rotate foot back to where it was before the last solving
|
||||
RotateTo(foot, footRotation);
|
||||
|
||||
// Toes pass
|
||||
if (!hasToes)
|
||||
{
|
||||
FixTwistRotations();
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 b = Vector3.Cross(foot.solverPosition - thigh.solverPosition, toes.solverPosition - foot.solverPosition).normalized;
|
||||
|
||||
VirtualBone.SolveTrigonometric(bones, 0, 2, 3, position, b, 1f);
|
||||
|
||||
// Fix thigh twist relative to target rotation
|
||||
FixTwistRotations();
|
||||
|
||||
// Keep toe rotation fixed
|
||||
toes.solverRotation = rotation;
|
||||
}
|
||||
|
||||
private void FixTwistRotations()
|
||||
{
|
||||
if (LOD < 1)
|
||||
{
|
||||
if (bendToTargetWeight > 0f)
|
||||
{
|
||||
// Fix thigh twist relative to target rotation
|
||||
Quaternion thighRotation = rotation * thighRelToFoot;
|
||||
Quaternion f = Quaternion.FromToRotation(thighRotation * thigh.axis, calf.solverPosition - thigh.solverPosition);
|
||||
if (bendToTargetWeight < 1f)
|
||||
{
|
||||
thigh.solverRotation = Quaternion.Slerp(thigh.solverRotation, f * thighRotation, bendToTargetWeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
thigh.solverRotation = f * thighRotation;
|
||||
}
|
||||
}
|
||||
|
||||
// Fix calf twist relative to thigh
|
||||
Quaternion calfRotation = thigh.solverRotation * calfRelToThigh;
|
||||
Quaternion fromTo = Quaternion.FromToRotation(calfRotation * calf.axis, foot.solverPosition - calf.solverPosition);
|
||||
calf.solverRotation = fromTo * calfRotation;
|
||||
}
|
||||
}
|
||||
|
||||
private void Stretching() {
|
||||
// Adjusting leg length
|
||||
float legLength = thigh.length + calf.length;
|
||||
Vector3 kneeAdd = Vector3.zero;
|
||||
Vector3 footAdd = Vector3.zero;
|
||||
|
||||
if (legLengthMlp != 1f) {
|
||||
legLength *= legLengthMlp;
|
||||
kneeAdd = (calf.solverPosition - thigh.solverPosition) * (legLengthMlp - 1f);// * positionWeight;
|
||||
footAdd = (foot.solverPosition - calf.solverPosition) * (legLengthMlp - 1f);// * positionWeight;
|
||||
calf.solverPosition += kneeAdd;
|
||||
foot.solverPosition += kneeAdd + footAdd;
|
||||
if (hasToes) toes.solverPosition += kneeAdd + footAdd;
|
||||
}
|
||||
|
||||
// Stretching
|
||||
float distanceToTarget = Vector3.Distance(thigh.solverPosition, footPosition);
|
||||
float stretchF = distanceToTarget / legLength;
|
||||
|
||||
float m = stretchCurve.Evaluate(stretchF);// * positionWeight; mlp by positionWeight enables stretching only for foot trackers, but not for built-in or animated locomotion
|
||||
|
||||
kneeAdd = (calf.solverPosition - thigh.solverPosition) * m;
|
||||
footAdd = (foot.solverPosition - calf.solverPosition) * m;
|
||||
|
||||
calf.solverPosition += kneeAdd;
|
||||
foot.solverPosition += kneeAdd + footAdd;
|
||||
if (hasToes) toes.solverPosition += kneeAdd + footAdd;
|
||||
}
|
||||
|
||||
public override void Write(ref Vector3[] solvedPositions, ref Quaternion[] solvedRotations) {
|
||||
solvedRotations[index] = thigh.solverRotation;
|
||||
solvedRotations[index + 1] = calf.solverRotation;
|
||||
solvedRotations[index + 2] = foot.solverRotation;
|
||||
|
||||
solvedPositions[index] = thigh.solverPosition;
|
||||
solvedPositions[index + 1] = calf.solverPosition;
|
||||
solvedPositions[index + 2] = foot.solverPosition;
|
||||
|
||||
if (hasToes) {
|
||||
solvedRotations[index + 3] = toes.solverRotation;
|
||||
solvedPositions[index + 3] = toes.solverPosition;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ResetOffsets() {
|
||||
footPositionOffset = Vector3.zero;
|
||||
footRotationOffset = Quaternion.identity;
|
||||
heelPositionOffset = Vector3.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4efd2428843f841ed90f8096223b612d
|
||||
timeCreated: 1456910081
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,61 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Hybrid %IK solver designed for mapping a character to a VR headset and 2 hand controllers
|
||||
/// </summary>
|
||||
public partial class IKSolverVR: IKSolver {
|
||||
|
||||
[System.Serializable]
|
||||
public partial class Locomotion {
|
||||
|
||||
[System.Serializable]
|
||||
public enum Mode
|
||||
{
|
||||
Procedural = 0,
|
||||
Animated = 1,
|
||||
}
|
||||
|
||||
[Tooltip("Procedural (legacy) or animated locomotion.")]
|
||||
/// <summary>
|
||||
/// Procedural (legacy) or animated locomotion.
|
||||
/// </summary>
|
||||
public Mode mode;
|
||||
|
||||
[Tooltip("Used for blending in/out of procedural/animated locomotion.")]
|
||||
/// <summary>
|
||||
/// Used for blending in/out of procedural/animated locomotion.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)]
|
||||
public float weight = 1f;
|
||||
|
||||
public void Initiate(Animator animator, Vector3[] positions, Quaternion[] rotations, bool hasToes, float scale) {
|
||||
|
||||
Initiate_Procedural(positions, rotations, hasToes, scale);
|
||||
Initiate_Animated(animator, positions);
|
||||
}
|
||||
|
||||
public void Reset(Vector3[] positions, Quaternion[] rotations) {
|
||||
Reset_Procedural(positions, rotations);
|
||||
Reset_Animated(positions);
|
||||
}
|
||||
|
||||
public void Relax()
|
||||
{
|
||||
Relax_Procedural();
|
||||
}
|
||||
|
||||
public void AddDeltaRotation(Quaternion delta, Vector3 pivot) {
|
||||
AddDeltaRotation_Procedural(delta, pivot);
|
||||
AddDeltaRotation_Animated(delta, pivot);
|
||||
}
|
||||
|
||||
public void AddDeltaPosition(Vector3 delta) {
|
||||
AddDeltaPosition_Procedural(delta);
|
||||
AddDeltaPosition_Animated(delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbfd367345246d0448c45f0b1566a323
|
||||
timeCreated: 1462272254
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,345 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
public partial class IKSolverVR : IKSolver
|
||||
{
|
||||
public partial class Locomotion
|
||||
{
|
||||
[Tooltip("Start moving (horizontal distance to HMD + HMD velocity) threshold.")]
|
||||
/// <summary>
|
||||
/// Start moving (horizontal distance to HMD + HMD velocity) threshold.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Animated)]
|
||||
public float moveThreshold = 0.3f;
|
||||
|
||||
// ANIMATION
|
||||
[ShowLargeHeaderIf("Animation", "mode", Mode.Animated)] [SerializeField] byte animationHeader;
|
||||
|
||||
[Tooltip("Minimum locomotion animation speed.")]
|
||||
/// <summary>
|
||||
/// Minimum locomotion animation speed.
|
||||
/// </summary>
|
||||
[ShowRangeIf(0.1f, 1f, "mode", Mode.Animated)]
|
||||
public float minAnimationSpeed = 0.2f;
|
||||
|
||||
[Tooltip("Maximum locomotion animation speed.")]
|
||||
/// <summary>
|
||||
/// Maximum locomotion animation speed.
|
||||
/// </summary>
|
||||
[ShowRangeIf(1f, 10f, "mode", Mode.Animated)]
|
||||
public float maxAnimationSpeed = 3f;
|
||||
|
||||
[Tooltip("Smoothing time for Vector3.SmoothDamping 'VRIK_Horizontal' and 'VRIK_Vertical' parameters. Larger values make animation smoother, but less responsive.")]
|
||||
/// <summary>
|
||||
/// Smoothing time for Vector3.SmoothDamping 'VRIK_Horizontal' and 'VRIK_Vertical' parameters. Larger values make animation smoother, but less responsive.
|
||||
/// </summary>
|
||||
[ShowRangeIf(0.05f, 0.2f, "mode", Mode.Animated)]
|
||||
public float animationSmoothTime = 0.1f;
|
||||
|
||||
[ShowLargeHeaderIf("Root Position", "mode", Mode.Animated)] [SerializeField] byte rootPositionHeader;
|
||||
|
||||
[Tooltip("X and Z standing offset from the horizontal position of the HMD.")]
|
||||
/// <summary>
|
||||
/// X and Z standing offset from the horizontal position of the HMD.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Animated)]
|
||||
public Vector2 standOffset;
|
||||
|
||||
[Tooltip("Lerp root towards the horizontal position of the HMD with this speed while moving.")]
|
||||
/// <summary>
|
||||
/// Lerp root towards the horizontal position of the HMD with this speed while moving.
|
||||
/// </summary>
|
||||
[ShowRangeIf(0f, 50f, "mode", Mode.Animated)]
|
||||
public float rootLerpSpeedWhileMoving = 30f;
|
||||
|
||||
[Tooltip("Lerp root towards the horizontal position of the HMD with this speed while in transition from locomotion to idle state.")]
|
||||
/// <summary>
|
||||
/// Lerp root towards the horizontal position of the HMD with this speed while in transition from locomotion to idle state.
|
||||
/// </summary>
|
||||
[ShowRangeIf(0f, 50f, "mode", Mode.Animated)]
|
||||
public float rootLerpSpeedWhileStopping = 10f;
|
||||
|
||||
[Tooltip("Lerp root towards the horizontal position of the HMD with this speed while turning on spot.")]
|
||||
/// <summary>
|
||||
/// Lerp root towards the horizontal position of the HMD with this speed while turning on spot.
|
||||
/// </summary>
|
||||
[ShowRangeIf(0f, 50f, "mode", Mode.Animated)]
|
||||
public float rootLerpSpeedWhileTurning = 10f;
|
||||
|
||||
[Tooltip("Max horizontal distance from the root to the HMD.")]
|
||||
/// <summary>
|
||||
/// Max horizontal distance from the root to the HMD.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Animated)]
|
||||
public float maxRootOffset = 0.5f;
|
||||
|
||||
[ShowLargeHeaderIf("Root Rotation", "mode", Mode.Animated)] [SerializeField] byte rootRotationHeader;
|
||||
|
||||
[Tooltip("Max root angle from head forward while moving (ik.solver.spine.maxRootAngle).")]
|
||||
/// <summary>
|
||||
/// Max root angle from head forward while moving (ik.solver.spine.maxRootAngle).
|
||||
/// </summary>
|
||||
[ShowRangeIf(0f, 180f, "mode", Mode.Animated)]
|
||||
public float maxRootAngleMoving = 10f;
|
||||
|
||||
[Tooltip("Max root angle from head forward while standing (ik.solver.spine.maxRootAngle.")]
|
||||
/// <summary>
|
||||
/// Max root angle from head forward while standing (ik.solver.spine.maxRootAngle.
|
||||
/// </summary>
|
||||
[ShowRangeIf(0f, 180f, "mode", Mode.Animated)]
|
||||
public float maxRootAngleStanding = 90f;
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies "VRIK_Horizontal" and "VRIK_Vertical" parameters. Larger values make steps longer and animation slower.
|
||||
/// </summary>
|
||||
[HideInInspector][SerializeField] public float stepLengthMlp = 1f;
|
||||
|
||||
private Animator animator;
|
||||
private Vector3 velocityLocal, velocityLocalV;
|
||||
private Vector3 lastCorrection;
|
||||
private Vector3 lastHeadTargetPos;
|
||||
private Vector3 lastSpeedRootPos;
|
||||
private Vector3 lastEndRootPos;
|
||||
private float rootLerpSpeed, rootVelocityV;
|
||||
private float animSpeed = 1f;
|
||||
private float animSpeedV;
|
||||
private float stopMoveTimer;
|
||||
private float turn;
|
||||
private float maxRootAngleV;
|
||||
private float currentAnimationSmoothTime = 0.05f;
|
||||
private bool isMoving;
|
||||
private bool firstFrame = true;
|
||||
|
||||
private static int VRIK_Horizontal;
|
||||
private static int VRIK_Vertical;
|
||||
private static int VRIK_IsMoving;
|
||||
private static int VRIK_Speed;
|
||||
private static int VRIK_Turn;
|
||||
private static bool isHashed;
|
||||
|
||||
public void Initiate_Animated(Animator animator, Vector3[] positions)
|
||||
{
|
||||
this.animator = animator;
|
||||
|
||||
if (animator == null && mode == Mode.Animated)
|
||||
{
|
||||
Debug.LogError("VRIK is in Animated locomotion mode, but cannot find Animator on the VRIK root gameobject.");
|
||||
}
|
||||
|
||||
ResetParams(positions);
|
||||
}
|
||||
|
||||
private void ResetParams(Vector3[] positions)
|
||||
{
|
||||
lastHeadTargetPos = positions[5];
|
||||
lastSpeedRootPos = positions[0];
|
||||
lastEndRootPos = lastSpeedRootPos;
|
||||
lastCorrection = Vector3.zero;
|
||||
isMoving = false;
|
||||
currentAnimationSmoothTime = 0.05f;
|
||||
stopMoveTimer = 1f;
|
||||
}
|
||||
|
||||
public void Reset_Animated(Vector3[] positions)
|
||||
{
|
||||
ResetParams(positions);
|
||||
|
||||
if (animator == null) return;
|
||||
|
||||
if (!isHashed)
|
||||
{
|
||||
VRIK_Horizontal = Animator.StringToHash("VRIK_Horizontal");
|
||||
VRIK_Vertical = Animator.StringToHash("VRIK_Vertical");
|
||||
VRIK_IsMoving = Animator.StringToHash("VRIK_IsMoving");
|
||||
VRIK_Speed = Animator.StringToHash("VRIK_Speed");
|
||||
VRIK_Turn = Animator.StringToHash("VRIK_Turn");
|
||||
isHashed = true;
|
||||
}
|
||||
|
||||
if (!firstFrame)
|
||||
{
|
||||
animator.SetFloat(VRIK_Horizontal, 0f);
|
||||
animator.SetFloat(VRIK_Vertical, 0f);
|
||||
animator.SetBool(VRIK_IsMoving, false);
|
||||
animator.SetFloat(VRIK_Speed, 1f);
|
||||
animator.SetFloat(VRIK_Turn, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDeltaRotation_Animated(Quaternion delta, Vector3 pivot)
|
||||
{
|
||||
Vector3 toLastEndRootPos = lastEndRootPos - pivot;
|
||||
lastEndRootPos = pivot + delta * toLastEndRootPos;
|
||||
|
||||
Vector3 toLastSpeedRootPos = lastSpeedRootPos - pivot;
|
||||
lastSpeedRootPos = pivot + delta * toLastSpeedRootPos;
|
||||
|
||||
Vector3 toLastHeadTargetPos = lastHeadTargetPos - pivot;
|
||||
lastHeadTargetPos = pivot + delta * toLastHeadTargetPos;
|
||||
}
|
||||
|
||||
private void AddDeltaPosition_Animated(Vector3 delta)
|
||||
{
|
||||
lastEndRootPos += delta;
|
||||
lastSpeedRootPos += delta;
|
||||
lastHeadTargetPos += delta;
|
||||
}
|
||||
|
||||
private float lastVelLocalMag;
|
||||
|
||||
public void Solve_Animated(IKSolverVR solver, float scale, float deltaTime)
|
||||
{
|
||||
if (animator == null)
|
||||
{
|
||||
Debug.LogError("VRIK cannot find Animator on the VRIK root gameobject.", solver.root);
|
||||
return;
|
||||
}
|
||||
|
||||
if (deltaTime <= 0f) return;
|
||||
|
||||
// Root up vector
|
||||
Vector3 rootUp = solver.rootBone.solverRotation * Vector3.up;
|
||||
|
||||
// Substract any motion from parent transforms
|
||||
Vector3 externalDelta = solver.rootBone.solverPosition - lastEndRootPos;
|
||||
externalDelta -= animator.deltaPosition;
|
||||
|
||||
// Head target position
|
||||
Vector3 headTargetPos = solver.spine.headPosition;
|
||||
Vector3 standOffsetWorld = solver.rootBone.solverRotation * new Vector3(standOffset.x, 0f, standOffset.y) * scale;
|
||||
headTargetPos += standOffsetWorld;
|
||||
|
||||
if (firstFrame)
|
||||
{
|
||||
lastHeadTargetPos = headTargetPos;
|
||||
|
||||
firstFrame = false;
|
||||
}
|
||||
|
||||
// Head target velocity
|
||||
Vector3 headTargetVelocity = (headTargetPos - lastHeadTargetPos) / deltaTime;
|
||||
lastHeadTargetPos = headTargetPos;
|
||||
headTargetVelocity = V3Tools.Flatten(headTargetVelocity, rootUp);
|
||||
|
||||
// Head target offset
|
||||
Vector3 offset = headTargetPos - solver.rootBone.solverPosition;
|
||||
offset -= externalDelta;
|
||||
offset -= lastCorrection;
|
||||
offset = V3Tools.Flatten(offset, rootUp);
|
||||
|
||||
// Turning
|
||||
Vector3 headForward = (solver.spine.IKRotationHead * solver.spine.anchorRelativeToHead) * Vector3.forward;
|
||||
headForward.y = 0f;
|
||||
Vector3 headForwardLocal = Quaternion.Inverse(solver.rootBone.solverRotation) * headForward;
|
||||
float angle = Mathf.Atan2(headForwardLocal.x, headForwardLocal.z) * Mathf.Rad2Deg;
|
||||
angle += solver.spine.rootHeadingOffset;
|
||||
float turnTarget = angle / 90f;
|
||||
bool isTurning = true;
|
||||
if (Mathf.Abs(turnTarget) < 0.2f)
|
||||
{
|
||||
turnTarget = 0f;
|
||||
isTurning = false;
|
||||
}
|
||||
|
||||
turn = Mathf.Lerp(turn, turnTarget, Time.deltaTime * 3f);
|
||||
animator.SetFloat(VRIK_Turn, turn * 2f);
|
||||
|
||||
// Local Velocity, animation smoothing
|
||||
Vector3 velocityLocalTarget = Quaternion.Inverse(solver.readRotations[0]) * (headTargetVelocity + offset);
|
||||
velocityLocalTarget *= weight * stepLengthMlp;
|
||||
|
||||
float animationSmoothTimeTarget = isTurning && !isMoving ? 0.2f : animationSmoothTime;
|
||||
currentAnimationSmoothTime = Mathf.Lerp(currentAnimationSmoothTime, animationSmoothTimeTarget, deltaTime * 20f);
|
||||
|
||||
velocityLocal = Vector3.SmoothDamp(velocityLocal, velocityLocalTarget, ref velocityLocalV, currentAnimationSmoothTime, Mathf.Infinity, deltaTime);
|
||||
float velLocalMag = velocityLocal.magnitude / stepLengthMlp;
|
||||
|
||||
//animator.SetBool("VRIK_StartWithRightFoot", velocityLocal.x >= 0f);
|
||||
animator.SetFloat(VRIK_Horizontal, velocityLocal.x / scale);
|
||||
animator.SetFloat(VRIK_Vertical, velocityLocal.z / scale);
|
||||
|
||||
// Is Moving
|
||||
float m = moveThreshold * scale;
|
||||
if (isMoving) m *= 0.9f;
|
||||
bool isMovingRaw = velocityLocal.sqrMagnitude > m * m;
|
||||
if (isMovingRaw) stopMoveTimer = 0f;
|
||||
else stopMoveTimer += deltaTime;
|
||||
isMoving = stopMoveTimer < 0.05f;
|
||||
|
||||
// Max root angle
|
||||
float maxRootAngleTarget = isMoving ? maxRootAngleMoving : maxRootAngleStanding;
|
||||
solver.spine.maxRootAngle = Mathf.SmoothDamp(solver.spine.maxRootAngle, maxRootAngleTarget, ref maxRootAngleV, 0.2f, Mathf.Infinity, deltaTime);
|
||||
|
||||
animator.SetBool(VRIK_IsMoving, isMoving);
|
||||
|
||||
// Animation speed
|
||||
Vector3 currentRootPos = solver.rootBone.solverPosition;
|
||||
currentRootPos -= externalDelta;
|
||||
currentRootPos -= lastCorrection;
|
||||
|
||||
Vector3 rootVelocity = (currentRootPos - lastSpeedRootPos) / deltaTime;
|
||||
lastSpeedRootPos = solver.rootBone.solverPosition;
|
||||
float rootVelocityMag = rootVelocity.magnitude;
|
||||
|
||||
float animSpeedTarget = minAnimationSpeed;
|
||||
if (rootVelocityMag > 0f && isMovingRaw)
|
||||
{
|
||||
animSpeedTarget = animSpeed * (velLocalMag / rootVelocityMag);
|
||||
}
|
||||
animSpeedTarget = Mathf.Clamp(animSpeedTarget, minAnimationSpeed, maxAnimationSpeed);
|
||||
animSpeed = Mathf.SmoothDamp(animSpeed, animSpeedTarget, ref animSpeedV, 0.05f, Mathf.Infinity, deltaTime);
|
||||
animSpeed = Mathf.Lerp(1f, animSpeed, weight);
|
||||
|
||||
animator.SetFloat(VRIK_Speed, animSpeed);
|
||||
|
||||
// Is Stopping
|
||||
AnimatorTransitionInfo transInfo = animator.GetAnimatorTransitionInfo(0);
|
||||
bool isStopping = transInfo.IsUserName("VRIK_Stop");
|
||||
|
||||
// Root lerp speed
|
||||
float rootLerpSpeedTarget = 0;
|
||||
if (isMoving) rootLerpSpeedTarget = rootLerpSpeedWhileMoving;
|
||||
if (isStopping) rootLerpSpeedTarget = rootLerpSpeedWhileStopping;
|
||||
if (isTurning) rootLerpSpeedTarget = rootLerpSpeedWhileTurning;
|
||||
|
||||
rootLerpSpeedTarget *= Mathf.Max(headTargetVelocity.magnitude, 0.2f);
|
||||
rootLerpSpeed = Mathf.Lerp(rootLerpSpeed, rootLerpSpeedTarget, deltaTime * 20f);
|
||||
|
||||
// Root lerp and limits
|
||||
headTargetPos += V3Tools.ExtractVertical(solver.rootBone.solverPosition - headTargetPos, rootUp, 1f);
|
||||
|
||||
if (maxRootOffset > 0f)
|
||||
{
|
||||
// Lerp towards head target position
|
||||
Vector3 p = solver.rootBone.solverPosition;
|
||||
|
||||
if (rootLerpSpeed > 0f)
|
||||
{
|
||||
solver.rootBone.solverPosition = Vector3.Lerp(solver.rootBone.solverPosition, headTargetPos, rootLerpSpeed * deltaTime * weight);
|
||||
}
|
||||
|
||||
lastCorrection = solver.rootBone.solverPosition - p;
|
||||
|
||||
// Max offset
|
||||
offset = headTargetPos - solver.rootBone.solverPosition;
|
||||
offset = V3Tools.Flatten(offset, rootUp);
|
||||
float offsetMag = offset.magnitude;
|
||||
|
||||
if (offsetMag > maxRootOffset)
|
||||
{
|
||||
lastCorrection += (offset - (offset / offsetMag) * maxRootOffset) * weight;
|
||||
solver.rootBone.solverPosition += lastCorrection;
|
||||
}
|
||||
} else
|
||||
{
|
||||
// Snap to head target position
|
||||
lastCorrection = (headTargetPos - solver.rootBone.solverPosition) * weight;
|
||||
solver.rootBone.solverPosition += lastCorrection;
|
||||
}
|
||||
|
||||
lastEndRootPos = solver.rootBone.solverPosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff87fd8caf7c02d458e3280b027dfa18
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,475 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
public partial class IKSolverVR : IKSolver
|
||||
{
|
||||
public partial class Locomotion
|
||||
{
|
||||
[Tooltip("Tries to maintain this distance between the legs.")]
|
||||
/// <summary>
|
||||
/// Tries to maintain this distance between the legs.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public float footDistance = 0.3f;
|
||||
|
||||
[Tooltip("Makes a step only if step target position is at least this far from the current footstep or the foot does not reach the current footstep anymore or footstep angle is past the 'Angle Threshold'.")]
|
||||
/// <summary>
|
||||
/// Makes a step only if step target position is at least this far from the current footstep or the foot does not reach the current footstep anymore or footstep angle is past the 'Angle Threshold'.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public float stepThreshold = 0.4f;
|
||||
|
||||
[Tooltip("Makes a step only if step target position is at least 'Step Threshold' far from the current footstep or the foot does not reach the current footstep anymore or footstep angle is past this value.")]
|
||||
/// <summary>
|
||||
/// Makes a step only if step target position is at least 'Step Threshold' far from the current footstep or the foot does not reach the current footstep anymore or footstep angle is past this value.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public float angleThreshold = 60f;
|
||||
|
||||
[Tooltip("Multiplies angle of the center of mass - center of pressure vector. Larger value makes the character step sooner if losing balance.")]
|
||||
/// <summary>
|
||||
/// Multiplies angle of the center of mass - center of pressure vector. Larger value makes the character step sooner if losing balance.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public float comAngleMlp = 1f;
|
||||
|
||||
[Tooltip("Maximum magnitude of head/hand target velocity used in prediction.")]
|
||||
/// <summary>
|
||||
/// Maximum magnitude of head/hand target velocity used in prediction.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public float maxVelocity = 0.4f;
|
||||
|
||||
[Tooltip("The amount of head/hand target velocity prediction.")]
|
||||
/// <summary>
|
||||
/// The amount of head/hand target velocity prediction.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public float velocityFactor = 0.4f;
|
||||
|
||||
[Tooltip("How much can a leg be extended before it is forced to step to another position? 1 means fully stretched.")]
|
||||
/// <summary>
|
||||
/// How much can a leg be extended before it is forced to step to another position? 1 means fully stretched.
|
||||
/// </summary>
|
||||
[ShowRangeIf(0.9f, 1f, "mode", Mode.Procedural)]
|
||||
public float maxLegStretch = 1f;
|
||||
|
||||
[Tooltip("The speed of lerping the root of the character towards the horizontal mid-point of the footsteps.")]
|
||||
/// <summary>
|
||||
/// The speed of lerping the root of the character towards the horizontal mid-point of the footsteps.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public float rootSpeed = 20f;
|
||||
|
||||
[Tooltip("The speed of moving a foot to the next position.")]
|
||||
/// <summary>
|
||||
/// The speed of moving a foot to the next position.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public float stepSpeed = 3f;
|
||||
|
||||
[Tooltip("The height of the foot by normalized step progress (0 - 1).")]
|
||||
/// <summary>
|
||||
/// The height of the foot by normalized step progress (0 - 1).
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public AnimationCurve stepHeight;
|
||||
|
||||
[Tooltip("Reduce this value if locomotion makes the head bob too much.")]
|
||||
/// <summary>
|
||||
/// Reduce this value if locomotion makes the head bob too much.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public float maxBodyYOffset = 0.05f;
|
||||
|
||||
[Tooltip("The height offset of the heel by normalized step progress (0 - 1).")]
|
||||
/// <summary>
|
||||
/// The height offset of the heel by normalized step progress (0 - 1).
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public AnimationCurve heelHeight;
|
||||
|
||||
[Tooltip("Rotates the foot while the leg is not stepping to relax the twist rotation of the leg if ideal rotation is past this angle.")]
|
||||
/// <summary>
|
||||
/// Rotates the foot while the leg is not stepping to relax the twist rotation of the leg if ideal rotation is past this angle.
|
||||
/// </summary>
|
||||
[ShowRangeIf(0f, 180f, "mode", Mode.Procedural)]
|
||||
public float relaxLegTwistMinAngle = 20f;
|
||||
|
||||
[Tooltip("The speed of rotating the foot while the leg is not stepping to relax the twist rotation of the leg.")]
|
||||
/// <summary>
|
||||
/// The speed of rotating the foot while the leg is not stepping to relax the twist rotation of the leg.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public float relaxLegTwistSpeed = 400f;
|
||||
|
||||
[Tooltip("Interpolation mode of the step.")]
|
||||
/// <summary>
|
||||
/// Interpolation mode of the step.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public InterpolationMode stepInterpolation = InterpolationMode.InOutSine;
|
||||
|
||||
[Tooltip("Offset for the approximated center of mass.")]
|
||||
/// <summary>
|
||||
/// Offset for the approximated center of mass.
|
||||
/// </summary>
|
||||
[ShowIf("mode", Mode.Procedural)]
|
||||
public Vector3 offset;
|
||||
|
||||
[HideInInspector] public bool blockingEnabled;
|
||||
[HideInInspector] public LayerMask blockingLayers;
|
||||
[HideInInspector] public float raycastRadius = 0.2f;
|
||||
[HideInInspector] public float raycastHeight = 0.2f;
|
||||
|
||||
//[Tooltip("Called when the left foot has finished a step.")]
|
||||
/// <summary>
|
||||
/// Called when the left foot has finished a step.
|
||||
/// </summary>
|
||||
[HideInInspector][SerializeField] public UnityEvent onLeftFootstep = new UnityEvent(); // Events hidden because of Unity custom property drawer problems with UnityEvents, can still access and add listeners via script.
|
||||
|
||||
//[Tooltip("Called when the right foot has finished a step")]
|
||||
/// <summary>
|
||||
/// Called when the right foot has finished a step
|
||||
/// </summary>
|
||||
[HideInInspector] [SerializeField] public UnityEvent onRightFootstep = new UnityEvent(); // Events hidden because of Unity custom property drawer problems with UnityEvents, can still access and add listeners via script.
|
||||
|
||||
/// <summary>
|
||||
/// Gets the approximated center of mass.
|
||||
/// </summary>
|
||||
public Vector3 centerOfMass { get; private set; }
|
||||
|
||||
private Footstep[] footsteps = new Footstep[0];
|
||||
private Vector3 lastComPosition;
|
||||
private Vector3 comVelocity;
|
||||
private int leftFootIndex;
|
||||
private int rightFootIndex;
|
||||
|
||||
private void Initiate_Procedural(Vector3[] positions, Quaternion[] rotations, bool hasToes, float scale)
|
||||
{
|
||||
leftFootIndex = hasToes ? 17 : 16;
|
||||
rightFootIndex = hasToes ? 21 : 20;
|
||||
|
||||
footsteps = new Footstep[2] {
|
||||
new Footstep(rotations[0], positions[leftFootIndex], rotations[leftFootIndex], footDistance * scale * Vector3.left),
|
||||
new Footstep(rotations[0], positions[rightFootIndex], rotations[rightFootIndex], footDistance * scale * Vector3.right)
|
||||
};
|
||||
}
|
||||
|
||||
private void Reset_Procedural(Vector3[] positions, Quaternion[] rotations)
|
||||
{
|
||||
lastComPosition = Vector3.Lerp(positions[1], positions[5], 0.25f) + rotations[0] * offset;
|
||||
comVelocity = Vector3.zero;
|
||||
|
||||
footsteps[0].Reset(rotations[0], positions[leftFootIndex], rotations[leftFootIndex]);
|
||||
footsteps[1].Reset(rotations[0], positions[rightFootIndex], rotations[rightFootIndex]);
|
||||
}
|
||||
|
||||
private void Relax_Procedural()
|
||||
{
|
||||
footsteps[0].relaxFlag = true;
|
||||
footsteps[1].relaxFlag = true;
|
||||
}
|
||||
|
||||
private void AddDeltaRotation_Procedural(Quaternion delta, Vector3 pivot)
|
||||
{
|
||||
Vector3 toLastComPosition = lastComPosition - pivot;
|
||||
lastComPosition = pivot + delta * toLastComPosition;
|
||||
|
||||
foreach (Footstep f in footsteps)
|
||||
{
|
||||
f.rotation = delta * f.rotation;
|
||||
f.stepFromRot = delta * f.stepFromRot;
|
||||
f.stepToRot = delta * f.stepToRot;
|
||||
f.stepToRootRot = delta * f.stepToRootRot;
|
||||
|
||||
Vector3 toF = f.position - pivot;
|
||||
f.position = pivot + delta * toF;
|
||||
|
||||
Vector3 toStepFrom = f.stepFrom - pivot;
|
||||
f.stepFrom = pivot + delta * toStepFrom;
|
||||
|
||||
Vector3 toStepTo = f.stepTo - pivot;
|
||||
f.stepTo = pivot + delta * toStepTo;
|
||||
}
|
||||
}
|
||||
|
||||
private void AddDeltaPosition_Procedural(Vector3 delta)
|
||||
{
|
||||
lastComPosition += delta;
|
||||
|
||||
foreach (Footstep f in footsteps)
|
||||
{
|
||||
f.position += delta;
|
||||
f.stepFrom += delta;
|
||||
f.stepTo += delta;
|
||||
}
|
||||
}
|
||||
|
||||
public void Solve_Procedural(VirtualBone rootBone, Spine spine, Leg leftLeg, Leg rightLeg, Arm leftArm, Arm rightArm, int supportLegIndex, out Vector3 leftFootPosition, out Vector3 rightFootPosition, out Quaternion leftFootRotation, out Quaternion rightFootRotation, out float leftFootOffset, out float rightFootOffset, out float leftHeelOffset, out float rightHeelOffset, float scale, float deltaTime)
|
||||
{
|
||||
if (weight <= 0f || deltaTime <= 0f)
|
||||
{
|
||||
leftFootPosition = Vector3.zero;
|
||||
rightFootPosition = Vector3.zero;
|
||||
leftFootRotation = Quaternion.identity;
|
||||
rightFootRotation = Quaternion.identity;
|
||||
leftFootOffset = 0f;
|
||||
rightFootOffset = 0f;
|
||||
leftHeelOffset = 0f;
|
||||
rightHeelOffset = 0f;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 rootUp = rootBone.solverRotation * Vector3.up;
|
||||
|
||||
Vector3 leftThighPosition = spine.pelvis.solverPosition + spine.pelvis.solverRotation * leftLeg.thighRelativeToPelvis;
|
||||
Vector3 rightThighPosition = spine.pelvis.solverPosition + spine.pelvis.solverRotation * rightLeg.thighRelativeToPelvis;
|
||||
|
||||
footsteps[0].characterSpaceOffset = footDistance * Vector3.left * scale;
|
||||
footsteps[1].characterSpaceOffset = footDistance * Vector3.right * scale;
|
||||
|
||||
Vector3 forward = spine.faceDirection;
|
||||
Vector3 forwardY = V3Tools.ExtractVertical(forward, rootUp, 1f);
|
||||
forward -= forwardY;
|
||||
Quaternion forwardRotation = Quaternion.LookRotation(forward, rootUp);
|
||||
if (spine.rootHeadingOffset != 0f) forwardRotation = Quaternion.AngleAxis(spine.rootHeadingOffset, rootUp) * forwardRotation;
|
||||
|
||||
//centerOfMass = Vector3.Lerp(spine.pelvis.solverPosition, spine.head.solverPosition, 0.25f) + rootBone.solverRotation * offset;
|
||||
|
||||
float pelvisMass = 1f;
|
||||
float headMass = 1f;
|
||||
float armMass = 0.2f;
|
||||
float totalMass = pelvisMass + headMass + 2f * armMass;
|
||||
|
||||
centerOfMass = Vector3.zero;
|
||||
centerOfMass += spine.pelvis.solverPosition * pelvisMass;
|
||||
centerOfMass += spine.head.solverPosition * headMass;
|
||||
centerOfMass += leftArm.position * armMass;
|
||||
centerOfMass += rightArm.position * armMass;
|
||||
centerOfMass /= totalMass;
|
||||
|
||||
centerOfMass += rootBone.solverRotation * offset;
|
||||
|
||||
comVelocity = deltaTime > 0f ? (centerOfMass - lastComPosition) / deltaTime : Vector3.zero;
|
||||
lastComPosition = centerOfMass;
|
||||
comVelocity = Vector3.ClampMagnitude(comVelocity, maxVelocity) * velocityFactor * scale;
|
||||
Vector3 centerOfMassV = centerOfMass + comVelocity;
|
||||
|
||||
Vector3 pelvisPositionGroundLevel = V3Tools.PointToPlane(spine.pelvis.solverPosition, rootBone.solverPosition, rootUp);
|
||||
Vector3 centerOfMassVGroundLevel = V3Tools.PointToPlane(centerOfMassV, rootBone.solverPosition, rootUp);
|
||||
|
||||
Vector3 centerOfPressure = Vector3.Lerp(footsteps[0].position, footsteps[1].position, 0.5f);
|
||||
|
||||
Vector3 comDir = centerOfMassV - centerOfPressure;
|
||||
float comAngle = Vector3.Angle(comDir, rootBone.solverRotation * Vector3.up) * comAngleMlp;
|
||||
|
||||
// Set support leg
|
||||
for (int i = 0; i < footsteps.Length; i++)
|
||||
{
|
||||
footsteps[i].isSupportLeg = supportLegIndex == i;
|
||||
}
|
||||
|
||||
// Update stepTo while stepping
|
||||
for (int i = 0; i < footsteps.Length; i++)
|
||||
{
|
||||
|
||||
if (footsteps[i].isStepping)
|
||||
{
|
||||
Vector3 stepTo = centerOfMassVGroundLevel + rootBone.solverRotation * footsteps[i].characterSpaceOffset;
|
||||
|
||||
if (!StepBlocked(footsteps[i].stepFrom, stepTo, rootBone.solverPosition))
|
||||
{
|
||||
footsteps[i].UpdateStepping(stepTo, forwardRotation, 10f, deltaTime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
footsteps[i].UpdateStanding(forwardRotation, relaxLegTwistMinAngle, relaxLegTwistSpeed, deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Triggering new footsteps
|
||||
if (CanStep())
|
||||
{
|
||||
int stepLegIndex = -1;
|
||||
float bestValue = -Mathf.Infinity;
|
||||
|
||||
for (int i = 0; i < footsteps.Length; i++)
|
||||
{
|
||||
if (!footsteps[i].isStepping)
|
||||
{
|
||||
Vector3 stepTo = centerOfMassVGroundLevel + rootBone.solverRotation * footsteps[i].characterSpaceOffset;
|
||||
|
||||
float legLength = i == 0 ? leftLeg.mag : rightLeg.mag;
|
||||
Vector3 thighPos = i == 0 ? leftThighPosition : rightThighPosition;
|
||||
|
||||
float thighDistance = Vector3.Distance(footsteps[i].position, thighPos);
|
||||
|
||||
bool lengthStep = false;
|
||||
if (thighDistance >= legLength * maxLegStretch)
|
||||
{// * 0.95f) {
|
||||
stepTo = pelvisPositionGroundLevel + rootBone.solverRotation * footsteps[i].characterSpaceOffset;
|
||||
lengthStep = true;
|
||||
}
|
||||
|
||||
bool collision = false;
|
||||
for (int n = 0; n < footsteps.Length; n++)
|
||||
{
|
||||
if (n != i && !lengthStep)
|
||||
{
|
||||
if (Vector3.Distance(footsteps[i].position, footsteps[n].position) < 0.25f * scale && (footsteps[i].position - stepTo).sqrMagnitude < (footsteps[n].position - stepTo).sqrMagnitude)
|
||||
{
|
||||
}
|
||||
else collision = GetLineSphereCollision(footsteps[i].position, stepTo, footsteps[n].position, 0.25f * scale);
|
||||
if (collision) break;
|
||||
}
|
||||
}
|
||||
|
||||
float angle = Quaternion.Angle(forwardRotation, footsteps[i].stepToRootRot);
|
||||
|
||||
if (!collision || angle > angleThreshold)
|
||||
{
|
||||
float stepDistance = Vector3.Distance(footsteps[i].position, stepTo);
|
||||
float t = stepThreshold * scale;
|
||||
if (footsteps[i].relaxFlag) t = 0f;
|
||||
|
||||
float sT = Mathf.Lerp(t, t * 0.1f, comAngle * 0.015f);
|
||||
if (lengthStep) sT *= 0.5f;
|
||||
if (i == 0) sT *= 0.9f;
|
||||
|
||||
if (!StepBlocked(footsteps[i].position, stepTo, rootBone.solverPosition))
|
||||
{
|
||||
if (stepDistance > sT || angle > angleThreshold)
|
||||
{
|
||||
float value = 0f;
|
||||
|
||||
value -= stepDistance;
|
||||
|
||||
if (value > bestValue)
|
||||
{
|
||||
stepLegIndex = i;
|
||||
bestValue = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (stepLegIndex != -1)
|
||||
{
|
||||
Vector3 stepTo = centerOfMassVGroundLevel + rootBone.solverRotation * footsteps[stepLegIndex].characterSpaceOffset;
|
||||
footsteps[stepLegIndex].stepSpeed = UnityEngine.Random.Range(stepSpeed, stepSpeed * 1.5f);
|
||||
footsteps[stepLegIndex].StepTo(stepTo, forwardRotation, stepThreshold * scale);
|
||||
}
|
||||
}
|
||||
|
||||
footsteps[0].Update(stepInterpolation, onLeftFootstep, deltaTime);
|
||||
footsteps[1].Update(stepInterpolation, onRightFootstep, deltaTime);
|
||||
|
||||
leftFootPosition = footsteps[0].position;
|
||||
rightFootPosition = footsteps[1].position;
|
||||
|
||||
leftFootPosition = V3Tools.PointToPlane(leftFootPosition, leftLeg.lastBone.readPosition, rootUp);
|
||||
rightFootPosition = V3Tools.PointToPlane(rightFootPosition, rightLeg.lastBone.readPosition, rootUp);
|
||||
|
||||
leftFootOffset = stepHeight.Evaluate(footsteps[0].stepProgress) * scale;
|
||||
rightFootOffset = stepHeight.Evaluate(footsteps[1].stepProgress) * scale;
|
||||
|
||||
leftHeelOffset = heelHeight.Evaluate(footsteps[0].stepProgress) * scale;
|
||||
rightHeelOffset = heelHeight.Evaluate(footsteps[1].stepProgress) * scale;
|
||||
|
||||
leftFootRotation = footsteps[0].rotation;
|
||||
rightFootRotation = footsteps[1].rotation;
|
||||
}
|
||||
|
||||
public Vector3 leftFootstepPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return footsteps[0].position;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 rightFootstepPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
return footsteps[1].position;
|
||||
}
|
||||
}
|
||||
|
||||
public Quaternion leftFootstepRotation
|
||||
{
|
||||
get
|
||||
{
|
||||
return footsteps[0].rotation;
|
||||
}
|
||||
}
|
||||
|
||||
public Quaternion rightFootstepRotation
|
||||
{
|
||||
get
|
||||
{
|
||||
return footsteps[1].rotation;
|
||||
}
|
||||
}
|
||||
|
||||
private bool StepBlocked(Vector3 fromPosition, Vector3 toPosition, Vector3 rootPosition)
|
||||
{
|
||||
if (blockingLayers == -1 || !blockingEnabled) return false;
|
||||
|
||||
Vector3 origin = fromPosition;
|
||||
origin.y = rootPosition.y + raycastHeight + raycastRadius;
|
||||
|
||||
Vector3 direction = toPosition - origin;
|
||||
direction.y = 0f;
|
||||
|
||||
RaycastHit hit;
|
||||
|
||||
if (raycastRadius <= 0f)
|
||||
{
|
||||
return Physics.Raycast(origin, direction, out hit, direction.magnitude, blockingLayers);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Physics.SphereCast(origin, raycastRadius, direction, out hit, direction.magnitude, blockingLayers);
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanStep()
|
||||
{
|
||||
foreach (Footstep f in footsteps) if (f.isStepping && f.stepProgress < 0.8f) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool GetLineSphereCollision(Vector3 lineStart, Vector3 lineEnd, Vector3 sphereCenter, float sphereRadius)
|
||||
{
|
||||
Vector3 line = lineEnd - lineStart;
|
||||
Vector3 toSphere = sphereCenter - lineStart;
|
||||
float distToSphereCenter = toSphere.magnitude;
|
||||
float d = distToSphereCenter - sphereRadius;
|
||||
|
||||
if (d > line.magnitude) return false;
|
||||
|
||||
Quaternion q = Quaternion.LookRotation(line, toSphere);
|
||||
|
||||
Vector3 toSphereRotated = Quaternion.Inverse(q) * toSphere;
|
||||
|
||||
if (toSphereRotated.z < 0f)
|
||||
{
|
||||
return d < 0f;
|
||||
}
|
||||
|
||||
return toSphereRotated.y - sphereRadius < 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6d7aae105196f6743b27fef9c63a5ec0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,728 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
using RootMotion;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Hybrid %IK solver designed for mapping a character to a VR headset and 2 hand controllers
|
||||
/// </summary>
|
||||
public partial class IKSolverVR: IKSolver {
|
||||
|
||||
/// <summary>
|
||||
/// Spine solver for IKSolverVR.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class Spine: BodyPart {
|
||||
|
||||
[LargeHeader("Head")]
|
||||
|
||||
[Tooltip("The head target. This should not be the camera Transform itself, but a child GameObject parented to it so you could adjust its position/rotation to match the orientation of the head bone. The best practice for setup would be to move the camera to the avatar's eyes, duplicate the avatar's head bone and parent it to the camera. Then assign the duplicate to this slot.")]
|
||||
/// <summary>
|
||||
/// The head target. This should not be the camera Transform itself, but a child GameObject parented to it so you could adjust its position/rotation to match the orientation of the head bone. The best practice for setup would be to move the camera to the avatar's eyes, duplicate the avatar's head bone and parent it to the camera. Then assign the duplicate to this slot.
|
||||
/// </summary>
|
||||
public Transform headTarget;
|
||||
|
||||
[Tooltip("Positional weight of the head target. Note that if you have nulled the headTarget, the head will still be pulled to the last position of the headTarget until you set this value to 0.")]
|
||||
/// <summary>
|
||||
/// Positional weight of the head target. Note that if you have nulled the headTarget, the head will still be pulled to the last position of the headTarget until you set this value to 0.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float positionWeight = 1f;
|
||||
|
||||
[Tooltip("Rotational weight of the head target. Note that if you have nulled the headTarget, the head will still be rotated to the last rotation of the headTarget until you set this value to 0.")]
|
||||
/// <summary>
|
||||
/// Rotational weight of the head target. Note that if you have nulled the headTarget, the head will still be rotated to the last rotation of the headTarget until you set this value to 0.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float rotationWeight = 1f;
|
||||
|
||||
[Tooltip("Clamps head rotation. Value of 0.5 allows 90 degrees of rotation for the head relative to the headTarget. Value of 0 allows 180 degrees and value of 1 means head rotation will be locked to the target.")]
|
||||
/// <summary>
|
||||
/// Clamps head rotation. Value of 0.5 allows 90 degrees of rotation for the head relative to the headTarget. Value of 0 allows 180 degrees and value of 1 means head rotation will be locked to the target.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float headClampWeight = 0.6f;
|
||||
|
||||
[Tooltip("Minimum height of the head from the root of the character.")]
|
||||
/// <summary>
|
||||
/// Minimum height of the head from the root of the character.
|
||||
/// </summary>
|
||||
public float minHeadHeight = 0.8f;
|
||||
|
||||
[Tooltip("Allows for more natural locomotion animation for 3rd person networked avatars by inheriting vertical head bob motion from the animation while head target height is close to head bone height.")]
|
||||
/// <summary>
|
||||
/// Allows for more natural locomotion animation for 3rd person networked avatars by inheriting vertical head bob motion from the animation while head target height is close to head bone height.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float useAnimatedHeadHeightWeight;
|
||||
|
||||
[Tooltip("If abs(head target height - head bone height) < this value, will use head bone height as head target Y.")]
|
||||
/// <summary>
|
||||
/// If abs(head target height - head bone height) < this value, will use head bone height as head target Y.
|
||||
/// </summary>
|
||||
[ShowIf("useAnimatedHeadHeightWeight", 0f, Mathf.Infinity)]
|
||||
public float useAnimatedHeadHeightRange = 0.1f;
|
||||
|
||||
[Tooltip("Falloff range for the 'Use Animated Head Height Range' effect above. If head target height from head bone height is greater than useAnimatedHeadHeightRange + animatedHeadHeightBlend, then the head will be vertically locked to the head target again.")]
|
||||
/// <summary>
|
||||
/// Falloff range for the 'Use Animated Head Height Range' effect above. If head target height from head bone height is greater than useAnimatedHeadHeightRange + animatedHeadHeightBlend, then the head will be vertically locked to the head target again.
|
||||
/// </summary>
|
||||
[ShowIf("useAnimatedHeadHeightWeight", 0f, Mathf.Infinity)]
|
||||
public float animatedHeadHeightBlend = 0.3f;
|
||||
|
||||
[LargeHeader("Pelvis")]
|
||||
|
||||
[Tooltip("The pelvis target (optional), useful for seated rigs or if you had an additional tracker on the backpack or belt are. The best practice for setup would be to duplicate the avatar's pelvis bone and parenting it to the pelvis tracker. Then assign the duplicate to this slot.")]
|
||||
/// <summary>
|
||||
/// The pelvis target (optional), useful for seated rigs or if you had an additional tracker on the backpack or belt are. The best practice for setup would be to duplicate the avatar's pelvis bone and parenting it to the pelvis tracker. Then assign the duplicate to this slot.
|
||||
/// </summary>
|
||||
public Transform pelvisTarget;
|
||||
|
||||
[Tooltip("Positional weight of the pelvis target. Note that if you have nulled the pelvisTarget, the pelvis will still be pulled to the last position of the pelvisTarget until you set this value to 0.")]
|
||||
/// <summary>
|
||||
/// Positional weight of the pelvis target. Note that if you have nulled the pelvisTarget, the pelvis will still be pulled to the last position of the pelvisTarget until you set this value to 0.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float pelvisPositionWeight;
|
||||
|
||||
[Tooltip("Rotational weight of the pelvis target. Note that if you have nulled the pelvisTarget, the pelvis will still be rotated to the last rotation of the pelvisTarget until you set this value to 0.")]
|
||||
/// <summary>
|
||||
/// Rotational weight of the pelvis target. Note that if you have nulled the pelvisTarget, the pelvis will still be rotated to the last rotation of the pelvisTarget until you set this value to 0.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float pelvisRotationWeight;
|
||||
|
||||
[Tooltip("How much will the pelvis maintain its animated position?")]
|
||||
/// <summary>
|
||||
/// How much will the pelvis maintain its animated position?
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float maintainPelvisPosition = 0.2f;
|
||||
|
||||
[LargeHeader("Chest")]
|
||||
|
||||
[Tooltip("If 'Chest Goal Weight' is greater than 0, the chest will be turned towards this Transform.")]
|
||||
/// <summary>
|
||||
/// If chestGoalWeight is greater than 0, the chest will be turned towards this Transform.
|
||||
/// </summary>
|
||||
public Transform chestGoal;
|
||||
|
||||
[Tooltip("Weight of turning the chest towards the 'Chest Goal'.")]
|
||||
/// <summary>
|
||||
/// Weight of turning the chest towards the chestGoal.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float chestGoalWeight;
|
||||
|
||||
[Tooltip("Clamps chest rotation. Value of 0.5 allows 90 degrees of rotation for the chest relative to the head. Value of 0 allows 180 degrees and value of 1 means the chest will be locked relative to the head.")]
|
||||
/// <summary>
|
||||
/// Clamps chest rotation. Value of 0.5 allows 90 degrees of rotation for the chest relative to the head. Value of 0 allows 180 degrees and value of 1 means the chest will be locked relative to the head.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float chestClampWeight = 0.5f;
|
||||
|
||||
[Tooltip("The amount of rotation applied to the chest based on hand positions.")]
|
||||
/// <summary>
|
||||
/// The amount of rotation applied to the chest based on hand positions.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float rotateChestByHands = 1f;
|
||||
|
||||
[LargeHeader("Spine")]
|
||||
|
||||
[Tooltip("Determines how much the body will follow the position of the head.")]
|
||||
/// <summary>
|
||||
/// Determines how much the body will follow the position of the head.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float bodyPosStiffness = 0.55f;
|
||||
|
||||
[Tooltip("Determines how much the body will follow the rotation of the head.")]
|
||||
/// <summary>
|
||||
/// Determines how much the body will follow the rotation of the head.
|
||||
/// </summary>
|
||||
[Range(0f, 1f)] public float bodyRotStiffness = 0.1f;
|
||||
|
||||
[Tooltip("Determines how much the chest will rotate to the rotation of the head.")]
|
||||
/// <summary>
|
||||
/// Determines how much the chest will rotate to the rotation of the head.
|
||||
/// </summary>
|
||||
[FormerlySerializedAs("chestRotationWeight")]
|
||||
[Range(0f, 1f)] public float neckStiffness = 0.2f;
|
||||
|
||||
[Tooltip("Moves the body horizontally along -character.forward axis by that value when the player is crouching.")]
|
||||
/// <summary>
|
||||
/// Moves the body horizontally along -character.forward axis by that value when the player is crouching.
|
||||
/// </summary>
|
||||
public float moveBodyBackWhenCrouching = 0.5f;
|
||||
|
||||
[LargeHeader("Root Rotation")]
|
||||
|
||||
[Tooltip("Will automatically rotate the root of the character if the head target has turned past this angle.")]
|
||||
/// <summary>
|
||||
/// Will automatically rotate the root of the character if the head target has turned past this angle.
|
||||
/// </summary>
|
||||
[Range(0f, 180f)] public float maxRootAngle = 25f;
|
||||
|
||||
[Tooltip("Angular offset for root heading. Adjust this value to turn the root relative to the HMD around the vertical axis. Usefulf for fighting or shooting games where you would sometimes want the avatar to stand at an angled stance.")]
|
||||
/// <summary>
|
||||
/// Angular offset for root heading. Adjust this value to turn the root relative to the HMD around the vertical axis. Usefulf for fighting or shooting games where you would sometimes want the avatar to stand at an angled stance.
|
||||
/// </summary>
|
||||
[Range(-180f, 180f)] public float rootHeadingOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Target position of the head. Will be overwritten if target is assigned.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Vector3 IKPositionHead;
|
||||
|
||||
/// <summary>
|
||||
/// Target rotation of the head. Will be overwritten if target is assigned.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Quaternion IKRotationHead = Quaternion.identity;
|
||||
|
||||
/// <summary>
|
||||
/// Target position of the pelvis. Will be overwritten if target is assigned.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Vector3 IKPositionPelvis;
|
||||
|
||||
/// <summary>
|
||||
/// Target rotation of the pelvis. Will be overwritten if target is assigned.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Quaternion IKRotationPelvis = Quaternion.identity;
|
||||
|
||||
/// <summary>
|
||||
/// The goal position for the chest. If chestGoalWeight > 0, the chest will be turned towards this position.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Vector3 goalPositionChest;
|
||||
|
||||
/// <summary>
|
||||
/// Position offset of the pelvis. Will be applied on top of pelvis target position and reset to Vector3.zero after each update.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Vector3 pelvisPositionOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Position offset of the chest. Will be reset to Vector3.zero after each update.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Vector3 chestPositionOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Position offset of the head. Will be applied on top of head target position and reset to Vector3.zero after each update.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Vector3 headPositionOffset;
|
||||
|
||||
/// <summary>
|
||||
/// Rotation offset of the pelvis. Will be reset to Quaternion.identity after each update.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Quaternion pelvisRotationOffset = Quaternion.identity;
|
||||
|
||||
/// <summary>
|
||||
/// Rotation offset of the chest. Will be reset to Quaternion.identity after each update.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Quaternion chestRotationOffset = Quaternion.identity;
|
||||
|
||||
/// <summary>
|
||||
/// Rotation offset of the head. Will be applied on top of head target rotation and reset to Quaternion.identity after each update.
|
||||
/// </summary>
|
||||
[NonSerialized][HideInInspector] public Quaternion headRotationOffset = Quaternion.identity;
|
||||
|
||||
internal VirtualBone pelvis { get { return bones[pelvisIndex]; }}
|
||||
internal VirtualBone firstSpineBone { get { return bones[spineIndex]; }}
|
||||
internal VirtualBone chest {
|
||||
get {
|
||||
if (hasChest) return bones[chestIndex];
|
||||
return bones[spineIndex];
|
||||
}
|
||||
}
|
||||
internal VirtualBone head { get { return bones[headIndex]; } }
|
||||
private VirtualBone neck { get { return bones[neckIndex]; }}
|
||||
|
||||
|
||||
[NonSerialized][HideInInspector] public Vector3 faceDirection;
|
||||
[NonSerialized][HideInInspector] internal Vector3 headPosition;
|
||||
|
||||
internal Quaternion anchorRotation { get; private set; }
|
||||
internal Quaternion anchorRelativeToHead { get; private set; }
|
||||
|
||||
private Quaternion headRotation = Quaternion.identity;
|
||||
private Quaternion pelvisRotation = Quaternion.identity;
|
||||
private Quaternion anchorRelativeToPelvis = Quaternion.identity;
|
||||
private Quaternion pelvisRelativeRotation = Quaternion.identity;
|
||||
private Quaternion chestRelativeRotation = Quaternion.identity;
|
||||
private Vector3 headDeltaPosition;
|
||||
private Quaternion pelvisDeltaRotation = Quaternion.identity;
|
||||
private Quaternion chestTargetRotation = Quaternion.identity;
|
||||
private int pelvisIndex = 0, spineIndex = 1, chestIndex = -1, neckIndex = -1, headIndex = -1;
|
||||
private float length;
|
||||
private bool hasChest;
|
||||
private bool hasNeck;
|
||||
private bool hasLegs;
|
||||
private float headHeight;
|
||||
private float sizeMlp;
|
||||
private Vector3 chestForward;
|
||||
|
||||
protected override void OnRead(Vector3[] positions, Quaternion[] rotations, bool hasChest, bool hasNeck, bool hasShoulders, bool hasToes, bool hasLegs, int rootIndex, int index) {
|
||||
Vector3 pelvisPos = positions[index];
|
||||
Quaternion pelvisRot = rotations[index];
|
||||
Vector3 spinePos = positions[index + 1];
|
||||
Quaternion spineRot = rotations[index + 1];
|
||||
Vector3 chestPos = positions[index + 2];
|
||||
Quaternion chestRot = rotations[index + 2];
|
||||
Vector3 neckPos = positions[index + 3];
|
||||
Quaternion neckRot = rotations[index + 3];
|
||||
Vector3 headPos = positions[index + 4];
|
||||
Quaternion headRot = rotations[index + 4];
|
||||
|
||||
this.hasLegs = hasLegs;
|
||||
|
||||
if (!hasChest) {
|
||||
chestPos = spinePos;
|
||||
chestRot = spineRot;
|
||||
}
|
||||
|
||||
if (!initiated) {
|
||||
this.hasChest = hasChest;
|
||||
this.hasNeck = hasNeck;
|
||||
headHeight = V3Tools.ExtractVertical(headPos - positions[0], rotations[0] * Vector3.up, 1f).magnitude;
|
||||
|
||||
int boneCount = 3;
|
||||
if (hasChest) boneCount++;
|
||||
if (hasNeck) boneCount++;
|
||||
bones = new VirtualBone[boneCount];
|
||||
|
||||
chestIndex = hasChest? 2: 1;
|
||||
|
||||
neckIndex = 1;
|
||||
if (hasChest) neckIndex++;
|
||||
if (hasNeck) neckIndex++;
|
||||
|
||||
headIndex = 2;
|
||||
if (hasChest) headIndex++;
|
||||
if (hasNeck) headIndex++;
|
||||
|
||||
bones[0] = new VirtualBone(pelvisPos, pelvisRot);
|
||||
bones[1] = new VirtualBone(spinePos, spineRot);
|
||||
if (hasChest) bones[chestIndex] = new VirtualBone(chestPos, chestRot);
|
||||
if (hasNeck) bones[neckIndex] = new VirtualBone(neckPos, neckRot);
|
||||
bones[headIndex] = new VirtualBone(headPos, headRot);
|
||||
|
||||
pelvisRotationOffset = Quaternion.identity;
|
||||
chestRotationOffset = Quaternion.identity;
|
||||
headRotationOffset = Quaternion.identity;
|
||||
|
||||
anchorRelativeToHead = Quaternion.Inverse(headRot) * rotations[0];
|
||||
anchorRelativeToPelvis = Quaternion.Inverse(pelvisRot) * rotations[0];
|
||||
|
||||
faceDirection = rotations[0] * Vector3.forward;
|
||||
|
||||
IKPositionHead = headPos;
|
||||
IKRotationHead = headRot;
|
||||
IKPositionPelvis = pelvisPos;
|
||||
IKRotationPelvis = pelvisRot;
|
||||
goalPositionChest = chestPos + rotations[0] * Vector3.forward;
|
||||
}
|
||||
|
||||
// Forward and up axes
|
||||
pelvisRelativeRotation = Quaternion.Inverse(headRot) * pelvisRot;
|
||||
chestRelativeRotation = Quaternion.Inverse(headRot) * chestRot;
|
||||
|
||||
chestForward = Quaternion.Inverse(chestRot) * (rotations[0] * Vector3.forward);
|
||||
|
||||
bones[0].Read(pelvisPos, pelvisRot);
|
||||
bones[1].Read(spinePos, spineRot);
|
||||
if (hasChest) bones[chestIndex].Read(chestPos, chestRot);
|
||||
if (hasNeck) bones[neckIndex].Read(neckPos, neckRot);
|
||||
bones[headIndex].Read(headPos, headRot);
|
||||
|
||||
float spineLength = Vector3.Distance (pelvisPos, headPos);
|
||||
sizeMlp = spineLength / 0.7f;
|
||||
}
|
||||
|
||||
public override void PreSolve(float scale) {
|
||||
if (headTarget != null) {
|
||||
IKPositionHead = headTarget.position;
|
||||
IKRotationHead = headTarget.rotation;
|
||||
}
|
||||
|
||||
if (chestGoal != null) {
|
||||
goalPositionChest = chestGoal.position;
|
||||
}
|
||||
|
||||
if (pelvisTarget != null) {
|
||||
IKPositionPelvis = pelvisTarget.position;
|
||||
IKRotationPelvis = pelvisTarget.rotation;
|
||||
}
|
||||
|
||||
// Use animated head height range
|
||||
if (useAnimatedHeadHeightWeight > 0f && useAnimatedHeadHeightRange > 0f)
|
||||
{
|
||||
Vector3 rootUp = rootRotation * Vector3.up;
|
||||
|
||||
if (animatedHeadHeightBlend > 0f)
|
||||
{
|
||||
float headTargetVOffset = V3Tools.ExtractVertical(IKPositionHead - head.solverPosition, rootUp, 1f).magnitude;
|
||||
float abs = Mathf.Abs(headTargetVOffset);
|
||||
abs = Mathf.Max(abs - useAnimatedHeadHeightRange * scale, 0f);
|
||||
float f = Mathf.Lerp(0f, 1f, abs / (animatedHeadHeightBlend * scale));
|
||||
f = Interp.Float(1f - f, InterpolationMode.InOutSine);
|
||||
|
||||
Vector3 toHeadPos = head.solverPosition - IKPositionHead;
|
||||
IKPositionHead += V3Tools.ExtractVertical(toHeadPos, rootUp, f * useAnimatedHeadHeightWeight);
|
||||
}
|
||||
else
|
||||
{
|
||||
IKPositionHead += V3Tools.ExtractVertical(head.solverPosition - IKPositionHead, rootUp, useAnimatedHeadHeightWeight);
|
||||
}
|
||||
}
|
||||
|
||||
headPosition = V3Tools.Lerp(head.solverPosition, IKPositionHead, positionWeight);
|
||||
headRotation = QuaTools.Lerp(head.solverRotation, IKRotationHead, rotationWeight);
|
||||
|
||||
pelvisRotation = QuaTools.Lerp(pelvis.solverRotation, IKRotationPelvis, rotationWeight);
|
||||
}
|
||||
|
||||
public override void ApplyOffsets(float scale) {
|
||||
headPosition += headPositionOffset;
|
||||
|
||||
float mHH = minHeadHeight * scale;
|
||||
|
||||
Vector3 rootUp = rootRotation * Vector3.up;
|
||||
if (rootUp == Vector3.up) {
|
||||
headPosition.y = Math.Max(rootPosition.y + mHH, headPosition.y);
|
||||
} else {
|
||||
Vector3 toHead = headPosition - rootPosition;
|
||||
Vector3 hor = V3Tools.ExtractHorizontal(toHead, rootUp, 1f);
|
||||
Vector3 ver = toHead - hor;
|
||||
float dot = Vector3.Dot(ver, rootUp);
|
||||
if (dot > 0f) {
|
||||
if (ver.magnitude < mHH) ver = ver.normalized * mHH;
|
||||
} else {
|
||||
ver = -ver.normalized * mHH;
|
||||
}
|
||||
|
||||
headPosition = rootPosition + hor + ver;
|
||||
}
|
||||
|
||||
headRotation = headRotationOffset * headRotation;
|
||||
|
||||
headDeltaPosition = headPosition - head.solverPosition;
|
||||
pelvisDeltaRotation = QuaTools.FromToRotation(pelvis.solverRotation, headRotation * pelvisRelativeRotation);
|
||||
|
||||
if (pelvisRotationWeight <= 0f) anchorRotation = headRotation * anchorRelativeToHead;
|
||||
else if (pelvisRotationWeight > 0f && pelvisRotationWeight < 1f) anchorRotation = Quaternion.Lerp(headRotation * anchorRelativeToHead, pelvisRotation * anchorRelativeToPelvis, pelvisRotationWeight);
|
||||
else if (pelvisRotationWeight >= 1f) anchorRotation = pelvisRotation * anchorRelativeToPelvis;
|
||||
}
|
||||
|
||||
private void CalculateChestTargetRotation(VirtualBone rootBone, Arm[] arms) {
|
||||
chestTargetRotation = headRotation * chestRelativeRotation;
|
||||
|
||||
// Use hands to adjust c
|
||||
if (arms[0] != null) AdjustChestByHands(ref chestTargetRotation, arms);
|
||||
|
||||
faceDirection = Vector3.Cross(anchorRotation * Vector3.right, rootBone.readRotation * Vector3.up) + anchorRotation * Vector3.forward;
|
||||
}
|
||||
|
||||
public void Solve(Animator animator, VirtualBone rootBone, Leg[] legs, Arm[] arms, float scale) {
|
||||
CalculateChestTargetRotation(rootBone, arms);
|
||||
|
||||
// Root rotation
|
||||
if (maxRootAngle < 180f)
|
||||
{
|
||||
Vector3 f = faceDirection;
|
||||
if (rootHeadingOffset != 0f) f = Quaternion.AngleAxis(rootHeadingOffset, Vector3.up) * f;
|
||||
Vector3 faceDirLocal = Quaternion.Inverse(rootBone.solverRotation) * f;
|
||||
float angle = Mathf.Atan2(faceDirLocal.x, faceDirLocal.z) * Mathf.Rad2Deg;
|
||||
|
||||
float rotation = 0f;
|
||||
float maxAngle = maxRootAngle;
|
||||
|
||||
if (angle > maxAngle)
|
||||
{
|
||||
rotation = angle - maxAngle;
|
||||
}
|
||||
if (angle < -maxAngle)
|
||||
{
|
||||
rotation = angle + maxAngle;
|
||||
}
|
||||
|
||||
Quaternion fix = Quaternion.AngleAxis(rotation, rootBone.readRotation * Vector3.up);
|
||||
|
||||
if (animator != null && animator.enabled)
|
||||
{
|
||||
// Rotate root around animator.pivotPosition
|
||||
Vector3 pivot = animator.applyRootMotion? animator.pivotPosition: animator.transform.position;
|
||||
Vector3 dir = rootBone.solverPosition - pivot;
|
||||
rootBone.solverPosition = pivot + fix * dir;
|
||||
}
|
||||
|
||||
// Rotate root
|
||||
rootBone.solverRotation = fix * rootBone.solverRotation;
|
||||
}
|
||||
|
||||
Vector3 animatedPelvisPos = pelvis.solverPosition;
|
||||
Vector3 rootUp = rootBone.solverRotation * Vector3.up;
|
||||
|
||||
// Translate pelvis to make the head's position & rotation match with the head target
|
||||
TranslatePelvis(legs, headDeltaPosition, pelvisDeltaRotation, scale);
|
||||
|
||||
FABRIKPass(animatedPelvisPos, rootUp, positionWeight);
|
||||
|
||||
// Bend the spine to look towards chest target rotation
|
||||
Bend(bones, pelvisIndex, chestIndex, chestTargetRotation, chestRotationOffset, chestClampWeight, false, neckStiffness * rotationWeight);
|
||||
|
||||
if (LOD < 1 && chestGoalWeight > 0f) {
|
||||
Quaternion c = Quaternion.FromToRotation(bones[chestIndex].solverRotation * chestForward, goalPositionChest - bones[chestIndex].solverPosition) * bones[chestIndex].solverRotation;
|
||||
Bend(bones, pelvisIndex, chestIndex, c, chestRotationOffset, chestClampWeight, false, chestGoalWeight * rotationWeight);
|
||||
}
|
||||
|
||||
InverseTranslateToHead(legs, false, false, Vector3.zero, positionWeight);
|
||||
|
||||
if (LOD < 1) FABRIKPass(animatedPelvisPos, rootUp, positionWeight);
|
||||
|
||||
Bend(bones, neckIndex, headIndex, headRotation, headClampWeight, true, rotationWeight);
|
||||
|
||||
SolvePelvis ();
|
||||
}
|
||||
|
||||
private void FABRIKPass(Vector3 animatedPelvisPos, Vector3 rootUp, float weight) {
|
||||
Vector3 startPos = Vector3.Lerp(pelvis.solverPosition, animatedPelvisPos, maintainPelvisPosition) + pelvisPositionOffset;// - chestPositionOffset;
|
||||
Vector3 endPos = headPosition - chestPositionOffset;
|
||||
//Vector3 startOffset = rootUp * (bones[bones.Length - 1].solverPosition - bones[0].solverPosition).magnitude;
|
||||
Vector3 startOffset = Vector3.zero;// (bones[bones.Length - 1].solverPosition - bones[0].solverPosition) * weight;
|
||||
|
||||
float dist = Vector3.Distance(bones[0].solverPosition, bones[bones.Length - 1].solverPosition);
|
||||
|
||||
VirtualBone.SolveFABRIK(bones, startPos, endPos, weight, 1f, 1, dist, startOffset);
|
||||
}
|
||||
|
||||
private void SolvePelvis() {
|
||||
// Pelvis target
|
||||
if (pelvisPositionWeight > 0f)
|
||||
{
|
||||
Quaternion headSolverRotation = head.solverRotation;
|
||||
|
||||
Vector3 delta = ((IKPositionPelvis + pelvisPositionOffset) - pelvis.solverPosition) * pelvisPositionWeight;
|
||||
foreach (VirtualBone bone in bones) bone.solverPosition += delta;
|
||||
|
||||
Vector3 bendNormal = anchorRotation * Vector3.right;
|
||||
|
||||
if (hasChest && hasNeck)
|
||||
{
|
||||
VirtualBone.SolveTrigonometric(bones, spineIndex, chestIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight * 0.9f);
|
||||
VirtualBone.SolveTrigonometric(bones, chestIndex, neckIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight);
|
||||
|
||||
}
|
||||
else if (hasChest && !hasNeck)
|
||||
{
|
||||
VirtualBone.SolveTrigonometric(bones, spineIndex, chestIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight);
|
||||
}
|
||||
else if (!hasChest && hasNeck)
|
||||
{
|
||||
VirtualBone.SolveTrigonometric(bones, spineIndex, neckIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight);
|
||||
}
|
||||
else if (!hasNeck && !hasChest)
|
||||
{
|
||||
VirtualBone.SolveTrigonometric(bones, pelvisIndex, spineIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight);
|
||||
}
|
||||
|
||||
head.solverRotation = headSolverRotation;
|
||||
}
|
||||
|
||||
/* FIK v 1.9 - pelvis rotation to pelvis target was not working right
|
||||
// Pelvis target
|
||||
if (pelvisPositionWeight > 0f) {
|
||||
Quaternion headSolverRotation = head.solverRotation;
|
||||
|
||||
Vector3 delta = ((IKPositionPelvis + pelvisPositionOffset) - pelvis.solverPosition) * pelvisPositionWeight;
|
||||
foreach (VirtualBone bone in bones) bone.solverPosition += delta;
|
||||
|
||||
Vector3 bendNormal = anchorRotation * Vector3.right;
|
||||
|
||||
if (hasChest && hasNeck) {
|
||||
VirtualBone.SolveTrigonometric(bones, pelvisIndex, spineIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight * 0.6f);
|
||||
VirtualBone.SolveTrigonometric(bones, pelvisIndex, chestIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight * 0.6f);
|
||||
VirtualBone.SolveTrigonometric(bones, pelvisIndex, neckIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight * 1f);
|
||||
} else if (hasChest && !hasNeck) {
|
||||
VirtualBone.SolveTrigonometric(bones, pelvisIndex, spineIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight * 0.75f);
|
||||
VirtualBone.SolveTrigonometric(bones, pelvisIndex, chestIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight * 1f);
|
||||
}
|
||||
else if (!hasChest && hasNeck) {
|
||||
VirtualBone.SolveTrigonometric(bones, pelvisIndex, spineIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight * 0.75f);
|
||||
VirtualBone.SolveTrigonometric(bones, pelvisIndex, neckIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight * 1f);
|
||||
} else if (!hasNeck && !hasChest) {
|
||||
VirtualBone.SolveTrigonometric(bones, pelvisIndex, spineIndex, headIndex, headPosition, bendNormal, pelvisPositionWeight);
|
||||
}
|
||||
|
||||
head.solverRotation = headSolverRotation;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
public override void Write(ref Vector3[] solvedPositions, ref Quaternion[] solvedRotations) {
|
||||
// Pelvis
|
||||
solvedPositions[index] = bones[0].solverPosition;
|
||||
solvedRotations[index] = bones[0].solverRotation;
|
||||
|
||||
// Spine
|
||||
solvedRotations[index + 1] = bones[1].solverRotation;
|
||||
|
||||
// Chest
|
||||
if (hasChest) solvedRotations[index + 2] = bones[chestIndex].solverRotation;
|
||||
|
||||
// Neck
|
||||
if (hasNeck) solvedRotations[index + 3] = bones[neckIndex].solverRotation;
|
||||
|
||||
// Head
|
||||
solvedRotations[index + 4] = bones[headIndex].solverRotation;
|
||||
}
|
||||
|
||||
public override void ResetOffsets() {
|
||||
// Reset offsets to zero
|
||||
pelvisPositionOffset = Vector3.zero;
|
||||
chestPositionOffset = Vector3.zero;
|
||||
headPositionOffset = Vector3.zero;
|
||||
pelvisRotationOffset = Quaternion.identity;
|
||||
chestRotationOffset = Quaternion.identity;
|
||||
headRotationOffset = Quaternion.identity;
|
||||
}
|
||||
|
||||
private void AdjustChestByHands(ref Quaternion chestTargetRotation, Arm[] arms) {
|
||||
if (LOD > 0) return;
|
||||
|
||||
Quaternion h = Quaternion.Inverse(anchorRotation);
|
||||
|
||||
Vector3 pLeft = h * (arms[0].position - headPosition) / sizeMlp;
|
||||
Vector3 pRight = h * (arms[1].position - headPosition) / sizeMlp;
|
||||
|
||||
Vector3 c = Vector3.forward;
|
||||
c.x += pLeft.x * Mathf.Abs(pLeft.x);
|
||||
c.x += pLeft.z * Mathf.Abs(pLeft.z);
|
||||
c.x += pRight.x * Mathf.Abs(pRight.x);
|
||||
c.x -= pRight.z * Mathf.Abs(pRight.z);
|
||||
c.x *= 5f * rotateChestByHands;
|
||||
|
||||
float angle = Mathf.Atan2(c.x, c.z) * Mathf.Rad2Deg;
|
||||
Quaternion q = Quaternion.AngleAxis(angle, rootRotation * Vector3.up);
|
||||
|
||||
chestTargetRotation = q * chestTargetRotation;
|
||||
|
||||
Vector3 t = Vector3.up;
|
||||
t.x += pLeft.y;
|
||||
t.x -= pRight.y;
|
||||
t.x *= 0.5f * rotateChestByHands;
|
||||
|
||||
angle = Mathf.Atan2(t.x, t.y) * Mathf.Rad2Deg;
|
||||
q = Quaternion.AngleAxis(angle, rootRotation * Vector3.back);
|
||||
|
||||
chestTargetRotation = q * chestTargetRotation;
|
||||
}
|
||||
|
||||
// Move the pelvis so that the head would remain fixed to the anchor
|
||||
public void InverseTranslateToHead(Leg[] legs, bool limited, bool useCurrentLegMag, Vector3 offset, float w) {
|
||||
Vector3 delta = (headPosition + offset - head.solverPosition) * w;// * (1f - pelvisPositionWeight); This makes the head lose its target when pelvisPositionWeight is between 0 and 1.
|
||||
|
||||
Vector3 p = pelvis.solverPosition + delta;
|
||||
MovePosition( limited? LimitPelvisPosition(legs, p, useCurrentLegMag): p);
|
||||
}
|
||||
|
||||
// Move and rotate the pelvis
|
||||
private void TranslatePelvis(Leg[] legs, Vector3 deltaPosition, Quaternion deltaRotation, float scale) {
|
||||
// Rotation
|
||||
Vector3 p = head.solverPosition;
|
||||
|
||||
deltaRotation = QuaTools.ClampRotation(deltaRotation, chestClampWeight, 2);
|
||||
|
||||
Quaternion r = Quaternion.Slerp (Quaternion.identity, deltaRotation, bodyRotStiffness * rotationWeight);
|
||||
r = Quaternion.Slerp (r, QuaTools.FromToRotation (pelvis.solverRotation, IKRotationPelvis), pelvisRotationWeight);
|
||||
VirtualBone.RotateAroundPoint(bones, 0, pelvis.solverPosition, pelvisRotationOffset * r);
|
||||
|
||||
deltaPosition -= head.solverPosition - p;
|
||||
|
||||
// Position
|
||||
// Move the body back when head is moving down
|
||||
Vector3 m = rootRotation * Vector3.forward;
|
||||
float deltaY = V3Tools.ExtractVertical(deltaPosition, rootRotation * Vector3.up, 1f).magnitude;
|
||||
if (scale > 0f) deltaY /= scale;
|
||||
float backOffset = deltaY * -moveBodyBackWhenCrouching * headHeight;
|
||||
deltaPosition += m * backOffset;
|
||||
|
||||
MovePosition (LimitPelvisPosition(legs, pelvis.solverPosition + deltaPosition * bodyPosStiffness * positionWeight, false));
|
||||
}
|
||||
|
||||
// Limit the position of the pelvis so that the feet/toes would remain fixed
|
||||
private Vector3 LimitPelvisPosition(Leg[] legs, Vector3 pelvisPosition, bool useCurrentLegMag, int it = 2) {
|
||||
if (!hasLegs) return pelvisPosition;
|
||||
|
||||
// Cache leg current mag
|
||||
if (useCurrentLegMag) {
|
||||
foreach (Leg leg in legs) {
|
||||
leg.currentMag = Mathf.Max(Vector3.Distance(leg.thigh.solverPosition, leg.lastBone.solverPosition), leg.currentMag);
|
||||
}
|
||||
}
|
||||
|
||||
// Solve a 3-point constraint
|
||||
for (int i = 0; i < it; i++) {
|
||||
foreach (Leg leg in legs) {
|
||||
Vector3 delta = pelvisPosition - pelvis.solverPosition;
|
||||
Vector3 wantedThighPos = leg.thigh.solverPosition + delta;
|
||||
Vector3 toWantedThighPos = wantedThighPos - leg.position;
|
||||
float maxMag = useCurrentLegMag? leg.currentMag: leg.mag;
|
||||
Vector3 limitedThighPos = leg.position + Vector3.ClampMagnitude(toWantedThighPos, maxMag);
|
||||
pelvisPosition += limitedThighPos - wantedThighPos;
|
||||
|
||||
// TODO rotate pelvis to accommodate, rotate the spine back then
|
||||
}
|
||||
}
|
||||
|
||||
return pelvisPosition;
|
||||
}
|
||||
|
||||
// Bending the spine to the head effector
|
||||
private void Bend(VirtualBone[] bones, int firstIndex, int lastIndex, Quaternion targetRotation, float clampWeight, bool uniformWeight, float w) {
|
||||
if (w <= 0f) return;
|
||||
if (bones.Length == 0) return;
|
||||
int bonesCount = (lastIndex + 1) - firstIndex;
|
||||
if (bonesCount < 1) return;
|
||||
|
||||
Quaternion r = QuaTools.FromToRotation(bones[lastIndex].solverRotation, targetRotation);
|
||||
r = QuaTools.ClampRotation(r, clampWeight, 2);
|
||||
|
||||
float step = uniformWeight? 1f / bonesCount: 0f;
|
||||
|
||||
for (int i = firstIndex; i < lastIndex + 1; i++) {
|
||||
if (!uniformWeight) step = Mathf.Clamp(((i - firstIndex) + 1) / bonesCount, 0, 1f);
|
||||
VirtualBone.RotateAroundPoint(bones, i, bones[i].solverPosition, Quaternion.Slerp(Quaternion.identity, r, step * w));
|
||||
}
|
||||
}
|
||||
|
||||
// Bending the spine to the head effector
|
||||
private void Bend(VirtualBone[] bones, int firstIndex, int lastIndex, Quaternion targetRotation, Quaternion rotationOffset, float clampWeight, bool uniformWeight, float w) {
|
||||
if (w <= 0f) return;
|
||||
if (bones.Length == 0) return;
|
||||
int bonesCount = (lastIndex + 1) - firstIndex;
|
||||
if (bonesCount < 1) return;
|
||||
|
||||
Quaternion r = QuaTools.FromToRotation(bones[lastIndex].solverRotation, targetRotation);
|
||||
r = QuaTools.ClampRotation(r, clampWeight, 2);
|
||||
float step = uniformWeight ? 1f / bonesCount : 0f;
|
||||
|
||||
for (int i = firstIndex; i < lastIndex + 1; i++)
|
||||
{
|
||||
|
||||
if (!uniformWeight)
|
||||
{
|
||||
if (bonesCount == 1)
|
||||
{
|
||||
step = 1f;
|
||||
} else if (bonesCount == 2)
|
||||
{
|
||||
step = i == 0 ? 0.2f : 0.8f;
|
||||
} else if (bonesCount == 3)
|
||||
{
|
||||
if (i == 0) step = 0.15f;
|
||||
else if (i == 1) step = 0.4f;
|
||||
else step = 0.45f;
|
||||
} else if (bonesCount > 3)
|
||||
{
|
||||
step = 1f / bonesCount;
|
||||
}
|
||||
}
|
||||
|
||||
//if (!uniformWeight) step = Mathf.Clamp(((i - firstIndex) + 1) / bonesCount, 0, 1f);
|
||||
VirtualBone.RotateAroundPoint(bones, i, bones[i].solverPosition, Quaternion.Slerp(Quaternion.Slerp(Quaternion.identity, rotationOffset, step), r, step * w));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c34f2fc23becf47eaac7e16737cfb2d9
|
||||
timeCreated: 1456904601
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,242 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using System;
|
||||
using RootMotion;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Hybrid %IK solver designed for mapping a character to a VR headset and 2 hand controllers
|
||||
/// </summary>
|
||||
public partial class IKSolverVR: IKSolver {
|
||||
|
||||
[System.Serializable]
|
||||
public enum PositionOffset {
|
||||
Pelvis,
|
||||
Chest,
|
||||
Head,
|
||||
LeftHand,
|
||||
RightHand,
|
||||
LeftFoot,
|
||||
RightFoot,
|
||||
LeftHeel,
|
||||
RightHeel
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public enum RotationOffset {
|
||||
Pelvis,
|
||||
Chest,
|
||||
Head,
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class VirtualBone {
|
||||
|
||||
public Vector3 readPosition;
|
||||
public Quaternion readRotation;
|
||||
|
||||
public Vector3 solverPosition;
|
||||
public Quaternion solverRotation;
|
||||
|
||||
public float length;
|
||||
public float sqrMag;
|
||||
public Vector3 axis;
|
||||
|
||||
public VirtualBone(Vector3 position, Quaternion rotation) {
|
||||
Read(position, rotation);
|
||||
}
|
||||
|
||||
public void Read(Vector3 position, Quaternion rotation) {
|
||||
this.readPosition = position;
|
||||
this.readRotation = rotation;
|
||||
this.solverPosition = position;
|
||||
this.solverRotation = rotation;
|
||||
}
|
||||
|
||||
public static void SwingRotation(VirtualBone[] 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) r = Quaternion.Lerp(Quaternion.identity, r, weight);
|
||||
|
||||
for (int i = index; i < bones.Length; i++) {
|
||||
bones[i].solverRotation = r * bones[i].solverRotation;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculates bone lengths and axes, returns the length of the entire chain
|
||||
public static float PreSolve(ref VirtualBone[] bones) {
|
||||
float length = 0;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static void RotateAroundPoint(VirtualBone[] bones, int index, Vector3 point, Quaternion rotation) {
|
||||
for (int i = index; i < bones.Length; i++) {
|
||||
if (bones[i] != null) {
|
||||
Vector3 dir = bones[i].solverPosition - point;
|
||||
bones[i].solverPosition = point + rotation * dir;
|
||||
bones[i].solverRotation = rotation * bones[i].solverRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RotateBy(VirtualBone[] bones, int index, Quaternion rotation) {
|
||||
for (int i = index; i < bones.Length; i++) {
|
||||
if (bones[i] != null) {
|
||||
Vector3 dir = bones[i].solverPosition - bones[index].solverPosition;
|
||||
bones[i].solverPosition = bones[index].solverPosition + rotation * dir;
|
||||
bones[i].solverRotation = rotation * bones[i].solverRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RotateBy(VirtualBone[] bones, Quaternion rotation) {
|
||||
for (int i = 0; i < bones.Length; i++) {
|
||||
if (bones[i] != null) {
|
||||
if (i > 0) {
|
||||
Vector3 dir = bones[i].solverPosition - bones[0].solverPosition;
|
||||
bones[i].solverPosition = bones[0].solverPosition + rotation * dir;
|
||||
}
|
||||
|
||||
bones[i].solverRotation = rotation * bones[i].solverRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void RotateTo(VirtualBone[] bones, int index, Quaternion rotation) {
|
||||
Quaternion q = QuaTools.FromToRotation(bones[index].solverRotation, rotation);
|
||||
|
||||
RotateAroundPoint(bones, index, bones[index].solverPosition, q);
|
||||
}
|
||||
|
||||
// TODO Move to IKSolverTrigonometric
|
||||
/// <summary>
|
||||
/// Solve the bone chain virtually using both solverPositions and SolverRotations. This will work the same as IKSolverTrigonometric.Solve.
|
||||
/// </summary>
|
||||
public static void SolveTrigonometric(VirtualBone[] bones, int first, int second, int third, Vector3 targetPosition, Vector3 bendNormal, float weight) {
|
||||
if (weight <= 0f) return;
|
||||
|
||||
// Direction of the limb in solver
|
||||
targetPosition = Vector3.Lerp(bones[third].solverPosition, targetPosition, weight);
|
||||
|
||||
Vector3 dir = targetPosition - bones[first].solverPosition;
|
||||
|
||||
// Distance between the first and the last transform solver positions
|
||||
float sqrMag = dir.sqrMagnitude;
|
||||
if (sqrMag == 0f) return;
|
||||
float length = Mathf.Sqrt(sqrMag);
|
||||
|
||||
float sqrMag1 = (bones[second].solverPosition - bones[first].solverPosition).sqrMagnitude;
|
||||
float sqrMag2 = (bones[third].solverPosition - bones[second].solverPosition).sqrMagnitude;
|
||||
|
||||
// Get the general world space bending direction
|
||||
Vector3 bendDir = Vector3.Cross(dir, bendNormal);
|
||||
|
||||
// Get the direction to the trigonometrically solved position of the second transform
|
||||
Vector3 toBendPoint = GetDirectionToBendPoint(dir, length, bendDir, sqrMag1, sqrMag2);
|
||||
|
||||
// Position the second transform
|
||||
Quaternion q1 = Quaternion.FromToRotation(bones[second].solverPosition - bones[first].solverPosition, toBendPoint);
|
||||
if (weight < 1f) q1 = Quaternion.Lerp(Quaternion.identity, q1, weight);
|
||||
|
||||
RotateAroundPoint(bones, first, bones[first].solverPosition, q1);
|
||||
|
||||
Quaternion q2 = Quaternion.FromToRotation(bones[third].solverPosition - bones[second].solverPosition, targetPosition - bones[second].solverPosition);
|
||||
if (weight < 1f) q2 = Quaternion.Lerp(Quaternion.identity, q2, weight);
|
||||
|
||||
RotateAroundPoint(bones, second, bones[second].solverPosition, q2);
|
||||
}
|
||||
|
||||
//Calculates the bend direction based on the law of cosines. NB! Magnitude of the returned vector does not equal to the length of the first bone!
|
||||
private static Vector3 GetDirectionToBendPoint(Vector3 direction, float directionMag, Vector3 bendDirection, float sqrMag1, float sqrMag2) {
|
||||
float x = ((directionMag * directionMag) + (sqrMag1 - sqrMag2)) / 2f / directionMag;
|
||||
float y = (float)Math.Sqrt(Mathf.Clamp(sqrMag1 - x * x, 0, Mathf.Infinity));
|
||||
|
||||
if (direction == Vector3.zero) return Vector3.zero;
|
||||
return Quaternion.LookRotation(direction, bendDirection) * new Vector3(0f, y, x);
|
||||
}
|
||||
|
||||
// TODO Move to IKSolverFABRIK
|
||||
// Solves a simple FABRIK pass for a bone hierarchy, not using rotation limits or singularity breaking here
|
||||
public static void SolveFABRIK(VirtualBone[] bones, Vector3 startPosition, Vector3 targetPosition, float weight, float minNormalizedTargetDistance, int iterations, float length, Vector3 startOffset) {
|
||||
if (weight <= 0f) return;
|
||||
|
||||
if (minNormalizedTargetDistance > 0f) {
|
||||
Vector3 targetDirection = targetPosition - startPosition;
|
||||
float targetLength = targetDirection.magnitude;
|
||||
Vector3 tP = startPosition + (targetDirection / targetLength) * Mathf.Max(length * minNormalizedTargetDistance, targetLength);
|
||||
targetPosition = Vector3.Lerp(targetPosition, tP, weight);
|
||||
}
|
||||
|
||||
// Iterating the solver
|
||||
for (int iteration = 0; iteration < iterations; iteration ++) {
|
||||
// Stage 1
|
||||
bones[bones.Length - 1].solverPosition = Vector3.Lerp(bones[bones.Length - 1].solverPosition, targetPosition, weight);
|
||||
|
||||
for (int i = bones.Length - 2; i > -1; i--) {
|
||||
// Finding joint positions
|
||||
bones[i].solverPosition = SolveFABRIKJoint(bones[i].solverPosition, bones[i + 1].solverPosition, bones[i].length);
|
||||
}
|
||||
|
||||
// Stage 2
|
||||
if (iteration == 0) {
|
||||
foreach (VirtualBone bone in bones) bone.solverPosition += startOffset;
|
||||
}
|
||||
|
||||
bones[0].solverPosition = startPosition;
|
||||
|
||||
for (int i = 1; i < bones.Length; i++) {
|
||||
bones[i].solverPosition = SolveFABRIKJoint(bones[i].solverPosition, bones[i - 1].solverPosition, bones[i - 1].length);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < bones.Length - 1; i++) {
|
||||
VirtualBone.SwingRotation(bones, i, bones[i + 1].solverPosition);
|
||||
}
|
||||
}
|
||||
|
||||
// Solves a FABRIK joint between two bones.
|
||||
private static Vector3 SolveFABRIKJoint(Vector3 pos1, Vector3 pos2, float length) {
|
||||
return pos2 + (pos1 - pos2).normalized * length;
|
||||
}
|
||||
|
||||
public static void SolveCCD(VirtualBone[] bones, Vector3 targetPosition, float weight, int iterations) {
|
||||
if (weight <= 0f) return;
|
||||
|
||||
// Iterating the solver
|
||||
for (int iteration = 0; iteration < iterations; iteration ++) {
|
||||
for (int i = bones.Length - 2; i > -1; i--) {
|
||||
Vector3 toLastBone = bones[bones.Length - 1].solverPosition - bones[i].solverPosition;
|
||||
Vector3 toTarget = targetPosition - bones[i].solverPosition;
|
||||
|
||||
|
||||
Quaternion rotation = Quaternion.FromToRotation(toLastBone, toTarget);
|
||||
|
||||
if (weight >= 1) {
|
||||
//bones[i].transform.rotation = targetRotation;
|
||||
VirtualBone.RotateBy(bones, i, rotation);
|
||||
} else {
|
||||
VirtualBone.RotateBy(bones, i, Quaternion.Lerp(Quaternion.identity, rotation, weight));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16c601bf2e7a39247856d2c8521cbe4d
|
||||
timeCreated: 1467799164
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,75 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Relaxes the twist rotation if the TwistSolver transforms relative to their parent and a child Transforms, using their initial rotations as the most relaxed pose.
|
||||
/// </summary>
|
||||
public class TwistRelaxer : MonoBehaviour
|
||||
{
|
||||
|
||||
public IK ik;
|
||||
|
||||
[Tooltip("If using multiple solvers, add them in inverse hierarchical order - first forearm roll bone, then forearm bone and upper arm bone.")]
|
||||
public TwistSolver[] twistSolvers = new TwistSolver[0];
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (twistSolvers.Length == 0)
|
||||
{
|
||||
Debug.LogError("TwistRelaxer has no TwistSolvers. TwistRelaxer.cs was restructured for FIK v2.0 to support multiple relaxers on the same body part and TwistRelaxer components need to be set up again, sorry for the inconvenience!", transform);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (TwistSolver twistSolver in twistSolvers)
|
||||
{
|
||||
twistSolver.Initiate();
|
||||
}
|
||||
|
||||
if (ik != null)
|
||||
{
|
||||
ik.GetIKSolver().OnPostUpdate += OnPostUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (ik != null && ik.fixTransforms)
|
||||
{
|
||||
foreach (TwistSolver twistSolver in twistSolvers)
|
||||
{
|
||||
twistSolver.FixTransforms();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnPostUpdate()
|
||||
{
|
||||
if (ik != null)
|
||||
{
|
||||
foreach (TwistSolver twistSolver in twistSolvers)
|
||||
{
|
||||
twistSolver.Relax();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LateUpdate()
|
||||
{
|
||||
if (ik == null)
|
||||
{
|
||||
foreach (TwistSolver twistSolver in twistSolvers)
|
||||
{
|
||||
twistSolver.Relax();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (ik != null) ik.GetIKSolver().OnPostUpdate -= OnPostUpdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e561d2b0d3d2241fd9bc4929a8f64b7f
|
||||
timeCreated: 1465797451
|
||||
licenseType: Store
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 10300
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,174 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace RootMotion.FinalIK
|
||||
{
|
||||
/// <summary>
|
||||
/// Relaxes the twist rotation if the Transform relative to its parent and a child Transforms, using the Transform's initial rotation as the most relaxed pose.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class TwistSolver
|
||||
{
|
||||
[Tooltip("The transform that this solver operates on.")]
|
||||
public Transform transform;
|
||||
|
||||
[Tooltip("If this is the forearm roll bone, the parent should be the forearm bone. If null, will be found automatically.")]
|
||||
public Transform parent;
|
||||
|
||||
[Tooltip("If this is the forearm roll bone, the child should be the hand bone. If null, will attempt to find automatically. Assign the hand manually if the hand bone is not a child of the roll bone.")]
|
||||
public Transform[] children = new Transform[0];
|
||||
|
||||
[Tooltip("The weight of relaxing the twist of this Transform")]
|
||||
[Range(0f, 1f)] public float weight = 1f;
|
||||
|
||||
[Tooltip("If 0.5, this Transform will be twisted half way from parent to child. If 1, the twist angle will be locked to the child and will rotate with along with it.")]
|
||||
[Range(0f, 1f)] public float parentChildCrossfade = 0.5f;
|
||||
|
||||
[Tooltip("Rotation offset around the twist axis.")]
|
||||
[Range(-180f, 180f)] public float twistAngleOffset;
|
||||
|
||||
private Vector3 twistAxis = Vector3.right;
|
||||
private Vector3 axis = Vector3.forward;
|
||||
private Vector3 axisRelativeToParentDefault, axisRelativeToChildDefault;
|
||||
private Quaternion[] childRotations;
|
||||
private bool inititated;
|
||||
private Quaternion defaultLocalRotation = Quaternion.identity;
|
||||
private Quaternion[] defaultChildLocalRotations;
|
||||
|
||||
public TwistSolver()
|
||||
{
|
||||
weight = 1f;
|
||||
parentChildCrossfade = 0.5f;
|
||||
}
|
||||
|
||||
public TwistSolver(Transform t)
|
||||
{
|
||||
transform = t;
|
||||
weight = 1f;
|
||||
parentChildCrossfade = 0.5f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiate this TwistSolver
|
||||
/// </summary>
|
||||
public void Initiate()
|
||||
{
|
||||
if (inititated) return;
|
||||
|
||||
if (transform == null)
|
||||
{
|
||||
Debug.LogError("TwistRelaxer solver has unassigned Transform. TwistRelaxer.cs was restructured for FIK v2.0 to support multiple relaxers on the same body part and TwistRelaxer components need to be set up again, sorry for the inconvenience!", transform);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parent == null) parent = transform.parent;
|
||||
|
||||
if (children.Length == 0)
|
||||
{
|
||||
if (transform.childCount == 0)
|
||||
{
|
||||
var children = parent.GetComponentsInChildren<Transform>();
|
||||
for (int i = 1; i < children.Length; i++)
|
||||
{
|
||||
if (children[i] != transform)
|
||||
{
|
||||
children = new Transform[1] { children[i] };
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
children = new Transform[1] { transform.GetChild(0) };
|
||||
}
|
||||
}
|
||||
|
||||
if (children.Length == 0 || children[0] == null)
|
||||
{
|
||||
Debug.LogError("TwistRelaxer has no children assigned.", transform);
|
||||
return;
|
||||
}
|
||||
|
||||
twistAxis = transform.InverseTransformDirection(children[0].position - transform.position);
|
||||
axis = new Vector3(twistAxis.y, twistAxis.z, twistAxis.x);
|
||||
|
||||
// Axis in world space
|
||||
Vector3 axisWorld = transform.rotation * axis;
|
||||
|
||||
// Store the axis in worldspace relative to the rotations of the parent and child
|
||||
axisRelativeToParentDefault = Quaternion.Inverse(parent.rotation) * axisWorld;
|
||||
axisRelativeToChildDefault = Quaternion.Inverse(children[0].rotation) * axisWorld;
|
||||
|
||||
childRotations = new Quaternion[children.Length];
|
||||
|
||||
defaultLocalRotation = transform.localRotation;
|
||||
defaultChildLocalRotations = new Quaternion[children.Length];
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
{
|
||||
defaultChildLocalRotations[i] = children[i].localRotation;
|
||||
}
|
||||
|
||||
//if (ik != null) ik.GetIKSolver().OnPostUpdate += OnPostUpdate;
|
||||
inititated = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotates the bone back to default localRotation.
|
||||
/// </summary>
|
||||
public void FixTransforms()
|
||||
{
|
||||
transform.localRotation = defaultLocalRotation;
|
||||
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
{
|
||||
children[i].localRotation = defaultChildLocalRotations[i];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rotate this Transform to relax its twist angle relative to the "parent" and "child" Transforms.
|
||||
/// </summary>
|
||||
public void Relax()
|
||||
{
|
||||
if (!inititated) return;
|
||||
if (weight <= 0f) return; // Nothing to do here
|
||||
|
||||
Quaternion rotation = transform.rotation;
|
||||
Quaternion twistOffset = Quaternion.AngleAxis(twistAngleOffset, rotation * twistAxis);
|
||||
rotation = twistOffset * rotation;
|
||||
|
||||
// Find the world space relaxed axes of the parent and child
|
||||
Vector3 relaxedAxisParent = twistOffset * parent.rotation * axisRelativeToParentDefault;
|
||||
Quaternion f = Quaternion.FromToRotation(transform.position - parent.position, children[0].position - transform.position);
|
||||
relaxedAxisParent = f * relaxedAxisParent;
|
||||
|
||||
Vector3 relaxedAxisChild = twistOffset * children[0].rotation * axisRelativeToChildDefault;
|
||||
|
||||
// Cross-fade between the parent and child
|
||||
Vector3 relaxedAxis = Vector3.Slerp(relaxedAxisParent, relaxedAxisChild, parentChildCrossfade);
|
||||
|
||||
// Convert relaxedAxis to (axis, twistAxis) space so we could calculate the twist angle
|
||||
Quaternion r = Quaternion.LookRotation(rotation * axis, rotation * twistAxis);
|
||||
relaxedAxis = Quaternion.Inverse(r) * relaxedAxis;
|
||||
|
||||
// Calculate the angle by which we need to rotate this Transform around the twist axis.
|
||||
float angle = Mathf.Atan2(relaxedAxis.x, relaxedAxis.z) * Mathf.Rad2Deg;
|
||||
|
||||
// Store the rotation of the child so it would not change with twisting this Transform
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
{
|
||||
childRotations[i] = children[i].rotation;
|
||||
}
|
||||
|
||||
// Twist the bone
|
||||
transform.rotation = Quaternion.AngleAxis(angle * weight, rotation * twistAxis) * rotation;
|
||||
|
||||
// Revert the rotation of the child
|
||||
for (int i = 0; i < children.Length; i++)
|
||||
{
|
||||
children[i].rotation = childRotations[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2b5bf5cae9f30014f83b16a298eb5508
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user