697 lines
19 KiB
C#
697 lines
19 KiB
C#
using System;
|
|
using UnityEngine;
|
|
|
|
namespace RootMotion.FinalIK
|
|
{
|
|
[Serializable]
|
|
public class Grounding
|
|
{
|
|
[Serializable]
|
|
public enum Quality
|
|
{
|
|
Fastest = 0,
|
|
Simple = 1,
|
|
Best = 2
|
|
}
|
|
|
|
public delegate bool OnRaycastDelegate(Vector3 origin, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);
|
|
|
|
public delegate bool OnCapsuleCastDelegate(Vector3 point1, Vector3 point2, float radius, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);
|
|
|
|
public delegate bool OnSphereCastDelegate(Vector3 origin, float radius, Vector3 direction, out RaycastHit hitInfo, float maxDistance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);
|
|
|
|
public class Leg
|
|
{
|
|
public Quaternion rotationOffset = Quaternion.identity;
|
|
|
|
public bool invertFootCenter;
|
|
|
|
private Grounding grounding;
|
|
|
|
private float lastTime;
|
|
|
|
private float deltaTime;
|
|
|
|
private Vector3 lastPosition;
|
|
|
|
private Quaternion toHitNormal;
|
|
|
|
private Quaternion r;
|
|
|
|
private Vector3 up = Vector3.up;
|
|
|
|
private bool doOverrideFootPosition;
|
|
|
|
private Vector3 overrideFootPosition;
|
|
|
|
private Vector3 transformPosition;
|
|
|
|
public bool isGrounded { get; private set; }
|
|
|
|
public Vector3 IKPosition { get; private set; }
|
|
|
|
public bool initiated { get; private set; }
|
|
|
|
public float heightFromGround { get; private set; }
|
|
|
|
public Vector3 velocity { get; private set; }
|
|
|
|
public Transform transform { get; private set; }
|
|
|
|
public float IKOffset { get; private set; }
|
|
|
|
public RaycastHit heelHit { get; private set; }
|
|
|
|
public RaycastHit capsuleHit { get; private set; }
|
|
|
|
public RaycastHit GetHitPoint
|
|
{
|
|
get
|
|
{
|
|
if (grounding.quality == Quality.Best)
|
|
{
|
|
return capsuleHit;
|
|
}
|
|
return heelHit;
|
|
}
|
|
}
|
|
|
|
public float stepHeightFromGround => Mathf.Clamp(heightFromGround, 0f - grounding.maxStep, grounding.maxStep);
|
|
|
|
private float rootYOffset => grounding.GetVerticalOffset(transformPosition, grounding.root.position - up * grounding.heightOffset);
|
|
|
|
public void SetFootPosition(Vector3 position)
|
|
{
|
|
doOverrideFootPosition = true;
|
|
overrideFootPosition = position;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
public void OnEnable()
|
|
{
|
|
if (initiated)
|
|
{
|
|
lastPosition = transform.position;
|
|
lastTime = Time.deltaTime;
|
|
}
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
lastPosition = transform.position;
|
|
lastTime = Time.deltaTime;
|
|
IKOffset = 0f;
|
|
IKPosition = transform.position;
|
|
rotationOffset = Quaternion.identity;
|
|
}
|
|
|
|
public void Process()
|
|
{
|
|
if (!initiated || grounding.maxStep <= 0f)
|
|
{
|
|
return;
|
|
}
|
|
transformPosition = (doOverrideFootPosition ? overrideFootPosition : transform.position);
|
|
doOverrideFootPosition = false;
|
|
deltaTime = Time.time - lastTime;
|
|
lastTime = Time.time;
|
|
if (deltaTime == 0f)
|
|
{
|
|
return;
|
|
}
|
|
up = grounding.up;
|
|
heightFromGround = float.PositiveInfinity;
|
|
velocity = (transformPosition - lastPosition) / deltaTime;
|
|
lastPosition = transformPosition;
|
|
Vector3 vector = velocity * grounding.prediction;
|
|
if (grounding.footRadius <= 0f)
|
|
{
|
|
grounding.quality = Quality.Fastest;
|
|
}
|
|
isGrounded = false;
|
|
switch (grounding.quality)
|
|
{
|
|
case Quality.Fastest:
|
|
{
|
|
RaycastHit raycastHit = GetRaycastHit(vector);
|
|
SetFootToPoint(raycastHit.normal, raycastHit.point);
|
|
if (raycastHit.collider != null)
|
|
{
|
|
isGrounded = true;
|
|
}
|
|
break;
|
|
}
|
|
case Quality.Simple:
|
|
{
|
|
heelHit = GetRaycastHit(Vector3.zero);
|
|
Vector3 vector2 = grounding.GetFootCenterOffset();
|
|
if (invertFootCenter)
|
|
{
|
|
vector2 = -vector2;
|
|
}
|
|
RaycastHit raycastHit2 = GetRaycastHit(vector2 + vector);
|
|
RaycastHit raycastHit3 = GetRaycastHit(grounding.root.right * grounding.footRadius * 0.5f);
|
|
if (heelHit.collider != null || raycastHit2.collider != null || raycastHit3.collider != null)
|
|
{
|
|
isGrounded = true;
|
|
}
|
|
Vector3 vector3 = Vector3.Cross(raycastHit2.point - heelHit.point, raycastHit3.point - heelHit.point).normalized;
|
|
if (Vector3.Dot(vector3, up) < 0f)
|
|
{
|
|
vector3 = -vector3;
|
|
}
|
|
SetFootToPlane(vector3, heelHit.point, heelHit.point);
|
|
break;
|
|
}
|
|
case Quality.Best:
|
|
heelHit = GetRaycastHit(invertFootCenter ? (-grounding.GetFootCenterOffset()) : Vector3.zero);
|
|
capsuleHit = GetCapsuleHit(vector);
|
|
if (heelHit.collider != null || capsuleHit.collider != null)
|
|
{
|
|
isGrounded = true;
|
|
}
|
|
SetFootToPlane(capsuleHit.normal, capsuleHit.point, heelHit.point);
|
|
break;
|
|
}
|
|
float num = stepHeightFromGround;
|
|
if (!grounding.rootGrounded)
|
|
{
|
|
num = 0f;
|
|
}
|
|
IKOffset = Interp.LerpValue(IKOffset, num, grounding.footSpeed, grounding.footSpeed);
|
|
IKOffset = Mathf.Lerp(IKOffset, num, deltaTime * grounding.footSpeed);
|
|
float verticalOffset = grounding.GetVerticalOffset(transformPosition, grounding.root.position);
|
|
float num2 = Mathf.Clamp(grounding.maxStep - verticalOffset, 0f, grounding.maxStep);
|
|
IKOffset = Mathf.Clamp(IKOffset, 0f - num2, IKOffset);
|
|
RotateFoot();
|
|
IKPosition = transformPosition - up * IKOffset;
|
|
float footRotationWeight = grounding.footRotationWeight;
|
|
rotationOffset = ((footRotationWeight >= 1f) ? r : Quaternion.Slerp(Quaternion.identity, r, footRotationWeight));
|
|
}
|
|
|
|
private RaycastHit GetCapsuleHit(Vector3 offsetFromHeel)
|
|
{
|
|
RaycastHit hitInfo = default(RaycastHit);
|
|
Vector3 vector = grounding.GetFootCenterOffset();
|
|
if (invertFootCenter)
|
|
{
|
|
vector = -vector;
|
|
}
|
|
Vector3 vector2 = transformPosition + vector;
|
|
if (grounding.overstepFallsDown)
|
|
{
|
|
hitInfo.point = vector2 - up * grounding.maxStep;
|
|
}
|
|
else
|
|
{
|
|
hitInfo.point = new Vector3(vector2.x, grounding.root.position.y, vector2.z);
|
|
}
|
|
hitInfo.normal = up;
|
|
Vector3 vector3 = vector2 + grounding.maxStep * up;
|
|
Vector3 point = vector3 + offsetFromHeel;
|
|
if (grounding.CapsuleCast(vector3, point, grounding.footRadius, -up, out hitInfo, grounding.maxStep * 2f, grounding.layers, QueryTriggerInteraction.Ignore) && float.IsNaN(hitInfo.point.x))
|
|
{
|
|
hitInfo.point = vector2 - up * grounding.maxStep * 2f;
|
|
hitInfo.normal = up;
|
|
}
|
|
if (hitInfo.point == Vector3.zero && hitInfo.normal == Vector3.zero)
|
|
{
|
|
if (grounding.overstepFallsDown)
|
|
{
|
|
hitInfo.point = vector2 - up * grounding.maxStep;
|
|
}
|
|
else
|
|
{
|
|
hitInfo.point = new Vector3(vector2.x, grounding.root.position.y, vector2.z);
|
|
}
|
|
}
|
|
return hitInfo;
|
|
}
|
|
|
|
private RaycastHit GetRaycastHit(Vector3 offsetFromHeel)
|
|
{
|
|
RaycastHit hitInfo = default(RaycastHit);
|
|
Vector3 vector = transformPosition + offsetFromHeel;
|
|
if (grounding.overstepFallsDown)
|
|
{
|
|
hitInfo.point = vector - up * grounding.maxStep;
|
|
}
|
|
else
|
|
{
|
|
hitInfo.point = new Vector3(vector.x, grounding.root.position.y, vector.z);
|
|
}
|
|
hitInfo.normal = up;
|
|
if (grounding.maxStep <= 0f)
|
|
{
|
|
return hitInfo;
|
|
}
|
|
grounding.Raycast(vector + grounding.maxStep * up, -up, out hitInfo, grounding.maxStep * 2f, grounding.layers, QueryTriggerInteraction.Ignore);
|
|
if (hitInfo.point == Vector3.zero && hitInfo.normal == Vector3.zero)
|
|
{
|
|
if (grounding.overstepFallsDown)
|
|
{
|
|
hitInfo.point = vector - up * grounding.maxStep;
|
|
}
|
|
else
|
|
{
|
|
hitInfo.point = new Vector3(vector.x, grounding.root.position.y, vector.z);
|
|
}
|
|
}
|
|
return hitInfo;
|
|
}
|
|
|
|
private Vector3 RotateNormal(Vector3 normal)
|
|
{
|
|
if (grounding.quality == Quality.Best)
|
|
{
|
|
return normal;
|
|
}
|
|
return Vector3.RotateTowards(up, normal, grounding.maxFootRotationAngle * (MathF.PI / 180f), deltaTime);
|
|
}
|
|
|
|
private void SetFootToPoint(Vector3 normal, Vector3 point)
|
|
{
|
|
toHitNormal = Quaternion.FromToRotation(up, RotateNormal(normal));
|
|
heightFromGround = GetHeightFromGround(point);
|
|
}
|
|
|
|
private void SetFootToPlane(Vector3 planeNormal, Vector3 planePoint, Vector3 heelHitPoint)
|
|
{
|
|
planeNormal = RotateNormal(planeNormal);
|
|
toHitNormal = Quaternion.FromToRotation(up, planeNormal);
|
|
Vector3 hitPoint = V3Tools.LineToPlane(transformPosition + up * grounding.maxStep, -up, planeNormal, planePoint);
|
|
heightFromGround = GetHeightFromGround(hitPoint);
|
|
float max = GetHeightFromGround(heelHitPoint);
|
|
heightFromGround = Mathf.Clamp(heightFromGround, float.NegativeInfinity, max);
|
|
}
|
|
|
|
private float GetHeightFromGround(Vector3 hitPoint)
|
|
{
|
|
return grounding.GetVerticalOffset(transformPosition, hitPoint) - rootYOffset;
|
|
}
|
|
|
|
private void RotateFoot()
|
|
{
|
|
Quaternion rotationOffsetTarget = GetRotationOffsetTarget();
|
|
r = Quaternion.Slerp(r, rotationOffsetTarget, deltaTime * grounding.footRotationSpeed);
|
|
}
|
|
|
|
private Quaternion GetRotationOffsetTarget()
|
|
{
|
|
if (grounding.maxFootRotationAngle <= 0f)
|
|
{
|
|
return Quaternion.identity;
|
|
}
|
|
if (grounding.maxFootRotationAngle >= 180f)
|
|
{
|
|
return toHitNormal;
|
|
}
|
|
return Quaternion.RotateTowards(Quaternion.identity, toHitNormal, grounding.maxFootRotationAngle);
|
|
}
|
|
}
|
|
|
|
public class Pelvis
|
|
{
|
|
private Grounding grounding;
|
|
|
|
private Vector3 lastRootPosition;
|
|
|
|
private float damperF;
|
|
|
|
private bool initiated;
|
|
|
|
private float lastTime;
|
|
|
|
public Vector3 IKOffset { get; private set; }
|
|
|
|
public float heightOffset { get; private set; }
|
|
|
|
public void Initiate(Grounding grounding)
|
|
{
|
|
this.grounding = grounding;
|
|
initiated = true;
|
|
OnEnable();
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
lastRootPosition = grounding.root.transform.position;
|
|
lastTime = Time.deltaTime;
|
|
IKOffset = Vector3.zero;
|
|
heightOffset = 0f;
|
|
}
|
|
|
|
public void OnEnable()
|
|
{
|
|
if (initiated)
|
|
{
|
|
lastRootPosition = grounding.root.transform.position;
|
|
lastTime = Time.time;
|
|
}
|
|
}
|
|
|
|
public void Process(float lowestOffset, float highestOffset, bool isGrounded)
|
|
{
|
|
if (!initiated)
|
|
{
|
|
return;
|
|
}
|
|
float num = Time.time - lastTime;
|
|
lastTime = Time.time;
|
|
if (!(num <= 0f))
|
|
{
|
|
float b = lowestOffset + highestOffset;
|
|
if (!grounding.rootGrounded)
|
|
{
|
|
b = 0f;
|
|
}
|
|
heightOffset = Mathf.Lerp(heightOffset, b, num * grounding.pelvisSpeed);
|
|
Vector3 p = grounding.root.position - lastRootPosition;
|
|
lastRootPosition = grounding.root.position;
|
|
damperF = Interp.LerpValue(damperF, isGrounded ? 1f : 0f, 1f, 10f);
|
|
heightOffset -= grounding.GetVerticalOffset(p, Vector3.zero) * grounding.pelvisDamper * damperF;
|
|
IKOffset = grounding.up * heightOffset;
|
|
}
|
|
}
|
|
}
|
|
|
|
[Tooltip("Layers to ground the character to. Make sure to exclude the layer of the character controller.")]
|
|
public LayerMask layers;
|
|
|
|
[Tooltip("Max step height. Maximum vertical distance of Grounding from the root of the character.")]
|
|
public float maxStep = 0.5f;
|
|
|
|
[Tooltip("The height offset of the root.")]
|
|
public float heightOffset;
|
|
|
|
[Tooltip("The speed of moving the feet up/down.")]
|
|
public float footSpeed = 2.5f;
|
|
|
|
[Tooltip("CapsuleCast radius. Should match approximately with the size of the feet.")]
|
|
public float footRadius = 0.15f;
|
|
|
|
[Tooltip("Offset of the foot center along character forward axis.")]
|
|
[HideInInspector]
|
|
public float footCenterOffset;
|
|
|
|
[Tooltip("Amount of velocity based prediction of the foot positions.")]
|
|
public float prediction = 0.05f;
|
|
|
|
[Tooltip("Weight of rotating the feet to the ground normal offset.")]
|
|
[Range(0f, 1f)]
|
|
public float footRotationWeight = 1f;
|
|
|
|
[Tooltip("Speed of slerping the feet to their grounded rotations.")]
|
|
public float footRotationSpeed = 7f;
|
|
|
|
[Tooltip("Max Foot Rotation Angle. Max angular offset from the foot's rotation.")]
|
|
[Range(0f, 90f)]
|
|
public float maxFootRotationAngle = 45f;
|
|
|
|
[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;
|
|
|
|
[Tooltip("The speed of moving the character up/down.")]
|
|
public float pelvisSpeed = 5f;
|
|
|
|
[Tooltip("Used for smoothing out vertical pelvis movement (range 0 - 1).")]
|
|
[Range(0f, 1f)]
|
|
public float pelvisDamper;
|
|
|
|
[Tooltip("The weight of lowering the pelvis to the lowest foot.")]
|
|
public float lowerPelvisWeight = 1f;
|
|
|
|
[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;
|
|
|
|
[Tooltip("The radius of the spherecast from the root that determines whether the character root is grounded.")]
|
|
public float rootSphereCastRadius = 0.1f;
|
|
|
|
[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;
|
|
|
|
[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;
|
|
|
|
public OnRaycastDelegate Raycast = Physics.Raycast;
|
|
|
|
public OnCapsuleCastDelegate CapsuleCast = Physics.CapsuleCast;
|
|
|
|
public OnSphereCastDelegate SphereCast = Physics.SphereCast;
|
|
|
|
private bool initiated;
|
|
|
|
public Leg[] legs { get; private set; }
|
|
|
|
public Pelvis pelvis { get; private set; }
|
|
|
|
public bool isGrounded { get; private set; }
|
|
|
|
public Transform root { get; private set; }
|
|
|
|
public RaycastHit rootHit { get; private set; }
|
|
|
|
public bool rootGrounded => rootHit.distance < maxStep * 2f;
|
|
|
|
public Vector3 up
|
|
{
|
|
get
|
|
{
|
|
if (!useRootRotation)
|
|
{
|
|
return Vector3.up;
|
|
}
|
|
return root.up;
|
|
}
|
|
}
|
|
|
|
private bool useRootRotation
|
|
{
|
|
get
|
|
{
|
|
if (!rotateSolver)
|
|
{
|
|
return false;
|
|
}
|
|
if (root.up == Vector3.up)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
public RaycastHit GetRootHit(float maxDistanceMlp = 10f)
|
|
{
|
|
RaycastHit hitInfo = default(RaycastHit);
|
|
Vector3 vector = up;
|
|
Vector3 zero = Vector3.zero;
|
|
Leg[] array = legs;
|
|
foreach (Leg leg in array)
|
|
{
|
|
zero += leg.transform.position;
|
|
}
|
|
zero /= (float)legs.Length;
|
|
hitInfo.point = zero - vector * maxStep * 10f;
|
|
float num = maxDistanceMlp + 1f;
|
|
hitInfo.distance = maxStep * num;
|
|
if (maxStep <= 0f)
|
|
{
|
|
return hitInfo;
|
|
}
|
|
if (quality != Quality.Best)
|
|
{
|
|
Raycast(zero + vector * maxStep, -vector, out hitInfo, maxStep * num, layers, QueryTriggerInteraction.Ignore);
|
|
}
|
|
else
|
|
{
|
|
SphereCast(zero + vector * maxStep, rootSphereCastRadius, -up, out hitInfo, maxStep * num, layers, QueryTriggerInteraction.Ignore);
|
|
}
|
|
return hitInfo;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
public void Initiate(Transform root, Transform[] feet)
|
|
{
|
|
this.root = root;
|
|
initiated = false;
|
|
rootHit = default(RaycastHit);
|
|
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();
|
|
}
|
|
}
|
|
if (pelvis == null)
|
|
{
|
|
pelvis = new Pelvis();
|
|
}
|
|
string errorMessage = string.Empty;
|
|
if (!IsValid(ref errorMessage))
|
|
{
|
|
Warning.Log(errorMessage, root);
|
|
}
|
|
else if (Application.isPlaying)
|
|
{
|
|
for (int j = 0; j < feet.Length; j++)
|
|
{
|
|
legs[j].Initiate(this, feet[j]);
|
|
}
|
|
pelvis.Initiate(this);
|
|
initiated = true;
|
|
}
|
|
}
|
|
|
|
public void Update()
|
|
{
|
|
if (!initiated)
|
|
{
|
|
return;
|
|
}
|
|
if ((int)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);
|
|
rootHit = GetRootHit();
|
|
float num = float.NegativeInfinity;
|
|
float num2 = float.PositiveInfinity;
|
|
isGrounded = false;
|
|
Leg[] array = legs;
|
|
foreach (Leg leg in array)
|
|
{
|
|
leg.Process();
|
|
if (leg.IKOffset > num)
|
|
{
|
|
num = leg.IKOffset;
|
|
}
|
|
if (leg.IKOffset < num2)
|
|
{
|
|
num2 = leg.IKOffset;
|
|
}
|
|
if (leg.isGrounded)
|
|
{
|
|
isGrounded = true;
|
|
}
|
|
}
|
|
num = Mathf.Max(num, 0f);
|
|
num2 = Mathf.Min(num2, 0f);
|
|
pelvis.Process((0f - num) * lowerPelvisWeight, (0f - num2) * liftPelvisWeight, isGrounded);
|
|
}
|
|
|
|
public Vector3 GetLegsPlaneNormal()
|
|
{
|
|
if (!initiated)
|
|
{
|
|
return Vector3.up;
|
|
}
|
|
Vector3 vector = up;
|
|
Vector3 vector2 = vector;
|
|
for (int i = 0; i < legs.Length; i++)
|
|
{
|
|
Vector3 vector3 = legs[i].IKPosition - root.position;
|
|
Vector3 normal = vector;
|
|
Vector3 tangent = vector3;
|
|
Vector3.OrthoNormalize(ref normal, ref tangent);
|
|
vector2 = Quaternion.FromToRotation(tangent, vector3) * vector2;
|
|
}
|
|
return vector2;
|
|
}
|
|
|
|
public void Reset()
|
|
{
|
|
if (Application.isPlaying)
|
|
{
|
|
pelvis.Reset();
|
|
Leg[] array = legs;
|
|
for (int i = 0; i < array.Length; i++)
|
|
{
|
|
array[i].Reset();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void LogWarning(string message)
|
|
{
|
|
Warning.Log(message, root);
|
|
}
|
|
|
|
public float GetVerticalOffset(Vector3 p1, Vector3 p2)
|
|
{
|
|
if (useRootRotation)
|
|
{
|
|
return (Quaternion.Inverse(root.rotation) * (p1 - p2)).y;
|
|
}
|
|
return p1.y - p2.y;
|
|
}
|
|
|
|
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 = 0f;
|
|
return v;
|
|
}
|
|
|
|
public Vector3 GetFootCenterOffset()
|
|
{
|
|
return root.forward * footRadius + root.forward * footCenterOffset;
|
|
}
|
|
}
|
|
}
|