gizmo.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. import { Quaternion, Vector3, Matrix, TmpVectors } from "../Maths/math.vector.js";
  2. import { Mesh } from "../Meshes/mesh.js";
  3. import { Camera } from "../Cameras/camera.js";
  4. import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer.js";
  5. import { PointerEventTypes } from "../Events/pointerEvents.js";
  6. import { Light } from "../Lights/light.js";
  7. /**
  8. * Anchor options where the Gizmo can be positioned in relation to its anchored node
  9. */
  10. export var GizmoAnchorPoint;
  11. (function (GizmoAnchorPoint) {
  12. /** The origin of the attached node */
  13. GizmoAnchorPoint[GizmoAnchorPoint["Origin"] = 0] = "Origin";
  14. /** The pivot point of the attached node*/
  15. GizmoAnchorPoint[GizmoAnchorPoint["Pivot"] = 1] = "Pivot";
  16. })(GizmoAnchorPoint || (GizmoAnchorPoint = {}));
  17. /**
  18. * Coordinates mode: Local or World. Defines how axis is aligned: either on world axis or transform local axis
  19. */
  20. export var GizmoCoordinatesMode;
  21. (function (GizmoCoordinatesMode) {
  22. GizmoCoordinatesMode[GizmoCoordinatesMode["World"] = 0] = "World";
  23. GizmoCoordinatesMode[GizmoCoordinatesMode["Local"] = 1] = "Local";
  24. })(GizmoCoordinatesMode || (GizmoCoordinatesMode = {}));
  25. /**
  26. * Renders gizmos on top of an existing scene which provide controls for position, rotation, etc.
  27. */
  28. export class Gizmo {
  29. /**
  30. * Ratio for the scale of the gizmo (Default: 1)
  31. */
  32. set scaleRatio(value) {
  33. this._scaleRatio = value;
  34. }
  35. get scaleRatio() {
  36. return this._scaleRatio;
  37. }
  38. /**
  39. * True when the mouse pointer is hovered a gizmo mesh
  40. */
  41. get isHovered() {
  42. return this._isHovered;
  43. }
  44. /**
  45. * Mesh that the gizmo will be attached to. (eg. on a drag gizmo the mesh that will be dragged)
  46. * * When set, interactions will be enabled
  47. */
  48. get attachedMesh() {
  49. return this._attachedMesh;
  50. }
  51. set attachedMesh(value) {
  52. this._attachedMesh = value;
  53. if (value) {
  54. this._attachedNode = value;
  55. }
  56. this._rootMesh.setEnabled(value ? true : false);
  57. this._attachedNodeChanged(value);
  58. }
  59. /**
  60. * Node that the gizmo will be attached to. (eg. on a drag gizmo the mesh, bone or NodeTransform that will be dragged)
  61. * * When set, interactions will be enabled
  62. */
  63. get attachedNode() {
  64. return this._attachedNode;
  65. }
  66. set attachedNode(value) {
  67. this._attachedNode = value;
  68. this._attachedMesh = null;
  69. this._rootMesh.setEnabled(value ? true : false);
  70. this._attachedNodeChanged(value);
  71. }
  72. /**
  73. * Disposes and replaces the current meshes in the gizmo with the specified mesh
  74. * @param mesh The mesh to replace the default mesh of the gizmo
  75. */
  76. setCustomMesh(mesh) {
  77. if (mesh.getScene() != this.gizmoLayer.utilityLayerScene) {
  78. // eslint-disable-next-line no-throw-literal
  79. throw "When setting a custom mesh on a gizmo, the custom meshes scene must be the same as the gizmos (eg. gizmo.gizmoLayer.utilityLayerScene)";
  80. }
  81. this._rootMesh.getChildMeshes().forEach((c) => {
  82. c.dispose();
  83. });
  84. mesh.parent = this._rootMesh;
  85. this._customMeshSet = true;
  86. }
  87. /**
  88. * Additional transform applied to the gizmo.
  89. * It's useful when the gizmo is attached to a bone: if the bone is part of a skeleton attached to a mesh, you should define the mesh as additionalTransformNode if you want the gizmo to be displayed at the bone's correct location.
  90. * Otherwise, as the gizmo is relative to the skeleton root, the mesh transformation will not be taken into account.
  91. */
  92. get additionalTransformNode() {
  93. return this._additionalTransformNode;
  94. }
  95. set additionalTransformNode(value) {
  96. this._additionalTransformNode = value;
  97. }
  98. /**
  99. * If set the gizmo's rotation will be updated to match the attached mesh each frame (Default: true)
  100. * NOTE: This is only possible for meshes with uniform scaling, as otherwise it's not possible to decompose the rotation
  101. */
  102. set updateGizmoRotationToMatchAttachedMesh(value) {
  103. this._updateGizmoRotationToMatchAttachedMesh = value;
  104. }
  105. get updateGizmoRotationToMatchAttachedMesh() {
  106. return this._updateGizmoRotationToMatchAttachedMesh;
  107. }
  108. /**
  109. * If set the gizmo's position will be updated to match the attached mesh each frame (Default: true)
  110. */
  111. set updateGizmoPositionToMatchAttachedMesh(value) {
  112. this._updateGizmoPositionToMatchAttachedMesh = value;
  113. }
  114. get updateGizmoPositionToMatchAttachedMesh() {
  115. return this._updateGizmoPositionToMatchAttachedMesh;
  116. }
  117. /**
  118. * Defines where the gizmo will be positioned if `updateGizmoPositionToMatchAttachedMesh` is enabled.
  119. * (Default: GizmoAnchorPoint.Origin)
  120. */
  121. set anchorPoint(value) {
  122. this._anchorPoint = value;
  123. }
  124. get anchorPoint() {
  125. return this._anchorPoint;
  126. }
  127. /**
  128. * Set the coordinate system to use. By default it's local.
  129. * But it's possible for a user to tweak so its local for translation and world for rotation.
  130. * In that case, setting the coordinate system will change `updateGizmoRotationToMatchAttachedMesh` and `updateGizmoPositionToMatchAttachedMesh`
  131. */
  132. set coordinatesMode(coordinatesMode) {
  133. this._coordinatesMode = coordinatesMode;
  134. const local = coordinatesMode == GizmoCoordinatesMode.Local;
  135. this.updateGizmoRotationToMatchAttachedMesh = local;
  136. this.updateGizmoPositionToMatchAttachedMesh = true;
  137. }
  138. get coordinatesMode() {
  139. return this._coordinatesMode;
  140. }
  141. /**
  142. * When set, the gizmo will always appear the same size no matter where the camera is (default: true)
  143. */
  144. set updateScale(value) {
  145. this._updateScale = value;
  146. }
  147. get updateScale() {
  148. return this._updateScale;
  149. }
  150. // eslint-disable-next-line @typescript-eslint/no-unused-vars
  151. _attachedNodeChanged(value) { }
  152. /**
  153. * Creates a gizmo
  154. * @param gizmoLayer The utility layer the gizmo will be added to
  155. */
  156. constructor(
  157. /** The utility layer the gizmo will be added to */
  158. gizmoLayer = UtilityLayerRenderer.DefaultUtilityLayer) {
  159. this.gizmoLayer = gizmoLayer;
  160. this._attachedMesh = null;
  161. this._attachedNode = null;
  162. this._customRotationQuaternion = null;
  163. /**
  164. * Ratio for the scale of the gizmo (Default: 1)
  165. */
  166. this._scaleRatio = 1;
  167. /**
  168. * boolean updated by pointermove when a gizmo mesh is hovered
  169. */
  170. this._isHovered = false;
  171. /**
  172. * If a custom mesh has been set (Default: false)
  173. */
  174. this._customMeshSet = false;
  175. this._updateGizmoRotationToMatchAttachedMesh = true;
  176. this._updateGizmoPositionToMatchAttachedMesh = true;
  177. this._anchorPoint = GizmoAnchorPoint.Origin;
  178. this._updateScale = true;
  179. this._coordinatesMode = GizmoCoordinatesMode.Local;
  180. this._interactionsEnabled = true;
  181. this._rightHandtoLeftHandMatrix = Matrix.RotationY(Math.PI);
  182. this._rootMesh = new Mesh("gizmoRootNode", gizmoLayer.utilityLayerScene);
  183. this._rootMesh.rotationQuaternion = Quaternion.Identity();
  184. this._beforeRenderObserver = this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.add(() => {
  185. this._update();
  186. });
  187. }
  188. /**
  189. * posture that the gizmo will be display
  190. * When set null, default value will be used (Quaternion(0, 0, 0, 1))
  191. */
  192. get customRotationQuaternion() {
  193. return this._customRotationQuaternion;
  194. }
  195. set customRotationQuaternion(customRotationQuaternion) {
  196. this._customRotationQuaternion = customRotationQuaternion;
  197. }
  198. /**
  199. * Updates the gizmo to match the attached mesh's position/rotation
  200. */
  201. _update() {
  202. if (this.attachedNode) {
  203. let effectiveNode = this.attachedNode;
  204. if (this.attachedMesh) {
  205. effectiveNode = this.attachedMesh || this.attachedNode;
  206. }
  207. // Position
  208. if (this.updateGizmoPositionToMatchAttachedMesh) {
  209. if (this.anchorPoint == GizmoAnchorPoint.Pivot && effectiveNode.getAbsolutePivotPoint) {
  210. const position = effectiveNode.getAbsolutePivotPoint();
  211. this._rootMesh.position.copyFrom(position);
  212. }
  213. else {
  214. const row = effectiveNode.getWorldMatrix().getRow(3);
  215. const position = row ? row.toVector3() : new Vector3(0, 0, 0);
  216. this._rootMesh.position.copyFrom(position);
  217. }
  218. }
  219. // Rotation
  220. if (this.updateGizmoRotationToMatchAttachedMesh) {
  221. const supportedNode = effectiveNode._isMesh ||
  222. effectiveNode.getClassName() === "AbstractMesh" ||
  223. effectiveNode.getClassName() === "TransformNode" ||
  224. effectiveNode.getClassName() === "InstancedMesh";
  225. const transformNode = supportedNode ? effectiveNode : undefined;
  226. effectiveNode.getWorldMatrix().decompose(undefined, this._rootMesh.rotationQuaternion, undefined, Gizmo.PreserveScaling ? transformNode : undefined);
  227. this._rootMesh.rotationQuaternion.normalize();
  228. }
  229. else {
  230. if (this._customRotationQuaternion) {
  231. this._rootMesh.rotationQuaternion.copyFrom(this._customRotationQuaternion);
  232. }
  233. else {
  234. this._rootMesh.rotationQuaternion.set(0, 0, 0, 1);
  235. }
  236. }
  237. // Scale
  238. if (this.updateScale) {
  239. const activeCamera = this.gizmoLayer.utilityLayerScene.activeCamera;
  240. const cameraPosition = activeCamera.globalPosition;
  241. this._rootMesh.position.subtractToRef(cameraPosition, TmpVectors.Vector3[0]);
  242. let scale = this.scaleRatio;
  243. if (activeCamera.mode == Camera.ORTHOGRAPHIC_CAMERA) {
  244. if (activeCamera.orthoTop && activeCamera.orthoBottom) {
  245. const orthoHeight = activeCamera.orthoTop - activeCamera.orthoBottom;
  246. scale *= orthoHeight;
  247. }
  248. }
  249. else {
  250. const camForward = activeCamera.getScene().useRightHandedSystem ? Vector3.RightHandedForwardReadOnly : Vector3.LeftHandedForwardReadOnly;
  251. const direction = activeCamera.getDirection(camForward);
  252. scale *= Vector3.Dot(TmpVectors.Vector3[0], direction);
  253. }
  254. this._rootMesh.scaling.setAll(scale);
  255. // Account for handedness, similar to Matrix.decompose
  256. if (effectiveNode._getWorldMatrixDeterminant() < 0 && !Gizmo.PreserveScaling) {
  257. this._rootMesh.scaling.y *= -1;
  258. }
  259. }
  260. else {
  261. this._rootMesh.scaling.setAll(this.scaleRatio);
  262. }
  263. }
  264. if (this.additionalTransformNode) {
  265. this._rootMesh.computeWorldMatrix(true);
  266. this._rootMesh.getWorldMatrix().multiplyToRef(this.additionalTransformNode.getWorldMatrix(), TmpVectors.Matrix[0]);
  267. TmpVectors.Matrix[0].decompose(this._rootMesh.scaling, this._rootMesh.rotationQuaternion, this._rootMesh.position);
  268. }
  269. }
  270. /**
  271. * if transform has a pivot and is not using PostMultiplyPivotMatrix, then the worldMatrix contains the pivot matrix (it's not cancelled at the end)
  272. * so, when extracting the world matrix component, the translation (and other components) is containing the pivot translation.
  273. * And the pivot is applied each frame. Removing it anyway here makes it applied only in computeWorldMatrix.
  274. * @param transform local transform that needs to be transform by the pivot inverse matrix
  275. * @param localMatrix local matrix that needs to be transform by the pivot inverse matrix
  276. * @param result resulting matrix transformed by pivot inverse if the transform node is using pivot without using post Multiply Pivot Matrix
  277. */
  278. _handlePivotMatrixInverse(transform, localMatrix, result) {
  279. if (transform.isUsingPivotMatrix() && !transform.isUsingPostMultiplyPivotMatrix()) {
  280. transform.getPivotMatrix().invertToRef(TmpVectors.Matrix[5]);
  281. TmpVectors.Matrix[5].multiplyToRef(localMatrix, result);
  282. return;
  283. }
  284. result.copyFrom(localMatrix);
  285. }
  286. /**
  287. * computes the rotation/scaling/position of the transform once the Node world matrix has changed.
  288. */
  289. _matrixChanged() {
  290. if (!this._attachedNode) {
  291. return;
  292. }
  293. if (this._attachedNode._isCamera) {
  294. const camera = this._attachedNode;
  295. let worldMatrix;
  296. let worldMatrixUC;
  297. if (camera.parent) {
  298. const parentInv = TmpVectors.Matrix[1];
  299. camera.parent._worldMatrix.invertToRef(parentInv);
  300. this._attachedNode._worldMatrix.multiplyToRef(parentInv, TmpVectors.Matrix[0]);
  301. worldMatrix = TmpVectors.Matrix[0];
  302. }
  303. else {
  304. worldMatrix = this._attachedNode._worldMatrix;
  305. }
  306. if (camera.getScene().useRightHandedSystem) {
  307. // avoid desync with RH matrix computation. Otherwise, rotation of PI around Y axis happens each frame resulting in axis flipped because worldMatrix is computed as inverse of viewMatrix.
  308. this._rightHandtoLeftHandMatrix.multiplyToRef(worldMatrix, TmpVectors.Matrix[1]);
  309. worldMatrixUC = TmpVectors.Matrix[1];
  310. }
  311. else {
  312. worldMatrixUC = worldMatrix;
  313. }
  314. worldMatrixUC.decompose(TmpVectors.Vector3[1], TmpVectors.Quaternion[0], TmpVectors.Vector3[0]);
  315. const inheritsTargetCamera = this._attachedNode.getClassName() === "FreeCamera" ||
  316. this._attachedNode.getClassName() === "FlyCamera" ||
  317. this._attachedNode.getClassName() === "ArcFollowCamera" ||
  318. this._attachedNode.getClassName() === "TargetCamera" ||
  319. this._attachedNode.getClassName() === "TouchCamera" ||
  320. this._attachedNode.getClassName() === "UniversalCamera";
  321. if (inheritsTargetCamera) {
  322. const targetCamera = this._attachedNode;
  323. targetCamera.rotation = TmpVectors.Quaternion[0].toEulerAngles();
  324. if (targetCamera.rotationQuaternion) {
  325. targetCamera.rotationQuaternion.copyFrom(TmpVectors.Quaternion[0]);
  326. targetCamera.rotationQuaternion.normalize();
  327. }
  328. }
  329. camera.position.copyFrom(TmpVectors.Vector3[0]);
  330. }
  331. else if (this._attachedNode._isMesh ||
  332. this._attachedNode.getClassName() === "AbstractMesh" ||
  333. this._attachedNode.getClassName() === "TransformNode" ||
  334. this._attachedNode.getClassName() === "InstancedMesh") {
  335. const transform = this._attachedNode;
  336. if (transform.parent) {
  337. const parentInv = TmpVectors.Matrix[0];
  338. const localMat = TmpVectors.Matrix[1];
  339. transform.parent.getWorldMatrix().invertToRef(parentInv);
  340. this._attachedNode.getWorldMatrix().multiplyToRef(parentInv, localMat);
  341. const matrixToDecompose = TmpVectors.Matrix[4];
  342. this._handlePivotMatrixInverse(transform, localMat, matrixToDecompose);
  343. matrixToDecompose.decompose(TmpVectors.Vector3[0], TmpVectors.Quaternion[0], transform.position, Gizmo.PreserveScaling ? transform : undefined, Gizmo.UseAbsoluteScaling);
  344. TmpVectors.Quaternion[0].normalize();
  345. if (transform.isUsingPivotMatrix()) {
  346. // Calculate the local matrix without the translation.
  347. // Copied from TranslateNode.computeWorldMatrix
  348. const r = TmpVectors.Quaternion[1];
  349. Quaternion.RotationYawPitchRollToRef(transform.rotation.y, transform.rotation.x, transform.rotation.z, r);
  350. const scaleMatrix = TmpVectors.Matrix[2];
  351. Matrix.ScalingToRef(transform.scaling.x, transform.scaling.y, transform.scaling.z, scaleMatrix);
  352. const rotationMatrix = TmpVectors.Matrix[2];
  353. r.toRotationMatrix(rotationMatrix);
  354. const pivotMatrix = transform.getPivotMatrix();
  355. const invPivotMatrix = TmpVectors.Matrix[3];
  356. pivotMatrix.invertToRef(invPivotMatrix);
  357. pivotMatrix.multiplyToRef(scaleMatrix, TmpVectors.Matrix[4]);
  358. TmpVectors.Matrix[4].multiplyToRef(rotationMatrix, TmpVectors.Matrix[5]);
  359. TmpVectors.Matrix[5].multiplyToRef(invPivotMatrix, TmpVectors.Matrix[6]);
  360. TmpVectors.Matrix[6].getTranslationToRef(TmpVectors.Vector3[1]);
  361. transform.position.subtractInPlace(TmpVectors.Vector3[1]);
  362. }
  363. }
  364. else {
  365. const matrixToDecompose = TmpVectors.Matrix[4];
  366. this._handlePivotMatrixInverse(transform, this._attachedNode._worldMatrix, matrixToDecompose);
  367. matrixToDecompose.decompose(TmpVectors.Vector3[0], TmpVectors.Quaternion[0], transform.position, Gizmo.PreserveScaling ? transform : undefined, Gizmo.UseAbsoluteScaling);
  368. }
  369. TmpVectors.Vector3[0].scaleInPlace(1.0 / transform.scalingDeterminant);
  370. transform.scaling.copyFrom(TmpVectors.Vector3[0]);
  371. if (!transform.billboardMode) {
  372. if (transform.rotationQuaternion) {
  373. transform.rotationQuaternion.copyFrom(TmpVectors.Quaternion[0]);
  374. transform.rotationQuaternion.normalize();
  375. }
  376. else {
  377. transform.rotation = TmpVectors.Quaternion[0].toEulerAngles();
  378. }
  379. }
  380. }
  381. else if (this._attachedNode.getClassName() === "Bone") {
  382. const bone = this._attachedNode;
  383. const parent = bone.getParent();
  384. if (parent) {
  385. const invParent = TmpVectors.Matrix[0];
  386. const boneLocalMatrix = TmpVectors.Matrix[1];
  387. parent.getFinalMatrix().invertToRef(invParent);
  388. bone.getFinalMatrix().multiplyToRef(invParent, boneLocalMatrix);
  389. const lmat = bone.getLocalMatrix();
  390. lmat.copyFrom(boneLocalMatrix);
  391. }
  392. else {
  393. const lmat = bone.getLocalMatrix();
  394. lmat.copyFrom(bone.getFinalMatrix());
  395. }
  396. bone.markAsDirty();
  397. }
  398. else {
  399. const light = this._attachedNode;
  400. if (light.getTypeID) {
  401. const type = light.getTypeID();
  402. if (type === Light.LIGHTTYPEID_DIRECTIONALLIGHT || type === Light.LIGHTTYPEID_SPOTLIGHT || type === Light.LIGHTTYPEID_POINTLIGHT) {
  403. const parent = light.parent;
  404. if (parent) {
  405. const invParent = TmpVectors.Matrix[0];
  406. const nodeLocalMatrix = TmpVectors.Matrix[1];
  407. parent.getWorldMatrix().invertToRef(invParent);
  408. light.getWorldMatrix().multiplyToRef(invParent, nodeLocalMatrix);
  409. nodeLocalMatrix.decompose(undefined, TmpVectors.Quaternion[0], TmpVectors.Vector3[0]);
  410. }
  411. else {
  412. this._attachedNode._worldMatrix.decompose(undefined, TmpVectors.Quaternion[0], TmpVectors.Vector3[0]);
  413. }
  414. // setter doesn't copy values. Need a new Vector3
  415. light.position = new Vector3(TmpVectors.Vector3[0].x, TmpVectors.Vector3[0].y, TmpVectors.Vector3[0].z);
  416. if (light.direction) {
  417. light.direction = new Vector3(light.direction.x, light.direction.y, light.direction.z);
  418. }
  419. }
  420. }
  421. }
  422. }
  423. /**
  424. * refresh gizmo mesh material
  425. * @param gizmoMeshes
  426. * @param material material to apply
  427. */
  428. _setGizmoMeshMaterial(gizmoMeshes, material) {
  429. if (gizmoMeshes) {
  430. gizmoMeshes.forEach((m) => {
  431. m.material = material;
  432. if (m.color) {
  433. m.color = material.diffuseColor;
  434. }
  435. });
  436. }
  437. }
  438. /**
  439. * Subscribes to pointer up, down, and hover events. Used for responsive gizmos.
  440. * @param gizmoLayer The utility layer the gizmo will be added to
  441. * @param gizmoAxisCache Gizmo axis definition used for reactive gizmo UI
  442. * @returns {Observer<PointerInfo>} pointerObserver
  443. */
  444. static GizmoAxisPointerObserver(gizmoLayer, gizmoAxisCache) {
  445. let dragging = false;
  446. const pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
  447. if (pointerInfo.pickInfo) {
  448. // On Hover Logic
  449. if (pointerInfo.type === PointerEventTypes.POINTERMOVE) {
  450. if (dragging) {
  451. return;
  452. }
  453. gizmoAxisCache.forEach((cache) => {
  454. if (cache.colliderMeshes && cache.gizmoMeshes) {
  455. const isHovered = cache.colliderMeshes?.indexOf(pointerInfo?.pickInfo?.pickedMesh) != -1;
  456. const material = cache.dragBehavior.enabled ? (isHovered || cache.active ? cache.hoverMaterial : cache.material) : cache.disableMaterial;
  457. cache.gizmoMeshes.forEach((m) => {
  458. m.material = material;
  459. if (m.color) {
  460. m.color = material.diffuseColor;
  461. }
  462. });
  463. }
  464. });
  465. }
  466. // On Mouse Down
  467. if (pointerInfo.type === PointerEventTypes.POINTERDOWN) {
  468. // If user Clicked Gizmo
  469. if (gizmoAxisCache.has(pointerInfo.pickInfo.pickedMesh?.parent)) {
  470. dragging = true;
  471. const statusMap = gizmoAxisCache.get(pointerInfo.pickInfo.pickedMesh?.parent);
  472. statusMap.active = true;
  473. gizmoAxisCache.forEach((cache) => {
  474. const isHovered = cache.colliderMeshes?.indexOf(pointerInfo?.pickInfo?.pickedMesh) != -1;
  475. const material = (isHovered || cache.active) && cache.dragBehavior.enabled ? cache.hoverMaterial : cache.disableMaterial;
  476. cache.gizmoMeshes.forEach((m) => {
  477. m.material = material;
  478. if (m.color) {
  479. m.color = material.diffuseColor;
  480. }
  481. });
  482. });
  483. }
  484. }
  485. // On Mouse Up
  486. if (pointerInfo.type === PointerEventTypes.POINTERUP) {
  487. gizmoAxisCache.forEach((cache) => {
  488. cache.active = false;
  489. dragging = false;
  490. cache.gizmoMeshes.forEach((m) => {
  491. m.material = cache.dragBehavior.enabled ? cache.material : cache.disableMaterial;
  492. if (m.color) {
  493. m.color = cache.material.diffuseColor;
  494. }
  495. });
  496. });
  497. }
  498. }
  499. });
  500. return pointerObserver;
  501. }
  502. /**
  503. * Disposes of the gizmo
  504. */
  505. dispose() {
  506. this._rootMesh.dispose();
  507. if (this._beforeRenderObserver) {
  508. this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.remove(this._beforeRenderObserver);
  509. }
  510. }
  511. }
  512. /**
  513. * When enabled, any gizmo operation will perserve scaling sign. Default is off.
  514. * Only valid for TransformNode derived classes (Mesh, AbstractMesh, ...)
  515. */
  516. Gizmo.PreserveScaling = false;
  517. /**
  518. * There are 2 ways to preserve scaling: using mesh scaling or absolute scaling. Depending of hierarchy, non uniform scaling and LH or RH coordinates. One is preferable than the other.
  519. * If the scaling to be preserved is the local scaling, then set this value to false.
  520. * Default is true which means scaling to be preserved is absolute one (with hierarchy applied)
  521. */
  522. Gizmo.UseAbsoluteScaling = true;
  523. //# sourceMappingURL=gizmo.js.map