Kaynağa Gözat

Merge pull request #4576 from TrevorDev/gizmoSnapping

Gizmo snapping
Trevor Baron 7 yıl önce
ebeveyn
işleme
1eee542b81

+ 1 - 1
dist/preview release/what's new.md

@@ -8,7 +8,7 @@
 - New GUI 3D controls toolset. [Complete doc + demos](http://doc.babylonjs.com/how_to/gui3d) ([Deltakosh](https://github.com/deltakosh))
 - Added [Environment Texture Tools](https://doc.babylonjs.com/how_to/physically_based_rendering#creating-a-compressed-environment-texture) to reduce the size of the usual .DDS file ([sebavan](http://www.github.com/sebavan))
 - New GUI control: the [Grid](http://doc.babylonjs.com/how_to/gui#grid) ([Deltakosh](https://github.com/deltakosh))
-- Gizmo and GizmoManager classes used to manipulate meshes in a scene. Gizmo types include: position, rotation, scale, and bounding box ([TrevorDev](https://github.com/TrevorDev))
+- Gizmo and GizmoManager classes used to manipulate meshes in a scene. Gizmo types include: position, rotation, scale and bounding box ([TrevorDev](https://github.com/TrevorDev))
 - Particle system improvements ([Deltakosh](https://github.com/deltakosh))
   - Improved CPU particles rendering performance (up to x2 on low end devices)
   - Added support for `isBillboardBased`. [Doc](http://doc.babylonjs.com/babylon101/particles#alignment)

+ 32 - 6
src/Gizmos/babylon.axisDragGizmo.ts

@@ -6,6 +6,15 @@ module BABYLON {
         private _dragBehavior:PointerDragBehavior;
         private _pointerObserver:Nullable<Observer<PointerInfo>> = null;
         /**
+         * Drag distance in babylon units that the gizmo will snap to when dragged (Default: 0)
+         */
+        public snapDistance = 0;
+        /**
+         * Event that fires each time the gizmo snaps to a new location.
+         * * snapDistance is the the change in distance
+         */
+        public onSnapObservable = new Observable<{snapDistance:number}>();
+        /**
          * Creates an AxisDragGizmo
          * @param gizmoLayer The utility layer the gizmo will be added to
          * @param dragAxis The axis which the gizmo will be able to drag on
@@ -42,16 +51,30 @@ module BABYLON {
 
             this._rootMesh.addChild(arrow)
 
+            var currentSnapDragDistance = 0;
+            var tmpVector = new Vector3();
+            var tmpSnapEvent = {snapDistance: 0};
             // Add drag behavior to handle events when the gizmo is dragged
             this._dragBehavior = new PointerDragBehavior({dragAxis: dragAxis});
             this._dragBehavior.moveAttached = false;
             this._rootMesh.addBehavior(this._dragBehavior);
             this._dragBehavior.onDragObservable.add((event)=>{
-                if(!this.interactionsEnabled){
-                    return;
-                }
                 if(this.attachedMesh){
-                    this.attachedMesh.position.addInPlace(event.delta);
+                    // Snapping logic
+                    if(this.snapDistance == 0){
+                        this.attachedMesh.position.addInPlace(event.delta);
+                    }else{
+                        currentSnapDragDistance+=event.dragDistance
+                        if(Math.abs(currentSnapDragDistance)>this.snapDistance){
+                            var dragSteps = Math.floor(Math.abs(currentSnapDragDistance)/this.snapDistance);
+                            currentSnapDragDistance = currentSnapDragDistance % this.snapDistance;
+                            event.delta.normalizeToRef(tmpVector);
+                            tmpVector.scaleInPlace(this.snapDistance*dragSteps);
+                            this.attachedMesh.position.addInPlace(tmpVector);
+                            tmpSnapEvent.snapDistance = this.snapDistance*dragSteps;
+                            this.onSnapObservable.notifyObservers(tmpSnapEvent);
+                        }
+                    }
                 }
             })
 
@@ -67,13 +90,16 @@ module BABYLON {
                 }
             });
         }
-        protected _onInteractionsEnabledChanged(value:boolean){
-            this._dragBehavior.enabled = value;
+        protected _attachedMeshChanged(value:Nullable<AbstractMesh>){
+            if(this._dragBehavior){
+                this._dragBehavior.enabled = value?true:false;
+            }
         }
         /**
          * Disposes of the gizmo
          */
         public dispose(){
+            this.onSnapObservable.clear();
             this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
             this._dragBehavior.detach();
             super.dispose();

+ 39 - 7
src/Gizmos/babylon.axisScaleGizmo.ts

@@ -6,6 +6,15 @@ module BABYLON {
         private _dragBehavior:PointerDragBehavior;
         private _pointerObserver:Nullable<Observer<PointerInfo>> = null;
         /**
+         * Scale distance in babylon units that the gizmo will snap to when dragged (Default: 0)
+         */
+        public snapDistance = 0;
+        /**
+         * Event that fires each time the gizmo snaps to a new location.
+         * * snapDistance is the the change in distance
+         */
+        public onSnapObservable = new Observable<{snapDistance:number}>();
+        /**
          * Creates an AxisScaleGizmo
          * @param gizmoLayer The utility layer the gizmo will be added to
          * @param dragAxis The axis which the gizmo will be able to scale on
@@ -46,13 +55,28 @@ module BABYLON {
             this._dragBehavior.moveAttached = false;
             this._rootMesh.addBehavior(this._dragBehavior);
 
+            var currentSnapDragDistance = 0;
             var tmpVector = new Vector3();
-            this._dragBehavior.onDragObservable.add((event)=>{
-                if(!this.interactionsEnabled){
-                    return;
-                }
+            var tmpSnapEvent = {snapDistance: 0};
+            this._dragBehavior.onDragObservable.add((event)=>{                
                 if(this.attachedMesh){
-                    dragAxis.scaleToRef(event.dragDistance, tmpVector);
+                    // Snapping logic
+                    var snapped = false;
+                    var dragSteps = 0;
+                    if(this.snapDistance == 0){
+                        dragAxis.scaleToRef(event.dragDistance, tmpVector);
+                    }else{
+                        currentSnapDragDistance+=event.dragDistance;
+                        if(Math.abs(currentSnapDragDistance)>this.snapDistance){
+                            dragSteps = Math.floor(currentSnapDragDistance/this.snapDistance);
+                            currentSnapDragDistance = currentSnapDragDistance % this.snapDistance;
+                            dragAxis.scaleToRef(this.snapDistance*dragSteps, tmpVector);
+                            snapped = true;
+                        }else{
+                            tmpVector.scaleInPlace(0);
+                        }
+                    }
+                    
                     var invertCount = 0;
                     if(this.attachedMesh.scaling["x"] < 0){
                         invertCount++;
@@ -68,6 +92,11 @@ module BABYLON {
                     }else{
                         this.attachedMesh.scaling.subtractInPlace(tmpVector);
                     }
+
+                    if(snapped){
+                        tmpSnapEvent.snapDistance = this.snapDistance*dragSteps;
+                        this.onSnapObservable.notifyObservers(tmpSnapEvent);
+                    }
                 }
             })
 
@@ -84,14 +113,17 @@ module BABYLON {
             });
         }
         
-        protected _onInteractionsEnabledChanged(value:boolean){
-            this._dragBehavior.enabled = value;
+        protected _attachedMeshChanged(value:Nullable<AbstractMesh>){
+            if(this._dragBehavior){
+                this._dragBehavior.enabled = value ? true : false;
+            }
         }
         
         /**
          * Disposes of the gizmo
          */
         public dispose(){
+            this.onSnapObservable.clear();
             this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
             this._dragBehavior.detach();
             super.dispose();

+ 16 - 0
src/Gizmos/babylon.boundingBoxGizmo.ts

@@ -248,6 +248,22 @@ module BABYLON {
         }
 
         /**
+         * Enables rotation on the specified axis and disables rotation on the others
+         * @param axis The list of axis that should be enabled (eg. "xy" or "xyz")
+         */
+        public setEnabledRotationAxis(axis:string){
+            this._rotateSpheresParent.getChildMeshes().forEach((m,i)=>{
+                if(i<4){
+                    m.setEnabled(axis.indexOf("x")!=-1);
+                }else if(i<8){
+                    m.setEnabled(axis.indexOf("y")!=-1);
+                }else{
+                    m.setEnabled(axis.indexOf("z")!=-1);
+                }
+            })
+        }
+
+        /**
          * Disposes of the gizmo
          */
         public dispose(){

+ 12 - 13
src/Gizmos/babylon.gizmo.ts

@@ -7,10 +7,19 @@ module BABYLON {
          * The root mesh of the gizmo
          */
         protected _rootMesh:Mesh;
+        private _attachedMesh:Nullable<AbstractMesh>;
         /**
          * Mesh that the gizmo will be attached to. (eg. on a drag gizmo the mesh that will be dragged)
+         * * When set, interactions will be enabled
          */
-        public attachedMesh:Nullable<AbstractMesh>;
+        public get attachedMesh(){
+            return this._attachedMesh;
+        }
+        public set attachedMesh(value){
+            this._attachedMesh = value;
+            this._rootMesh.setEnabled(value?true:false);
+            this._attachedMeshChanged(value);
+        }
         /**
          * If set the gizmo's rotation will be updated to match the attached mesh each frame (Default: true)
          */
@@ -24,18 +33,7 @@ module BABYLON {
          */
         protected _updateScale = true;
         protected _interactionsEnabled = true;
-        protected _onInteractionsEnabledChanged(value:boolean){
-        }
-
-        /**
-         * If interactions are enabled with this gizmo. (eg. dragging/rotation)
-         */
-        public set interactionsEnabled(value:boolean){
-            this._interactionsEnabled = value;
-            this._onInteractionsEnabledChanged(value);
-        }
-        public get interactionsEnabled(){
-            return this._interactionsEnabled;
+        protected _attachedMeshChanged(value:Nullable<AbstractMesh>){
         }
 
         private _beforeRenderObserver:Nullable<Observer<Scene>>;
@@ -63,6 +61,7 @@ module BABYLON {
                     }
                 }
             })
+            this.attachedMesh = null;
         }
         /**
          * Disposes of the gizmo

+ 43 - 14
src/Gizmos/babylon.planeRotationGizmo.ts

@@ -5,6 +5,17 @@ module BABYLON {
     export class PlaneRotationGizmo extends Gizmo {
         private _dragBehavior:PointerDragBehavior;
         private _pointerObserver:Nullable<Observer<PointerInfo>> = null;
+        
+        /**
+         * Rotation distance in radians that the gizmo will snap to (Default: 0)
+         */
+        public snapDistance = 0;
+        /**
+         * Event that fires each time the gizmo snaps to a new location.
+         * * snapDistance is the the change in distance
+         */
+        public onSnapObservable = new Observable<{snapDistance:number}>();
+
         /**
          * Creates a PlaneRotationGizmo
          * @param gizmoLayer The utility layer the gizmo will be added to
@@ -43,19 +54,18 @@ module BABYLON {
             var lastDragPosition:Nullable<Vector3> = null;
 
             this._dragBehavior.onDragStartObservable.add((e)=>{
-                if(!this.interactionsEnabled){
-                    return;
+                if(this.attachedMesh){
+                    lastDragPosition = e.dragPlanePoint;
                 }
-                lastDragPosition = e.dragPlanePoint;
             })
 
             var rotationMatrix = new Matrix();
             var planeNormalTowardsCamera = new Vector3();
             var localPlaneNormalTowardsCamera = new Vector3();
+
+            var tmpSnapEvent = {snapDistance: 0};
+            var currentSnapDragDistance = 0;
             this._dragBehavior.onDragObservable.add((event)=>{
-                if(!this.interactionsEnabled){
-                    return;
-                }
                 if(this.attachedMesh && lastDragPosition){
                     if(!this.attachedMesh.rotationQuaternion){
                         this.attachedMesh.rotationQuaternion = new BABYLON.Quaternion();
@@ -83,15 +93,31 @@ module BABYLON {
                     var halfCircleSide = Vector3.Dot(localPlaneNormalTowardsCamera, cross) > 0.0;
                     if (halfCircleSide) angle = -angle;
                     
+                    // Snapping logic
+                    var snapped = false;
+                    if(this.snapDistance != 0){
+                        currentSnapDragDistance+=angle
+                        if(Math.abs(currentSnapDragDistance)>this.snapDistance){
+                            var dragSteps = Math.floor(currentSnapDragDistance/this.snapDistance);
+                            currentSnapDragDistance = currentSnapDragDistance % this.snapDistance;
+                            angle = this.snapDistance*dragSteps;
+                            snapped = true;
+                        }else{
+                            angle = 0;
+                        }
+                    }
+                     // Convert angle and axis to quaternion (http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm)
+                     var quaternionCoefficient = Math.sin(angle/2)
+                     var amountToRotate = new BABYLON.Quaternion(planeNormalTowardsCamera.x*quaternionCoefficient,planeNormalTowardsCamera.y*quaternionCoefficient,planeNormalTowardsCamera.z*quaternionCoefficient,Math.cos(angle/2));
 
-                    // Convert angle and axis to quaternion (http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm)
-                    var quaternionCoefficient = Math.sin(angle/2)
-                    var amountToRotate = new BABYLON.Quaternion(planeNormalTowardsCamera.x*quaternionCoefficient,planeNormalTowardsCamera.y*quaternionCoefficient,planeNormalTowardsCamera.z*quaternionCoefficient,Math.cos(angle/2));
-
-                    // Rotate selected mesh quaternion over fixed axis
-                    this.attachedMesh.rotationQuaternion.multiplyToRef(amountToRotate,this.attachedMesh.rotationQuaternion);
+                     // Rotate selected mesh quaternion over fixed axis
+                     this.attachedMesh.rotationQuaternion.multiplyToRef(amountToRotate,this.attachedMesh.rotationQuaternion);
 
                     lastDragPosition = event.dragPlanePoint;
+                    if(snapped){
+                        tmpSnapEvent.snapDistance = angle;
+                        this.onSnapObservable.notifyObservers(tmpSnapEvent);
+                    }
                 }
             })
 
@@ -108,14 +134,17 @@ module BABYLON {
             });
         }
 
-        protected _onInteractionsEnabledChanged(value:boolean){
-            this._dragBehavior.enabled = value;
+        protected _attachedMeshChanged(value:Nullable<AbstractMesh>){
+            if(this._dragBehavior){
+                this._dragBehavior.enabled = value ? true : false;
+            }
         }
 
         /**
          * Disposes of the gizmo
          */
         public dispose(){
+            this.onSnapObservable.clear();
             this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
             this._dragBehavior.detach();
             super.dispose();

+ 7 - 10
src/Gizmos/babylon.positionGizmo.ts

@@ -8,11 +8,13 @@ module BABYLON {
         private _zDrag:AxisDragGizmo;
 
         public set attachedMesh(mesh:Nullable<AbstractMesh>){
-            this._xDrag.attachedMesh = mesh;
-            this._yDrag.attachedMesh = mesh;
-            this._zDrag.attachedMesh = mesh;
+            if(this._xDrag){
+                this._xDrag.attachedMesh = mesh;
+                this._yDrag.attachedMesh = mesh;
+                this._zDrag.attachedMesh = mesh;
+            }
         }
-        /**
+    /**
          * Creates a PositionGizmo
          * @param gizmoLayer The utility layer the gizmo will be added to
          */
@@ -21,12 +23,7 @@ module BABYLON {
             this._xDrag = new AxisDragGizmo(gizmoLayer, new Vector3(1,0,0), BABYLON.Color3.Green().scale(0.5));
             this._yDrag = new AxisDragGizmo(gizmoLayer, new Vector3(0,1,0), BABYLON.Color3.Red().scale(0.5));
             this._zDrag = new AxisDragGizmo(gizmoLayer, new Vector3(0,0,1), BABYLON.Color3.Blue().scale(0.5));
-        }
-
-        protected _onInteractionsEnabledChanged(value:boolean){
-            this._xDrag.interactionsEnabled = value
-            this._yDrag.interactionsEnabled = value
-            this._zDrag.interactionsEnabled = value
+            this.attachedMesh = null;
         }
 
         public set updateGizmoRotationToMatchAttachedMesh(value:boolean){

+ 6 - 9
src/Gizmos/babylon.rotationGizmo.ts

@@ -8,9 +8,11 @@ module BABYLON {
         private _zDrag:PlaneRotationGizmo;
 
         public set attachedMesh(mesh:Nullable<AbstractMesh>){
-            this._xDrag.attachedMesh = mesh;
-            this._yDrag.attachedMesh = mesh;
-            this._zDrag.attachedMesh = mesh;
+            if(this._xDrag){
+                this._xDrag.attachedMesh = mesh;
+                this._yDrag.attachedMesh = mesh;
+                this._zDrag.attachedMesh = mesh;
+            }
         }
         /**
          * Creates a RotationGizmo
@@ -21,12 +23,7 @@ module BABYLON {
             this._xDrag = new PlaneRotationGizmo(gizmoLayer, new Vector3(1,0,0), BABYLON.Color3.Green().scale(0.5));
             this._yDrag = new PlaneRotationGizmo(gizmoLayer, new Vector3(0,1,0), BABYLON.Color3.Red().scale(0.5));
             this._zDrag = new PlaneRotationGizmo(gizmoLayer, new Vector3(0,0,1), BABYLON.Color3.Blue().scale(0.5));
-        }
-
-        protected _onInteractionsEnabledChanged(value:boolean){
-            this._xDrag.interactionsEnabled = value
-            this._yDrag.interactionsEnabled = value
-            this._zDrag.interactionsEnabled = value
+            this.attachedMesh = null;
         }
 
         public set updateGizmoRotationToMatchAttachedMesh(value:boolean){

+ 6 - 9
src/Gizmos/babylon.scaleGizmo.ts

@@ -8,9 +8,11 @@ module BABYLON {
         private _zDrag:AxisScaleGizmo;
 
         public set attachedMesh(mesh:Nullable<AbstractMesh>){
-            this._xDrag.attachedMesh = mesh;
-            this._yDrag.attachedMesh = mesh;
-            this._zDrag.attachedMesh = mesh;
+            if(this._xDrag){
+                this._xDrag.attachedMesh = mesh;
+                this._yDrag.attachedMesh = mesh;
+                this._zDrag.attachedMesh = mesh;
+            }
         }
         /**
          * Creates a ScaleGizmo
@@ -21,12 +23,7 @@ module BABYLON {
             this._xDrag = new AxisScaleGizmo(gizmoLayer, new Vector3(1,0,0), BABYLON.Color3.Green().scale(0.5));
             this._yDrag = new AxisScaleGizmo(gizmoLayer, new Vector3(0,1,0), BABYLON.Color3.Red().scale(0.5));
             this._zDrag = new AxisScaleGizmo(gizmoLayer, new Vector3(0,0,1), BABYLON.Color3.Blue().scale(0.5));
-        }
-
-        protected _onInteractionsEnabledChanged(value:boolean){
-            this._xDrag.interactionsEnabled = value
-            this._yDrag.interactionsEnabled = value
-            this._zDrag.interactionsEnabled = value
+            this.attachedMesh = null;
         }
 
         public set updateGizmoRotationToMatchAttachedMesh(value:boolean){