boneIKController.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import { Vector3, Quaternion, Matrix } from "../Maths/math.vector.js";
  2. import { Space } from "../Maths/math.axis.js";
  3. import { Logger } from "../Misc/logger.js";
  4. /**
  5. * Class used to apply inverse kinematics to bones
  6. * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/bonesSkeletons#boneikcontroller
  7. */
  8. export class BoneIKController {
  9. /**
  10. * Gets or sets maximum allowed angle
  11. */
  12. get maxAngle() {
  13. return this._maxAngle;
  14. }
  15. set maxAngle(value) {
  16. this._setMaxAngle(value);
  17. }
  18. /**
  19. * Creates a new BoneIKController
  20. * @param mesh defines the TransformNode to control
  21. * @param bone defines the bone to control. The bone needs to have a parent bone. It also needs to have a length greater than 0 or a children we can use to infer its length.
  22. * @param options defines options to set up the controller
  23. * @param options.targetMesh
  24. * @param options.poleTargetMesh
  25. * @param options.poleTargetBone
  26. * @param options.poleTargetLocalOffset
  27. * @param options.poleAngle
  28. * @param options.bendAxis
  29. * @param options.maxAngle
  30. * @param options.slerpAmount
  31. */
  32. constructor(mesh, bone, options) {
  33. /**
  34. * Gets or sets the target position
  35. */
  36. this.targetPosition = Vector3.Zero();
  37. /**
  38. * Gets or sets the pole target position
  39. */
  40. this.poleTargetPosition = Vector3.Zero();
  41. /**
  42. * Gets or sets the pole target local offset
  43. */
  44. this.poleTargetLocalOffset = Vector3.Zero();
  45. /**
  46. * Gets or sets the pole angle
  47. */
  48. this.poleAngle = 0;
  49. /**
  50. * The amount to slerp (spherical linear interpolation) to the target. Set this to a value between 0 and 1 (a value of 1 disables slerp)
  51. */
  52. this.slerpAmount = 1;
  53. this._bone1Quat = Quaternion.Identity();
  54. this._bone1Mat = Matrix.Identity();
  55. this._bone2Ang = Math.PI;
  56. this._maxAngle = Math.PI;
  57. this._rightHandedSystem = false;
  58. this._bendAxis = Vector3.Right();
  59. this._slerping = false;
  60. this._adjustRoll = 0;
  61. this._notEnoughInformation = false;
  62. this._bone2 = bone;
  63. const bone1 = bone.getParent();
  64. if (!bone1) {
  65. this._notEnoughInformation = true;
  66. Logger.Error("BoneIKController: bone must have a parent for IK to work.");
  67. return;
  68. }
  69. this._bone1 = bone1;
  70. if (this._bone2.children.length === 0 && !this._bone2.length) {
  71. this._notEnoughInformation = true;
  72. Logger.Error("BoneIKController: bone must not be a leaf or it should have a length for IK to work.");
  73. return;
  74. }
  75. this.mesh = mesh;
  76. bone.getSkeleton().computeAbsoluteMatrices();
  77. const bonePos = bone.getPosition();
  78. if (bone.getAbsoluteMatrix().determinant() > 0) {
  79. this._rightHandedSystem = true;
  80. this._bendAxis.x = 0;
  81. this._bendAxis.y = 0;
  82. this._bendAxis.z = -1;
  83. if (bonePos.x > bonePos.y && bonePos.x > bonePos.z) {
  84. this._adjustRoll = Math.PI * 0.5;
  85. this._bendAxis.z = 1;
  86. }
  87. }
  88. if (this._bone1.length && this._bone2.length) {
  89. const boneScale1 = this._bone1.getScale();
  90. const boneScale2 = this._bone2.getScale();
  91. this._bone1Length = this._bone1.length * boneScale1.y * this.mesh.scaling.y;
  92. this._bone2Length = this._bone2.length * boneScale2.y * this.mesh.scaling.y;
  93. }
  94. else if (this._bone2.children[0]) {
  95. mesh.computeWorldMatrix(true);
  96. const pos1 = this._bone2.children[0].getAbsolutePosition(mesh);
  97. const pos2 = this._bone2.getAbsolutePosition(mesh);
  98. const pos3 = this._bone1.getAbsolutePosition(mesh);
  99. this._bone2Length = Vector3.Distance(pos1, pos2);
  100. this._bone1Length = Vector3.Distance(pos2, pos3);
  101. }
  102. else {
  103. mesh.computeWorldMatrix(true);
  104. const boneScale2 = this._bone2.getScale();
  105. this._bone2Length = this._bone2.length * boneScale2.y * this.mesh.scaling.y;
  106. const pos2 = this._bone2.getAbsolutePosition(mesh);
  107. const pos3 = this._bone1.getAbsolutePosition(mesh);
  108. this._bone1Length = Vector3.Distance(pos2, pos3);
  109. }
  110. this._bone1.getRotationMatrixToRef(Space.WORLD, mesh, this._bone1Mat);
  111. this.maxAngle = Math.PI;
  112. if (options) {
  113. if (options.targetMesh) {
  114. this.targetMesh = options.targetMesh;
  115. this.targetMesh.computeWorldMatrix(true);
  116. }
  117. if (options.poleTargetMesh) {
  118. this.poleTargetMesh = options.poleTargetMesh;
  119. this.poleTargetMesh.computeWorldMatrix(true);
  120. }
  121. else if (options.poleTargetBone) {
  122. this.poleTargetBone = options.poleTargetBone;
  123. }
  124. else if (this._bone1.getParent()) {
  125. this.poleTargetBone = this._bone1.getParent();
  126. }
  127. if (options.poleTargetLocalOffset) {
  128. this.poleTargetLocalOffset.copyFrom(options.poleTargetLocalOffset);
  129. }
  130. if (options.poleAngle) {
  131. this.poleAngle = options.poleAngle;
  132. }
  133. if (options.bendAxis) {
  134. this._bendAxis.copyFrom(options.bendAxis);
  135. }
  136. if (options.maxAngle) {
  137. this.maxAngle = options.maxAngle;
  138. }
  139. if (options.slerpAmount) {
  140. this.slerpAmount = options.slerpAmount;
  141. }
  142. }
  143. }
  144. _setMaxAngle(ang) {
  145. if (ang < 0) {
  146. ang = 0;
  147. }
  148. if (ang > Math.PI || ang == undefined) {
  149. ang = Math.PI;
  150. }
  151. this._maxAngle = ang;
  152. const a = this._bone1Length;
  153. const b = this._bone2Length;
  154. this._maxReach = Math.sqrt(a * a + b * b - 2 * a * b * Math.cos(ang));
  155. }
  156. /**
  157. * Force the controller to update the bones
  158. */
  159. update() {
  160. if (this._notEnoughInformation) {
  161. return;
  162. }
  163. const target = this.targetPosition;
  164. const poleTarget = this.poleTargetPosition;
  165. const mat1 = BoneIKController._TmpMats[0];
  166. const mat2 = BoneIKController._TmpMats[1];
  167. if (this.targetMesh) {
  168. target.copyFrom(this.targetMesh.getAbsolutePosition());
  169. }
  170. if (this.poleTargetBone) {
  171. this.poleTargetBone.getAbsolutePositionFromLocalToRef(this.poleTargetLocalOffset, this.mesh, poleTarget);
  172. }
  173. else if (this.poleTargetMesh) {
  174. Vector3.TransformCoordinatesToRef(this.poleTargetLocalOffset, this.poleTargetMesh.getWorldMatrix(), poleTarget);
  175. }
  176. const bonePos = BoneIKController._TmpVecs[0];
  177. const zaxis = BoneIKController._TmpVecs[1];
  178. const xaxis = BoneIKController._TmpVecs[2];
  179. const yaxis = BoneIKController._TmpVecs[3];
  180. const upAxis = BoneIKController._TmpVecs[4];
  181. const tmpQuat = BoneIKController._TmpQuat;
  182. this._bone1.getAbsolutePositionToRef(this.mesh, bonePos);
  183. poleTarget.subtractToRef(bonePos, upAxis);
  184. if (upAxis.x == 0 && upAxis.y == 0 && upAxis.z == 0) {
  185. upAxis.y = 1;
  186. }
  187. else {
  188. upAxis.normalize();
  189. }
  190. target.subtractToRef(bonePos, yaxis);
  191. yaxis.normalize();
  192. Vector3.CrossToRef(yaxis, upAxis, zaxis);
  193. zaxis.normalize();
  194. Vector3.CrossToRef(yaxis, zaxis, xaxis);
  195. xaxis.normalize();
  196. Matrix.FromXYZAxesToRef(xaxis, yaxis, zaxis, mat1);
  197. const a = this._bone1Length;
  198. const b = this._bone2Length;
  199. let c = Vector3.Distance(bonePos, target);
  200. if (this._maxReach > 0) {
  201. c = Math.min(this._maxReach, c);
  202. }
  203. let acosa = (b * b + c * c - a * a) / (2 * b * c);
  204. let acosb = (c * c + a * a - b * b) / (2 * c * a);
  205. if (acosa > 1) {
  206. acosa = 1;
  207. }
  208. if (acosb > 1) {
  209. acosb = 1;
  210. }
  211. if (acosa < -1) {
  212. acosa = -1;
  213. }
  214. if (acosb < -1) {
  215. acosb = -1;
  216. }
  217. const angA = Math.acos(acosa);
  218. const angB = Math.acos(acosb);
  219. let angC = -angA - angB;
  220. if (this._rightHandedSystem) {
  221. Matrix.RotationYawPitchRollToRef(0, 0, this._adjustRoll, mat2);
  222. mat2.multiplyToRef(mat1, mat1);
  223. Matrix.RotationAxisToRef(this._bendAxis, angB, mat2);
  224. mat2.multiplyToRef(mat1, mat1);
  225. }
  226. else {
  227. const _tmpVec = BoneIKController._TmpVecs[5];
  228. _tmpVec.copyFrom(this._bendAxis);
  229. _tmpVec.x *= -1;
  230. Matrix.RotationAxisToRef(_tmpVec, -angB, mat2);
  231. mat2.multiplyToRef(mat1, mat1);
  232. }
  233. if (this.poleAngle) {
  234. Matrix.RotationAxisToRef(yaxis, this.poleAngle, mat2);
  235. mat1.multiplyToRef(mat2, mat1);
  236. }
  237. if (this._bone1) {
  238. if (this.slerpAmount < 1) {
  239. if (!this._slerping) {
  240. Quaternion.FromRotationMatrixToRef(this._bone1Mat, this._bone1Quat);
  241. }
  242. Quaternion.FromRotationMatrixToRef(mat1, tmpQuat);
  243. Quaternion.SlerpToRef(this._bone1Quat, tmpQuat, this.slerpAmount, this._bone1Quat);
  244. angC = this._bone2Ang * (1.0 - this.slerpAmount) + angC * this.slerpAmount;
  245. this._bone1.setRotationQuaternion(this._bone1Quat, Space.WORLD, this.mesh);
  246. this._slerping = true;
  247. }
  248. else {
  249. this._bone1.setRotationMatrix(mat1, Space.WORLD, this.mesh);
  250. this._bone1Mat.copyFrom(mat1);
  251. this._slerping = false;
  252. }
  253. this._updateLinkedTransformRotation(this._bone1);
  254. }
  255. this._bone2.setAxisAngle(this._bendAxis, angC, Space.LOCAL);
  256. this._updateLinkedTransformRotation(this._bone2);
  257. this._bone2Ang = angC;
  258. }
  259. _updateLinkedTransformRotation(bone) {
  260. if (bone._linkedTransformNode) {
  261. if (!bone._linkedTransformNode.rotationQuaternion) {
  262. bone._linkedTransformNode.rotationQuaternion = new Quaternion();
  263. }
  264. bone.getRotationQuaternionToRef(Space.LOCAL, null, bone._linkedTransformNode.rotationQuaternion);
  265. }
  266. }
  267. }
  268. BoneIKController._TmpVecs = [Vector3.Zero(), Vector3.Zero(), Vector3.Zero(), Vector3.Zero(), Vector3.Zero(), Vector3.Zero()];
  269. BoneIKController._TmpQuat = Quaternion.Identity();
  270. BoneIKController._TmpMats = [Matrix.Identity(), Matrix.Identity()];
  271. //# sourceMappingURL=boneIKController.js.map