GroundingLeg.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. using UnityEngine;
  2. using System.Collections;
  3. namespace RootMotion.FinalIK {
  4. public partial class Grounding {
  5. /// <summary>
  6. /// The %Grounding %Leg.
  7. /// </summary>
  8. public class Leg {
  9. /// <summary>
  10. /// Returns true distance from foot to ground is less that maxStep
  11. /// </summary>
  12. public bool isGrounded { get; private set; }
  13. /// <summary>
  14. /// Gets the current IK position of the foot.
  15. /// </summary>
  16. public Vector3 IKPosition { get; private set; }
  17. /// <summary>
  18. /// Gets the current rotation offset of the foot.
  19. /// </summary>
  20. public Quaternion rotationOffset = Quaternion.identity;
  21. /// <summary>
  22. /// Returns true, if the leg is valid and initiated
  23. /// </summary>
  24. public bool initiated { get; private set; }
  25. /// <summary>
  26. /// The height of foot from ground.
  27. /// </summary>
  28. public float heightFromGround { get; private set; }
  29. /// <summary>
  30. /// Velocity of the foot
  31. /// </summary>
  32. public Vector3 velocity { get; private set; }
  33. /// <summary>
  34. /// Gets the foot Transform.
  35. /// </summary>
  36. public Transform transform { get; private set; }
  37. /// <summary>
  38. /// Gets the current IK offset.
  39. /// </summary>
  40. public float IKOffset { get; private set; }
  41. public bool invertFootCenter;
  42. public RaycastHit heelHit { get; private set; }
  43. public RaycastHit capsuleHit { get; private set; }
  44. /// <summary>
  45. /// Gets the RaycastHit last used by the Grounder to get ground height at foot position.
  46. /// </summary>
  47. public RaycastHit GetHitPoint {
  48. get
  49. {
  50. if (grounding.quality == Quality.Best) return capsuleHit;
  51. return heelHit;
  52. }
  53. }
  54. /// <summary>
  55. /// Overrides the animated position of the foot.
  56. /// </summary>
  57. public void SetFootPosition(Vector3 position)
  58. {
  59. doOverrideFootPosition = true;
  60. overrideFootPosition = position;
  61. }
  62. private Grounding grounding;
  63. private float lastTime, deltaTime;
  64. private Vector3 lastPosition;
  65. private Quaternion toHitNormal, r;
  66. private Vector3 up = Vector3.up;
  67. private bool doOverrideFootPosition;
  68. private Vector3 overrideFootPosition;
  69. private Vector3 transformPosition;
  70. // Initiates the Leg
  71. public void Initiate(Grounding grounding, Transform transform) {
  72. initiated = false;
  73. this.grounding = grounding;
  74. this.transform = transform;
  75. up = Vector3.up;
  76. IKPosition = transform.position;
  77. rotationOffset = Quaternion.identity;
  78. initiated = true;
  79. OnEnable();
  80. }
  81. // Should be called each time the leg is (re)activated
  82. public void OnEnable() {
  83. if (!initiated) return;
  84. lastPosition = transform.position;
  85. lastTime = Time.deltaTime;
  86. }
  87. // Set everything to 0
  88. public void Reset() {
  89. lastPosition = transform.position;
  90. lastTime = Time.deltaTime;
  91. IKOffset = 0f;
  92. IKPosition = transform.position;
  93. rotationOffset = Quaternion.identity;
  94. }
  95. // Raycasting, processing the leg's position
  96. public void Process() {
  97. if (!initiated) return;
  98. if (grounding.maxStep <= 0) return;
  99. transformPosition = doOverrideFootPosition ? overrideFootPosition : transform.position;
  100. doOverrideFootPosition = false;
  101. deltaTime = Time.time - lastTime;
  102. lastTime = Time.time;
  103. if (deltaTime == 0f) return;
  104. up = grounding.up;
  105. heightFromGround = Mathf.Infinity;
  106. // Calculating velocity
  107. velocity = (transformPosition - lastPosition) / deltaTime;
  108. //velocity = grounding.Flatten(velocity);
  109. lastPosition = transformPosition;
  110. Vector3 prediction = velocity * grounding.prediction;
  111. if (grounding.footRadius <= 0) grounding.quality = Grounding.Quality.Fastest;
  112. isGrounded = false;
  113. // Raycasting
  114. switch (grounding.quality)
  115. {
  116. // The fastest, single raycast
  117. case Grounding.Quality.Fastest:
  118. RaycastHit predictedHit = GetRaycastHit(prediction);
  119. SetFootToPoint(predictedHit.normal, predictedHit.point);
  120. if (predictedHit.collider != null) isGrounded = true;
  121. break;
  122. // Medium, 3 raycasts
  123. case Grounding.Quality.Simple:
  124. heelHit = GetRaycastHit(Vector3.zero);
  125. Vector3 f = grounding.GetFootCenterOffset();
  126. if (invertFootCenter) f = -f;
  127. RaycastHit toeHit = GetRaycastHit(f + prediction);
  128. RaycastHit sideHit = GetRaycastHit(grounding.root.right * grounding.footRadius * 0.5f);
  129. if (heelHit.collider != null || toeHit.collider != null || sideHit.collider != null) isGrounded = true;
  130. Vector3 planeNormal = Vector3.Cross(toeHit.point - heelHit.point, sideHit.point - heelHit.point).normalized;
  131. if (Vector3.Dot(planeNormal, up) < 0) planeNormal = -planeNormal;
  132. SetFootToPlane(planeNormal, heelHit.point, heelHit.point);
  133. break;
  134. // The slowest, raycast and a capsule cast
  135. case Grounding.Quality.Best:
  136. heelHit = GetRaycastHit(invertFootCenter ? -grounding.GetFootCenterOffset() : Vector3.zero);
  137. capsuleHit = GetCapsuleHit(prediction);
  138. if (heelHit.collider != null || capsuleHit.collider != null) isGrounded = true;
  139. SetFootToPlane(capsuleHit.normal, capsuleHit.point, heelHit.point);
  140. break;
  141. }
  142. float offsetTarget = stepHeightFromGround;
  143. if (!grounding.rootGrounded) offsetTarget = 0f;
  144. IKOffset = Interp.LerpValue(IKOffset, offsetTarget, grounding.footSpeed, grounding.footSpeed);
  145. IKOffset = Mathf.Lerp(IKOffset, offsetTarget, deltaTime * grounding.footSpeed);
  146. float legHeight = grounding.GetVerticalOffset(transformPosition, grounding.root.position);
  147. float currentMaxOffset = Mathf.Clamp(grounding.maxStep - legHeight, 0f, grounding.maxStep);
  148. IKOffset = Mathf.Clamp(IKOffset, -currentMaxOffset, IKOffset);
  149. RotateFoot();
  150. // Update IK values
  151. IKPosition = transformPosition - up * IKOffset;
  152. float rW = grounding.footRotationWeight;
  153. rotationOffset = rW >= 1? r: Quaternion.Slerp(Quaternion.identity, r, rW);
  154. }
  155. // Gets the height from ground clamped between min and max step height
  156. public float stepHeightFromGround {
  157. get {
  158. return Mathf.Clamp(heightFromGround, -grounding.maxStep, grounding.maxStep);
  159. }
  160. }
  161. // Get predicted Capsule hit from the middle of the foot
  162. private RaycastHit GetCapsuleHit(Vector3 offsetFromHeel)
  163. {
  164. RaycastHit hit = new RaycastHit();
  165. Vector3 f = grounding.GetFootCenterOffset();
  166. if (invertFootCenter) f = -f;
  167. Vector3 origin = transformPosition + f;
  168. if (grounding.overstepFallsDown)
  169. {
  170. hit.point = origin - up * grounding.maxStep;
  171. }
  172. else
  173. {
  174. hit.point = new Vector3(origin.x, grounding.root.position.y, origin.z);
  175. }
  176. hit.normal = up;
  177. // Start point of the capsule
  178. Vector3 capsuleStart = origin + grounding.maxStep * up;
  179. // End point of the capsule depending on the foot's velocity.
  180. Vector3 capsuleEnd = capsuleStart + offsetFromHeel;
  181. if (Physics.CapsuleCast(capsuleStart, capsuleEnd, grounding.footRadius, -up, out hit, grounding.maxStep * 2, grounding.layers, QueryTriggerInteraction.Ignore))
  182. {
  183. // Safeguarding from a CapsuleCast bug in Unity that might cause it to return NaN for hit.point when cast against large colliders.
  184. if (float.IsNaN(hit.point.x))
  185. {
  186. hit.point = origin - up * grounding.maxStep * 2f;
  187. hit.normal = up;
  188. }
  189. }
  190. // Since Unity2017 Raycasts will return Vector3.zero when starting from inside a collider
  191. if (hit.point == Vector3.zero && hit.normal == Vector3.zero)
  192. {
  193. if (grounding.overstepFallsDown)
  194. {
  195. hit.point = origin - up * grounding.maxStep;
  196. }
  197. else
  198. {
  199. hit.point = new Vector3(origin.x, grounding.root.position.y, origin.z);
  200. }
  201. }
  202. return hit;
  203. }
  204. // Get simple Raycast from the heel
  205. private RaycastHit GetRaycastHit(Vector3 offsetFromHeel)
  206. {
  207. RaycastHit hit = new RaycastHit();
  208. Vector3 origin = transformPosition + offsetFromHeel;
  209. if (grounding.overstepFallsDown)
  210. {
  211. hit.point = origin - up * grounding.maxStep;
  212. }
  213. else
  214. {
  215. hit.point = new Vector3(origin.x, grounding.root.position.y, origin.z);
  216. }
  217. hit.normal = up;
  218. if (grounding.maxStep <= 0f) return hit;
  219. Physics.Raycast(origin + grounding.maxStep * up, -up, out hit, grounding.maxStep * 2, grounding.layers, QueryTriggerInteraction.Ignore);
  220. // Since Unity2017 Raycasts will return Vector3.zero when starting from inside a collider
  221. if (hit.point == Vector3.zero && hit.normal == Vector3.zero)
  222. {
  223. if (grounding.overstepFallsDown)
  224. {
  225. hit.point = origin - up * grounding.maxStep;
  226. }
  227. else
  228. {
  229. hit.point = new Vector3(origin.x, grounding.root.position.y, origin.z);
  230. }
  231. }
  232. return hit;
  233. }
  234. // Rotates ground normal with respect to maxFootRotationAngle
  235. private Vector3 RotateNormal(Vector3 normal) {
  236. if (grounding.quality == Grounding.Quality.Best) return normal;
  237. return Vector3.RotateTowards(up, normal, grounding.maxFootRotationAngle * Mathf.Deg2Rad, deltaTime);
  238. }
  239. // Set foot height from ground relative to a point
  240. private void SetFootToPoint(Vector3 normal, Vector3 point) {
  241. toHitNormal = Quaternion.FromToRotation(up, RotateNormal(normal));
  242. heightFromGround = GetHeightFromGround(point);
  243. }
  244. // Set foot height from ground relative to a plane
  245. private void SetFootToPlane(Vector3 planeNormal, Vector3 planePoint, Vector3 heelHitPoint) {
  246. planeNormal = RotateNormal(planeNormal);
  247. toHitNormal = Quaternion.FromToRotation(up, planeNormal);
  248. Vector3 pointOnPlane = V3Tools.LineToPlane(transformPosition + up * grounding.maxStep, -up, planeNormal, planePoint);
  249. // Get the height offset of the point on the plane
  250. heightFromGround = GetHeightFromGround(pointOnPlane);
  251. // Making sure the heel doesn't penetrate the ground
  252. float heelHeight = GetHeightFromGround(heelHitPoint);
  253. heightFromGround = Mathf.Clamp(heightFromGround, -Mathf.Infinity, heelHeight);
  254. }
  255. // Calculate height offset of a point
  256. private float GetHeightFromGround(Vector3 hitPoint) {
  257. return grounding.GetVerticalOffset(transformPosition, hitPoint) - rootYOffset;
  258. }
  259. // Adding ground normal offset to the foot's rotation
  260. private void RotateFoot() {
  261. // Getting the full target rotation
  262. Quaternion rotationOffsetTarget = GetRotationOffsetTarget();
  263. // Slerping the rotation offset
  264. r = Quaternion.Slerp(r, rotationOffsetTarget, deltaTime * grounding.footRotationSpeed);
  265. }
  266. // Gets the target hit normal offset as a Quaternion
  267. private Quaternion GetRotationOffsetTarget() {
  268. if (grounding.maxFootRotationAngle <= 0f) return Quaternion.identity;
  269. if (grounding.maxFootRotationAngle >= 180f) return toHitNormal;
  270. return Quaternion.RotateTowards(Quaternion.identity, toHitNormal, grounding.maxFootRotationAngle);
  271. }
  272. // The foot's height from ground in the animation
  273. private float rootYOffset {
  274. get {
  275. return grounding.GetVerticalOffset(transformPosition, grounding.root.position - up * grounding.heightOffset);
  276. }
  277. }
  278. }
  279. }
  280. }