initial upload

This commit is contained in:
tom.hempel
2025-09-21 22:42:26 +02:00
commit d03bcd4ba5
6231 changed files with 351582 additions and 0 deletions

View File

@ -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();
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 20d3b31ccf0b543f5b659a1c3292d5c5
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 391939450fd8044e3839af08f34c2af8
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: c0b08042695f548e79a4e5c84112fe52, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 6c72e1df647af4c0098866e944a04b01
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 36056811931e14f20adcc2767d6262e7, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 65ace204533ef4c24ac80f11ef8ee8ea
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: 7c84c5721b6d74a15ab63fe46effb886, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 9823e47edf1dd40c29dfe0ba019f33a6
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {fileID: 2800000, guid: c92822fd107844dccaf6c30c024e5806, type: 3}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: cc6729d441e4044f482135cdc532367d
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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);
}
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: 0098ae58c812d4f599afc603a2d8b4b0
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -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;
}
}
}
}

View File

@ -0,0 +1,10 @@
fileFormatVersion: 2
guid: f32aae971577b4a689afe07820935737
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: