boneLookController.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. import { ArrayTools } from "../Misc/arrayTools.js";
  2. import { Vector3, Quaternion, Matrix } from "../Maths/math.vector.js";
  3. import { Space, Axis } from "../Maths/math.axis.js";
  4. /**
  5. * Class used to make a bone look toward a point in space
  6. * @see https://doc.babylonjs.com/features/featuresDeepDive/mesh/bonesSkeletons#bonelookcontroller
  7. */
  8. export class BoneLookController {
  9. /**
  10. * Gets or sets the minimum yaw angle that the bone can look to
  11. */
  12. get minYaw() {
  13. return this._minYaw;
  14. }
  15. set minYaw(value) {
  16. this._minYaw = value;
  17. this._minYawSin = Math.sin(value);
  18. this._minYawCos = Math.cos(value);
  19. if (this._maxYaw != null) {
  20. this._midYawConstraint = this._getAngleDiff(this._minYaw, this._maxYaw) * 0.5 + this._minYaw;
  21. this._yawRange = this._maxYaw - this._minYaw;
  22. }
  23. }
  24. /**
  25. * Gets or sets the maximum yaw angle that the bone can look to
  26. */
  27. get maxYaw() {
  28. return this._maxYaw;
  29. }
  30. set maxYaw(value) {
  31. this._maxYaw = value;
  32. this._maxYawSin = Math.sin(value);
  33. this._maxYawCos = Math.cos(value);
  34. if (this._minYaw != null) {
  35. this._midYawConstraint = this._getAngleDiff(this._minYaw, this._maxYaw) * 0.5 + this._minYaw;
  36. this._yawRange = this._maxYaw - this._minYaw;
  37. }
  38. }
  39. /**
  40. * Gets or sets the minimum pitch angle that the bone can look to
  41. */
  42. get minPitch() {
  43. return this._minPitch;
  44. }
  45. set minPitch(value) {
  46. this._minPitch = value;
  47. this._minPitchTan = Math.tan(value);
  48. }
  49. /**
  50. * Gets or sets the maximum pitch angle that the bone can look to
  51. */
  52. get maxPitch() {
  53. return this._maxPitch;
  54. }
  55. set maxPitch(value) {
  56. this._maxPitch = value;
  57. this._maxPitchTan = Math.tan(value);
  58. }
  59. /**
  60. * Create a BoneLookController
  61. * @param mesh the TransformNode that the bone belongs to
  62. * @param bone the bone that will be looking to the target
  63. * @param target the target Vector3 to look at
  64. * @param options optional settings:
  65. * * maxYaw: the maximum angle the bone will yaw to
  66. * * minYaw: the minimum angle the bone will yaw to
  67. * * maxPitch: the maximum angle the bone will pitch to
  68. * * minPitch: the minimum angle the bone will yaw to
  69. * * slerpAmount: set the between 0 and 1 to make the bone slerp to the target.
  70. * * upAxis: the up axis of the coordinate system
  71. * * upAxisSpace: the space that the up axis is in - Space.BONE, Space.LOCAL (default), or Space.WORLD.
  72. * * yawAxis: set yawAxis if the bone does not yaw on the y axis
  73. * * pitchAxis: set pitchAxis if the bone does not pitch on the x axis
  74. * * adjustYaw: used to make an adjustment to the yaw of the bone
  75. * * adjustPitch: used to make an adjustment to the pitch of the bone
  76. * * adjustRoll: used to make an adjustment to the roll of the bone
  77. * @param options.maxYaw
  78. * @param options.minYaw
  79. * @param options.maxPitch
  80. * @param options.minPitch
  81. * @param options.slerpAmount
  82. * @param options.upAxis
  83. * @param options.upAxisSpace
  84. * @param options.yawAxis
  85. * @param options.pitchAxis
  86. * @param options.adjustYaw
  87. * @param options.adjustPitch
  88. * @param options.adjustRoll
  89. **/
  90. constructor(mesh, bone, target, options) {
  91. /**
  92. * The up axis of the coordinate system that is used when the bone is rotated
  93. */
  94. this.upAxis = Vector3.Up();
  95. /**
  96. * The space that the up axis is in - Space.BONE, Space.LOCAL (default), or Space.WORLD
  97. */
  98. this.upAxisSpace = Space.LOCAL;
  99. /**
  100. * Used to make an adjustment to the yaw of the bone
  101. */
  102. this.adjustYaw = 0;
  103. /**
  104. * Used to make an adjustment to the pitch of the bone
  105. */
  106. this.adjustPitch = 0;
  107. /**
  108. * Used to make an adjustment to the roll of the bone
  109. */
  110. this.adjustRoll = 0;
  111. /**
  112. * 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)
  113. */
  114. this.slerpAmount = 1;
  115. this._boneQuat = Quaternion.Identity();
  116. this._slerping = false;
  117. this._firstFrameSkipped = false;
  118. this._fowardAxis = Vector3.Forward();
  119. /**
  120. * Use the absolute value for yaw when checking the min/max constraints
  121. */
  122. this.useAbsoluteValueForYaw = false;
  123. this.mesh = mesh;
  124. this.bone = bone;
  125. this.target = target;
  126. if (options) {
  127. if (options.adjustYaw) {
  128. this.adjustYaw = options.adjustYaw;
  129. }
  130. if (options.adjustPitch) {
  131. this.adjustPitch = options.adjustPitch;
  132. }
  133. if (options.adjustRoll) {
  134. this.adjustRoll = options.adjustRoll;
  135. }
  136. if (options.maxYaw != null) {
  137. this.maxYaw = options.maxYaw;
  138. }
  139. else {
  140. this.maxYaw = Math.PI;
  141. }
  142. if (options.minYaw != null) {
  143. this.minYaw = options.minYaw;
  144. }
  145. else {
  146. this.minYaw = -Math.PI;
  147. }
  148. if (options.maxPitch != null) {
  149. this.maxPitch = options.maxPitch;
  150. }
  151. else {
  152. this.maxPitch = Math.PI;
  153. }
  154. if (options.minPitch != null) {
  155. this.minPitch = options.minPitch;
  156. }
  157. else {
  158. this.minPitch = -Math.PI;
  159. }
  160. if (options.slerpAmount != null) {
  161. this.slerpAmount = options.slerpAmount;
  162. }
  163. if (options.upAxis != null) {
  164. this.upAxis = options.upAxis;
  165. }
  166. if (options.upAxisSpace != null) {
  167. this.upAxisSpace = options.upAxisSpace;
  168. }
  169. if (options.yawAxis != null || options.pitchAxis != null) {
  170. let newYawAxis = Axis.Y;
  171. let newPitchAxis = Axis.X;
  172. if (options.yawAxis != null) {
  173. newYawAxis = options.yawAxis.clone();
  174. newYawAxis.normalize();
  175. }
  176. if (options.pitchAxis != null) {
  177. newPitchAxis = options.pitchAxis.clone();
  178. newPitchAxis.normalize();
  179. }
  180. const newRollAxis = Vector3.Cross(newPitchAxis, newYawAxis);
  181. this._transformYawPitch = Matrix.Identity();
  182. Matrix.FromXYZAxesToRef(newPitchAxis, newYawAxis, newRollAxis, this._transformYawPitch);
  183. this._transformYawPitchInv = this._transformYawPitch.clone();
  184. this._transformYawPitch.invert();
  185. }
  186. if (options.useAbsoluteValueForYaw !== undefined) {
  187. this.useAbsoluteValueForYaw = options.useAbsoluteValueForYaw;
  188. }
  189. }
  190. if (!bone.getParent() && this.upAxisSpace == Space.BONE) {
  191. this.upAxisSpace = Space.LOCAL;
  192. }
  193. }
  194. /**
  195. * Update the bone to look at the target. This should be called before the scene is rendered (use scene.registerBeforeRender())
  196. */
  197. update() {
  198. //skip the first frame when slerping so that the TransformNode rotation is correct
  199. if (this.slerpAmount < 1 && !this._firstFrameSkipped) {
  200. this._firstFrameSkipped = true;
  201. return;
  202. }
  203. const bone = this.bone;
  204. const bonePos = BoneLookController._TmpVecs[0];
  205. bone.getAbsolutePositionToRef(this.mesh, bonePos);
  206. let target = this.target;
  207. const _tmpMat1 = BoneLookController._TmpMats[0];
  208. const _tmpMat2 = BoneLookController._TmpMats[1];
  209. const mesh = this.mesh;
  210. const parentBone = bone.getParent();
  211. const upAxis = BoneLookController._TmpVecs[1];
  212. upAxis.copyFrom(this.upAxis);
  213. if (this.upAxisSpace == Space.BONE && parentBone) {
  214. if (this._transformYawPitch) {
  215. Vector3.TransformCoordinatesToRef(upAxis, this._transformYawPitchInv, upAxis);
  216. }
  217. parentBone.getDirectionToRef(upAxis, this.mesh, upAxis);
  218. }
  219. else if (this.upAxisSpace == Space.LOCAL) {
  220. mesh.getDirectionToRef(upAxis, upAxis);
  221. if (mesh.scaling.x != 1 || mesh.scaling.y != 1 || mesh.scaling.z != 1) {
  222. upAxis.normalize();
  223. }
  224. }
  225. let checkYaw = false;
  226. let checkPitch = false;
  227. if (this._maxYaw != Math.PI || this._minYaw != -Math.PI) {
  228. checkYaw = true;
  229. }
  230. if (this._maxPitch != Math.PI || this._minPitch != -Math.PI) {
  231. checkPitch = true;
  232. }
  233. if (checkYaw || checkPitch) {
  234. const spaceMat = BoneLookController._TmpMats[2];
  235. const spaceMatInv = BoneLookController._TmpMats[3];
  236. if (this.upAxisSpace == Space.BONE && upAxis.y == 1 && parentBone) {
  237. parentBone.getRotationMatrixToRef(Space.WORLD, this.mesh, spaceMat);
  238. }
  239. else if (this.upAxisSpace == Space.LOCAL && upAxis.y == 1 && !parentBone) {
  240. spaceMat.copyFrom(mesh.getWorldMatrix());
  241. }
  242. else {
  243. let forwardAxis = BoneLookController._TmpVecs[2];
  244. forwardAxis.copyFrom(this._fowardAxis);
  245. if (this._transformYawPitch) {
  246. Vector3.TransformCoordinatesToRef(forwardAxis, this._transformYawPitchInv, forwardAxis);
  247. }
  248. if (parentBone) {
  249. parentBone.getDirectionToRef(forwardAxis, this.mesh, forwardAxis);
  250. }
  251. else {
  252. mesh.getDirectionToRef(forwardAxis, forwardAxis);
  253. }
  254. const rightAxis = Vector3.Cross(upAxis, forwardAxis);
  255. rightAxis.normalize();
  256. forwardAxis = Vector3.Cross(rightAxis, upAxis);
  257. Matrix.FromXYZAxesToRef(rightAxis, upAxis, forwardAxis, spaceMat);
  258. }
  259. spaceMat.invertToRef(spaceMatInv);
  260. let xzlen = null;
  261. if (checkPitch) {
  262. const localTarget = BoneLookController._TmpVecs[3];
  263. target.subtractToRef(bonePos, localTarget);
  264. Vector3.TransformCoordinatesToRef(localTarget, spaceMatInv, localTarget);
  265. xzlen = Math.sqrt(localTarget.x * localTarget.x + localTarget.z * localTarget.z);
  266. const pitch = Math.atan2(localTarget.y, xzlen);
  267. let newPitch = pitch;
  268. if (pitch > this._maxPitch) {
  269. localTarget.y = this._maxPitchTan * xzlen;
  270. newPitch = this._maxPitch;
  271. }
  272. else if (pitch < this._minPitch) {
  273. localTarget.y = this._minPitchTan * xzlen;
  274. newPitch = this._minPitch;
  275. }
  276. if (pitch != newPitch) {
  277. Vector3.TransformCoordinatesToRef(localTarget, spaceMat, localTarget);
  278. localTarget.addInPlace(bonePos);
  279. target = localTarget;
  280. }
  281. }
  282. if (checkYaw) {
  283. const localTarget = BoneLookController._TmpVecs[4];
  284. target.subtractToRef(bonePos, localTarget);
  285. Vector3.TransformCoordinatesToRef(localTarget, spaceMatInv, localTarget);
  286. const yaw = Math.atan2(localTarget.x, localTarget.z);
  287. const yawCheck = this.useAbsoluteValueForYaw ? Math.abs(yaw) : yaw;
  288. let newYaw = yaw;
  289. if (yawCheck > this._maxYaw || yawCheck < this._minYaw) {
  290. if (xzlen == null) {
  291. xzlen = Math.sqrt(localTarget.x * localTarget.x + localTarget.z * localTarget.z);
  292. }
  293. if (this._yawRange > Math.PI) {
  294. if (this._isAngleBetween(yaw, this._maxYaw, this._midYawConstraint)) {
  295. localTarget.z = this._maxYawCos * xzlen;
  296. localTarget.x = this._maxYawSin * xzlen;
  297. newYaw = this._maxYaw;
  298. }
  299. else if (this._isAngleBetween(yaw, this._midYawConstraint, this._minYaw)) {
  300. localTarget.z = this._minYawCos * xzlen;
  301. localTarget.x = this._minYawSin * xzlen;
  302. newYaw = this._minYaw;
  303. }
  304. }
  305. else {
  306. if (yawCheck > this._maxYaw) {
  307. localTarget.z = this._maxYawCos * xzlen;
  308. localTarget.x = this._maxYawSin * xzlen;
  309. if (yaw < 0 && this.useAbsoluteValueForYaw) {
  310. localTarget.x *= -1;
  311. }
  312. newYaw = this._maxYaw;
  313. }
  314. else if (yawCheck < this._minYaw) {
  315. localTarget.z = this._minYawCos * xzlen;
  316. localTarget.x = this._minYawSin * xzlen;
  317. if (yaw < 0 && this.useAbsoluteValueForYaw) {
  318. localTarget.x *= -1;
  319. }
  320. newYaw = this._minYaw;
  321. }
  322. }
  323. }
  324. if (this._slerping && this._yawRange > Math.PI) {
  325. //are we going to be crossing into the min/max region?
  326. const boneFwd = BoneLookController._TmpVecs[8];
  327. boneFwd.copyFrom(Axis.Z);
  328. if (this._transformYawPitch) {
  329. Vector3.TransformCoordinatesToRef(boneFwd, this._transformYawPitchInv, boneFwd);
  330. }
  331. const boneRotMat = BoneLookController._TmpMats[4];
  332. this._boneQuat.toRotationMatrix(boneRotMat);
  333. this.mesh.getWorldMatrix().multiplyToRef(boneRotMat, boneRotMat);
  334. Vector3.TransformCoordinatesToRef(boneFwd, boneRotMat, boneFwd);
  335. Vector3.TransformCoordinatesToRef(boneFwd, spaceMatInv, boneFwd);
  336. const boneYaw = Math.atan2(boneFwd.x, boneFwd.z);
  337. const angBtwTar = this._getAngleBetween(boneYaw, yaw);
  338. const angBtwMidYaw = this._getAngleBetween(boneYaw, this._midYawConstraint);
  339. if (angBtwTar > angBtwMidYaw) {
  340. if (xzlen == null) {
  341. xzlen = Math.sqrt(localTarget.x * localTarget.x + localTarget.z * localTarget.z);
  342. }
  343. const angBtwMax = this._getAngleBetween(boneYaw, this._maxYaw);
  344. const angBtwMin = this._getAngleBetween(boneYaw, this._minYaw);
  345. if (angBtwMin < angBtwMax) {
  346. newYaw = boneYaw + Math.PI * 0.75;
  347. localTarget.z = Math.cos(newYaw) * xzlen;
  348. localTarget.x = Math.sin(newYaw) * xzlen;
  349. }
  350. else {
  351. newYaw = boneYaw - Math.PI * 0.75;
  352. localTarget.z = Math.cos(newYaw) * xzlen;
  353. localTarget.x = Math.sin(newYaw) * xzlen;
  354. }
  355. }
  356. }
  357. if (yaw != newYaw) {
  358. Vector3.TransformCoordinatesToRef(localTarget, spaceMat, localTarget);
  359. localTarget.addInPlace(bonePos);
  360. target = localTarget;
  361. }
  362. }
  363. }
  364. const zaxis = BoneLookController._TmpVecs[5];
  365. const xaxis = BoneLookController._TmpVecs[6];
  366. const yaxis = BoneLookController._TmpVecs[7];
  367. const tmpQuat = BoneLookController._TmpQuat;
  368. const boneScaling = BoneLookController._TmpVecs[9];
  369. target.subtractToRef(bonePos, zaxis);
  370. zaxis.normalize();
  371. Vector3.CrossToRef(upAxis, zaxis, xaxis);
  372. xaxis.normalize();
  373. Vector3.CrossToRef(zaxis, xaxis, yaxis);
  374. yaxis.normalize();
  375. Matrix.FromXYZAxesToRef(xaxis, yaxis, zaxis, _tmpMat1);
  376. if (xaxis.x === 0 && xaxis.y === 0 && xaxis.z === 0) {
  377. return;
  378. }
  379. if (yaxis.x === 0 && yaxis.y === 0 && yaxis.z === 0) {
  380. return;
  381. }
  382. if (zaxis.x === 0 && zaxis.y === 0 && zaxis.z === 0) {
  383. return;
  384. }
  385. if (this.adjustYaw || this.adjustPitch || this.adjustRoll) {
  386. Matrix.RotationYawPitchRollToRef(this.adjustYaw, this.adjustPitch, this.adjustRoll, _tmpMat2);
  387. _tmpMat2.multiplyToRef(_tmpMat1, _tmpMat1);
  388. }
  389. boneScaling.copyFrom(this.bone.getScale());
  390. if (this.slerpAmount < 1) {
  391. if (!this._slerping) {
  392. this.bone.getRotationQuaternionToRef(Space.WORLD, this.mesh, this._boneQuat);
  393. }
  394. if (this._transformYawPitch) {
  395. this._transformYawPitch.multiplyToRef(_tmpMat1, _tmpMat1);
  396. }
  397. Quaternion.FromRotationMatrixToRef(_tmpMat1, tmpQuat);
  398. Quaternion.SlerpToRef(this._boneQuat, tmpQuat, this.slerpAmount, this._boneQuat);
  399. this.bone.setRotationQuaternion(this._boneQuat, Space.WORLD, this.mesh);
  400. this._slerping = true;
  401. }
  402. else {
  403. if (this._transformYawPitch) {
  404. this._transformYawPitch.multiplyToRef(_tmpMat1, _tmpMat1);
  405. }
  406. this.bone.setRotationMatrix(_tmpMat1, Space.WORLD, this.mesh);
  407. this._slerping = false;
  408. }
  409. this.bone.setScale(boneScaling);
  410. this._updateLinkedTransformRotation();
  411. }
  412. _getAngleDiff(ang1, ang2) {
  413. let angDiff = ang2 - ang1;
  414. angDiff %= Math.PI * 2;
  415. if (angDiff > Math.PI) {
  416. angDiff -= Math.PI * 2;
  417. }
  418. else if (angDiff < -Math.PI) {
  419. angDiff += Math.PI * 2;
  420. }
  421. return angDiff;
  422. }
  423. _getAngleBetween(ang1, ang2) {
  424. ang1 %= 2 * Math.PI;
  425. ang1 = ang1 < 0 ? ang1 + 2 * Math.PI : ang1;
  426. ang2 %= 2 * Math.PI;
  427. ang2 = ang2 < 0 ? ang2 + 2 * Math.PI : ang2;
  428. let ab = 0;
  429. if (ang1 < ang2) {
  430. ab = ang2 - ang1;
  431. }
  432. else {
  433. ab = ang1 - ang2;
  434. }
  435. if (ab > Math.PI) {
  436. ab = Math.PI * 2 - ab;
  437. }
  438. return ab;
  439. }
  440. _isAngleBetween(ang, ang1, ang2) {
  441. ang %= 2 * Math.PI;
  442. ang = ang < 0 ? ang + 2 * Math.PI : ang;
  443. ang1 %= 2 * Math.PI;
  444. ang1 = ang1 < 0 ? ang1 + 2 * Math.PI : ang1;
  445. ang2 %= 2 * Math.PI;
  446. ang2 = ang2 < 0 ? ang2 + 2 * Math.PI : ang2;
  447. if (ang1 < ang2) {
  448. if (ang > ang1 && ang < ang2) {
  449. return true;
  450. }
  451. }
  452. else {
  453. if (ang > ang2 && ang < ang1) {
  454. return true;
  455. }
  456. }
  457. return false;
  458. }
  459. _updateLinkedTransformRotation() {
  460. const bone = this.bone;
  461. if (bone._linkedTransformNode) {
  462. if (!bone._linkedTransformNode.rotationQuaternion) {
  463. bone._linkedTransformNode.rotationQuaternion = new Quaternion();
  464. }
  465. bone.getRotationQuaternionToRef(Space.LOCAL, null, bone._linkedTransformNode.rotationQuaternion);
  466. }
  467. }
  468. }
  469. BoneLookController._TmpVecs = ArrayTools.BuildArray(10, Vector3.Zero);
  470. BoneLookController._TmpQuat = Quaternion.Identity();
  471. BoneLookController._TmpMats = ArrayTools.BuildArray(5, Matrix.Identity);
  472. //# sourceMappingURL=boneLookController.js.map