123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345 |
- 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 (Physics.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;
- Physics.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);
- }
- }
- }
- }
- }
|