123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437 |
- 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 it's rotation).
- /// </summary>
- [Tooltip("The head (optional, if you intend to maintain it's 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() {
- 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;
- }
-
- // 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;
- }
- }
- }
- }
- }
|