babylon.boundingBoxGizmo.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. module BABYLON {
  2. /**
  3. * Bounding box gizmo
  4. */
  5. export class BoundingBoxGizmo extends Gizmo {
  6. private _lineBoundingBox:AbstractMesh;
  7. private _rotateSpheresParent:AbstractMesh;
  8. private _scaleBoxesParent:AbstractMesh;
  9. private _boundingDimensions = new BABYLON.Vector3(1,1,1);
  10. private _renderObserver:Nullable<Observer<Scene>> = null;
  11. private _pointerObserver:Nullable<Observer<PointerInfo>> = null;
  12. private _scaleDragSpeed = 0.2;
  13. private _tmpQuaternion = new Quaternion();
  14. private _tmpVector = new Vector3(0,0,0);
  15. /**
  16. * Creates an BoundingBoxGizmo
  17. * @param gizmoLayer The utility layer the gizmo will be added to
  18. * @param color The color of the gizmo
  19. */
  20. constructor(gizmoLayer:UtilityLayerRenderer, color:Color3){
  21. super(gizmoLayer);
  22. // Do not update the gizmo's scale so it has a fixed size to the object its attached to
  23. this._updateScale = false;
  24. // Create Materials
  25. var coloredMaterial = new BABYLON.StandardMaterial("", gizmoLayer.utilityLayerScene);
  26. coloredMaterial.disableLighting = true;
  27. coloredMaterial.emissiveColor = color;
  28. var hoverColoredMaterial = new BABYLON.StandardMaterial("", gizmoLayer.utilityLayerScene);
  29. hoverColoredMaterial.disableLighting = true;
  30. hoverColoredMaterial.emissiveColor = color.clone().add(new Color3(0.2,0.2,0.2));
  31. // Build bounding box out of lines
  32. this._lineBoundingBox = new BABYLON.AbstractMesh("", gizmoLayer.utilityLayerScene);
  33. this._lineBoundingBox.rotationQuaternion = new BABYLON.Quaternion();
  34. var lines = []
  35. lines.push(BABYLON.MeshBuilder.CreateLines("lines", {points: [new BABYLON.Vector3(0,0,0), new BABYLON.Vector3(this._boundingDimensions.x,0,0)]}, gizmoLayer.utilityLayerScene));
  36. lines.push(BABYLON.MeshBuilder.CreateLines("lines", {points: [new BABYLON.Vector3(0,0,0), new BABYLON.Vector3(0,this._boundingDimensions.y,0)]}, gizmoLayer.utilityLayerScene));
  37. lines.push(BABYLON.MeshBuilder.CreateLines("lines", {points: [new BABYLON.Vector3(0,0,0), new BABYLON.Vector3(0,0,this._boundingDimensions.z)]}, gizmoLayer.utilityLayerScene));
  38. lines.push(BABYLON.MeshBuilder.CreateLines("lines", {points: [new BABYLON.Vector3(this._boundingDimensions.x,0,0), new BABYLON.Vector3(this._boundingDimensions.x,this._boundingDimensions.y,0)]}, gizmoLayer.utilityLayerScene));
  39. lines.push(BABYLON.MeshBuilder.CreateLines("lines", {points: [new BABYLON.Vector3(this._boundingDimensions.x,0,0), new BABYLON.Vector3(this._boundingDimensions.x,0,this._boundingDimensions.z)]}, gizmoLayer.utilityLayerScene));
  40. lines.push(BABYLON.MeshBuilder.CreateLines("lines", {points: [new BABYLON.Vector3(0,this._boundingDimensions.y,0), new BABYLON.Vector3(this._boundingDimensions.x,this._boundingDimensions.y,0)]}, gizmoLayer.utilityLayerScene));
  41. lines.push(BABYLON.MeshBuilder.CreateLines("lines", {points: [new BABYLON.Vector3(0,this._boundingDimensions.y,0), new BABYLON.Vector3(0,this._boundingDimensions.y,this._boundingDimensions.z)]}, gizmoLayer.utilityLayerScene));
  42. lines.push(BABYLON.MeshBuilder.CreateLines("lines", {points: [new BABYLON.Vector3(0,0,this._boundingDimensions.z), new BABYLON.Vector3(this._boundingDimensions.x,0,this._boundingDimensions.z)]}, gizmoLayer.utilityLayerScene));
  43. lines.push(BABYLON.MeshBuilder.CreateLines("lines", {points: [new BABYLON.Vector3(0,0,this._boundingDimensions.z), new BABYLON.Vector3(0,this._boundingDimensions.y,this._boundingDimensions.z)]}, gizmoLayer.utilityLayerScene));
  44. lines.push(BABYLON.MeshBuilder.CreateLines("lines", {points: [new BABYLON.Vector3(this._boundingDimensions.x,this._boundingDimensions.y,this._boundingDimensions.z), new BABYLON.Vector3(0,this._boundingDimensions.y,this._boundingDimensions.z)]}, gizmoLayer.utilityLayerScene));
  45. lines.push(BABYLON.MeshBuilder.CreateLines("lines", {points: [new BABYLON.Vector3(this._boundingDimensions.x,this._boundingDimensions.y,this._boundingDimensions.z), new BABYLON.Vector3(this._boundingDimensions.x,0,this._boundingDimensions.z)]}, gizmoLayer.utilityLayerScene));
  46. lines.push(BABYLON.MeshBuilder.CreateLines("lines", {points: [new BABYLON.Vector3(this._boundingDimensions.x,this._boundingDimensions.y,this._boundingDimensions.z), new BABYLON.Vector3(this._boundingDimensions.x,this._boundingDimensions.y,0)]}, gizmoLayer.utilityLayerScene));
  47. lines.forEach((l)=>{
  48. l.color = color
  49. l.position.addInPlace(new BABYLON.Vector3(-this._boundingDimensions.x/2,-this._boundingDimensions.y/2,-this._boundingDimensions.z/2))
  50. l.isPickable=false;
  51. this._lineBoundingBox.addChild(l)
  52. })
  53. this._rootMesh.addChild(this._lineBoundingBox);
  54. // Create rotation spheres
  55. this._rotateSpheresParent = new BABYLON.AbstractMesh("", gizmoLayer.utilityLayerScene);
  56. this._rotateSpheresParent.rotationQuaternion = new Quaternion();
  57. for(let i=0;i<12;i++){
  58. let sphere = BABYLON.MeshBuilder.CreateSphere("", {diameter: 0.1}, gizmoLayer.utilityLayerScene);
  59. sphere.rotationQuaternion = new Quaternion();
  60. sphere.material = coloredMaterial;
  61. // Drag behavior
  62. var _dragBehavior = new PointerDragBehavior({});
  63. _dragBehavior.moveAttached = false;
  64. _dragBehavior.updateDragPlane = false;
  65. sphere.addBehavior(_dragBehavior);
  66. let startingTurnDirection = new Vector3(1,0,0);
  67. let totalTurnAmountOfDrag = 0;
  68. _dragBehavior.onDragStartObservable.add((event)=>{
  69. startingTurnDirection.copyFrom(sphere.forward);
  70. totalTurnAmountOfDrag = 0;
  71. })
  72. _dragBehavior.onDragObservable.add((event)=>{
  73. if(this.attachedMesh){
  74. var worldDragDirection = startingTurnDirection;
  75. // Project the world right on to the drag plane
  76. var toSub = event.dragPlaneNormal.scale(Vector3.Dot(event.dragPlaneNormal, worldDragDirection));
  77. var dragAxis = worldDragDirection.subtract(toSub).normalizeToNew();
  78. // project drag delta on to the resulting drag axis and rotate based on that
  79. var projectDist = -Vector3.Dot(dragAxis, event.delta);
  80. // Rotate based on axis
  81. if(!this.attachedMesh.rotationQuaternion){
  82. this.attachedMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.attachedMesh.rotation.y,this.attachedMesh.rotation.x,this.attachedMesh.rotation.z);
  83. }
  84. // Do not allow the object to turn more than a full circle
  85. totalTurnAmountOfDrag+=projectDist;
  86. if(Math.abs(totalTurnAmountOfDrag)<=2*Math.PI){
  87. if(i>=8){
  88. Quaternion.RotationYawPitchRollToRef(0,0,projectDist, this._tmpQuaternion);
  89. }else if(i>=4){
  90. Quaternion.RotationYawPitchRollToRef(projectDist,0,0, this._tmpQuaternion);
  91. }else{
  92. Quaternion.RotationYawPitchRollToRef(0,projectDist,0, this._tmpQuaternion);
  93. }
  94. this.attachedMesh.rotationQuaternion!.multiplyInPlace(this._tmpQuaternion);
  95. }
  96. }
  97. });
  98. // Selection/deselection
  99. _dragBehavior.onDragStartObservable.add(()=>{
  100. this._selectNode(sphere)
  101. })
  102. _dragBehavior.onDragEndObservable.add(()=>{
  103. this._selectNode(null)
  104. })
  105. this._rotateSpheresParent.addChild(sphere);
  106. }
  107. this._rootMesh.addChild(this._rotateSpheresParent);
  108. // Create scale cubes
  109. this._scaleBoxesParent = new BABYLON.AbstractMesh("", gizmoLayer.utilityLayerScene);
  110. this._scaleBoxesParent.rotationQuaternion = new Quaternion();
  111. for(var i=0;i<2;i++){
  112. for(var j=0;j<2;j++){
  113. for(var k=0;k<2;k++){
  114. let box = BABYLON.MeshBuilder.CreateBox("", {size: 0.1}, gizmoLayer.utilityLayerScene);
  115. box.material = coloredMaterial;
  116. // Dragging logic
  117. let dragAxis = new BABYLON.Vector3(i==0?-1:1,j==0?-1:1,k==0?-1:1);
  118. var _dragBehavior = new PointerDragBehavior({dragAxis: dragAxis});
  119. _dragBehavior.moveAttached = false;
  120. box.addBehavior(_dragBehavior);
  121. _dragBehavior.onDragObservable.add((event)=>{
  122. if(this.attachedMesh){
  123. // Current boudning box dimensions
  124. var boundingInfo = this.attachedMesh.getBoundingInfo().boundingBox;
  125. var boundBoxDimensions = boundingInfo.maximum.subtract(boundingInfo.minimum).multiplyInPlace(this.attachedMesh.scaling);
  126. // Get the change in bounding box size/2 and add this to the mesh's position to offset from scaling with center pivot point
  127. var deltaScale = new Vector3(event.dragDistance,event.dragDistance,event.dragDistance);
  128. deltaScale.scaleInPlace(this._scaleDragSpeed);
  129. var scaleRatio = deltaScale.divide(this.attachedMesh.scaling).scaleInPlace(0.5);
  130. var moveDirection = boundBoxDimensions.multiply(scaleRatio).multiplyInPlace(dragAxis);
  131. var worldMoveDirection = Vector3.TransformCoordinates(moveDirection, this.attachedMesh.getWorldMatrix().getRotationMatrix());
  132. // Update scale and position
  133. this.attachedMesh.scaling.addInPlace(deltaScale);
  134. this.attachedMesh.getAbsolutePosition().addToRef(worldMoveDirection, this._tmpVector)
  135. this.attachedMesh.setAbsolutePosition(this._tmpVector);
  136. }
  137. })
  138. // Selection/deselection
  139. _dragBehavior.onDragStartObservable.add(()=>{
  140. this._selectNode(box)
  141. })
  142. _dragBehavior.onDragEndObservable.add(()=>{
  143. this._selectNode(null)
  144. })
  145. this._scaleBoxesParent.addChild(box);
  146. }
  147. }
  148. }
  149. this._rootMesh.addChild(this._scaleBoxesParent);
  150. // Hover color change
  151. var pointerIds = new Array<AbstractMesh>();
  152. this._pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo, eventState)=>{
  153. if(!pointerIds[(<PointerEvent>pointerInfo.event).pointerId]){
  154. this._rotateSpheresParent.getChildMeshes().concat(this._scaleBoxesParent.getChildMeshes()).forEach((mesh)=>{
  155. if(pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh == mesh){
  156. pointerIds[(<PointerEvent>pointerInfo.event).pointerId]=mesh;
  157. mesh.material = hoverColoredMaterial;
  158. }
  159. });
  160. }else{
  161. if(pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh != pointerIds[(<PointerEvent>pointerInfo.event).pointerId]){
  162. pointerIds[(<PointerEvent>pointerInfo.event).pointerId].material = coloredMaterial;
  163. delete pointerIds[(<PointerEvent>pointerInfo.event).pointerId];
  164. }
  165. }
  166. });
  167. // Update bounding box positions
  168. this._renderObserver = this.gizmoLayer.originalScene.onBeforeRenderObservable.add(()=>{
  169. this._updateBoundingBox();
  170. })
  171. this._updateBoundingBox();
  172. }
  173. private _selectNode(selectedMesh:Nullable<Mesh>){
  174. this._rotateSpheresParent.getChildMeshes()
  175. .concat(this._scaleBoxesParent.getChildMeshes()).forEach((m,i)=>{
  176. m.isVisible = (!selectedMesh || m == selectedMesh);
  177. })
  178. }
  179. private _updateBoundingBox(){
  180. if(this.attachedMesh){
  181. // Update bounding dimensions/positions
  182. var boundingInfo = this.attachedMesh.getBoundingInfo().boundingBox;
  183. var boundBoxDimensions = boundingInfo.maximum.subtract(boundingInfo.minimum).multiplyInPlace(this.attachedMesh.scaling);
  184. this._boundingDimensions.copyFrom(boundBoxDimensions);
  185. this._lineBoundingBox.scaling.copyFrom(this._boundingDimensions);
  186. }
  187. // Update rotation sphere locations
  188. var rotateSpheres = this._rotateSpheresParent.getChildMeshes();
  189. for(var i=0;i<3;i++){
  190. for(var j=0;j<2;j++){
  191. for(var k=0;k<2;k++){
  192. var index= ((i*4)+(j*2))+k
  193. if(i==0){
  194. rotateSpheres[index].position.set(this._boundingDimensions.x/2,this._boundingDimensions.y*j,this._boundingDimensions.z*k);
  195. rotateSpheres[index].position.addInPlace(new BABYLON.Vector3(-this._boundingDimensions.x/2,-this._boundingDimensions.y/2,-this._boundingDimensions.z/2));
  196. rotateSpheres[index].lookAt(Vector3.Cross(Vector3.Right(), rotateSpheres[index].position.normalizeToNew()).normalizeToNew().add(rotateSpheres[index].position));
  197. }
  198. if(i==1){
  199. rotateSpheres[index].position.set(this._boundingDimensions.x*j,this._boundingDimensions.y/2,this._boundingDimensions.z*k);
  200. rotateSpheres[index].position.addInPlace(new BABYLON.Vector3(-this._boundingDimensions.x/2,-this._boundingDimensions.y/2,-this._boundingDimensions.z/2));
  201. rotateSpheres[index].lookAt(Vector3.Cross(Vector3.Up(), rotateSpheres[index].position.normalizeToNew()).normalizeToNew().add(rotateSpheres[index].position));
  202. }
  203. if(i==2){
  204. rotateSpheres[index].position.set(this._boundingDimensions.x*j,this._boundingDimensions.y*k,this._boundingDimensions.z/2);
  205. rotateSpheres[index].position.addInPlace(new BABYLON.Vector3(-this._boundingDimensions.x/2,-this._boundingDimensions.y/2,-this._boundingDimensions.z/2));
  206. rotateSpheres[index].lookAt(Vector3.Cross(Vector3.Forward(), rotateSpheres[index].position.normalizeToNew()).normalizeToNew().add(rotateSpheres[index].position));
  207. }
  208. }
  209. }
  210. }
  211. // Update scale box locations
  212. var scaleBoxes = this._scaleBoxesParent.getChildMeshes();
  213. for(var i=0;i<2;i++){
  214. for(var j=0;j<2;j++){
  215. for(var k=0;k<2;k++){
  216. var index= ((i*4)+(j*2))+k;
  217. if(scaleBoxes[index]){
  218. scaleBoxes[index].position.set(this._boundingDimensions.x*i,this._boundingDimensions.y*j,this._boundingDimensions.z*k);
  219. scaleBoxes[index].position.addInPlace(new BABYLON.Vector3(-this._boundingDimensions.x/2,-this._boundingDimensions.y/2,-this._boundingDimensions.z/2));
  220. }
  221. }
  222. }
  223. }
  224. }
  225. /**
  226. * Enables rotation on the specified axis and disables rotation on the others
  227. * @param axis The list of axis that should be enabled (eg. "xy" or "xyz")
  228. */
  229. public setEnabledRotationAxis(axis:string){
  230. this._rotateSpheresParent.getChildMeshes().forEach((m,i)=>{
  231. if(i<4){
  232. m.setEnabled(axis.indexOf("x")!=-1);
  233. }else if(i<8){
  234. m.setEnabled(axis.indexOf("y")!=-1);
  235. }else{
  236. m.setEnabled(axis.indexOf("z")!=-1);
  237. }
  238. })
  239. }
  240. /**
  241. * Disposes of the gizmo
  242. */
  243. public dispose(){
  244. this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
  245. this.gizmoLayer.originalScene.onBeforeRenderObservable.remove(this._renderObserver);
  246. this._lineBoundingBox.dispose();
  247. this._rotateSpheresParent.dispose();
  248. this._scaleBoxesParent.dispose();
  249. super.dispose();
  250. }
  251. }
  252. }