axisDragGizmo.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. import { Observable } from "../Misc/observable.js";
  2. import { TransformNode } from "../Meshes/transformNode.js";
  3. import { Mesh } from "../Meshes/mesh.js";
  4. import { CreateCylinder } from "../Meshes/Builders/cylinderBuilder.js";
  5. import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior.js";
  6. import { Gizmo } from "./gizmo.js";
  7. import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer.js";
  8. import { StandardMaterial } from "../Materials/standardMaterial.js";
  9. import { Color3 } from "../Maths/math.color.js";
  10. import { TmpVectors } from "../Maths/math.vector.js";
  11. /**
  12. * Single axis drag gizmo
  13. */
  14. export class AxisDragGizmo extends Gizmo {
  15. /** Default material used to render when gizmo is not disabled or hovered */
  16. get coloredMaterial() {
  17. return this._coloredMaterial;
  18. }
  19. /** Material used to render when gizmo is hovered with mouse*/
  20. get hoverMaterial() {
  21. return this._hoverMaterial;
  22. }
  23. /** Material used to render when gizmo is disabled. typically grey.*/
  24. get disableMaterial() {
  25. return this._disableMaterial;
  26. }
  27. /**
  28. * @internal
  29. */
  30. static _CreateArrow(scene, material, thickness = 1, isCollider = false) {
  31. const arrow = new TransformNode("arrow", scene);
  32. const cylinder = CreateCylinder("cylinder", {
  33. diameterTop: 0,
  34. height: 0.075,
  35. diameterBottom: 0.0375 * (1 + (thickness - 1) / 4),
  36. tessellation: 96,
  37. }, scene);
  38. const line = CreateCylinder("cylinder", {
  39. diameterTop: 0.005 * thickness,
  40. height: 0.275,
  41. diameterBottom: 0.005 * thickness,
  42. tessellation: 96,
  43. }, scene);
  44. // Position arrow pointing in its drag axis
  45. cylinder.parent = arrow;
  46. cylinder.material = material;
  47. cylinder.rotation.x = Math.PI / 2;
  48. cylinder.position.z += 0.3;
  49. line.parent = arrow;
  50. line.material = material;
  51. line.position.z += 0.275 / 2;
  52. line.rotation.x = Math.PI / 2;
  53. if (isCollider) {
  54. line.visibility = 0;
  55. cylinder.visibility = 0;
  56. }
  57. return arrow;
  58. }
  59. /**
  60. * @internal
  61. */
  62. static _CreateArrowInstance(scene, arrow) {
  63. const instance = new TransformNode("arrow", scene);
  64. for (const mesh of arrow.getChildMeshes()) {
  65. const childInstance = mesh.createInstance(mesh.name);
  66. childInstance.parent = instance;
  67. }
  68. return instance;
  69. }
  70. /**
  71. * Creates an AxisDragGizmo
  72. * @param dragAxis The axis which the gizmo will be able to drag on
  73. * @param color The color of the gizmo
  74. * @param gizmoLayer The utility layer the gizmo will be added to
  75. * @param parent
  76. * @param thickness display gizmo axis thickness
  77. * @param hoverColor The color of the gizmo when hovering over and dragging
  78. * @param disableColor The Color of the gizmo when its disabled
  79. */
  80. constructor(dragAxis, color = Color3.Gray(), gizmoLayer = UtilityLayerRenderer.DefaultUtilityLayer, parent = null, thickness = 1, hoverColor = Color3.Yellow(), disableColor = Color3.Gray()) {
  81. super(gizmoLayer);
  82. this._pointerObserver = null;
  83. /**
  84. * Drag distance in babylon units that the gizmo will snap to when dragged (Default: 0)
  85. */
  86. this.snapDistance = 0;
  87. /**
  88. * Event that fires each time the gizmo snaps to a new location.
  89. * * snapDistance is the change in distance
  90. */
  91. this.onSnapObservable = new Observable();
  92. this._isEnabled = true;
  93. this._parent = null;
  94. this._dragging = false;
  95. this._parent = parent;
  96. // Create Material
  97. this._coloredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
  98. this._coloredMaterial.diffuseColor = color;
  99. this._coloredMaterial.specularColor = color.subtract(new Color3(0.1, 0.1, 0.1));
  100. this._hoverMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
  101. this._hoverMaterial.diffuseColor = hoverColor;
  102. this._disableMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
  103. this._disableMaterial.diffuseColor = disableColor;
  104. this._disableMaterial.alpha = 0.4;
  105. // Build Mesh + Collider
  106. const arrow = AxisDragGizmo._CreateArrow(gizmoLayer.utilityLayerScene, this._coloredMaterial, thickness);
  107. const collider = AxisDragGizmo._CreateArrow(gizmoLayer.utilityLayerScene, this._coloredMaterial, thickness + 4, true);
  108. // Add to Root Node
  109. this._gizmoMesh = new Mesh("", gizmoLayer.utilityLayerScene);
  110. this._gizmoMesh.addChild(arrow);
  111. this._gizmoMesh.addChild(collider);
  112. this._gizmoMesh.lookAt(this._rootMesh.position.add(dragAxis));
  113. this._gizmoMesh.scaling.scaleInPlace(1 / 3);
  114. this._gizmoMesh.parent = this._rootMesh;
  115. let currentSnapDragDistance = 0;
  116. const tmpSnapEvent = { snapDistance: 0 };
  117. // Add drag behavior to handle events when the gizmo is dragged
  118. this.dragBehavior = new PointerDragBehavior({ dragAxis: dragAxis });
  119. this.dragBehavior.moveAttached = false;
  120. this.dragBehavior.updateDragPlane = false;
  121. this._rootMesh.addBehavior(this.dragBehavior);
  122. this.dragBehavior.onDragObservable.add((event) => {
  123. if (this.attachedNode) {
  124. // Keep world translation and use it to update world transform
  125. // if the node has parent, the local transform properties (position, rotation, scale)
  126. // will be recomputed in _matrixChanged function
  127. let matrixChanged = false;
  128. // Snapping logic
  129. if (this.snapDistance == 0) {
  130. this.attachedNode.getWorldMatrix().getTranslationToRef(TmpVectors.Vector3[2]);
  131. TmpVectors.Vector3[2].addInPlace(event.delta);
  132. if (this.dragBehavior.validateDrag(TmpVectors.Vector3[2])) {
  133. if (this.attachedNode.position) {
  134. // Required for nodes like lights
  135. this.attachedNode.position.addInPlaceFromFloats(event.delta.x, event.delta.y, event.delta.z);
  136. }
  137. // use _worldMatrix to not force a matrix update when calling GetWorldMatrix especially with Cameras
  138. this.attachedNode.getWorldMatrix().addTranslationFromFloats(event.delta.x, event.delta.y, event.delta.z);
  139. this.attachedNode.updateCache();
  140. matrixChanged = true;
  141. }
  142. }
  143. else {
  144. currentSnapDragDistance += event.dragDistance;
  145. if (Math.abs(currentSnapDragDistance) > this.snapDistance) {
  146. const dragSteps = Math.floor(Math.abs(currentSnapDragDistance) / this.snapDistance);
  147. currentSnapDragDistance = currentSnapDragDistance % this.snapDistance;
  148. event.delta.normalizeToRef(TmpVectors.Vector3[1]);
  149. TmpVectors.Vector3[1].scaleInPlace(this.snapDistance * dragSteps);
  150. this.attachedNode.getWorldMatrix().getTranslationToRef(TmpVectors.Vector3[2]);
  151. TmpVectors.Vector3[2].addInPlace(TmpVectors.Vector3[1]);
  152. if (this.dragBehavior.validateDrag(TmpVectors.Vector3[2])) {
  153. this.attachedNode.getWorldMatrix().addTranslationFromFloats(TmpVectors.Vector3[1].x, TmpVectors.Vector3[1].y, TmpVectors.Vector3[1].z);
  154. this.attachedNode.updateCache();
  155. tmpSnapEvent.snapDistance = this.snapDistance * dragSteps * Math.sign(currentSnapDragDistance);
  156. this.onSnapObservable.notifyObservers(tmpSnapEvent);
  157. matrixChanged = true;
  158. }
  159. }
  160. }
  161. if (matrixChanged) {
  162. this._matrixChanged();
  163. }
  164. }
  165. });
  166. this.dragBehavior.onDragStartObservable.add(() => {
  167. this._dragging = true;
  168. });
  169. this.dragBehavior.onDragEndObservable.add(() => {
  170. this._dragging = false;
  171. });
  172. const light = gizmoLayer._getSharedGizmoLight();
  173. light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes(false));
  174. const cache = {
  175. gizmoMeshes: arrow.getChildMeshes(),
  176. colliderMeshes: collider.getChildMeshes(),
  177. material: this._coloredMaterial,
  178. hoverMaterial: this._hoverMaterial,
  179. disableMaterial: this._disableMaterial,
  180. active: false,
  181. dragBehavior: this.dragBehavior,
  182. };
  183. this._parent?.addToAxisCache(collider, cache);
  184. this._pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
  185. if (this._customMeshSet) {
  186. return;
  187. }
  188. this._isHovered = !!(cache.colliderMeshes.indexOf(pointerInfo?.pickInfo?.pickedMesh) != -1);
  189. if (!this._parent) {
  190. const material = this.dragBehavior.enabled ? (this._isHovered || this._dragging ? this._hoverMaterial : this._coloredMaterial) : this._disableMaterial;
  191. this._setGizmoMeshMaterial(cache.gizmoMeshes, material);
  192. }
  193. });
  194. this.dragBehavior.onEnabledObservable.add((newState) => {
  195. this._setGizmoMeshMaterial(cache.gizmoMeshes, newState ? cache.material : cache.disableMaterial);
  196. });
  197. }
  198. _attachedNodeChanged(value) {
  199. if (this.dragBehavior) {
  200. this.dragBehavior.enabled = value ? true : false;
  201. }
  202. }
  203. /**
  204. * If the gizmo is enabled
  205. */
  206. set isEnabled(value) {
  207. this._isEnabled = value;
  208. if (!value) {
  209. this.attachedMesh = null;
  210. this.attachedNode = null;
  211. }
  212. else {
  213. if (this._parent) {
  214. this.attachedMesh = this._parent.attachedMesh;
  215. this.attachedNode = this._parent.attachedNode;
  216. }
  217. }
  218. }
  219. get isEnabled() {
  220. return this._isEnabled;
  221. }
  222. /**
  223. * Disposes of the gizmo
  224. */
  225. dispose() {
  226. this.onSnapObservable.clear();
  227. this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
  228. this.dragBehavior.detach();
  229. if (this._gizmoMesh) {
  230. this._gizmoMesh.dispose();
  231. }
  232. [this._coloredMaterial, this._hoverMaterial, this._disableMaterial].forEach((matl) => {
  233. if (matl) {
  234. matl.dispose();
  235. }
  236. });
  237. super.dispose();
  238. }
  239. }
  240. //# sourceMappingURL=axisDragGizmo.js.map