initial upload
This commit is contained in:
@ -0,0 +1,92 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Dedicated abstrac base component for the Grounding solver.
|
||||
/// </summary>
|
||||
public abstract class Grounder: MonoBehaviour {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The master weight. Use this to fade in/out the grounding effect.
|
||||
/// </summary>
|
||||
[Tooltip("The master weight. Use this to fade in/out the grounding effect.")]
|
||||
[Range(0f, 1f)] public float weight = 1f;
|
||||
/// <summary>
|
||||
/// The %Grounding solver. Not to confuse with IK solvers.
|
||||
/// </summary>
|
||||
[Tooltip("The Grounding solver. Not to confuse with IK solvers.")]
|
||||
public Grounding solver = new Grounding();
|
||||
|
||||
/// <summary>
|
||||
/// Delegate for Grounder events.
|
||||
/// </summary>
|
||||
public delegate void GrounderDelegate();
|
||||
/// <summary>
|
||||
/// Called before the Grounder updates its solver.
|
||||
/// </summary>
|
||||
public GrounderDelegate OnPreGrounder;
|
||||
/// <summary>
|
||||
/// Called after the Grounder has updated its solver and before the IK is applied.
|
||||
/// </summary>
|
||||
public GrounderDelegate OnPostGrounder;
|
||||
/// <summary>
|
||||
/// Called after the IK has updated.
|
||||
/// </summary>
|
||||
public GrounderDelegate OnPostIK;
|
||||
|
||||
/// <summary>
|
||||
/// Resets this Grounder so characters can be teleported instananeously.
|
||||
/// </summary>
|
||||
public abstract void ResetPosition();
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
public bool initiated { get; protected set; }
|
||||
|
||||
// Gets the spine bend direction
|
||||
protected Vector3 GetSpineOffsetTarget() {
|
||||
Vector3 sum = Vector3.zero;
|
||||
for (int i = 0; i < solver.legs.Length; i++) {
|
||||
sum += GetLegSpineBendVector(solver.legs[i]);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
// Logs the warning if no other warning has beed logged in this session.
|
||||
protected void LogWarning(string message) {
|
||||
Warning.Log(message, transform);
|
||||
}
|
||||
|
||||
// Gets the bend direction for a foot
|
||||
private Vector3 GetLegSpineBendVector(Grounding.Leg leg) {
|
||||
Vector3 spineTangent = GetLegSpineTangent(leg);
|
||||
float dotF = (Vector3.Dot(solver.root.forward, spineTangent.normalized) + 1) * 0.5f; // Default behaviour, not bending spine when going downhill
|
||||
//float dotF = Mathf.Abs(Vector3.Dot(solver.root.forward, spineTangent.normalized)); // Bending spine backwards when going downhill
|
||||
//float dotF = Vector3.Dot(solver.root.forward, spineTangent.normalized); // Bending spine forward when going downhill
|
||||
float w = (leg.IKPosition - leg.transform.position).magnitude;
|
||||
return spineTangent * w * dotF;
|
||||
}
|
||||
|
||||
// Gets the direction from the root to the foot (ortho-normalized to root.up)
|
||||
private Vector3 GetLegSpineTangent(Grounding.Leg leg) {
|
||||
Vector3 tangent = leg.transform.position - solver.root.position;
|
||||
|
||||
if (!solver.rotateSolver || solver.root.up == Vector3.up) return new Vector3(tangent.x, 0f, tangent.z);
|
||||
|
||||
Vector3 normal = solver.root.up;
|
||||
Vector3.OrthoNormalize(ref normal, ref tangent);
|
||||
return tangent;
|
||||
}
|
||||
|
||||
// Open the User Manual url
|
||||
protected abstract void OpenUserManual();
|
||||
|
||||
// Open the Script Reference url
|
||||
protected abstract void OpenScriptReference();
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20d3b31ccf0b543f5b659a1c3292d5c5
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,196 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Grounding for BipedIK characters.
|
||||
/// </summary>
|
||||
[HelpURL("http://www.root-motion.com/finalikdox/html/page9.html")]
|
||||
[AddComponentMenu("Scripts/RootMotion.FinalIK/Grounder/Grounder Biped")]
|
||||
public class GrounderBipedIK: Grounder {
|
||||
|
||||
// Open the User Manual URL
|
||||
[ContextMenu("User Manual")]
|
||||
protected override void OpenUserManual() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page9.html");
|
||||
}
|
||||
|
||||
// Open the Script Reference URL
|
||||
[ContextMenu("Scrpt Reference")]
|
||||
protected override void OpenScriptReference() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_grounder_biped_i_k.html");
|
||||
}
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The BipedIK componet.
|
||||
/// </summary>
|
||||
[Tooltip("The BipedIK componet.")]
|
||||
public BipedIK ik;
|
||||
/// <summary>
|
||||
/// The amount of spine bending towards upward slopes.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of spine bending towards upward slopes.")]
|
||||
public float spineBend = 7f;
|
||||
/// <summary>
|
||||
/// The interpolation speed of spine bending.
|
||||
/// </summary>
|
||||
[Tooltip("The interpolation speed of spine bending.")]
|
||||
public float spineSpeed = 3f;
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
public override void ResetPosition() {
|
||||
solver.Reset();
|
||||
spineOffset = Vector3.zero;
|
||||
}
|
||||
|
||||
private Transform[] feet = new Transform[2];
|
||||
private Quaternion[] footRotations = new Quaternion[2];
|
||||
private Vector3 animatedPelvisLocalPosition, solvedPelvisLocalPosition;
|
||||
private Vector3 spineOffset;
|
||||
private float lastWeight;
|
||||
|
||||
// Can we initiate the Grounding?
|
||||
private bool IsReadyToInitiate() {
|
||||
if (ik == null) return false;
|
||||
if (!ik.solvers.leftFoot.initiated) return false;
|
||||
if (!ik.solvers.rightFoot.initiated) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Initiate once we have a BipedIK component
|
||||
void Update() {
|
||||
weight = Mathf.Clamp(weight, 0f, 1f);
|
||||
if (weight <= 0f) return;
|
||||
|
||||
if (initiated) return;
|
||||
if (!IsReadyToInitiate()) return;
|
||||
|
||||
Initiate();
|
||||
}
|
||||
|
||||
private void Initiate() {
|
||||
// Gathering both foot bones from the BipedIK
|
||||
feet = new Transform[2];
|
||||
footRotations = new Quaternion[2];
|
||||
|
||||
feet[0] = ik.references.leftFoot;
|
||||
feet[1] = ik.references.rightFoot;
|
||||
|
||||
footRotations[0] = Quaternion.identity;
|
||||
footRotations[1] = Quaternion.identity;
|
||||
|
||||
// Adding to the delegates to get call at certain points in the solving process
|
||||
ik.solvers.spine.OnPreUpdate += OnSolverUpdate;
|
||||
ik.solvers.rightFoot.OnPostUpdate += OnPostSolverUpdate;
|
||||
|
||||
// Store the default localPosition of the pelvis
|
||||
animatedPelvisLocalPosition = ik.references.pelvis.localPosition;
|
||||
|
||||
// Initiate the Grounding
|
||||
solver.Initiate(ik.references.root, feet);
|
||||
|
||||
initiated = true;
|
||||
}
|
||||
|
||||
// Weigh out the limb solvers properly when the component is disabled
|
||||
void OnDisable() {
|
||||
if (!initiated) return;
|
||||
|
||||
ik.solvers.leftFoot.IKPositionWeight = 0f;
|
||||
ik.solvers.rightFoot.IKPositionWeight = 0f;
|
||||
}
|
||||
|
||||
// Called before updating the spine IK solver
|
||||
private void OnSolverUpdate() {
|
||||
if (!enabled) return;
|
||||
|
||||
if (weight <= 0f) {
|
||||
if (lastWeight <= 0f) return;
|
||||
|
||||
// Weigh out the limb solvers properly
|
||||
OnDisable();
|
||||
}
|
||||
|
||||
lastWeight = weight;
|
||||
|
||||
if (OnPreGrounder != null) OnPreGrounder();
|
||||
|
||||
// If the pelvis local position has not changed since last solved state, consider it unanimated
|
||||
if (ik.references.pelvis.localPosition != solvedPelvisLocalPosition) animatedPelvisLocalPosition = ik.references.pelvis.localPosition;
|
||||
else ik.references.pelvis.localPosition = animatedPelvisLocalPosition;
|
||||
|
||||
// Update the Grounding
|
||||
solver.Update();
|
||||
|
||||
// Move the pelvis
|
||||
ik.references.pelvis.position += solver.pelvis.IKOffset * weight;
|
||||
|
||||
// Update IKPositions and IKPositionWeights of the feet
|
||||
SetLegIK(ik.solvers.leftFoot, 0);
|
||||
SetLegIK(ik.solvers.rightFoot, 1);
|
||||
|
||||
// Bending the spine
|
||||
if (spineBend != 0f && ik.references.spine.Length > 0) {
|
||||
spineSpeed = Mathf.Clamp(spineSpeed, 0f, spineSpeed);
|
||||
|
||||
Vector3 spineOffseTarget = GetSpineOffsetTarget() * weight;
|
||||
spineOffset = Vector3.Lerp(spineOffset, spineOffseTarget * spineBend, Time.deltaTime * spineSpeed);
|
||||
|
||||
// Store upper arm rotations to revert them after we rotate the spine
|
||||
Quaternion leftArmRotation = ik.references.leftUpperArm.rotation;
|
||||
Quaternion rightArmRotation = ik.references.rightUpperArm.rotation;
|
||||
|
||||
// Get the offset rotation for the spine
|
||||
Vector3 up = solver.up;
|
||||
Quaternion f = Quaternion.FromToRotation(up, up + spineOffset);
|
||||
|
||||
// Rotate the spine
|
||||
ik.references.spine[0].rotation = f * ik.references.spine[0].rotation;
|
||||
|
||||
// Revert the upper arms
|
||||
ik.references.leftUpperArm.rotation = leftArmRotation;
|
||||
ik.references.rightUpperArm.rotation = rightArmRotation;
|
||||
|
||||
ik.solvers.lookAt.SetDirty();
|
||||
}
|
||||
|
||||
if (OnPostGrounder != null) OnPostGrounder();
|
||||
}
|
||||
|
||||
// Set the IK position and weight for a limb
|
||||
private void SetLegIK(IKSolverLimb limb, int index) {
|
||||
footRotations[index] = feet[index].rotation;
|
||||
|
||||
limb.IKPosition = solver.legs[index].IKPosition;
|
||||
limb.IKPositionWeight = weight;
|
||||
}
|
||||
|
||||
// Rotating the feet after IK has finished
|
||||
private void OnPostSolverUpdate() {
|
||||
if (weight <= 0f) return;
|
||||
if (!enabled) return;
|
||||
|
||||
for (int i = 0; i < feet.Length; i++) {
|
||||
feet[i].rotation = Quaternion.Slerp(Quaternion.identity, solver.legs[i].rotationOffset, weight) * footRotations[i];
|
||||
}
|
||||
|
||||
// Store the local position of the pelvis so we know it it changes
|
||||
solvedPelvisLocalPosition = ik.references.pelvis.localPosition;
|
||||
|
||||
if (OnPostIK != null) OnPostIK();
|
||||
}
|
||||
|
||||
// Cleaning up the delegates
|
||||
void OnDestroy() {
|
||||
if (initiated && ik != null) {
|
||||
ik.solvers.spine.OnPreUpdate -= OnSolverUpdate;
|
||||
ik.solvers.rightFoot.OnPostUpdate -= OnPostSolverUpdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 391939450fd8044e3839af08f34c2af8
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: c0b08042695f548e79a4e5c84112fe52, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,215 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Grounding for FBBIK characters.
|
||||
/// </summary>
|
||||
[HelpURL("https://www.youtube.com/watch?v=9MiZiaJorws&index=6&list=PLVxSIA1OaTOu8Nos3CalXbJ2DrKnntMv6")]
|
||||
[AddComponentMenu("Scripts/RootMotion.FinalIK/Grounder/Grounder Full Body Biped")]
|
||||
public class GrounderFBBIK: Grounder {
|
||||
|
||||
// Open a video tutorial video
|
||||
[ContextMenu("TUTORIAL VIDEO")]
|
||||
void OpenTutorial() {
|
||||
Application.OpenURL("https://www.youtube.com/watch?v=9MiZiaJorws&index=6&list=PLVxSIA1OaTOu8Nos3CalXbJ2DrKnntMv6");
|
||||
}
|
||||
|
||||
// Open the User Manual URL
|
||||
[ContextMenu("User Manual")]
|
||||
protected override void OpenUserManual() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page9.html");
|
||||
}
|
||||
|
||||
// Open the Script Reference URL
|
||||
[ContextMenu("Scrpt Reference")]
|
||||
protected override void OpenScriptReference() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_grounder_f_b_b_i_k.html");
|
||||
}
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// Contains the bending weights for an effector.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class SpineEffector {
|
||||
/// <summary>
|
||||
/// The type of the effector.
|
||||
/// </summary>
|
||||
[Tooltip("The type of the effector.")]
|
||||
public FullBodyBipedEffector effectorType;
|
||||
/// <summary>
|
||||
/// The weight of horizontal bend offset towards the slope..
|
||||
/// </summary>
|
||||
[Tooltip("The weight of horizontal bend offset towards the slope.")]
|
||||
public float horizontalWeight = 1f;
|
||||
/// <summary>
|
||||
/// The vertical bend offset weight.
|
||||
/// </summary>
|
||||
[Tooltip("The vertical bend offset weight.")]
|
||||
public float verticalWeight;
|
||||
|
||||
public SpineEffector() {}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RootMotion.FinalIK.GrounderFBBIK+SpineEffector"/> class.
|
||||
/// </summary>
|
||||
/// <param name="effectorType">Effector type.</param>
|
||||
/// <param name="horizontalWeight">Horizontal weight.</param>
|
||||
/// <param name="verticalWeight">Vertical weight.</param>
|
||||
public SpineEffector(FullBodyBipedEffector effectorType, float horizontalWeight, float verticalWeight) {
|
||||
this.effectorType = effectorType;
|
||||
this.horizontalWeight = horizontalWeight;
|
||||
this.verticalWeight = verticalWeight;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reference to the FBBIK componet.
|
||||
/// </summary>
|
||||
[Tooltip("Reference to the FBBIK componet.")]
|
||||
public FullBodyBipedIK ik;
|
||||
/// <summary>
|
||||
/// The amount of spine bending towards upward slopes.
|
||||
/// </summary>
|
||||
[Tooltip("The amount of spine bending towards upward slopes.")]
|
||||
public float spineBend = 2f;
|
||||
/// <summary>
|
||||
/// The interpolation speed of spine bending.
|
||||
/// </summary>
|
||||
[Tooltip("The interpolation speed of spine bending.")]
|
||||
public float spineSpeed = 3f;
|
||||
/// <summary>
|
||||
/// The spine bending effectors.
|
||||
/// </summary>
|
||||
public SpineEffector[] spine = new SpineEffector[0];
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
public override void ResetPosition() {
|
||||
solver.Reset();
|
||||
spineOffset = Vector3.zero;
|
||||
}
|
||||
|
||||
public void Reinitiate()
|
||||
{
|
||||
initiated = false;
|
||||
firstSolve = false;
|
||||
}
|
||||
|
||||
private Transform[] feet = new Transform[2];
|
||||
private Vector3 spineOffset;
|
||||
private bool firstSolve;
|
||||
|
||||
// Can we initiate the Grounding?
|
||||
private bool IsReadyToInitiate() {
|
||||
if (ik == null) return false;
|
||||
if (!ik.solver.initiated) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Initiate once we have a FBBIK component
|
||||
void Update() {
|
||||
firstSolve = true;
|
||||
weight = Mathf.Clamp(weight, 0f, 1f);
|
||||
if (weight <= 0f) return;
|
||||
|
||||
if (initiated) return;
|
||||
if (!IsReadyToInitiate()) return;
|
||||
|
||||
Initiate();
|
||||
}
|
||||
|
||||
void FixedUpdate() {
|
||||
firstSolve = true;
|
||||
}
|
||||
|
||||
void LateUpdate() {
|
||||
firstSolve = true;
|
||||
}
|
||||
|
||||
private void Initiate () {
|
||||
// Set maintainRotationWeight to 1 for both limbs so their rotation will be maintained as animated
|
||||
ik.solver.leftLegMapping.maintainRotationWeight = 1f;
|
||||
ik.solver.rightLegMapping.maintainRotationWeight = 1f;
|
||||
|
||||
// Gathering both foot bones from the FBBIK
|
||||
feet = new Transform[2];
|
||||
feet[0] = ik.solver.leftFootEffector.bone;
|
||||
feet[1] = ik.solver.rightFootEffector.bone;
|
||||
|
||||
// Add to the FBBIK OnPreUpdate delegate to know when it solves
|
||||
ik.solver.OnPreUpdate += OnSolverUpdate;
|
||||
ik.solver.OnPostUpdate += OnPostSolverUpdate;
|
||||
|
||||
// Initiate Grounding
|
||||
solver.Initiate(ik.references.root, feet);
|
||||
|
||||
initiated = true;
|
||||
}
|
||||
|
||||
// Called before updating the main IK solver
|
||||
private void OnSolverUpdate() {
|
||||
if (!firstSolve) return;
|
||||
firstSolve = false;
|
||||
if (!enabled) return;
|
||||
if (weight <= 0f) return;
|
||||
|
||||
if (OnPreGrounder != null) OnPreGrounder();
|
||||
|
||||
solver.Update();
|
||||
|
||||
// Move the pelvis
|
||||
ik.references.pelvis.position += solver.pelvis.IKOffset * weight;
|
||||
|
||||
// Set effector positionOffsets for the feet
|
||||
SetLegIK(ik.solver.leftFootEffector, solver.legs[0]);
|
||||
SetLegIK(ik.solver.rightFootEffector, solver.legs[1]);
|
||||
|
||||
// Bending the spine
|
||||
if (spineBend != 0f) {
|
||||
spineSpeed = Mathf.Clamp(spineSpeed, 0f, spineSpeed);
|
||||
|
||||
Vector3 spineOffseTarget = GetSpineOffsetTarget() * weight;
|
||||
spineOffset = Vector3.Lerp(spineOffset, spineOffseTarget * spineBend, Time.deltaTime * spineSpeed);
|
||||
Vector3 verticalOffset = ik.references.root.up * spineOffset.magnitude;
|
||||
|
||||
for (int i = 0; i < spine.Length; i++) {
|
||||
ik.solver.GetEffector(spine[i].effectorType).positionOffset += (spineOffset * spine[i].horizontalWeight) + (verticalOffset * spine[i].verticalWeight);
|
||||
}
|
||||
}
|
||||
|
||||
if (OnPostGrounder != null) OnPostGrounder();
|
||||
}
|
||||
|
||||
// Set the effector positionOffset for the foot
|
||||
private void SetLegIK(IKEffector effector, Grounding.Leg leg) {
|
||||
effector.positionOffset += (leg.IKPosition - effector.bone.position) * weight;
|
||||
|
||||
effector.bone.rotation = Quaternion.Slerp(Quaternion.identity, leg.rotationOffset, weight) * effector.bone.rotation;
|
||||
}
|
||||
|
||||
// Auto-assign ik
|
||||
void OnDrawGizmosSelected() {
|
||||
if (ik == null) ik = GetComponent<FullBodyBipedIK>();
|
||||
if (ik == null) ik = GetComponentInParent<FullBodyBipedIK>();
|
||||
if (ik == null) ik = GetComponentInChildren<FullBodyBipedIK>();
|
||||
}
|
||||
|
||||
private void OnPostSolverUpdate()
|
||||
{
|
||||
if (OnPostIK != null) OnPostIK();
|
||||
}
|
||||
|
||||
// Cleaning up the delegate
|
||||
void OnDestroy() {
|
||||
if (initiated && ik != null)
|
||||
{
|
||||
ik.solver.OnPreUpdate -= OnSolverUpdate;
|
||||
ik.solver.OnPostUpdate -= OnPostSolverUpdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6c72e1df647af4c0098866e944a04b01
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 36056811931e14f20adcc2767d6262e7, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,280 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Grounding for LimbIK, CCD and/or FABRIK solvers.
|
||||
/// </summary>
|
||||
[HelpURL("http://www.root-motion.com/finalikdox/html/page9.html")]
|
||||
[AddComponentMenu("Scripts/RootMotion.FinalIK/Grounder/Grounder IK")]
|
||||
public class GrounderIK: Grounder {
|
||||
|
||||
// Open the User Manual URL
|
||||
[ContextMenu("User Manual")]
|
||||
protected override void OpenUserManual() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page9.html");
|
||||
}
|
||||
|
||||
// Open the Script Reference URL
|
||||
[ContextMenu("Scrpt Reference")]
|
||||
protected override void OpenScriptReference() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_grounder_i_k.html");
|
||||
}
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The leg %IK componets (can be any type of IK component).
|
||||
/// </summary>
|
||||
public IK[] legs;
|
||||
/// <summary>
|
||||
/// The pelvis transform. Common ancestor of all the legs.
|
||||
/// </summary>
|
||||
[Tooltip("The pelvis transform. Common ancestor of all the legs.")]
|
||||
public Transform pelvis;
|
||||
/// <summary>
|
||||
/// The root Transform of the character, with the rigidbody and the collider.
|
||||
/// </summary>
|
||||
[Tooltip("The root Transform of the character, with the rigidbody and the collider.")]
|
||||
public Transform characterRoot;
|
||||
/// <summary>
|
||||
/// The weight of rotating the character root to the ground normal (range: 0 - 1).
|
||||
/// </summary>
|
||||
[Tooltip("The weight of rotating the character root to the ground normal (range: 0 - 1).")]
|
||||
[Range(0f, 1f)]
|
||||
public float rootRotationWeight;
|
||||
/// <summary>
|
||||
/// The speed of rotating the character root to the ground normal (range: 0 - inf).
|
||||
/// </summary>
|
||||
[Tooltip("The speed of rotating the character root to the ground normal (range: 0 - inf).")]
|
||||
public float rootRotationSpeed = 5f;
|
||||
/// <summary>
|
||||
/// The maximum angle of root rotation (range: 0 - 90).
|
||||
/// </summary>
|
||||
[Tooltip("The maximum angle of root rotation (range: 0 - 90).")]
|
||||
public float maxRootRotationAngle = 45f;
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
public override void ResetPosition() {
|
||||
for (int i = 0; i < legs.Length; i++) {
|
||||
legs[i].GetIKSolver().IKPosition = feet[i].transform.position;
|
||||
if (legs[i] is LimbIK)
|
||||
{
|
||||
var leg = legs[i] as LimbIK;
|
||||
leg.solver.IKRotation = solver.legs[i].transform.rotation;
|
||||
}
|
||||
footRotations[i] = feet[i].rotation;
|
||||
}
|
||||
|
||||
animatedPelvisLocalPosition = pelvis.localPosition;
|
||||
solvedPelvisLocalPosition = pelvis.localPosition;
|
||||
|
||||
solver.Reset();
|
||||
}
|
||||
|
||||
private Transform[] feet = new Transform[0];
|
||||
private Quaternion[] footRotations = new Quaternion[0];
|
||||
private Vector3 animatedPelvisLocalPosition, solvedPelvisLocalPosition;
|
||||
private int solvedFeet;
|
||||
private bool solved;
|
||||
private float lastWeight;
|
||||
private Rigidbody characterRootRigidbody;
|
||||
|
||||
// Can we initiate the Grounding?
|
||||
private bool IsReadyToInitiate() {
|
||||
if (pelvis == null) return false;
|
||||
|
||||
if (legs.Length == 0) return false;
|
||||
|
||||
foreach (IK leg in legs) {
|
||||
if (leg == null) return false;
|
||||
|
||||
if (leg is FullBodyBipedIK) {
|
||||
LogWarning("GrounderIK does not support FullBodyBipedIK, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead. If you want to use FullBodyBipedIK, use the GrounderFBBIK component.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (leg is FABRIKRoot) {
|
||||
LogWarning("GrounderIK does not support FABRIKRoot, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (leg is AimIK) {
|
||||
LogWarning("GrounderIK does not support AimIK, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Weigh out the IK solvers properly when the component is disabled
|
||||
void OnDisable() {
|
||||
if (!initiated) return;
|
||||
|
||||
for (int i = 0; i < legs.Length; i++) {
|
||||
if (legs[i] != null) legs[i].GetIKSolver().IKPositionWeight = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Initiate once we have all the required components
|
||||
void Update() {
|
||||
weight = Mathf.Clamp(weight, 0f, 1f);
|
||||
if (weight <= 0f) return;
|
||||
|
||||
solved = false;
|
||||
|
||||
if (initiated) {
|
||||
// Clamping values
|
||||
rootRotationWeight = Mathf.Clamp(rootRotationWeight, 0f, 1f);
|
||||
rootRotationSpeed = Mathf.Clamp(rootRotationSpeed, 0f, rootRotationSpeed);
|
||||
|
||||
// Root rotation
|
||||
if (characterRoot != null && rootRotationSpeed > 0f && rootRotationWeight > 0f && solver.isGrounded) {
|
||||
Vector3 normal = solver.GetLegsPlaneNormal();
|
||||
|
||||
// Root rotation weight
|
||||
if (rootRotationWeight < 1f) {
|
||||
normal = Vector3.Slerp(Vector3.up, normal, rootRotationWeight);
|
||||
}
|
||||
|
||||
// Root rotation limit
|
||||
Quaternion upRotation = Quaternion.FromToRotation(transform.up, Vector3.up) * characterRoot.rotation;
|
||||
Quaternion rotationTarget = Quaternion.RotateTowards(upRotation, Quaternion.FromToRotation(transform.up, normal) * characterRoot.rotation, maxRootRotationAngle);
|
||||
|
||||
// Rotate the root
|
||||
if (characterRootRigidbody == null) {
|
||||
characterRoot.rotation = Quaternion.Lerp(characterRoot.rotation, rotationTarget, Time.deltaTime * rootRotationSpeed);
|
||||
} else {
|
||||
characterRootRigidbody.MoveRotation(Quaternion.Lerp(characterRoot.rotation, rotationTarget, Time.deltaTime * rootRotationSpeed));
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!IsReadyToInitiate()) return;
|
||||
|
||||
Initiate();
|
||||
}
|
||||
|
||||
private void Initiate() {
|
||||
// Building arrays
|
||||
feet = new Transform[legs.Length];
|
||||
footRotations = new Quaternion[legs.Length];
|
||||
|
||||
for (int i = 0; i < feet.Length; i++) footRotations[i] = Quaternion.identity;
|
||||
|
||||
// Gathering the last bones of the IK solvers as feet
|
||||
for (int i = 0; i < legs.Length; i++) {
|
||||
IKSolver.Point[] points = legs[i].GetIKSolver().GetPoints();
|
||||
|
||||
feet[i] = points[points.Length - 1].transform;
|
||||
|
||||
// Add to the update delegates of each ik solver
|
||||
legs[i].GetIKSolver().OnPreUpdate += OnSolverUpdate;
|
||||
legs[i].GetIKSolver().OnPostUpdate += OnPostSolverUpdate;
|
||||
}
|
||||
|
||||
// Store the default localPosition of the pelvis
|
||||
animatedPelvisLocalPosition = pelvis.localPosition;
|
||||
|
||||
// Initiate the Grounding
|
||||
solver.Initiate(transform, feet);
|
||||
|
||||
for (int i = 0; i < legs.Length; i++) {
|
||||
if (legs [i] is LegIK) {
|
||||
solver.legs[i].invertFootCenter = true;
|
||||
}
|
||||
}
|
||||
|
||||
characterRootRigidbody = characterRoot.GetComponent<Rigidbody>();
|
||||
|
||||
initiated = true;
|
||||
}
|
||||
|
||||
// Called before updating the first IK solver
|
||||
private void OnSolverUpdate() {
|
||||
if (!enabled) return;
|
||||
|
||||
if (weight <= 0f) {
|
||||
if (lastWeight <= 0f) return;
|
||||
|
||||
// Weigh out the limb solvers properly
|
||||
OnDisable();
|
||||
}
|
||||
|
||||
lastWeight = weight;
|
||||
|
||||
// If another IK has already solved in this frame, do nothing
|
||||
if (solved) return;
|
||||
|
||||
if (OnPreGrounder != null) OnPreGrounder();
|
||||
|
||||
// If the pelvis local position has not changed since last solved state, consider it unanimated
|
||||
if (pelvis.localPosition != solvedPelvisLocalPosition) animatedPelvisLocalPosition = pelvis.localPosition;
|
||||
else pelvis.localPosition = animatedPelvisLocalPosition;
|
||||
|
||||
// Update the Grounding
|
||||
solver.Update();
|
||||
|
||||
// Update the IKPositions and IKPositonWeights of the legs
|
||||
for (int i = 0; i < legs.Length; i++) SetLegIK(i);
|
||||
|
||||
// Move the pelvis
|
||||
pelvis.position += solver.pelvis.IKOffset * weight;
|
||||
|
||||
solved = true;
|
||||
solvedFeet = 0;
|
||||
|
||||
if (OnPostGrounder != null) OnPostGrounder();
|
||||
}
|
||||
|
||||
// Set the IK position and weight for a limb
|
||||
private void SetLegIK(int index) {
|
||||
footRotations[index] = feet[index].rotation;
|
||||
|
||||
if (legs [index] is LegIK) {
|
||||
(legs[index].GetIKSolver() as IKSolverLeg).IKRotation = Quaternion.Slerp(Quaternion.identity, solver.legs[index].rotationOffset, weight) * footRotations[index];
|
||||
(legs[index].GetIKSolver() as IKSolverLeg).IKRotationWeight = 1f;
|
||||
}
|
||||
|
||||
legs[index].GetIKSolver().IKPosition = solver.legs[index].IKPosition;
|
||||
legs[index].GetIKSolver().IKPositionWeight = weight;
|
||||
}
|
||||
|
||||
// Rotating the feet after IK has finished
|
||||
private void OnPostSolverUpdate() {
|
||||
if (weight <= 0f) return;
|
||||
if (!enabled) return;
|
||||
|
||||
// Only do this after the last IK solver has finished
|
||||
solvedFeet ++;
|
||||
if (solvedFeet < feet.Length) return;
|
||||
solved = false;
|
||||
|
||||
for (int i = 0; i < feet.Length; i++) {
|
||||
feet[i].rotation = Quaternion.Slerp(Quaternion.identity, solver.legs[i].rotationOffset, weight) * footRotations[i];
|
||||
}
|
||||
|
||||
// Store the local position of the pelvis so we know it it changes
|
||||
solvedPelvisLocalPosition = pelvis.localPosition;
|
||||
|
||||
if (OnPostIK != null) OnPostIK();
|
||||
}
|
||||
|
||||
// Cleaning up the delegates
|
||||
void OnDestroy() {
|
||||
if (initiated) {
|
||||
foreach (IK leg in legs) {
|
||||
if (leg != null) {
|
||||
leg.GetIKSolver().OnPreUpdate -= OnSolverUpdate;
|
||||
leg.GetIKSolver().OnPostUpdate -= OnPostSolverUpdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 65ace204533ef4c24ac80f11ef8ee8ea
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: 7c84c5721b6d74a15ab63fe46effb886, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,449 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Grounding for LimbIK, CCD and/or FABRIK solvers.
|
||||
/// </summary>
|
||||
[HelpURL("http://www.root-motion.com/finalikdox/html/page9.html")]
|
||||
[AddComponentMenu("Scripts/RootMotion.FinalIK/Grounder/Grounder Quadruped")]
|
||||
public class GrounderQuadruped: Grounder {
|
||||
|
||||
// Open the User Manual URL
|
||||
[ContextMenu("User Manual")]
|
||||
protected override void OpenUserManual() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/page9.html");
|
||||
}
|
||||
|
||||
// Open the Script Reference URL
|
||||
[ContextMenu("Scrpt Reference")]
|
||||
protected override void OpenScriptReference() {
|
||||
Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_grounder_quadruped.html");
|
||||
}
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The %Grounding solver for the forelegs.
|
||||
/// </summary>
|
||||
[Tooltip("The Grounding solver for the forelegs.")]
|
||||
public Grounding forelegSolver = new Grounding();
|
||||
/// <summary>
|
||||
/// The weight of rotating the character root to the ground angle (range: 0 - 1).
|
||||
/// </summary>
|
||||
[Tooltip("The weight of rotating the character root to the ground angle (range: 0 - 1).")]
|
||||
[Range(0f, 1f)]
|
||||
public float rootRotationWeight = 0.5f;
|
||||
/// <summary>
|
||||
/// The maximum angle of rotating the quadruped downwards (going downhill, range: -90 - 0).
|
||||
/// </summary>
|
||||
[Tooltip("The maximum angle of rotating the quadruped downwards (going downhill, range: -90 - 0).")]
|
||||
[Range(-90f, 0f)]
|
||||
public float minRootRotation = -25f;
|
||||
/// <summary>
|
||||
/// The maximum angle of rotating the quadruped upwards (going uphill, range: 0 - 90).
|
||||
/// </summary>
|
||||
[Tooltip("The maximum angle of rotating the quadruped upwards (going uphill, range: 0 - 90).")]
|
||||
[Range(0f, 90f)]
|
||||
public float maxRootRotation = 45f;
|
||||
/// <summary>
|
||||
/// The speed of interpolating the character root rotation (range: 0 - inf).
|
||||
/// </summary>
|
||||
[Tooltip("The speed of interpolating the character root rotation (range: 0 - inf).")]
|
||||
public float rootRotationSpeed = 5f;
|
||||
/// <summary>
|
||||
/// The maximum IK offset for the legs (range: 0 - inf).
|
||||
/// </summary>
|
||||
[Tooltip("The maximum IK offset for the legs (range: 0 - inf).")]
|
||||
public float maxLegOffset = 0.5f;
|
||||
/// <summary>
|
||||
/// The maximum IK offset for the forelegs (range: 0 - inf).
|
||||
/// </summary>
|
||||
[Tooltip("The maximum IK offset for the forelegs (range: 0 - inf).")]
|
||||
public float maxForeLegOffset = 0.5f;
|
||||
/// <summary>
|
||||
/// The weight of maintaining the head's rotation as it was before solving the Grounding (range: 0 - 1).
|
||||
/// </summary>
|
||||
[Tooltip("The weight of maintaining the head's rotation as it was before solving the Grounding (range: 0 - 1).")]
|
||||
[Range(0f, 1f)]
|
||||
public float maintainHeadRotationWeight = 0.5f;
|
||||
/// <summary>
|
||||
/// The root Transform of the character, with the rigidbody and the collider.
|
||||
/// </summary>
|
||||
[Tooltip("The root Transform of the character, with the rigidbody and the collider.")]
|
||||
public Transform characterRoot;
|
||||
/// <summary>
|
||||
/// The pelvis transform. Common ancestor of both legs and the spine.
|
||||
/// </summary>
|
||||
[Tooltip("The pelvis transform. Common ancestor of both legs and the spine.")]
|
||||
public Transform pelvis;
|
||||
/// <summary>
|
||||
/// The last bone in the spine that is the common parent for both forelegs.
|
||||
/// </summary>
|
||||
[Tooltip("The last bone in the spine that is the common parent for both forelegs.")]
|
||||
public Transform lastSpineBone;
|
||||
/// <summary>
|
||||
/// The head (optional, if you intend to maintain its rotation).
|
||||
/// </summary>
|
||||
[Tooltip("The head (optional, if you intend to maintain its rotation).")]
|
||||
public Transform head;
|
||||
/// <summary>
|
||||
/// %IK componets of the hindlegs. Can be any type of IK components.
|
||||
/// </summary>
|
||||
public IK[] legs;
|
||||
/// <summary>
|
||||
/// %IK components for the forelegs. Can be any type of IK components.
|
||||
/// </summary>
|
||||
public IK[] forelegs;
|
||||
/// <summary>
|
||||
/// When using GrounderQuadruped on a spherical object, update this vector to always point towards the center of that object.
|
||||
/// </summary>
|
||||
[HideInInspector] public Vector3 gravity = Vector3.down;
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
public override void ResetPosition() {
|
||||
for (int i = 0; i < legs.Length; i++)
|
||||
{
|
||||
legs[i].GetIKSolver().IKPosition = feet[i].transform.position;
|
||||
if (legs[i] is LimbIK)
|
||||
{
|
||||
var leg = legs[i] as LimbIK;
|
||||
leg.solver.IKRotation = solver.legs[i].transform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
solver.Reset();
|
||||
forelegSolver.Reset();
|
||||
}
|
||||
|
||||
// Contains all the required information about a foot
|
||||
public struct Foot {
|
||||
public IKSolver solver;
|
||||
public Transform transform;
|
||||
public Quaternion rotation;
|
||||
public Grounding.Leg leg;
|
||||
|
||||
// The custom constructor
|
||||
public Foot (IKSolver solver, Transform transform) {
|
||||
this.solver = solver;
|
||||
this.transform = transform;
|
||||
this.leg = null;
|
||||
rotation = transform.rotation;
|
||||
}
|
||||
}
|
||||
|
||||
private Foot[] feet = new Foot[0];
|
||||
private Vector3 animatedPelvisLocalPosition;
|
||||
private Quaternion animatedPelvisLocalRotation;
|
||||
private Quaternion animatedHeadLocalRotation;
|
||||
private Vector3 solvedPelvisLocalPosition;
|
||||
private Quaternion solvedPelvisLocalRotation;
|
||||
private Quaternion solvedHeadLocalRotation;
|
||||
private int solvedFeet;
|
||||
private bool solved;
|
||||
private float angle;
|
||||
private Transform forefeetRoot;
|
||||
private Quaternion headRotation;
|
||||
private float lastWeight;
|
||||
private Rigidbody characterRootRigidbody;
|
||||
|
||||
// Can we initiate the Grounding?
|
||||
private bool IsReadyToInitiate() {
|
||||
if (pelvis == null) return false;
|
||||
if (lastSpineBone == null) return false;
|
||||
|
||||
if (legs.Length == 0) return false;
|
||||
if (forelegs.Length == 0) return false;
|
||||
|
||||
if (characterRoot == null) return false;
|
||||
|
||||
if (!IsReadyToInitiateLegs(legs)) return false;
|
||||
if (!IsReadyToInitiateLegs(forelegs)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Are the leg IK components valid for initiation?
|
||||
private bool IsReadyToInitiateLegs(IK[] ikComponents) {
|
||||
foreach (IK leg in ikComponents) {
|
||||
if (leg == null) return false;
|
||||
|
||||
if (leg is FullBodyBipedIK) {
|
||||
LogWarning("GrounderIK does not support FullBodyBipedIK, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead. If you want to use FullBodyBipedIK, use the GrounderFBBIK component.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (leg is FABRIKRoot) {
|
||||
LogWarning("GrounderIK does not support FABRIKRoot, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (leg is AimIK) {
|
||||
LogWarning("GrounderIK does not support AimIK, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Weigh out the IK solvers properly when the component is disabled
|
||||
void OnDisable() {
|
||||
if (!initiated) return;
|
||||
|
||||
for (int i = 0; i < feet.Length; i++) {
|
||||
if (feet[i].solver != null) feet[i].solver.IKPositionWeight = 0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Initiate once we have all the required components
|
||||
void Update() {
|
||||
weight = Mathf.Clamp(weight, 0f, 1f);
|
||||
if (weight <= 0f) return;
|
||||
|
||||
solved = false;
|
||||
|
||||
if (initiated) return;
|
||||
if (!IsReadyToInitiate()) return;
|
||||
|
||||
Initiate();
|
||||
}
|
||||
|
||||
// Initiate this Grounder
|
||||
private void Initiate() {
|
||||
// Building the feet
|
||||
feet = new Foot[legs.Length + forelegs.Length];
|
||||
|
||||
// Gathering the last bones of the IK solvers as feet
|
||||
Transform[] footBones = InitiateFeet(legs, ref feet, 0);
|
||||
Transform[] forefootBones = InitiateFeet(forelegs, ref feet, legs.Length);
|
||||
|
||||
// Store the default localPosition and localRotation of the pelvis
|
||||
animatedPelvisLocalPosition = pelvis.localPosition;
|
||||
animatedPelvisLocalRotation = pelvis.localRotation;
|
||||
if (head != null) animatedHeadLocalRotation = head.localRotation;
|
||||
|
||||
forefeetRoot = new GameObject().transform;
|
||||
forefeetRoot.parent = transform;
|
||||
forefeetRoot.name = "Forefeet Root";
|
||||
|
||||
// Initiate the Grounding
|
||||
solver.Initiate(transform, footBones);
|
||||
forelegSolver.Initiate(forefeetRoot, forefootBones);
|
||||
|
||||
for (int i = 0; i < footBones.Length; i++) feet[i].leg = solver.legs[i];
|
||||
for (int i = 0; i < forefootBones.Length; i++) feet[i + legs.Length].leg = forelegSolver.legs[i];
|
||||
|
||||
characterRootRigidbody = characterRoot.GetComponent<Rigidbody>();
|
||||
|
||||
initiated = true;
|
||||
}
|
||||
|
||||
// Initiate the feet
|
||||
private Transform[] InitiateFeet(IK[] ikComponents, ref Foot[] f, int indexOffset) {
|
||||
Transform[] bones = new Transform[ikComponents.Length];
|
||||
|
||||
for (int i = 0; i < ikComponents.Length; i++) {
|
||||
IKSolver.Point[] points = ikComponents[i].GetIKSolver().GetPoints();
|
||||
|
||||
f[i + indexOffset] = new Foot(ikComponents[i].GetIKSolver(), points[points.Length - 1].transform);
|
||||
bones[i] = f[i + indexOffset].transform;
|
||||
|
||||
// Add to the update delegates of each ik solver
|
||||
f[i + indexOffset].solver.OnPreUpdate += OnSolverUpdate;
|
||||
f[i + indexOffset].solver.OnPostUpdate += OnPostSolverUpdate;
|
||||
}
|
||||
|
||||
return bones;
|
||||
}
|
||||
|
||||
void LateUpdate () {
|
||||
if (weight <= 0f) return;
|
||||
|
||||
// Clamping values
|
||||
rootRotationWeight = Mathf.Clamp(rootRotationWeight, 0f, 1f);
|
||||
minRootRotation = Mathf.Clamp(minRootRotation, -90f, maxRootRotation);
|
||||
maxRootRotation = Mathf.Clamp(maxRootRotation, minRootRotation, 90f);
|
||||
rootRotationSpeed = Mathf.Clamp(rootRotationSpeed, 0f, rootRotationSpeed);
|
||||
maxLegOffset = Mathf.Clamp(maxLegOffset, 0f, maxLegOffset);
|
||||
maxForeLegOffset = Mathf.Clamp(maxForeLegOffset, 0f, maxForeLegOffset);
|
||||
maintainHeadRotationWeight = Mathf.Clamp(maintainHeadRotationWeight, 0f, 1f);
|
||||
|
||||
// Rotate the character root
|
||||
RootRotation();
|
||||
}
|
||||
|
||||
// Rotate the character along with the terrain
|
||||
private void RootRotation() {
|
||||
if (rootRotationWeight <= 0f) return;
|
||||
if (rootRotationSpeed <= 0f) return;
|
||||
|
||||
solver.rotateSolver = true;
|
||||
forelegSolver.rotateSolver = true;
|
||||
|
||||
// Get the horizontal rotation of the character
|
||||
Vector3 tangent = characterRoot.forward;
|
||||
|
||||
Vector3 normal = -gravity;
|
||||
Vector3.OrthoNormalize(ref normal, ref tangent);
|
||||
Quaternion horizontalRotation = Quaternion.LookRotation(tangent, -gravity);
|
||||
|
||||
// Get the direction from root hit to forelegs root hit in the space of the horizontal character rotation
|
||||
Vector3 hitDirection = forelegSolver.rootHit.point - solver.rootHit.point;
|
||||
Vector3 hitDirectionLocal = Quaternion.Inverse(horizontalRotation) * hitDirection;
|
||||
|
||||
// Get the angle between the horizontal and hit directions
|
||||
float angleTarget = Mathf.Atan2(hitDirectionLocal.y, hitDirectionLocal.z) * Mathf.Rad2Deg;
|
||||
angleTarget = Mathf.Clamp(angleTarget * rootRotationWeight, minRootRotation, maxRootRotation);
|
||||
|
||||
// Interpolate the angle
|
||||
angle = Mathf.Lerp(angle, angleTarget, Time.deltaTime * rootRotationSpeed);
|
||||
|
||||
if (characterRootRigidbody == null) {
|
||||
characterRoot.rotation = Quaternion.Slerp(characterRoot.rotation, Quaternion.AngleAxis(-angle, characterRoot.right) * horizontalRotation, weight);
|
||||
} else {
|
||||
characterRootRigidbody.MoveRotation(Quaternion.Slerp(characterRoot.rotation, Quaternion.AngleAxis(-angle, characterRoot.right) * horizontalRotation, weight));
|
||||
}
|
||||
}
|
||||
|
||||
// Called before updating the first IK solver
|
||||
private void OnSolverUpdate() {
|
||||
if (!enabled) return;
|
||||
|
||||
if (weight <= 0f) {
|
||||
if (lastWeight <= 0f) return;
|
||||
|
||||
// Weigh out the limb solvers properly
|
||||
OnDisable();
|
||||
}
|
||||
|
||||
lastWeight = weight;
|
||||
|
||||
// If another IK has already solved in this frame, do nothing
|
||||
if (solved) return;
|
||||
|
||||
if (OnPreGrounder != null) OnPreGrounder();
|
||||
|
||||
// If the bone transforms have not changed since last solved state, consider them unanimated
|
||||
if (pelvis.localPosition != solvedPelvisLocalPosition) animatedPelvisLocalPosition = pelvis.localPosition;
|
||||
else pelvis.localPosition = animatedPelvisLocalPosition;
|
||||
|
||||
if (pelvis.localRotation != solvedPelvisLocalRotation) animatedPelvisLocalRotation = pelvis.localRotation;
|
||||
else pelvis.localRotation = animatedPelvisLocalRotation;
|
||||
|
||||
if (head != null) {
|
||||
if (head.localRotation != solvedHeadLocalRotation) animatedHeadLocalRotation = head.localRotation;
|
||||
else head.localRotation = animatedHeadLocalRotation;
|
||||
}
|
||||
|
||||
for (int i = 0; i < feet.Length; i++) feet[i].rotation = feet[i].transform.rotation;
|
||||
|
||||
// Store the head rotation so it could be maintained later
|
||||
if (head != null) headRotation = head.rotation;
|
||||
|
||||
// Position the forefeet root to the center of forefeet
|
||||
UpdateForefeetRoot();
|
||||
|
||||
// Update the Grounding
|
||||
solver.Update();
|
||||
forelegSolver.Update();
|
||||
|
||||
// Move the pelvis
|
||||
pelvis.position += solver.pelvis.IKOffset * weight;
|
||||
|
||||
// Rotate the pelvis
|
||||
Vector3 spineDirection = lastSpineBone.position - pelvis.position;
|
||||
|
||||
Vector3 newSpinePosition =
|
||||
lastSpineBone.position +
|
||||
forelegSolver.root.up * Mathf.Clamp(forelegSolver.pelvis.heightOffset, Mathf.NegativeInfinity, 0f) -
|
||||
solver.root.up * solver.pelvis.heightOffset;
|
||||
|
||||
Vector3 newDirection = newSpinePosition - pelvis.position;
|
||||
|
||||
Quaternion f = Quaternion.FromToRotation(spineDirection, newDirection);
|
||||
pelvis.rotation = Quaternion.Slerp(Quaternion.identity, f, weight) * pelvis.rotation;
|
||||
|
||||
// Update the IKPositions and IKPositonWeights of the legs
|
||||
for (int i = 0; i < feet.Length; i++) SetFootIK(feet[i], (i < 2? maxLegOffset: maxForeLegOffset));
|
||||
|
||||
solved = true;
|
||||
solvedFeet = 0;
|
||||
|
||||
if (OnPostGrounder != null) OnPostGrounder();
|
||||
}
|
||||
|
||||
// Position the forefeet root to the center of forefeet
|
||||
private void UpdateForefeetRoot() {
|
||||
// Get the centroid
|
||||
Vector3 foreFeetCenter = Vector3.zero;
|
||||
|
||||
for (int i = 0; i < forelegSolver.legs.Length; i++) {
|
||||
foreFeetCenter += forelegSolver.legs[i].transform.position;
|
||||
}
|
||||
|
||||
foreFeetCenter /= (float)forelegs.Length;
|
||||
Vector3 dir = foreFeetCenter - transform.position;
|
||||
|
||||
// Ortho-normalize to this Transform's rotation
|
||||
Vector3 normal = transform.up;
|
||||
Vector3 tangent = dir;
|
||||
Vector3.OrthoNormalize(ref normal, ref tangent);
|
||||
|
||||
// Positioning the forefeet root
|
||||
forefeetRoot.position = transform.position + tangent.normalized * dir.magnitude;
|
||||
}
|
||||
|
||||
// Set the IK position and weight for a limb
|
||||
private void SetFootIK(Foot foot, float maxOffset) {
|
||||
Vector3 direction = foot.leg.IKPosition - foot.transform.position;
|
||||
|
||||
foot.solver.IKPosition = foot.transform.position + Vector3.ClampMagnitude(direction, maxOffset);
|
||||
foot.solver.IKPositionWeight = weight;
|
||||
}
|
||||
|
||||
// Rotating the feet after IK has finished
|
||||
private void OnPostSolverUpdate() {
|
||||
if (weight <= 0f) return;
|
||||
if (!enabled) return;
|
||||
|
||||
// Only do this after the last IK solver has finished
|
||||
solvedFeet ++;
|
||||
if (solvedFeet < feet.Length) return;
|
||||
|
||||
for (int i = 0; i < feet.Length; i++) {
|
||||
feet[i].transform.rotation = Quaternion.Slerp(Quaternion.identity, feet[i].leg.rotationOffset, weight) * feet[i].rotation;
|
||||
}
|
||||
|
||||
if (head != null) head.rotation = Quaternion.Lerp(head.rotation, headRotation, maintainHeadRotationWeight * weight);
|
||||
|
||||
// Store the solved transform's of the bones so we know if they are not animated
|
||||
solvedPelvisLocalPosition = pelvis.localPosition;
|
||||
solvedPelvisLocalRotation = pelvis.localRotation;
|
||||
if (head != null) solvedHeadLocalRotation = head.localRotation;
|
||||
|
||||
if (OnPostIK != null) OnPostIK();
|
||||
}
|
||||
|
||||
// Cleaning up the delegates
|
||||
void OnDestroy() {
|
||||
if (initiated) {
|
||||
DestroyLegs(legs);
|
||||
DestroyLegs(forelegs);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleaning up the delegates
|
||||
private void DestroyLegs(IK[] ikComponents) {
|
||||
foreach (IK leg in ikComponents) {
|
||||
if (leg != null) {
|
||||
leg.GetIKSolver().OnPreUpdate -= OnSolverUpdate;
|
||||
leg.GetIKSolver().OnPostUpdate -= OnPostSolverUpdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9823e47edf1dd40c29dfe0ba019f33a6
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {fileID: 2800000, guid: c92822fd107844dccaf6c30c024e5806, type: 3}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,364 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
/// <summary>
|
||||
/// Foot placement system.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public partial class Grounding {
|
||||
|
||||
#region Main Interface
|
||||
|
||||
/// <summary>
|
||||
/// The raycasting quality. Fastest is a single raycast per foot, Simple is three raycasts, Best is one raycast and a capsule cast per foot.
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public enum Quality {
|
||||
Fastest,
|
||||
Simple,
|
||||
Best
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Layers to ground the character to. Make sure to exclude the layer of the character controller.
|
||||
/// </summary>
|
||||
[Tooltip("Layers to ground the character to. Make sure to exclude the layer of the character controller.")]
|
||||
public LayerMask layers;
|
||||
/// <summary>
|
||||
/// Max step height. Maximum vertical distance of Grounding from the root of the character.
|
||||
/// </summary>
|
||||
[Tooltip("Max step height. Maximum vertical distance of Grounding from the root of the character.")]
|
||||
public float maxStep = 0.5f;
|
||||
/// <summary>
|
||||
/// The height offset of the root.
|
||||
/// </summary>
|
||||
[Tooltip("The height offset of the root.")]
|
||||
public float heightOffset;
|
||||
/// <summary>
|
||||
/// The speed of moving the feet up/down.
|
||||
/// </summary>
|
||||
[Tooltip("The speed of moving the feet up/down.")]
|
||||
public float footSpeed = 2.5f;
|
||||
/// <summary>
|
||||
/// CapsuleCast radius. Should match approximately with the size of the feet.
|
||||
/// </summary>
|
||||
[Tooltip("CapsuleCast radius. Should match approximately with the size of the feet.")]
|
||||
public float footRadius = 0.15f;
|
||||
/// <summary>
|
||||
/// Offset of the foot center along character forward axis.
|
||||
/// </summary>
|
||||
[Tooltip("Offset of the foot center along character forward axis.")]
|
||||
[HideInInspector] public float footCenterOffset; // TODO make visible in inspector if Grounder Visualization is finished.
|
||||
/// <summary>
|
||||
/// Amount of velocity based prediction of the foot positions.
|
||||
/// </summary>
|
||||
[Tooltip("Amount of velocity based prediction of the foot positions.")]
|
||||
public float prediction = 0.05f;
|
||||
/// <summary>
|
||||
/// Weight of rotating the feet to the ground normal offset.
|
||||
/// </summary>
|
||||
[Tooltip("Weight of rotating the feet to the ground normal offset.")]
|
||||
[Range(0f, 1f)]
|
||||
public float footRotationWeight = 1f;
|
||||
/// <summary>
|
||||
/// Speed of slerping the feet to their grounded rotations.
|
||||
/// </summary>
|
||||
[Tooltip("Speed of slerping the feet to their grounded rotations.")]
|
||||
public float footRotationSpeed = 7f;
|
||||
/// <summary>
|
||||
/// Max Foot Rotation Angle, Max angular offset from the foot's rotation (Reasonable range: 0-90 degrees).
|
||||
/// </summary>
|
||||
[Tooltip("Max Foot Rotation Angle. Max angular offset from the foot's rotation.")]
|
||||
[Range(0f, 90f)]
|
||||
public float maxFootRotationAngle = 45f;
|
||||
/// <summary>
|
||||
/// If true, solver will rotate with the character root so the character can be grounded for example to spherical planets.
|
||||
/// For performance reasons leave this off unless needed.
|
||||
/// </summary>
|
||||
[Tooltip("If true, solver will rotate with the character root so the character can be grounded for example to spherical planets. For performance reasons leave this off unless needed.")]
|
||||
public bool rotateSolver;
|
||||
/// <summary>
|
||||
/// The speed of moving the character up/down.
|
||||
/// </summary>
|
||||
[Tooltip("The speed of moving the character up/down.")]
|
||||
public float pelvisSpeed = 5f;
|
||||
/// <summary>
|
||||
/// Used for smoothing out vertical pelvis movement (range 0 - 1).
|
||||
/// </summary>
|
||||
[Tooltip("Used for smoothing out vertical pelvis movement (range 0 - 1).")]
|
||||
[Range(0f, 1f)]
|
||||
public float pelvisDamper;
|
||||
/// <summary>
|
||||
/// The weight of lowering the pelvis to the lowest foot.
|
||||
/// </summary>
|
||||
[Tooltip("The weight of lowering the pelvis to the lowest foot.")]
|
||||
public float lowerPelvisWeight = 1f;
|
||||
/// <summary>
|
||||
/// The weight of lifting the pelvis to the highest foot. This is useful when you don't want the feet to go too high relative to the body when crouching.
|
||||
/// </summary>
|
||||
[Tooltip("The weight of lifting the pelvis to the highest foot. This is useful when you don't want the feet to go too high relative to the body when crouching.")]
|
||||
public float liftPelvisWeight;
|
||||
/// <summary>
|
||||
/// The radius of the spherecast from the root that determines whether the character root is grounded.
|
||||
/// </summary>
|
||||
[Tooltip("The radius of the spherecast from the root that determines whether the character root is grounded.")]
|
||||
public float rootSphereCastRadius = 0.1f;
|
||||
/// <summary>
|
||||
/// If false, keeps the foot that is over a ledge at the root level. If true, lowers the overstepping foot and body by the 'Max Step' value.
|
||||
/// </summary>
|
||||
[Tooltip("If false, keeps the foot that is over a ledge at the root level. If true, lowers the overstepping foot and body by the 'Max Step' value.")]
|
||||
public bool overstepFallsDown = true;
|
||||
/// <summary>
|
||||
/// The raycasting quality. Fastest is a single raycast per foot, Simple is three raycasts, Best is one raycast and a capsule cast per foot.
|
||||
/// </summary>
|
||||
[Tooltip("The raycasting quality. Fastest is a single raycast per foot, Simple is three raycasts, Best is one raycast and a capsule cast per foot.")]
|
||||
public Quality quality = Quality.Best;
|
||||
|
||||
/// <summary>
|
||||
/// The %Grounding legs.
|
||||
/// </summary>
|
||||
public Leg[] legs { get; private set; }
|
||||
/// <summary>
|
||||
/// The %Grounding pelvis.
|
||||
/// </summary>
|
||||
public Pelvis pelvis { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether any of the legs are grounded
|
||||
/// </summary>
|
||||
public bool isGrounded { get; private set; }
|
||||
/// <summary>
|
||||
/// The root Transform
|
||||
/// </summary>
|
||||
public Transform root { get; private set; }
|
||||
/// <summary>
|
||||
/// Ground height at the root position.
|
||||
/// </summary>
|
||||
public RaycastHit rootHit { get; private set; }
|
||||
/// <summary>
|
||||
/// Is the RaycastHit from the root grounded?
|
||||
/// </summary>
|
||||
public bool rootGrounded {
|
||||
get {
|
||||
return rootHit.distance < maxStep * 2f;
|
||||
}
|
||||
}
|
||||
|
||||
// For overriding ray/capsule/sphere casting functions
|
||||
public delegate bool OnRaycastDelegate(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);
|
||||
public OnRaycastDelegate Raycast = Physics.Raycast;
|
||||
|
||||
public delegate bool OnCapsuleCastDelegate(Vector3 point1, Vector3 point2, float radius, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);
|
||||
public OnCapsuleCastDelegate CapsuleCast = Physics.CapsuleCast;
|
||||
|
||||
public delegate bool OnSphereCastDelegate(Vector3 origin, float radius, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);
|
||||
public OnSphereCastDelegate SphereCast = Physics.SphereCast;
|
||||
|
||||
/// <summary>
|
||||
/// Raycasts or sphereCasts to find the root ground point. Distance of the Ray/Sphere cast is maxDistanceMlp x maxStep. Use this instead of rootHit if the Grounder is weighed out/disabled and not updated.
|
||||
/// </summary>
|
||||
public RaycastHit GetRootHit(float maxDistanceMlp = 10f) {
|
||||
RaycastHit h = new RaycastHit();
|
||||
Vector3 _up = up;
|
||||
|
||||
Vector3 legsCenter = Vector3.zero;
|
||||
foreach (Leg leg in legs) legsCenter += leg.transform.position;
|
||||
legsCenter /= (float)legs.Length;
|
||||
|
||||
h.point = legsCenter - _up * maxStep * 10f;
|
||||
float distMlp = maxDistanceMlp + 1;
|
||||
h.distance = maxStep * distMlp;
|
||||
|
||||
if (maxStep <= 0f) return h;
|
||||
|
||||
if (quality != Quality.Best) Raycast(legsCenter + _up * maxStep, -_up, out h, maxStep * distMlp, layers, QueryTriggerInteraction.Ignore);
|
||||
else SphereCast(legsCenter + _up * maxStep, rootSphereCastRadius, -up, out h, maxStep * distMlp, layers, QueryTriggerInteraction.Ignore);
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this <see cref="Grounding"/> is valid.
|
||||
/// </summary>
|
||||
public bool IsValid(ref string errorMessage) {
|
||||
if (root == null) {
|
||||
errorMessage = "Root transform is null. Can't initiate Grounding.";
|
||||
return false;
|
||||
}
|
||||
if (legs == null) {
|
||||
errorMessage = "Grounding legs is null. Can't initiate Grounding.";
|
||||
return false;
|
||||
}
|
||||
if (pelvis == null) {
|
||||
errorMessage = "Grounding pelvis is null. Can't initiate Grounding.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (legs.Length == 0) {
|
||||
errorMessage = "Grounding has 0 legs. Can't initiate Grounding.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initiate the %Grounding as an integrated solver by providing the root Transform, leg solvers, pelvis Transform and spine solver.
|
||||
/// </summary>
|
||||
public void Initiate(Transform root, Transform[] feet) {
|
||||
this.root = root;
|
||||
initiated = false;
|
||||
|
||||
rootHit = new RaycastHit();
|
||||
|
||||
// Constructing Legs
|
||||
if (legs == null) legs = new Leg[feet.Length];
|
||||
if (legs.Length != feet.Length) legs = new Leg[feet.Length];
|
||||
for (int i = 0; i < feet.Length; i++) if (legs[i] == null) legs[i] = new Leg();
|
||||
|
||||
// Constructing pelvis
|
||||
if (pelvis == null) pelvis = new Pelvis();
|
||||
|
||||
string errorMessage = string.Empty;
|
||||
if (!IsValid(ref errorMessage)) {
|
||||
Warning.Log(errorMessage, root, false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Initiate solvers only if application is playing
|
||||
if (Application.isPlaying) {
|
||||
for (int i = 0; i < feet.Length; i++) legs[i].Initiate(this, feet[i]);
|
||||
pelvis.Initiate(this);
|
||||
|
||||
initiated = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the Grounding.
|
||||
/// </summary>
|
||||
public void Update() {
|
||||
if (!initiated) return;
|
||||
|
||||
if (layers == 0) LogWarning("Grounding layers are set to nothing. Please add a ground layer.");
|
||||
|
||||
maxStep = Mathf.Clamp(maxStep, 0f, maxStep);
|
||||
footRadius = Mathf.Clamp(footRadius, 0.0001f, maxStep);
|
||||
pelvisDamper = Mathf.Clamp(pelvisDamper, 0f, 1f);
|
||||
rootSphereCastRadius = Mathf.Clamp(rootSphereCastRadius, 0.0001f, rootSphereCastRadius);
|
||||
maxFootRotationAngle = Mathf.Clamp(maxFootRotationAngle, 0f, 90f);
|
||||
prediction = Mathf.Clamp(prediction, 0f, prediction);
|
||||
footSpeed = Mathf.Clamp(footSpeed, 0f, footSpeed);
|
||||
|
||||
// Root hit
|
||||
rootHit = GetRootHit();
|
||||
|
||||
float lowestOffset = Mathf.NegativeInfinity;
|
||||
float highestOffset = Mathf.Infinity;
|
||||
isGrounded = false;
|
||||
|
||||
// Process legs
|
||||
foreach (Leg leg in legs) {
|
||||
leg.Process();
|
||||
|
||||
if (leg.IKOffset > lowestOffset) lowestOffset = leg.IKOffset;
|
||||
if (leg.IKOffset < highestOffset) highestOffset = leg.IKOffset;
|
||||
|
||||
if (leg.isGrounded) isGrounded = true;
|
||||
}
|
||||
|
||||
// Precess pelvis
|
||||
lowestOffset = Mathf.Max(lowestOffset, 0f);
|
||||
highestOffset = Mathf.Min(highestOffset, 0f);
|
||||
pelvis.Process(-lowestOffset * lowerPelvisWeight, -highestOffset * liftPelvisWeight, isGrounded);
|
||||
}
|
||||
|
||||
// Calculate the normal of the plane defined by leg positions, so we know how to rotate the body
|
||||
public Vector3 GetLegsPlaneNormal() {
|
||||
if (!initiated) return Vector3.up;
|
||||
|
||||
Vector3 _up = up;
|
||||
Vector3 normal = _up;
|
||||
|
||||
// Go through all the legs, rotate the normal by its offset
|
||||
for (int i = 0; i < legs.Length; i++) {
|
||||
// Direction from the root to the leg
|
||||
Vector3 legDirection = legs[i].IKPosition - root.position;
|
||||
|
||||
// Find the tangent
|
||||
Vector3 legNormal = _up;
|
||||
Vector3 legTangent = legDirection;
|
||||
Vector3.OrthoNormalize(ref legNormal, ref legTangent);
|
||||
|
||||
// Find the rotation offset from the tangent to the direction
|
||||
Quaternion fromTo = Quaternion.FromToRotation(legTangent, legDirection);
|
||||
|
||||
// Rotate the normal
|
||||
normal = fromTo * normal;
|
||||
}
|
||||
|
||||
return normal;
|
||||
}
|
||||
|
||||
// Set everything to 0
|
||||
public void Reset() {
|
||||
if (!Application.isPlaying) return;
|
||||
pelvis.Reset();
|
||||
foreach (Leg leg in legs) leg.Reset();
|
||||
}
|
||||
|
||||
#endregion Main Interface
|
||||
|
||||
private bool initiated;
|
||||
|
||||
// Logs the warning if no other warning has beed logged in this session.
|
||||
public void LogWarning(string message) {
|
||||
Warning.Log(message, root);
|
||||
}
|
||||
|
||||
// The up vector in solver rotation space.
|
||||
public Vector3 up {
|
||||
get {
|
||||
return (useRootRotation? root.up: Vector3.up);
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the vertical offset between two vectors in solver rotation space
|
||||
public float GetVerticalOffset(Vector3 p1, Vector3 p2) {
|
||||
if (useRootRotation) {
|
||||
Vector3 v = Quaternion.Inverse(root.rotation) * (p1 - p2);
|
||||
return v.y;
|
||||
}
|
||||
|
||||
return p1.y - p2.y;
|
||||
}
|
||||
|
||||
// Flattens a vector to ground plane in solver rotation space
|
||||
public Vector3 Flatten(Vector3 v) {
|
||||
if (useRootRotation) {
|
||||
Vector3 tangent = v;
|
||||
Vector3 normal = root.up;
|
||||
Vector3.OrthoNormalize(ref normal, ref tangent);
|
||||
return Vector3.Project(v, tangent);
|
||||
}
|
||||
|
||||
v.y = 0;
|
||||
return v;
|
||||
}
|
||||
|
||||
// Determines whether to use root rotation as solver rotation
|
||||
private bool useRootRotation {
|
||||
get {
|
||||
if (!rotateSolver) return false;
|
||||
if (root.up == Vector3.up) return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public Vector3 GetFootCenterOffset() {
|
||||
return root.forward * footRadius + root.forward * footCenterOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cc6729d441e4044f482135cdc532367d
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,345 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
public partial class Grounding {
|
||||
|
||||
/// <summary>
|
||||
/// The %Grounding %Leg.
|
||||
/// </summary>
|
||||
public class Leg {
|
||||
|
||||
/// <summary>
|
||||
/// Returns true distance from foot to ground is less that maxStep
|
||||
/// </summary>
|
||||
public bool isGrounded { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the current IK position of the foot.
|
||||
/// </summary>
|
||||
public Vector3 IKPosition { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the current rotation offset of the foot.
|
||||
/// </summary>
|
||||
public Quaternion rotationOffset = Quaternion.identity;
|
||||
/// <summary>
|
||||
/// Returns true, if the leg is valid and initiated
|
||||
/// </summary>
|
||||
public bool initiated { get; private set; }
|
||||
/// <summary>
|
||||
/// The height of foot from ground.
|
||||
/// </summary>
|
||||
public float heightFromGround { get; private set; }
|
||||
/// <summary>
|
||||
/// Velocity of the foot
|
||||
/// </summary>
|
||||
public Vector3 velocity { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the foot Transform.
|
||||
/// </summary>
|
||||
public Transform transform { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the current IK offset.
|
||||
/// </summary>
|
||||
public float IKOffset { get; private set; }
|
||||
|
||||
public bool invertFootCenter;
|
||||
|
||||
public RaycastHit heelHit { get; private set; }
|
||||
public RaycastHit capsuleHit { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RaycastHit last used by the Grounder to get ground height at foot position.
|
||||
/// </summary>
|
||||
public RaycastHit GetHitPoint {
|
||||
get
|
||||
{
|
||||
if (grounding.quality == Quality.Best) return capsuleHit;
|
||||
return heelHit;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the animated position of the foot.
|
||||
/// </summary>
|
||||
public void SetFootPosition(Vector3 position)
|
||||
{
|
||||
doOverrideFootPosition = true;
|
||||
overrideFootPosition = position;
|
||||
}
|
||||
|
||||
private Grounding grounding;
|
||||
private float lastTime, deltaTime;
|
||||
private Vector3 lastPosition;
|
||||
private Quaternion toHitNormal, r;
|
||||
private Vector3 up = Vector3.up;
|
||||
private bool doOverrideFootPosition;
|
||||
private Vector3 overrideFootPosition;
|
||||
private Vector3 transformPosition;
|
||||
|
||||
// Initiates the Leg
|
||||
public void Initiate(Grounding grounding, Transform transform) {
|
||||
initiated = false;
|
||||
this.grounding = grounding;
|
||||
this.transform = transform;
|
||||
up = Vector3.up;
|
||||
IKPosition = transform.position;
|
||||
rotationOffset = Quaternion.identity;
|
||||
|
||||
initiated = true;
|
||||
OnEnable();
|
||||
}
|
||||
|
||||
// Should be called each time the leg is (re)activated
|
||||
public void OnEnable() {
|
||||
if (!initiated) return;
|
||||
|
||||
lastPosition = transform.position;
|
||||
lastTime = Time.deltaTime;
|
||||
}
|
||||
|
||||
// Set everything to 0
|
||||
public void Reset() {
|
||||
lastPosition = transform.position;
|
||||
lastTime = Time.deltaTime;
|
||||
IKOffset = 0f;
|
||||
IKPosition = transform.position;
|
||||
rotationOffset = Quaternion.identity;
|
||||
}
|
||||
|
||||
// Raycasting, processing the leg's position
|
||||
public void Process() {
|
||||
if (!initiated) return;
|
||||
if (grounding.maxStep <= 0) return;
|
||||
|
||||
transformPosition = doOverrideFootPosition ? overrideFootPosition : transform.position;
|
||||
doOverrideFootPosition = false;
|
||||
|
||||
deltaTime = Time.time - lastTime;
|
||||
lastTime = Time.time;
|
||||
if (deltaTime == 0f) return;
|
||||
|
||||
up = grounding.up;
|
||||
heightFromGround = Mathf.Infinity;
|
||||
|
||||
// Calculating velocity
|
||||
velocity = (transformPosition - lastPosition) / deltaTime;
|
||||
//velocity = grounding.Flatten(velocity);
|
||||
lastPosition = transformPosition;
|
||||
|
||||
Vector3 prediction = velocity * grounding.prediction;
|
||||
|
||||
if (grounding.footRadius <= 0) grounding.quality = Grounding.Quality.Fastest;
|
||||
|
||||
isGrounded = false;
|
||||
|
||||
// Raycasting
|
||||
switch (grounding.quality)
|
||||
{
|
||||
|
||||
// The fastest, single raycast
|
||||
case Grounding.Quality.Fastest:
|
||||
|
||||
RaycastHit predictedHit = GetRaycastHit(prediction);
|
||||
SetFootToPoint(predictedHit.normal, predictedHit.point);
|
||||
if (predictedHit.collider != null) isGrounded = true;
|
||||
break;
|
||||
|
||||
// Medium, 3 raycasts
|
||||
case Grounding.Quality.Simple:
|
||||
|
||||
heelHit = GetRaycastHit(Vector3.zero);
|
||||
Vector3 f = grounding.GetFootCenterOffset();
|
||||
if (invertFootCenter) f = -f;
|
||||
RaycastHit toeHit = GetRaycastHit(f + prediction);
|
||||
RaycastHit sideHit = GetRaycastHit(grounding.root.right * grounding.footRadius * 0.5f);
|
||||
|
||||
if (heelHit.collider != null || toeHit.collider != null || sideHit.collider != null) isGrounded = true;
|
||||
|
||||
Vector3 planeNormal = Vector3.Cross(toeHit.point - heelHit.point, sideHit.point - heelHit.point).normalized;
|
||||
if (Vector3.Dot(planeNormal, up) < 0) planeNormal = -planeNormal;
|
||||
|
||||
SetFootToPlane(planeNormal, heelHit.point, heelHit.point);
|
||||
break;
|
||||
|
||||
// The slowest, raycast and a capsule cast
|
||||
case Grounding.Quality.Best:
|
||||
heelHit = GetRaycastHit(invertFootCenter ? -grounding.GetFootCenterOffset() : Vector3.zero);
|
||||
capsuleHit = GetCapsuleHit(prediction);
|
||||
|
||||
if (heelHit.collider != null || capsuleHit.collider != null) isGrounded = true;
|
||||
|
||||
SetFootToPlane(capsuleHit.normal, capsuleHit.point, heelHit.point);
|
||||
break;
|
||||
}
|
||||
|
||||
float offsetTarget = stepHeightFromGround;
|
||||
if (!grounding.rootGrounded) offsetTarget = 0f;
|
||||
|
||||
IKOffset = Interp.LerpValue(IKOffset, offsetTarget, grounding.footSpeed, grounding.footSpeed);
|
||||
IKOffset = Mathf.Lerp(IKOffset, offsetTarget, deltaTime * grounding.footSpeed);
|
||||
|
||||
float legHeight = grounding.GetVerticalOffset(transformPosition, grounding.root.position);
|
||||
float currentMaxOffset = Mathf.Clamp(grounding.maxStep - legHeight, 0f, grounding.maxStep);
|
||||
|
||||
IKOffset = Mathf.Clamp(IKOffset, -currentMaxOffset, IKOffset);
|
||||
|
||||
RotateFoot();
|
||||
|
||||
// Update IK values
|
||||
IKPosition = transformPosition - up * IKOffset;
|
||||
|
||||
float rW = grounding.footRotationWeight;
|
||||
rotationOffset = rW >= 1? r: Quaternion.Slerp(Quaternion.identity, r, rW);
|
||||
}
|
||||
|
||||
// Gets the height from ground clamped between min and max step height
|
||||
public float stepHeightFromGround {
|
||||
get {
|
||||
return Mathf.Clamp(heightFromGround, -grounding.maxStep, grounding.maxStep);
|
||||
}
|
||||
}
|
||||
|
||||
// Get predicted Capsule hit from the middle of the foot
|
||||
private RaycastHit GetCapsuleHit(Vector3 offsetFromHeel)
|
||||
{
|
||||
RaycastHit hit = new RaycastHit();
|
||||
Vector3 f = grounding.GetFootCenterOffset();
|
||||
if (invertFootCenter) f = -f;
|
||||
Vector3 origin = transformPosition + f;
|
||||
|
||||
if (grounding.overstepFallsDown)
|
||||
{
|
||||
hit.point = origin - up * grounding.maxStep;
|
||||
}
|
||||
else
|
||||
{
|
||||
hit.point = new Vector3(origin.x, grounding.root.position.y, origin.z);
|
||||
}
|
||||
hit.normal = up;
|
||||
|
||||
// Start point of the capsule
|
||||
Vector3 capsuleStart = origin + grounding.maxStep * up;
|
||||
// End point of the capsule depending on the foot's velocity.
|
||||
Vector3 capsuleEnd = capsuleStart + offsetFromHeel;
|
||||
|
||||
if (grounding.CapsuleCast(capsuleStart, capsuleEnd, grounding.footRadius, -up, out hit, grounding.maxStep * 2, grounding.layers, QueryTriggerInteraction.Ignore))
|
||||
{
|
||||
// Safeguarding from a CapsuleCast bug in Unity that might cause it to return NaN for hit.point when cast against large colliders.
|
||||
if (float.IsNaN(hit.point.x))
|
||||
{
|
||||
hit.point = origin - up * grounding.maxStep * 2f;
|
||||
hit.normal = up;
|
||||
}
|
||||
}
|
||||
|
||||
// Since Unity2017 Raycasts will return Vector3.zero when starting from inside a collider
|
||||
if (hit.point == Vector3.zero && hit.normal == Vector3.zero)
|
||||
{
|
||||
if (grounding.overstepFallsDown)
|
||||
{
|
||||
hit.point = origin - up * grounding.maxStep;
|
||||
}
|
||||
else
|
||||
{
|
||||
hit.point = new Vector3(origin.x, grounding.root.position.y, origin.z);
|
||||
}
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
// Get simple Raycast from the heel
|
||||
private RaycastHit GetRaycastHit(Vector3 offsetFromHeel)
|
||||
{
|
||||
RaycastHit hit = new RaycastHit();
|
||||
Vector3 origin = transformPosition + offsetFromHeel;
|
||||
|
||||
if (grounding.overstepFallsDown)
|
||||
{
|
||||
hit.point = origin - up * grounding.maxStep;
|
||||
}
|
||||
else
|
||||
{
|
||||
hit.point = new Vector3(origin.x, grounding.root.position.y, origin.z);
|
||||
}
|
||||
hit.normal = up;
|
||||
|
||||
if (grounding.maxStep <= 0f) return hit;
|
||||
|
||||
grounding.Raycast(origin + grounding.maxStep * up, -up, out hit, grounding.maxStep * 2, grounding.layers, QueryTriggerInteraction.Ignore);
|
||||
|
||||
// Since Unity2017 Raycasts will return Vector3.zero when starting from inside a collider
|
||||
if (hit.point == Vector3.zero && hit.normal == Vector3.zero)
|
||||
{
|
||||
if (grounding.overstepFallsDown)
|
||||
{
|
||||
hit.point = origin - up * grounding.maxStep;
|
||||
}
|
||||
else
|
||||
{
|
||||
hit.point = new Vector3(origin.x, grounding.root.position.y, origin.z);
|
||||
}
|
||||
}
|
||||
|
||||
return hit;
|
||||
}
|
||||
|
||||
// Rotates ground normal with respect to maxFootRotationAngle
|
||||
private Vector3 RotateNormal(Vector3 normal) {
|
||||
if (grounding.quality == Grounding.Quality.Best) return normal;
|
||||
return Vector3.RotateTowards(up, normal, grounding.maxFootRotationAngle * Mathf.Deg2Rad, deltaTime);
|
||||
}
|
||||
|
||||
// Set foot height from ground relative to a point
|
||||
private void SetFootToPoint(Vector3 normal, Vector3 point) {
|
||||
toHitNormal = Quaternion.FromToRotation(up, RotateNormal(normal));
|
||||
|
||||
heightFromGround = GetHeightFromGround(point);
|
||||
}
|
||||
|
||||
// Set foot height from ground relative to a plane
|
||||
private void SetFootToPlane(Vector3 planeNormal, Vector3 planePoint, Vector3 heelHitPoint) {
|
||||
planeNormal = RotateNormal(planeNormal);
|
||||
toHitNormal = Quaternion.FromToRotation(up, planeNormal);
|
||||
|
||||
Vector3 pointOnPlane = V3Tools.LineToPlane(transformPosition + up * grounding.maxStep, -up, planeNormal, planePoint);
|
||||
|
||||
// Get the height offset of the point on the plane
|
||||
heightFromGround = GetHeightFromGround(pointOnPlane);
|
||||
|
||||
// Making sure the heel doesn't penetrate the ground
|
||||
float heelHeight = GetHeightFromGround(heelHitPoint);
|
||||
heightFromGround = Mathf.Clamp(heightFromGround, -Mathf.Infinity, heelHeight);
|
||||
}
|
||||
|
||||
// Calculate height offset of a point
|
||||
private float GetHeightFromGround(Vector3 hitPoint) {
|
||||
return grounding.GetVerticalOffset(transformPosition, hitPoint) - rootYOffset;
|
||||
}
|
||||
|
||||
// Adding ground normal offset to the foot's rotation
|
||||
private void RotateFoot() {
|
||||
// Getting the full target rotation
|
||||
Quaternion rotationOffsetTarget = GetRotationOffsetTarget();
|
||||
|
||||
// Slerping the rotation offset
|
||||
r = Quaternion.Slerp(r, rotationOffsetTarget, deltaTime * grounding.footRotationSpeed);
|
||||
}
|
||||
|
||||
// Gets the target hit normal offset as a Quaternion
|
||||
private Quaternion GetRotationOffsetTarget() {
|
||||
if (grounding.maxFootRotationAngle <= 0f) return Quaternion.identity;
|
||||
if (grounding.maxFootRotationAngle >= 180f) return toHitNormal;
|
||||
return Quaternion.RotateTowards(Quaternion.identity, toHitNormal, grounding.maxFootRotationAngle);
|
||||
}
|
||||
|
||||
// The foot's height from ground in the animation
|
||||
private float rootYOffset {
|
||||
get {
|
||||
return grounding.GetVerticalOffset(transformPosition, grounding.root.position - up * grounding.heightOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0098ae58c812d4f599afc603a2d8b4b0
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,82 @@
|
||||
using UnityEngine;
|
||||
using System.Collections;
|
||||
using RootMotion;
|
||||
|
||||
namespace RootMotion.FinalIK {
|
||||
|
||||
public partial class Grounding {
|
||||
|
||||
/// <summary>
|
||||
/// The %Grounding %Pelvis.
|
||||
/// </summary>
|
||||
public class Pelvis {
|
||||
|
||||
/// <summary>
|
||||
/// Offset of the pelvis as a Vector3.
|
||||
/// </summary>
|
||||
public Vector3 IKOffset { get; private set; }
|
||||
/// <summary>
|
||||
/// Scalar vertical offset of the pelvis.
|
||||
/// </summary>
|
||||
public float heightOffset { get; private set; }
|
||||
|
||||
private Grounding grounding;
|
||||
private Vector3 lastRootPosition;
|
||||
private float damperF;
|
||||
private bool initiated;
|
||||
private float lastTime;
|
||||
|
||||
// Initiating the pelvis
|
||||
public void Initiate(Grounding grounding) {
|
||||
this.grounding = grounding;
|
||||
|
||||
initiated = true;
|
||||
OnEnable();
|
||||
}
|
||||
|
||||
// Set everything to 0
|
||||
public void Reset() {
|
||||
this.lastRootPosition = grounding.root.transform.position;
|
||||
lastTime = Time.deltaTime;
|
||||
IKOffset = Vector3.zero;
|
||||
heightOffset = 0f;
|
||||
}
|
||||
|
||||
// Should be called each time the pelvis is (re)activated
|
||||
public void OnEnable() {
|
||||
if (!initiated) return;
|
||||
this.lastRootPosition = grounding.root.transform.position;
|
||||
lastTime = Time.time;
|
||||
}
|
||||
|
||||
// Updates the pelvis position offset
|
||||
public void Process(float lowestOffset, float highestOffset, bool isGrounded) {
|
||||
if (!initiated) return;
|
||||
|
||||
float deltaTime = Time.time - lastTime;
|
||||
lastTime = Time.time;
|
||||
if (deltaTime <= 0f) return;
|
||||
|
||||
float offsetTarget = lowestOffset + highestOffset;
|
||||
if (!grounding.rootGrounded) offsetTarget = 0f;
|
||||
|
||||
// Interpolating the offset
|
||||
heightOffset = Mathf.Lerp(heightOffset, offsetTarget, deltaTime * grounding.pelvisSpeed);
|
||||
|
||||
// Damper
|
||||
Vector3 rootDelta = (grounding.root.position - lastRootPosition);
|
||||
lastRootPosition = grounding.root.position;
|
||||
|
||||
// Fading out damper when ungrounded
|
||||
damperF = Interp.LerpValue(damperF, isGrounded? 1f: 0f, 1f, 10f);
|
||||
|
||||
// Calculating the final damper
|
||||
heightOffset -= grounding.GetVerticalOffset(rootDelta, Vector3.zero) * grounding.pelvisDamper * damperF;
|
||||
|
||||
// Update IK value
|
||||
IKOffset = grounding.up * heightOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f32aae971577b4a689afe07820935737
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user