boundingBoxGizmo.ts 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583
  1. import { Observer, Observable } from "../Misc/observable";
  2. import { Logger } from "../Misc/logger";
  3. import { Nullable } from "../types";
  4. import { PointerInfo } from "../Events/pointerEvents";
  5. import { Scene } from "../scene";
  6. import { Quaternion, Matrix, Vector3, Color3, Epsilon } from "../Maths/math";
  7. import { AbstractMesh } from "../Meshes/abstractMesh";
  8. import { Mesh } from "../Meshes/mesh";
  9. import { MeshBuilder } from "../Meshes/meshBuilder";
  10. import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior";
  11. import { _TimeToken } from "../Instrumentation/timeToken";
  12. import { _DepthCullingState, _StencilState, _AlphaState } from "../States/index";
  13. import { Gizmo } from "./gizmo";
  14. import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
  15. import { StandardMaterial } from "../Materials/standardMaterial";
  16. import { PivotTools } from "../Misc/pivotTools";
  17. /**
  18. * Bounding box gizmo
  19. */
  20. export class BoundingBoxGizmo extends Gizmo {
  21. private _lineBoundingBox: AbstractMesh;
  22. private _rotateSpheresParent: AbstractMesh;
  23. private _scaleBoxesParent: AbstractMesh;
  24. private _boundingDimensions = new Vector3(1, 1, 1);
  25. private _renderObserver: Nullable<Observer<Scene>> = null;
  26. private _pointerObserver: Nullable<Observer<PointerInfo>> = null;
  27. private _scaleDragSpeed = 0.2;
  28. private _tmpQuaternion = new Quaternion();
  29. private _tmpVector = new Vector3(0, 0, 0);
  30. private _tmpRotationMatrix = new Matrix();
  31. /**
  32. * If child meshes should be ignored when calculating the boudning box. This should be set to true to avoid perf hits with heavily nested meshes (Default: false)
  33. */
  34. public ignoreChildren = false;
  35. /**
  36. * Returns true if a descendant should be included when computing the bounding box. When null, all descendants are included. If ignoreChildren is set this will be ignored. (Default: null)
  37. */
  38. public includeChildPredicate: Nullable<(abstractMesh: AbstractMesh) => boolean> = null;
  39. /**
  40. * The size of the rotation spheres attached to the bounding box (Default: 0.1)
  41. */
  42. public rotationSphereSize = 0.1;
  43. /**
  44. * The size of the scale boxes attached to the bounding box (Default: 0.1)
  45. */
  46. public scaleBoxSize = 0.1;
  47. /**
  48. * If set, the rotation spheres and scale boxes will increase in size based on the distance away from the camera to have a consistent screen size (Default: false)
  49. */
  50. public fixedDragMeshScreenSize = false;
  51. /**
  52. * The distance away from the object which the draggable meshes should appear world sized when fixedDragMeshScreenSize is set to true (default: 10)
  53. */
  54. public fixedDragMeshScreenSizeDistanceFactor = 10;
  55. /**
  56. * Fired when a rotation sphere or scale box is dragged
  57. */
  58. public onDragStartObservable = new Observable<{}>();
  59. /**
  60. * Fired when a scale box is dragged
  61. */
  62. public onScaleBoxDragObservable = new Observable<{}>();
  63. /**
  64. * Fired when a scale box drag is ended
  65. */
  66. public onScaleBoxDragEndObservable = new Observable<{}>();
  67. /**
  68. * Fired when a rotation sphere is dragged
  69. */
  70. public onRotationSphereDragObservable = new Observable<{}>();
  71. /**
  72. * Fired when a rotation sphere drag is ended
  73. */
  74. public onRotationSphereDragEndObservable = new Observable<{}>();
  75. /**
  76. * Relative bounding box pivot used when scaling the attached mesh. When null object with scale from the opposite corner. 0.5,0.5,0.5 for center and 0.5,0,0.5 for bottom (Default: null)
  77. */
  78. public scalePivot: Nullable<Vector3> = null;
  79. private _anchorMesh: AbstractMesh;
  80. private _existingMeshScale = new Vector3();
  81. // Dragging
  82. private _dragMesh: Nullable<Mesh> = null;
  83. private pointerDragBehavior = new PointerDragBehavior();
  84. /**
  85. * Creates an BoundingBoxGizmo
  86. * @param gizmoLayer The utility layer the gizmo will be added to
  87. * @param color The color of the gizmo
  88. */
  89. constructor(color: Color3 = Color3.Gray(), gizmoLayer: UtilityLayerRenderer = UtilityLayerRenderer.DefaultKeepDepthUtilityLayer) {
  90. super(gizmoLayer);
  91. // Do not update the gizmo's scale so it has a fixed size to the object its attached to
  92. this._updateScale = false;
  93. this._anchorMesh = new AbstractMesh("anchor", gizmoLayer.utilityLayerScene);
  94. // Create Materials
  95. var coloredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
  96. coloredMaterial.disableLighting = true;
  97. coloredMaterial.emissiveColor = color;
  98. var hoverColoredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
  99. hoverColoredMaterial.disableLighting = true;
  100. hoverColoredMaterial.emissiveColor = color.clone().add(new Color3(0.3, 0.3, 0.3));
  101. // Build bounding box out of lines
  102. this._lineBoundingBox = new AbstractMesh("", gizmoLayer.utilityLayerScene);
  103. this._lineBoundingBox.rotationQuaternion = new Quaternion();
  104. var lines = [];
  105. lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, 0, 0), new Vector3(this._boundingDimensions.x, 0, 0)] }, gizmoLayer.utilityLayerScene));
  106. lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, 0, 0), new Vector3(0, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
  107. lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, 0, 0), new Vector3(0, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
  108. lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, 0, 0), new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
  109. lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, 0, 0), new Vector3(this._boundingDimensions.x, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
  110. lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, this._boundingDimensions.y, 0), new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
  111. lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, this._boundingDimensions.y, 0), new Vector3(0, this._boundingDimensions.y, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
  112. lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, 0, this._boundingDimensions.z), new Vector3(this._boundingDimensions.x, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
  113. lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(0, 0, this._boundingDimensions.z), new Vector3(0, this._boundingDimensions.y, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
  114. lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, this._boundingDimensions.z), new Vector3(0, this._boundingDimensions.y, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
  115. lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, this._boundingDimensions.z), new Vector3(this._boundingDimensions.x, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
  116. lines.push(MeshBuilder.CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, this._boundingDimensions.z), new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
  117. lines.forEach((l) => {
  118. l.color = color;
  119. l.position.addInPlace(new Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
  120. l.isPickable = false;
  121. this._lineBoundingBox.addChild(l);
  122. });
  123. this._rootMesh.addChild(this._lineBoundingBox);
  124. // Create rotation spheres
  125. this._rotateSpheresParent = new AbstractMesh("", gizmoLayer.utilityLayerScene);
  126. this._rotateSpheresParent.rotationQuaternion = new Quaternion();
  127. for (let i = 0; i < 12; i++) {
  128. let sphere = MeshBuilder.CreateSphere("", { diameter: 1 }, gizmoLayer.utilityLayerScene);
  129. sphere.rotationQuaternion = new Quaternion();
  130. sphere.material = coloredMaterial;
  131. // Drag behavior
  132. var _dragBehavior = new PointerDragBehavior({});
  133. _dragBehavior.moveAttached = false;
  134. _dragBehavior.updateDragPlane = false;
  135. sphere.addBehavior(_dragBehavior);
  136. let startingTurnDirection = new Vector3(1, 0, 0);
  137. let totalTurnAmountOfDrag = 0;
  138. _dragBehavior.onDragStartObservable.add(() => {
  139. startingTurnDirection.copyFrom(sphere.forward);
  140. totalTurnAmountOfDrag = 0;
  141. });
  142. _dragBehavior.onDragObservable.add((event) => {
  143. this.onRotationSphereDragObservable.notifyObservers({});
  144. if (this.attachedMesh) {
  145. var originalParent = this.attachedMesh.parent;
  146. if (originalParent && ((originalParent as Mesh).scaling && (originalParent as Mesh).scaling.isNonUniformWithinEpsilon(0.001))) {
  147. Logger.Warn("BoundingBoxGizmo controls are not supported on child meshes with non-uniform parent scaling");
  148. return;
  149. }
  150. PivotTools._RemoveAndStorePivotPoint(this.attachedMesh);
  151. var worldDragDirection = startingTurnDirection;
  152. // Project the world right on to the drag plane
  153. var toSub = event.dragPlaneNormal.scale(Vector3.Dot(event.dragPlaneNormal, worldDragDirection));
  154. var dragAxis = worldDragDirection.subtract(toSub).normalizeToNew();
  155. // project drag delta on to the resulting drag axis and rotate based on that
  156. var projectDist = Vector3.Dot(dragAxis, event.delta) < 0 ? Math.abs(event.delta.length()) : -Math.abs(event.delta.length());
  157. // Make rotation relative to size of mesh.
  158. projectDist = (projectDist / this._boundingDimensions.length()) * this._anchorMesh.scaling.length();
  159. // Rotate based on axis
  160. if (!this.attachedMesh.rotationQuaternion) {
  161. this.attachedMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.attachedMesh.rotation.y, this.attachedMesh.rotation.x, this.attachedMesh.rotation.z);
  162. }
  163. if (!this._anchorMesh.rotationQuaternion) {
  164. this._anchorMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this._anchorMesh.rotation.y, this._anchorMesh.rotation.x, this._anchorMesh.rotation.z);
  165. }
  166. // Do not allow the object to turn more than a full circle
  167. totalTurnAmountOfDrag += projectDist;
  168. if (Math.abs(totalTurnAmountOfDrag) <= 2 * Math.PI) {
  169. if (i >= 8) {
  170. Quaternion.RotationYawPitchRollToRef(0, 0, projectDist, this._tmpQuaternion);
  171. } else if (i >= 4) {
  172. Quaternion.RotationYawPitchRollToRef(projectDist, 0, 0, this._tmpQuaternion);
  173. } else {
  174. Quaternion.RotationYawPitchRollToRef(0, projectDist, 0, this._tmpQuaternion);
  175. }
  176. // Rotate around center of bounding box
  177. this._anchorMesh.addChild(this.attachedMesh);
  178. this._anchorMesh.rotationQuaternion!.multiplyToRef(this._tmpQuaternion, this._anchorMesh.rotationQuaternion!);
  179. this._anchorMesh.removeChild(this.attachedMesh);
  180. this.attachedMesh.setParent(originalParent);
  181. }
  182. this.updateBoundingBox();
  183. PivotTools._RestorePivotPoint(this.attachedMesh);
  184. }
  185. this._updateDummy();
  186. });
  187. // Selection/deselection
  188. _dragBehavior.onDragStartObservable.add(() => {
  189. this.onDragStartObservable.notifyObservers({});
  190. this._selectNode(sphere);
  191. });
  192. _dragBehavior.onDragEndObservable.add(() => {
  193. this.onRotationSphereDragEndObservable.notifyObservers({});
  194. this._selectNode(null);
  195. this._updateDummy();
  196. });
  197. this._rotateSpheresParent.addChild(sphere);
  198. }
  199. this._rootMesh.addChild(this._rotateSpheresParent);
  200. // Create scale cubes
  201. this._scaleBoxesParent = new AbstractMesh("", gizmoLayer.utilityLayerScene);
  202. this._scaleBoxesParent.rotationQuaternion = new Quaternion();
  203. for (var i = 0; i < 2; i++) {
  204. for (var j = 0; j < 2; j++) {
  205. for (var k = 0; k < 2; k++) {
  206. let box = MeshBuilder.CreateBox("", { size: 1 }, gizmoLayer.utilityLayerScene);
  207. box.material = coloredMaterial;
  208. // Dragging logic
  209. let dragAxis = new Vector3(i == 0 ? -1 : 1, j == 0 ? -1 : 1, k == 0 ? -1 : 1);
  210. var _dragBehavior = new PointerDragBehavior({ dragAxis: dragAxis });
  211. _dragBehavior.moveAttached = false;
  212. box.addBehavior(_dragBehavior);
  213. _dragBehavior.onDragObservable.add((event) => {
  214. this.onScaleBoxDragObservable.notifyObservers({});
  215. if (this.attachedMesh) {
  216. var originalParent = this.attachedMesh.parent;
  217. if (originalParent && ((originalParent as Mesh).scaling && (originalParent as Mesh).scaling.isNonUniformWithinEpsilon(0.001))) {
  218. Logger.Warn("BoundingBoxGizmo controls are not supported on child meshes with non-uniform parent scaling");
  219. return;
  220. }
  221. PivotTools._RemoveAndStorePivotPoint(this.attachedMesh);
  222. var relativeDragDistance = (event.dragDistance / this._boundingDimensions.length()) * this._anchorMesh.scaling.length();
  223. var deltaScale = new Vector3(relativeDragDistance, relativeDragDistance, relativeDragDistance);
  224. deltaScale.scaleInPlace(this._scaleDragSpeed);
  225. this.updateBoundingBox();
  226. if (this.scalePivot) {
  227. this.attachedMesh.getWorldMatrix().getRotationMatrixToRef(this._tmpRotationMatrix);
  228. // Move anchor to desired pivot point (Bottom left corner + dimension/2)
  229. this._boundingDimensions.scaleToRef(0.5, this._tmpVector);
  230. Vector3.TransformCoordinatesToRef(this._tmpVector, this._tmpRotationMatrix, this._tmpVector);
  231. this._anchorMesh.position.subtractInPlace(this._tmpVector);
  232. this._boundingDimensions.multiplyToRef(this.scalePivot, this._tmpVector);
  233. Vector3.TransformCoordinatesToRef(this._tmpVector, this._tmpRotationMatrix, this._tmpVector);
  234. this._anchorMesh.position.addInPlace(this._tmpVector);
  235. } else {
  236. // Scale from the position of the opposite corner
  237. box.absolutePosition.subtractToRef(this._anchorMesh.position, this._tmpVector);
  238. this._anchorMesh.position.subtractInPlace(this._tmpVector);
  239. }
  240. this._anchorMesh.addChild(this.attachedMesh);
  241. this._anchorMesh.scaling.addInPlace(deltaScale);
  242. if (this._anchorMesh.scaling.x < 0 || this._anchorMesh.scaling.y < 0 || this._anchorMesh.scaling.z < 0) {
  243. this._anchorMesh.scaling.subtractInPlace(deltaScale);
  244. }
  245. this._anchorMesh.removeChild(this.attachedMesh);
  246. this.attachedMesh.setParent(originalParent);
  247. PivotTools._RestorePivotPoint(this.attachedMesh);
  248. }
  249. this._updateDummy();
  250. });
  251. // Selection/deselection
  252. _dragBehavior.onDragStartObservable.add(() => {
  253. this.onDragStartObservable.notifyObservers({});
  254. this._selectNode(box);
  255. });
  256. _dragBehavior.onDragEndObservable.add(() => {
  257. this.onScaleBoxDragEndObservable.notifyObservers({});
  258. this._selectNode(null);
  259. this._updateDummy();
  260. });
  261. this._scaleBoxesParent.addChild(box);
  262. }
  263. }
  264. }
  265. this._rootMesh.addChild(this._scaleBoxesParent);
  266. // Hover color change
  267. var pointerIds = new Array<AbstractMesh>();
  268. this._pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
  269. if (!pointerIds[(<PointerEvent>pointerInfo.event).pointerId]) {
  270. this._rotateSpheresParent.getChildMeshes().concat(this._scaleBoxesParent.getChildMeshes()).forEach((mesh) => {
  271. if (pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh == mesh) {
  272. pointerIds[(<PointerEvent>pointerInfo.event).pointerId] = mesh;
  273. mesh.material = hoverColoredMaterial;
  274. }
  275. });
  276. } else {
  277. if (pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh != pointerIds[(<PointerEvent>pointerInfo.event).pointerId]) {
  278. pointerIds[(<PointerEvent>pointerInfo.event).pointerId].material = coloredMaterial;
  279. delete pointerIds[(<PointerEvent>pointerInfo.event).pointerId];
  280. }
  281. }
  282. });
  283. // Update bounding box positions
  284. this._renderObserver = this.gizmoLayer.originalScene.onBeforeRenderObservable.add(() => {
  285. // Only update the bouding box if scaling has changed
  286. if (this.attachedMesh && !this._existingMeshScale.equals(this.attachedMesh.scaling)) {
  287. this.updateBoundingBox();
  288. } else if (this.fixedDragMeshScreenSize) {
  289. this._updateRotationSpheres();
  290. this._updateScaleBoxes();
  291. }
  292. // If dragg mesh is enabled and dragging, update the attached mesh pose to match the drag mesh
  293. if (this._dragMesh && this.attachedMesh && this.pointerDragBehavior.dragging) {
  294. this._lineBoundingBox.position.rotateByQuaternionToRef(this._rootMesh.rotationQuaternion!, this._tmpVector);
  295. this.attachedMesh.setAbsolutePosition(this._dragMesh.position.add(this._tmpVector.scale(-1)));
  296. }
  297. });
  298. this.updateBoundingBox();
  299. }
  300. protected _attachedMeshChanged(value: Nullable<AbstractMesh>) {
  301. if (value) {
  302. // Reset anchor mesh to match attached mesh's scale
  303. // This is needed to avoid invalid box/sphere position on first drag
  304. PivotTools._RemoveAndStorePivotPoint(value);
  305. var originalParent = value.parent;
  306. this._anchorMesh.addChild(value);
  307. this._anchorMesh.removeChild(value);
  308. value.setParent(originalParent);
  309. PivotTools._RestorePivotPoint(value);
  310. this.updateBoundingBox();
  311. value.getChildMeshes(false).forEach((m) => {
  312. m.markAsDirty("scaling");
  313. });
  314. this.gizmoLayer.utilityLayerScene.onAfterRenderObservable.addOnce(() => {
  315. this._updateDummy();
  316. });
  317. }
  318. }
  319. private _selectNode(selectedMesh: Nullable<Mesh>) {
  320. this._rotateSpheresParent.getChildMeshes()
  321. .concat(this._scaleBoxesParent.getChildMeshes()).forEach((m) => {
  322. m.isVisible = (!selectedMesh || m == selectedMesh);
  323. });
  324. }
  325. /**
  326. * Updates the bounding box information for the Gizmo
  327. */
  328. public updateBoundingBox() {
  329. if (this.attachedMesh) {
  330. PivotTools._RemoveAndStorePivotPoint(this.attachedMesh);
  331. var originalParent = this.attachedMesh.parent;
  332. this.attachedMesh.setParent(null);
  333. this._update();
  334. // Rotate based on axis
  335. if (!this.attachedMesh.rotationQuaternion) {
  336. this.attachedMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.attachedMesh.rotation.y, this.attachedMesh.rotation.x, this.attachedMesh.rotation.z);
  337. }
  338. if (!this._anchorMesh.rotationQuaternion) {
  339. this._anchorMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this._anchorMesh.rotation.y, this._anchorMesh.rotation.x, this._anchorMesh.rotation.z);
  340. }
  341. this._anchorMesh.rotationQuaternion.copyFrom(this.attachedMesh.rotationQuaternion);
  342. // Store original position and reset mesh to origin before computing the bounding box
  343. this._tmpQuaternion.copyFrom(this.attachedMesh.rotationQuaternion);
  344. this._tmpVector.copyFrom(this.attachedMesh.position);
  345. this.attachedMesh.rotationQuaternion.set(0, 0, 0, 1);
  346. this.attachedMesh.position.set(0, 0, 0);
  347. // Update bounding dimensions/positions
  348. var boundingMinMax = this.attachedMesh.getHierarchyBoundingVectors(!this.ignoreChildren, this.includeChildPredicate);
  349. boundingMinMax.max.subtractToRef(boundingMinMax.min, this._boundingDimensions);
  350. // Update gizmo to match bounding box scaling and rotation
  351. this._lineBoundingBox.scaling.copyFrom(this._boundingDimensions);
  352. this._lineBoundingBox.position.set((boundingMinMax.max.x + boundingMinMax.min.x) / 2, (boundingMinMax.max.y + boundingMinMax.min.y) / 2, (boundingMinMax.max.z + boundingMinMax.min.z) / 2);
  353. this._rotateSpheresParent.position.copyFrom(this._lineBoundingBox.position);
  354. this._scaleBoxesParent.position.copyFrom(this._lineBoundingBox.position);
  355. this._lineBoundingBox.computeWorldMatrix();
  356. this._anchorMesh.position.copyFrom(this._lineBoundingBox.absolutePosition);
  357. // restore position/rotation values
  358. this.attachedMesh.rotationQuaternion.copyFrom(this._tmpQuaternion);
  359. this.attachedMesh.position.copyFrom(this._tmpVector);
  360. this.attachedMesh.setParent(originalParent);
  361. }
  362. this._updateRotationSpheres();
  363. this._updateScaleBoxes();
  364. if (this.attachedMesh) {
  365. this._existingMeshScale.copyFrom(this.attachedMesh.scaling);
  366. PivotTools._RestorePivotPoint(this.attachedMesh);
  367. }
  368. }
  369. private _updateRotationSpheres() {
  370. var rotateSpheres = this._rotateSpheresParent.getChildMeshes();
  371. for (var i = 0; i < 3; i++) {
  372. for (var j = 0; j < 2; j++) {
  373. for (var k = 0; k < 2; k++) {
  374. var index = ((i * 4) + (j * 2)) + k;
  375. if (i == 0) {
  376. rotateSpheres[index].position.set(this._boundingDimensions.x / 2, this._boundingDimensions.y * j, this._boundingDimensions.z * k);
  377. rotateSpheres[index].position.addInPlace(new Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
  378. rotateSpheres[index].lookAt(Vector3.Cross(rotateSpheres[index].position.normalizeToNew(), Vector3.Right()).normalizeToNew().add(rotateSpheres[index].position));
  379. }
  380. if (i == 1) {
  381. rotateSpheres[index].position.set(this._boundingDimensions.x * j, this._boundingDimensions.y / 2, this._boundingDimensions.z * k);
  382. rotateSpheres[index].position.addInPlace(new Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
  383. rotateSpheres[index].lookAt(Vector3.Cross(rotateSpheres[index].position.normalizeToNew(), Vector3.Up()).normalizeToNew().add(rotateSpheres[index].position));
  384. }
  385. if (i == 2) {
  386. rotateSpheres[index].position.set(this._boundingDimensions.x * j, this._boundingDimensions.y * k, this._boundingDimensions.z / 2);
  387. rotateSpheres[index].position.addInPlace(new Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
  388. rotateSpheres[index].lookAt(Vector3.Cross(rotateSpheres[index].position.normalizeToNew(), Vector3.Forward()).normalizeToNew().add(rotateSpheres[index].position));
  389. }
  390. if (this.fixedDragMeshScreenSize && this.gizmoLayer.utilityLayerScene.activeCamera) {
  391. rotateSpheres[index].absolutePosition.subtractToRef(this.gizmoLayer.utilityLayerScene.activeCamera.position, this._tmpVector);
  392. var distanceFromCamera = this.rotationSphereSize * this._tmpVector.length() / this.fixedDragMeshScreenSizeDistanceFactor;
  393. rotateSpheres[index].scaling.set(distanceFromCamera, distanceFromCamera, distanceFromCamera);
  394. } else {
  395. rotateSpheres[index].scaling.set(this.rotationSphereSize, this.rotationSphereSize, this.rotationSphereSize);
  396. }
  397. }
  398. }
  399. }
  400. }
  401. private _updateScaleBoxes() {
  402. var scaleBoxes = this._scaleBoxesParent.getChildMeshes();
  403. for (var i = 0; i < 2; i++) {
  404. for (var j = 0; j < 2; j++) {
  405. for (var k = 0; k < 2; k++) {
  406. var index = ((i * 4) + (j * 2)) + k;
  407. if (scaleBoxes[index]) {
  408. scaleBoxes[index].position.set(this._boundingDimensions.x * i, this._boundingDimensions.y * j, this._boundingDimensions.z * k);
  409. scaleBoxes[index].position.addInPlace(new Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
  410. if (this.fixedDragMeshScreenSize && this.gizmoLayer.utilityLayerScene.activeCamera) {
  411. scaleBoxes[index].absolutePosition.subtractToRef(this.gizmoLayer.utilityLayerScene.activeCamera.position, this._tmpVector);
  412. var distanceFromCamera = this.scaleBoxSize * this._tmpVector.length() / this.fixedDragMeshScreenSizeDistanceFactor;
  413. scaleBoxes[index].scaling.set(distanceFromCamera, distanceFromCamera, distanceFromCamera);
  414. } else {
  415. scaleBoxes[index].scaling.set(this.scaleBoxSize, this.scaleBoxSize, this.scaleBoxSize);
  416. }
  417. }
  418. }
  419. }
  420. }
  421. }
  422. /**
  423. * Enables rotation on the specified axis and disables rotation on the others
  424. * @param axis The list of axis that should be enabled (eg. "xy" or "xyz")
  425. */
  426. public setEnabledRotationAxis(axis: string) {
  427. this._rotateSpheresParent.getChildMeshes().forEach((m, i) => {
  428. if (i < 4) {
  429. m.setEnabled(axis.indexOf("x") != -1);
  430. } else if (i < 8) {
  431. m.setEnabled(axis.indexOf("y") != -1);
  432. } else {
  433. m.setEnabled(axis.indexOf("z") != -1);
  434. }
  435. });
  436. }
  437. /**
  438. * Enables/disables scaling
  439. * @param enable if scaling should be enabled
  440. */
  441. public setEnabledScaling(enable: boolean) {
  442. this._scaleBoxesParent.getChildMeshes().forEach((m, i) => {
  443. m.setEnabled(enable);
  444. });
  445. }
  446. private _updateDummy() {
  447. if (this._dragMesh) {
  448. this._dragMesh.position.copyFrom(this._lineBoundingBox.getAbsolutePosition());
  449. this._dragMesh.scaling.copyFrom(this._lineBoundingBox.scaling);
  450. this._dragMesh.rotationQuaternion!.copyFrom(this._rootMesh.rotationQuaternion!);
  451. }
  452. }
  453. /**
  454. * Enables a pointer drag behavior on the bounding box of the gizmo
  455. */
  456. public enableDragBehavior() {
  457. this._dragMesh = Mesh.CreateBox("dummy", 1, this.gizmoLayer.utilityLayerScene);
  458. this._dragMesh.visibility = 0;
  459. this._dragMesh.rotationQuaternion = new Quaternion();
  460. this.pointerDragBehavior.useObjectOrienationForDragging = false;
  461. this._dragMesh.addBehavior(this.pointerDragBehavior);
  462. }
  463. /**
  464. * Disposes of the gizmo
  465. */
  466. public dispose() {
  467. this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
  468. this.gizmoLayer.originalScene.onBeforeRenderObservable.remove(this._renderObserver);
  469. this._lineBoundingBox.dispose();
  470. this._rotateSpheresParent.dispose();
  471. this._scaleBoxesParent.dispose();
  472. if (this._dragMesh) {
  473. this._dragMesh.dispose();
  474. }
  475. super.dispose();
  476. }
  477. /**
  478. * Makes a mesh not pickable and wraps the mesh inside of a bounding box mesh that is pickable. (This is useful to avoid picking within complex geometry)
  479. * @param mesh the mesh to wrap in the bounding box mesh and make not pickable
  480. * @returns the bounding box mesh with the passed in mesh as a child
  481. */
  482. public static MakeNotPickableAndWrapInBoundingBox(mesh: Mesh): Mesh {
  483. var makeNotPickable = (root: AbstractMesh) => {
  484. root.isPickable = false;
  485. root.getChildMeshes().forEach((c) => {
  486. makeNotPickable(c);
  487. });
  488. };
  489. makeNotPickable(mesh);
  490. // Reset position to get boudning box from origin with no rotation
  491. if (!mesh.rotationQuaternion) {
  492. mesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(mesh.rotation.y, mesh.rotation.x, mesh.rotation.z);
  493. }
  494. var oldPos = mesh.position.clone();
  495. var oldRot = mesh.rotationQuaternion.clone();
  496. mesh.rotationQuaternion.set(0, 0, 0, 1);
  497. mesh.position.set(0, 0, 0);
  498. // Update bounding dimensions/positions
  499. var box = MeshBuilder.CreateBox("box", { size: 1 }, mesh.getScene());
  500. var boundingMinMax = mesh.getHierarchyBoundingVectors();
  501. boundingMinMax.max.subtractToRef(boundingMinMax.min, box.scaling);
  502. // Adjust scale to avoid undefined behavior when adding child
  503. if (box.scaling.y === 0) {
  504. box.scaling.y = Epsilon;
  505. }
  506. if (box.scaling.x === 0) {
  507. box.scaling.x = Epsilon;
  508. }
  509. if (box.scaling.z === 0) {
  510. box.scaling.z = Epsilon;
  511. }
  512. box.position.set((boundingMinMax.max.x + boundingMinMax.min.x) / 2, (boundingMinMax.max.y + boundingMinMax.min.y) / 2, (boundingMinMax.max.z + boundingMinMax.min.z) / 2);
  513. // Restore original positions
  514. mesh.addChild(box);
  515. mesh.rotationQuaternion.copyFrom(oldRot);
  516. mesh.position.copyFrom(oldPos);
  517. // Reverse parenting
  518. mesh.removeChild(box);
  519. box.addChild(mesh);
  520. box.visibility = 0;
  521. return box;
  522. }
  523. /**
  524. * CustomMeshes are not supported by this gizmo
  525. * @param mesh The mesh to replace the default mesh of the gizmo
  526. */
  527. public setCustomMesh(mesh: Mesh) {
  528. Logger.Error("Custom meshes are not supported on this gizmo");
  529. }
  530. }