boundingBoxGizmo.ts 33 KB

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