GrounderQuadruped.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. using UnityEngine;
  2. using System.Collections;
  3. namespace RootMotion.FinalIK {
  4. /// <summary>
  5. /// Grounding for LimbIK, CCD and/or FABRIK solvers.
  6. /// </summary>
  7. [HelpURL("http://www.root-motion.com/finalikdox/html/page9.html")]
  8. [AddComponentMenu("Scripts/RootMotion.FinalIK/Grounder/Grounder Quadruped")]
  9. public class GrounderQuadruped: Grounder {
  10. // Open the User Manual URL
  11. [ContextMenu("User Manual")]
  12. protected override void OpenUserManual() {
  13. Application.OpenURL("http://www.root-motion.com/finalikdox/html/page9.html");
  14. }
  15. // Open the Script Reference URL
  16. [ContextMenu("Scrpt Reference")]
  17. protected override void OpenScriptReference() {
  18. Application.OpenURL("http://www.root-motion.com/finalikdox/html/class_root_motion_1_1_final_i_k_1_1_grounder_quadruped.html");
  19. }
  20. #region Main Interface
  21. /// <summary>
  22. /// The %Grounding solver for the forelegs.
  23. /// </summary>
  24. [Tooltip("The Grounding solver for the forelegs.")]
  25. public Grounding forelegSolver = new Grounding();
  26. /// <summary>
  27. /// The weight of rotating the character root to the ground angle (range: 0 - 1).
  28. /// </summary>
  29. [Tooltip("The weight of rotating the character root to the ground angle (range: 0 - 1).")]
  30. [Range(0f, 1f)]
  31. public float rootRotationWeight = 0.5f;
  32. /// <summary>
  33. /// The maximum angle of rotating the quadruped downwards (going downhill, range: -90 - 0).
  34. /// </summary>
  35. [Tooltip("The maximum angle of rotating the quadruped downwards (going downhill, range: -90 - 0).")]
  36. [Range(-90f, 0f)]
  37. public float minRootRotation = -25f;
  38. /// <summary>
  39. /// The maximum angle of rotating the quadruped upwards (going uphill, range: 0 - 90).
  40. /// </summary>
  41. [Tooltip("The maximum angle of rotating the quadruped upwards (going uphill, range: 0 - 90).")]
  42. [Range(0f, 90f)]
  43. public float maxRootRotation = 45f;
  44. /// <summary>
  45. /// The speed of interpolating the character root rotation (range: 0 - inf).
  46. /// </summary>
  47. [Tooltip("The speed of interpolating the character root rotation (range: 0 - inf).")]
  48. public float rootRotationSpeed = 5f;
  49. /// <summary>
  50. /// The maximum IK offset for the legs (range: 0 - inf).
  51. /// </summary>
  52. [Tooltip("The maximum IK offset for the legs (range: 0 - inf).")]
  53. public float maxLegOffset = 0.5f;
  54. /// <summary>
  55. /// The maximum IK offset for the forelegs (range: 0 - inf).
  56. /// </summary>
  57. [Tooltip("The maximum IK offset for the forelegs (range: 0 - inf).")]
  58. public float maxForeLegOffset = 0.5f;
  59. /// <summary>
  60. /// The weight of maintaining the head's rotation as it was before solving the Grounding (range: 0 - 1).
  61. /// </summary>
  62. [Tooltip("The weight of maintaining the head's rotation as it was before solving the Grounding (range: 0 - 1).")]
  63. [Range(0f, 1f)]
  64. public float maintainHeadRotationWeight = 0.5f;
  65. /// <summary>
  66. /// The root Transform of the character, with the rigidbody and the collider.
  67. /// </summary>
  68. [Tooltip("The root Transform of the character, with the rigidbody and the collider.")]
  69. public Transform characterRoot;
  70. /// <summary>
  71. /// The pelvis transform. Common ancestor of both legs and the spine.
  72. /// </summary>
  73. [Tooltip("The pelvis transform. Common ancestor of both legs and the spine.")]
  74. public Transform pelvis;
  75. /// <summary>
  76. /// The last bone in the spine that is the common parent for both forelegs.
  77. /// </summary>
  78. [Tooltip("The last bone in the spine that is the common parent for both forelegs.")]
  79. public Transform lastSpineBone;
  80. /// <summary>
  81. /// The head (optional, if you intend to maintain it's rotation).
  82. /// </summary>
  83. [Tooltip("The head (optional, if you intend to maintain it's rotation).")]
  84. public Transform head;
  85. /// <summary>
  86. /// %IK componets of the hindlegs. Can be any type of IK components.
  87. /// </summary>
  88. public IK[] legs;
  89. /// <summary>
  90. /// %IK components for the forelegs. Can be any type of IK components.
  91. /// </summary>
  92. public IK[] forelegs;
  93. /// <summary>
  94. /// When using GrounderQuadruped on a spherical object, update this vector to always point towards the center of that object.
  95. /// </summary>
  96. [HideInInspector] public Vector3 gravity = Vector3.down;
  97. #endregion Main Interface
  98. public override void ResetPosition() {
  99. solver.Reset();
  100. forelegSolver.Reset();
  101. }
  102. // Contains all the required information about a foot
  103. public struct Foot {
  104. public IKSolver solver;
  105. public Transform transform;
  106. public Quaternion rotation;
  107. public Grounding.Leg leg;
  108. // The custom constructor
  109. public Foot (IKSolver solver, Transform transform) {
  110. this.solver = solver;
  111. this.transform = transform;
  112. this.leg = null;
  113. rotation = transform.rotation;
  114. }
  115. }
  116. private Foot[] feet = new Foot[0];
  117. private Vector3 animatedPelvisLocalPosition;
  118. private Quaternion animatedPelvisLocalRotation;
  119. private Quaternion animatedHeadLocalRotation;
  120. private Vector3 solvedPelvisLocalPosition;
  121. private Quaternion solvedPelvisLocalRotation;
  122. private Quaternion solvedHeadLocalRotation;
  123. private int solvedFeet;
  124. private bool solved;
  125. private float angle;
  126. private Transform forefeetRoot;
  127. private Quaternion headRotation;
  128. private float lastWeight;
  129. private Rigidbody characterRootRigidbody;
  130. // Can we initiate the Grounding?
  131. private bool IsReadyToInitiate() {
  132. if (pelvis == null) return false;
  133. if (lastSpineBone == null) return false;
  134. if (legs.Length == 0) return false;
  135. if (forelegs.Length == 0) return false;
  136. if (characterRoot == null) return false;
  137. if (!IsReadyToInitiateLegs(legs)) return false;
  138. if (!IsReadyToInitiateLegs(forelegs)) return false;
  139. return true;
  140. }
  141. // Are the leg IK components valid for initiation?
  142. private bool IsReadyToInitiateLegs(IK[] ikComponents) {
  143. foreach (IK leg in ikComponents) {
  144. if (leg == null) return false;
  145. if (leg is FullBodyBipedIK) {
  146. LogWarning("GrounderIK does not support FullBodyBipedIK, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead. If you want to use FullBodyBipedIK, use the GrounderFBBIK component.");
  147. return false;
  148. }
  149. if (leg is FABRIKRoot) {
  150. LogWarning("GrounderIK does not support FABRIKRoot, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead.");
  151. return false;
  152. }
  153. if (leg is AimIK) {
  154. LogWarning("GrounderIK does not support AimIK, use CCDIK, FABRIK, LimbIK or TrigonometricIK instead.");
  155. return false;
  156. }
  157. }
  158. return true;
  159. }
  160. // Weigh out the IK solvers properly when the component is disabled
  161. void OnDisable() {
  162. if (!initiated) return;
  163. for (int i = 0; i < feet.Length; i++) {
  164. if (feet[i].solver != null) feet[i].solver.IKPositionWeight = 0f;
  165. }
  166. }
  167. // Initiate once we have all the required components
  168. void Update() {
  169. weight = Mathf.Clamp(weight, 0f, 1f);
  170. if (weight <= 0f) return;
  171. solved = false;
  172. if (initiated) return;
  173. if (!IsReadyToInitiate()) return;
  174. Initiate();
  175. }
  176. // Initiate this Grounder
  177. private void Initiate() {
  178. // Building the feet
  179. feet = new Foot[legs.Length + forelegs.Length];
  180. // Gathering the last bones of the IK solvers as feet
  181. Transform[] footBones = InitiateFeet(legs, ref feet, 0);
  182. Transform[] forefootBones = InitiateFeet(forelegs, ref feet, legs.Length);
  183. // Store the default localPosition and localRotation of the pelvis
  184. animatedPelvisLocalPosition = pelvis.localPosition;
  185. animatedPelvisLocalRotation = pelvis.localRotation;
  186. if (head != null) animatedHeadLocalRotation = head.localRotation;
  187. forefeetRoot = new GameObject().transform;
  188. forefeetRoot.parent = transform;
  189. forefeetRoot.name = "Forefeet Root";
  190. // Initiate the Grounding
  191. solver.Initiate(transform, footBones);
  192. forelegSolver.Initiate(forefeetRoot, forefootBones);
  193. for (int i = 0; i < footBones.Length; i++) feet[i].leg = solver.legs[i];
  194. for (int i = 0; i < forefootBones.Length; i++) feet[i + legs.Length].leg = forelegSolver.legs[i];
  195. characterRootRigidbody = characterRoot.GetComponent<Rigidbody>();
  196. initiated = true;
  197. }
  198. // Initiate the feet
  199. private Transform[] InitiateFeet(IK[] ikComponents, ref Foot[] f, int indexOffset) {
  200. Transform[] bones = new Transform[ikComponents.Length];
  201. for (int i = 0; i < ikComponents.Length; i++) {
  202. IKSolver.Point[] points = ikComponents[i].GetIKSolver().GetPoints();
  203. f[i + indexOffset] = new Foot(ikComponents[i].GetIKSolver(), points[points.Length - 1].transform);
  204. bones[i] = f[i + indexOffset].transform;
  205. // Add to the update delegates of each ik solver
  206. f[i + indexOffset].solver.OnPreUpdate += OnSolverUpdate;
  207. f[i + indexOffset].solver.OnPostUpdate += OnPostSolverUpdate;
  208. }
  209. return bones;
  210. }
  211. void LateUpdate () {
  212. if (weight <= 0f) return;
  213. // Clamping values
  214. rootRotationWeight = Mathf.Clamp(rootRotationWeight, 0f, 1f);
  215. minRootRotation = Mathf.Clamp(minRootRotation, -90f, maxRootRotation);
  216. maxRootRotation = Mathf.Clamp(maxRootRotation, minRootRotation, 90f);
  217. rootRotationSpeed = Mathf.Clamp(rootRotationSpeed, 0f, rootRotationSpeed);
  218. maxLegOffset = Mathf.Clamp(maxLegOffset, 0f, maxLegOffset);
  219. maxForeLegOffset = Mathf.Clamp(maxForeLegOffset, 0f, maxForeLegOffset);
  220. maintainHeadRotationWeight = Mathf.Clamp(maintainHeadRotationWeight, 0f, 1f);
  221. // Rotate the character root
  222. RootRotation();
  223. }
  224. // Rotate the character along with the terrain
  225. private void RootRotation() {
  226. if (rootRotationWeight <= 0f) return;
  227. if (rootRotationSpeed <= 0f) return;
  228. solver.rotateSolver = true;
  229. forelegSolver.rotateSolver = true;
  230. // Get the horizontal rotation of the character
  231. Vector3 tangent = characterRoot.forward;
  232. Vector3 normal = -gravity;
  233. Vector3.OrthoNormalize(ref normal, ref tangent);
  234. Quaternion horizontalRotation = Quaternion.LookRotation(tangent, -gravity);
  235. // Get the direction from root hit to forelegs root hit in the space of the horizontal character rotation
  236. Vector3 hitDirection = forelegSolver.rootHit.point - solver.rootHit.point;
  237. Vector3 hitDirectionLocal = Quaternion.Inverse(horizontalRotation) * hitDirection;
  238. // Get the angle between the horizontal and hit directions
  239. float angleTarget = Mathf.Atan2(hitDirectionLocal.y, hitDirectionLocal.z) * Mathf.Rad2Deg;
  240. angleTarget = Mathf.Clamp(angleTarget * rootRotationWeight, minRootRotation, maxRootRotation);
  241. // Interpolate the angle
  242. angle = Mathf.Lerp(angle, angleTarget, Time.deltaTime * rootRotationSpeed);
  243. if (characterRootRigidbody == null) {
  244. characterRoot.rotation = Quaternion.Slerp(characterRoot.rotation, Quaternion.AngleAxis(-angle, characterRoot.right) * horizontalRotation, weight);
  245. } else {
  246. characterRootRigidbody.MoveRotation(Quaternion.Slerp(characterRoot.rotation, Quaternion.AngleAxis(-angle, characterRoot.right) * horizontalRotation, weight));
  247. }
  248. }
  249. // Called before updating the first IK solver
  250. private void OnSolverUpdate() {
  251. if (!enabled) return;
  252. if (weight <= 0f) {
  253. if (lastWeight <= 0f) return;
  254. // Weigh out the limb solvers properly
  255. OnDisable();
  256. }
  257. lastWeight = weight;
  258. // If another IK has already solved in this frame, do nothing
  259. if (solved) return;
  260. if (OnPreGrounder != null) OnPreGrounder();
  261. // If the bone transforms have not changed since last solved state, consider them unanimated
  262. if (pelvis.localPosition != solvedPelvisLocalPosition) animatedPelvisLocalPosition = pelvis.localPosition;
  263. else pelvis.localPosition = animatedPelvisLocalPosition;
  264. if (pelvis.localRotation != solvedPelvisLocalRotation) animatedPelvisLocalRotation = pelvis.localRotation;
  265. else pelvis.localRotation = animatedPelvisLocalRotation;
  266. if (head != null) {
  267. if (head.localRotation != solvedHeadLocalRotation) animatedHeadLocalRotation = head.localRotation;
  268. else head.localRotation = animatedHeadLocalRotation;
  269. }
  270. for (int i = 0; i < feet.Length; i++) feet[i].rotation = feet[i].transform.rotation;
  271. // Store the head rotation so it could be maintained later
  272. if (head != null) headRotation = head.rotation;
  273. // Position the forefeet root to the center of forefeet
  274. UpdateForefeetRoot();
  275. // Update the Grounding
  276. solver.Update();
  277. forelegSolver.Update();
  278. // Move the pelvis
  279. pelvis.position += solver.pelvis.IKOffset * weight;
  280. // Rotate the pelvis
  281. Vector3 spineDirection = lastSpineBone.position - pelvis.position;
  282. Vector3 newSpinePosition =
  283. lastSpineBone.position +
  284. forelegSolver.root.up * Mathf.Clamp(forelegSolver.pelvis.heightOffset, Mathf.NegativeInfinity, 0f) -
  285. solver.root.up * solver.pelvis.heightOffset;
  286. Vector3 newDirection = newSpinePosition - pelvis.position;
  287. Quaternion f = Quaternion.FromToRotation(spineDirection, newDirection);
  288. pelvis.rotation = Quaternion.Slerp(Quaternion.identity, f, weight) * pelvis.rotation;
  289. // Update the IKPositions and IKPositonWeights of the legs
  290. for (int i = 0; i < feet.Length; i++) SetFootIK(feet[i], (i < 2? maxLegOffset: maxForeLegOffset));
  291. solved = true;
  292. solvedFeet = 0;
  293. if (OnPostGrounder != null) OnPostGrounder();
  294. }
  295. // Position the forefeet root to the center of forefeet
  296. private void UpdateForefeetRoot() {
  297. // Get the centroid
  298. Vector3 foreFeetCenter = Vector3.zero;
  299. for (int i = 0; i < forelegSolver.legs.Length; i++) {
  300. foreFeetCenter += forelegSolver.legs[i].transform.position;
  301. }
  302. foreFeetCenter /= (float)forelegs.Length;
  303. Vector3 dir = foreFeetCenter - transform.position;
  304. // Ortho-normalize to this Transform's rotation
  305. Vector3 normal = transform.up;
  306. Vector3 tangent = dir;
  307. Vector3.OrthoNormalize(ref normal, ref tangent);
  308. // Positioning the forefeet root
  309. forefeetRoot.position = transform.position + tangent.normalized * dir.magnitude;
  310. }
  311. // Set the IK position and weight for a limb
  312. private void SetFootIK(Foot foot, float maxOffset) {
  313. Vector3 direction = foot.leg.IKPosition - foot.transform.position;
  314. foot.solver.IKPosition = foot.transform.position + Vector3.ClampMagnitude(direction, maxOffset);
  315. foot.solver.IKPositionWeight = weight;
  316. }
  317. // Rotating the feet after IK has finished
  318. private void OnPostSolverUpdate() {
  319. if (weight <= 0f) return;
  320. if (!enabled) return;
  321. // Only do this after the last IK solver has finished
  322. solvedFeet ++;
  323. if (solvedFeet < feet.Length) return;
  324. for (int i = 0; i < feet.Length; i++) {
  325. feet[i].transform.rotation = Quaternion.Slerp(Quaternion.identity, feet[i].leg.rotationOffset, weight) * feet[i].rotation;
  326. }
  327. if (head != null) head.rotation = Quaternion.Lerp(head.rotation, headRotation, maintainHeadRotationWeight * weight);
  328. // Store the solved transform's of the bones so we know if they are not animated
  329. solvedPelvisLocalPosition = pelvis.localPosition;
  330. solvedPelvisLocalRotation = pelvis.localRotation;
  331. if (head != null) solvedHeadLocalRotation = head.localRotation;
  332. }
  333. // Cleaning up the delegates
  334. void OnDestroy() {
  335. if (initiated) {
  336. DestroyLegs(legs);
  337. DestroyLegs(forelegs);
  338. }
  339. }
  340. // Cleaning up the delegates
  341. private void DestroyLegs(IK[] ikComponents) {
  342. foreach (IK leg in ikComponents) {
  343. if (leg != null) {
  344. leg.GetIKSolver().OnPreUpdate -= OnSolverUpdate;
  345. leg.GetIKSolver().OnPostUpdate -= OnPostSolverUpdate;
  346. }
  347. }
  348. }
  349. }
  350. }