boundingBoxGizmo.ts 32 KB

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