123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- 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;
- }
- }
- /// <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) Physics.Raycast(legsCenter + _up * maxStep, -_up, out h, maxStep * distMlp, layers, QueryTriggerInteraction.Ignore);
- else Physics.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 it's 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;
- }
- }
- }
|