Explorar o código

Merge remote-tracking branch 'BabylonJS/master' into viewer-modules

Raanan Weber %!s(int64=7) %!d(string=hai) anos
pai
achega
9e48a11b8e
Modificáronse 47 ficheiros con 29582 adicións e 26524 borrados
  1. 13179 13105
      Playground/babylon.d.txt
  2. BIN=BIN
      Playground/textures/mixMap_2.png
  3. 14 1
      Tools/Gulp/config.json
  4. 5 1
      Viewer/src/configuration/configuration.ts
  5. 29 13
      Viewer/src/labs/viewerLabs.ts
  6. 1 1
      Viewer/src/viewer/defaultViewer.ts
  7. 31 6
      Viewer/src/viewer/sceneManager.ts
  8. 12997 12922
      dist/preview release/babylon.d.ts
  9. 43 43
      dist/preview release/babylon.js
  10. 231 64
      dist/preview release/babylon.max.js
  11. 231 64
      dist/preview release/babylon.no-module.max.js
  12. 50 50
      dist/preview release/babylon.worker.js
  13. 233 66
      dist/preview release/es6.js
  14. 2 3
      dist/preview release/gui/babylon.gui.d.ts
  15. 9 5
      dist/preview release/gui/babylon.gui.js
  16. 1 1
      dist/preview release/gui/babylon.gui.min.js
  17. 2 3
      dist/preview release/gui/babylon.gui.module.d.ts
  18. 56 0
      dist/preview release/materialsLibrary/babylon.mixMaterial.d.ts
  19. 469 0
      dist/preview release/materialsLibrary/babylon.mixMaterial.js
  20. 1 0
      dist/preview release/materialsLibrary/babylon.mixMaterial.min.js
  21. 57 0
      dist/preview release/materialsLibrary/babylonjs.materials.d.ts
  22. 456 0
      dist/preview release/materialsLibrary/babylonjs.materials.js
  23. 3 2
      dist/preview release/materialsLibrary/babylonjs.materials.min.js
  24. 57 0
      dist/preview release/materialsLibrary/babylonjs.materials.module.d.ts
  25. 18 2
      dist/preview release/serializers/babylon.glTF2Serializer.js
  26. 2 2
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  27. 18 2
      dist/preview release/serializers/babylonjs.serializers.js
  28. 2 2
      dist/preview release/serializers/babylonjs.serializers.min.js
  29. 6 3
      dist/preview release/viewer/babylon.viewer.d.ts
  30. 22 22
      dist/preview release/viewer/babylon.viewer.js
  31. 283 80
      dist/preview release/viewer/babylon.viewer.max.js
  32. 6 3
      dist/preview release/viewer/babylon.viewer.module.d.ts
  33. 3 0
      dist/preview release/what's new.md
  34. 3 3
      gui/src/3D/gui3DManager.ts
  35. 7 1
      materialsLibrary/index.html
  36. 517 0
      materialsLibrary/src/mix/babylon.mixMaterial.ts
  37. 172 0
      materialsLibrary/src/mix/mix.fragment.fx
  38. 102 0
      materialsLibrary/src/mix/mix.vertex.fx
  39. 29 0
      materialsLibrary/test/addMix.js
  40. 18 2
      serializers/src/glTF/2.0/babylon.glTFExporter.ts
  41. 40 23
      src/Behaviors/Mesh/babylon.pointerDragBehavior.ts
  42. 4 0
      src/Events/babylon.pointerEvents.ts
  43. 55 0
      src/Gizmos/babylon.axisDragGizmo.ts
  44. 41 0
      src/Gizmos/babylon.gizmo.ts
  45. 35 0
      src/Gizmos/babylon.positionGizmo.ts
  46. 7 0
      src/Materials/babylon.material.ts
  47. 35 29
      src/babylon.scene.ts

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 13179 - 13105
Playground/babylon.d.txt


BIN=BIN
Playground/textures/mixMap_2.png


+ 14 - 1
Tools/Gulp/config.json

@@ -1021,7 +1021,10 @@
                 "../../src/Debug/babylon.debugLayer.js",
                 "../../src/Debug/babylon.physicsViewer.js",
                 "../../src/Rendering/babylon.boundingBoxRenderer.js",
-                "../../src/Rendering/babylon.utilityLayerRenderer.js"
+                "../../src/Rendering/babylon.utilityLayerRenderer.js",
+                "../../src/Gizmos/babylon.gizmo.js",
+                "../../src/Gizmos/babylon.axisDragGizmo.js",
+                "../../src/Gizmos/babylon.positionGizmo.js"
             ],
             "dependUpon": [
                 "shaderMaterial",
@@ -1319,6 +1322,16 @@
             },
             {
                 "files": [
+                    "../../materialsLibrary/src/mix/babylon.mixMaterial.ts"
+                ],
+                "shaderFiles": [
+                    "../../materialsLibrary/src/mix/mix.vertex.fx",
+                    "../../materialsLibrary/src/mix/mix.fragment.fx"
+                ],
+                "output": "babylon.mixMaterial.js"
+            },
+            {
+                "files": [
                     "../../materialsLibrary/src/triPlanar/babylon.triPlanarMaterial.ts"
                 ],
                 "shaderFiles": [

+ 5 - 1
Viewer/src/configuration/configuration.ts

@@ -43,7 +43,10 @@ export interface ViewerConfiguration {
     skybox?: boolean | ISkyboxConfiguration;
 
     ground?: boolean | IGroundConfiguration;
-    lights?: { [name: string]: boolean | ILightConfiguration },
+    lights?: {
+        //globalRotation: number,
+        [name: string]: number | boolean | ILightConfiguration
+    },
     // engine configuration. optional!
     engine?: {
         renderInBackground?: boolean;
@@ -107,6 +110,7 @@ export interface ViewerConfiguration {
             tintLevel: number;
         }
         defaultRenderingPipelines?: boolean | IDefaultRenderingPipelineConfiguration;
+        globalLightRotation?: number;
     }
 }
 

+ 29 - 13
Viewer/src/labs/viewerLabs.ts

@@ -1,7 +1,7 @@
 import { PBREnvironment, EnvironmentDeserializer } from "./environmentSerializer";
 import { SceneManager } from '../viewer/sceneManager';
 
-import { Tools, Quaternion } from 'babylonjs';
+import { Tools, Quaternion, ShadowLight, Vector3, Axis, Matrix, SphericalPolynomial, Tmp } from 'babylonjs';
 import { ViewerConfiguration } from "../configuration/configuration";
 import { TextureUtils } from "./texture";
 
@@ -18,15 +18,15 @@ export class ViewerLabs {
     public environment: PBREnvironment = {
         //irradiance
         irradiancePolynomialCoefficients: {
-            x: new BABYLON.Vector3(0, 0, 0),
-            y: new BABYLON.Vector3(0, 0, 0),
-            z: new BABYLON.Vector3(0, 0, 0),
-            xx: new BABYLON.Vector3(0, 0, 0),
-            yy: new BABYLON.Vector3(0, 0, 0),
-            zz: new BABYLON.Vector3(0, 0, 0),
-            yz: new BABYLON.Vector3(0, 0, 0),
-            zx: new BABYLON.Vector3(0, 0, 0),
-            xy: new BABYLON.Vector3(0, 0, 0)
+            x: new Vector3(0, 0, 0),
+            y: new Vector3(0, 0, 0),
+            z: new Vector3(0, 0, 0),
+            xx: new Vector3(0, 0, 0),
+            yy: new Vector3(0, 0, 0),
+            zz: new Vector3(0, 0, 0),
+            yz: new Vector3(0, 0, 0),
+            zx: new Vector3(0, 0, 0),
+            xy: new Vector3(0, 0, 0)
         },
 
         textureIntensityScale: 1.0
@@ -93,7 +93,7 @@ export class ViewerLabs {
         if (!this.environment) return;
 
         //set orientation
-        let rotatquatRotationionY = Quaternion.RotationAxis(BABYLON.Axis.Y, rotationY || 0);
+        let rotatquatRotationionY = Quaternion.RotationAxis(Axis.Y, rotationY || 0);
 
         // Add env texture to the scene.
         if (this.environment.specularTexture) {
@@ -106,7 +106,7 @@ export class ViewerLabs {
                 this._sceneManager.scene.environmentTexture.invertZ = true;
                 this._sceneManager.scene.environmentTexture.lodLevelInAlpha = true;
 
-                var poly = this._sceneManager.scene.environmentTexture.sphericalPolynomial || new BABYLON.SphericalPolynomial();
+                var poly = this._sceneManager.scene.environmentTexture.sphericalPolynomial || new SphericalPolynomial();
                 poly.x = this.environment.irradiancePolynomialCoefficients.x;
                 poly.y = this.environment.irradiancePolynomialCoefficients.y;
                 poly.z = this.environment.irradiancePolynomialCoefficients.z;
@@ -119,7 +119,7 @@ export class ViewerLabs {
                 this._sceneManager.scene.environmentTexture.sphericalPolynomial = poly;
 
                 //set orientation
-                BABYLON.Matrix.FromQuaternionToRef(rotatquatRotationionY, this._sceneManager.scene.environmentTexture.getReflectionTextureMatrix());
+                Matrix.FromQuaternionToRef(rotatquatRotationionY, this._sceneManager.scene.environmentTexture.getReflectionTextureMatrix());
             }
         }
     }
@@ -143,4 +143,20 @@ export class ViewerLabs {
         return returnUrl;
     }
 
+    public rotateShadowLight(shadowLight: ShadowLight, amount: number, point = Vector3.Zero(), axis = Axis.Y, target = Vector3.Zero()) {
+        axis.normalize();
+        point.subtractToRef(shadowLight.position, Tmp.Vector3[0]);
+        Matrix.TranslationToRef(Tmp.Vector3[0].x, Tmp.Vector3[0].y, Tmp.Vector3[0].z, Tmp.Matrix[0]);
+        Tmp.Matrix[0].invertToRef(Tmp.Matrix[2]);
+        Matrix.RotationAxisToRef(axis, amount, Tmp.Matrix[1]);
+        Tmp.Matrix[2].multiplyToRef(Tmp.Matrix[1], Tmp.Matrix[2]);
+        Tmp.Matrix[2].multiplyToRef(Tmp.Matrix[0], Tmp.Matrix[2]);
+
+        Tmp.Matrix[2].decompose(Tmp.Vector3[0], Tmp.Quaternion[0], Tmp.Vector3[1]);
+
+        shadowLight.position.addInPlace(Tmp.Vector3[1]);
+
+        shadowLight.setDirectionToTarget(target);
+    }
+
 }

+ 1 - 1
Viewer/src/viewer/defaultViewer.ts

@@ -481,7 +481,7 @@ export class DefaultViewer extends AbstractViewer {
      * @param lightsConfiguration the light configuration to use
      * @param model the model that will be used to configure the lights (if the lights are model-dependant)
      */
-    private _configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}, model?: ViewerModel) {
+    private _configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean | number } = {}, model?: ViewerModel) {
         // labs feature - flashlight
         if (this._configuration.lab && this._configuration.lab.flashlight) {
             let pointerPosition = Vector3.Zero();

+ 31 - 6
Viewer/src/viewer/sceneManager.ts

@@ -41,7 +41,7 @@ export class SceneManager {
     /**
      * Will notify after the lights were configured. Can be used to further configure lights
      */
-    onLightsConfiguredObservable: Observable<IPostConfigurationCallback<Array<Light>, { [name: string]: ILightConfiguration | boolean }>>;
+    onLightsConfiguredObservable: Observable<IPostConfigurationCallback<Array<Light>, { [name: string]: ILightConfiguration | boolean | number }>>;
     /**
      * Will notify after the model(s) were configured. Can be used to further configure models
      */
@@ -93,6 +93,8 @@ export class SceneManager {
     private _reflectionColor = Color3.White();
     private readonly _white = Color3.White();
 
+    private _forceShadowUpdate: boolean = false;
+
     /**
      * The labs variable consists of objects that will have their API change.
      * Please be careful when using labs in production.
@@ -137,9 +139,10 @@ export class SceneManager {
                 }
             }
             scene.registerBeforeRender(() => {
-                if (scene.animatables && scene.animatables.length > 0) {
+                if (this._forceShadowUpdate || (scene.animatables && scene.animatables.length > 0)) {
                     // make sure all models are loaded
                     updateShadows();
+                    this._forceShadowUpdate = false;
                 } else if (!(this.models.every((model) => {
                     if (!model.shadowsRenderedAfterLoad) {
                         model.shadowsRenderedAfterLoad = true;
@@ -431,6 +434,15 @@ export class SceneManager {
                 let mainColor = new Color3().copyFrom(newConfiguration.lab.environmentMainColor as Color3);
                 this.environmentHelper.setMainColor(mainColor);
             }
+
+            if (newConfiguration.lab.globalLightRotation !== undefined) {
+                // rotate all lights that are shadow lights
+                this.scene.lights.filter(light => light instanceof ShadowLight).forEach(light => {
+                    // casting and '!' are safe, due to the constraints tested before
+                    this.labs.rotateShadowLight(<ShadowLight>light, newConfiguration.lab!.globalLightRotation!);
+                });
+                this._forceShadowUpdate = true;
+            }
         }
 
         if (this._defaultRenderingPipeline && this._defaultRenderingPipeline.imageProcessing) {
@@ -789,7 +801,7 @@ export class SceneManager {
         if (this.scene.imageProcessingConfiguration) {
             this.scene.imageProcessingConfiguration.colorCurvesEnabled = true;
             this.scene.imageProcessingConfiguration.vignetteEnabled = true;
-            this.scene.imageProcessingConfiguration.toneMappingEnabled = !!cameraConfig.toneMappingEnabled;
+            this.scene.imageProcessingConfiguration.toneMappingEnabled = !!getConfigurationKey("camera.toneMappingEnabled", this._viewer.configuration);
         }
 
         extendClassWithConfig(this.camera, cameraConfig);
@@ -810,6 +822,11 @@ export class SceneManager {
         this.camera.alpha = (this._viewer.configuration.camera && this._viewer.configuration.camera.alpha) || this.camera.alpha;
         this.camera.beta = (this._viewer.configuration.camera && this._viewer.configuration.camera.beta) || this.camera.beta;
         this.camera.radius = (this._viewer.configuration.camera && this._viewer.configuration.camera.radius) || this.camera.radius;
+
+        /*this.scene.lights.filter(light => light instanceof ShadowLight).forEach(light => {
+            // casting ais safe, due to the constraints tested before
+            (<ShadowLight>light).setDirectionToTarget(center);
+        });*/
     }
 
     protected _configureEnvironment(skyboxConifguration?: ISkyboxConfiguration | boolean, groundConfiguration?: IGroundConfiguration | boolean) {
@@ -989,10 +1006,12 @@ export class SceneManager {
      * @param lightsConfiguration the (new) light(s) configuration
      * @param model optionally use the model to configure the camera.
      */
-    protected _configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean } = {}) {
+    protected _configureLights(lightsConfiguration: { [name: string]: ILightConfiguration | boolean | number } = {}) {
 
         // sanity check!
-        if (!Object.keys(lightsConfiguration).length) {
+        let lightKeys = Object.keys(lightsConfiguration).filter(name => name !== 'globalRotation');
+
+        if (!lightKeys.length) {
             if (!this.scene.lights.length)
                 this.scene.createDefaultLight(true);
         } else {
@@ -1008,11 +1027,14 @@ export class SceneManager {
                 });
             }
 
-            Object.keys(lightsConfiguration).forEach((name, idx) => {
+            lightKeys.forEach((name, idx) => {
                 let lightConfig: ILightConfiguration = { type: 0 };
                 if (typeof lightsConfiguration[name] === 'object') {
                     lightConfig = <ILightConfiguration>lightsConfiguration[name];
                 }
+                if (typeof lightsConfiguration[name] === 'number') {
+                    lightConfig.type = <number>lightsConfiguration[name];
+                }
 
                 lightConfig.name = name;
 
@@ -1025,6 +1047,9 @@ export class SceneManager {
                 } else {
                     // available? get it from the scene
                     light = <Light>this.scene.getLightByName(name);
+                    if (typeof lightsConfiguration[name] === 'boolean') {
+                        lightConfig.type = light.getTypeID();
+                    }
                     lightsAvailable = lightsAvailable.filter(ln => ln !== name);
                     if (lightConfig.type !== undefined && light.getTypeID() !== lightConfig.type) {
                         light.dispose();

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 12997 - 12922
dist/preview release/babylon.d.ts


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 43 - 43
dist/preview release/babylon.js


+ 231 - 64
dist/preview release/babylon.max.js

@@ -1380,6 +1380,10 @@ var BABYLON;
         __extends(PointerInfoPre, _super);
         function PointerInfoPre(type, event, localX, localY) {
             var _this = _super.call(this, type, event) || this;
+            /**
+             * Ray from a pointer if availible (eg. 6dof controller)
+             */
+            _this.ray = null;
             _this.skipOnPointerObservable = false;
             _this.localPosition = new BABYLON.Vector2(localX, localY);
             return _this;
@@ -25051,6 +25055,9 @@ var BABYLON;
          */
         Scene.prototype.simulatePointerMove = function (pickResult, pointerEventInit) {
             var evt = new PointerEvent("pointermove", pointerEventInit);
+            if (this._checkPrePointerObservable(pickResult, evt, BABYLON.PointerEventTypes.POINTERMOVE)) {
+                return this;
+            }
             return this._processPointerMove(pickResult, evt);
         };
         Scene.prototype._processPointerMove = function (pickResult, evt) {
@@ -25105,6 +25112,19 @@ var BABYLON;
             }
             return this;
         };
+        Scene.prototype._checkPrePointerObservable = function (pickResult, evt, type) {
+            var pi = new BABYLON.PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
+            if (pickResult) {
+                pi.ray = pickResult.ray;
+            }
+            this.onPrePointerObservable.notifyObservers(pi, type);
+            if (pi.skipOnPointerObservable) {
+                return true;
+            }
+            else {
+                return false;
+            }
+        };
         /**
          * Use this method to simulate a pointer down on a mesh
          * The pickResult parameter can be obtained from a scene.pick or scene.pickWithRay
@@ -25114,6 +25134,9 @@ var BABYLON;
          */
         Scene.prototype.simulatePointerDown = function (pickResult, pointerEventInit) {
             var evt = new PointerEvent("pointerdown", pointerEventInit);
+            if (this._checkPrePointerObservable(pickResult, evt, BABYLON.PointerEventTypes.POINTERDOWN)) {
+                return this;
+            }
             return this._processPointerDown(pickResult, evt);
         };
         Scene.prototype._processPointerDown = function (pickResult, evt) {
@@ -25177,6 +25200,9 @@ var BABYLON;
             var clickInfo = new ClickInfo();
             clickInfo.singleClick = true;
             clickInfo.ignore = true;
+            if (this._checkPrePointerObservable(pickResult, evt, BABYLON.PointerEventTypes.POINTERUP)) {
+                return this;
+            }
             return this._processPointerUp(pickResult, evt, clickInfo);
         };
         Scene.prototype._processPointerUp = function (pickResult, evt, clickInfo) {
@@ -25387,13 +25413,8 @@ var BABYLON;
             this._onPointerMove = function (evt) {
                 _this._updatePointerPosition(evt);
                 // PreObservable support
-                if (_this.onPrePointerObservable.hasObservers() && !_this._pointerCaptures[evt.pointerId]) {
-                    var type = evt.type === "mousewheel" || evt.type === "DOMMouseScroll" ? BABYLON.PointerEventTypes.POINTERWHEEL : BABYLON.PointerEventTypes.POINTERMOVE;
-                    var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                    _this.onPrePointerObservable.notifyObservers(pi, type);
-                    if (pi.skipOnPointerObservable) {
-                        return;
-                    }
+                if (_this._checkPrePointerObservable(null, evt, evt.type === "mousewheel" || evt.type === "DOMMouseScroll" ? BABYLON.PointerEventTypes.POINTERWHEEL : BABYLON.PointerEventTypes.POINTERMOVE)) {
+                    return;
                 }
                 if (!_this.cameraToUseForPointers && !_this.activeCamera) {
                     return;
@@ -25415,13 +25436,8 @@ var BABYLON;
                     canvas.focus();
                 }
                 // PreObservable support
-                if (_this.onPrePointerObservable.hasObservers()) {
-                    var type = BABYLON.PointerEventTypes.POINTERDOWN;
-                    var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                    _this.onPrePointerObservable.notifyObservers(pi, type);
-                    if (pi.skipOnPointerObservable) {
-                        return;
-                    }
+                if (_this._checkPrePointerObservable(null, evt, BABYLON.PointerEventTypes.POINTERDOWN)) {
+                    return;
                 }
                 if (!_this.cameraToUseForPointers && !_this.activeCamera) {
                     return;
@@ -25478,28 +25494,19 @@ var BABYLON;
                         if (!clickInfo.ignore) {
                             if (!clickInfo.hasSwiped) {
                                 if (clickInfo.singleClick && _this.onPrePointerObservable.hasSpecificMask(BABYLON.PointerEventTypes.POINTERTAP)) {
-                                    var type = BABYLON.PointerEventTypes.POINTERTAP;
-                                    var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                                    _this.onPrePointerObservable.notifyObservers(pi, type);
-                                    if (pi.skipOnPointerObservable) {
+                                    if (_this._checkPrePointerObservable(null, evt, BABYLON.PointerEventTypes.POINTERTAP)) {
                                         return;
                                     }
                                 }
                                 if (clickInfo.doubleClick && _this.onPrePointerObservable.hasSpecificMask(BABYLON.PointerEventTypes.POINTERDOUBLETAP)) {
-                                    var type = BABYLON.PointerEventTypes.POINTERDOUBLETAP;
-                                    var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                                    _this.onPrePointerObservable.notifyObservers(pi, type);
-                                    if (pi.skipOnPointerObservable) {
+                                    if (_this._checkPrePointerObservable(null, evt, BABYLON.PointerEventTypes.POINTERDOUBLETAP)) {
                                         return;
                                     }
                                 }
                             }
                         }
                         else {
-                            var type = BABYLON.PointerEventTypes.POINTERUP;
-                            var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                            _this.onPrePointerObservable.notifyObservers(pi, type);
-                            if (pi.skipOnPointerObservable) {
+                            if (_this._checkPrePointerObservable(null, evt, BABYLON.PointerEventTypes.POINTERUP)) {
                                 return;
                             }
                         }
@@ -27739,8 +27746,10 @@ var BABYLON;
         };
         /**
          * Render the scene
+         * @param updateCameras defines a boolean indicating if cameras must update according to their inputs (true by default)
          */
-        Scene.prototype.render = function () {
+        Scene.prototype.render = function (updateCameras) {
+            if (updateCameras === void 0) { updateCameras = true; }
             if (this.isDisposed) {
                 return;
             }
@@ -27807,24 +27816,26 @@ var BABYLON;
                 this._gamepadManager._checkGamepadsStatus();
             }
             // Update Cameras
-            if (this.activeCameras.length > 0) {
-                for (var cameraIndex = 0; cameraIndex < this.activeCameras.length; cameraIndex++) {
-                    var camera = this.activeCameras[cameraIndex];
-                    camera.update();
-                    if (camera.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
-                        // rig cameras
-                        for (var index = 0; index < camera._rigCameras.length; index++) {
-                            camera._rigCameras[index].update();
+            if (updateCameras) {
+                if (this.activeCameras.length > 0) {
+                    for (var cameraIndex = 0; cameraIndex < this.activeCameras.length; cameraIndex++) {
+                        var camera = this.activeCameras[cameraIndex];
+                        camera.update();
+                        if (camera.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
+                            // rig cameras
+                            for (var index = 0; index < camera._rigCameras.length; index++) {
+                                camera._rigCameras[index].update();
+                            }
                         }
                     }
                 }
-            }
-            else if (this.activeCamera) {
-                this.activeCamera.update();
-                if (this.activeCamera.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
-                    // rig cameras
-                    for (var index = 0; index < this.activeCamera._rigCameras.length; index++) {
-                        this.activeCamera._rigCameras[index].update();
+                else if (this.activeCamera) {
+                    this.activeCamera.update();
+                    if (this.activeCamera.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
+                        // rig cameras
+                        for (var index = 0; index < this.activeCamera._rigCameras.length; index++) {
+                            this.activeCamera._rigCameras[index].update();
+                        }
                     }
                 }
             }
@@ -86803,7 +86814,6 @@ var BABYLON;
             this.utilityLayerScene = new BABYLON.Scene(originalScene.getEngine());
             originalScene.getEngine().scenes.pop();
             // Render directly on top of existing scene without clearing
-            this.utilityLayerScene.clearColor = new BABYLON.Color4(0, 0, 0, 0);
             this.utilityLayerScene.autoClear = false;
             this._afterRenderObserver = this.originalScene.onAfterRenderObservable.add(function () {
                 if (_this.shouldRender) {
@@ -86819,7 +86829,7 @@ var BABYLON;
          */
         UtilityLayerRenderer.prototype.render = function () {
             this._updateCamera();
-            this.utilityLayerScene.render();
+            this.utilityLayerScene.render(false);
         };
         /**
          * Disposes of the renderer
@@ -86846,6 +86856,148 @@ var BABYLON;
 var BABYLON;
 (function (BABYLON) {
     /**
+     * Renders gizmos on top of an existing scene which provide controls for position, rotation, etc.
+     */
+    var Gizmo = /** @class */ (function () {
+        /**
+         * Creates a gizmo
+         * @param gizmoLayer The utility layer the gizmo will be added to
+         */
+        function Gizmo(/** The utility layer the gizmo will be added to */ gizmoLayer) {
+            var _this = this;
+            this.gizmoLayer = gizmoLayer;
+            this._rootMesh = new BABYLON.Mesh("gizmoRootNode", gizmoLayer.utilityLayerScene);
+            this._beforeRenderObserver = this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.add(function () {
+                if (_this.gizmoLayer.utilityLayerScene.activeCamera && _this.attachedMesh) {
+                    var dist = _this.attachedMesh.position.subtract(_this.gizmoLayer.utilityLayerScene.activeCamera.position).length() / 5;
+                    _this._rootMesh.scaling.set(dist, dist, dist);
+                }
+                if (_this.attachedMesh) {
+                    _this._rootMesh.position.copyFrom(_this.attachedMesh.position);
+                }
+            });
+        }
+        /**
+         * Disposes of the gizmo
+         */
+        Gizmo.prototype.dispose = function () {
+            this._rootMesh.dispose();
+            if (this._beforeRenderObserver) {
+                this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.remove(this._beforeRenderObserver);
+            }
+        };
+        return Gizmo;
+    }());
+    BABYLON.Gizmo = Gizmo;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.gizmo.js.map
+
+
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Single axis drag gizmo
+     */
+    var AxisDragGizmo = /** @class */ (function (_super) {
+        __extends(AxisDragGizmo, _super);
+        /**
+         * 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
+         * @param color The color of the gizmo
+         */
+        function AxisDragGizmo(gizmoLayer, dragAxis, color) {
+            var _this = _super.call(this, gizmoLayer) || this;
+            // Create Material
+            var coloredMaterial = new BABYLON.StandardMaterial("", gizmoLayer.utilityLayerScene);
+            coloredMaterial.disableLighting = true;
+            coloredMaterial.emissiveColor = color;
+            // Build mesh on root node
+            var arrowMesh = BABYLON.MeshBuilder.CreateCylinder("yPosMesh", { diameterTop: 0, height: 2, tessellation: 96 }, gizmoLayer.utilityLayerScene);
+            var arrowTail = BABYLON.MeshBuilder.CreateCylinder("yPosMesh", { diameter: 0.03, height: 0.2, tessellation: 96 }, gizmoLayer.utilityLayerScene);
+            _this._rootMesh.addChild(arrowMesh);
+            _this._rootMesh.addChild(arrowTail);
+            // Position arrow pointing in its drag axis
+            arrowMesh.scaling.scaleInPlace(0.1);
+            arrowMesh.material = coloredMaterial;
+            arrowMesh.rotation.x = Math.PI / 2;
+            arrowMesh.position.z += 0.3;
+            arrowTail.rotation.x = Math.PI / 2;
+            arrowTail.material = coloredMaterial;
+            arrowTail.position.z += 0.2;
+            _this._rootMesh.lookAt(_this._rootMesh.position.subtract(dragAxis));
+            // Add drag behavior to handle events when the gizmo is dragged
+            _this._dragBehavior = new BABYLON.PointerDragBehavior({ dragAxis: dragAxis, pointerObservableScene: gizmoLayer.originalScene });
+            _this._dragBehavior.moveAttached = false;
+            _this._rootMesh.addBehavior(_this._dragBehavior);
+            _this._dragBehavior.onDragObservable.add(function (event) {
+                if (_this.attachedMesh) {
+                    _this.attachedMesh.position.addInPlace(event.delta);
+                }
+            });
+            return _this;
+        }
+        /**
+         * Disposes of the gizmo
+         */
+        AxisDragGizmo.prototype.dispose = function () {
+            this._dragBehavior.detach();
+            _super.prototype.dispose.call(this);
+        };
+        return AxisDragGizmo;
+    }(BABYLON.Gizmo));
+    BABYLON.AxisDragGizmo = AxisDragGizmo;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.axisDragGizmo.js.map
+
+
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Gizmo that enables dragging a mesh along 3 axis
+     */
+    var PositionGizmo = /** @class */ (function (_super) {
+        __extends(PositionGizmo, _super);
+        /**
+         * Creates a PositionGizmo
+         * @param gizmoLayer The utility layer the gizmo will be added to
+         */
+        function PositionGizmo(gizmoLayer) {
+            var _this = _super.call(this, gizmoLayer) || this;
+            _this._xDrag = new BABYLON.AxisDragGizmo(gizmoLayer, new BABYLON.Vector3(1, 0, 0), BABYLON.Color3.FromHexString("#00b894"));
+            _this._yDrag = new BABYLON.AxisDragGizmo(gizmoLayer, new BABYLON.Vector3(0, 1, 0), BABYLON.Color3.FromHexString("#d63031"));
+            _this._zDrag = new BABYLON.AxisDragGizmo(gizmoLayer, new BABYLON.Vector3(0, 0, 1), BABYLON.Color3.FromHexString("#0984e3"));
+            return _this;
+        }
+        Object.defineProperty(PositionGizmo.prototype, "attachedMesh", {
+            set: function (mesh) {
+                this._xDrag.attachedMesh = mesh;
+                this._yDrag.attachedMesh = mesh;
+                this._zDrag.attachedMesh = mesh;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        /**
+         * Disposes of the gizmo
+         */
+        PositionGizmo.prototype.dispose = function () {
+            this._xDrag.dispose();
+            this._yDrag.dispose();
+            this._zDrag.dispose();
+        };
+        return PositionGizmo;
+    }(BABYLON.Gizmo));
+    BABYLON.PositionGizmo = PositionGizmo;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.positionGizmo.js.map
+
+var BABYLON;
+(function (BABYLON) {
+    /**
      * Defines a target to use with MorphTargetManager
      * @see http://doc.babylonjs.com/how_to/how_to_use_morphtargets
      */
@@ -96356,7 +96508,7 @@ var BABYLON;
     var PointerDragBehavior = /** @class */ (function () {
         /**
          * Creates a pointer drag behavior that can be attached to a mesh
-         * @param options The drag axis or normal of the plane that will be dragged accross
+         * @param options The drag axis or normal of the plane that will be dragged across. pointerObservableScene can be used to listen to drag events from another scene(eg. if the attached mesh is in an overlay scene).
          */
         function PointerDragBehavior(options) {
             this.options = options;
@@ -96416,6 +96568,9 @@ var BABYLON;
         PointerDragBehavior.prototype.attach = function (ownerNode) {
             var _this = this;
             this._scene = ownerNode.getScene();
+            if (!this.options.pointerObservableScene) {
+                this.options.pointerObservableScene = this._scene;
+            }
             this._attachedNode = ownerNode;
             // Initialize drag plane to not interfere with existing scene
             if (!PointerDragBehavior._planeScene) {
@@ -96427,32 +96582,44 @@ var BABYLON;
             var dragging = false;
             var lastPosition = new BABYLON.Vector3(0, 0, 0);
             var delta = new BABYLON.Vector3(0, 0, 0);
-            this._pointerObserver = this._scene.onPointerObservable.add(function (pointerInfo) {
-                if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN) {
-                    if (!dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray) {
-                        if (_this._attachedNode == pointerInfo.pickInfo.pickedMesh) {
-                            _this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
-                            var pickedPoint = _this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray);
-                            if (pickedPoint) {
-                                dragging = true;
-                                _this._draggingID = pointerInfo.event.pointerId;
-                                lastPosition.copyFrom(pickedPoint);
-                                _this.onDragStartObservable.notifyObservers({ dragPlanePoint: pickedPoint });
-                            }
+            var pickPredicate = function (m) {
+                return _this._attachedNode == m || m.isDescendantOf(_this._attachedNode);
+            };
+            this._pointerObserver = this.options.pointerObservableScene.onPrePointerObservable.add(function (pointerInfoPre, eventState) {
+                // Check if attached mesh is picked
+                var pickInfo = pointerInfoPre.ray ? _this._scene.pickWithRay(pointerInfoPre.ray, pickPredicate) : _this._scene.pick(_this._scene.pointerX, _this._scene.pointerY, pickPredicate);
+                if (pickInfo) {
+                    pickInfo.ray = pointerInfoPre.ray;
+                    if (!pickInfo.ray) {
+                        pickInfo.ray = _this.options.pointerObservableScene.createPickingRay(_this._scene.pointerX, _this._scene.pointerY, BABYLON.Matrix.Identity(), _this._scene.activeCamera);
+                    }
+                    if (pickInfo.hit) {
+                        eventState.skipNextObservers = true;
+                    }
+                }
+                if (pointerInfoPre.type == BABYLON.PointerEventTypes.POINTERDOWN) {
+                    if (!dragging && pickInfo && pickInfo.hit && pickInfo.pickedMesh && pickInfo.ray) {
+                        _this._updateDragPlanePosition(pickInfo.ray);
+                        var pickedPoint = _this._pickWithRayOnDragPlane(pickInfo.ray);
+                        if (pickedPoint) {
+                            dragging = true;
+                            _this._draggingID = pointerInfoPre.event.pointerId;
+                            lastPosition.copyFrom(pickedPoint);
+                            _this.onDragStartObservable.notifyObservers({ dragPlanePoint: pickedPoint });
                         }
                     }
                 }
-                else if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERUP) {
-                    if (_this._draggingID == pointerInfo.event.pointerId) {
+                else if (pointerInfoPre.type == BABYLON.PointerEventTypes.POINTERUP) {
+                    if (_this._draggingID == pointerInfoPre.event.pointerId) {
                         dragging = false;
                         _this._draggingID = -1;
                         _this.onDragEndObservable.notifyObservers({ dragPlanePoint: lastPosition });
                     }
                 }
-                else if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE) {
-                    if (_this._draggingID == pointerInfo.event.pointerId && dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray) {
-                        var pickedPoint = _this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray);
-                        _this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
+                else if (pointerInfoPre.type == BABYLON.PointerEventTypes.POINTERMOVE) {
+                    if (_this._draggingID == pointerInfoPre.event.pointerId && dragging && pickInfo && pickInfo.ray) {
+                        var pickedPoint = _this._pickWithRayOnDragPlane(pickInfo.ray);
+                        _this._updateDragPlanePosition(pickInfo.ray);
                         if (pickedPoint) {
                             // depending on the drag mode option drag accordingly
                             if (_this.options.dragAxis) {
@@ -96514,7 +96681,7 @@ var BABYLON;
          */
         PointerDragBehavior.prototype.detach = function () {
             if (this._pointerObserver) {
-                this._scene.onPointerObservable.remove(this._pointerObserver);
+                this._scene.onPrePointerObservable.remove(this._pointerObserver);
             }
         };
         return PointerDragBehavior;

+ 231 - 64
dist/preview release/babylon.no-module.max.js

@@ -1347,6 +1347,10 @@ var BABYLON;
         __extends(PointerInfoPre, _super);
         function PointerInfoPre(type, event, localX, localY) {
             var _this = _super.call(this, type, event) || this;
+            /**
+             * Ray from a pointer if availible (eg. 6dof controller)
+             */
+            _this.ray = null;
             _this.skipOnPointerObservable = false;
             _this.localPosition = new BABYLON.Vector2(localX, localY);
             return _this;
@@ -25018,6 +25022,9 @@ var BABYLON;
          */
         Scene.prototype.simulatePointerMove = function (pickResult, pointerEventInit) {
             var evt = new PointerEvent("pointermove", pointerEventInit);
+            if (this._checkPrePointerObservable(pickResult, evt, BABYLON.PointerEventTypes.POINTERMOVE)) {
+                return this;
+            }
             return this._processPointerMove(pickResult, evt);
         };
         Scene.prototype._processPointerMove = function (pickResult, evt) {
@@ -25072,6 +25079,19 @@ var BABYLON;
             }
             return this;
         };
+        Scene.prototype._checkPrePointerObservable = function (pickResult, evt, type) {
+            var pi = new BABYLON.PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
+            if (pickResult) {
+                pi.ray = pickResult.ray;
+            }
+            this.onPrePointerObservable.notifyObservers(pi, type);
+            if (pi.skipOnPointerObservable) {
+                return true;
+            }
+            else {
+                return false;
+            }
+        };
         /**
          * Use this method to simulate a pointer down on a mesh
          * The pickResult parameter can be obtained from a scene.pick or scene.pickWithRay
@@ -25081,6 +25101,9 @@ var BABYLON;
          */
         Scene.prototype.simulatePointerDown = function (pickResult, pointerEventInit) {
             var evt = new PointerEvent("pointerdown", pointerEventInit);
+            if (this._checkPrePointerObservable(pickResult, evt, BABYLON.PointerEventTypes.POINTERDOWN)) {
+                return this;
+            }
             return this._processPointerDown(pickResult, evt);
         };
         Scene.prototype._processPointerDown = function (pickResult, evt) {
@@ -25144,6 +25167,9 @@ var BABYLON;
             var clickInfo = new ClickInfo();
             clickInfo.singleClick = true;
             clickInfo.ignore = true;
+            if (this._checkPrePointerObservable(pickResult, evt, BABYLON.PointerEventTypes.POINTERUP)) {
+                return this;
+            }
             return this._processPointerUp(pickResult, evt, clickInfo);
         };
         Scene.prototype._processPointerUp = function (pickResult, evt, clickInfo) {
@@ -25354,13 +25380,8 @@ var BABYLON;
             this._onPointerMove = function (evt) {
                 _this._updatePointerPosition(evt);
                 // PreObservable support
-                if (_this.onPrePointerObservable.hasObservers() && !_this._pointerCaptures[evt.pointerId]) {
-                    var type = evt.type === "mousewheel" || evt.type === "DOMMouseScroll" ? BABYLON.PointerEventTypes.POINTERWHEEL : BABYLON.PointerEventTypes.POINTERMOVE;
-                    var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                    _this.onPrePointerObservable.notifyObservers(pi, type);
-                    if (pi.skipOnPointerObservable) {
-                        return;
-                    }
+                if (_this._checkPrePointerObservable(null, evt, evt.type === "mousewheel" || evt.type === "DOMMouseScroll" ? BABYLON.PointerEventTypes.POINTERWHEEL : BABYLON.PointerEventTypes.POINTERMOVE)) {
+                    return;
                 }
                 if (!_this.cameraToUseForPointers && !_this.activeCamera) {
                     return;
@@ -25382,13 +25403,8 @@ var BABYLON;
                     canvas.focus();
                 }
                 // PreObservable support
-                if (_this.onPrePointerObservable.hasObservers()) {
-                    var type = BABYLON.PointerEventTypes.POINTERDOWN;
-                    var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                    _this.onPrePointerObservable.notifyObservers(pi, type);
-                    if (pi.skipOnPointerObservable) {
-                        return;
-                    }
+                if (_this._checkPrePointerObservable(null, evt, BABYLON.PointerEventTypes.POINTERDOWN)) {
+                    return;
                 }
                 if (!_this.cameraToUseForPointers && !_this.activeCamera) {
                     return;
@@ -25445,28 +25461,19 @@ var BABYLON;
                         if (!clickInfo.ignore) {
                             if (!clickInfo.hasSwiped) {
                                 if (clickInfo.singleClick && _this.onPrePointerObservable.hasSpecificMask(BABYLON.PointerEventTypes.POINTERTAP)) {
-                                    var type = BABYLON.PointerEventTypes.POINTERTAP;
-                                    var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                                    _this.onPrePointerObservable.notifyObservers(pi, type);
-                                    if (pi.skipOnPointerObservable) {
+                                    if (_this._checkPrePointerObservable(null, evt, BABYLON.PointerEventTypes.POINTERTAP)) {
                                         return;
                                     }
                                 }
                                 if (clickInfo.doubleClick && _this.onPrePointerObservable.hasSpecificMask(BABYLON.PointerEventTypes.POINTERDOUBLETAP)) {
-                                    var type = BABYLON.PointerEventTypes.POINTERDOUBLETAP;
-                                    var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                                    _this.onPrePointerObservable.notifyObservers(pi, type);
-                                    if (pi.skipOnPointerObservable) {
+                                    if (_this._checkPrePointerObservable(null, evt, BABYLON.PointerEventTypes.POINTERDOUBLETAP)) {
                                         return;
                                     }
                                 }
                             }
                         }
                         else {
-                            var type = BABYLON.PointerEventTypes.POINTERUP;
-                            var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                            _this.onPrePointerObservable.notifyObservers(pi, type);
-                            if (pi.skipOnPointerObservable) {
+                            if (_this._checkPrePointerObservable(null, evt, BABYLON.PointerEventTypes.POINTERUP)) {
                                 return;
                             }
                         }
@@ -27706,8 +27713,10 @@ var BABYLON;
         };
         /**
          * Render the scene
+         * @param updateCameras defines a boolean indicating if cameras must update according to their inputs (true by default)
          */
-        Scene.prototype.render = function () {
+        Scene.prototype.render = function (updateCameras) {
+            if (updateCameras === void 0) { updateCameras = true; }
             if (this.isDisposed) {
                 return;
             }
@@ -27774,24 +27783,26 @@ var BABYLON;
                 this._gamepadManager._checkGamepadsStatus();
             }
             // Update Cameras
-            if (this.activeCameras.length > 0) {
-                for (var cameraIndex = 0; cameraIndex < this.activeCameras.length; cameraIndex++) {
-                    var camera = this.activeCameras[cameraIndex];
-                    camera.update();
-                    if (camera.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
-                        // rig cameras
-                        for (var index = 0; index < camera._rigCameras.length; index++) {
-                            camera._rigCameras[index].update();
+            if (updateCameras) {
+                if (this.activeCameras.length > 0) {
+                    for (var cameraIndex = 0; cameraIndex < this.activeCameras.length; cameraIndex++) {
+                        var camera = this.activeCameras[cameraIndex];
+                        camera.update();
+                        if (camera.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
+                            // rig cameras
+                            for (var index = 0; index < camera._rigCameras.length; index++) {
+                                camera._rigCameras[index].update();
+                            }
                         }
                     }
                 }
-            }
-            else if (this.activeCamera) {
-                this.activeCamera.update();
-                if (this.activeCamera.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
-                    // rig cameras
-                    for (var index = 0; index < this.activeCamera._rigCameras.length; index++) {
-                        this.activeCamera._rigCameras[index].update();
+                else if (this.activeCamera) {
+                    this.activeCamera.update();
+                    if (this.activeCamera.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
+                        // rig cameras
+                        for (var index = 0; index < this.activeCamera._rigCameras.length; index++) {
+                            this.activeCamera._rigCameras[index].update();
+                        }
                     }
                 }
             }
@@ -86770,7 +86781,6 @@ var BABYLON;
             this.utilityLayerScene = new BABYLON.Scene(originalScene.getEngine());
             originalScene.getEngine().scenes.pop();
             // Render directly on top of existing scene without clearing
-            this.utilityLayerScene.clearColor = new BABYLON.Color4(0, 0, 0, 0);
             this.utilityLayerScene.autoClear = false;
             this._afterRenderObserver = this.originalScene.onAfterRenderObservable.add(function () {
                 if (_this.shouldRender) {
@@ -86786,7 +86796,7 @@ var BABYLON;
          */
         UtilityLayerRenderer.prototype.render = function () {
             this._updateCamera();
-            this.utilityLayerScene.render();
+            this.utilityLayerScene.render(false);
         };
         /**
          * Disposes of the renderer
@@ -86813,6 +86823,148 @@ var BABYLON;
 var BABYLON;
 (function (BABYLON) {
     /**
+     * Renders gizmos on top of an existing scene which provide controls for position, rotation, etc.
+     */
+    var Gizmo = /** @class */ (function () {
+        /**
+         * Creates a gizmo
+         * @param gizmoLayer The utility layer the gizmo will be added to
+         */
+        function Gizmo(/** The utility layer the gizmo will be added to */ gizmoLayer) {
+            var _this = this;
+            this.gizmoLayer = gizmoLayer;
+            this._rootMesh = new BABYLON.Mesh("gizmoRootNode", gizmoLayer.utilityLayerScene);
+            this._beforeRenderObserver = this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.add(function () {
+                if (_this.gizmoLayer.utilityLayerScene.activeCamera && _this.attachedMesh) {
+                    var dist = _this.attachedMesh.position.subtract(_this.gizmoLayer.utilityLayerScene.activeCamera.position).length() / 5;
+                    _this._rootMesh.scaling.set(dist, dist, dist);
+                }
+                if (_this.attachedMesh) {
+                    _this._rootMesh.position.copyFrom(_this.attachedMesh.position);
+                }
+            });
+        }
+        /**
+         * Disposes of the gizmo
+         */
+        Gizmo.prototype.dispose = function () {
+            this._rootMesh.dispose();
+            if (this._beforeRenderObserver) {
+                this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.remove(this._beforeRenderObserver);
+            }
+        };
+        return Gizmo;
+    }());
+    BABYLON.Gizmo = Gizmo;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.gizmo.js.map
+
+
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Single axis drag gizmo
+     */
+    var AxisDragGizmo = /** @class */ (function (_super) {
+        __extends(AxisDragGizmo, _super);
+        /**
+         * 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
+         * @param color The color of the gizmo
+         */
+        function AxisDragGizmo(gizmoLayer, dragAxis, color) {
+            var _this = _super.call(this, gizmoLayer) || this;
+            // Create Material
+            var coloredMaterial = new BABYLON.StandardMaterial("", gizmoLayer.utilityLayerScene);
+            coloredMaterial.disableLighting = true;
+            coloredMaterial.emissiveColor = color;
+            // Build mesh on root node
+            var arrowMesh = BABYLON.MeshBuilder.CreateCylinder("yPosMesh", { diameterTop: 0, height: 2, tessellation: 96 }, gizmoLayer.utilityLayerScene);
+            var arrowTail = BABYLON.MeshBuilder.CreateCylinder("yPosMesh", { diameter: 0.03, height: 0.2, tessellation: 96 }, gizmoLayer.utilityLayerScene);
+            _this._rootMesh.addChild(arrowMesh);
+            _this._rootMesh.addChild(arrowTail);
+            // Position arrow pointing in its drag axis
+            arrowMesh.scaling.scaleInPlace(0.1);
+            arrowMesh.material = coloredMaterial;
+            arrowMesh.rotation.x = Math.PI / 2;
+            arrowMesh.position.z += 0.3;
+            arrowTail.rotation.x = Math.PI / 2;
+            arrowTail.material = coloredMaterial;
+            arrowTail.position.z += 0.2;
+            _this._rootMesh.lookAt(_this._rootMesh.position.subtract(dragAxis));
+            // Add drag behavior to handle events when the gizmo is dragged
+            _this._dragBehavior = new BABYLON.PointerDragBehavior({ dragAxis: dragAxis, pointerObservableScene: gizmoLayer.originalScene });
+            _this._dragBehavior.moveAttached = false;
+            _this._rootMesh.addBehavior(_this._dragBehavior);
+            _this._dragBehavior.onDragObservable.add(function (event) {
+                if (_this.attachedMesh) {
+                    _this.attachedMesh.position.addInPlace(event.delta);
+                }
+            });
+            return _this;
+        }
+        /**
+         * Disposes of the gizmo
+         */
+        AxisDragGizmo.prototype.dispose = function () {
+            this._dragBehavior.detach();
+            _super.prototype.dispose.call(this);
+        };
+        return AxisDragGizmo;
+    }(BABYLON.Gizmo));
+    BABYLON.AxisDragGizmo = AxisDragGizmo;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.axisDragGizmo.js.map
+
+
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Gizmo that enables dragging a mesh along 3 axis
+     */
+    var PositionGizmo = /** @class */ (function (_super) {
+        __extends(PositionGizmo, _super);
+        /**
+         * Creates a PositionGizmo
+         * @param gizmoLayer The utility layer the gizmo will be added to
+         */
+        function PositionGizmo(gizmoLayer) {
+            var _this = _super.call(this, gizmoLayer) || this;
+            _this._xDrag = new BABYLON.AxisDragGizmo(gizmoLayer, new BABYLON.Vector3(1, 0, 0), BABYLON.Color3.FromHexString("#00b894"));
+            _this._yDrag = new BABYLON.AxisDragGizmo(gizmoLayer, new BABYLON.Vector3(0, 1, 0), BABYLON.Color3.FromHexString("#d63031"));
+            _this._zDrag = new BABYLON.AxisDragGizmo(gizmoLayer, new BABYLON.Vector3(0, 0, 1), BABYLON.Color3.FromHexString("#0984e3"));
+            return _this;
+        }
+        Object.defineProperty(PositionGizmo.prototype, "attachedMesh", {
+            set: function (mesh) {
+                this._xDrag.attachedMesh = mesh;
+                this._yDrag.attachedMesh = mesh;
+                this._zDrag.attachedMesh = mesh;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        /**
+         * Disposes of the gizmo
+         */
+        PositionGizmo.prototype.dispose = function () {
+            this._xDrag.dispose();
+            this._yDrag.dispose();
+            this._zDrag.dispose();
+        };
+        return PositionGizmo;
+    }(BABYLON.Gizmo));
+    BABYLON.PositionGizmo = PositionGizmo;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.positionGizmo.js.map
+
+var BABYLON;
+(function (BABYLON) {
+    /**
      * Defines a target to use with MorphTargetManager
      * @see http://doc.babylonjs.com/how_to/how_to_use_morphtargets
      */
@@ -96323,7 +96475,7 @@ var BABYLON;
     var PointerDragBehavior = /** @class */ (function () {
         /**
          * Creates a pointer drag behavior that can be attached to a mesh
-         * @param options The drag axis or normal of the plane that will be dragged accross
+         * @param options The drag axis or normal of the plane that will be dragged across. pointerObservableScene can be used to listen to drag events from another scene(eg. if the attached mesh is in an overlay scene).
          */
         function PointerDragBehavior(options) {
             this.options = options;
@@ -96383,6 +96535,9 @@ var BABYLON;
         PointerDragBehavior.prototype.attach = function (ownerNode) {
             var _this = this;
             this._scene = ownerNode.getScene();
+            if (!this.options.pointerObservableScene) {
+                this.options.pointerObservableScene = this._scene;
+            }
             this._attachedNode = ownerNode;
             // Initialize drag plane to not interfere with existing scene
             if (!PointerDragBehavior._planeScene) {
@@ -96394,32 +96549,44 @@ var BABYLON;
             var dragging = false;
             var lastPosition = new BABYLON.Vector3(0, 0, 0);
             var delta = new BABYLON.Vector3(0, 0, 0);
-            this._pointerObserver = this._scene.onPointerObservable.add(function (pointerInfo) {
-                if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN) {
-                    if (!dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray) {
-                        if (_this._attachedNode == pointerInfo.pickInfo.pickedMesh) {
-                            _this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
-                            var pickedPoint = _this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray);
-                            if (pickedPoint) {
-                                dragging = true;
-                                _this._draggingID = pointerInfo.event.pointerId;
-                                lastPosition.copyFrom(pickedPoint);
-                                _this.onDragStartObservable.notifyObservers({ dragPlanePoint: pickedPoint });
-                            }
+            var pickPredicate = function (m) {
+                return _this._attachedNode == m || m.isDescendantOf(_this._attachedNode);
+            };
+            this._pointerObserver = this.options.pointerObservableScene.onPrePointerObservable.add(function (pointerInfoPre, eventState) {
+                // Check if attached mesh is picked
+                var pickInfo = pointerInfoPre.ray ? _this._scene.pickWithRay(pointerInfoPre.ray, pickPredicate) : _this._scene.pick(_this._scene.pointerX, _this._scene.pointerY, pickPredicate);
+                if (pickInfo) {
+                    pickInfo.ray = pointerInfoPre.ray;
+                    if (!pickInfo.ray) {
+                        pickInfo.ray = _this.options.pointerObservableScene.createPickingRay(_this._scene.pointerX, _this._scene.pointerY, BABYLON.Matrix.Identity(), _this._scene.activeCamera);
+                    }
+                    if (pickInfo.hit) {
+                        eventState.skipNextObservers = true;
+                    }
+                }
+                if (pointerInfoPre.type == BABYLON.PointerEventTypes.POINTERDOWN) {
+                    if (!dragging && pickInfo && pickInfo.hit && pickInfo.pickedMesh && pickInfo.ray) {
+                        _this._updateDragPlanePosition(pickInfo.ray);
+                        var pickedPoint = _this._pickWithRayOnDragPlane(pickInfo.ray);
+                        if (pickedPoint) {
+                            dragging = true;
+                            _this._draggingID = pointerInfoPre.event.pointerId;
+                            lastPosition.copyFrom(pickedPoint);
+                            _this.onDragStartObservable.notifyObservers({ dragPlanePoint: pickedPoint });
                         }
                     }
                 }
-                else if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERUP) {
-                    if (_this._draggingID == pointerInfo.event.pointerId) {
+                else if (pointerInfoPre.type == BABYLON.PointerEventTypes.POINTERUP) {
+                    if (_this._draggingID == pointerInfoPre.event.pointerId) {
                         dragging = false;
                         _this._draggingID = -1;
                         _this.onDragEndObservable.notifyObservers({ dragPlanePoint: lastPosition });
                     }
                 }
-                else if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE) {
-                    if (_this._draggingID == pointerInfo.event.pointerId && dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray) {
-                        var pickedPoint = _this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray);
-                        _this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
+                else if (pointerInfoPre.type == BABYLON.PointerEventTypes.POINTERMOVE) {
+                    if (_this._draggingID == pointerInfoPre.event.pointerId && dragging && pickInfo && pickInfo.ray) {
+                        var pickedPoint = _this._pickWithRayOnDragPlane(pickInfo.ray);
+                        _this._updateDragPlanePosition(pickInfo.ray);
                         if (pickedPoint) {
                             // depending on the drag mode option drag accordingly
                             if (_this.options.dragAxis) {
@@ -96481,7 +96648,7 @@ var BABYLON;
          */
         PointerDragBehavior.prototype.detach = function () {
             if (this._pointerObserver) {
-                this._scene.onPointerObservable.remove(this._pointerObserver);
+                this._scene.onPrePointerObservable.remove(this._pointerObserver);
             }
         };
         return PointerDragBehavior;

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 50 - 50
dist/preview release/babylon.worker.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 233 - 66
dist/preview release/es6.js


+ 2 - 3
dist/preview release/gui/babylon.gui.d.ts

@@ -995,7 +995,7 @@ declare module BABYLON.GUI {
          * @param scene
          */
         constructor(scene?: Scene);
-        private _doPicking(type, pointerEvent);
+        private _doPicking(type, pointerEvent, ray?);
         /**
          * Gets the root container
          */
@@ -1214,7 +1214,7 @@ declare module BABYLON.GUI {
          * Node creation.
          * Can be overriden by children
          * @param scene defines the scene where the node must be attached
-         * @returns the attached node or null if none
+         * @returns the attached node or null if none. Must return a Mesh or AbstractMesh if there is an atttached visible object
          */
         protected _createNode(scene: Scene): Nullable<TransformNode>;
         /**
@@ -1332,7 +1332,6 @@ declare module BABYLON.GUI {
         private _backFluentMaterial;
         private _frontFluentMaterial;
         private _text;
-        private _imageUrl;
         /**
          * Gets or sets text for the button
          */

+ 9 - 5
dist/preview release/gui/babylon.gui.js

@@ -5643,7 +5643,7 @@ var BABYLON;
                     if (!camera) {
                         return;
                     }
-                    pi.skipOnPointerObservable = _this._doPicking(pi.type, pointerEvent);
+                    pi.skipOnPointerObservable = _this._doPicking(pi.type, pointerEvent, pi.ray);
                 });
                 // Scene
                 this._utilityLayer.utilityLayerScene.autoClear = false;
@@ -5665,14 +5665,14 @@ var BABYLON;
                 enumerable: true,
                 configurable: true
             });
-            GUI3DManager.prototype._doPicking = function (type, pointerEvent) {
+            GUI3DManager.prototype._doPicking = function (type, pointerEvent, ray) {
                 if (!this._utilityLayer || !this._utilityLayer.utilityLayerScene.activeCamera) {
                     return false;
                 }
                 var pointerId = pointerEvent.pointerId || 0;
                 var buttonIndex = pointerEvent.button;
                 var utilityScene = this._utilityLayer.utilityLayerScene;
-                var pickingInfo = utilityScene.pick(this._scene.pointerX, this._scene.pointerY);
+                var pickingInfo = ray ? utilityScene.pickWithRay(ray) : utilityScene.pick(this._scene.pointerX, this._scene.pointerY);
                 if (!pickingInfo || !pickingInfo.hit) {
                     var previousControlOver = this._lastControlOver[pointerId];
                     if (previousControlOver) {
@@ -6214,7 +6214,7 @@ var BABYLON;
              * Node creation.
              * Can be overriden by children
              * @param scene defines the scene where the node must be attached
-             * @returns the attached node or null if none
+             * @returns the attached node or null if none. Must return a Mesh or AbstractMesh if there is an atttached visible object
              */
             Control3D.prototype._createNode = function (scene) {
                 // Do nothing by default
@@ -6573,6 +6573,7 @@ var BABYLON;
                 return _this;
             }
             Object.defineProperty(HolographicButton.prototype, "text", {
+                // private _imageUrl: string;
                 /**
                  * Gets or sets text for the button
                  */
@@ -6682,11 +6683,14 @@ var BABYLON;
                     return this._isVertical;
                 },
                 set: function (value) {
+                    var _this = this;
                     if (this._isVertical === value) {
                         return;
                     }
                     this._isVertical = value;
-                    this._arrangeChildren();
+                    BABYLON.Tools.SetImmediate(function () {
+                        _this._arrangeChildren();
+                    });
                 },
                 enumerable: true,
                 configurable: true

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 1
dist/preview release/gui/babylon.gui.min.js


+ 2 - 3
dist/preview release/gui/babylon.gui.module.d.ts

@@ -1000,7 +1000,7 @@ declare module BABYLON.GUI {
          * @param scene
          */
         constructor(scene?: Scene);
-        private _doPicking(type, pointerEvent);
+        private _doPicking(type, pointerEvent, ray?);
         /**
          * Gets the root container
          */
@@ -1219,7 +1219,7 @@ declare module BABYLON.GUI {
          * Node creation.
          * Can be overriden by children
          * @param scene defines the scene where the node must be attached
-         * @returns the attached node or null if none
+         * @returns the attached node or null if none. Must return a Mesh or AbstractMesh if there is an atttached visible object
          */
         protected _createNode(scene: Scene): Nullable<TransformNode>;
         /**
@@ -1337,7 +1337,6 @@ declare module BABYLON.GUI {
         private _backFluentMaterial;
         private _frontFluentMaterial;
         private _text;
-        private _imageUrl;
         /**
          * Gets or sets text for the button
          */

+ 56 - 0
dist/preview release/materialsLibrary/babylon.mixMaterial.d.ts

@@ -0,0 +1,56 @@
+
+declare module BABYLON {
+    class MixMaterial extends PushMaterial {
+        /**
+         * Mix textures
+         */
+        private _mixTexture1;
+        mixTexture1: BaseTexture;
+        private _mixTexture2;
+        mixTexture2: BaseTexture;
+        /**
+         * Diffuse textures
+         */
+        private _diffuseTexture1;
+        diffuseTexture1: Texture;
+        private _diffuseTexture2;
+        diffuseTexture2: Texture;
+        private _diffuseTexture3;
+        diffuseTexture3: Texture;
+        private _diffuseTexture4;
+        diffuseTexture4: Texture;
+        private _diffuseTexture5;
+        diffuseTexture5: Texture;
+        private _diffuseTexture6;
+        diffuseTexture6: Texture;
+        private _diffuseTexture7;
+        diffuseTexture7: Texture;
+        private _diffuseTexture8;
+        diffuseTexture8: Texture;
+        /**
+         * Uniforms
+         */
+        diffuseColor: Color3;
+        specularColor: Color3;
+        specularPower: number;
+        private _disableLighting;
+        disableLighting: boolean;
+        private _maxSimultaneousLights;
+        maxSimultaneousLights: number;
+        private _renderId;
+        constructor(name: string, scene: Scene);
+        needAlphaBlending(): boolean;
+        needAlphaTesting(): boolean;
+        getAlphaTestTexture(): Nullable<BaseTexture>;
+        isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances?: boolean): boolean;
+        bindForSubMesh(world: Matrix, mesh: Mesh, subMesh: SubMesh): void;
+        getAnimatables(): IAnimatable[];
+        getActiveTextures(): BaseTexture[];
+        hasTexture(texture: BaseTexture): boolean;
+        dispose(forceDisposeEffect?: boolean): void;
+        clone(name: string): MixMaterial;
+        serialize(): any;
+        getClassName(): string;
+        static Parse(source: any, scene: Scene, rootUrl: string): MixMaterial;
+    }
+}

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 469 - 0
dist/preview release/materialsLibrary/babylon.mixMaterial.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1 - 0
dist/preview release/materialsLibrary/babylon.mixMaterial.min.js


+ 57 - 0
dist/preview release/materialsLibrary/babylonjs.materials.d.ts

@@ -367,6 +367,63 @@ declare module BABYLON {
 
 
 declare module BABYLON {
+    class MixMaterial extends PushMaterial {
+        /**
+         * Mix textures
+         */
+        private _mixTexture1;
+        mixTexture1: BaseTexture;
+        private _mixTexture2;
+        mixTexture2: BaseTexture;
+        /**
+         * Diffuse textures
+         */
+        private _diffuseTexture1;
+        diffuseTexture1: Texture;
+        private _diffuseTexture2;
+        diffuseTexture2: Texture;
+        private _diffuseTexture3;
+        diffuseTexture3: Texture;
+        private _diffuseTexture4;
+        diffuseTexture4: Texture;
+        private _diffuseTexture5;
+        diffuseTexture5: Texture;
+        private _diffuseTexture6;
+        diffuseTexture6: Texture;
+        private _diffuseTexture7;
+        diffuseTexture7: Texture;
+        private _diffuseTexture8;
+        diffuseTexture8: Texture;
+        /**
+         * Uniforms
+         */
+        diffuseColor: Color3;
+        specularColor: Color3;
+        specularPower: number;
+        private _disableLighting;
+        disableLighting: boolean;
+        private _maxSimultaneousLights;
+        maxSimultaneousLights: number;
+        private _renderId;
+        constructor(name: string, scene: Scene);
+        needAlphaBlending(): boolean;
+        needAlphaTesting(): boolean;
+        getAlphaTestTexture(): Nullable<BaseTexture>;
+        isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances?: boolean): boolean;
+        bindForSubMesh(world: Matrix, mesh: Mesh, subMesh: SubMesh): void;
+        getAnimatables(): IAnimatable[];
+        getActiveTextures(): BaseTexture[];
+        hasTexture(texture: BaseTexture): boolean;
+        dispose(forceDisposeEffect?: boolean): void;
+        clone(name: string): MixMaterial;
+        serialize(): any;
+        getClassName(): string;
+        static Parse(source: any, scene: Scene, rootUrl: string): MixMaterial;
+    }
+}
+
+
+declare module BABYLON {
     class TriPlanarMaterial extends PushMaterial {
         mixTexture: BaseTexture;
         private _diffuseTextureX;

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 456 - 0
dist/preview release/materialsLibrary/babylonjs.materials.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 3 - 2
dist/preview release/materialsLibrary/babylonjs.materials.min.js


+ 57 - 0
dist/preview release/materialsLibrary/babylonjs.materials.module.d.ts

@@ -372,6 +372,63 @@ declare module BABYLON {
 
 
 declare module BABYLON {
+    class MixMaterial extends PushMaterial {
+        /**
+         * Mix textures
+         */
+        private _mixTexture1;
+        mixTexture1: BaseTexture;
+        private _mixTexture2;
+        mixTexture2: BaseTexture;
+        /**
+         * Diffuse textures
+         */
+        private _diffuseTexture1;
+        diffuseTexture1: Texture;
+        private _diffuseTexture2;
+        diffuseTexture2: Texture;
+        private _diffuseTexture3;
+        diffuseTexture3: Texture;
+        private _diffuseTexture4;
+        diffuseTexture4: Texture;
+        private _diffuseTexture5;
+        diffuseTexture5: Texture;
+        private _diffuseTexture6;
+        diffuseTexture6: Texture;
+        private _diffuseTexture7;
+        diffuseTexture7: Texture;
+        private _diffuseTexture8;
+        diffuseTexture8: Texture;
+        /**
+         * Uniforms
+         */
+        diffuseColor: Color3;
+        specularColor: Color3;
+        specularPower: number;
+        private _disableLighting;
+        disableLighting: boolean;
+        private _maxSimultaneousLights;
+        maxSimultaneousLights: number;
+        private _renderId;
+        constructor(name: string, scene: Scene);
+        needAlphaBlending(): boolean;
+        needAlphaTesting(): boolean;
+        getAlphaTestTexture(): Nullable<BaseTexture>;
+        isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances?: boolean): boolean;
+        bindForSubMesh(world: Matrix, mesh: Mesh, subMesh: SubMesh): void;
+        getAnimatables(): IAnimatable[];
+        getActiveTextures(): BaseTexture[];
+        hasTexture(texture: BaseTexture): boolean;
+        dispose(forceDisposeEffect?: boolean): void;
+        clone(name: string): MixMaterial;
+        serialize(): any;
+        getClassName(): string;
+        static Parse(source: any, scene: Scene, rootUrl: string): MixMaterial;
+    }
+}
+
+
+declare module BABYLON {
     class TriPlanarMaterial extends PushMaterial {
         mixTexture: BaseTexture;
         private _diffuseTextureX;

+ 18 - 2
dist/preview release/serializers/babylon.glTF2Serializer.js

@@ -698,6 +698,9 @@ var BABYLON;
              * @param babylonMesh The BabylonJS mesh
              */
             _Exporter.prototype.getMeshPrimitiveMode = function (babylonMesh) {
+                if (babylonMesh instanceof BABYLON.LinesMesh) {
+                    return BABYLON.Material.LineListDrawMode;
+                }
                 return babylonMesh.material ? babylonMesh.material.fillMode : BABYLON.Material.TriangleFillMode;
             };
             /**
@@ -841,7 +844,20 @@ var BABYLON;
                             var babylonMaterial = submesh.getMaterial();
                             var materialIndex = null;
                             if (babylonMaterial) {
-                                if (babylonMaterial instanceof BABYLON.MultiMaterial) {
+                                if (bufferMesh instanceof BABYLON.LinesMesh) {
+                                    // get the color from the lines mesh and set it in the material
+                                    var material = {
+                                        name: bufferMesh.name + ' material'
+                                    };
+                                    if (!bufferMesh.color.equals(BABYLON.Color3.White()) || bufferMesh.alpha < 1) {
+                                        material.pbrMetallicRoughness = {
+                                            baseColorFactor: bufferMesh.color.asArray().concat([bufferMesh.alpha])
+                                        };
+                                    }
+                                    this.materials.push(material);
+                                    materialIndex = this.materials.length - 1;
+                                }
+                                else if (babylonMaterial instanceof BABYLON.MultiMaterial) {
                                     babylonMaterial = babylonMaterial.subMaterials[submesh.materialIndex];
                                     if (babylonMaterial) {
                                         materialIndex = this.babylonScene.materials.indexOf(babylonMaterial);
@@ -853,6 +869,7 @@ var BABYLON;
                             }
                             var glTFMaterial = materialIndex != null ? this.materials[materialIndex] : null;
                             var meshPrimitive = { attributes: {} };
+                            this.setPrimitiveMode(meshPrimitive, primitiveMode);
                             for (var _c = 0, attributeData_2 = attributeData; _c < attributeData_2.length; _c++) {
                                 var attribute = attributeData_2[_c];
                                 var attributeKind = attribute.kind;
@@ -891,7 +908,6 @@ var BABYLON;
                             if (babylonMaterial) {
                                 if (materialIndex != null && Object.keys(meshPrimitive.attributes).length > 0) {
                                     var sideOrientation = this.babylonScene.materials[materialIndex].sideOrientation;
-                                    this.setPrimitiveMode(meshPrimitive, primitiveMode);
                                     if (this.convertToRightHandedSystem && sideOrientation === BABYLON.Material.ClockWiseSideOrientation) {
                                         //Overwrite the indices to be counter-clockwise
                                         var byteOffset = indexBufferViewIndex != null ? this.bufferViews[indexBufferViewIndex].byteOffset : null;

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 2 - 2
dist/preview release/serializers/babylon.glTF2Serializer.min.js


+ 18 - 2
dist/preview release/serializers/babylonjs.serializers.js

@@ -848,6 +848,9 @@ var BABYLON;
              * @param babylonMesh The BabylonJS mesh
              */
             _Exporter.prototype.getMeshPrimitiveMode = function (babylonMesh) {
+                if (babylonMesh instanceof BABYLON.LinesMesh) {
+                    return BABYLON.Material.LineListDrawMode;
+                }
                 return babylonMesh.material ? babylonMesh.material.fillMode : BABYLON.Material.TriangleFillMode;
             };
             /**
@@ -991,7 +994,20 @@ var BABYLON;
                             var babylonMaterial = submesh.getMaterial();
                             var materialIndex = null;
                             if (babylonMaterial) {
-                                if (babylonMaterial instanceof BABYLON.MultiMaterial) {
+                                if (bufferMesh instanceof BABYLON.LinesMesh) {
+                                    // get the color from the lines mesh and set it in the material
+                                    var material = {
+                                        name: bufferMesh.name + ' material'
+                                    };
+                                    if (!bufferMesh.color.equals(BABYLON.Color3.White()) || bufferMesh.alpha < 1) {
+                                        material.pbrMetallicRoughness = {
+                                            baseColorFactor: bufferMesh.color.asArray().concat([bufferMesh.alpha])
+                                        };
+                                    }
+                                    this.materials.push(material);
+                                    materialIndex = this.materials.length - 1;
+                                }
+                                else if (babylonMaterial instanceof BABYLON.MultiMaterial) {
                                     babylonMaterial = babylonMaterial.subMaterials[submesh.materialIndex];
                                     if (babylonMaterial) {
                                         materialIndex = this.babylonScene.materials.indexOf(babylonMaterial);
@@ -1003,6 +1019,7 @@ var BABYLON;
                             }
                             var glTFMaterial = materialIndex != null ? this.materials[materialIndex] : null;
                             var meshPrimitive = { attributes: {} };
+                            this.setPrimitiveMode(meshPrimitive, primitiveMode);
                             for (var _c = 0, attributeData_2 = attributeData; _c < attributeData_2.length; _c++) {
                                 var attribute = attributeData_2[_c];
                                 var attributeKind = attribute.kind;
@@ -1041,7 +1058,6 @@ var BABYLON;
                             if (babylonMaterial) {
                                 if (materialIndex != null && Object.keys(meshPrimitive.attributes).length > 0) {
                                     var sideOrientation = this.babylonScene.materials[materialIndex].sideOrientation;
-                                    this.setPrimitiveMode(meshPrimitive, primitiveMode);
                                     if (this.convertToRightHandedSystem && sideOrientation === BABYLON.Material.ClockWiseSideOrientation) {
                                         //Overwrite the indices to be counter-clockwise
                                         var byteOffset = indexBufferViewIndex != null ? this.bufferViews[indexBufferViewIndex].byteOffset : null;

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 2 - 2
dist/preview release/serializers/babylonjs.serializers.min.js


+ 6 - 3
dist/preview release/viewer/babylon.viewer.d.ts

@@ -969,7 +969,7 @@ declare module BabylonViewer {
             skybox?: boolean | ISkyboxConfiguration;
             ground?: boolean | IGroundConfiguration;
             lights?: {
-                    [name: string]: boolean | ILightConfiguration;
+                    [name: string]: number | boolean | ILightConfiguration;
             };
             engine?: {
                     renderInBackground?: boolean;
@@ -1035,6 +1035,7 @@ declare module BabylonViewer {
                             tintLevel: number;
                     };
                     defaultRenderingPipelines?: boolean | IDefaultRenderingPipelineConfiguration;
+                    globalLightRotation?: number;
             };
     }
     /**
@@ -1751,7 +1752,7 @@ declare module BabylonViewer {
                 * Will notify after the lights were configured. Can be used to further configure lights
                 */
             onLightsConfiguredObservable: BABYLON.Observable<IPostConfigurationCallback<Array<BABYLON.Light>, {
-                    [name: string]: ILightConfiguration | boolean;
+                    [name: string]: ILightConfiguration | boolean | number;
             }>>;
             /**
                 * Will notify after the model(s) were configured. Can be used to further configure models
@@ -1866,7 +1867,7 @@ declare module BabylonViewer {
                 * @param model optionally use the model to configure the camera.
                 */
             protected _configureLights(lightsConfiguration?: {
-                    [name: string]: ILightConfiguration | boolean;
+                    [name: string]: ILightConfiguration | boolean | number;
             }): void;
             /**
                 * Gets the shadow map blur kernel according to the light configuration.
@@ -1951,6 +1952,7 @@ declare module BabylonViewer {
 declare module BabylonViewer {
     
     
+    
     /**
         * The ViewerLabs class will hold functions that are not (!) backwards compatible.
         * The APIs in all labs-related classes and configuration  might change.
@@ -1995,6 +1997,7 @@ declare module BabylonViewer {
                 * @returns The Asset url using the `environmentAssetsRootURL` if the url is not an absolute path.
                 */
             getAssetUrl(url: string): string;
+            rotateShadowLight(shadowLight: BABYLON.ShadowLight, amount: number, point?: BABYLON.Vector3, axis?: BABYLON.Vector3, target?: BABYLON.Vector3): void;
     }
 }
 

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 22 - 22
dist/preview release/viewer/babylon.viewer.js


+ 283 - 80
dist/preview release/viewer/babylon.viewer.max.js

@@ -1468,6 +1468,10 @@ var BABYLON;
         __extends(PointerInfoPre, _super);
         function PointerInfoPre(type, event, localX, localY) {
             var _this = _super.call(this, type, event) || this;
+            /**
+             * Ray from a pointer if availible (eg. 6dof controller)
+             */
+            _this.ray = null;
             _this.skipOnPointerObservable = false;
             _this.localPosition = new BABYLON.Vector2(localX, localY);
             return _this;
@@ -25139,6 +25143,9 @@ var BABYLON;
          */
         Scene.prototype.simulatePointerMove = function (pickResult, pointerEventInit) {
             var evt = new PointerEvent("pointermove", pointerEventInit);
+            if (this._checkPrePointerObservable(pickResult, evt, BABYLON.PointerEventTypes.POINTERMOVE)) {
+                return this;
+            }
             return this._processPointerMove(pickResult, evt);
         };
         Scene.prototype._processPointerMove = function (pickResult, evt) {
@@ -25193,6 +25200,19 @@ var BABYLON;
             }
             return this;
         };
+        Scene.prototype._checkPrePointerObservable = function (pickResult, evt, type) {
+            var pi = new BABYLON.PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
+            if (pickResult) {
+                pi.ray = pickResult.ray;
+            }
+            this.onPrePointerObservable.notifyObservers(pi, type);
+            if (pi.skipOnPointerObservable) {
+                return true;
+            }
+            else {
+                return false;
+            }
+        };
         /**
          * Use this method to simulate a pointer down on a mesh
          * The pickResult parameter can be obtained from a scene.pick or scene.pickWithRay
@@ -25202,6 +25222,9 @@ var BABYLON;
          */
         Scene.prototype.simulatePointerDown = function (pickResult, pointerEventInit) {
             var evt = new PointerEvent("pointerdown", pointerEventInit);
+            if (this._checkPrePointerObservable(pickResult, evt, BABYLON.PointerEventTypes.POINTERDOWN)) {
+                return this;
+            }
             return this._processPointerDown(pickResult, evt);
         };
         Scene.prototype._processPointerDown = function (pickResult, evt) {
@@ -25265,6 +25288,9 @@ var BABYLON;
             var clickInfo = new ClickInfo();
             clickInfo.singleClick = true;
             clickInfo.ignore = true;
+            if (this._checkPrePointerObservable(pickResult, evt, BABYLON.PointerEventTypes.POINTERUP)) {
+                return this;
+            }
             return this._processPointerUp(pickResult, evt, clickInfo);
         };
         Scene.prototype._processPointerUp = function (pickResult, evt, clickInfo) {
@@ -25475,13 +25501,8 @@ var BABYLON;
             this._onPointerMove = function (evt) {
                 _this._updatePointerPosition(evt);
                 // PreObservable support
-                if (_this.onPrePointerObservable.hasObservers() && !_this._pointerCaptures[evt.pointerId]) {
-                    var type = evt.type === "mousewheel" || evt.type === "DOMMouseScroll" ? BABYLON.PointerEventTypes.POINTERWHEEL : BABYLON.PointerEventTypes.POINTERMOVE;
-                    var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                    _this.onPrePointerObservable.notifyObservers(pi, type);
-                    if (pi.skipOnPointerObservable) {
-                        return;
-                    }
+                if (_this._checkPrePointerObservable(null, evt, evt.type === "mousewheel" || evt.type === "DOMMouseScroll" ? BABYLON.PointerEventTypes.POINTERWHEEL : BABYLON.PointerEventTypes.POINTERMOVE)) {
+                    return;
                 }
                 if (!_this.cameraToUseForPointers && !_this.activeCamera) {
                     return;
@@ -25503,13 +25524,8 @@ var BABYLON;
                     canvas.focus();
                 }
                 // PreObservable support
-                if (_this.onPrePointerObservable.hasObservers()) {
-                    var type = BABYLON.PointerEventTypes.POINTERDOWN;
-                    var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                    _this.onPrePointerObservable.notifyObservers(pi, type);
-                    if (pi.skipOnPointerObservable) {
-                        return;
-                    }
+                if (_this._checkPrePointerObservable(null, evt, BABYLON.PointerEventTypes.POINTERDOWN)) {
+                    return;
                 }
                 if (!_this.cameraToUseForPointers && !_this.activeCamera) {
                     return;
@@ -25566,28 +25582,19 @@ var BABYLON;
                         if (!clickInfo.ignore) {
                             if (!clickInfo.hasSwiped) {
                                 if (clickInfo.singleClick && _this.onPrePointerObservable.hasSpecificMask(BABYLON.PointerEventTypes.POINTERTAP)) {
-                                    var type = BABYLON.PointerEventTypes.POINTERTAP;
-                                    var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                                    _this.onPrePointerObservable.notifyObservers(pi, type);
-                                    if (pi.skipOnPointerObservable) {
+                                    if (_this._checkPrePointerObservable(null, evt, BABYLON.PointerEventTypes.POINTERTAP)) {
                                         return;
                                     }
                                 }
                                 if (clickInfo.doubleClick && _this.onPrePointerObservable.hasSpecificMask(BABYLON.PointerEventTypes.POINTERDOUBLETAP)) {
-                                    var type = BABYLON.PointerEventTypes.POINTERDOUBLETAP;
-                                    var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                                    _this.onPrePointerObservable.notifyObservers(pi, type);
-                                    if (pi.skipOnPointerObservable) {
+                                    if (_this._checkPrePointerObservable(null, evt, BABYLON.PointerEventTypes.POINTERDOUBLETAP)) {
                                         return;
                                     }
                                 }
                             }
                         }
                         else {
-                            var type = BABYLON.PointerEventTypes.POINTERUP;
-                            var pi = new BABYLON.PointerInfoPre(type, evt, _this._unTranslatedPointerX, _this._unTranslatedPointerY);
-                            _this.onPrePointerObservable.notifyObservers(pi, type);
-                            if (pi.skipOnPointerObservable) {
+                            if (_this._checkPrePointerObservable(null, evt, BABYLON.PointerEventTypes.POINTERUP)) {
                                 return;
                             }
                         }
@@ -27827,8 +27834,10 @@ var BABYLON;
         };
         /**
          * Render the scene
+         * @param updateCameras defines a boolean indicating if cameras must update according to their inputs (true by default)
          */
-        Scene.prototype.render = function () {
+        Scene.prototype.render = function (updateCameras) {
+            if (updateCameras === void 0) { updateCameras = true; }
             if (this.isDisposed) {
                 return;
             }
@@ -27895,24 +27904,26 @@ var BABYLON;
                 this._gamepadManager._checkGamepadsStatus();
             }
             // Update Cameras
-            if (this.activeCameras.length > 0) {
-                for (var cameraIndex = 0; cameraIndex < this.activeCameras.length; cameraIndex++) {
-                    var camera = this.activeCameras[cameraIndex];
-                    camera.update();
-                    if (camera.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
-                        // rig cameras
-                        for (var index = 0; index < camera._rigCameras.length; index++) {
-                            camera._rigCameras[index].update();
+            if (updateCameras) {
+                if (this.activeCameras.length > 0) {
+                    for (var cameraIndex = 0; cameraIndex < this.activeCameras.length; cameraIndex++) {
+                        var camera = this.activeCameras[cameraIndex];
+                        camera.update();
+                        if (camera.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
+                            // rig cameras
+                            for (var index = 0; index < camera._rigCameras.length; index++) {
+                                camera._rigCameras[index].update();
+                            }
                         }
                     }
                 }
-            }
-            else if (this.activeCamera) {
-                this.activeCamera.update();
-                if (this.activeCamera.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
-                    // rig cameras
-                    for (var index = 0; index < this.activeCamera._rigCameras.length; index++) {
-                        this.activeCamera._rigCameras[index].update();
+                else if (this.activeCamera) {
+                    this.activeCamera.update();
+                    if (this.activeCamera.cameraRigMode !== BABYLON.Camera.RIG_MODE_NONE) {
+                        // rig cameras
+                        for (var index = 0; index < this.activeCamera._rigCameras.length; index++) {
+                            this.activeCamera._rigCameras[index].update();
+                        }
                     }
                 }
             }
@@ -86891,7 +86902,6 @@ var BABYLON;
             this.utilityLayerScene = new BABYLON.Scene(originalScene.getEngine());
             originalScene.getEngine().scenes.pop();
             // Render directly on top of existing scene without clearing
-            this.utilityLayerScene.clearColor = new BABYLON.Color4(0, 0, 0, 0);
             this.utilityLayerScene.autoClear = false;
             this._afterRenderObserver = this.originalScene.onAfterRenderObservable.add(function () {
                 if (_this.shouldRender) {
@@ -86907,7 +86917,7 @@ var BABYLON;
          */
         UtilityLayerRenderer.prototype.render = function () {
             this._updateCamera();
-            this.utilityLayerScene.render();
+            this.utilityLayerScene.render(false);
         };
         /**
          * Disposes of the renderer
@@ -86934,6 +86944,148 @@ var BABYLON;
 var BABYLON;
 (function (BABYLON) {
     /**
+     * Renders gizmos on top of an existing scene which provide controls for position, rotation, etc.
+     */
+    var Gizmo = /** @class */ (function () {
+        /**
+         * Creates a gizmo
+         * @param gizmoLayer The utility layer the gizmo will be added to
+         */
+        function Gizmo(/** The utility layer the gizmo will be added to */ gizmoLayer) {
+            var _this = this;
+            this.gizmoLayer = gizmoLayer;
+            this._rootMesh = new BABYLON.Mesh("gizmoRootNode", gizmoLayer.utilityLayerScene);
+            this._beforeRenderObserver = this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.add(function () {
+                if (_this.gizmoLayer.utilityLayerScene.activeCamera && _this.attachedMesh) {
+                    var dist = _this.attachedMesh.position.subtract(_this.gizmoLayer.utilityLayerScene.activeCamera.position).length() / 5;
+                    _this._rootMesh.scaling.set(dist, dist, dist);
+                }
+                if (_this.attachedMesh) {
+                    _this._rootMesh.position.copyFrom(_this.attachedMesh.position);
+                }
+            });
+        }
+        /**
+         * Disposes of the gizmo
+         */
+        Gizmo.prototype.dispose = function () {
+            this._rootMesh.dispose();
+            if (this._beforeRenderObserver) {
+                this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.remove(this._beforeRenderObserver);
+            }
+        };
+        return Gizmo;
+    }());
+    BABYLON.Gizmo = Gizmo;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.gizmo.js.map
+
+
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Single axis drag gizmo
+     */
+    var AxisDragGizmo = /** @class */ (function (_super) {
+        __extends(AxisDragGizmo, _super);
+        /**
+         * 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
+         * @param color The color of the gizmo
+         */
+        function AxisDragGizmo(gizmoLayer, dragAxis, color) {
+            var _this = _super.call(this, gizmoLayer) || this;
+            // Create Material
+            var coloredMaterial = new BABYLON.StandardMaterial("", gizmoLayer.utilityLayerScene);
+            coloredMaterial.disableLighting = true;
+            coloredMaterial.emissiveColor = color;
+            // Build mesh on root node
+            var arrowMesh = BABYLON.MeshBuilder.CreateCylinder("yPosMesh", { diameterTop: 0, height: 2, tessellation: 96 }, gizmoLayer.utilityLayerScene);
+            var arrowTail = BABYLON.MeshBuilder.CreateCylinder("yPosMesh", { diameter: 0.03, height: 0.2, tessellation: 96 }, gizmoLayer.utilityLayerScene);
+            _this._rootMesh.addChild(arrowMesh);
+            _this._rootMesh.addChild(arrowTail);
+            // Position arrow pointing in its drag axis
+            arrowMesh.scaling.scaleInPlace(0.1);
+            arrowMesh.material = coloredMaterial;
+            arrowMesh.rotation.x = Math.PI / 2;
+            arrowMesh.position.z += 0.3;
+            arrowTail.rotation.x = Math.PI / 2;
+            arrowTail.material = coloredMaterial;
+            arrowTail.position.z += 0.2;
+            _this._rootMesh.lookAt(_this._rootMesh.position.subtract(dragAxis));
+            // Add drag behavior to handle events when the gizmo is dragged
+            _this._dragBehavior = new BABYLON.PointerDragBehavior({ dragAxis: dragAxis, pointerObservableScene: gizmoLayer.originalScene });
+            _this._dragBehavior.moveAttached = false;
+            _this._rootMesh.addBehavior(_this._dragBehavior);
+            _this._dragBehavior.onDragObservable.add(function (event) {
+                if (_this.attachedMesh) {
+                    _this.attachedMesh.position.addInPlace(event.delta);
+                }
+            });
+            return _this;
+        }
+        /**
+         * Disposes of the gizmo
+         */
+        AxisDragGizmo.prototype.dispose = function () {
+            this._dragBehavior.detach();
+            _super.prototype.dispose.call(this);
+        };
+        return AxisDragGizmo;
+    }(BABYLON.Gizmo));
+    BABYLON.AxisDragGizmo = AxisDragGizmo;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.axisDragGizmo.js.map
+
+
+var BABYLON;
+(function (BABYLON) {
+    /**
+     * Gizmo that enables dragging a mesh along 3 axis
+     */
+    var PositionGizmo = /** @class */ (function (_super) {
+        __extends(PositionGizmo, _super);
+        /**
+         * Creates a PositionGizmo
+         * @param gizmoLayer The utility layer the gizmo will be added to
+         */
+        function PositionGizmo(gizmoLayer) {
+            var _this = _super.call(this, gizmoLayer) || this;
+            _this._xDrag = new BABYLON.AxisDragGizmo(gizmoLayer, new BABYLON.Vector3(1, 0, 0), BABYLON.Color3.FromHexString("#00b894"));
+            _this._yDrag = new BABYLON.AxisDragGizmo(gizmoLayer, new BABYLON.Vector3(0, 1, 0), BABYLON.Color3.FromHexString("#d63031"));
+            _this._zDrag = new BABYLON.AxisDragGizmo(gizmoLayer, new BABYLON.Vector3(0, 0, 1), BABYLON.Color3.FromHexString("#0984e3"));
+            return _this;
+        }
+        Object.defineProperty(PositionGizmo.prototype, "attachedMesh", {
+            set: function (mesh) {
+                this._xDrag.attachedMesh = mesh;
+                this._yDrag.attachedMesh = mesh;
+                this._zDrag.attachedMesh = mesh;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        /**
+         * Disposes of the gizmo
+         */
+        PositionGizmo.prototype.dispose = function () {
+            this._xDrag.dispose();
+            this._yDrag.dispose();
+            this._zDrag.dispose();
+        };
+        return PositionGizmo;
+    }(BABYLON.Gizmo));
+    BABYLON.PositionGizmo = PositionGizmo;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.positionGizmo.js.map
+
+var BABYLON;
+(function (BABYLON) {
+    /**
      * Defines a target to use with MorphTargetManager
      * @see http://doc.babylonjs.com/how_to/how_to_use_morphtargets
      */
@@ -96444,7 +96596,7 @@ var BABYLON;
     var PointerDragBehavior = /** @class */ (function () {
         /**
          * Creates a pointer drag behavior that can be attached to a mesh
-         * @param options The drag axis or normal of the plane that will be dragged accross
+         * @param options The drag axis or normal of the plane that will be dragged across. pointerObservableScene can be used to listen to drag events from another scene(eg. if the attached mesh is in an overlay scene).
          */
         function PointerDragBehavior(options) {
             this.options = options;
@@ -96504,6 +96656,9 @@ var BABYLON;
         PointerDragBehavior.prototype.attach = function (ownerNode) {
             var _this = this;
             this._scene = ownerNode.getScene();
+            if (!this.options.pointerObservableScene) {
+                this.options.pointerObservableScene = this._scene;
+            }
             this._attachedNode = ownerNode;
             // Initialize drag plane to not interfere with existing scene
             if (!PointerDragBehavior._planeScene) {
@@ -96515,32 +96670,44 @@ var BABYLON;
             var dragging = false;
             var lastPosition = new BABYLON.Vector3(0, 0, 0);
             var delta = new BABYLON.Vector3(0, 0, 0);
-            this._pointerObserver = this._scene.onPointerObservable.add(function (pointerInfo) {
-                if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN) {
-                    if (!dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray) {
-                        if (_this._attachedNode == pointerInfo.pickInfo.pickedMesh) {
-                            _this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
-                            var pickedPoint = _this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray);
-                            if (pickedPoint) {
-                                dragging = true;
-                                _this._draggingID = pointerInfo.event.pointerId;
-                                lastPosition.copyFrom(pickedPoint);
-                                _this.onDragStartObservable.notifyObservers({ dragPlanePoint: pickedPoint });
-                            }
+            var pickPredicate = function (m) {
+                return _this._attachedNode == m || m.isDescendantOf(_this._attachedNode);
+            };
+            this._pointerObserver = this.options.pointerObservableScene.onPrePointerObservable.add(function (pointerInfoPre, eventState) {
+                // Check if attached mesh is picked
+                var pickInfo = pointerInfoPre.ray ? _this._scene.pickWithRay(pointerInfoPre.ray, pickPredicate) : _this._scene.pick(_this._scene.pointerX, _this._scene.pointerY, pickPredicate);
+                if (pickInfo) {
+                    pickInfo.ray = pointerInfoPre.ray;
+                    if (!pickInfo.ray) {
+                        pickInfo.ray = _this.options.pointerObservableScene.createPickingRay(_this._scene.pointerX, _this._scene.pointerY, BABYLON.Matrix.Identity(), _this._scene.activeCamera);
+                    }
+                    if (pickInfo.hit) {
+                        eventState.skipNextObservers = true;
+                    }
+                }
+                if (pointerInfoPre.type == BABYLON.PointerEventTypes.POINTERDOWN) {
+                    if (!dragging && pickInfo && pickInfo.hit && pickInfo.pickedMesh && pickInfo.ray) {
+                        _this._updateDragPlanePosition(pickInfo.ray);
+                        var pickedPoint = _this._pickWithRayOnDragPlane(pickInfo.ray);
+                        if (pickedPoint) {
+                            dragging = true;
+                            _this._draggingID = pointerInfoPre.event.pointerId;
+                            lastPosition.copyFrom(pickedPoint);
+                            _this.onDragStartObservable.notifyObservers({ dragPlanePoint: pickedPoint });
                         }
                     }
                 }
-                else if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERUP) {
-                    if (_this._draggingID == pointerInfo.event.pointerId) {
+                else if (pointerInfoPre.type == BABYLON.PointerEventTypes.POINTERUP) {
+                    if (_this._draggingID == pointerInfoPre.event.pointerId) {
                         dragging = false;
                         _this._draggingID = -1;
                         _this.onDragEndObservable.notifyObservers({ dragPlanePoint: lastPosition });
                     }
                 }
-                else if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE) {
-                    if (_this._draggingID == pointerInfo.event.pointerId && dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray) {
-                        var pickedPoint = _this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray);
-                        _this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
+                else if (pointerInfoPre.type == BABYLON.PointerEventTypes.POINTERMOVE) {
+                    if (_this._draggingID == pointerInfoPre.event.pointerId && dragging && pickInfo && pickInfo.ray) {
+                        var pickedPoint = _this._pickWithRayOnDragPlane(pickInfo.ray);
+                        _this._updateDragPlanePosition(pickInfo.ray);
                         if (pickedPoint) {
                             // depending on the drag mode option drag accordingly
                             if (_this.options.dragAxis) {
@@ -96602,7 +96769,7 @@ var BABYLON;
          */
         PointerDragBehavior.prototype.detach = function () {
             if (this._pointerObserver) {
-                this._scene.onPointerObservable.remove(this._pointerObserver);
+                this._scene.onPrePointerObservable.remove(this._pointerObserver);
             }
         };
         return PointerDragBehavior;
@@ -108622,6 +108789,7 @@ var SceneManager = /** @class */ (function () {
         this._mainColor = babylonjs_1.Color3.White();
         this._reflectionColor = babylonjs_1.Color3.White();
         this._white = babylonjs_1.Color3.White();
+        this._forceShadowUpdate = false;
         this._processShadows = true;
         this._groundEnabled = true;
         this._groundMirrorEnabled = true;
@@ -108640,6 +108808,10 @@ var SceneManager = /** @class */ (function () {
             _this.camera.alpha = (_this._viewer.configuration.camera && _this._viewer.configuration.camera.alpha) || _this.camera.alpha;
             _this.camera.beta = (_this._viewer.configuration.camera && _this._viewer.configuration.camera.beta) || _this.camera.beta;
             _this.camera.radius = (_this._viewer.configuration.camera && _this._viewer.configuration.camera.radius) || _this.camera.radius;
+            /*this.scene.lights.filter(light => light instanceof ShadowLight).forEach(light => {
+                // casting ais safe, due to the constraints tested before
+                (<ShadowLight>light).setDirectionToTarget(center);
+            });*/
         };
         this._cameraBehaviorMapping = {};
         this.models = [];
@@ -108669,9 +108841,10 @@ var SceneManager = /** @class */ (function () {
                 }
             };
             scene.registerBeforeRender(function () {
-                if (scene.animatables && scene.animatables.length > 0) {
+                if (_this._forceShadowUpdate || (scene.animatables && scene.animatables.length > 0)) {
                     // make sure all models are loaded
                     updateShadows();
+                    _this._forceShadowUpdate = false;
                 }
                 else if (!(_this.models.every(function (model) {
                     if (!model.shadowsRenderedAfterLoad) {
@@ -108958,6 +109131,14 @@ var SceneManager = /** @class */ (function () {
                 var mainColor = new babylonjs_1.Color3().copyFrom(newConfiguration.lab.environmentMainColor);
                 this.environmentHelper.setMainColor(mainColor);
             }
+            if (newConfiguration.lab.globalLightRotation !== undefined) {
+                // rotate all lights that are shadow lights
+                this.scene.lights.filter(function (light) { return light instanceof babylonjs_1.ShadowLight; }).forEach(function (light) {
+                    // casting and '!' are safe, due to the constraints tested before
+                    _this.labs.rotateShadowLight(light, newConfiguration.lab.globalLightRotation);
+                });
+                this._forceShadowUpdate = true;
+            }
         }
         if (this._defaultRenderingPipeline && this._defaultRenderingPipeline.imageProcessing) {
             this._defaultRenderingPipeline.imageProcessing.fromLinearSpace = true;
@@ -109282,7 +109463,7 @@ var SceneManager = /** @class */ (function () {
         if (this.scene.imageProcessingConfiguration) {
             this.scene.imageProcessingConfiguration.colorCurvesEnabled = true;
             this.scene.imageProcessingConfiguration.vignetteEnabled = true;
-            this.scene.imageProcessingConfiguration.toneMappingEnabled = !!cameraConfig.toneMappingEnabled;
+            this.scene.imageProcessingConfiguration.toneMappingEnabled = !!configuration_1.getConfigurationKey("camera.toneMappingEnabled", this._viewer.configuration);
         }
         helper_1.extendClassWithConfig(this.camera, cameraConfig);
         this.onCameraConfiguredObservable.notifyObservers({
@@ -109452,7 +109633,8 @@ var SceneManager = /** @class */ (function () {
         var _this = this;
         if (lightsConfiguration === void 0) { lightsConfiguration = {}; }
         // sanity check!
-        if (!Object.keys(lightsConfiguration).length) {
+        var lightKeys = Object.keys(lightsConfiguration).filter(function (name) { return name !== 'globalRotation'; });
+        if (!lightKeys.length) {
             if (!this.scene.lights.length)
                 this.scene.createDefaultLight(true);
         }
@@ -109467,11 +109649,14 @@ var SceneManager = /** @class */ (function () {
                     }
                 });
             }
-            Object.keys(lightsConfiguration).forEach(function (name, idx) {
+            lightKeys.forEach(function (name, idx) {
                 var lightConfig = { type: 0 };
                 if (typeof lightsConfiguration[name] === 'object') {
                     lightConfig = lightsConfiguration[name];
                 }
+                if (typeof lightsConfiguration[name] === 'number') {
+                    lightConfig.type = lightsConfiguration[name];
+                }
                 lightConfig.name = name;
                 var light;
                 // light is not already available
@@ -109484,6 +109669,9 @@ var SceneManager = /** @class */ (function () {
                 else {
                     // available? get it from the scene
                     light = _this.scene.getLightByName(name);
+                    if (typeof lightsConfiguration[name] === 'boolean') {
+                        lightConfig.type = light.getTypeID();
+                    }
                     lightsAvailable_1 = lightsAvailable_1.filter(function (ln) { return ln !== name; });
                     if (lightConfig.type !== undefined && light.getTypeID() !== lightConfig.type) {
                         light.dispose();
@@ -110043,15 +110231,15 @@ var ViewerLabs = /** @class */ (function () {
         this.environment = {
             //irradiance
             irradiancePolynomialCoefficients: {
-                x: new BABYLON.Vector3(0, 0, 0),
-                y: new BABYLON.Vector3(0, 0, 0),
-                z: new BABYLON.Vector3(0, 0, 0),
-                xx: new BABYLON.Vector3(0, 0, 0),
-                yy: new BABYLON.Vector3(0, 0, 0),
-                zz: new BABYLON.Vector3(0, 0, 0),
-                yz: new BABYLON.Vector3(0, 0, 0),
-                zx: new BABYLON.Vector3(0, 0, 0),
-                xy: new BABYLON.Vector3(0, 0, 0)
+                x: new babylonjs_1.Vector3(0, 0, 0),
+                y: new babylonjs_1.Vector3(0, 0, 0),
+                z: new babylonjs_1.Vector3(0, 0, 0),
+                xx: new babylonjs_1.Vector3(0, 0, 0),
+                yy: new babylonjs_1.Vector3(0, 0, 0),
+                zz: new babylonjs_1.Vector3(0, 0, 0),
+                yz: new babylonjs_1.Vector3(0, 0, 0),
+                zx: new babylonjs_1.Vector3(0, 0, 0),
+                xy: new babylonjs_1.Vector3(0, 0, 0)
             },
             textureIntensityScale: 1.0
         };
@@ -110092,7 +110280,7 @@ var ViewerLabs = /** @class */ (function () {
         if (!this.environment)
             return;
         //set orientation
-        var rotatquatRotationionY = babylonjs_1.Quaternion.RotationAxis(BABYLON.Axis.Y, rotationY || 0);
+        var rotatquatRotationionY = babylonjs_1.Quaternion.RotationAxis(babylonjs_1.Axis.Y, rotationY || 0);
         // Add env texture to the scene.
         if (this.environment.specularTexture) {
             // IE crashes when disposing the old texture and setting a new one
@@ -110103,7 +110291,7 @@ var ViewerLabs = /** @class */ (function () {
                 this._sceneManager.scene.environmentTexture.level = this.environment.textureIntensityScale;
                 this._sceneManager.scene.environmentTexture.invertZ = true;
                 this._sceneManager.scene.environmentTexture.lodLevelInAlpha = true;
-                var poly = this._sceneManager.scene.environmentTexture.sphericalPolynomial || new BABYLON.SphericalPolynomial();
+                var poly = this._sceneManager.scene.environmentTexture.sphericalPolynomial || new babylonjs_1.SphericalPolynomial();
                 poly.x = this.environment.irradiancePolynomialCoefficients.x;
                 poly.y = this.environment.irradiancePolynomialCoefficients.y;
                 poly.z = this.environment.irradiancePolynomialCoefficients.z;
@@ -110115,7 +110303,7 @@ var ViewerLabs = /** @class */ (function () {
                 poly.zz = this.environment.irradiancePolynomialCoefficients.zz;
                 this._sceneManager.scene.environmentTexture.sphericalPolynomial = poly;
                 //set orientation
-                BABYLON.Matrix.FromQuaternionToRef(rotatquatRotationionY, this._sceneManager.scene.environmentTexture.getReflectionTextureMatrix());
+                babylonjs_1.Matrix.FromQuaternionToRef(rotatquatRotationionY, this._sceneManager.scene.environmentTexture.getReflectionTextureMatrix());
             }
         }
     };
@@ -110135,6 +110323,21 @@ var ViewerLabs = /** @class */ (function () {
         }
         return returnUrl;
     };
+    ViewerLabs.prototype.rotateShadowLight = function (shadowLight, amount, point, axis, target) {
+        if (point === void 0) { point = babylonjs_1.Vector3.Zero(); }
+        if (axis === void 0) { axis = babylonjs_1.Axis.Y; }
+        if (target === void 0) { target = babylonjs_1.Vector3.Zero(); }
+        axis.normalize();
+        point.subtractToRef(shadowLight.position, babylonjs_1.Tmp.Vector3[0]);
+        babylonjs_1.Matrix.TranslationToRef(babylonjs_1.Tmp.Vector3[0].x, babylonjs_1.Tmp.Vector3[0].y, babylonjs_1.Tmp.Vector3[0].z, babylonjs_1.Tmp.Matrix[0]);
+        babylonjs_1.Tmp.Matrix[0].invertToRef(babylonjs_1.Tmp.Matrix[2]);
+        babylonjs_1.Matrix.RotationAxisToRef(axis, amount, babylonjs_1.Tmp.Matrix[1]);
+        babylonjs_1.Tmp.Matrix[2].multiplyToRef(babylonjs_1.Tmp.Matrix[1], babylonjs_1.Tmp.Matrix[2]);
+        babylonjs_1.Tmp.Matrix[2].multiplyToRef(babylonjs_1.Tmp.Matrix[0], babylonjs_1.Tmp.Matrix[2]);
+        babylonjs_1.Tmp.Matrix[2].decompose(babylonjs_1.Tmp.Vector3[0], babylonjs_1.Tmp.Quaternion[0], babylonjs_1.Tmp.Vector3[1]);
+        shadowLight.position.addInPlace(babylonjs_1.Tmp.Vector3[1]);
+        shadowLight.setDirectionToTarget(target);
+    };
     return ViewerLabs;
 }());
 exports.ViewerLabs = ViewerLabs;

+ 6 - 3
dist/preview release/viewer/babylon.viewer.module.d.ts

@@ -969,7 +969,7 @@ declare module 'babylonjs-viewer/configuration/configuration' {
             skybox?: boolean | ISkyboxConfiguration;
             ground?: boolean | IGroundConfiguration;
             lights?: {
-                    [name: string]: boolean | ILightConfiguration;
+                    [name: string]: number | boolean | ILightConfiguration;
             };
             engine?: {
                     renderInBackground?: boolean;
@@ -1035,6 +1035,7 @@ declare module 'babylonjs-viewer/configuration/configuration' {
                             tintLevel: number;
                     };
                     defaultRenderingPipelines?: boolean | IDefaultRenderingPipelineConfiguration;
+                    globalLightRotation?: number;
             };
     }
     /**
@@ -1751,7 +1752,7 @@ declare module 'babylonjs-viewer/viewer/sceneManager' {
                 * Will notify after the lights were configured. Can be used to further configure lights
                 */
             onLightsConfiguredObservable: Observable<IPostConfigurationCallback<Array<Light>, {
-                    [name: string]: ILightConfiguration | boolean;
+                    [name: string]: ILightConfiguration | boolean | number;
             }>>;
             /**
                 * Will notify after the model(s) were configured. Can be used to further configure models
@@ -1866,7 +1867,7 @@ declare module 'babylonjs-viewer/viewer/sceneManager' {
                 * @param model optionally use the model to configure the camera.
                 */
             protected _configureLights(lightsConfiguration?: {
-                    [name: string]: ILightConfiguration | boolean;
+                    [name: string]: ILightConfiguration | boolean | number;
             }): void;
             /**
                 * Gets the shadow map blur kernel according to the light configuration.
@@ -1980,6 +1981,7 @@ declare module 'babylonjs-viewer/eventManager' {
 declare module 'babylonjs-viewer/labs/viewerLabs' {
     import { PBREnvironment } from "babylonjs-viewer/labs/environmentSerializer";
     import { SceneManager } from 'babylonjs-viewer/viewer/sceneManager';
+    import { ShadowLight, Vector3 } from 'babylonjs';
     /**
         * The ViewerLabs class will hold functions that are not (!) backwards compatible.
         * The APIs in all labs-related classes and configuration  might change.
@@ -2024,6 +2026,7 @@ declare module 'babylonjs-viewer/labs/viewerLabs' {
                 * @returns The Asset url using the `environmentAssetsRootURL` if the url is not an absolute path.
                 */
             getAssetUrl(url: string): string;
+            rotateShadowLight(shadowLight: ShadowLight, amount: number, point?: Vector3, axis?: Vector3, target?: Vector3): void;
     }
 }
 

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

@@ -3,6 +3,7 @@
 ## Major updates
 
 - Added a ParticleHelper class to create some pre-configured particle systems in a one-liner method style ([DevChris](https://github.com/yovanoc))
+- Added new `MixMaterial` to the Materials Library allowing to mix up to 8 textures ([julien-moreau](https://github.com/julien-moreau))
 
 ## Updates
 
@@ -21,6 +22,7 @@
 - UtilityLayer class to render another scene as a layer on top of an existing scene ([TrevorDev](https://github.com/TrevorDev))
 - AnimationGroup has now onAnimationGroupEnd observable ([RaananW](https://github.com/RaananW))
 - Pointer drag behavior to enable drag and drop with mouse or 6dof controller on a mesh ([TrevorDev](https://github.com/TrevorDev))
+- Gizmo class used to manipulate meshes in a scene, position gizmo ([TrevorDev](https://github.com/TrevorDev))
 
 ### glTF Loader
 
@@ -35,6 +37,7 @@
 - Viewer configuration supports deprecated values using the new configurationCompatibility processor  ([RaananW](https://github.com/RaananW))
 - Shadows will only render while models are entering the scene or animating ([RaananW](https://github.com/RaananW))
 - Support for model drag and drop onto the canvas ([RaananW](https://github.com/RaananW))
+- New lab feature - global light rotation [#4347](https://github.com/BabylonJS/Babylon.js/issues/4347) ([RaananW](https://github.com/RaananW))
 
 ## Bug fixes
 - VR experience helper will now fire pointer events even when no mesh is currently hit ([TrevorDev](https://github.com/TrevorDev))

+ 3 - 3
gui/src/3D/gui3DManager.ts

@@ -62,7 +62,7 @@ module BABYLON.GUI {
                     return;
                 }
 
-                pi.skipOnPointerObservable = this._doPicking(pi.type, pointerEvent)
+                pi.skipOnPointerObservable = this._doPicking(pi.type, pointerEvent, pi.ray)
             });
 
             // Scene
@@ -71,7 +71,7 @@ module BABYLON.GUI {
             new BABYLON.HemisphericLight("hemi", Vector3.Up(), this._utilityLayer.utilityLayerScene);
         }
 
-        private _doPicking(type: number, pointerEvent: PointerEvent): boolean {
+        private _doPicking(type: number, pointerEvent: PointerEvent, ray?:Nullable<Ray>): boolean {
             if (!this._utilityLayer || !this._utilityLayer.utilityLayerScene.activeCamera) {
                 return false;                
             }
@@ -80,7 +80,7 @@ module BABYLON.GUI {
             let buttonIndex = pointerEvent.button;
             var utilityScene = this._utilityLayer.utilityLayerScene;
 
-            let pickingInfo = utilityScene.pick(this._scene.pointerX, this._scene.pointerY);
+            let pickingInfo = ray ? utilityScene.pickWithRay(ray): utilityScene.pick(this._scene.pointerX, this._scene.pointerY);
             if (!pickingInfo || !pickingInfo.hit) {
                 var previousControlOver = this._lastControlOver[pointerId];
                 if (previousControlOver) {

+ 7 - 1
materialsLibrary/index.html

@@ -53,6 +53,7 @@
 	<script src="test/addpbrspecularglossiness.js"></script>
 	<script src="test/addCell.js"></script>
 	<script src="test/addbackground.js"></script>
+	<script src="test/addMix.js"></script>
 	
 	<script>
 	var backgroundSkybox = null;
@@ -215,13 +216,15 @@
 				var shadowOnly = new BABYLON.ShadowOnlyMaterial();
 
 				var cell = prepareCell();
+
+				var mix = prepareMix();
 				
 				// Default to std
 				var currentMaterial = std;
 				sphere.material = std;				
 				sphere.receiveShadows = true;
 
-				gui.add(options, 'material', ['standard', 'simple', 'water', 'fire', 'lava', 'normal', 'terrain', 'pbr', 'pbrmetallicroughness', 'pbrspecularglossiness', 'fur', 'triPlanar', 'gradient', 'sky', 'grid', 'shadowOnly', 'cell', 'background']).onFinishChange(function () {
+				gui.add(options, 'material', ['standard', 'simple', 'water', 'fire', 'lava', 'normal', 'terrain', 'pbr', 'pbrmetallicroughness', 'pbrspecularglossiness', 'fur', 'triPlanar', 'gradient', 'sky', 'grid', 'shadowOnly', 'cell', 'background', 'mix']).onFinishChange(function () {
 					water.enableRenderTargets(false);
 					skybox.material = skyboxMaterial;
 					currentMesh.isVisible = true;
@@ -287,6 +290,9 @@
 							currentMaterial = back;
 							backgroundSkybox.setEnabled(true);
 							break;
+						case "mix":
+							currentMaterial = mix;
+							break;
 						default:
 							currentMaterial = std;
 							break;

+ 517 - 0
materialsLibrary/src/mix/babylon.mixMaterial.ts

@@ -0,0 +1,517 @@
+/// <reference path="../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON {
+    class MixMaterialDefines extends MaterialDefines {
+        public DIFFUSE = false;
+        public CLIPPLANE = false;
+        public ALPHATEST = false;
+        public DEPTHPREPASS = false;
+        public POINTSIZE = false;
+        public FOG = false;
+        public SPECULARTERM = false;
+        public NORMAL = false;
+        public UV1 = false;
+        public UV2 = false;
+        public VERTEXCOLOR = false;
+        public VERTEXALPHA = false;
+        public NUM_BONE_INFLUENCERS = 0;
+        public BonesPerMesh = 0;
+        public INSTANCES = false;
+        public MIXMAP2 = false;
+
+        constructor() {
+            super();
+            this.rebuild();
+        }
+    }
+
+    export class MixMaterial extends PushMaterial {
+        /**
+         * Mix textures
+         */
+
+        @serializeAsTexture("mixTexture1")
+        private _mixTexture1: BaseTexture;
+        @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+        public mixTexture1: BaseTexture;
+
+        @serializeAsTexture("mixTexture2")
+        private _mixTexture2: BaseTexture;
+        @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+        public mixTexture2: BaseTexture;
+
+        /**
+         * Diffuse textures
+         */
+
+        @serializeAsTexture("diffuseTexture1")
+        private _diffuseTexture1: Texture;
+        @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+        public diffuseTexture1: Texture;
+
+        @serializeAsTexture("diffuseTexture2")
+        private _diffuseTexture2: Texture;
+        @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+        public diffuseTexture2: Texture;
+
+        @serializeAsTexture("diffuseTexture3")
+        private _diffuseTexture3: Texture;
+        @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+        public diffuseTexture3: Texture;
+
+        @serializeAsTexture("diffuseTexture4")
+        private _diffuseTexture4: Texture;
+        @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+        public diffuseTexture4: Texture;
+
+        @serializeAsTexture("diffuseTexture1")
+        private _diffuseTexture5: Texture;
+        @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+        public diffuseTexture5: Texture;
+
+        @serializeAsTexture("diffuseTexture2")
+        private _diffuseTexture6: Texture;
+        @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+        public diffuseTexture6: Texture;
+
+        @serializeAsTexture("diffuseTexture3")
+        private _diffuseTexture7: Texture;
+        @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+        public diffuseTexture7: Texture;
+
+        @serializeAsTexture("diffuseTexture4")
+        private _diffuseTexture8: Texture;
+        @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+        public diffuseTexture8: Texture;
+
+        /**
+         * Uniforms
+         */
+
+        @serializeAsColor3()
+        public diffuseColor = new Color3(1, 1, 1);
+
+        @serializeAsColor3()
+        public specularColor = new Color3(0, 0, 0);
+
+        @serialize()
+        public specularPower = 64;
+
+        @serialize("disableLighting")
+        private _disableLighting = false;
+        @expandToProperty("_markAllSubMeshesAsLightsDirty")
+        public disableLighting: boolean;
+
+        @serialize("maxSimultaneousLights")
+        private _maxSimultaneousLights = 4;
+        @expandToProperty("_markAllSubMeshesAsLightsDirty")
+        public maxSimultaneousLights: number;
+
+        private _renderId: number;
+
+        constructor(name: string, scene: Scene) {
+            super(name, scene);
+        }
+
+        public needAlphaBlending(): boolean {
+            return (this.alpha < 1.0);
+        }
+
+        public needAlphaTesting(): boolean {
+            return false;
+        }
+
+        public getAlphaTestTexture(): Nullable<BaseTexture> {
+            return null;
+        }
+
+        // Methods   
+        public isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances?: boolean): boolean {
+            if (this.isFrozen) {
+                if (this._wasPreviouslyReady && subMesh.effect) {
+                    return true;
+                }
+            }
+
+            if (!subMesh._materialDefines) {
+                subMesh._materialDefines = new MixMaterialDefines();
+            }
+
+            var defines = <MixMaterialDefines>subMesh._materialDefines;
+            var scene = this.getScene();
+
+            if (!this.checkReadyOnEveryCall && subMesh.effect) {
+                if (this._renderId === scene.getRenderId()) {
+                    return true;
+                }
+            }
+
+            var engine = scene.getEngine();
+
+            // Textures
+            if (scene.texturesEnabled) {
+                if (StandardMaterial.DiffuseTextureEnabled) {
+                    if (this._mixTexture1) {
+                        if (!this._mixTexture1.isReady()) {
+                            return false;
+                        } else {
+                            defines._needUVs = true;
+                            defines.DIFFUSE = true;
+                        }
+                    }
+                    if (this._mixTexture2) {
+                        if (!this._mixTexture2.isReady()) {
+                            return false;
+                        } else {
+                            defines.MIXMAP2 = true;
+                        }
+                    }
+                }
+            }
+
+            // Misc.
+            MaterialHelper.PrepareDefinesForMisc(mesh, scene, false, this.pointsCloud, this.fogEnabled, this._shouldTurnAlphaTestOn(mesh), defines);
+
+            // Lights
+            defines._needNormals = MaterialHelper.PrepareDefinesForLights(scene, mesh, defines, false, this._maxSimultaneousLights, this._disableLighting);
+
+            // Values that need to be evaluated on every frame
+            MaterialHelper.PrepareDefinesForFrameBoundValues(scene, engine, defines, useInstances ? true : false);
+
+            // Attribs
+            MaterialHelper.PrepareDefinesForAttributes(mesh, defines, true, true);
+
+            // Get correct effect      
+            if (defines.isDirty) {
+                defines.markAsProcessed();
+                scene.resetCachedMaterial();
+
+                // Fallbacks
+                var fallbacks = new EffectFallbacks();
+                if (defines.FOG) {
+                    fallbacks.addFallback(1, "FOG");
+                }
+
+                MaterialHelper.HandleFallbacksForShadows(defines, fallbacks, this.maxSimultaneousLights);
+
+                if (defines.NUM_BONE_INFLUENCERS > 0) {
+                    fallbacks.addCPUSkinningFallback(0, mesh);
+                }
+
+                //Attributes
+                var attribs = [VertexBuffer.PositionKind];
+
+                if (defines.NORMAL) {
+                    attribs.push(VertexBuffer.NormalKind);
+                }
+
+                if (defines.UV1) {
+                    attribs.push(VertexBuffer.UVKind);
+                }
+
+                if (defines.UV2) {
+                    attribs.push(VertexBuffer.UV2Kind);
+                }
+
+                if (defines.VERTEXCOLOR) {
+                    attribs.push(VertexBuffer.ColorKind);
+                }
+
+                MaterialHelper.PrepareAttributesForBones(attribs, mesh, defines, fallbacks);
+                MaterialHelper.PrepareAttributesForInstances(attribs, defines);
+
+                // Legacy browser patch
+                var shaderName = "mix";
+                var join = defines.toString();
+                var uniforms = [
+                    "world", "view", "viewProjection", "vEyePosition", "vLightsType", "vDiffuseColor", "vSpecularColor",
+                    "vFogInfos", "vFogColor", "pointSize",
+                    "vTextureInfos",
+                    "mBones",
+                    "vClipPlane", "textureMatrix",
+                    "diffuse1Infos", "diffuse2Infos", "diffuse3Infos", "diffuse4Infos",
+                    "diffuse5Infos", "diffuse6Infos", "diffuse7Infos", "diffuse8Infos"
+                ];
+                var samplers = [
+                    "mixMap1Sampler", "mixMap2Sampler",
+                    "diffuse1Sampler", "diffuse2Sampler", "diffuse3Sampler", "diffuse4Sampler",
+                    "diffuse5Sampler", "diffuse6Sampler", "diffuse7Sampler", "diffuse8Sampler"
+                ];
+
+                var uniformBuffers = new Array<string>()
+
+                MaterialHelper.PrepareUniformsAndSamplersList(<EffectCreationOptions>{
+                    uniformsNames: uniforms,
+                    uniformBuffersNames: uniformBuffers,
+                    samplers: samplers,
+                    defines: defines,
+                    maxSimultaneousLights: this.maxSimultaneousLights
+                });
+
+                subMesh.setEffect(scene.getEngine().createEffect(shaderName,
+                    <EffectCreationOptions>{
+                        attributes: attribs,
+                        uniformsNames: uniforms,
+                        uniformBuffersNames: uniformBuffers,
+                        samplers: samplers,
+                        defines: join,
+                        fallbacks: fallbacks,
+                        onCompiled: this.onCompiled,
+                        onError: this.onError,
+                        indexParameters: { maxSimultaneousLights: this.maxSimultaneousLights }
+                    }, engine), defines);
+            }
+            if (!subMesh.effect || !subMesh.effect.isReady()) {
+                return false;
+            }
+
+            this._renderId = scene.getRenderId();
+            this._wasPreviouslyReady = true;
+
+            return true;
+        }
+
+        public bindForSubMesh(world: Matrix, mesh: Mesh, subMesh: SubMesh): void {
+            var scene = this.getScene();
+
+            var defines = <MixMaterialDefines>subMesh._materialDefines;
+            if (!defines) {
+                return;
+            }
+
+            var effect = subMesh.effect;
+            if (!effect) {
+                return;
+            }
+            this._activeEffect = effect;
+
+            // Matrices        
+            this.bindOnlyWorldMatrix(world);
+            this._activeEffect.setMatrix("viewProjection", scene.getTransformMatrix());
+
+            // Bones
+            MaterialHelper.BindBonesParameters(mesh, this._activeEffect);
+
+            if (this._mustRebind(scene, effect)) {
+                // Textures        
+                if (this._mixTexture1) {
+                    this._activeEffect.setTexture("mixMap1Sampler", this._mixTexture1);
+                    this._activeEffect.setFloat2("vTextureInfos", this._mixTexture1.coordinatesIndex, this._mixTexture1.level);
+                    this._activeEffect.setMatrix("textureMatrix", this._mixTexture1.getTextureMatrix());
+
+                    if (StandardMaterial.DiffuseTextureEnabled) {
+                        if (this._diffuseTexture1) {
+                            this._activeEffect.setTexture("diffuse1Sampler", this._diffuseTexture1);
+                            this._activeEffect.setFloat2("diffuse1Infos", this._diffuseTexture1.uScale, this._diffuseTexture1.vScale);
+                        }
+                        if (this._diffuseTexture2) {
+                            this._activeEffect.setTexture("diffuse2Sampler", this._diffuseTexture2);
+                            this._activeEffect.setFloat2("diffuse2Infos", this._diffuseTexture2.uScale, this._diffuseTexture2.vScale);
+                        }
+                        if (this._diffuseTexture3) {
+                            this._activeEffect.setTexture("diffuse3Sampler", this._diffuseTexture3);
+                            this._activeEffect.setFloat2("diffuse3Infos", this._diffuseTexture3.uScale, this._diffuseTexture3.vScale);
+                        }
+                        if (this._diffuseTexture4) {
+                            this._activeEffect.setTexture("diffuse4Sampler", this._diffuseTexture4);
+                            this._activeEffect.setFloat2("diffuse4Infos", this._diffuseTexture4.uScale, this._diffuseTexture4.vScale);
+                        }
+                    }
+                }
+
+                if (this.mixTexture2) {
+                    this._activeEffect.setTexture("mixMap2Sampler", this._mixTexture2);
+
+                    if (StandardMaterial.DiffuseTextureEnabled) {
+                        if (this._diffuseTexture5) {
+                            this._activeEffect.setTexture("diffuse5Sampler", this._diffuseTexture5);
+                            this._activeEffect.setFloat2("diffuse5Infos", this._diffuseTexture5.uScale, this._diffuseTexture5.vScale);
+                        }
+                        if (this._diffuseTexture6) {
+                            this._activeEffect.setTexture("diffuse6Sampler", this._diffuseTexture6);
+                            this._activeEffect.setFloat2("diffuse6Infos", this._diffuseTexture6.uScale, this._diffuseTexture6.vScale);
+                        }
+                        if (this._diffuseTexture7) {
+                            this._activeEffect.setTexture("diffuse7Sampler", this._diffuseTexture7);
+                            this._activeEffect.setFloat2("diffuse7Infos", this._diffuseTexture7.uScale, this._diffuseTexture7.vScale);
+                        }
+                        if (this._diffuseTexture8) {
+                            this._activeEffect.setTexture("diffuse8Sampler", this._diffuseTexture8);
+                            this._activeEffect.setFloat2("diffuse8Infos", this._diffuseTexture8.uScale, this._diffuseTexture8.vScale);
+                        }
+                    }
+                }
+
+                // Clip plane
+                MaterialHelper.BindClipPlane(this._activeEffect, scene);
+
+                // Point size
+                if (this.pointsCloud) {
+                    this._activeEffect.setFloat("pointSize", this.pointSize);
+                }
+
+                MaterialHelper.BindEyePosition(effect, scene);
+            }
+
+            this._activeEffect.setColor4("vDiffuseColor", this.diffuseColor, this.alpha * mesh.visibility);
+
+            if (defines.SPECULARTERM) {
+                this._activeEffect.setColor4("vSpecularColor", this.specularColor, this.specularPower);
+            }
+
+            if (scene.lightsEnabled && !this.disableLighting) {
+                MaterialHelper.BindLights(scene, mesh, this._activeEffect, defines, this.maxSimultaneousLights);
+            }
+
+            // View
+            if (scene.fogEnabled && mesh.applyFog && scene.fogMode !== Scene.FOGMODE_NONE) {
+                this._activeEffect.setMatrix("view", scene.getViewMatrix());
+            }
+
+            // Fog
+            MaterialHelper.BindFogParameters(scene, mesh, this._activeEffect);
+
+            this._afterBind(mesh, this._activeEffect);
+        }
+
+        public getAnimatables(): IAnimatable[] {
+            var results = [];
+
+            if (this._mixTexture1 && this._mixTexture1.animations && this._mixTexture1.animations.length > 0) {
+                results.push(this._mixTexture1);
+            }
+
+            if (this._mixTexture2 && this._mixTexture2.animations && this._mixTexture2.animations.length > 0) {
+                results.push(this._mixTexture2);
+            }
+
+            return results;
+        }
+
+        public getActiveTextures(): BaseTexture[] {
+            var activeTextures = super.getActiveTextures();
+
+            // Mix map 1
+            if (this._mixTexture1) {
+                activeTextures.push(this._mixTexture1);
+            }
+
+            if (this._diffuseTexture1) {
+                activeTextures.push(this._diffuseTexture1);
+            }
+
+            if (this._diffuseTexture2) {
+                activeTextures.push(this._diffuseTexture2);
+            }
+
+            if (this._diffuseTexture3) {
+                activeTextures.push(this._diffuseTexture3);
+            }
+
+            if (this._diffuseTexture4) {
+                activeTextures.push(this._diffuseTexture4);
+            }
+
+            // Mix map 2
+            if (this._mixTexture2) {
+                activeTextures.push(this._mixTexture2);
+            }
+
+            if (this._diffuseTexture5) {
+                activeTextures.push(this._diffuseTexture5);
+            }
+
+            if (this._diffuseTexture6) {
+                activeTextures.push(this._diffuseTexture6);
+            }
+
+            if (this._diffuseTexture7) {
+                activeTextures.push(this._diffuseTexture7);
+            }
+
+            if (this._diffuseTexture8) {
+                activeTextures.push(this._diffuseTexture8);
+            }
+
+            return activeTextures;
+        }
+
+        public hasTexture(texture: BaseTexture): boolean {
+            if (super.hasTexture(texture)) {
+                return true;
+            }
+
+            // Mix map 1
+            if (this._mixTexture1 === texture) {
+                return true;
+            }
+
+            if (this._diffuseTexture1 === texture) {
+                return true;
+            }
+
+            if (this._diffuseTexture2 === texture) {
+                return true;
+            }
+
+            if (this._diffuseTexture3 === texture) {
+                return true;
+            }
+
+            if (this._diffuseTexture4 === texture) {
+                return true;
+            }
+
+            // Mix map 2
+            if (this._mixTexture2 === texture) {
+                return true;
+            }
+
+            if (this._diffuseTexture5 === texture) {
+                return true;
+            }
+
+            if (this._diffuseTexture6 === texture) {
+                return true;
+            }
+
+            if (this._diffuseTexture7 === texture) {
+                return true;
+            }
+
+            if (this._diffuseTexture8 === texture) {
+                return true;
+            }
+
+            return false;
+        }
+
+        public dispose(forceDisposeEffect?: boolean): void {
+            if (this._mixTexture1) {
+                this._mixTexture1.dispose();
+            }
+
+            super.dispose(forceDisposeEffect);
+        }
+
+        public clone(name: string): MixMaterial {
+            return SerializationHelper.Clone(() => new MixMaterial(name, this.getScene()), this);
+        }
+
+        public serialize(): any {
+            var serializationObject = SerializationHelper.Serialize(this);
+            serializationObject.customType = "BABYLON.MixMaterial";
+            return serializationObject;
+        }
+
+        public getClassName(): string {
+            return "MixMaterial";
+        }
+
+        // Statics
+        public static Parse(source: any, scene: Scene, rootUrl: string): MixMaterial {
+            return SerializationHelper.Parse(() => new MixMaterial(source.name, scene), source, scene, rootUrl);
+        }
+    }
+}
+

+ 172 - 0
materialsLibrary/src/mix/mix.fragment.fx

@@ -0,0 +1,172 @@
+precision highp float;
+
+// Constants
+uniform vec3 vEyePosition;
+uniform vec4 vDiffuseColor;
+
+#ifdef SPECULARTERM
+uniform vec4 vSpecularColor;
+#endif
+
+// Input
+varying vec3 vPositionW;
+
+#ifdef NORMAL
+varying vec3 vNormalW;
+#endif
+
+#ifdef VERTEXCOLOR
+varying vec4 vColor;
+#endif
+
+// Helper functions
+#include<helperFunctions>
+
+// Lights
+#include<__decl__lightFragment>[0..maxSimultaneousLights]
+
+// Samplers
+#ifdef DIFFUSE
+varying vec2 vTextureUV;
+uniform sampler2D mixMap1Sampler;
+uniform vec2 vTextureInfos;
+
+#ifdef MIXMAP2
+uniform sampler2D mixMap2Sampler;
+#endif
+
+uniform sampler2D diffuse1Sampler;
+uniform sampler2D diffuse2Sampler;
+uniform sampler2D diffuse3Sampler;
+uniform sampler2D diffuse4Sampler;
+
+uniform vec2 diffuse1Infos;
+uniform vec2 diffuse2Infos;
+uniform vec2 diffuse3Infos;
+uniform vec2 diffuse4Infos;
+
+#ifdef MIXMAP2
+uniform sampler2D diffuse5Sampler;
+uniform sampler2D diffuse6Sampler;
+uniform sampler2D diffuse7Sampler;
+uniform sampler2D diffuse8Sampler;
+
+uniform vec2 diffuse5Infos;
+uniform vec2 diffuse6Infos;
+uniform vec2 diffuse7Infos;
+uniform vec2 diffuse8Infos;
+#endif
+
+#endif
+
+// Shadows
+#include<lightsFragmentFunctions>
+#include<shadowsFragmentFunctions>
+#include<clipPlaneFragmentDeclaration>
+
+// Fog
+#include<fogFragmentDeclaration>
+
+void main(void) {
+	// Clip plane
+#ifdef CLIPPLANE
+	if (fClipDistance > 0.0)
+		discard;
+#endif
+
+	vec3 viewDirectionW = normalize(vEyePosition - vPositionW);
+
+	// Base color
+	vec4 finalMixColor = vec4(1., 1., 1., 1.);
+	vec3 diffuseColor = vDiffuseColor.rgb;
+
+#ifdef MIXMAP2
+	vec4 mixColor2 = vec4(1., 1., 1., 1.);
+#endif
+	
+#ifdef SPECULARTERM
+	float glossiness = vSpecularColor.a;
+	vec3 specularColor = vSpecularColor.rgb;
+#else
+	float glossiness = 0.;
+#endif
+
+	// Alpha
+	float alpha = vDiffuseColor.a;
+	
+	// Normal
+#ifdef NORMAL
+	vec3 normalW = normalize(vNormalW);
+#else
+	vec3 normalW = vec3(1.0, 1.0, 1.0);
+#endif
+
+#ifdef DIFFUSE
+	vec4 mixColor = texture2D(mixMap1Sampler, vTextureUV);
+
+#include<depthPrePass>
+
+	mixColor.rgb *= vTextureInfos.y;
+	
+	vec4 diffuse1Color = texture2D(diffuse1Sampler, vTextureUV * diffuse1Infos);
+	vec4 diffuse2Color = texture2D(diffuse2Sampler, vTextureUV * diffuse2Infos);
+	vec4 diffuse3Color = texture2D(diffuse3Sampler, vTextureUV * diffuse3Infos);
+	vec4 diffuse4Color = texture2D(diffuse4Sampler, vTextureUV * diffuse4Infos);
+	
+	diffuse1Color.rgb *= mixColor.r;
+   	diffuse2Color.rgb = mix(diffuse1Color.rgb, diffuse2Color.rgb, mixColor.g);
+   	diffuse3Color.rgb = mix(diffuse2Color.rgb, diffuse3Color.rgb, mixColor.b);
+	finalMixColor.rgb = mix(diffuse3Color.rgb, diffuse4Color.rgb, 1.0 - mixColor.a);
+
+#ifdef MIXMAP2
+	mixColor = texture2D(mixMap2Sampler, vTextureUV);
+	mixColor.rgb *= vTextureInfos.y;
+
+	vec4 diffuse5Color = texture2D(diffuse5Sampler, vTextureUV * diffuse5Infos);
+	vec4 diffuse6Color = texture2D(diffuse6Sampler, vTextureUV * diffuse6Infos);
+	vec4 diffuse7Color = texture2D(diffuse7Sampler, vTextureUV * diffuse7Infos);
+	vec4 diffuse8Color = texture2D(diffuse8Sampler, vTextureUV * diffuse8Infos);
+
+	diffuse5Color.rgb *= mixColor.r;
+   	diffuse6Color.rgb = mix(diffuse5Color.rgb, diffuse6Color.rgb, mixColor.g);
+   	diffuse7Color.rgb = mix(diffuse6Color.rgb, diffuse7Color.rgb, mixColor.b);
+	mixColor.rgb = mix(diffuse7Color.rgb, diffuse8Color.rgb, 1.0 - mixColor.a);
+
+	finalMixColor.rgb = mix(finalMixColor.rgb, mixColor.rgb, 0.5);
+#endif
+	
+#endif
+
+#ifdef VERTEXCOLOR
+	finalMixColor.rgb *= vColor.rgb;
+#endif
+
+	// Lighting
+	vec3 diffuseBase = vec3(0., 0., 0.);
+    lightingInfo info;
+	float shadow = 1.;
+	
+#ifdef SPECULARTERM
+	vec3 specularBase = vec3(0., 0., 0.);
+#endif
+	#include<lightFragment>[0..maxSimultaneousLights]
+
+#ifdef VERTEXALPHA
+	alpha *= vColor.a;
+#endif
+
+#ifdef SPECULARTERM
+	vec3 finalSpecular = specularBase * specularColor;
+#else
+	vec3 finalSpecular = vec3(0.0);
+#endif
+
+    vec3 finalDiffuse = clamp(diffuseBase * diffuseColor * finalMixColor.rgb, 0.0, 1.0);
+
+	// Composition
+	vec4 color = vec4(finalDiffuse + finalSpecular, alpha);
+
+#include<fogFragment>
+
+	gl_FragColor = color;
+}

+ 102 - 0
materialsLibrary/src/mix/mix.vertex.fx

@@ -0,0 +1,102 @@
+precision highp float;
+
+// Attributes
+attribute vec3 position;
+#ifdef NORMAL
+attribute vec3 normal;
+#endif
+#ifdef UV1
+attribute vec2 uv;
+#endif
+#ifdef UV2
+attribute vec2 uv2;
+#endif
+#ifdef VERTEXCOLOR
+attribute vec4 color;
+#endif
+
+#include<bonesDeclaration>
+
+// Uniforms
+#include<instancesDeclaration>
+
+uniform mat4 view;
+uniform mat4 viewProjection;
+
+#ifdef DIFFUSE
+varying vec2 vTextureUV;
+uniform mat4 textureMatrix;
+uniform vec2 vTextureInfos;
+#endif
+
+#ifdef POINTSIZE
+uniform float pointSize;
+#endif
+
+// Output
+varying vec3 vPositionW;
+#ifdef NORMAL
+varying vec3 vNormalW;
+#endif
+
+#ifdef VERTEXCOLOR
+varying vec4 vColor;
+#endif
+
+#include<clipPlaneVertexDeclaration>
+#include<fogVertexDeclaration>
+#include<__decl__lightFragment>[0..maxSimultaneousLights]
+
+void main(void) {
+	#include<instancesVertex>
+    #include<bonesVertex>
+	
+	gl_Position = viewProjection * finalWorld * vec4(position, 1.0);
+
+	vec4 worldPos = finalWorld * vec4(position, 1.0);
+	vPositionW = vec3(worldPos);
+
+#ifdef NORMAL
+	vNormalW = normalize(vec3(finalWorld * vec4(normal, 0.0)));
+#endif
+
+	// Texture coordinates
+#ifndef UV1
+	vec2 uv = vec2(0., 0.);
+#endif
+#ifndef UV2
+	vec2 uv2 = vec2(0., 0.);
+#endif
+
+#ifdef DIFFUSE
+	if (vTextureInfos.x == 0.)
+	{
+		vTextureUV = vec2(textureMatrix * vec4(uv, 1.0, 0.0));
+	}
+	else
+	{
+		vTextureUV = vec2(textureMatrix * vec4(uv2, 1.0, 0.0));
+	}
+#endif
+
+	// Clip plane
+#ifdef CLIPPLANE
+	fClipDistance = dot(worldPos, vClipPlane);
+#endif
+
+	// Fog
+	#include<fogVertex>
+	
+	// Shadows
+    #include<shadowsVertex>[0..maxSimultaneousLights]
+
+	// Vertex color
+#ifdef VERTEXCOLOR
+	vColor = color;
+#endif
+
+	// Point size
+#ifdef POINTSIZE
+	gl_PointSize = pointSize;
+#endif
+}

+ 29 - 0
materialsLibrary/test/addMix.js

@@ -0,0 +1,29 @@
+window.prepareMix = function() {
+    var mix = new BABYLON.MixMaterial("mix", scene);
+    mix.specularColor = new BABYLON.Color3(0.5, 0.5, 0.5);
+    mix.specularPower = 64;
+    mix.mixTexture1 = new BABYLON.Texture("/playground/textures/mixMap.png", scene);
+    mix.mixTexture2 = new BABYLON.Texture("/playground/textures/mixMap_2.png", scene);
+
+    mix.diffuseTexture1 = new BABYLON.Texture("/playground/textures/floor.png", scene);
+    mix.diffuseTexture2 = new BABYLON.Texture("/playground/textures/rock.png", scene);
+    mix.diffuseTexture3 = new BABYLON.Texture("/playground/textures/grass.png", scene);
+    mix.diffuseTexture4 = new BABYLON.Texture("/playground/textures/floor.png", scene);
+
+    mix.diffuseTexture1.uScale = mix.diffuseTexture1.vScale = 10;
+    mix.diffuseTexture2.uScale = mix.diffuseTexture2.vScale = 10;
+    mix.diffuseTexture3.uScale = mix.diffuseTexture3.vScale = 10;
+    mix.diffuseTexture4.uScale = mix.diffuseTexture4.vScale = 10;
+
+    mix.diffuseTexture5 = new BABYLON.Texture("/playground/textures/leopard_fur.jpg", scene);
+    mix.diffuseTexture6 = new BABYLON.Texture("/playground/textures/fur.jpg", scene);
+    mix.diffuseTexture7 = new BABYLON.Texture("/playground/textures/sand.jpg", scene);
+    mix.diffuseTexture8 = new BABYLON.Texture("/playground/textures/crate.png", scene);
+
+    mix.diffuseTexture5.uScale = mix.diffuseTexture5.vScale = 10;
+    mix.diffuseTexture6.uScale = mix.diffuseTexture6.vScale = 10;
+    mix.diffuseTexture7.uScale = mix.diffuseTexture7.vScale = 5;
+    mix.diffuseTexture8.uScale = mix.diffuseTexture8.vScale = 10;
+    
+    return mix;
+};

+ 18 - 2
serializers/src/glTF/2.0/babylon.glTFExporter.ts

@@ -799,6 +799,9 @@ module BABYLON.GLTF2 {
          * @param babylonMesh The BabylonJS mesh
          */
         private getMeshPrimitiveMode(babylonMesh: AbstractMesh): number {
+            if (babylonMesh instanceof LinesMesh) {
+                return Material.LineListDrawMode;
+            }
             return babylonMesh.material ? babylonMesh.material.fillMode : Material.TriangleFillMode;
         }
 
@@ -951,7 +954,20 @@ module BABYLON.GLTF2 {
 
                         let materialIndex: Nullable<number> = null;
                         if (babylonMaterial) {
-                            if (babylonMaterial instanceof MultiMaterial) {
+                            if (bufferMesh instanceof LinesMesh) {
+                                // get the color from the lines mesh and set it in the material
+                                const material: IMaterial = {
+                                    name: bufferMesh.name + ' material'
+                                }
+                                if (!bufferMesh.color.equals(Color3.White()) || bufferMesh.alpha < 1) {
+                                    material.pbrMetallicRoughness = {
+                                        baseColorFactor: bufferMesh.color.asArray().concat([bufferMesh.alpha])
+                                    }
+                                }
+                                this.materials.push(material);
+                                materialIndex = this.materials.length - 1;
+                            }
+                            else if (babylonMaterial instanceof MultiMaterial) {
                                 babylonMaterial = babylonMaterial.subMaterials[submesh.materialIndex];
                                 if (babylonMaterial) {
                                     materialIndex = this.babylonScene.materials.indexOf(babylonMaterial);
@@ -965,6 +981,7 @@ module BABYLON.GLTF2 {
                         let glTFMaterial: Nullable<IMaterial> = materialIndex != null ? this.materials[materialIndex] : null;
 
                         const meshPrimitive: IMeshPrimitive = { attributes: {} };
+                        this.setPrimitiveMode(meshPrimitive, primitiveMode);
 
                         for (const attribute of attributeData) {
                             const attributeKind = attribute.kind;
@@ -1003,7 +1020,6 @@ module BABYLON.GLTF2 {
                         if (babylonMaterial) {
                             if (materialIndex != null && Object.keys(meshPrimitive.attributes).length > 0) {
                                 let sideOrientation = this.babylonScene.materials[materialIndex].sideOrientation;
-                                this.setPrimitiveMode(meshPrimitive, primitiveMode);
 
                                 if (this.convertToRightHandedSystem && sideOrientation === Material.ClockWiseSideOrientation) {
                                     //Overwrite the indices to be counter-clockwise

+ 40 - 23
src/Behaviors/Mesh/babylon.pointerDragBehavior.ts

@@ -6,7 +6,7 @@ module BABYLON {
         private _attachedNode: Node; 
         private _dragPlane: Mesh;
         private _scene:Scene;
-        private _pointerObserver:Nullable<Observer<PointerInfo>>;
+        private _pointerObserver:Nullable<Observer<PointerInfoPre>>;
         private static _planeScene:Scene;
         private _draggingID = -1;
         
@@ -33,9 +33,9 @@ module BABYLON {
         
         /**
          * Creates a pointer drag behavior that can be attached to a mesh
-         * @param options The drag axis or normal of the plane that will be dragged accross
+         * @param options The drag axis or normal of the plane that will be dragged across. pointerObservableScene can be used to listen to drag events from another scene(eg. if the attached mesh is in an overlay scene).
          */
-        constructor(private options:{dragAxis?:Vector3, dragPlaneNormal?:Vector3}){
+        constructor(private options:{dragAxis?:Vector3, dragPlaneNormal?:Vector3, pointerObservableScene?:Scene}){
             var optionCount = 0;
             if(options.dragAxis){
                 optionCount++;
@@ -69,6 +69,9 @@ module BABYLON {
          */
         public attach(ownerNode: Mesh): void {
             this._scene = ownerNode.getScene();
+            if(!this.options.pointerObservableScene){
+                this.options.pointerObservableScene = this._scene;
+            }
             this._attachedNode = ownerNode;
 
             // Initialize drag plane to not interfere with existing scene
@@ -79,34 +82,48 @@ module BABYLON {
             this._dragPlane = BABYLON.Mesh.CreatePlane("pointerDragPlane", 1000, PointerDragBehavior._planeScene, false, BABYLON.Mesh.DOUBLESIDE);
 
             // State of the drag
-            var dragging = false
+            var dragging = false;
             var lastPosition = new BABYLON.Vector3(0,0,0);
             var delta = new BABYLON.Vector3(0,0,0);
 
-            this._pointerObserver = this._scene.onPointerObservable.add((pointerInfo)=>{
-                if (pointerInfo.type == BABYLON.PointerEventTypes.POINTERDOWN) {
-                    if(!dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.hit && pointerInfo.pickInfo.pickedMesh && pointerInfo.pickInfo.ray){
-                        if(this._attachedNode == pointerInfo.pickInfo.pickedMesh){
-                            this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
-                            var pickedPoint = this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray)
-                            if(pickedPoint){
-                                dragging = true;
-                                this._draggingID = (<PointerEvent>pointerInfo.event).pointerId;
-                                lastPosition.copyFrom(pickedPoint);
-                                this.onDragStartObservable.notifyObservers({dragPlanePoint: pickedPoint});
-                            }
+            var pickPredicate = (m:AbstractMesh)=>{
+                return this._attachedNode == m || m.isDescendantOf(this._attachedNode)
+            }
+
+            this._pointerObserver = this.options.pointerObservableScene!.onPrePointerObservable.add((pointerInfoPre, eventState)=>{
+                // Check if attached mesh is picked
+                var pickInfo = pointerInfoPre.ray ? this._scene.pickWithRay(pointerInfoPre.ray, pickPredicate) : this._scene.pick(this._scene.pointerX, this._scene.pointerY, pickPredicate);
+                if(pickInfo){
+                    pickInfo.ray = pointerInfoPre.ray;
+                    if(!pickInfo.ray){
+                        pickInfo.ray = this.options.pointerObservableScene!.createPickingRay(this._scene.pointerX, this._scene.pointerY, Matrix.Identity(), this._scene.activeCamera);
+                    }
+                    if(pickInfo.hit){
+                        eventState.skipNextObservers = true;
+                    }
+                }
+                
+                if (pointerInfoPre.type == BABYLON.PointerEventTypes.POINTERDOWN) {
+                    if(!dragging && pickInfo && pickInfo.hit && pickInfo.pickedMesh && pickInfo.ray){
+                        this._updateDragPlanePosition(pickInfo.ray);
+                        var pickedPoint = this._pickWithRayOnDragPlane(pickInfo.ray);
+                        if(pickedPoint){
+                            dragging = true;
+                            this._draggingID = (<PointerEvent>pointerInfoPre.event).pointerId;
+                            lastPosition.copyFrom(pickedPoint);
+                            this.onDragStartObservable.notifyObservers({dragPlanePoint: pickedPoint});
                         }
                     }
-                }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERUP){
-                    if(this._draggingID == (<PointerEvent>pointerInfo.event).pointerId){
+                }else if(pointerInfoPre.type == BABYLON.PointerEventTypes.POINTERUP){
+                    if(this._draggingID == (<PointerEvent>pointerInfoPre.event).pointerId){
                         dragging = false;
                         this._draggingID = -1;
                         this.onDragEndObservable.notifyObservers({dragPlanePoint: lastPosition});
                     }
-                }else if(pointerInfo.type == BABYLON.PointerEventTypes.POINTERMOVE){
-                    if(this._draggingID == (<PointerEvent>pointerInfo.event).pointerId && dragging && pointerInfo.pickInfo && pointerInfo.pickInfo.ray){
-                        var pickedPoint = this._pickWithRayOnDragPlane(pointerInfo.pickInfo.ray)
-                        this._updateDragPlanePosition(pointerInfo.pickInfo.ray);
+                }else if(pointerInfoPre.type == BABYLON.PointerEventTypes.POINTERMOVE){
+                    if(this._draggingID == (<PointerEvent>pointerInfoPre.event).pointerId && dragging && pickInfo && pickInfo.ray){
+                        var pickedPoint = this._pickWithRayOnDragPlane(pickInfo.ray);
+                        this._updateDragPlanePosition(pickInfo.ray);
                         if (pickedPoint) {
                             // depending on the drag mode option drag accordingly
                             if(this.options.dragAxis){
@@ -170,7 +187,7 @@ module BABYLON {
          */
         public detach(): void {
             if(this._pointerObserver){
-                this._scene.onPointerObservable.remove(this._pointerObserver);
+                this._scene.onPrePointerObservable.remove(this._pointerObserver);
             }
         }
     }

+ 4 - 0
src/Events/babylon.pointerEvents.ts

@@ -47,6 +47,10 @@ module BABYLON {
      * Set the skipOnPointerObservable property to true if you want the engine to stop any process after this event is triggered, even not calling onPointerObservable
      */
     export class PointerInfoPre extends PointerInfoBase {
+        /**
+         * Ray from a pointer if availible (eg. 6dof controller)
+         */
+        public ray:Nullable<Ray> = null;
         constructor(type: number, event: PointerEvent | MouseWheelEvent, localX: number, localY: number) {
             super(type, event);
             this.skipOnPointerObservable = false;

+ 55 - 0
src/Gizmos/babylon.axisDragGizmo.ts

@@ -0,0 +1,55 @@
+module BABYLON {
+    /**
+     * Single axis drag gizmo
+     */
+    export class AxisDragGizmo extends Gizmo {
+        private _dragBehavior:PointerDragBehavior;
+        /**
+         * 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
+         * @param color The color of the gizmo
+         */
+        constructor(gizmoLayer:UtilityLayerRenderer, dragAxis:Vector3, color:Color3){
+            super(gizmoLayer);
+
+            // Create Material
+            var coloredMaterial = new BABYLON.StandardMaterial("", gizmoLayer.utilityLayerScene);
+            coloredMaterial.disableLighting = true;
+            coloredMaterial.emissiveColor = color;
+
+            // Build mesh on root node
+            var arrowMesh = BABYLON.MeshBuilder.CreateCylinder("yPosMesh", {diameterTop:0, height: 2, tessellation: 96}, gizmoLayer.utilityLayerScene);
+            var arrowTail = BABYLON.MeshBuilder.CreateCylinder("yPosMesh", {diameter:0.03, height: 0.2, tessellation: 96}, gizmoLayer.utilityLayerScene);
+            this._rootMesh.addChild(arrowMesh);
+            this._rootMesh.addChild(arrowTail);
+
+            // Position arrow pointing in its drag axis
+            arrowMesh.scaling.scaleInPlace(0.1);
+            arrowMesh.material = coloredMaterial;
+            arrowMesh.rotation.x = Math.PI/2;
+            arrowMesh.position.z+=0.3;
+            arrowTail.rotation.x = Math.PI/2;
+            arrowTail.material = coloredMaterial;
+            arrowTail.position.z+=0.2;
+            this._rootMesh.lookAt(this._rootMesh.position.subtract(dragAxis));
+
+            // Add drag behavior to handle events when the gizmo is dragged
+            this._dragBehavior = new PointerDragBehavior({dragAxis: dragAxis, pointerObservableScene: gizmoLayer.originalScene});
+            this._dragBehavior.moveAttached = false;
+            this._rootMesh.addBehavior(this._dragBehavior);
+            this._dragBehavior.onDragObservable.add((event)=>{
+                if(this.attachedMesh){
+                    this.attachedMesh.position.addInPlace(event.delta);
+                }
+            })
+        }
+        /**
+         * Disposes of the gizmo
+         */
+        public dispose(){
+            this._dragBehavior.detach();
+            super.dispose();
+        } 
+    }
+}

+ 41 - 0
src/Gizmos/babylon.gizmo.ts

@@ -0,0 +1,41 @@
+module BABYLON {
+    /**
+     * Renders gizmos on top of an existing scene which provide controls for position, rotation, etc.
+     */
+    export class Gizmo implements IDisposable {
+        /**
+         * The root mesh of the gizmo
+         */
+        protected _rootMesh:Mesh;
+        /**
+         * Mesh that the gizmo will be attached to. (eg. on a drag gizmo the mesh that will be dragged)
+         */
+        public attachedMesh:Nullable<Mesh>;
+        private _beforeRenderObserver:Nullable<Observer<Scene>>;
+        /**
+         * Creates a gizmo
+         * @param gizmoLayer The utility layer the gizmo will be added to
+         */
+        constructor(/** The utility layer the gizmo will be added to */ public gizmoLayer:UtilityLayerRenderer){
+            this._rootMesh = new BABYLON.Mesh("gizmoRootNode",gizmoLayer.utilityLayerScene);
+            this._beforeRenderObserver = this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.add(()=>{
+                if(this.gizmoLayer.utilityLayerScene.activeCamera && this.attachedMesh){
+                    var dist = this.attachedMesh.position.subtract(this.gizmoLayer.utilityLayerScene.activeCamera.position).length()/5;
+                    this._rootMesh.scaling.set(dist, dist, dist);
+                }
+                if(this.attachedMesh){
+                    this._rootMesh.position.copyFrom(this.attachedMesh.position);
+                }
+            })
+        }
+        /**
+         * Disposes of the gizmo
+         */
+        public dispose(){
+            this._rootMesh.dispose()
+            if(this._beforeRenderObserver){
+                this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.remove(this._beforeRenderObserver);
+            }
+        }
+    }
+}

+ 35 - 0
src/Gizmos/babylon.positionGizmo.ts

@@ -0,0 +1,35 @@
+module BABYLON {
+    /**
+     * Gizmo that enables dragging a mesh along 3 axis
+     */
+    export class PositionGizmo extends Gizmo {
+        private _xDrag:AxisDragGizmo;
+        private _yDrag:AxisDragGizmo;
+        private _zDrag:AxisDragGizmo;
+
+        public set attachedMesh(mesh:Nullable<Mesh>){
+            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
+         */
+        constructor(gizmoLayer:UtilityLayerRenderer){
+            super(gizmoLayer);
+            this._xDrag = new AxisDragGizmo(gizmoLayer, new Vector3(1,0,0), BABYLON.Color3.FromHexString("#00b894"));
+            this._yDrag = new AxisDragGizmo(gizmoLayer, new Vector3(0,1,0), BABYLON.Color3.FromHexString("#d63031"));
+            this._zDrag = new AxisDragGizmo(gizmoLayer, new Vector3(0,0,1), BABYLON.Color3.FromHexString("#0984e3"));
+        }
+
+        /**
+         * Disposes of the gizmo
+         */
+        public dispose(){
+            this._xDrag.dispose();
+            this._yDrag.dispose();
+            this._zDrag.dispose();
+        }
+    }
+}

+ 7 - 0
src/Materials/babylon.material.ts

@@ -395,6 +395,12 @@
         public id: string;
 
         /**
+         * Gets or sets the unique id of the material
+         */
+        @serialize()
+        public uniqueId: number;
+
+        /**
          * The name of the material
          */
         @serialize()
@@ -776,6 +782,7 @@
             this.id = name || Tools.RandomId();
 
             this._scene = scene || Engine.LastCreatedScene;
+            this.uniqueId = this._scene.getUniqueId();
 
             if (this._scene.useRightHandedSystem) {
                 this.sideOrientation = Material.ClockWiseSideOrientation;

+ 35 - 29
src/babylon.scene.ts

@@ -1621,6 +1621,10 @@
          */
         public simulatePointerMove(pickResult: PickingInfo, pointerEventInit?: PointerEventInit): Scene {
             let evt = new PointerEvent("pointermove", pointerEventInit);
+
+            if(this._checkPrePointerObservable(pickResult, evt, PointerEventTypes.POINTERMOVE)){
+                return this;
+            }
             return this._processPointerMove(pickResult, evt);
         }
 
@@ -1682,6 +1686,19 @@
             return this;
         }
 
+        private _checkPrePointerObservable(pickResult: Nullable<PickingInfo>, evt: PointerEvent, type: number){
+            let pi = new PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
+            if(pickResult){
+                pi.ray = pickResult.ray;
+            }
+            this.onPrePointerObservable.notifyObservers(pi, type);
+            if (pi.skipOnPointerObservable) {
+                return true;
+            }else{
+                return false;
+            }
+        }
+
         /**
          * Use this method to simulate a pointer down on a mesh
          * The pickResult parameter can be obtained from a scene.pick or scene.pickWithRay
@@ -1691,7 +1708,11 @@
          */
         public simulatePointerDown(pickResult: PickingInfo, pointerEventInit?: PointerEventInit): Scene {
             let evt = new PointerEvent("pointerdown", pointerEventInit);
-
+            
+            if(this._checkPrePointerObservable(pickResult, evt, PointerEventTypes.POINTERDOWN)){
+                return this;
+            }
+            
             return this._processPointerDown(pickResult, evt);
         }
 
@@ -1765,6 +1786,10 @@
             clickInfo.singleClick = true;
             clickInfo.ignore = true;
 
+            if(this._checkPrePointerObservable(pickResult, evt, PointerEventTypes.POINTERUP)){
+                return this;
+            }
+
             return this._processPointerUp(pickResult, evt, clickInfo);
         }
 
@@ -1991,13 +2016,8 @@
                 this._updatePointerPosition(evt);
 
                 // PreObservable support
-                if (this.onPrePointerObservable.hasObservers() && !this._pointerCaptures[evt.pointerId]) {
-                    let type = evt.type === "mousewheel" || evt.type === "DOMMouseScroll" ? PointerEventTypes.POINTERWHEEL : PointerEventTypes.POINTERMOVE;
-                    let pi = new PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
-                    this.onPrePointerObservable.notifyObservers(pi, type);
-                    if (pi.skipOnPointerObservable) {
-                        return;
-                    }
+                if(this._checkPrePointerObservable(null, evt, evt.type === "mousewheel" || evt.type === "DOMMouseScroll" ? PointerEventTypes.POINTERWHEEL : PointerEventTypes.POINTERMOVE)){
+                    return;
                 }
 
                 if (!this.cameraToUseForPointers && !this.activeCamera) {
@@ -2027,13 +2047,8 @@
                 }
 
                 // PreObservable support
-                if (this.onPrePointerObservable.hasObservers()) {
-                    let type = PointerEventTypes.POINTERDOWN;
-                    let pi = new PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
-                    this.onPrePointerObservable.notifyObservers(pi, type);
-                    if (pi.skipOnPointerObservable) {
-                        return;
-                    }
+                if(this._checkPrePointerObservable(null, evt, PointerEventTypes.POINTERDOWN)){
+                    return;
                 }
 
                 if (!this.cameraToUseForPointers && !this.activeCamera) {
@@ -2100,28 +2115,19 @@
                         if (!clickInfo.ignore) {
                             if (!clickInfo.hasSwiped) {
                                 if (clickInfo.singleClick && this.onPrePointerObservable.hasSpecificMask(PointerEventTypes.POINTERTAP)) {
-                                    let type = PointerEventTypes.POINTERTAP;
-                                    let pi = new PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
-                                    this.onPrePointerObservable.notifyObservers(pi, type);
-                                    if (pi.skipOnPointerObservable) {
+                                    if(this._checkPrePointerObservable(null, evt, PointerEventTypes.POINTERTAP)){
                                         return;
                                     }
                                 }
                                 if (clickInfo.doubleClick && this.onPrePointerObservable.hasSpecificMask(PointerEventTypes.POINTERDOUBLETAP)) {
-                                    let type = PointerEventTypes.POINTERDOUBLETAP;
-                                    let pi = new PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
-                                    this.onPrePointerObservable.notifyObservers(pi, type);
-                                    if (pi.skipOnPointerObservable) {
+                                    if(this._checkPrePointerObservable(null, evt, PointerEventTypes.POINTERDOUBLETAP)){
                                         return;
                                     }
                                 }
                             }
                         }
                         else {
-                            let type = PointerEventTypes.POINTERUP;
-                            let pi = new PointerInfoPre(type, evt, this._unTranslatedPointerX, this._unTranslatedPointerY);
-                            this.onPrePointerObservable.notifyObservers(pi, type);
-                            if (pi.skipOnPointerObservable) {
+                            if(this._checkPrePointerObservable(null, evt, PointerEventTypes.POINTERUP)){
                                 return;
                             }
                         }
@@ -4710,7 +4716,7 @@
 
         /** 
          * Render the scene
-         * @param updateCamerasd defines a boolean indicating if cameras must update according to their inputs (true by default)
+         * @param updateCameras defines a boolean indicating if cameras must update according to their inputs (true by default)
          */
         public render(updateCameras = true): void {
             if (this.isDisposed) {
@@ -5739,7 +5745,7 @@
          * @param fastCheck Launch a fast check only using the bounding boxes. Can be set to null
          * @returns a PickingInfo
          */
-        public pickWithRay(ray: Ray, predicate: (mesh: AbstractMesh) => boolean, fastCheck?: boolean): Nullable<PickingInfo> {
+        public pickWithRay(ray: Ray, predicate?: (mesh: AbstractMesh) => boolean, fastCheck?: boolean): Nullable<PickingInfo> {
             var result = this._internalPick(world => {
                 if (!this._pickWithRayInverseMatrix) {
                     this._pickWithRayInverseMatrix = Matrix.Identity();