boneLookController.ts 20 KB

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