boneLookController.ts 20 KB

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