babylon.transformNode.ts 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053
  1. module BABYLON {
  2. export class TransformNode extends Node {
  3. // Statics
  4. public static BILLBOARDMODE_NONE = 0;
  5. public static BILLBOARDMODE_X = 1;
  6. public static BILLBOARDMODE_Y = 2;
  7. public static BILLBOARDMODE_Z = 4;
  8. public static BILLBOARDMODE_ALL = 7;
  9. private _forward = new Vector3(0, 0, 1);
  10. private _forwardInverted = new Vector3(0, 0, -1);
  11. private _up = new Vector3(0, 1, 0);
  12. private _right = new Vector3(1, 0, 0);
  13. private _rightInverted = new Vector3(-1, 0, 0);
  14. // Properties
  15. @serializeAsVector3()
  16. private _rotation = Vector3.Zero();
  17. @serializeAsQuaternion()
  18. private _rotationQuaternion: Nullable<Quaternion>;
  19. @serializeAsVector3()
  20. protected _scaling = Vector3.One();
  21. protected _isDirty = false;
  22. private _transformToBoneReferal: Nullable<TransformNode>;
  23. /**
  24. * Set the billboard mode. Default is 0.
  25. *
  26. * | Value | Type | Description |
  27. * | --- | --- | --- |
  28. * | 0 | BILLBOARDMODE_NONE | |
  29. * | 1 | BILLBOARDMODE_X | |
  30. * | 2 | BILLBOARDMODE_Y | |
  31. * | 4 | BILLBOARDMODE_Z | |
  32. * | 7 | BILLBOARDMODE_ALL | |
  33. *
  34. */
  35. @serialize()
  36. public billboardMode = TransformNode.BILLBOARDMODE_NONE;
  37. @serialize()
  38. public scalingDeterminant = 1;
  39. @serialize()
  40. public infiniteDistance = false;
  41. /**
  42. * Gets or sets a boolean indicating that non uniform scaling (when at least one component is different from others) should be ignored.
  43. * By default the system will update normals to compensate
  44. */
  45. @serialize()
  46. public ignoreNonUniformScaling = false;
  47. @serializeAsVector3()
  48. public position = Vector3.Zero();
  49. // Cache
  50. public _poseMatrix: Matrix;
  51. private _localWorld = Matrix.Zero();
  52. public _worldMatrix = Matrix.Zero();
  53. public _worldMatrixDeterminant = 0;
  54. private _absolutePosition = Vector3.Zero();
  55. private _pivotMatrix = Matrix.Identity();
  56. private _pivotMatrixInverse: Matrix;
  57. private _postMultiplyPivotMatrix = false;
  58. protected _isWorldMatrixFrozen = false;
  59. /**
  60. * An event triggered after the world matrix is updated
  61. */
  62. public onAfterWorldMatrixUpdateObservable = new Observable<TransformNode>();
  63. constructor(name: string, scene: Nullable<Scene> = null, isPure = true) {
  64. super(name, scene);
  65. if (isPure) {
  66. this.getScene().addTransformNode(this);
  67. }
  68. }
  69. /**
  70. * Gets a string idenfifying the name of the class
  71. * @returns "TransformNode" string
  72. */
  73. public getClassName(): string {
  74. return "TransformNode";
  75. }
  76. /**
  77. * Rotation property : a Vector3 depicting the rotation value in radians around each local axis X, Y, Z.
  78. * If rotation quaternion is set, this Vector3 will (almost always) be the Zero vector!
  79. * Default : (0.0, 0.0, 0.0)
  80. */
  81. public get rotation(): Vector3 {
  82. return this._rotation;
  83. }
  84. public set rotation(newRotation: Vector3) {
  85. this._rotation = newRotation;
  86. }
  87. /**
  88. * Scaling property : a Vector3 depicting the mesh scaling along each local axis X, Y, Z.
  89. * Default : (1.0, 1.0, 1.0)
  90. */
  91. public get scaling(): Vector3 {
  92. return this._scaling;
  93. }
  94. /**
  95. * Scaling property : a Vector3 depicting the mesh scaling along each local axis X, Y, Z.
  96. * Default : (1.0, 1.0, 1.0)
  97. */
  98. public set scaling(newScaling: Vector3) {
  99. this._scaling = newScaling;
  100. }
  101. /**
  102. * Rotation Quaternion property : this a Quaternion object depicting the mesh rotation by using a unit quaternion.
  103. * It's null by default.
  104. * If set, only the rotationQuaternion is then used to compute the mesh rotation and its property `.rotation\ is then ignored and set to (0.0, 0.0, 0.0)
  105. */
  106. public get rotationQuaternion(): Nullable<Quaternion> {
  107. return this._rotationQuaternion;
  108. }
  109. public set rotationQuaternion(quaternion: Nullable<Quaternion>) {
  110. this._rotationQuaternion = quaternion;
  111. //reset the rotation vector.
  112. if (quaternion && this.rotation.length()) {
  113. this.rotation.copyFromFloats(0.0, 0.0, 0.0);
  114. }
  115. }
  116. /**
  117. * The forward direction of that transform in world space.
  118. */
  119. public get forward(): Vector3 {
  120. return Vector3.Normalize(Vector3.TransformNormal(
  121. this.getScene().useRightHandedSystem ? this._forwardInverted : this._forward,
  122. this.getWorldMatrix()
  123. ));
  124. }
  125. /**
  126. * The up direction of that transform in world space.
  127. */
  128. public get up(): Vector3 {
  129. return Vector3.Normalize(Vector3.TransformNormal(
  130. this._up,
  131. this.getWorldMatrix()
  132. ));
  133. }
  134. /**
  135. * The right direction of that transform in world space.
  136. */
  137. public get right(): Vector3 {
  138. return Vector3.Normalize(Vector3.TransformNormal(
  139. this.getScene().useRightHandedSystem ? this._rightInverted : this._right,
  140. this.getWorldMatrix()
  141. ));
  142. }
  143. /**
  144. * Returns the latest update of the World matrix
  145. * Returns a Matrix.
  146. */
  147. public getWorldMatrix(): Matrix {
  148. if (this._currentRenderId !== this.getScene().getRenderId()) {
  149. this.computeWorldMatrix();
  150. }
  151. return this._worldMatrix;
  152. }
  153. /** @hidden */
  154. public _getWorldMatrixDeterminant(): number {
  155. return this._worldMatrixDeterminant;
  156. }
  157. /**
  158. * Returns directly the latest state of the mesh World matrix.
  159. * A Matrix is returned.
  160. */
  161. public get worldMatrixFromCache(): Matrix {
  162. return this._worldMatrix;
  163. }
  164. /**
  165. * Copies the paramater passed Matrix into the mesh Pose matrix.
  166. * Returns the TransformNode.
  167. */
  168. public updatePoseMatrix(matrix: Matrix): TransformNode {
  169. this._poseMatrix.copyFrom(matrix);
  170. return this;
  171. }
  172. /**
  173. * Returns the mesh Pose matrix.
  174. * Returned object : Matrix
  175. */
  176. public getPoseMatrix(): Matrix {
  177. return this._poseMatrix;
  178. }
  179. public _isSynchronized(): boolean {
  180. if (this._isDirty) {
  181. return false;
  182. }
  183. if (this.billboardMode !== this._cache.billboardMode || this.billboardMode !== TransformNode.BILLBOARDMODE_NONE)
  184. return false;
  185. if (this._cache.pivotMatrixUpdated) {
  186. return false;
  187. }
  188. if (this.infiniteDistance) {
  189. return false;
  190. }
  191. if (!this._cache.position.equals(this.position))
  192. return false;
  193. if (this.rotationQuaternion) {
  194. if (!this._cache.rotationQuaternion.equals(this.rotationQuaternion))
  195. return false;
  196. }
  197. if (!this._cache.rotation.equals(this.rotation))
  198. return false;
  199. if (!this._cache.scaling.equals(this.scaling))
  200. return false;
  201. return true;
  202. }
  203. public _initCache() {
  204. super._initCache();
  205. this._cache.localMatrixUpdated = false;
  206. this._cache.position = Vector3.Zero();
  207. this._cache.scaling = Vector3.Zero();
  208. this._cache.rotation = Vector3.Zero();
  209. this._cache.rotationQuaternion = new Quaternion(0, 0, 0, 0);
  210. this._cache.billboardMode = -1;
  211. }
  212. public markAsDirty(property: string): TransformNode {
  213. if (property === "rotation") {
  214. this.rotationQuaternion = null;
  215. }
  216. this._currentRenderId = Number.MAX_VALUE;
  217. this._isDirty = true;
  218. return this;
  219. }
  220. /**
  221. * Returns the current mesh absolute position.
  222. * Retuns a Vector3.
  223. */
  224. public get absolutePosition(): Vector3 {
  225. return this._absolutePosition;
  226. }
  227. /**
  228. * Sets a new matrix to apply before all other transformation
  229. * @param matrix defines the transform matrix
  230. * @returns the current TransformNode
  231. */
  232. public setPreTransformMatrix(matrix: Matrix): TransformNode {
  233. return this.setPivotMatrix(matrix, false);
  234. }
  235. /**
  236. * Sets a new pivot matrix to the current node
  237. * @param matrix defines the new pivot matrix to use
  238. * @param postMultiplyPivotMatrix defines if the pivot matrix must be cancelled in the world matrix. When this parameter is set to true (default), the inverse of the pivot matrix is also applied at the end to cancel the transformation effect
  239. * @returns the current TransformNode
  240. */
  241. public setPivotMatrix(matrix: Matrix, postMultiplyPivotMatrix = true): TransformNode {
  242. this._pivotMatrix = matrix.clone();
  243. this._cache.pivotMatrixUpdated = true;
  244. this._postMultiplyPivotMatrix = postMultiplyPivotMatrix;
  245. if (this._postMultiplyPivotMatrix) {
  246. if (!this._pivotMatrixInverse) {
  247. this._pivotMatrixInverse = Matrix.Invert(this._pivotMatrix);
  248. } else {
  249. this._pivotMatrix.invertToRef(this._pivotMatrixInverse);
  250. }
  251. }
  252. return this;
  253. }
  254. /**
  255. * Returns the mesh pivot matrix.
  256. * Default : Identity.
  257. * A Matrix is returned.
  258. */
  259. public getPivotMatrix(): Matrix {
  260. return this._pivotMatrix;
  261. }
  262. /**
  263. * Prevents the World matrix to be computed any longer.
  264. * Returns the TransformNode.
  265. */
  266. public freezeWorldMatrix(): TransformNode {
  267. this._isWorldMatrixFrozen = false; // no guarantee world is not already frozen, switch off temporarily
  268. this.computeWorldMatrix(true);
  269. this._isWorldMatrixFrozen = true;
  270. return this;
  271. }
  272. /**
  273. * Allows back the World matrix computation.
  274. * Returns the TransformNode.
  275. */
  276. public unfreezeWorldMatrix() {
  277. this._isWorldMatrixFrozen = false;
  278. this.computeWorldMatrix(true);
  279. return this;
  280. }
  281. /**
  282. * True if the World matrix has been frozen.
  283. * Returns a boolean.
  284. */
  285. public get isWorldMatrixFrozen(): boolean {
  286. return this._isWorldMatrixFrozen;
  287. }
  288. /**
  289. * Retuns the mesh absolute position in the World.
  290. * Returns a Vector3.
  291. */
  292. public getAbsolutePosition(): Vector3 {
  293. this.computeWorldMatrix();
  294. return this._absolutePosition;
  295. }
  296. /**
  297. * Sets the mesh absolute position in the World from a Vector3 or an Array(3).
  298. * Returns the TransformNode.
  299. */
  300. public setAbsolutePosition(absolutePosition: Vector3): TransformNode {
  301. if (!absolutePosition) {
  302. return this;
  303. }
  304. var absolutePositionX;
  305. var absolutePositionY;
  306. var absolutePositionZ;
  307. if (absolutePosition.x === undefined) {
  308. if (arguments.length < 3) {
  309. return this;
  310. }
  311. absolutePositionX = arguments[0];
  312. absolutePositionY = arguments[1];
  313. absolutePositionZ = arguments[2];
  314. }
  315. else {
  316. absolutePositionX = absolutePosition.x;
  317. absolutePositionY = absolutePosition.y;
  318. absolutePositionZ = absolutePosition.z;
  319. }
  320. if (this.parent) {
  321. var invertParentWorldMatrix = this.parent.getWorldMatrix().clone();
  322. invertParentWorldMatrix.invert();
  323. var worldPosition = new Vector3(absolutePositionX, absolutePositionY, absolutePositionZ);
  324. this.position = Vector3.TransformCoordinates(worldPosition, invertParentWorldMatrix);
  325. } else {
  326. this.position.x = absolutePositionX;
  327. this.position.y = absolutePositionY;
  328. this.position.z = absolutePositionZ;
  329. }
  330. return this;
  331. }
  332. /**
  333. * Sets the mesh position in its local space.
  334. * Returns the TransformNode.
  335. */
  336. public setPositionWithLocalVector(vector3: Vector3): TransformNode {
  337. this.computeWorldMatrix();
  338. this.position = Vector3.TransformNormal(vector3, this._localWorld);
  339. return this;
  340. }
  341. /**
  342. * Returns the mesh position in the local space from the current World matrix values.
  343. * Returns a new Vector3.
  344. */
  345. public getPositionExpressedInLocalSpace(): Vector3 {
  346. this.computeWorldMatrix();
  347. var invLocalWorldMatrix = this._localWorld.clone();
  348. invLocalWorldMatrix.invert();
  349. return Vector3.TransformNormal(this.position, invLocalWorldMatrix);
  350. }
  351. /**
  352. * Translates the mesh along the passed Vector3 in its local space.
  353. * Returns the TransformNode.
  354. */
  355. public locallyTranslate(vector3: Vector3): TransformNode {
  356. this.computeWorldMatrix(true);
  357. this.position = Vector3.TransformCoordinates(vector3, this._localWorld);
  358. return this;
  359. }
  360. private static _lookAtVectorCache = new Vector3(0, 0, 0);
  361. /**
  362. * Orients a mesh towards a target point. Mesh must be drawn facing user.
  363. * @param targetPoint the position (must be in same space as current mesh) to look at
  364. * @param yawCor optional yaw (y-axis) correction in radians
  365. * @param pitchCor optional pitch (x-axis) correction in radians
  366. * @param rollCor optional roll (z-axis) correction in radians
  367. * @param space the choosen space of the target
  368. * @returns the TransformNode.
  369. */
  370. public lookAt(targetPoint: Vector3, yawCor: number = 0, pitchCor: number = 0, rollCor: number = 0, space: Space = Space.LOCAL): TransformNode {
  371. var dv = TransformNode._lookAtVectorCache;
  372. var pos = space === Space.LOCAL ? this.position : this.getAbsolutePosition();
  373. targetPoint.subtractToRef(pos, dv);
  374. var yaw = -Math.atan2(dv.z, dv.x) - Math.PI / 2;
  375. var len = Math.sqrt(dv.x * dv.x + dv.z * dv.z);
  376. var pitch = Math.atan2(dv.y, len);
  377. if (this.rotationQuaternion) {
  378. Quaternion.RotationYawPitchRollToRef(yaw + yawCor, pitch + pitchCor, rollCor, this.rotationQuaternion);
  379. }
  380. else {
  381. this.rotation.x = pitch + pitchCor;
  382. this.rotation.y = yaw + yawCor;
  383. this.rotation.z = rollCor;
  384. }
  385. return this;
  386. }
  387. /**
  388. * Returns a new Vector3 what is the localAxis, expressed in the mesh local space, rotated like the mesh.
  389. * This Vector3 is expressed in the World space.
  390. */
  391. public getDirection(localAxis: Vector3): Vector3 {
  392. var result = Vector3.Zero();
  393. this.getDirectionToRef(localAxis, result);
  394. return result;
  395. }
  396. /**
  397. * Sets the Vector3 "result" as the rotated Vector3 "localAxis" in the same rotation than the mesh.
  398. * localAxis is expressed in the mesh local space.
  399. * result is computed in the Wordl space from the mesh World matrix.
  400. * Returns the TransformNode.
  401. */
  402. public getDirectionToRef(localAxis: Vector3, result: Vector3): TransformNode {
  403. Vector3.TransformNormalToRef(localAxis, this.getWorldMatrix(), result);
  404. return this;
  405. }
  406. /**
  407. * Sets a new pivot point to the current node
  408. * @param point defines the new pivot point to use
  409. * @param space defines if the point is in world or local space (local by default)
  410. * @returns the current TransformNode
  411. */
  412. public setPivotPoint(point: Vector3, space: Space = Space.LOCAL): TransformNode {
  413. if (this.getScene().getRenderId() == 0) {
  414. this.computeWorldMatrix(true);
  415. }
  416. var wm = this.getWorldMatrix();
  417. if (space == Space.WORLD) {
  418. var tmat = Tmp.Matrix[0];
  419. wm.invertToRef(tmat);
  420. point = Vector3.TransformCoordinates(point, tmat);
  421. }
  422. return this.setPivotMatrix(Matrix.Translation(-point.x, -point.y, -point.z), true);
  423. }
  424. /**
  425. * Returns a new Vector3 set with the mesh pivot point coordinates in the local space.
  426. */
  427. public getPivotPoint(): Vector3 {
  428. var point = Vector3.Zero();
  429. this.getPivotPointToRef(point);
  430. return point;
  431. }
  432. /**
  433. * Sets the passed Vector3 "result" with the coordinates of the mesh pivot point in the local space.
  434. * Returns the TransformNode.
  435. */
  436. public getPivotPointToRef(result: Vector3): TransformNode {
  437. result.x = -this._pivotMatrix.m[12];
  438. result.y = -this._pivotMatrix.m[13];
  439. result.z = -this._pivotMatrix.m[14];
  440. return this;
  441. }
  442. /**
  443. * Returns a new Vector3 set with the mesh pivot point World coordinates.
  444. */
  445. public getAbsolutePivotPoint(): Vector3 {
  446. var point = Vector3.Zero();
  447. this.getAbsolutePivotPointToRef(point);
  448. return point;
  449. }
  450. /**
  451. * Sets the Vector3 "result" coordinates with the mesh pivot point World coordinates.
  452. * Returns the TransformNode.
  453. */
  454. public getAbsolutePivotPointToRef(result: Vector3): TransformNode {
  455. result.x = this._pivotMatrix.m[12];
  456. result.y = this._pivotMatrix.m[13];
  457. result.z = this._pivotMatrix.m[14];
  458. this.getPivotPointToRef(result);
  459. Vector3.TransformCoordinatesToRef(result, this.getWorldMatrix(), result);
  460. return this;
  461. }
  462. /**
  463. * Defines the passed node as the parent of the current node.
  464. * The node will remain exactly where it is and its position / rotation will be updated accordingly
  465. * Returns the TransformNode.
  466. */
  467. public setParent(node: Nullable<Node>): TransformNode {
  468. if (!node && !this.parent) {
  469. return this;
  470. }
  471. if (!node) {
  472. var rotation = Tmp.Quaternion[0];
  473. var position = Tmp.Vector3[0];
  474. var scale = Tmp.Vector3[1];
  475. if (this.parent && this.parent.computeWorldMatrix) {
  476. this.parent.computeWorldMatrix(true);
  477. }
  478. this.computeWorldMatrix(true);
  479. this.getWorldMatrix().decompose(scale, rotation, position);
  480. if (this.rotationQuaternion) {
  481. this.rotationQuaternion.copyFrom(rotation);
  482. } else {
  483. rotation.toEulerAnglesToRef(this.rotation);
  484. }
  485. this.scaling.x = scale.x;
  486. this.scaling.y = scale.y;
  487. this.scaling.z = scale.z;
  488. this.position.x = position.x;
  489. this.position.y = position.y;
  490. this.position.z = position.z;
  491. } else {
  492. var rotation = Tmp.Quaternion[0];
  493. var position = Tmp.Vector3[0];
  494. var scale = Tmp.Vector3[1];
  495. var diffMatrix = Tmp.Matrix[0];
  496. var invParentMatrix = Tmp.Matrix[1];
  497. this.computeWorldMatrix(true);
  498. node.computeWorldMatrix(true);
  499. node.getWorldMatrix().invertToRef(invParentMatrix);
  500. this.getWorldMatrix().multiplyToRef(invParentMatrix, diffMatrix);
  501. diffMatrix.decompose(scale, rotation, position);
  502. if (this.rotationQuaternion) {
  503. this.rotationQuaternion.copyFrom(rotation);
  504. } else {
  505. rotation.toEulerAnglesToRef(this.rotation);
  506. }
  507. this.position.x = position.x;
  508. this.position.y = position.y;
  509. this.position.z = position.z;
  510. this.scaling.x = scale.x;
  511. this.scaling.y = scale.y;
  512. this.scaling.z = scale.z;
  513. }
  514. this.parent = node;
  515. return this;
  516. }
  517. private _nonUniformScaling = false;
  518. public get nonUniformScaling(): boolean {
  519. return this._nonUniformScaling;
  520. }
  521. public _updateNonUniformScalingState(value: boolean): boolean {
  522. if (this._nonUniformScaling === value) {
  523. return false;
  524. }
  525. this._nonUniformScaling = value;
  526. return true;
  527. }
  528. /**
  529. * Attach the current TransformNode to another TransformNode associated with a bone
  530. * @param bone Bone affecting the TransformNode
  531. * @param affectedTransformNode TransformNode associated with the bone
  532. */
  533. public attachToBone(bone: Bone, affectedTransformNode: TransformNode): TransformNode {
  534. this._transformToBoneReferal = affectedTransformNode;
  535. this.parent = bone;
  536. if (bone.getWorldMatrix().determinant() < 0) {
  537. this.scalingDeterminant *= -1;
  538. }
  539. return this;
  540. }
  541. public detachFromBone(): TransformNode {
  542. if (!this.parent) {
  543. return this;
  544. }
  545. if (this.parent.getWorldMatrix().determinant() < 0) {
  546. this.scalingDeterminant *= -1;
  547. }
  548. this._transformToBoneReferal = null;
  549. this.parent = null;
  550. return this;
  551. }
  552. private static _rotationAxisCache = new Quaternion();
  553. /**
  554. * Rotates the mesh around the axis vector for the passed angle (amount) expressed in radians, in the given space.
  555. * space (default LOCAL) can be either BABYLON.Space.LOCAL, either BABYLON.Space.WORLD.
  556. * Note that the property `rotationQuaternion` is then automatically updated and the property `rotation` is set to (0,0,0) and no longer used.
  557. * The passed axis is also normalized.
  558. * Returns the TransformNode.
  559. */
  560. public rotate(axis: Vector3, amount: number, space?: Space): TransformNode {
  561. axis.normalize();
  562. if (!this.rotationQuaternion) {
  563. this.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.rotation.y, this.rotation.x, this.rotation.z);
  564. this.rotation = Vector3.Zero();
  565. }
  566. var rotationQuaternion: Quaternion;
  567. if (!space || (space as any) === Space.LOCAL) {
  568. rotationQuaternion = Quaternion.RotationAxisToRef(axis, amount, TransformNode._rotationAxisCache);
  569. this.rotationQuaternion.multiplyToRef(rotationQuaternion, this.rotationQuaternion);
  570. }
  571. else {
  572. if (this.parent) {
  573. var invertParentWorldMatrix = this.parent.getWorldMatrix().clone();
  574. invertParentWorldMatrix.invert();
  575. axis = Vector3.TransformNormal(axis, invertParentWorldMatrix);
  576. }
  577. rotationQuaternion = Quaternion.RotationAxisToRef(axis, amount, TransformNode._rotationAxisCache);
  578. rotationQuaternion.multiplyToRef(this.rotationQuaternion, this.rotationQuaternion);
  579. }
  580. return this;
  581. }
  582. /**
  583. * Rotates the mesh around the axis vector for the passed angle (amount) expressed in radians, in world space.
  584. * Note that the property `rotationQuaternion` is then automatically updated and the property `rotation` is set to (0,0,0) and no longer used.
  585. * The passed axis is also normalized.
  586. * Returns the TransformNode.
  587. * Method is based on http://www.euclideanspace.com/maths/geometry/affine/aroundPoint/index.htm
  588. */
  589. public rotateAround(point: Vector3, axis: Vector3, amount: number): TransformNode {
  590. axis.normalize();
  591. if (!this.rotationQuaternion) {
  592. this.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.rotation.y, this.rotation.x, this.rotation.z);
  593. this.rotation.copyFromFloats(0, 0, 0);
  594. }
  595. point.subtractToRef(this.position, Tmp.Vector3[0]);
  596. Matrix.TranslationToRef(Tmp.Vector3[0].x, Tmp.Vector3[0].y, Tmp.Vector3[0].z, Tmp.Matrix[0]);
  597. Tmp.Matrix[0].invertToRef(Tmp.Matrix[2]);
  598. Matrix.RotationAxisToRef(axis, amount, Tmp.Matrix[1]);
  599. Tmp.Matrix[2].multiplyToRef(Tmp.Matrix[1], Tmp.Matrix[2]);
  600. Tmp.Matrix[2].multiplyToRef(Tmp.Matrix[0], Tmp.Matrix[2]);
  601. Tmp.Matrix[2].decompose(Tmp.Vector3[0], Tmp.Quaternion[0], Tmp.Vector3[1]);
  602. this.position.addInPlace(Tmp.Vector3[1]);
  603. Tmp.Quaternion[0].multiplyToRef(this.rotationQuaternion, this.rotationQuaternion);
  604. return this;
  605. }
  606. /**
  607. * Translates the mesh along the axis vector for the passed distance in the given space.
  608. * space (default LOCAL) can be either BABYLON.Space.LOCAL, either BABYLON.Space.WORLD.
  609. * Returns the TransformNode.
  610. */
  611. public translate(axis: Vector3, distance: number, space?: Space): TransformNode {
  612. var displacementVector = axis.scale(distance);
  613. if (!space || (space as any) === Space.LOCAL) {
  614. var tempV3 = this.getPositionExpressedInLocalSpace().add(displacementVector);
  615. this.setPositionWithLocalVector(tempV3);
  616. }
  617. else {
  618. this.setAbsolutePosition(this.getAbsolutePosition().add(displacementVector));
  619. }
  620. return this;
  621. }
  622. /**
  623. * Adds a rotation step to the mesh current rotation.
  624. * x, y, z are Euler angles expressed in radians.
  625. * This methods updates the current mesh rotation, either mesh.rotation, either mesh.rotationQuaternion if it's set.
  626. * This means this rotation is made in the mesh local space only.
  627. * It's useful to set a custom rotation order different from the BJS standard one YXZ.
  628. * Example : this rotates the mesh first around its local X axis, then around its local Z axis, finally around its local Y axis.
  629. * ```javascript
  630. * mesh.addRotation(x1, 0, 0).addRotation(0, 0, z2).addRotation(0, 0, y3);
  631. * ```
  632. * Note that `addRotation()` accumulates the passed rotation values to the current ones and computes the .rotation or .rotationQuaternion updated values.
  633. * Under the hood, only quaternions are used. So it's a little faster is you use .rotationQuaternion because it doesn't need to translate them back to Euler angles.
  634. * Returns the TransformNode.
  635. */
  636. public addRotation(x: number, y: number, z: number): TransformNode {
  637. var rotationQuaternion;
  638. if (this.rotationQuaternion) {
  639. rotationQuaternion = this.rotationQuaternion;
  640. }
  641. else {
  642. rotationQuaternion = Tmp.Quaternion[1];
  643. Quaternion.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, rotationQuaternion);
  644. }
  645. var accumulation = Tmp.Quaternion[0];
  646. Quaternion.RotationYawPitchRollToRef(y, x, z, accumulation);
  647. rotationQuaternion.multiplyInPlace(accumulation);
  648. if (!this.rotationQuaternion) {
  649. rotationQuaternion.toEulerAnglesToRef(this.rotation);
  650. }
  651. return this;
  652. }
  653. /**
  654. * Computes the mesh World matrix and returns it.
  655. * If the mesh world matrix is frozen, this computation does nothing more than returning the last frozen values.
  656. * If the parameter `force` is let to `false` (default), the current cached World matrix is returned.
  657. * If the parameter `force`is set to `true`, the actual computation is done.
  658. * Returns the mesh World Matrix.
  659. */
  660. public computeWorldMatrix(force?: boolean): Matrix {
  661. if (this._isWorldMatrixFrozen) {
  662. return this._worldMatrix;
  663. }
  664. if (!force && this.isSynchronized(true)) {
  665. this._currentRenderId = this.getScene().getRenderId();
  666. return this._worldMatrix;
  667. }
  668. this._cache.position.copyFrom(this.position);
  669. this._cache.scaling.copyFrom(this.scaling);
  670. this._cache.pivotMatrixUpdated = false;
  671. this._cache.billboardMode = this.billboardMode;
  672. this._currentRenderId = this.getScene().getRenderId();
  673. this._childRenderId = this.getScene().getRenderId();
  674. this._isDirty = false;
  675. // Scaling
  676. Matrix.ScalingToRef(this.scaling.x * this.scalingDeterminant, this.scaling.y * this.scalingDeterminant, this.scaling.z * this.scalingDeterminant, Tmp.Matrix[1]);
  677. // Rotation
  678. //rotate, if quaternion is set and rotation was used
  679. if (this.rotationQuaternion) {
  680. var len = this.rotation.length();
  681. if (len) {
  682. this.rotationQuaternion.multiplyInPlace(Quaternion.RotationYawPitchRoll(this.rotation.y, this.rotation.x, this.rotation.z))
  683. this.rotation.copyFromFloats(0, 0, 0);
  684. }
  685. }
  686. if (this.rotationQuaternion) {
  687. this.rotationQuaternion.toRotationMatrix(Tmp.Matrix[0]);
  688. this._cache.rotationQuaternion.copyFrom(this.rotationQuaternion);
  689. } else {
  690. Matrix.RotationYawPitchRollToRef(this.rotation.y, this.rotation.x, this.rotation.z, Tmp.Matrix[0]);
  691. this._cache.rotation.copyFrom(this.rotation);
  692. }
  693. // Translation
  694. let camera = (<Camera>this.getScene().activeCamera);
  695. if (this.infiniteDistance && !this.parent && camera) {
  696. var cameraWorldMatrix = camera.getWorldMatrix();
  697. var cameraGlobalPosition = new Vector3(cameraWorldMatrix.m[12], cameraWorldMatrix.m[13], cameraWorldMatrix.m[14]);
  698. Matrix.TranslationToRef(this.position.x + cameraGlobalPosition.x, this.position.y + cameraGlobalPosition.y,
  699. this.position.z + cameraGlobalPosition.z, Tmp.Matrix[2]);
  700. } else {
  701. Matrix.TranslationToRef(this.position.x, this.position.y, this.position.z, Tmp.Matrix[2]);
  702. }
  703. // Composing transformations
  704. this._pivotMatrix.multiplyToRef(Tmp.Matrix[1], Tmp.Matrix[4]);
  705. Tmp.Matrix[4].multiplyToRef(Tmp.Matrix[0], Tmp.Matrix[5]);
  706. // Billboarding (testing PG:http://www.babylonjs-playground.com/#UJEIL#13)
  707. if (this.billboardMode !== TransformNode.BILLBOARDMODE_NONE && camera) {
  708. if ((this.billboardMode & TransformNode.BILLBOARDMODE_ALL) !== TransformNode.BILLBOARDMODE_ALL) {
  709. // Need to decompose each rotation here
  710. var currentPosition = Tmp.Vector3[3];
  711. if (this.parent && this.parent.getWorldMatrix) {
  712. if (this._transformToBoneReferal) {
  713. this.parent.getWorldMatrix().multiplyToRef(this._transformToBoneReferal.getWorldMatrix(), Tmp.Matrix[6]);
  714. Vector3.TransformCoordinatesToRef(this.position, Tmp.Matrix[6], currentPosition);
  715. } else {
  716. Vector3.TransformCoordinatesToRef(this.position, this.parent.getWorldMatrix(), currentPosition);
  717. }
  718. } else {
  719. currentPosition.copyFrom(this.position);
  720. }
  721. currentPosition.subtractInPlace(camera.globalPosition);
  722. var finalEuler = Tmp.Vector3[4].copyFromFloats(0, 0, 0);
  723. if ((this.billboardMode & TransformNode.BILLBOARDMODE_X) === TransformNode.BILLBOARDMODE_X) {
  724. finalEuler.x = Math.atan2(-currentPosition.y, currentPosition.z);
  725. }
  726. if ((this.billboardMode & TransformNode.BILLBOARDMODE_Y) === TransformNode.BILLBOARDMODE_Y) {
  727. finalEuler.y = Math.atan2(currentPosition.x, currentPosition.z);
  728. }
  729. if ((this.billboardMode & TransformNode.BILLBOARDMODE_Z) === TransformNode.BILLBOARDMODE_Z) {
  730. finalEuler.z = Math.atan2(currentPosition.y, currentPosition.x);
  731. }
  732. Matrix.RotationYawPitchRollToRef(finalEuler.y, finalEuler.x, finalEuler.z, Tmp.Matrix[0]);
  733. } else {
  734. Tmp.Matrix[1].copyFrom(camera.getViewMatrix());
  735. Tmp.Matrix[1].setTranslationFromFloats(0, 0, 0);
  736. Tmp.Matrix[1].invertToRef(Tmp.Matrix[0]);
  737. }
  738. Tmp.Matrix[1].copyFrom(Tmp.Matrix[5]);
  739. Tmp.Matrix[1].multiplyToRef(Tmp.Matrix[0], Tmp.Matrix[5]);
  740. }
  741. // Local world
  742. Tmp.Matrix[5].multiplyToRef(Tmp.Matrix[2], this._localWorld);
  743. // Parent
  744. if (this.parent && this.parent.getWorldMatrix) {
  745. if (this.billboardMode !== TransformNode.BILLBOARDMODE_NONE) {
  746. if (this._transformToBoneReferal) {
  747. this.parent.getWorldMatrix().multiplyToRef(this._transformToBoneReferal.getWorldMatrix(), Tmp.Matrix[6]);
  748. Tmp.Matrix[5].copyFrom(Tmp.Matrix[6]);
  749. } else {
  750. Tmp.Matrix[5].copyFrom(this.parent.getWorldMatrix());
  751. }
  752. this._localWorld.getTranslationToRef(Tmp.Vector3[5]);
  753. Vector3.TransformCoordinatesToRef(Tmp.Vector3[5], Tmp.Matrix[5], Tmp.Vector3[5]);
  754. this._worldMatrix.copyFrom(this._localWorld);
  755. this._worldMatrix.setTranslation(Tmp.Vector3[5]);
  756. } else {
  757. if (this._transformToBoneReferal) {
  758. this._localWorld.multiplyToRef(this.parent.getWorldMatrix(), Tmp.Matrix[6]);
  759. Tmp.Matrix[6].multiplyToRef(this._transformToBoneReferal.getWorldMatrix(), this._worldMatrix);
  760. } else {
  761. this._localWorld.multiplyToRef(this.parent.getWorldMatrix(), this._worldMatrix);
  762. }
  763. }
  764. this._markSyncedWithParent();
  765. } else {
  766. this._worldMatrix.copyFrom(this._localWorld);
  767. }
  768. // Post multiply inverse of pivotMatrix
  769. if (this._postMultiplyPivotMatrix) {
  770. this._worldMatrix.multiplyToRef(this._pivotMatrixInverse, this._worldMatrix);
  771. }
  772. // Normal matrix
  773. if (!this.ignoreNonUniformScaling) {
  774. if (this.scaling.isNonUniform) {
  775. this._updateNonUniformScalingState(true);
  776. } else if (this.parent && (<TransformNode>this.parent)._nonUniformScaling) {
  777. this._updateNonUniformScalingState((<TransformNode>this.parent)._nonUniformScaling);
  778. } else {
  779. this._updateNonUniformScalingState(false);
  780. }
  781. }else {
  782. this._updateNonUniformScalingState(false);
  783. }
  784. this._afterComputeWorldMatrix();
  785. // Absolute position
  786. this._absolutePosition.copyFromFloats(this._worldMatrix.m[12], this._worldMatrix.m[13], this._worldMatrix.m[14]);
  787. // Callbacks
  788. this.onAfterWorldMatrixUpdateObservable.notifyObservers(this);
  789. if (!this._poseMatrix) {
  790. this._poseMatrix = Matrix.Invert(this._worldMatrix);
  791. }
  792. // Cache the determinant
  793. this._worldMatrixDeterminant = this._worldMatrix.determinant();
  794. return this._worldMatrix;
  795. }
  796. protected _afterComputeWorldMatrix(): void {
  797. }
  798. /**
  799. * If you'd like to be called back after the mesh position, rotation or scaling has been updated.
  800. * @param func: callback function to add
  801. *
  802. * Returns the TransformNode.
  803. */
  804. public registerAfterWorldMatrixUpdate(func: (mesh: TransformNode) => void): TransformNode {
  805. this.onAfterWorldMatrixUpdateObservable.add(func);
  806. return this;
  807. }
  808. /**
  809. * Removes a registered callback function.
  810. * Returns the TransformNode.
  811. */
  812. public unregisterAfterWorldMatrixUpdate(func: (mesh: TransformNode) => void): TransformNode {
  813. this.onAfterWorldMatrixUpdateObservable.removeCallback(func);
  814. return this;
  815. }
  816. /**
  817. * Clone the current transform node
  818. * Returns the new transform node
  819. * @param name Name of the new clone
  820. * @param newParent New parent for the clone
  821. * @param doNotCloneChildren Do not clone children hierarchy
  822. */
  823. public clone(name: string, newParent: Node, doNotCloneChildren?: boolean): Nullable<TransformNode> {
  824. var result = SerializationHelper.Clone(() => new TransformNode(name, this.getScene()), this);
  825. result.name = name;
  826. result.id = name;
  827. if (newParent) {
  828. result.parent = newParent;
  829. }
  830. if (!doNotCloneChildren) {
  831. // Children
  832. let directDescendants = this.getDescendants(true);
  833. for (let index = 0; index < directDescendants.length; index++) {
  834. var child = directDescendants[index];
  835. if ((<any>child).clone) {
  836. (<any>child).clone(name + "." + child.name, result);
  837. }
  838. }
  839. }
  840. return result;
  841. }
  842. public serialize(currentSerializationObject?: any): any {
  843. let serializationObject = SerializationHelper.Serialize(this, currentSerializationObject);
  844. serializationObject.type = this.getClassName();
  845. // Parent
  846. if (this.parent) {
  847. serializationObject.parentId = this.parent.id;
  848. }
  849. if (Tags && Tags.HasTags(this)) {
  850. serializationObject.tags = Tags.GetTags(this);
  851. }
  852. serializationObject.localMatrix = this.getPivotMatrix().asArray();
  853. serializationObject.isEnabled = this.isEnabled();
  854. // Parent
  855. if (this.parent) {
  856. serializationObject.parentId = this.parent.id;
  857. }
  858. return serializationObject;
  859. }
  860. // Statics
  861. /**
  862. * Returns a new TransformNode object parsed from the source provided.
  863. * The parameter `parsedMesh` is the source.
  864. * The parameter `rootUrl` is a string, it's the root URL to prefix the `delayLoadingFile` property with
  865. */
  866. public static Parse(parsedTransformNode: any, scene: Scene, rootUrl: string): TransformNode {
  867. var transformNode = SerializationHelper.Parse(() => new TransformNode(parsedTransformNode.name, scene), parsedTransformNode, scene, rootUrl);
  868. if (Tags) {
  869. Tags.AddTagsTo(transformNode, parsedTransformNode.tags);
  870. }
  871. if (parsedTransformNode.localMatrix) {
  872. transformNode.setPreTransformMatrix(Matrix.FromArray(parsedTransformNode.localMatrix));
  873. } else if (parsedTransformNode.pivotMatrix) {
  874. transformNode.setPivotMatrix(Matrix.FromArray(parsedTransformNode.pivotMatrix));
  875. }
  876. transformNode.setEnabled(parsedTransformNode.isEnabled);
  877. // Parent
  878. if (parsedTransformNode.parentId) {
  879. transformNode._waitingParentId = parsedTransformNode.parentId;
  880. }
  881. return transformNode;
  882. }
  883. /**
  884. * Releases resources associated with this transform node.
  885. * @param doNotRecurse Set to true to not recurse into each children (recurse into each children by default)
  886. * @param disposeMaterialAndTextures Set to true to also dispose referenced materials and textures (false by default)
  887. */
  888. public dispose(doNotRecurse?: boolean, disposeMaterialAndTextures = false): void {
  889. // Animations
  890. this.getScene().stopAnimation(this);
  891. // Remove from scene
  892. this.getScene().removeTransformNode(this);
  893. this.onAfterWorldMatrixUpdateObservable.clear();
  894. super.dispose(doNotRecurse, disposeMaterialAndTextures);
  895. }
  896. }
  897. }