Browse Source

Gizmo Node support (#8540)

* Gizmo Node support

* More tests and fixes for bones

* unused import

* feedback addressed

* more lint fix
Cedric Guillemet 5 years ago
parent
commit
9327846952

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

@@ -19,6 +19,7 @@
 
 - Refactored React refs from old string API to React.createRef() API ([belfortk](https://github.com/belfortk))
 - Scale on one axis for `BoundingBoxGizmo` ([cedricguillemet](https://github.com/cedricguillemet))
+- Node support (Transform, Bone) for gizmos ([cedricguillemet](https://github.com/cedricguillemet))
 - Simplified code contributions by fully automating the dev setup with gitpod ([nisarhassan12](https://github.com/nisarhassan12))
 - Add a `CascadedShadowMap.IsSupported` method and log an error instead of throwing an exception when CSM is not supported ([Popov72](https://github.com/Popov72))
 - Added initial code for DeviceInputSystem ([PolygonalSun](https://github.com/PolygonalSun))

+ 12 - 7
src/Gizmos/axisDragGizmo.ts

@@ -3,7 +3,7 @@ import { Nullable } from "../types";
 import { PointerInfo } from "../Events/pointerEvents";
 import { Vector3, Matrix } from "../Maths/math.vector";
 import { TransformNode } from "../Meshes/transformNode";
-import { AbstractMesh } from "../Meshes/abstractMesh";
+import { Node } from "../node";
 import { Mesh } from "../Meshes/mesh";
 import { LinesMesh } from "../Meshes/linesMesh";
 import { CylinderBuilder } from "../Meshes/Builders/cylinderBuilder";
@@ -103,10 +103,10 @@ export class AxisDragGizmo extends Gizmo {
         var localDelta = new Vector3();
         var tmpMatrix = new Matrix();
         this.dragBehavior.onDragObservable.add((event) => {
-            if (this.attachedMesh) {
+            if (this.attachedNode) {
                 // Convert delta to local translation if it has a parent
-                if (this.attachedMesh.parent) {
-                    this.attachedMesh.parent.computeWorldMatrix().invertToRef(tmpMatrix);
+                if (this.attachedNode.parent) {
+                    this.attachedNode.parent.getWorldMatrix().invertToRef(tmpMatrix);
                     tmpMatrix.setTranslationFromFloats(0, 0, 0);
                     Vector3.TransformCoordinatesToRef(event.delta, tmpMatrix, localDelta);
                 } else {
@@ -114,7 +114,8 @@ export class AxisDragGizmo extends Gizmo {
                 }
                 // Snapping logic
                 if (this.snapDistance == 0) {
-                    this.attachedMesh.position.addInPlace(localDelta);
+                    this.attachedNode.getWorldMatrix().addTranslationFromFloats(localDelta.x, localDelta.y, localDelta.z);
+                    this.attachedNode.updateCache();
                 } else {
                     currentSnapDragDistance += event.dragDistance;
                     if (Math.abs(currentSnapDragDistance) > this.snapDistance) {
@@ -122,11 +123,13 @@ export class AxisDragGizmo extends Gizmo {
                         currentSnapDragDistance = currentSnapDragDistance % this.snapDistance;
                         localDelta.normalizeToRef(tmpVector);
                         tmpVector.scaleInPlace(this.snapDistance * dragSteps);
-                        this.attachedMesh.position.addInPlace(tmpVector);
+                        this.attachedNode.getWorldMatrix().addTranslationFromFloats(tmpVector.x, tmpVector.y, tmpVector.z);
+                        this.attachedNode.updateCache();
                         tmpSnapEvent.snapDistance = this.snapDistance * dragSteps;
                         this.onSnapObservable.notifyObservers(tmpSnapEvent);
                     }
                 }
+                this._matrixChanged();
             }
         });
 
@@ -147,7 +150,7 @@ export class AxisDragGizmo extends Gizmo {
         var light = gizmoLayer._getSharedGizmoLight();
         light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes(false));
     }
-    protected _attachedMeshChanged(value: Nullable<AbstractMesh>) {
+    protected _attachedNodeChanged(value: Nullable<Node>) {
         if (this.dragBehavior) {
             this.dragBehavior.enabled = value ? true : false;
         }
@@ -160,10 +163,12 @@ export class AxisDragGizmo extends Gizmo {
         this._isEnabled = value;
         if (!value) {
             this.attachedMesh = null;
+            this.attachedNode = null;
         }
         else {
             if (this._parent) {
                 this.attachedMesh = this._parent.attachedMesh;
+                this.attachedNode = this._parent.attachedNode;
             }
         }
     }

+ 12 - 5
src/Gizmos/axisScaleGizmo.ts

@@ -1,8 +1,9 @@
 import { Observer, Observable } from "../Misc/observable";
 import { Nullable } from "../types";
 import { PointerInfo } from "../Events/pointerEvents";
-import { Vector3 } from "../Maths/math.vector";
+import { Vector3, Matrix } from "../Maths/math.vector";
 import { AbstractMesh } from "../Meshes/abstractMesh";
+import { Node } from "../node";
 import { Mesh } from "../Meshes/mesh";
 import { LinesMesh } from "../Meshes/linesMesh";
 import { BoxBuilder } from "../Meshes/Builders/boxBuilder";
@@ -93,7 +94,7 @@ export class AxisScaleGizmo extends Gizmo {
         var tmpVector = new Vector3();
         var tmpSnapEvent = { snapDistance: 0 };
         this.dragBehavior.onDragObservable.add((event) => {
-            if (this.attachedMesh) {
+            if (this.attachedNode) {
                 // Drag strength is modified by the scale of the gizmo (eg. for small objects like boombox the strength will be increased to match the behavior of larger objects)
                 var dragStrength = this.sensitivity * event.dragDistance * ((this.scaleRatio * 3) / this._rootMesh.scaling.length());
 
@@ -101,7 +102,8 @@ export class AxisScaleGizmo extends Gizmo {
                 var snapped = false;
                 var dragSteps = 0;
                 if (this.uniformScaling) {
-                    this.attachedMesh.scaling.normalizeToRef(tmpVector);
+                    this.attachedNode.getWorldMatrix().decompose(tmpVector);
+                    tmpVector.normalize();
                     if (tmpVector.y < 0) {
                         tmpVector.scaleInPlace(-1);
                     }
@@ -125,12 +127,15 @@ export class AxisScaleGizmo extends Gizmo {
                     }
                 }
 
-                this.attachedMesh.scaling.addInPlace(tmpVector);
+                const scalingMatrix = new Matrix();
+                Matrix.ScalingToRef(1 + tmpVector.x, 1 + tmpVector.y, 1 + tmpVector.z, scalingMatrix);
+                this.attachedNode.getWorldMatrix().copyFrom(scalingMatrix.multiply(this.attachedNode.getWorldMatrix()));
 
                 if (snapped) {
                     tmpSnapEvent.snapDistance = this.snapDistance * dragSteps;
                     this.onSnapObservable.notifyObservers(tmpSnapEvent);
                 }
+                this._matrixChanged();
             }
         });
 
@@ -152,7 +157,7 @@ export class AxisScaleGizmo extends Gizmo {
         light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes());
     }
 
-    protected _attachedMeshChanged(value: Nullable<AbstractMesh>) {
+    protected _attachedNodeChanged(value: Nullable<Node>) {
         if (this.dragBehavior) {
             this.dragBehavior.enabled = value ? true : false;
         }
@@ -165,10 +170,12 @@ export class AxisScaleGizmo extends Gizmo {
         this._isEnabled = value;
         if (!value) {
             this.attachedMesh = null;
+            this.attachedNode = null;
         }
         else {
             if (this._parent) {
                 this.attachedMesh = this._parent.attachedMesh;
+                this.attachedNode = this._parent.attachedNode;
             }
         }
     }

+ 3 - 3
src/Gizmos/boundingBoxGizmo.ts

@@ -82,12 +82,12 @@ export class BoundingBoxGizmo extends Gizmo {
      */
     public onRotationSphereDragEndObservable = new Observable<{}>();
     /**
-     * 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)
+     * Relative bounding box pivot used when scaling the attached node. 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)
      */
     public scalePivot: Nullable<Vector3> = null;
 
     /**
-     * Mesh used as a pivot to rotate the attached mesh
+     * Mesh used as a pivot to rotate the attached node
      */
     private _anchorMesh: AbstractMesh;
 
@@ -366,7 +366,7 @@ export class BoundingBoxGizmo extends Gizmo {
         this.updateBoundingBox();
     }
 
-    protected _attachedMeshChanged(value: Nullable<AbstractMesh>) {
+    protected _attachedNodeChanged(value: Nullable<AbstractMesh>) {
         if (value) {
             // Reset anchor mesh to match attached mesh's scale
             // This is needed to avoid invalid box/sphere position on first drag

+ 74 - 8
src/Gizmos/gizmo.ts

@@ -2,10 +2,13 @@ import { Observer } from "../Misc/observable";
 import { Nullable } from "../types";
 import { WebVRFreeCamera } from "../Cameras/VR/webVRCamera";
 import { Scene, IDisposable } from "../scene";
-import { Quaternion, Vector3 } from "../Maths/math.vector";
+import { Quaternion, Vector3, Matrix } from "../Maths/math.vector";
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { Mesh } from "../Meshes/mesh";
+import { Node } from "../node";
+import { Bone } from "../Bones/bone";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
+import { TransformNode } from '../Meshes/transformNode';
 /**
  * Renders gizmos on top of an existing scene which provide controls for position, rotation, etc.
  */
@@ -15,6 +18,7 @@ export class Gizmo implements IDisposable {
      */
     public _rootMesh: Mesh;
     private _attachedMesh: Nullable<AbstractMesh> = null;
+    private _attachedNode: Nullable<Node> = null;
     /**
      * Ratio for the scale of the gizmo (Default: 1)
      */
@@ -32,8 +36,24 @@ export class Gizmo implements IDisposable {
     }
     public set attachedMesh(value) {
         this._attachedMesh = value;
+        if (value) {
+            this._attachedNode = value;
+        }
+        this._rootMesh.setEnabled(value ? true : false);
+        this._attachedNodeChanged(value);
+    }
+    /**
+     * Node that the gizmo will be attached to. (eg. on a drag gizmo the mesh, bone or NodeTransform that will be dragged)
+     * * When set, interactions will be enabled
+     */
+    public get attachedNode() {
+        return this._attachedNode;
+    }
+    public set attachedNode(value) {
+        this._attachedNode = value;
+        this._attachedMesh = null;
         this._rootMesh.setEnabled(value ? true : false);
-        this._attachedMeshChanged(value);
+        this._attachedNodeChanged(value);
     }
 
     /**
@@ -64,7 +84,7 @@ export class Gizmo implements IDisposable {
      */
     public updateScale = true;
     protected _interactionsEnabled = true;
-    protected _attachedMeshChanged(value: Nullable<AbstractMesh>) {
+    protected _attachedNodeChanged(value: Nullable<Node>) {
     }
 
     private _beforeRenderObserver: Nullable<Observer<Scene>>;
@@ -90,17 +110,22 @@ export class Gizmo implements IDisposable {
      * Updates the gizmo to match the attached mesh's position/rotation
      */
     protected _update() {
-        if (this.attachedMesh) {
-            const effectiveMesh = this.attachedMesh._effectiveMesh || this.attachedMesh;
+        if (this.attachedNode) {
+            var effectiveNode = this.attachedNode;
+            if (this.attachedMesh) {
+                effectiveNode = this.attachedMesh._effectiveMesh || this.attachedNode;
+            }
 
             // Position
             if (this.updateGizmoPositionToMatchAttachedMesh) {
-                this._rootMesh.position.copyFrom(effectiveMesh.absolutePosition);
+                const row = effectiveNode.getWorldMatrix().getRow(3);
+                const position = row ? row.toVector3() : new Vector3(0, 0, 0);
+                this._rootMesh.position.copyFrom(position);
             }
 
             // Rotation
             if (this.updateGizmoRotationToMatchAttachedMesh) {
-                effectiveMesh.getWorldMatrix().decompose(undefined, this._rootMesh.rotationQuaternion!);
+                effectiveNode.getWorldMatrix().decompose(undefined, this._rootMesh.rotationQuaternion!);
             }
             else {
                 this._rootMesh.rotationQuaternion!.set(0, 0, 0, 1);
@@ -118,7 +143,7 @@ export class Gizmo implements IDisposable {
                 this._rootMesh.scaling.set(dist, dist, dist);
 
                 // Account for handedness, similar to Matrix.decompose
-                if (effectiveMesh._getWorldMatrixDeterminant() < 0) {
+                if (effectiveNode._getWorldMatrixDeterminant() < 0) {
                     this._rootMesh.scaling.y *= -1;
                 }
             } else {
@@ -128,6 +153,47 @@ export class Gizmo implements IDisposable {
     }
 
     /**
+     * computes the rotation/scaling/position of the transform once the Node world matrix has changed.
+     * @param value Node, TransformNode or mesh
+     */
+    protected _matrixChanged()
+    {
+        if (!this._attachedNode) {
+            return;
+        }
+        if (this._attachedNode.getClassName() === "Mesh" || this._attachedNode.getClassName() === "TransformNode") {
+            var transform = this._attachedNode as TransformNode;
+            var transformQuaternion = new Quaternion(0, 0, 0, 1);
+            if (transform.parent) {
+                var parentInv = new Matrix();
+                var localMat = new Matrix();
+                transform.parent.getWorldMatrix().invertToRef(parentInv);
+                this._attachedNode._worldMatrix.multiplyToRef(parentInv, localMat);
+                localMat.decompose(transform.scaling, transformQuaternion, transform.position);
+            } else {
+                this._attachedNode._worldMatrix.decompose(transform.scaling, transformQuaternion, transform.position);
+            }
+            transform.rotation = transformQuaternion.toEulerAngles();
+            if (transform.rotationQuaternion) {
+                transform.rotationQuaternion = transformQuaternion;
+            }
+        } else if (this._attachedNode.getClassName() === "Bone") {
+            var bone = this._attachedNode as Bone;
+            const parent = bone.getParent();
+
+            if (parent) {
+                var invParent = new Matrix();
+                var boneLocalMatrix = new Matrix();
+                parent.getWorldMatrix().invertToRef(invParent);
+                bone.getWorldMatrix().multiplyToRef(invParent, boneLocalMatrix);
+                var lmat = bone.getLocalMatrix();
+                lmat.copyFrom(boneLocalMatrix);
+                bone.markAsDirty();
+            }
+        }
+    }
+
+    /**
      * Disposes of the gizmo
      */
     public dispose() {

+ 10 - 9
src/Gizmos/planeDragGizmo.ts

@@ -4,7 +4,7 @@ import { PointerInfo } from "../Events/pointerEvents";
 import { Vector3, Matrix } from "../Maths/math.vector";
 import { Color3 } from '../Maths/math.color';
 import { TransformNode } from "../Meshes/transformNode";
-import { AbstractMesh } from "../Meshes/abstractMesh";
+import { Node } from "../node";
 import { Mesh } from "../Meshes/mesh";
 import { PlaneBuilder } from "../Meshes/Builders/planeBuilder";
 import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior";
@@ -98,10 +98,10 @@ export class PlaneDragGizmo extends Gizmo {
         var localDelta = new Vector3();
         var tmpMatrix = new Matrix();
         this.dragBehavior.onDragObservable.add((event) => {
-            if (this.attachedMesh) {
+            if (this.attachedNode) {
                 // Convert delta to local translation if it has a parent
-                if (this.attachedMesh.parent) {
-                    this.attachedMesh.parent.computeWorldMatrix().invertToRef(tmpMatrix);
+                if (this.attachedNode.parent) {
+                    this.attachedNode.parent.computeWorldMatrix().invertToRef(tmpMatrix);
                     tmpMatrix.setTranslationFromFloats(0, 0, 0);
                     Vector3.TransformCoordinatesToRef(event.delta, tmpMatrix, localDelta);
                 } else {
@@ -109,7 +109,7 @@ export class PlaneDragGizmo extends Gizmo {
                 }
                 // Snapping logic
                 if (this.snapDistance == 0) {
-                    this.attachedMesh.position.addInPlace(localDelta);
+                    this.attachedNode.getWorldMatrix().addTranslationFromFloats(localDelta.x, localDelta.y, localDelta.z);
                 } else {
                     currentSnapDragDistance += event.dragDistance;
                     if (Math.abs(currentSnapDragDistance) > this.snapDistance) {
@@ -117,11 +117,12 @@ export class PlaneDragGizmo extends Gizmo {
                         currentSnapDragDistance = currentSnapDragDistance % this.snapDistance;
                         localDelta.normalizeToRef(tmpVector);
                         tmpVector.scaleInPlace(this.snapDistance * dragSteps);
-                        this.attachedMesh.position.addInPlace(tmpVector);
+                        this.attachedNode.getWorldMatrix().addTranslationFromFloats(tmpVector.x, tmpVector.y, tmpVector.z);
                         tmpSnapEvent.snapDistance = this.snapDistance * dragSteps;
                         this.onSnapObservable.notifyObservers(tmpSnapEvent);
                     }
                 }
+                this._matrixChanged();
             }
         });
 
@@ -139,7 +140,7 @@ export class PlaneDragGizmo extends Gizmo {
         var light = gizmoLayer._getSharedGizmoLight();
         light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes(false));
     }
-    protected _attachedMeshChanged(value: Nullable<AbstractMesh>) {
+    protected _attachedNodeChanged(value: Nullable<Node>) {
         if (this.dragBehavior) {
             this.dragBehavior.enabled = value ? true : false;
         }
@@ -151,11 +152,11 @@ export class PlaneDragGizmo extends Gizmo {
     public set isEnabled(value: boolean) {
         this._isEnabled = value;
         if (!value) {
-            this.attachedMesh = null;
+            this.attachedNode = null;
         }
         else {
             if (this._parent) {
-                this.attachedMesh = this._parent.attachedMesh;
+                this.attachedNode = this._parent.attachedNode;
             }
         }
     }

+ 20 - 37
src/Gizmos/planeRotationGizmo.ts

@@ -5,6 +5,7 @@ import { Quaternion, Matrix, Vector3 } from "../Maths/math.vector";
 import { Color3 } from '../Maths/math.color';
 import { AbstractMesh } from "../Meshes/abstractMesh";
 import { Mesh } from "../Meshes/mesh";
+import { Node } from "../node";
 import { LinesMesh } from "../Meshes/linesMesh";
 import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior";
 import { Gizmo } from "./gizmo";
@@ -83,7 +84,7 @@ export class PlaneRotationGizmo extends Gizmo {
         var lastDragPosition = new Vector3();
 
         this.dragBehavior.onDragStartObservable.add((e) => {
-            if (this.attachedMesh) {
+            if (this.attachedNode) {
                 lastDragPosition.copyFrom(e.dragPlanePoint);
             }
         });
@@ -95,35 +96,29 @@ export class PlaneRotationGizmo extends Gizmo {
         var tmpSnapEvent = { snapDistance: 0 };
         var currentSnapDragDistance = 0;
         var tmpMatrix = new Matrix();
-        var tmpVector = new Vector3();
         var amountToRotate = new Quaternion();
         this.dragBehavior.onDragObservable.add((event) => {
-            if (this.attachedMesh) {
-                if (!this.attachedMesh.rotationQuaternion || useEulerRotation) {
-                    this.attachedMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.attachedMesh.rotation.y, this.attachedMesh.rotation.x, this.attachedMesh.rotation.z);
-                }
-
-                // Remove parent priort to rotating
-                var attachedMeshParent = this.attachedMesh.parent;
-                if (attachedMeshParent) {
-                    this.attachedMesh.setParent(null);
-                }
-
+            if (this.attachedNode) {
                 // Calc angle over full 360 degree (https://stackoverflow.com/questions/43493711/the-angle-between-two-3d-vectors-with-a-result-range-0-360)
-                var newVector = event.dragPlanePoint.subtract(this.attachedMesh.absolutePosition).normalize();
-                var originalVector = lastDragPosition.subtract(this.attachedMesh.absolutePosition).normalize();
+                var nodeScale = new Vector3(1, 1, 1);
+                var nodeQuaternion = new Quaternion(0, 0, 0, 1);
+                var nodeTranslation = new Vector3(0, 0, 0);
+                this.attachedNode.getWorldMatrix().decompose(nodeScale, nodeQuaternion, nodeTranslation);
+
+                var newVector = event.dragPlanePoint.subtract(nodeTranslation).normalize();
+                var originalVector = lastDragPosition.subtract(nodeTranslation).normalize();
                 var cross = Vector3.Cross(newVector, originalVector);
                 var dot = Vector3.Dot(newVector, originalVector);
                 var angle = Math.atan2(cross.length(), dot);
                 planeNormalTowardsCamera.copyFrom(planeNormal);
                 localPlaneNormalTowardsCamera.copyFrom(planeNormal);
                 if (this.updateGizmoRotationToMatchAttachedMesh) {
-                    this.attachedMesh.rotationQuaternion.toRotationMatrix(rotationMatrix);
+                    nodeQuaternion.toRotationMatrix(rotationMatrix);
                     localPlaneNormalTowardsCamera = Vector3.TransformCoordinates(planeNormalTowardsCamera, rotationMatrix);
                 }
                 // Flip up vector depending on which side the camera is on
                 if (gizmoLayer.utilityLayerScene.activeCamera) {
-                    var camVec = gizmoLayer.utilityLayerScene.activeCamera.position.subtract(this.attachedMesh.position);
+                    var camVec = gizmoLayer.utilityLayerScene.activeCamera.position.subtract(nodeTranslation);
                     if (Vector3.Dot(camVec, localPlaneNormalTowardsCamera) > 0) {
                         planeNormalTowardsCamera.scaleInPlace(-1);
                         localPlaneNormalTowardsCamera.scaleInPlace(-1);
@@ -149,37 +144,27 @@ export class PlaneRotationGizmo extends Gizmo {
                     }
                 }
 
-                // If the mesh has a parent, convert needed world rotation to local rotation
-                tmpMatrix.reset();
-                if (this.attachedMesh.parent) {
-                    this.attachedMesh.parent.computeWorldMatrix().invertToRef(tmpMatrix);
-                    tmpMatrix.getRotationMatrixToRef(tmpMatrix);
-                    Vector3.TransformCoordinatesToRef(planeNormalTowardsCamera, tmpMatrix, planeNormalTowardsCamera);
-                }
-
                 // Convert angle and axis to quaternion (http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm)
                 var quaternionCoefficient = Math.sin(angle / 2);
                 amountToRotate.set(planeNormalTowardsCamera.x * quaternionCoefficient, planeNormalTowardsCamera.y * quaternionCoefficient, planeNormalTowardsCamera.z * quaternionCoefficient, Math.cos(angle / 2));
 
                 // If the meshes local scale is inverted (eg. loaded gltf file parent with z scale of -1) the rotation needs to be inverted on the y axis
                 if (tmpMatrix.determinant() > 0) {
+                    var tmpVector = new Vector3();
                     amountToRotate.toEulerAnglesToRef(tmpVector);
                     Quaternion.RotationYawPitchRollToRef(tmpVector.y, -tmpVector.x, -tmpVector.z, amountToRotate);
                 }
 
                 if (this.updateGizmoRotationToMatchAttachedMesh) {
                     // Rotate selected mesh quaternion over fixed axis
-                    this.attachedMesh.rotationQuaternion.multiplyToRef(amountToRotate, this.attachedMesh.rotationQuaternion);
+                    nodeQuaternion.multiplyToRef(amountToRotate, nodeQuaternion);
                 } else {
                     // Rotate selected mesh quaternion over rotated axis
-                    amountToRotate.multiplyToRef(this.attachedMesh.rotationQuaternion, this.attachedMesh.rotationQuaternion);
+                    amountToRotate.multiplyToRef(nodeQuaternion, nodeQuaternion);
                 }
 
-                if (useEulerRotation) {
-                    this.attachedMesh.rotationQuaternion.toEulerAnglesToRef(tmpVector);
-                    this.attachedMesh.rotationQuaternion = null;
-                    this.attachedMesh.rotation.copyFrom(tmpVector);
-                }
+                // recompose matrix
+                this.attachedNode.getWorldMatrix().copyFrom(Matrix.Compose(nodeScale, nodeQuaternion, nodeTranslation));
 
                 lastDragPosition.copyFrom(event.dragPlanePoint);
                 if (snapped) {
@@ -187,10 +172,7 @@ export class PlaneRotationGizmo extends Gizmo {
                     this.onSnapObservable.notifyObservers(tmpSnapEvent);
                 }
 
-                // Restore parent
-                if (attachedMeshParent) {
-                    this.attachedMesh.setParent(attachedMeshParent);
-                }
+                this._matrixChanged();
             }
         });
 
@@ -212,11 +194,12 @@ export class PlaneRotationGizmo extends Gizmo {
         light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes(false));
     }
 
-    protected _attachedMeshChanged(value: Nullable<AbstractMesh>) {
+    protected _attachedNodeChanged(value: Nullable<Node>) {
         if (this.dragBehavior) {
             this.dragBehavior.enabled = value ? true : false;
         }
     }
+
     /**
          * If the gizmo is enabled
          */

+ 24 - 1
src/Gizmos/positionGizmo.ts

@@ -4,6 +4,7 @@ import { Nullable } from "../types";
 import { Vector3 } from "../Maths/math.vector";
 import { Color3 } from '../Maths/math.color';
 import { AbstractMesh } from "../Meshes/abstractMesh";
+import { Node } from "../node";
 import { Mesh } from "../Meshes/mesh";
 import { Gizmo } from "./gizmo";
 import { AxisDragGizmo } from "./axisDragGizmo";
@@ -42,6 +43,7 @@ export class PositionGizmo extends Gizmo {
      * private variables
      */
     private _meshAttached: Nullable<AbstractMesh> = null;
+    private _nodeAttached: Nullable<Node> = null;
     private _updateGizmoRotationToMatchAttachedMesh: boolean;
     private _snapDistance: number;
     private _scaleRatio: number;
@@ -61,6 +63,7 @@ export class PositionGizmo extends Gizmo {
     }
     public set attachedMesh(mesh: Nullable<AbstractMesh>) {
         this._meshAttached = mesh;
+        this._nodeAttached = mesh;
         [this.xGizmo, this.yGizmo, this.zGizmo, this.xPlaneGizmo, this.yPlaneGizmo, this.zPlaneGizmo].forEach((gizmo) => {
             if (gizmo.isEnabled) {
                 gizmo.attachedMesh = mesh;
@@ -69,7 +72,22 @@ export class PositionGizmo extends Gizmo {
                 gizmo.attachedMesh = null;
             }
         });
+    }
 
+    public get attachedNode() {
+        return this._nodeAttached;
+    }
+    public set attachedNode(node: Nullable<Node>) {
+        this._meshAttached = null;
+        this._nodeAttached = null;
+        [this.xGizmo, this.yGizmo, this.zGizmo, this.xPlaneGizmo, this.yPlaneGizmo, this.zPlaneGizmo].forEach((gizmo) => {
+            if (gizmo.isEnabled) {
+                gizmo.attachedNode = node;
+            }
+            else {
+                gizmo.attachedNode = null;
+            }
+        });
     }
     /**
      * Creates a PositionGizmo
@@ -107,7 +125,12 @@ export class PositionGizmo extends Gizmo {
             if (gizmo) {
                 gizmo.isEnabled = value;
                 if (value) {
-                    gizmo.attachedMesh = this.attachedMesh;
+                    if (gizmo.attachedMesh) {
+                        gizmo.attachedMesh = this.attachedMesh;
+                    } else {
+                        gizmo.attachedNode = this.attachedNode;
+                    }
+
                 }
             }
         }, this);

+ 20 - 1
src/Gizmos/rotationGizmo.ts

@@ -8,6 +8,7 @@ import { Mesh } from "../Meshes/mesh";
 import { Gizmo } from "./gizmo";
 import { PlaneRotationGizmo } from "./planeRotationGizmo";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
+import { Node } from "../node";
 /**
  * Gizmo that enables rotating a mesh along 3 axis
  */
@@ -31,13 +32,14 @@ export class RotationGizmo extends Gizmo {
     public onDragEndObservable = new Observable();
 
     private _meshAttached: Nullable<AbstractMesh>;
+    private _nodeAttached: Nullable<Node>;
 
     public get attachedMesh() {
         return this._meshAttached;
     }
     public set attachedMesh(mesh: Nullable<AbstractMesh>) {
         this._meshAttached = mesh;
-
+        this._nodeAttached = mesh;
         [this.xGizmo, this.yGizmo, this.zGizmo].forEach((gizmo) => {
             if (gizmo.isEnabled) {
                 gizmo.attachedMesh = mesh;
@@ -47,6 +49,22 @@ export class RotationGizmo extends Gizmo {
             }
         });
     }
+
+    public get attachedNode() {
+        return this._nodeAttached;
+    }
+    public set attachedNode(node: Nullable<Node>) {
+        this._meshAttached = null;
+        this._nodeAttached = node;
+        [this.xGizmo, this.yGizmo, this.zGizmo].forEach((gizmo) => {
+            if (gizmo.isEnabled) {
+                gizmo.attachedNode = node;
+            }
+            else {
+                gizmo.attachedNode = null;
+            }
+        });
+    }
     /**
      * Creates a RotationGizmo
      * @param gizmoLayer The utility layer the gizmo will be added to
@@ -70,6 +88,7 @@ export class RotationGizmo extends Gizmo {
         });
 
         this.attachedMesh = null;
+        this.attachedNode = null;
     }
 
     public set updateGizmoRotationToMatchAttachedMesh(value: boolean) {

+ 21 - 0
src/Gizmos/scaleGizmo.ts

@@ -9,6 +9,7 @@ import { Gizmo } from "./gizmo";
 import { AxisScaleGizmo } from "./axisScaleGizmo";
 import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer";
 import { Mesh } from "../Meshes/mesh";
+import { Node } from "../node";
 /**
  * Gizmo that enables scaling a mesh along 3 axis
  */
@@ -32,6 +33,7 @@ export class ScaleGizmo extends Gizmo {
     public uniformScaleGizmo: AxisScaleGizmo;
 
     private _meshAttached: Nullable<AbstractMesh> = null;
+    private _nodeAttached: Nullable<Node> = null;
     private _updateGizmoRotationToMatchAttachedMesh: boolean;
     private _snapDistance: number;
     private _scaleRatio: number;
@@ -49,6 +51,7 @@ export class ScaleGizmo extends Gizmo {
     }
     public set attachedMesh(mesh: Nullable<AbstractMesh>) {
         this._meshAttached = mesh;
+        this._nodeAttached = mesh;
         [this.xGizmo, this.yGizmo, this.zGizmo, this.uniformScaleGizmo].forEach((gizmo) => {
             if (gizmo.isEnabled) {
                 gizmo.attachedMesh = mesh;
@@ -58,6 +61,23 @@ export class ScaleGizmo extends Gizmo {
             }
         });
     }
+
+    public get attachedNode() {
+        return this._nodeAttached;
+    }
+    public set attachedNode(node: Nullable<Node>) {
+        this._meshAttached = null;
+        this._nodeAttached = node;
+        [this.xGizmo, this.yGizmo, this.zGizmo, this.uniformScaleGizmo].forEach((gizmo) => {
+            if (gizmo.isEnabled) {
+                gizmo.attachedNode = node;
+            }
+            else {
+                gizmo.attachedNode = null;
+            }
+        });
+    }
+
     /**
      * Creates a ScaleGizmo
      * @param gizmoLayer The utility layer the gizmo will be added to
@@ -93,6 +113,7 @@ export class ScaleGizmo extends Gizmo {
         });
 
         this.attachedMesh = null;
+        this.attachedNode = null;
     }
 
     public set updateGizmoRotationToMatchAttachedMesh(value: boolean) {