瀏覽代碼

Merge branch 'master' into pointerDragBehaviorWith6Dof

Trevor Baron 7 年之前
父節點
當前提交
a9c00e9257
共有 81 個文件被更改,包括 22325 次插入18769 次删除
  1. 3121 2772
      Playground/babylon.d.txt
  2. 14 1
      Tools/Gulp/config.json
  3. 14123 14074
      dist/preview release/babylon.d.ts
  4. 13 13
      dist/preview release/babylon.js
  5. 29 10
      dist/preview release/babylon.max.js
  6. 29 10
      dist/preview release/babylon.no-module.max.js
  7. 13 13
      dist/preview release/babylon.worker.js
  8. 29 10
      dist/preview release/es6.js
  9. 1 1
      dist/preview release/gltf2Interface/package.json
  10. 311 1
      dist/preview release/gui/babylon.gui.d.ts
  11. 867 2
      dist/preview release/gui/babylon.gui.js
  12. 4 4
      dist/preview release/gui/babylon.gui.min.js
  13. 311 1
      dist/preview release/gui/babylon.gui.module.d.ts
  14. 1 1
      dist/preview release/gui/package.json
  15. 5 5
      dist/preview release/inspector/babylon.inspector.bundle.js
  16. 25 0
      dist/preview release/inspector/babylon.inspector.css
  17. 14 1
      dist/preview release/inspector/babylon.inspector.d.ts
  18. 132 8
      dist/preview release/inspector/babylon.inspector.js
  19. 4 4
      dist/preview release/inspector/babylon.inspector.min.js
  20. 1 1
      dist/preview release/inspector/package.json
  21. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.d.ts
  22. 17 8
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  23. 2 2
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  24. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.d.ts
  25. 17 8
      dist/preview release/loaders/babylon.glTFFileLoader.js
  26. 2 2
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  27. 1 1
      dist/preview release/loaders/babylonjs.loaders.d.ts
  28. 17 8
      dist/preview release/loaders/babylonjs.loaders.js
  29. 4 4
      dist/preview release/loaders/babylonjs.loaders.min.js
  30. 1 1
      dist/preview release/loaders/babylonjs.loaders.module.d.ts
  31. 2 2
      dist/preview release/loaders/package.json
  32. 1 1
      dist/preview release/materialsLibrary/package.json
  33. 1 1
      dist/preview release/postProcessesLibrary/package.json
  34. 1 1
      dist/preview release/proceduralTexturesLibrary/package.json
  35. 27 31
      dist/preview release/serializers/babylon.glTF2Serializer.d.ts
  36. 453 407
      dist/preview release/serializers/babylon.glTF2Serializer.js
  37. 2 2
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  38. 27 31
      dist/preview release/serializers/babylonjs.serializers.d.ts
  39. 453 407
      dist/preview release/serializers/babylonjs.serializers.js
  40. 2 2
      dist/preview release/serializers/babylonjs.serializers.min.js
  41. 27 31
      dist/preview release/serializers/babylonjs.serializers.module.d.ts
  42. 2 2
      dist/preview release/serializers/package.json
  43. 2 40
      dist/preview release/typedocValidationBaseline.json
  44. 50 50
      dist/preview release/viewer/babylon.viewer.js
  45. 46 18
      dist/preview release/viewer/babylon.viewer.max.js
  46. 1 1
      dist/preview release/viewer/package.json
  47. 2 0
      dist/preview release/what's new.md
  48. 6 1
      gui/src/2D/advancedDynamicTexture.ts
  49. 105 0
      gui/src/3D/controls/button3D.ts
  50. 82 0
      gui/src/3D/controls/container3D.ts
  51. 368 1
      gui/src/3D/controls/control3D.ts
  52. 56 0
      gui/src/3D/controls/holographicButton.ts
  53. 181 0
      gui/src/3D/gui3DManager.ts
  54. 173 0
      gui/src/3D/materials/fluentMaterial.ts
  55. 10 0
      gui/src/3D/materials/shaders/fluent.fragment.fx
  56. 19 0
      gui/src/3D/materials/shaders/fluent.vertex.fx
  57. 9 0
      gui/src/3D/vector3WithInfo.ts
  58. 38 0
      inspector/sass/tabs/_gltfTab.scss
  59. 165 9
      inspector/src/tabs/GLTFTab.ts
  60. 4 3
      inspector/src/tsconfig.json
  61. 17 8
      loaders/src/glTF/2.0/babylon.glTFLoader.ts
  62. 2 2
      package.json
  63. 9 2
      sandbox/index-local.html
  64. 41 52
      sandbox/index.js
  65. 141 164
      serializers/src/glTF/2.0/babylon.glTFExporter.ts
  66. 341 236
      serializers/src/glTF/2.0/babylon.glTFMaterial.ts
  67. 8 31
      serializers/src/glTF/2.0/babylon.glTFSerializer.ts
  68. 10 0
      serializers/src/glTF/2.0/shaders/setAlphaToOne.fragment.fx
  69. 1 3
      src/Animations/babylon.animatable.ts
  70. 28 4
      src/Animations/babylon.animationGroup.ts
  71. 40 2
      src/Behaviors/babylon.behavior.ts
  72. 7 0
      src/Debug/babylon.debugLayer.ts
  73. 1 1
      src/Engine/babylon.engine.ts
  74. 3 2
      src/Materials/Textures/babylon.dynamicTexture.ts
  75. 8 0
      src/Mesh/babylon.abstractMesh.ts
  76. 3 0
      src/Rendering/babylon.edgesRenderer.ts
  77. 1 1
      src/Rendering/babylon.renderingGroup.ts
  78. 4 1
      src/Tools/babylon.filesInput.ts
  79. 2 6
      src/babylon.node.ts
  80. 217 244
      tests/unit/babylon/serializers/babylon.glTFSerializer.tests.ts
  81. 16 1
      tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts

文件差異過大導致無法顯示
+ 3121 - 2772
Playground/babylon.d.txt


+ 14 - 1
Tools/Gulp/config.json

@@ -1622,6 +1622,9 @@
                     "../../serializers/src/glTF/2.0/babylon.glTFAnimation.ts",
                     "../../serializers/src/glTF/2.0/babylon.glTFUtilities.ts"
                 ],
+                "shaderFiles": [
+                    "../../serializers/src/glTF/2.0/shaders/setAlphaToOne.fragment.fx"
+                ],
                 "output": "babylon.glTF2Serializer.js"
             }
         ],
@@ -1668,7 +1671,17 @@
                     "../../gui/src/2D/controls/inputText.ts",
                     "../../gui/src/2D/controls/virtualKeyboard.ts",
                     "../../gui/src/2D/controls/multiLine.ts",
-                    "../../gui/src/3D/controls/control3D.ts"
+                    "../../gui/src/3D/gui3DManager.ts",
+                    "../../gui/src/3D/materials/fluentMaterial.ts",
+                    "../../gui/src/3D/vector3WithInfo.ts",
+                    "../../gui/src/3D/controls/control3D.ts",
+                    "../../gui/src/3D/controls/container3D.ts",
+                    "../../gui/src/3D/controls/button3D.ts",
+                    "../../gui/src/3D/controls/holographicButton.ts"
+                ],
+                "shaderFiles": [
+                    "../../gui/src/3D/materials/shaders/fluent.vertex.fx",
+                    "../../gui/src/3D/materials/shaders/fluent.fragment.fx"
                 ],
                 "output": "babylon.gui.js",
                 "buildAsModule": true,

文件差異過大導致無法顯示
+ 14123 - 14074
dist/preview release/babylon.d.ts


文件差異過大導致無法顯示
+ 13 - 13
dist/preview release/babylon.js


+ 29 - 10
dist/preview release/babylon.max.js

@@ -12085,7 +12085,7 @@ var BABYLON;
              * Returns the current version of the framework
              */
             get: function () {
-                return "3.3.0-alpha.1";
+                return "3.3.0-alpha.2";
             },
             enumerable: true,
             configurable: true
@@ -17657,12 +17657,8 @@ var BABYLON;
             behavior.init();
             if (this._scene.isLoading) {
                 // We defer the attach when the scene will be loaded
-                var observer = this._scene.onDataLoadedObservable.add(function () {
+                this._scene.onDataLoadedObservable.addOnce(function () {
                     behavior.attach(_this);
-                    setTimeout(function () {
-                        // Need to use a timeout to avoid removing an observer while iterating the list of observers
-                        _this._scene.onDataLoadedObservable.remove(observer);
-                    }, 0);
                 });
             }
             else {
@@ -20157,6 +20153,16 @@ var BABYLON;
             this._edgesRenderer = new BABYLON.EdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices);
             return this;
         };
+        Object.defineProperty(AbstractMesh.prototype, "edgesRenderer", {
+            /**
+             * Gets the edgesRenderer associated with the mesh
+             */
+            get: function () {
+                return this._edgesRenderer;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(AbstractMesh.prototype, "isBlocked", {
             /**
              * Returns true if the mesh is blocked. Implemented by child classes
@@ -23594,7 +23600,7 @@ var BABYLON;
                 }
                 this._opaqueSubMeshes.push(subMesh); // Opaque
             }
-            if (mesh._edgesRenderer !== null && mesh._edgesRenderer !== undefined) {
+            if (mesh._edgesRenderer !== null && mesh._edgesRenderer !== undefined && mesh._edgesRenderer.isEnabled) {
                 this._edgesRenderers.push(mesh._edgesRenderer);
             }
         };
@@ -65117,9 +65123,11 @@ var BABYLON;
         /**
          * Updates the texture
          * @param invertY defines the direction for the Y axis (default is true - y increases downwards)
+         * @param premulAlpha defines if alpha is stored as premultiplied (default is false)
          */
-        DynamicTexture.prototype.update = function (invertY) {
-            this._engine.updateDynamicTexture(this._texture, this._canvas, invertY === undefined ? true : invertY, undefined, this._format || undefined);
+        DynamicTexture.prototype.update = function (invertY, premulAlpha) {
+            if (premulAlpha === void 0) { premulAlpha = false; }
+            this._engine.updateDynamicTexture(this._texture, this._canvas, invertY === undefined ? true : invertY, premulAlpha, this._format || undefined);
         };
         /**
          * Draws text onto the texture
@@ -69064,13 +69072,15 @@ var BABYLON;
                         BABYLON.Tools.ClearLogCache();
                     }
                     this._engine.stopRenderLoop();
-                    this._currentScene.dispose();
                 }
                 BABYLON.SceneLoader.LoadAsync("file:", this._sceneFileToLoad, this._engine, function (progress) {
                     if (_this._progressCallback) {
                         _this._progressCallback(progress);
                     }
                 }).then(function (scene) {
+                    if (_this._currentScene) {
+                        _this._currentScene.dispose();
+                    }
                     _this._currentScene = scene;
                     if (_this._sceneLoadedCallback) {
                         _this._sceneLoadedCallback(_this._sceneFileToLoad, _this._currentScene);
@@ -86393,6 +86403,13 @@ var BABYLON;
                 this._createInspector(config);
             }
         };
+        /**
+         * Gets the active tab
+         * @return the index of the active tab or -1 if the inspector is hidden
+         */
+        DebugLayer.prototype.getActiveTab = function () {
+            return this._inspector ? this._inspector.getActiveTabIndex() : -1;
+        };
         DebugLayer.InspectorURL = 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js';
         return DebugLayer;
     }());
@@ -92234,6 +92251,8 @@ var BABYLON;
             this._linesIndices = new Array();
             this._buffers = {};
             this._checkVerticesInsteadOfIndices = false;
+            /** Gets or sets a boolean indicating if the edgesRenderer is active */
+            this.isEnabled = true;
             this._source = source;
             this._checkVerticesInsteadOfIndices = checkVerticesInsteadOfIndices;
             this._epsilon = epsilon;

+ 29 - 10
dist/preview release/babylon.no-module.max.js

@@ -12052,7 +12052,7 @@ var BABYLON;
              * Returns the current version of the framework
              */
             get: function () {
-                return "3.3.0-alpha.1";
+                return "3.3.0-alpha.2";
             },
             enumerable: true,
             configurable: true
@@ -17624,12 +17624,8 @@ var BABYLON;
             behavior.init();
             if (this._scene.isLoading) {
                 // We defer the attach when the scene will be loaded
-                var observer = this._scene.onDataLoadedObservable.add(function () {
+                this._scene.onDataLoadedObservable.addOnce(function () {
                     behavior.attach(_this);
-                    setTimeout(function () {
-                        // Need to use a timeout to avoid removing an observer while iterating the list of observers
-                        _this._scene.onDataLoadedObservable.remove(observer);
-                    }, 0);
                 });
             }
             else {
@@ -20124,6 +20120,16 @@ var BABYLON;
             this._edgesRenderer = new BABYLON.EdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices);
             return this;
         };
+        Object.defineProperty(AbstractMesh.prototype, "edgesRenderer", {
+            /**
+             * Gets the edgesRenderer associated with the mesh
+             */
+            get: function () {
+                return this._edgesRenderer;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(AbstractMesh.prototype, "isBlocked", {
             /**
              * Returns true if the mesh is blocked. Implemented by child classes
@@ -23561,7 +23567,7 @@ var BABYLON;
                 }
                 this._opaqueSubMeshes.push(subMesh); // Opaque
             }
-            if (mesh._edgesRenderer !== null && mesh._edgesRenderer !== undefined) {
+            if (mesh._edgesRenderer !== null && mesh._edgesRenderer !== undefined && mesh._edgesRenderer.isEnabled) {
                 this._edgesRenderers.push(mesh._edgesRenderer);
             }
         };
@@ -65084,9 +65090,11 @@ var BABYLON;
         /**
          * Updates the texture
          * @param invertY defines the direction for the Y axis (default is true - y increases downwards)
+         * @param premulAlpha defines if alpha is stored as premultiplied (default is false)
          */
-        DynamicTexture.prototype.update = function (invertY) {
-            this._engine.updateDynamicTexture(this._texture, this._canvas, invertY === undefined ? true : invertY, undefined, this._format || undefined);
+        DynamicTexture.prototype.update = function (invertY, premulAlpha) {
+            if (premulAlpha === void 0) { premulAlpha = false; }
+            this._engine.updateDynamicTexture(this._texture, this._canvas, invertY === undefined ? true : invertY, premulAlpha, this._format || undefined);
         };
         /**
          * Draws text onto the texture
@@ -69031,13 +69039,15 @@ var BABYLON;
                         BABYLON.Tools.ClearLogCache();
                     }
                     this._engine.stopRenderLoop();
-                    this._currentScene.dispose();
                 }
                 BABYLON.SceneLoader.LoadAsync("file:", this._sceneFileToLoad, this._engine, function (progress) {
                     if (_this._progressCallback) {
                         _this._progressCallback(progress);
                     }
                 }).then(function (scene) {
+                    if (_this._currentScene) {
+                        _this._currentScene.dispose();
+                    }
                     _this._currentScene = scene;
                     if (_this._sceneLoadedCallback) {
                         _this._sceneLoadedCallback(_this._sceneFileToLoad, _this._currentScene);
@@ -86360,6 +86370,13 @@ var BABYLON;
                 this._createInspector(config);
             }
         };
+        /**
+         * Gets the active tab
+         * @return the index of the active tab or -1 if the inspector is hidden
+         */
+        DebugLayer.prototype.getActiveTab = function () {
+            return this._inspector ? this._inspector.getActiveTabIndex() : -1;
+        };
         DebugLayer.InspectorURL = 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js';
         return DebugLayer;
     }());
@@ -92201,6 +92218,8 @@ var BABYLON;
             this._linesIndices = new Array();
             this._buffers = {};
             this._checkVerticesInsteadOfIndices = false;
+            /** Gets or sets a boolean indicating if the edgesRenderer is active */
+            this.isEnabled = true;
             this._source = source;
             this._checkVerticesInsteadOfIndices = checkVerticesInsteadOfIndices;
             this._epsilon = epsilon;

文件差異過大導致無法顯示
+ 13 - 13
dist/preview release/babylon.worker.js


+ 29 - 10
dist/preview release/es6.js

@@ -12052,7 +12052,7 @@ var BABYLON;
              * Returns the current version of the framework
              */
             get: function () {
-                return "3.3.0-alpha.1";
+                return "3.3.0-alpha.2";
             },
             enumerable: true,
             configurable: true
@@ -17624,12 +17624,8 @@ var BABYLON;
             behavior.init();
             if (this._scene.isLoading) {
                 // We defer the attach when the scene will be loaded
-                var observer = this._scene.onDataLoadedObservable.add(function () {
+                this._scene.onDataLoadedObservable.addOnce(function () {
                     behavior.attach(_this);
-                    setTimeout(function () {
-                        // Need to use a timeout to avoid removing an observer while iterating the list of observers
-                        _this._scene.onDataLoadedObservable.remove(observer);
-                    }, 0);
                 });
             }
             else {
@@ -20124,6 +20120,16 @@ var BABYLON;
             this._edgesRenderer = new BABYLON.EdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices);
             return this;
         };
+        Object.defineProperty(AbstractMesh.prototype, "edgesRenderer", {
+            /**
+             * Gets the edgesRenderer associated with the mesh
+             */
+            get: function () {
+                return this._edgesRenderer;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(AbstractMesh.prototype, "isBlocked", {
             /**
              * Returns true if the mesh is blocked. Implemented by child classes
@@ -23561,7 +23567,7 @@ var BABYLON;
                 }
                 this._opaqueSubMeshes.push(subMesh); // Opaque
             }
-            if (mesh._edgesRenderer !== null && mesh._edgesRenderer !== undefined) {
+            if (mesh._edgesRenderer !== null && mesh._edgesRenderer !== undefined && mesh._edgesRenderer.isEnabled) {
                 this._edgesRenderers.push(mesh._edgesRenderer);
             }
         };
@@ -65084,9 +65090,11 @@ var BABYLON;
         /**
          * Updates the texture
          * @param invertY defines the direction for the Y axis (default is true - y increases downwards)
+         * @param premulAlpha defines if alpha is stored as premultiplied (default is false)
          */
-        DynamicTexture.prototype.update = function (invertY) {
-            this._engine.updateDynamicTexture(this._texture, this._canvas, invertY === undefined ? true : invertY, undefined, this._format || undefined);
+        DynamicTexture.prototype.update = function (invertY, premulAlpha) {
+            if (premulAlpha === void 0) { premulAlpha = false; }
+            this._engine.updateDynamicTexture(this._texture, this._canvas, invertY === undefined ? true : invertY, premulAlpha, this._format || undefined);
         };
         /**
          * Draws text onto the texture
@@ -69031,13 +69039,15 @@ var BABYLON;
                         BABYLON.Tools.ClearLogCache();
                     }
                     this._engine.stopRenderLoop();
-                    this._currentScene.dispose();
                 }
                 BABYLON.SceneLoader.LoadAsync("file:", this._sceneFileToLoad, this._engine, function (progress) {
                     if (_this._progressCallback) {
                         _this._progressCallback(progress);
                     }
                 }).then(function (scene) {
+                    if (_this._currentScene) {
+                        _this._currentScene.dispose();
+                    }
                     _this._currentScene = scene;
                     if (_this._sceneLoadedCallback) {
                         _this._sceneLoadedCallback(_this._sceneFileToLoad, _this._currentScene);
@@ -86360,6 +86370,13 @@ var BABYLON;
                 this._createInspector(config);
             }
         };
+        /**
+         * Gets the active tab
+         * @return the index of the active tab or -1 if the inspector is hidden
+         */
+        DebugLayer.prototype.getActiveTab = function () {
+            return this._inspector ? this._inspector.getActiveTabIndex() : -1;
+        };
         DebugLayer.InspectorURL = 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js';
         return DebugLayer;
     }());
@@ -92201,6 +92218,8 @@ var BABYLON;
             this._linesIndices = new Array();
             this._buffers = {};
             this._checkVerticesInsteadOfIndices = false;
+            /** Gets or sets a boolean indicating if the edgesRenderer is active */
+            this.isEnabled = true;
             this._source = source;
             this._checkVerticesInsteadOfIndices = checkVerticesInsteadOfIndices;
             this._epsilon = epsilon;

+ 1 - 1
dist/preview release/gltf2Interface/package.json

@@ -1,7 +1,7 @@
 {
     "name": "babylonjs-gltf2interface",
     "description": "A typescript declaration of babylon's gltf2 inteface.",
-    "version": "3.3.0-alpha.1",
+    "version": "3.3.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 311 - 1
dist/preview release/gui/babylon.gui.d.ts

@@ -99,6 +99,10 @@ declare module BABYLON.GUI {
         private _focusedControl;
         private _blockNextFocusCheck;
         private _renderScale;
+        /**
+         * Gets or sets a boolean defining if alpha is stored as premultiplied
+         */
+        premulAlpha: boolean;
         renderScale: number;
         background: string;
         idealWidth: number;
@@ -965,8 +969,314 @@ declare module BABYLON.GUI {
 
 
 declare module BABYLON.GUI {
-    class Control3D {
+    /**
+     * Class used to manage 3D user interface
+     */
+    class GUI3DManager implements BABYLON.IDisposable {
+        private _scene;
+        private _sceneDisposeObserver;
+        private _utilityLayer;
+        private _rootContainer;
+        private _pointerObserver;
+        _lastPickedControl: Control3D;
+        /** @hidden */
+        _lastControlOver: {
+            [pointerId: number]: Control3D;
+        };
+        /** @hidden */
+        _lastControlDown: {
+            [pointerId: number]: Control3D;
+        };
+        /** Gets the hosting scene */
+        readonly scene: Scene;
+        readonly utilityLayer: Nullable<UtilityLayerRenderer>;
+        /**
+         * Creates a new GUI3DManager
+         * @param scene
+         */
+        constructor(scene?: Scene);
+        private _doPicking(type, pointerEvent);
+        /**
+         * Gets the root container
+         */
+        readonly rootContainer: Container3D;
+        /**
+         * Gets a boolean indicating if the given control is in the root child list
+         * @param control defines the control to check
+         * @returns true if the control is in the root child list
+         */
+        containsControl(control: Control3D): boolean;
+        /**
+         * Adds a control to the root child list
+         * @param control defines the control to add
+         * @returns the current manager
+         */
+        addControl(control: Control3D): GUI3DManager;
+        /**
+         * Removes the control from the root child list
+         * @param control defines the control to remove
+         * @returns the current container
+         */
+        removeControl(control: Control3D): GUI3DManager;
+        /**
+         * Releases all associated resources
+         */
+        dispose(): void;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    /**
+     * Class used to render controls with fluent desgin
+     */
+    class FluentMaterial extends PushMaterial {
+        private _emissiveTexture;
+        emissiveTexture: BaseTexture;
+        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;
+        getActiveTextures(): BaseTexture[];
+        hasTexture(texture: BaseTexture): boolean;
+        dispose(forceDisposeEffect?: boolean): void;
+        clone(name: string): FluentMaterial;
+        serialize(): any;
+        getClassName(): string;
+        static Parse(source: any, scene: Scene, rootUrl: string): FluentMaterial;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    class Vector3WithInfo extends Vector3 {
+        buttonIndex: number;
+        constructor(source: Vector3, buttonIndex?: number);
+    }
+}
+
+
+declare module BABYLON.GUI {
+    /**
+     * Class used as base class for controls
+     */
+    class Control3D implements IDisposable, IBehaviorAware<Control3D> {
+        /** Defines the control name */
+        name: string | undefined;
+        /** @hidden */
+        _host: GUI3DManager;
+        private _mesh;
+        private _downCount;
+        private _enterCount;
+        private _downPointerIds;
+        private _isVisible;
+        /** Callback used to start pointer enter animation */
+        pointerEnterAnimation: () => void;
+        /** Callback used to start pointer out animation */
+        pointerOutAnimation: () => void;
+        /** Callback used to start pointer down animation */
+        pointerDownAnimation: () => void;
+        /** Callback used to start pointer up animation */
+        pointerUpAnimation: () => void;
+        /**
+        * An event triggered when the pointer move over the control.
+        */
+        onPointerMoveObservable: Observable<Vector3>;
+        /**
+         * An event triggered when the pointer move out of the control.
+         */
+        onPointerOutObservable: Observable<Control3D>;
+        /**
+         * An event triggered when the pointer taps the control
+         */
+        onPointerDownObservable: Observable<Vector3WithInfo>;
+        /**
+         * An event triggered when pointer up
+         */
+        onPointerUpObservable: Observable<Vector3WithInfo>;
+        /**
+         * An event triggered when a control is clicked on
+         */
+        onPointerClickObservable: Observable<Vector3WithInfo>;
+        /**
+         * An event triggered when pointer enters the control
+         */
+        onPointerEnterObservable: Observable<Control3D>;
+        /**
+         * Gets or sets the parent container
+         */
+        parent: Nullable<Container3D>;
+        private _behaviors;
+        /**
+         * Gets the list of attached behaviors
+         * @see http://doc.babylonjs.com/features/behaviour
+         */
+        readonly behaviors: Behavior<Control3D>[];
+        /**
+         * Attach a behavior to the control
+         * @see http://doc.babylonjs.com/features/behaviour
+         * @param behavior defines the behavior to attach
+         * @returns the current control
+         */
+        addBehavior(behavior: Behavior<Control3D>): Control3D;
+        /**
+         * Remove an attached behavior
+         * @see http://doc.babylonjs.com/features/behaviour
+         * @param behavior defines the behavior to attach
+         * @returns the current control
+         */
+        removeBehavior(behavior: Behavior<Control3D>): Control3D;
+        /**
+         * Gets an attached behavior by name
+         * @param name defines the name of the behavior to look for
+         * @see http://doc.babylonjs.com/features/behaviour
+         * @returns null if behavior was not found else the requested behavior
+         */
+        getBehaviorByName(name: string): Nullable<Behavior<Control3D>>;
+        /** Gets or sets a boolean indicating if the control is visible */
+        isVisible: boolean;
+        /** Gets or sets the control position */
+        position: Vector3;
+        /**
+         * Creates a new control
+         * @param name defines the control name
+         */
+        constructor(
+            /** Defines the control name */
+            name?: string | undefined);
+        /**
+         * Gets a string representing the class name
+         */
         readonly typeName: string;
         protected _getTypeName(): string;
+        /**
+         * Gets the mesh used to render this control
+         */
+        readonly mesh: Nullable<Mesh>;
+        /**
+         * Link the control as child of the given mesh
+         * @param mesh defines the mesh to link to. Use null to unlink the control
+         * @returns the current control
+         */
+        linkToMesh(mesh: Nullable<AbstractMesh>): Control3D;
+        /**
+         * Get the attached mesh used to render the control
+         * @param scene defines the scene where the mesh must be attached
+         * @returns the attached mesh or null if none
+         */
+        prepareMesh(scene: Scene): Nullable<Mesh>;
+        /**
+         * Mesh creation.
+         * Can be overriden by children
+         * @param scene defines the scene where the mesh must be attached
+         * @returns the attached mesh or null if none
+         */
+        protected _createMesh(scene: Scene): Nullable<Mesh>;
+        /**
+         * Affect a material to the given mesh
+         * @param mesh defines the mesh which will represent the control
+         */
+        protected _affectMaterial(mesh: Mesh): void;
+        /** @hidden */
+        _onPointerMove(target: Control3D, coordinates: Vector3): void;
+        /** @hidden */
+        _onPointerEnter(target: Control3D): boolean;
+        /** @hidden */
+        _onPointerOut(target: Control3D): void;
+        /** @hidden */
+        _onPointerDown(target: Control3D, coordinates: Vector3, pointerId: number, buttonIndex: number): boolean;
+        /** @hidden */
+        _onPointerUp(target: Control3D, coordinates: Vector3, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
+        /** @hidden */
+        forcePointerUp(pointerId?: Nullable<number>): void;
+        /** @hidden */
+        _processObservables(type: number, pickedPoint: Vector3, pointerId: number, buttonIndex: number): boolean;
+        /**
+         * Releases all associated resources
+         */
+        dispose(): void;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    /**
+     * Class used to create containers for controls
+     */
+    class Container3D extends Control3D {
+        private _children;
+        /**
+         * Creates a new container
+         * @param name defines the container name
+         */
+        constructor(name?: string);
+        /**
+         * Gets a boolean indicating if the given control is in the children of this control
+         * @param control defines the control to check
+         * @returns true if the control is in the child list
+         */
+        containsControl(control: Control3D): boolean;
+        /**
+         * Adds a control to the children of this control
+         * @param control defines the control to add
+         * @returns the current container
+         */
+        addControl(control: Control3D): Container3D;
+        /**
+         * Removes the control from the children of this control
+         * @param control defines the control to remove
+         * @returns the current container
+         */
+        removeControl(control: Control3D): Container3D;
+        protected _getTypeName(): string;
+        /**
+         * Releases all associated resources
+         */
+        dispose(): void;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    /**
+     * Class used to create a button in 3D
+     */
+    class Button3D extends Control3D {
+        /** @hidden */
+        protected _currentMaterial: Material;
+        private _facadeTexture;
+        private _content;
+        /**
+         * Creates a new button
+         * @param name defines the control name
+         */
+        constructor(name?: string);
+        /**
+         * Gets or sets the GUI 2D content used to display the button's facade
+         */
+        content: Control;
+        protected _getTypeName(): string;
+        protected _createMesh(scene: Scene): Mesh;
+        protected _affectMaterial(mesh: Mesh): void;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    /**
+     * Class used to create a button in 3D
+     */
+    class HolographicButton extends Button3D {
+        /**
+         * Creates a new button
+         * @param name defines the control name
+         */
+        constructor(name?: string);
+        protected _getTypeName(): string;
+        protected _createMesh(scene: Scene): Mesh;
+        protected _affectMaterial(mesh: Mesh): void;
     }
 }

+ 867 - 2
dist/preview release/gui/babylon.gui.js

@@ -23,6 +23,9 @@
 
 var __decorate=this&&this.__decorate||function(e,t,r,c){var o,f=arguments.length,n=f<3?t:null===c?c=Object.getOwnPropertyDescriptor(t,r):c;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,r,c);else for(var l=e.length-1;l>=0;l--)(o=e[l])&&(n=(f<3?o(n):f>3?o(t,r,n):o(t,r))||n);return f>3&&n&&Object.defineProperty(t,r,n),n};
 var __extends=this&&this.__extends||function(){var t=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,o){t.__proto__=o}||function(t,o){for(var n in o)o.hasOwnProperty(n)&&(t[n]=o[n])};return function(o,n){function r(){this.constructor=o}t(o,n),o.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}}();
+BABYLON.Effect.ShadersStore['fluentVertexShader'] = "precision highp float;\n\nattribute vec3 position;\nattribute vec3 normal;\nattribute vec2 uv;\n\nuniform mat4 world;\nuniform mat4 viewProjection;\nuniform mat4 emissiveMatrix;\nvarying vec2 vEmissiveUV;\nvoid main(void) {\nvEmissiveUV=vec2(emissiveMatrix*vec4(uv,1.0,0.0));\ngl_Position=viewProjection*world*vec4(position,1.0);\n}\n";
+BABYLON.Effect.ShadersStore['fluentPixelShader'] = "precision highp float;\nvarying vec2 vEmissiveUV;\nuniform sampler2D emissiveSampler;\nvoid main(void) {\nvec3 emissiveColor=texture2D(emissiveSampler,vEmissiveUV).rgb;\ngl_FragColor=vec4(emissiveColor,1.0);\n}";
+
 /// <reference path="../../../dist/preview release/babylon.d.ts"/>
 var BABYLON;
 (function (BABYLON) {
@@ -269,6 +272,10 @@ var BABYLON;
                 _this._renderAtIdealSize = false;
                 _this._blockNextFocusCheck = false;
                 _this._renderScale = 1;
+                /**
+                 * Gets or sets a boolean defining if alpha is stored as premultiplied
+                 */
+                _this.premulAlpha = false;
                 scene = _this.getScene();
                 if (!scene || !_this._texture) {
                     return _this;
@@ -578,7 +585,7 @@ var BABYLON;
                 }
                 this._isDirty = false;
                 this._render();
-                this.update();
+                this.update(true, this.premulAlpha);
             };
             AdvancedDynamicTexture.prototype._render = function () {
                 var textureSize = this.getSize();
@@ -5592,15 +5599,484 @@ var BABYLON;
     })(GUI = BABYLON.GUI || (BABYLON.GUI = {}));
 })(BABYLON || (BABYLON = {}));
 
+/// <reference path="../../../dist/preview release/babylon.d.ts"/>
+var BABYLON;
+(function (BABYLON) {
+    var GUI;
+    (function (GUI) {
+        /**
+         * Class used to manage 3D user interface
+         */
+        var GUI3DManager = /** @class */ (function () {
+            /**
+             * Creates a new GUI3DManager
+             * @param scene
+             */
+            function GUI3DManager(scene) {
+                var _this = this;
+                /** @hidden */
+                this._lastControlOver = {};
+                /** @hidden */
+                this._lastControlDown = {};
+                this._scene = scene || BABYLON.Engine.LastCreatedScene;
+                this._sceneDisposeObserver = this._scene.onDisposeObservable.add(function () {
+                    _this._sceneDisposeObserver = null;
+                    _this._utilityLayer = null;
+                    _this.dispose();
+                });
+                this._utilityLayer = new BABYLON.UtilityLayerRenderer(this._scene);
+                // Root
+                this._rootContainer = new GUI.Container3D("RootContainer");
+                this._rootContainer._host = this;
+                // Events
+                this._pointerObserver = this._scene.onPrePointerObservable.add(function (pi, state) {
+                    var pointerEvent = (pi.event);
+                    if (_this._scene.isPointerCaptured(pointerEvent.pointerId)) {
+                        return;
+                    }
+                    if (pi.type !== BABYLON.PointerEventTypes.POINTERMOVE
+                        && pi.type !== BABYLON.PointerEventTypes.POINTERUP
+                        && pi.type !== BABYLON.PointerEventTypes.POINTERDOWN) {
+                        return;
+                    }
+                    var camera = _this._scene.cameraToUseForPointers || _this._scene.activeCamera;
+                    if (!camera) {
+                        return;
+                    }
+                    pi.skipOnPointerObservable = _this._doPicking(pi.type, pointerEvent);
+                });
+                // Scene
+                this._utilityLayer.utilityLayerScene.autoClear = false;
+                this._utilityLayer.utilityLayerScene.autoClearDepthAndStencil = false;
+                new BABYLON.HemisphericLight("hemi", BABYLON.Vector3.Up(), this._utilityLayer.utilityLayerScene);
+            }
+            Object.defineProperty(GUI3DManager.prototype, "scene", {
+                /** Gets the hosting scene */
+                get: function () {
+                    return this._scene;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(GUI3DManager.prototype, "utilityLayer", {
+                get: function () {
+                    return this._utilityLayer;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            GUI3DManager.prototype._doPicking = function (type, pointerEvent) {
+                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);
+                if (!pickingInfo || !pickingInfo.hit) {
+                    var previousControlOver = this._lastControlOver[pointerId];
+                    if (previousControlOver) {
+                        previousControlOver._onPointerOut(previousControlOver);
+                        delete this._lastControlOver[pointerId];
+                    }
+                    if (type === BABYLON.PointerEventTypes.POINTERUP) {
+                        if (this._lastControlDown[pointerEvent.pointerId]) {
+                            this._lastControlDown[pointerEvent.pointerId].forcePointerUp();
+                            delete this._lastControlDown[pointerEvent.pointerId];
+                        }
+                    }
+                    return false;
+                }
+                var control = (pickingInfo.pickedMesh.metadata);
+                if (!control._processObservables(type, pickingInfo.pickedPoint, pointerId, buttonIndex)) {
+                    if (type === BABYLON.PointerEventTypes.POINTERMOVE) {
+                        if (this._lastControlOver[pointerId]) {
+                            this._lastControlOver[pointerId]._onPointerOut(this._lastControlOver[pointerId]);
+                        }
+                        delete this._lastControlOver[pointerId];
+                    }
+                }
+                if (type === BABYLON.PointerEventTypes.POINTERUP) {
+                    if (this._lastControlDown[pointerEvent.pointerId]) {
+                        this._lastControlDown[pointerEvent.pointerId].forcePointerUp();
+                        delete this._lastControlDown[pointerEvent.pointerId];
+                    }
+                }
+                return true;
+            };
+            Object.defineProperty(GUI3DManager.prototype, "rootContainer", {
+                /**
+                 * Gets the root container
+                 */
+                get: function () {
+                    return this._rootContainer;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            /**
+             * Gets a boolean indicating if the given control is in the root child list
+             * @param control defines the control to check
+             * @returns true if the control is in the root child list
+             */
+            GUI3DManager.prototype.containsControl = function (control) {
+                return this._rootContainer.containsControl(control);
+            };
+            /**
+             * Adds a control to the root child list
+             * @param control defines the control to add
+             * @returns the current manager
+             */
+            GUI3DManager.prototype.addControl = function (control) {
+                this._rootContainer.addControl(control);
+                return this;
+            };
+            /**
+             * Removes the control from the root child list
+             * @param control defines the control to remove
+             * @returns the current container
+             */
+            GUI3DManager.prototype.removeControl = function (control) {
+                this._rootContainer.removeControl(control);
+                return this;
+            };
+            /**
+             * Releases all associated resources
+             */
+            GUI3DManager.prototype.dispose = function () {
+                this._rootContainer.dispose();
+                if (this._scene) {
+                    if (this._pointerObserver) {
+                        this._scene.onPrePointerObservable.remove(this._pointerObserver);
+                        this._pointerObserver = null;
+                    }
+                    if (this._sceneDisposeObserver) {
+                        this._scene.onDisposeObservable.remove(this._sceneDisposeObserver);
+                        this._sceneDisposeObserver = null;
+                    }
+                }
+                if (this._utilityLayer) {
+                    this._utilityLayer.dispose();
+                }
+            };
+            return GUI3DManager;
+        }());
+        GUI.GUI3DManager = GUI3DManager;
+    })(GUI = BABYLON.GUI || (BABYLON.GUI = {}));
+})(BABYLON || (BABYLON = {}));
+
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+
+
+var BABYLON;
+(function (BABYLON) {
+    var GUI;
+    (function (GUI) {
+        /**
+         * Class used to render controls with fluent desgin
+         */
+        var FluentMaterial = /** @class */ (function (_super) {
+            __extends(FluentMaterial, _super);
+            function FluentMaterial(name, scene) {
+                return _super.call(this, name, scene) || this;
+            }
+            FluentMaterial.prototype.needAlphaBlending = function () {
+                return false;
+            };
+            FluentMaterial.prototype.needAlphaTesting = function () {
+                return false;
+            };
+            FluentMaterial.prototype.getAlphaTestTexture = function () {
+                return null;
+            };
+            FluentMaterial.prototype.isReadyForSubMesh = function (mesh, subMesh, useInstances) {
+                if (this.isFrozen) {
+                    if (this._wasPreviouslyReady && subMesh.effect) {
+                        return true;
+                    }
+                }
+                var scene = this.getScene();
+                if (!this.checkReadyOnEveryCall && subMesh.effect) {
+                    if (this._renderId === scene.getRenderId()) {
+                        return true;
+                    }
+                }
+                var engine = scene.getEngine();
+                scene.resetCachedMaterial();
+                //Attributes
+                var attribs = [BABYLON.VertexBuffer.PositionKind];
+                attribs.push(BABYLON.VertexBuffer.NormalKind);
+                attribs.push(BABYLON.VertexBuffer.UVKind);
+                var shaderName = "fluent";
+                var uniforms = ["world", "viewProjection", "emissiveMatrix"];
+                var samplers = ["emissiveSampler"];
+                var uniformBuffers = new Array();
+                BABYLON.MaterialHelper.PrepareUniformsAndSamplersList({
+                    uniformsNames: uniforms,
+                    uniformBuffersNames: uniformBuffers,
+                    samplers: samplers,
+                    defines: "",
+                    maxSimultaneousLights: 4
+                });
+                subMesh.setEffect(scene.getEngine().createEffect(shaderName, {
+                    attributes: attribs,
+                    uniformsNames: uniforms,
+                    uniformBuffersNames: uniformBuffers,
+                    samplers: samplers,
+                    defines: "",
+                    fallbacks: null,
+                    onCompiled: this.onCompiled,
+                    onError: this.onError,
+                    indexParameters: { maxSimultaneousLights: 4 }
+                }, engine));
+                if (!subMesh.effect || !subMesh.effect.isReady()) {
+                    return false;
+                }
+                this._renderId = scene.getRenderId();
+                this._wasPreviouslyReady = true;
+                return true;
+            };
+            FluentMaterial.prototype.bindForSubMesh = function (world, mesh, subMesh) {
+                var scene = this.getScene();
+                var effect = subMesh.effect;
+                if (!effect) {
+                    return;
+                }
+                this._activeEffect = effect;
+                // Matrices        
+                this.bindOnlyWorldMatrix(world);
+                this._activeEffect.setMatrix("viewProjection", scene.getTransformMatrix());
+                if (this._mustRebind(scene, effect)) {
+                    // Textures        
+                    if (this._emissiveTexture && BABYLON.StandardMaterial.DiffuseTextureEnabled) {
+                        this._activeEffect.setTexture("emissiveSampler", this._emissiveTexture);
+                        this._activeEffect.setMatrix("emissiveMatrix", this._emissiveTexture.getTextureMatrix());
+                    }
+                }
+                this._afterBind(mesh, this._activeEffect);
+            };
+            FluentMaterial.prototype.getActiveTextures = function () {
+                var activeTextures = _super.prototype.getActiveTextures.call(this);
+                if (this._emissiveTexture) {
+                    activeTextures.push(this._emissiveTexture);
+                }
+                return activeTextures;
+            };
+            FluentMaterial.prototype.hasTexture = function (texture) {
+                if (_super.prototype.hasTexture.call(this, texture)) {
+                    return true;
+                }
+                if (this._emissiveTexture === texture) {
+                    return true;
+                }
+                return false;
+            };
+            FluentMaterial.prototype.dispose = function (forceDisposeEffect) {
+                if (this._emissiveTexture) {
+                    this._emissiveTexture.dispose();
+                }
+                _super.prototype.dispose.call(this, forceDisposeEffect);
+            };
+            FluentMaterial.prototype.clone = function (name) {
+                var _this = this;
+                return BABYLON.SerializationHelper.Clone(function () { return new FluentMaterial(name, _this.getScene()); }, this);
+            };
+            FluentMaterial.prototype.serialize = function () {
+                var serializationObject = BABYLON.SerializationHelper.Serialize(this);
+                serializationObject.customType = "BABYLON.GUI.FluentMaterial";
+                return serializationObject;
+            };
+            FluentMaterial.prototype.getClassName = function () {
+                return "FluentMaterial";
+            };
+            // Statics
+            FluentMaterial.Parse = function (source, scene, rootUrl) {
+                return BABYLON.SerializationHelper.Parse(function () { return new FluentMaterial(source.name, scene); }, source, scene, rootUrl);
+            };
+            __decorate([
+                BABYLON.serializeAsTexture("emissiveTexture")
+            ], FluentMaterial.prototype, "_emissiveTexture", void 0);
+            __decorate([
+                BABYLON.expandToProperty("_markAllSubMeshesAsTexturesDirty")
+            ], FluentMaterial.prototype, "emissiveTexture", void 0);
+            return FluentMaterial;
+        }(BABYLON.PushMaterial));
+        GUI.FluentMaterial = FluentMaterial;
+    })(GUI = BABYLON.GUI || (BABYLON.GUI = {}));
+})(BABYLON || (BABYLON = {}));
+
+/// <reference path="../../../dist/preview release/babylon.d.ts"/>
+
+var BABYLON;
+(function (BABYLON) {
+    var GUI;
+    (function (GUI) {
+        var Vector3WithInfo = /** @class */ (function (_super) {
+            __extends(Vector3WithInfo, _super);
+            function Vector3WithInfo(source, buttonIndex) {
+                if (buttonIndex === void 0) { buttonIndex = 0; }
+                var _this = _super.call(this, source.x, source.y, source.z) || this;
+                _this.buttonIndex = buttonIndex;
+                return _this;
+            }
+            return Vector3WithInfo;
+        }(BABYLON.Vector3));
+        GUI.Vector3WithInfo = Vector3WithInfo;
+    })(GUI = BABYLON.GUI || (BABYLON.GUI = {}));
+})(BABYLON || (BABYLON = {}));
+
 /// <reference path="../../../../dist/preview release/babylon.d.ts"/>
 var BABYLON;
 (function (BABYLON) {
     var GUI;
     (function (GUI) {
+        /**
+         * Class used as base class for controls
+         */
         var Control3D = /** @class */ (function () {
-            function Control3D() {
+            /**
+             * Creates a new control
+             * @param name defines the control name
+             */
+            function Control3D(
+            /** Defines the control name */
+            name) {
+                this.name = name;
+                this._downCount = 0;
+                this._enterCount = 0;
+                this._downPointerIds = {};
+                this._isVisible = true;
+                /**
+                * An event triggered when the pointer move over the control.
+                */
+                this.onPointerMoveObservable = new BABYLON.Observable();
+                /**
+                 * An event triggered when the pointer move out of the control.
+                 */
+                this.onPointerOutObservable = new BABYLON.Observable();
+                /**
+                 * An event triggered when the pointer taps the control
+                 */
+                this.onPointerDownObservable = new BABYLON.Observable();
+                /**
+                 * An event triggered when pointer up
+                 */
+                this.onPointerUpObservable = new BABYLON.Observable();
+                /**
+                 * An event triggered when a control is clicked on
+                 */
+                this.onPointerClickObservable = new BABYLON.Observable();
+                /**
+                 * An event triggered when pointer enters the control
+                 */
+                this.onPointerEnterObservable = new BABYLON.Observable();
+                // Behaviors
+                this._behaviors = new Array();
             }
+            Object.defineProperty(Control3D.prototype, "behaviors", {
+                /**
+                 * Gets the list of attached behaviors
+                 * @see http://doc.babylonjs.com/features/behaviour
+                 */
+                get: function () {
+                    return this._behaviors;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            /**
+             * Attach a behavior to the control
+             * @see http://doc.babylonjs.com/features/behaviour
+             * @param behavior defines the behavior to attach
+             * @returns the current control
+             */
+            Control3D.prototype.addBehavior = function (behavior) {
+                var _this = this;
+                var index = this._behaviors.indexOf(behavior);
+                if (index !== -1) {
+                    return this;
+                }
+                behavior.init();
+                var scene = this._host.scene;
+                if (scene.isLoading) {
+                    // We defer the attach when the scene will be loaded
+                    scene.onDataLoadedObservable.addOnce(function () {
+                        behavior.attach(_this);
+                    });
+                }
+                else {
+                    behavior.attach(this);
+                }
+                this._behaviors.push(behavior);
+                return this;
+            };
+            /**
+             * Remove an attached behavior
+             * @see http://doc.babylonjs.com/features/behaviour
+             * @param behavior defines the behavior to attach
+             * @returns the current control
+             */
+            Control3D.prototype.removeBehavior = function (behavior) {
+                var index = this._behaviors.indexOf(behavior);
+                if (index === -1) {
+                    return this;
+                }
+                this._behaviors[index].detach();
+                this._behaviors.splice(index, 1);
+                return this;
+            };
+            /**
+             * Gets an attached behavior by name
+             * @param name defines the name of the behavior to look for
+             * @see http://doc.babylonjs.com/features/behaviour
+             * @returns null if behavior was not found else the requested behavior
+             */
+            Control3D.prototype.getBehaviorByName = function (name) {
+                for (var _i = 0, _a = this._behaviors; _i < _a.length; _i++) {
+                    var behavior = _a[_i];
+                    if (behavior.name === name) {
+                        return behavior;
+                    }
+                }
+                return null;
+            };
+            Object.defineProperty(Control3D.prototype, "isVisible", {
+                /** Gets or sets a boolean indicating if the control is visible */
+                get: function () {
+                    return this._isVisible;
+                },
+                set: function (value) {
+                    if (this._isVisible === value) {
+                        return;
+                    }
+                    this._isVisible = value;
+                    if (this._mesh) {
+                        this._mesh.isVisible = value;
+                    }
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Object.defineProperty(Control3D.prototype, "position", {
+                /** Gets or sets the control position */
+                get: function () {
+                    if (this._mesh) {
+                        return this._mesh.position;
+                    }
+                    return BABYLON.Vector3.Zero();
+                },
+                set: function (value) {
+                    if (this._mesh) {
+                        this._mesh.position = value;
+                    }
+                },
+                enumerable: true,
+                configurable: true
+            });
             Object.defineProperty(Control3D.prototype, "typeName", {
+                /**
+                 * Gets a string representing the class name
+                 */
                 get: function () {
                     return this._getTypeName();
                 },
@@ -5610,12 +6086,401 @@ var BABYLON;
             Control3D.prototype._getTypeName = function () {
                 return "Control3D";
             };
+            Object.defineProperty(Control3D.prototype, "mesh", {
+                /**
+                 * Gets the mesh used to render this control
+                 */
+                get: function () {
+                    return this._mesh;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            /**
+             * Link the control as child of the given mesh
+             * @param mesh defines the mesh to link to. Use null to unlink the control
+             * @returns the current control
+             */
+            Control3D.prototype.linkToMesh = function (mesh) {
+                if (this._mesh) {
+                    this._mesh.parent = mesh;
+                }
+                return this;
+            };
+            /**
+             * Get the attached mesh used to render the control
+             * @param scene defines the scene where the mesh must be attached
+             * @returns the attached mesh or null if none
+             */
+            Control3D.prototype.prepareMesh = function (scene) {
+                if (!this._mesh) {
+                    this._mesh = this._createMesh(scene);
+                    this._mesh.isPickable = true;
+                    this._mesh.metadata = this; // Store the control on the metadata field in order to get it when picking
+                    this._affectMaterial(this._mesh);
+                }
+                return this._mesh;
+            };
+            /**
+             * Mesh creation.
+             * Can be overriden by children
+             * @param scene defines the scene where the mesh must be attached
+             * @returns the attached mesh or null if none
+             */
+            Control3D.prototype._createMesh = function (scene) {
+                // Do nothing by default
+                return null;
+            };
+            /**
+             * Affect a material to the given mesh
+             * @param mesh defines the mesh which will represent the control
+             */
+            Control3D.prototype._affectMaterial = function (mesh) {
+                mesh.material = null;
+            };
+            // Pointers
+            /** @hidden */
+            Control3D.prototype._onPointerMove = function (target, coordinates) {
+                this.onPointerMoveObservable.notifyObservers(coordinates, -1, target, this);
+            };
+            /** @hidden */
+            Control3D.prototype._onPointerEnter = function (target) {
+                if (this._enterCount !== 0) {
+                    return false;
+                }
+                this._enterCount++;
+                this.onPointerEnterObservable.notifyObservers(this, -1, target, this);
+                if (this.pointerEnterAnimation) {
+                    this.pointerEnterAnimation();
+                }
+                return true;
+            };
+            /** @hidden */
+            Control3D.prototype._onPointerOut = function (target) {
+                this._enterCount = 0;
+                this.onPointerOutObservable.notifyObservers(this, -1, target, this);
+                if (this.pointerOutAnimation) {
+                    this.pointerOutAnimation();
+                }
+            };
+            /** @hidden */
+            Control3D.prototype._onPointerDown = function (target, coordinates, pointerId, buttonIndex) {
+                if (this._downCount !== 0) {
+                    return false;
+                }
+                this._downCount++;
+                this._downPointerIds[pointerId] = true;
+                this.onPointerDownObservable.notifyObservers(new GUI.Vector3WithInfo(coordinates, buttonIndex), -1, target, this);
+                if (this.pointerDownAnimation) {
+                    this.pointerDownAnimation();
+                }
+                return true;
+            };
+            /** @hidden */
+            Control3D.prototype._onPointerUp = function (target, coordinates, pointerId, buttonIndex, notifyClick) {
+                this._downCount = 0;
+                delete this._downPointerIds[pointerId];
+                if (notifyClick && this._enterCount > 0) {
+                    this.onPointerClickObservable.notifyObservers(new GUI.Vector3WithInfo(coordinates, buttonIndex), -1, target, this);
+                }
+                this.onPointerUpObservable.notifyObservers(new GUI.Vector3WithInfo(coordinates, buttonIndex), -1, target, this);
+                if (this.pointerUpAnimation) {
+                    this.pointerUpAnimation();
+                }
+            };
+            /** @hidden */
+            Control3D.prototype.forcePointerUp = function (pointerId) {
+                if (pointerId === void 0) { pointerId = null; }
+                if (pointerId !== null) {
+                    this._onPointerUp(this, BABYLON.Vector3.Zero(), pointerId, 0, true);
+                }
+                else {
+                    for (var key in this._downPointerIds) {
+                        this._onPointerUp(this, BABYLON.Vector3.Zero(), +key, 0, true);
+                    }
+                }
+            };
+            /** @hidden */
+            Control3D.prototype._processObservables = function (type, pickedPoint, pointerId, buttonIndex) {
+                if (type === BABYLON.PointerEventTypes.POINTERMOVE) {
+                    this._onPointerMove(this, pickedPoint);
+                    var previousControlOver = this._host._lastControlOver[pointerId];
+                    if (previousControlOver && previousControlOver !== this) {
+                        previousControlOver._onPointerOut(this);
+                    }
+                    if (previousControlOver !== this) {
+                        this._onPointerEnter(this);
+                    }
+                    this._host._lastControlOver[pointerId] = this;
+                    return true;
+                }
+                if (type === BABYLON.PointerEventTypes.POINTERDOWN) {
+                    this._onPointerDown(this, pickedPoint, pointerId, buttonIndex);
+                    this._host._lastControlDown[pointerId] = this;
+                    this._host._lastPickedControl = this;
+                    return true;
+                }
+                if (type === BABYLON.PointerEventTypes.POINTERUP) {
+                    if (this._host._lastControlDown[pointerId]) {
+                        this._host._lastControlDown[pointerId]._onPointerUp(this, pickedPoint, pointerId, buttonIndex, true);
+                    }
+                    delete this._host._lastControlDown[pointerId];
+                    return true;
+                }
+                return false;
+            };
+            /**
+             * Releases all associated resources
+             */
+            Control3D.prototype.dispose = function () {
+                this.onPointerDownObservable.clear();
+                this.onPointerEnterObservable.clear();
+                this.onPointerMoveObservable.clear();
+                this.onPointerOutObservable.clear();
+                this.onPointerUpObservable.clear();
+                this.onPointerClickObservable.clear();
+                if (this._mesh) {
+                    this._mesh.dispose(false, true);
+                    this._mesh = null;
+                }
+                // Behaviors
+                for (var _i = 0, _a = this._behaviors; _i < _a.length; _i++) {
+                    var behavior = _a[_i];
+                    behavior.detach();
+                }
+            };
             return Control3D;
         }());
         GUI.Control3D = Control3D;
     })(GUI = BABYLON.GUI || (BABYLON.GUI = {}));
 })(BABYLON || (BABYLON = {}));
 
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+
+var BABYLON;
+(function (BABYLON) {
+    var GUI;
+    (function (GUI) {
+        /**
+         * Class used to create containers for controls
+         */
+        var Container3D = /** @class */ (function (_super) {
+            __extends(Container3D, _super);
+            /**
+             * Creates a new container
+             * @param name defines the container name
+             */
+            function Container3D(name) {
+                var _this = _super.call(this, name) || this;
+                _this._children = new Array();
+                return _this;
+            }
+            /**
+             * Gets a boolean indicating if the given control is in the children of this control
+             * @param control defines the control to check
+             * @returns true if the control is in the child list
+             */
+            Container3D.prototype.containsControl = function (control) {
+                return this._children.indexOf(control) !== -1;
+            };
+            /**
+             * Adds a control to the children of this control
+             * @param control defines the control to add
+             * @returns the current container
+             */
+            Container3D.prototype.addControl = function (control) {
+                var index = this._children.indexOf(control);
+                if (index !== -1) {
+                    return this;
+                }
+                control.parent = this;
+                control._host = this._host;
+                if (this._host.utilityLayer) {
+                    control.prepareMesh(this._host.utilityLayer.utilityLayerScene);
+                }
+                return this;
+            };
+            /**
+             * Removes the control from the children of this control
+             * @param control defines the control to remove
+             * @returns the current container
+             */
+            Container3D.prototype.removeControl = function (control) {
+                var index = this._children.indexOf(control);
+                if (index !== -1) {
+                    this._children.splice(index, 1);
+                    control.parent = null;
+                }
+                return this;
+            };
+            Container3D.prototype._getTypeName = function () {
+                return "Container3D";
+            };
+            /**
+             * Releases all associated resources
+             */
+            Container3D.prototype.dispose = function () {
+                for (var _i = 0, _a = this._children; _i < _a.length; _i++) {
+                    var control = _a[_i];
+                    control.dispose();
+                }
+                this._children = [];
+                _super.prototype.dispose.call(this);
+            };
+            return Container3D;
+        }(GUI.Control3D));
+        GUI.Container3D = Container3D;
+    })(GUI = BABYLON.GUI || (BABYLON.GUI = {}));
+})(BABYLON || (BABYLON = {}));
+
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+
+var BABYLON;
+(function (BABYLON) {
+    var GUI;
+    (function (GUI) {
+        /**
+         * Class used to create a button in 3D
+         */
+        var Button3D = /** @class */ (function (_super) {
+            __extends(Button3D, _super);
+            /**
+             * Creates a new button
+             * @param name defines the control name
+             */
+            function Button3D(name) {
+                var _this = _super.call(this, name) || this;
+                // Default animations
+                _this.pointerEnterAnimation = function () {
+                    if (!_this.mesh) {
+                        return;
+                    }
+                    _this._currentMaterial.emissiveColor = BABYLON.Color3.Red();
+                };
+                _this.pointerOutAnimation = function () {
+                    _this._currentMaterial.emissiveColor = BABYLON.Color3.Black();
+                };
+                _this.pointerDownAnimation = function () {
+                    if (!_this.mesh) {
+                        return;
+                    }
+                    _this.mesh.scaling.scaleInPlace(0.95);
+                };
+                _this.pointerUpAnimation = function () {
+                    if (!_this.mesh) {
+                        return;
+                    }
+                    _this.mesh.scaling.scaleInPlace(1.0 / 0.95);
+                };
+                return _this;
+            }
+            Object.defineProperty(Button3D.prototype, "content", {
+                /**
+                 * Gets or sets the GUI 2D content used to display the button's facade
+                 */
+                get: function () {
+                    return this._content;
+                },
+                set: function (value) {
+                    if (!this._host || !this._host.utilityLayer) {
+                        return;
+                    }
+                    if (!this._facadeTexture) {
+                        this._facadeTexture = new BABYLON.GUI.AdvancedDynamicTexture("Facade", 512, 512, this._host.utilityLayer.utilityLayerScene, true, BABYLON.Texture.TRILINEAR_SAMPLINGMODE);
+                        this._facadeTexture.rootContainer.scaleX = 2;
+                        this._facadeTexture.rootContainer.scaleY = 2;
+                        this._facadeTexture.premulAlpha = true;
+                    }
+                    this._facadeTexture.addControl(value);
+                    this._currentMaterial.emissiveTexture = this._facadeTexture;
+                },
+                enumerable: true,
+                configurable: true
+            });
+            Button3D.prototype._getTypeName = function () {
+                return "Button3D";
+            };
+            // Mesh association
+            Button3D.prototype._createMesh = function (scene) {
+                var faceUV = new Array(6);
+                for (var i = 0; i < 6; i++) {
+                    faceUV[i] = new BABYLON.Vector4(0, 0, 0, 0);
+                }
+                faceUV[1] = new BABYLON.Vector4(0, 0, 1, 1);
+                var mesh = BABYLON.MeshBuilder.CreateBox(this.name + "Mesh", {
+                    width: 1.0,
+                    height: 1.0,
+                    depth: 0.1,
+                    faceUV: faceUV
+                }, scene);
+                return mesh;
+            };
+            Button3D.prototype._affectMaterial = function (mesh) {
+                var material = new BABYLON.StandardMaterial(this.name + "Material", mesh.getScene());
+                material.specularColor = BABYLON.Color3.Black();
+                mesh.material = material;
+                this._currentMaterial = material;
+            };
+            return Button3D;
+        }(GUI.Control3D));
+        GUI.Button3D = Button3D;
+    })(GUI = BABYLON.GUI || (BABYLON.GUI = {}));
+})(BABYLON || (BABYLON = {}));
+
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+
+var BABYLON;
+(function (BABYLON) {
+    var GUI;
+    (function (GUI) {
+        /**
+         * Class used to create a button in 3D
+         */
+        var HolographicButton = /** @class */ (function (_super) {
+            __extends(HolographicButton, _super);
+            /**
+             * Creates a new button
+             * @param name defines the control name
+             */
+            function HolographicButton(name) {
+                var _this = _super.call(this, name) || this;
+                // Default animations
+                _this.pointerEnterAnimation = function () {
+                    if (!_this.mesh) {
+                        return;
+                    }
+                    _this.mesh.edgesRenderer.isEnabled = true;
+                };
+                _this.pointerOutAnimation = function () {
+                    if (!_this.mesh) {
+                        return;
+                    }
+                    _this.mesh.edgesRenderer.isEnabled = false;
+                };
+                return _this;
+            }
+            HolographicButton.prototype._getTypeName = function () {
+                return "HolographicButton";
+            };
+            // Mesh association
+            HolographicButton.prototype._createMesh = function (scene) {
+                var mesh = _super.prototype._createMesh.call(this, scene);
+                mesh.edgesWidth = 0.5;
+                mesh.edgesColor = new BABYLON.Color4(1.0, 1.0, 1.0, 1.0);
+                mesh.enableEdgesRendering();
+                mesh.edgesRenderer.isEnabled = false;
+                return mesh;
+            };
+            HolographicButton.prototype._affectMaterial = function (mesh) {
+                this._currentMaterial = new GUI.FluentMaterial(this.name + "Material", mesh.getScene());
+                mesh.material = this._currentMaterial;
+            };
+            return HolographicButton;
+        }(GUI.Button3D));
+        GUI.HolographicButton = HolographicButton;
+    })(GUI = BABYLON.GUI || (BABYLON.GUI = {}));
+})(BABYLON || (BABYLON = {}));
+
     
 
     return BABYLON.GUI;

文件差異過大導致無法顯示
+ 4 - 4
dist/preview release/gui/babylon.gui.min.js


+ 311 - 1
dist/preview release/gui/babylon.gui.module.d.ts

@@ -104,6 +104,10 @@ declare module BABYLON.GUI {
         private _focusedControl;
         private _blockNextFocusCheck;
         private _renderScale;
+        /**
+         * Gets or sets a boolean defining if alpha is stored as premultiplied
+         */
+        premulAlpha: boolean;
         renderScale: number;
         background: string;
         idealWidth: number;
@@ -970,8 +974,314 @@ declare module BABYLON.GUI {
 
 
 declare module BABYLON.GUI {
-    class Control3D {
+    /**
+     * Class used to manage 3D user interface
+     */
+    class GUI3DManager implements BABYLON.IDisposable {
+        private _scene;
+        private _sceneDisposeObserver;
+        private _utilityLayer;
+        private _rootContainer;
+        private _pointerObserver;
+        _lastPickedControl: Control3D;
+        /** @hidden */
+        _lastControlOver: {
+            [pointerId: number]: Control3D;
+        };
+        /** @hidden */
+        _lastControlDown: {
+            [pointerId: number]: Control3D;
+        };
+        /** Gets the hosting scene */
+        readonly scene: Scene;
+        readonly utilityLayer: Nullable<UtilityLayerRenderer>;
+        /**
+         * Creates a new GUI3DManager
+         * @param scene
+         */
+        constructor(scene?: Scene);
+        private _doPicking(type, pointerEvent);
+        /**
+         * Gets the root container
+         */
+        readonly rootContainer: Container3D;
+        /**
+         * Gets a boolean indicating if the given control is in the root child list
+         * @param control defines the control to check
+         * @returns true if the control is in the root child list
+         */
+        containsControl(control: Control3D): boolean;
+        /**
+         * Adds a control to the root child list
+         * @param control defines the control to add
+         * @returns the current manager
+         */
+        addControl(control: Control3D): GUI3DManager;
+        /**
+         * Removes the control from the root child list
+         * @param control defines the control to remove
+         * @returns the current container
+         */
+        removeControl(control: Control3D): GUI3DManager;
+        /**
+         * Releases all associated resources
+         */
+        dispose(): void;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    /**
+     * Class used to render controls with fluent desgin
+     */
+    class FluentMaterial extends PushMaterial {
+        private _emissiveTexture;
+        emissiveTexture: BaseTexture;
+        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;
+        getActiveTextures(): BaseTexture[];
+        hasTexture(texture: BaseTexture): boolean;
+        dispose(forceDisposeEffect?: boolean): void;
+        clone(name: string): FluentMaterial;
+        serialize(): any;
+        getClassName(): string;
+        static Parse(source: any, scene: Scene, rootUrl: string): FluentMaterial;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    class Vector3WithInfo extends Vector3 {
+        buttonIndex: number;
+        constructor(source: Vector3, buttonIndex?: number);
+    }
+}
+
+
+declare module BABYLON.GUI {
+    /**
+     * Class used as base class for controls
+     */
+    class Control3D implements IDisposable, IBehaviorAware<Control3D> {
+        /** Defines the control name */
+        name: string | undefined;
+        /** @hidden */
+        _host: GUI3DManager;
+        private _mesh;
+        private _downCount;
+        private _enterCount;
+        private _downPointerIds;
+        private _isVisible;
+        /** Callback used to start pointer enter animation */
+        pointerEnterAnimation: () => void;
+        /** Callback used to start pointer out animation */
+        pointerOutAnimation: () => void;
+        /** Callback used to start pointer down animation */
+        pointerDownAnimation: () => void;
+        /** Callback used to start pointer up animation */
+        pointerUpAnimation: () => void;
+        /**
+        * An event triggered when the pointer move over the control.
+        */
+        onPointerMoveObservable: Observable<Vector3>;
+        /**
+         * An event triggered when the pointer move out of the control.
+         */
+        onPointerOutObservable: Observable<Control3D>;
+        /**
+         * An event triggered when the pointer taps the control
+         */
+        onPointerDownObservable: Observable<Vector3WithInfo>;
+        /**
+         * An event triggered when pointer up
+         */
+        onPointerUpObservable: Observable<Vector3WithInfo>;
+        /**
+         * An event triggered when a control is clicked on
+         */
+        onPointerClickObservable: Observable<Vector3WithInfo>;
+        /**
+         * An event triggered when pointer enters the control
+         */
+        onPointerEnterObservable: Observable<Control3D>;
+        /**
+         * Gets or sets the parent container
+         */
+        parent: Nullable<Container3D>;
+        private _behaviors;
+        /**
+         * Gets the list of attached behaviors
+         * @see http://doc.babylonjs.com/features/behaviour
+         */
+        readonly behaviors: Behavior<Control3D>[];
+        /**
+         * Attach a behavior to the control
+         * @see http://doc.babylonjs.com/features/behaviour
+         * @param behavior defines the behavior to attach
+         * @returns the current control
+         */
+        addBehavior(behavior: Behavior<Control3D>): Control3D;
+        /**
+         * Remove an attached behavior
+         * @see http://doc.babylonjs.com/features/behaviour
+         * @param behavior defines the behavior to attach
+         * @returns the current control
+         */
+        removeBehavior(behavior: Behavior<Control3D>): Control3D;
+        /**
+         * Gets an attached behavior by name
+         * @param name defines the name of the behavior to look for
+         * @see http://doc.babylonjs.com/features/behaviour
+         * @returns null if behavior was not found else the requested behavior
+         */
+        getBehaviorByName(name: string): Nullable<Behavior<Control3D>>;
+        /** Gets or sets a boolean indicating if the control is visible */
+        isVisible: boolean;
+        /** Gets or sets the control position */
+        position: Vector3;
+        /**
+         * Creates a new control
+         * @param name defines the control name
+         */
+        constructor(
+            /** Defines the control name */
+            name?: string | undefined);
+        /**
+         * Gets a string representing the class name
+         */
         readonly typeName: string;
         protected _getTypeName(): string;
+        /**
+         * Gets the mesh used to render this control
+         */
+        readonly mesh: Nullable<Mesh>;
+        /**
+         * Link the control as child of the given mesh
+         * @param mesh defines the mesh to link to. Use null to unlink the control
+         * @returns the current control
+         */
+        linkToMesh(mesh: Nullable<AbstractMesh>): Control3D;
+        /**
+         * Get the attached mesh used to render the control
+         * @param scene defines the scene where the mesh must be attached
+         * @returns the attached mesh or null if none
+         */
+        prepareMesh(scene: Scene): Nullable<Mesh>;
+        /**
+         * Mesh creation.
+         * Can be overriden by children
+         * @param scene defines the scene where the mesh must be attached
+         * @returns the attached mesh or null if none
+         */
+        protected _createMesh(scene: Scene): Nullable<Mesh>;
+        /**
+         * Affect a material to the given mesh
+         * @param mesh defines the mesh which will represent the control
+         */
+        protected _affectMaterial(mesh: Mesh): void;
+        /** @hidden */
+        _onPointerMove(target: Control3D, coordinates: Vector3): void;
+        /** @hidden */
+        _onPointerEnter(target: Control3D): boolean;
+        /** @hidden */
+        _onPointerOut(target: Control3D): void;
+        /** @hidden */
+        _onPointerDown(target: Control3D, coordinates: Vector3, pointerId: number, buttonIndex: number): boolean;
+        /** @hidden */
+        _onPointerUp(target: Control3D, coordinates: Vector3, pointerId: number, buttonIndex: number, notifyClick: boolean): void;
+        /** @hidden */
+        forcePointerUp(pointerId?: Nullable<number>): void;
+        /** @hidden */
+        _processObservables(type: number, pickedPoint: Vector3, pointerId: number, buttonIndex: number): boolean;
+        /**
+         * Releases all associated resources
+         */
+        dispose(): void;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    /**
+     * Class used to create containers for controls
+     */
+    class Container3D extends Control3D {
+        private _children;
+        /**
+         * Creates a new container
+         * @param name defines the container name
+         */
+        constructor(name?: string);
+        /**
+         * Gets a boolean indicating if the given control is in the children of this control
+         * @param control defines the control to check
+         * @returns true if the control is in the child list
+         */
+        containsControl(control: Control3D): boolean;
+        /**
+         * Adds a control to the children of this control
+         * @param control defines the control to add
+         * @returns the current container
+         */
+        addControl(control: Control3D): Container3D;
+        /**
+         * Removes the control from the children of this control
+         * @param control defines the control to remove
+         * @returns the current container
+         */
+        removeControl(control: Control3D): Container3D;
+        protected _getTypeName(): string;
+        /**
+         * Releases all associated resources
+         */
+        dispose(): void;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    /**
+     * Class used to create a button in 3D
+     */
+    class Button3D extends Control3D {
+        /** @hidden */
+        protected _currentMaterial: Material;
+        private _facadeTexture;
+        private _content;
+        /**
+         * Creates a new button
+         * @param name defines the control name
+         */
+        constructor(name?: string);
+        /**
+         * Gets or sets the GUI 2D content used to display the button's facade
+         */
+        content: Control;
+        protected _getTypeName(): string;
+        protected _createMesh(scene: Scene): Mesh;
+        protected _affectMaterial(mesh: Mesh): void;
+    }
+}
+
+
+declare module BABYLON.GUI {
+    /**
+     * Class used to create a button in 3D
+     */
+    class HolographicButton extends Button3D {
+        /**
+         * Creates a new button
+         * @param name defines the control name
+         */
+        constructor(name?: string);
+        protected _getTypeName(): string;
+        protected _createMesh(scene: Scene): Mesh;
+        protected _affectMaterial(mesh: Mesh): void;
     }
 }

+ 1 - 1
dist/preview release/gui/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-gui",
     "description": "The Babylon.js GUI library is an extension you can use to generate interactive user interface. It is build on top of the DynamicTexture.",
-    "version": "3.3.0-alpha.1",
+    "version": "3.3.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

文件差異過大導致無法顯示
+ 5 - 5
dist/preview release/inspector/babylon.inspector.bundle.js


+ 25 - 0
dist/preview release/inspector/babylon.inspector.css

@@ -250,6 +250,31 @@
       padding-bottom: 10px;
       border-bottom: 1px solid #5db0d7;
       margin: 10px 0 10px 0; }
+    .insp-wrapper .tab-panel .gltf-actions .gltf-action, .insp-wrapper .tab-panel .gltf-actions .gltf-checkbox {
+      height: 20px;
+      line-height: 20px;
+      width: 100%;
+      cursor: pointer;
+      white-space: nowrap; }
+      .insp-wrapper .tab-panel .gltf-actions .gltf-action:hover, .insp-wrapper .tab-panel .gltf-actions .gltf-checkbox:hover {
+        background-color: #2c2c2c; }
+    .insp-wrapper .tab-panel .gltf-actions .gltf-checkbox:before {
+      width: 1em;
+      height: 1em;
+      line-height: 1em;
+      display: inline-block;
+      font-family: 'FontAwesome', sans-serif;
+      content: "\f096";
+      margin-right: 10px; }
+    .insp-wrapper .tab-panel .gltf-actions .gltf-checkbox.active:before {
+      width: 1em;
+      height: 1em;
+      line-height: 1em;
+      display: inline-block;
+      font-family: 'FontAwesome', sans-serif;
+      content: "\f14a";
+      color: #5db0d7;
+      margin-right: 10px; }
     .insp-wrapper .tab-panel .gltf-actions .gltf-input {
       background-color: #2c2c2c;
       border: none;

+ 14 - 1
dist/preview release/inspector/babylon.inspector.d.ts

@@ -1028,11 +1028,24 @@ declare module INSPECTOR {
 
 
 
+
+declare function Split(elements: HTMLElement[], options: any): any;
 declare module INSPECTOR {
     class GLTFTab extends Tab {
+        private static _LoaderExtensionSettings;
+        private _inspector;
+        private _actions;
+        private _detailsPanel;
+        private _split;
+        /** @hidden */
+        static _Initialize(): void;
         constructor(tabbar: TabBar, inspector: Inspector);
         dispose(): void;
-        private _addExport(inspector, actions);
+        private _addImport();
+        private _getLoaderExtensionOverridesAsync();
+        private _updateLoaderExtensionDetails(settings);
+        private _closeDetailsPanel();
+        private _addExport();
         private static _IsSkyBox(transformNode);
     }
 }

+ 132 - 8
dist/preview release/inspector/babylon.inspector.js

@@ -4049,6 +4049,7 @@ var INSPECTOR;
 })(INSPECTOR || (INSPECTOR = {}));
 
 /// <reference path="../../../dist/preview release/gltf2Interface/babylon.glTF2Interface.d.ts"/>
+/// <reference path="../../../dist/preview release/loaders/babylon.glTF2FileLoader.d.ts"/>
 /// <reference path="../../../dist/preview release/serializers/babylon.glTF2Serializer.d.ts"/>
 var __extends = (this && this.__extends) || (function () {
     var extendStatics = Object.setPrototypeOf ||
@@ -4062,27 +4063,148 @@ var __extends = (this && this.__extends) || (function () {
 })();
 var INSPECTOR;
 (function (INSPECTOR) {
+    ;
     var GLTFTab = /** @class */ (function (_super) {
         __extends(GLTFTab, _super);
         function GLTFTab(tabbar, inspector) {
             var _this = _super.call(this, tabbar, 'GLTF') || this;
+            _this._detailsPanel = null;
+            _this._inspector = inspector;
             _this._panel = INSPECTOR.Helpers.CreateDiv('tab-panel');
-            var actions = INSPECTOR.Helpers.CreateDiv('gltf-actions', _this._panel);
-            _this._addExport(inspector, actions);
+            _this._actions = INSPECTOR.Helpers.CreateDiv('gltf-actions', _this._panel);
+            _this._actions.addEventListener('click', function (event) {
+                _this._closeDetailsPanel();
+            });
+            _this._addImport();
+            _this._addExport();
             return _this;
         }
+        /** @hidden */
+        GLTFTab._Initialize = function () {
+            // Must register with OnPluginActivatedObservable as early as possible to
+            // override the default settings for each extension.
+            BABYLON.SceneLoader.OnPluginActivatedObservable.add(function (loader) {
+                if (loader.name === "gltf" && GLTFTab._LoaderExtensionSettings) {
+                    loader.onExtensionLoadedObservable.add(function (extension) {
+                        var settings = GLTFTab._LoaderExtensionSettings[extension.name];
+                        for (var settingName in settings) {
+                            extension[settingName] = settings[settingName];
+                        }
+                    });
+                }
+            });
+        };
         GLTFTab.prototype.dispose = function () {
-            // Nothing to dispose
+            if (this._detailsPanel) {
+                this._detailsPanel.dispose();
+            }
+        };
+        GLTFTab.prototype._addImport = function () {
+            var _this = this;
+            var importActions = INSPECTOR.Helpers.CreateDiv(null, this._actions);
+            this._getLoaderExtensionOverridesAsync().then(function (loaderExtensionSettings) {
+                var title = INSPECTOR.Helpers.CreateDiv('gltf-title', importActions);
+                title.textContent = 'Import';
+                var extensionActions = INSPECTOR.Helpers.CreateDiv('gltf-actions', importActions);
+                var extensionsTitle = INSPECTOR.Helpers.CreateDiv('gltf-title', extensionActions);
+                extensionsTitle.textContent = "Extensions";
+                var _loop_1 = function (extensionName) {
+                    var settings = loaderExtensionSettings[extensionName];
+                    var extensionAction = INSPECTOR.Helpers.CreateDiv('gltf-action', extensionActions);
+                    extensionAction.addEventListener('click', function (event) {
+                        if (_this._updateLoaderExtensionDetails(settings)) {
+                            event.stopPropagation();
+                        }
+                    });
+                    var checkbox = INSPECTOR.Helpers.CreateElement('span', 'gltf-checkbox', extensionAction);
+                    if (settings.enabled) {
+                        checkbox.classList.add('action', 'active');
+                    }
+                    checkbox.addEventListener('click', function () {
+                        checkbox.classList.toggle('active');
+                        settings.enabled = checkbox.classList.contains('active');
+                    });
+                    var label = INSPECTOR.Helpers.CreateElement('span', null, extensionAction);
+                    label.textContent = extensionName;
+                };
+                for (var extensionName in loaderExtensionSettings) {
+                    _loop_1(extensionName);
+                }
+            });
         };
-        GLTFTab.prototype._addExport = function (inspector, actions) {
-            var title = INSPECTOR.Helpers.CreateDiv('gltf-title', actions);
+        GLTFTab.prototype._getLoaderExtensionOverridesAsync = function () {
+            if (GLTFTab._LoaderExtensionSettings) {
+                return Promise.resolve(GLTFTab._LoaderExtensionSettings);
+            }
+            var loaderExtensionSettings = {};
+            var engine = new BABYLON.NullEngine();
+            var scene = new BABYLON.Scene(engine);
+            var loader = new BABYLON.GLTF2.GLTFLoader();
+            loader.onExtensionLoadedObservable.add(function (extension) {
+                loaderExtensionSettings[extension.name] = {};
+                var settings = loaderExtensionSettings[extension.name];
+                for (var _i = 0, _a = Object.keys(extension); _i < _a.length; _i++) {
+                    var key = _a[_i];
+                    if (key !== "name" && key[0] !== '_') {
+                        var value = extension[key];
+                        if (typeof value !== "object") {
+                            settings[key] = value;
+                        }
+                    }
+                }
+            });
+            var data = { json: {}, bin: null };
+            return loader.importMeshAsync([], scene, data, "").then(function () {
+                scene.dispose();
+                engine.dispose();
+                return (GLTFTab._LoaderExtensionSettings = loaderExtensionSettings);
+            });
+        };
+        GLTFTab.prototype._updateLoaderExtensionDetails = function (settings) {
+            if (Object.keys(settings).length === 1) {
+                return false;
+            }
+            if (!this._detailsPanel) {
+                this._detailsPanel = new INSPECTOR.DetailPanel();
+                this._panel.appendChild(this._detailsPanel.toHtml());
+                this._split = Split([this._actions, this._detailsPanel.toHtml()], {
+                    blockDrag: this._inspector.popupMode,
+                    sizes: [50, 50],
+                    direction: 'vertical'
+                });
+            }
+            this._detailsPanel.clean();
+            var details = new Array();
+            for (var key in settings) {
+                if (key !== "enabled") {
+                    details.push(new INSPECTOR.PropertyLine(new INSPECTOR.Property(key, settings)));
+                }
+            }
+            this._detailsPanel.details = details;
+            return true;
+        };
+        GLTFTab.prototype._closeDetailsPanel = function () {
+            if (this._detailsPanel) {
+                this._detailsPanel.toHtml().remove();
+                this._detailsPanel.dispose();
+                this._detailsPanel = null;
+            }
+            if (this._split) {
+                this._split.destroy();
+                delete this._split;
+            }
+        };
+        GLTFTab.prototype._addExport = function () {
+            var _this = this;
+            var exportActions = INSPECTOR.Helpers.CreateDiv(null, this._actions);
+            var title = INSPECTOR.Helpers.CreateDiv('gltf-title', exportActions);
             title.textContent = 'Export';
-            var name = INSPECTOR.Helpers.CreateInput('gltf-input', actions);
+            var name = INSPECTOR.Helpers.CreateInput('gltf-input', exportActions);
             name.placeholder = "File name...";
-            var button = INSPECTOR.Helpers.CreateElement('button', 'gltf-button', actions);
+            var button = INSPECTOR.Helpers.CreateElement('button', 'gltf-button', exportActions);
             button.innerText = 'Export GLB';
             button.addEventListener('click', function () {
-                BABYLON.GLTF2Export.GLBAsync(inspector.scene, name.value || "scene", {
+                BABYLON.GLTF2Export.GLBAsync(_this._inspector.scene, name.value || "scene", {
                     shouldExportTransformNode: function (transformNode) { return !GLTFTab._IsSkyBox(transformNode); }
                 }).then(function (glb) {
                     glb.downloadFiles();
@@ -4101,9 +4223,11 @@ var INSPECTOR;
             }
             return false;
         };
+        GLTFTab._LoaderExtensionSettings = null;
         return GLTFTab;
     }(INSPECTOR.Tab));
     INSPECTOR.GLTFTab = GLTFTab;
+    GLTFTab._Initialize();
 })(INSPECTOR || (INSPECTOR = {}));
 
 var __extends = (this && this.__extends) || (function () {

文件差異過大導致無法顯示
+ 4 - 4
dist/preview release/inspector/babylon.inspector.min.js


+ 1 - 1
dist/preview release/inspector/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-inspector",
     "description": "The Babylon.js inspector.",
-    "version": "3.3.0-alpha.1",
+    "version": "3.3.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.d.ts

@@ -615,7 +615,7 @@ declare module BABYLON.GLTF2 {
          * @returns a promise which completes when objects have been loaded to the scene
          */
         loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void>;
-        private _loadAsync(nodes, scene, data, rootUrl, onProgress?);
+        private _loadAsync(nodes);
         private _loadData(data);
         private _setupData();
         private _loadExtensions();

+ 17 - 8
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -751,6 +751,10 @@ var BABYLON;
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onProgress) {
                 var _this = this;
                 return Promise.resolve().then(function () {
+                    _this._babylonScene = scene;
+                    _this._rootUrl = rootUrl;
+                    _this._progressCallback = onProgress;
+                    _this._loadData(data);
                     var nodes = null;
                     if (meshesNames) {
                         var nodeMap_1 = {};
@@ -771,7 +775,7 @@ var BABYLON;
                             return node;
                         });
                     }
-                    return _this._loadAsync(nodes, scene, data, rootUrl, onProgress).then(function () {
+                    return _this._loadAsync(nodes).then(function () {
                         return {
                             meshes: _this._getMeshes(),
                             particleSystems: [],
@@ -790,16 +794,19 @@ var BABYLON;
              * @returns a promise which completes when objects have been loaded to the scene
              */
             GLTFLoader.prototype.loadAsync = function (scene, data, rootUrl, onProgress) {
-                return this._loadAsync(null, scene, data, rootUrl, onProgress);
-            };
-            GLTFLoader.prototype._loadAsync = function (nodes, scene, data, rootUrl, onProgress) {
                 var _this = this;
                 return Promise.resolve().then(function () {
                     _this._babylonScene = scene;
                     _this._rootUrl = rootUrl;
                     _this._progressCallback = onProgress;
-                    _this._state = BABYLON.GLTFLoaderState.LOADING;
                     _this._loadData(data);
+                    return _this._loadAsync(null);
+                });
+            };
+            GLTFLoader.prototype._loadAsync = function (nodes) {
+                var _this = this;
+                return Promise.resolve().then(function () {
+                    _this._state = BABYLON.GLTFLoaderState.LOADING;
                     _this._loadExtensions();
                     _this._checkExtensions();
                     var promises = new Array();
@@ -807,8 +814,8 @@ var BABYLON;
                         promises.push(_this._loadNodesAsync(nodes));
                     }
                     else {
-                        var scene_1 = GLTFLoader._GetProperty("#/scene", _this._gltf.scenes, _this._gltf.scene || 0);
-                        promises.push(_this._loadSceneAsync("#/scenes/" + scene_1._index, scene_1));
+                        var scene = GLTFLoader._GetProperty("#/scene", _this._gltf.scenes, _this._gltf.scene || 0);
+                        promises.push(_this._loadSceneAsync("#/scenes/" + scene._index, scene));
                     }
                     if (_this.compileMaterials) {
                         promises.push(_this._compileMaterialsAsync());
@@ -822,7 +829,9 @@ var BABYLON;
                         _this._startAnimations();
                     });
                     resultPromise.then(function () {
-                        _this._rootBabylonMesh.setEnabled(true);
+                        if (_this._rootBabylonMesh) {
+                            _this._rootBabylonMesh.setEnabled(true);
+                        }
                         BABYLON.Tools.SetImmediate(function () {
                             if (!_this._disposed) {
                                 Promise.all(_this._completePromises).then(function () {

文件差異過大導致無法顯示
+ 2 - 2
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


+ 1 - 1
dist/preview release/loaders/babylon.glTFFileLoader.d.ts

@@ -1192,7 +1192,7 @@ declare module BABYLON.GLTF2 {
          * @returns a promise which completes when objects have been loaded to the scene
          */
         loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void>;
-        private _loadAsync(nodes, scene, data, rootUrl, onProgress?);
+        private _loadAsync(nodes);
         private _loadData(data);
         private _setupData();
         private _loadExtensions();

+ 17 - 8
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -2967,6 +2967,10 @@ var BABYLON;
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onProgress) {
                 var _this = this;
                 return Promise.resolve().then(function () {
+                    _this._babylonScene = scene;
+                    _this._rootUrl = rootUrl;
+                    _this._progressCallback = onProgress;
+                    _this._loadData(data);
                     var nodes = null;
                     if (meshesNames) {
                         var nodeMap_1 = {};
@@ -2987,7 +2991,7 @@ var BABYLON;
                             return node;
                         });
                     }
-                    return _this._loadAsync(nodes, scene, data, rootUrl, onProgress).then(function () {
+                    return _this._loadAsync(nodes).then(function () {
                         return {
                             meshes: _this._getMeshes(),
                             particleSystems: [],
@@ -3006,16 +3010,19 @@ var BABYLON;
              * @returns a promise which completes when objects have been loaded to the scene
              */
             GLTFLoader.prototype.loadAsync = function (scene, data, rootUrl, onProgress) {
-                return this._loadAsync(null, scene, data, rootUrl, onProgress);
-            };
-            GLTFLoader.prototype._loadAsync = function (nodes, scene, data, rootUrl, onProgress) {
                 var _this = this;
                 return Promise.resolve().then(function () {
                     _this._babylonScene = scene;
                     _this._rootUrl = rootUrl;
                     _this._progressCallback = onProgress;
-                    _this._state = BABYLON.GLTFLoaderState.LOADING;
                     _this._loadData(data);
+                    return _this._loadAsync(null);
+                });
+            };
+            GLTFLoader.prototype._loadAsync = function (nodes) {
+                var _this = this;
+                return Promise.resolve().then(function () {
+                    _this._state = BABYLON.GLTFLoaderState.LOADING;
                     _this._loadExtensions();
                     _this._checkExtensions();
                     var promises = new Array();
@@ -3023,8 +3030,8 @@ var BABYLON;
                         promises.push(_this._loadNodesAsync(nodes));
                     }
                     else {
-                        var scene_1 = GLTFLoader._GetProperty("#/scene", _this._gltf.scenes, _this._gltf.scene || 0);
-                        promises.push(_this._loadSceneAsync("#/scenes/" + scene_1._index, scene_1));
+                        var scene = GLTFLoader._GetProperty("#/scene", _this._gltf.scenes, _this._gltf.scene || 0);
+                        promises.push(_this._loadSceneAsync("#/scenes/" + scene._index, scene));
                     }
                     if (_this.compileMaterials) {
                         promises.push(_this._compileMaterialsAsync());
@@ -3038,7 +3045,9 @@ var BABYLON;
                         _this._startAnimations();
                     });
                     resultPromise.then(function () {
-                        _this._rootBabylonMesh.setEnabled(true);
+                        if (_this._rootBabylonMesh) {
+                            _this._rootBabylonMesh.setEnabled(true);
+                        }
                         BABYLON.Tools.SetImmediate(function () {
                             if (!_this._disposed) {
                                 Promise.all(_this._completePromises).then(function () {

文件差異過大導致無法顯示
+ 2 - 2
dist/preview release/loaders/babylon.glTFFileLoader.min.js


+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.d.ts

@@ -1288,7 +1288,7 @@ declare module BABYLON.GLTF2 {
          * @returns a promise which completes when objects have been loaded to the scene
          */
         loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void>;
-        private _loadAsync(nodes, scene, data, rootUrl, onProgress?);
+        private _loadAsync(nodes);
         private _loadData(data);
         private _setupData();
         private _loadExtensions();

+ 17 - 8
dist/preview release/loaders/babylonjs.loaders.js

@@ -3949,6 +3949,10 @@ var BABYLON;
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onProgress) {
                 var _this = this;
                 return Promise.resolve().then(function () {
+                    _this._babylonScene = scene;
+                    _this._rootUrl = rootUrl;
+                    _this._progressCallback = onProgress;
+                    _this._loadData(data);
                     var nodes = null;
                     if (meshesNames) {
                         var nodeMap_1 = {};
@@ -3969,7 +3973,7 @@ var BABYLON;
                             return node;
                         });
                     }
-                    return _this._loadAsync(nodes, scene, data, rootUrl, onProgress).then(function () {
+                    return _this._loadAsync(nodes).then(function () {
                         return {
                             meshes: _this._getMeshes(),
                             particleSystems: [],
@@ -3988,16 +3992,19 @@ var BABYLON;
              * @returns a promise which completes when objects have been loaded to the scene
              */
             GLTFLoader.prototype.loadAsync = function (scene, data, rootUrl, onProgress) {
-                return this._loadAsync(null, scene, data, rootUrl, onProgress);
-            };
-            GLTFLoader.prototype._loadAsync = function (nodes, scene, data, rootUrl, onProgress) {
                 var _this = this;
                 return Promise.resolve().then(function () {
                     _this._babylonScene = scene;
                     _this._rootUrl = rootUrl;
                     _this._progressCallback = onProgress;
-                    _this._state = BABYLON.GLTFLoaderState.LOADING;
                     _this._loadData(data);
+                    return _this._loadAsync(null);
+                });
+            };
+            GLTFLoader.prototype._loadAsync = function (nodes) {
+                var _this = this;
+                return Promise.resolve().then(function () {
+                    _this._state = BABYLON.GLTFLoaderState.LOADING;
                     _this._loadExtensions();
                     _this._checkExtensions();
                     var promises = new Array();
@@ -4005,8 +4012,8 @@ var BABYLON;
                         promises.push(_this._loadNodesAsync(nodes));
                     }
                     else {
-                        var scene_1 = GLTFLoader._GetProperty("#/scene", _this._gltf.scenes, _this._gltf.scene || 0);
-                        promises.push(_this._loadSceneAsync("#/scenes/" + scene_1._index, scene_1));
+                        var scene = GLTFLoader._GetProperty("#/scene", _this._gltf.scenes, _this._gltf.scene || 0);
+                        promises.push(_this._loadSceneAsync("#/scenes/" + scene._index, scene));
                     }
                     if (_this.compileMaterials) {
                         promises.push(_this._compileMaterialsAsync());
@@ -4020,7 +4027,9 @@ var BABYLON;
                         _this._startAnimations();
                     });
                     resultPromise.then(function () {
-                        _this._rootBabylonMesh.setEnabled(true);
+                        if (_this._rootBabylonMesh) {
+                            _this._rootBabylonMesh.setEnabled(true);
+                        }
                         BABYLON.Tools.SetImmediate(function () {
                             if (!_this._disposed) {
                                 Promise.all(_this._completePromises).then(function () {

文件差異過大導致無法顯示
+ 4 - 4
dist/preview release/loaders/babylonjs.loaders.min.js


+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.module.d.ts

@@ -1295,7 +1295,7 @@ declare module BABYLON.GLTF2 {
          * @returns a promise which completes when objects have been loaded to the scene
          */
         loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void>;
-        private _loadAsync(nodes, scene, data, rootUrl, onProgress?);
+        private _loadAsync(nodes);
         private _loadData(data);
         private _setupData();
         private _loadExtensions();

+ 2 - 2
dist/preview release/loaders/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-loaders",
     "description": "The Babylon.js file loaders library is an extension you can use to load different 3D file types into a Babylon scene.",
-    "version": "3.3.0-alpha.1",
+    "version": "3.3.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs-gltf2interface": "3.3.0-alpha.1"
+        "babylonjs-gltf2interface": "3.3.0-alpha.2"
     },
     "peerDependencies": {
         "babylonjs": ">=3.2.0-alpha"

+ 1 - 1
dist/preview release/materialsLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-materials",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "3.3.0-alpha.1",
+    "version": "3.3.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 1 - 1
dist/preview release/postProcessesLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-post-process",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "3.3.0-alpha.1",
+    "version": "3.3.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 1 - 1
dist/preview release/proceduralTexturesLibrary/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-procedural-textures",
     "description": "The Babylon.js materials library is a collection of advanced materials to be used in a Babylon.js scene.",
-    "version": "3.3.0-alpha.1",
+    "version": "3.3.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 27 - 31
dist/preview release/serializers/babylon.glTF2Serializer.d.ts

@@ -20,15 +20,6 @@ declare module BABYLON {
      */
     class GLTF2Export {
         /**
-         * Exports the geometry of the scene to .gltf file format synchronously
-         * @param scene Babylon scene with scene hierarchy information
-         * @param filePrefix File prefix to use when generating the glTF file
-         * @param options Exporter options
-         * @returns Returns an object with a .gltf file and associates texture names
-         * as keys and their data and paths as values
-         */
-        private static GLTF(scene, filePrefix, options?);
-        /**
          * Exports the geometry of the scene to .gltf file format asynchronously
          * @param scene Babylon scene with scene hierarchy information
          * @param filePrefix File prefix to use when generating the glTF file
@@ -38,14 +29,6 @@ declare module BABYLON {
          */
         static GLTFAsync(scene: Scene, filePrefix: string, options?: IExportOptions): Promise<GLTFData>;
         /**
-         * Exports the geometry of the scene to .glb file format synchronously
-         * @param scene Babylon scene with scene hierarchy information
-         * @param filePrefix File prefix to use when generating glb file
-         * @param options Exporter options
-         * @returns Returns an object with a .glb filename as key and data as value
-         */
-        private static GLB(scene, filePrefix, options?);
-        /**
          * Exports the geometry of the scene to .glb file format asychronously
          * @param scene Babylon scene with scene hierarchy information
          * @param filePrefix File prefix to use when generating glb file
@@ -139,6 +122,9 @@ declare module BABYLON.GLTF2 {
          * Baked animation sample rate
          */
         private animationSampleRate;
+        /**
+         * Callback which specifies if a transform node should be exported or not
+         */
         private shouldExportTransformNode;
         /**
          * Creates a glTF Exporter instance, which can accept optional exporter options
@@ -226,12 +212,12 @@ declare module BABYLON.GLTF2 {
          * @param glTFPrefix Text to use when prefixing a glTF file
          * @returns GLTFData with glTF file data
          */
-        _generateGLTF(glTFPrefix: string): GLTFData;
+        _generateGLTFAsync(glTFPrefix: string): Promise<GLTFData>;
         /**
          * Creates a binary buffer for glTF
          * @returns array buffer for binary data
          */
-        private generateBinary();
+        private _generateBinaryAsync();
         /**
          * Pads the number to a multiple of 4
          * @param num number to pad
@@ -244,7 +230,7 @@ declare module BABYLON.GLTF2 {
          * @param glTFPrefix
          * @returns object with glb filename as key and data as value
          */
-        _generateGLB(glTFPrefix: string): GLTFData;
+        _generateGLBAsync(glTFPrefix: string): Promise<GLTFData>;
         /**
          * Sets the TRS for each node
          * @param node glTF Node for storing the transformation data
@@ -290,10 +276,12 @@ declare module BABYLON.GLTF2 {
          * @param babylonScene Babylon scene to get the mesh data from
          * @param binaryWriter Buffer to write binary data to
          */
-        private createScene(babylonScene, binaryWriter);
+        private createSceneAsync(babylonScene, binaryWriter);
         /**
          * Creates a mapping of Node unique id to node index and handles animations
          * @param babylonScene Babylon Scene
+         * @param nodes Babylon transform nodes
+         * @param shouldExportTransformNode Callback specifying if a transform node should be exported
          * @param binaryWriter Buffer to write binary data to
          * @returns Node mapping of unique id to index
          */
@@ -433,12 +421,12 @@ declare module BABYLON.GLTF2 {
          * @param imageData mapping of texture names to base64 textures
          * @param hasTextureCoords specifies if texture coordinates are present on the material
          */
-        static _ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
+        static _ConvertMaterialsToGLTFAsync(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
             [fileName: string]: {
                 data: Uint8Array;
                 mimeType: ImageMimeType;
             };
-        }, hasTextureCoords: boolean): void;
+        }, hasTextureCoords: boolean): Promise<void>;
         /**
          * Makes a copy of the glTF material without the texture parameters
          * @param originalMaterial original glTF material
@@ -481,12 +469,19 @@ declare module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        static _ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
+        static _ConvertStandardMaterialAsync(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
             [fileName: string]: {
                 data: Uint8Array;
                 mimeType: ImageMimeType;
             };
-        }, hasTextureCoords: boolean): void;
+        }, hasTextureCoords: boolean): Promise<void>;
+        /**
+         *
+         * @param texture Texture with alpha to overwrite to one
+         * @param useAlpha Specifies if alpha should be preserved or not
+         * @returns Promise with texture
+         */
+        static _SetAlphaToOneAsync(texture: BaseTexture, useAlpha: boolean): Promise<Texture>;
         /**
          * Converts a Babylon PBR Metallic Roughness Material to a glTF Material
          * @param babylonPBRMetalRoughMaterial BJS PBR Metallic Roughness Material
@@ -497,12 +492,12 @@ declare module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        static _ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
+        static _ConvertPBRMetallicRoughnessMaterialAsync(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
             [fileName: string]: {
                 data: Uint8Array;
                 mimeType: ImageMimeType;
             };
-        }, hasTextureCoords: boolean): void;
+        }, hasTextureCoords: boolean): Promise<void>;
         /**
          * Converts an image typed array buffer to a base64 image
          * @param buffer typed array buffer
@@ -568,7 +563,7 @@ declare module BABYLON.GLTF2 {
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          * @returns glTF PBR Metallic Roughness factors
          */
-        private static _ConvertMetalRoughFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
+        private static _ConvertMetalRoughFactorsToMetallicRoughnessAsync(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
         private static _GetGLTFTextureSampler(texture);
         private static _GetGLTFTextureWrapMode(wrapMode);
         private static _GetGLTFTextureWrapModesSampler(texture);
@@ -594,12 +589,13 @@ declare module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        static _ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
+        static _ConvertPBRMaterialAsync(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
             [fileName: string]: {
                 data: Uint8Array;
                 mimeType: ImageMimeType;
             };
-        }, hasTextureCoords: boolean): void;
+        }, hasTextureCoords: boolean): Promise<void>;
+        private static SetMetallicRoughnessPbrMaterial(metallicRoughness, babylonPBRMaterial, glTFMaterial, glTFPbrMetallicRoughness, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
         private static GetPixelsFromTexture(babylonTexture);
         /**
          * Extracts a texture from a Babylon texture into file data and glTF data
@@ -610,7 +606,7 @@ declare module BABYLON.GLTF2 {
          * @param imageData map of image file name and data
          * @return glTF texture info, or null if the texture format is not supported
          */
-        private static _ExportTexture(babylonTexture, mimeType, images, textures, samplers, imageData);
+        private static _ExportTextureAsync(babylonTexture, mimeType, images, textures, samplers, imageData, useAlpha);
         /**
          * Builds a texture from base64 string
          * @param base64Texture base64 texture string

文件差異過大導致無法顯示
+ 453 - 407
dist/preview release/serializers/babylon.glTF2Serializer.js


文件差異過大導致無法顯示
+ 2 - 2
dist/preview release/serializers/babylon.glTF2Serializer.min.js


+ 27 - 31
dist/preview release/serializers/babylonjs.serializers.d.ts

@@ -28,15 +28,6 @@ declare module BABYLON {
      */
     class GLTF2Export {
         /**
-         * Exports the geometry of the scene to .gltf file format synchronously
-         * @param scene Babylon scene with scene hierarchy information
-         * @param filePrefix File prefix to use when generating the glTF file
-         * @param options Exporter options
-         * @returns Returns an object with a .gltf file and associates texture names
-         * as keys and their data and paths as values
-         */
-        private static GLTF(scene, filePrefix, options?);
-        /**
          * Exports the geometry of the scene to .gltf file format asynchronously
          * @param scene Babylon scene with scene hierarchy information
          * @param filePrefix File prefix to use when generating the glTF file
@@ -46,14 +37,6 @@ declare module BABYLON {
          */
         static GLTFAsync(scene: Scene, filePrefix: string, options?: IExportOptions): Promise<GLTFData>;
         /**
-         * Exports the geometry of the scene to .glb file format synchronously
-         * @param scene Babylon scene with scene hierarchy information
-         * @param filePrefix File prefix to use when generating glb file
-         * @param options Exporter options
-         * @returns Returns an object with a .glb filename as key and data as value
-         */
-        private static GLB(scene, filePrefix, options?);
-        /**
          * Exports the geometry of the scene to .glb file format asychronously
          * @param scene Babylon scene with scene hierarchy information
          * @param filePrefix File prefix to use when generating glb file
@@ -147,6 +130,9 @@ declare module BABYLON.GLTF2 {
          * Baked animation sample rate
          */
         private animationSampleRate;
+        /**
+         * Callback which specifies if a transform node should be exported or not
+         */
         private shouldExportTransformNode;
         /**
          * Creates a glTF Exporter instance, which can accept optional exporter options
@@ -234,12 +220,12 @@ declare module BABYLON.GLTF2 {
          * @param glTFPrefix Text to use when prefixing a glTF file
          * @returns GLTFData with glTF file data
          */
-        _generateGLTF(glTFPrefix: string): GLTFData;
+        _generateGLTFAsync(glTFPrefix: string): Promise<GLTFData>;
         /**
          * Creates a binary buffer for glTF
          * @returns array buffer for binary data
          */
-        private generateBinary();
+        private _generateBinaryAsync();
         /**
          * Pads the number to a multiple of 4
          * @param num number to pad
@@ -252,7 +238,7 @@ declare module BABYLON.GLTF2 {
          * @param glTFPrefix
          * @returns object with glb filename as key and data as value
          */
-        _generateGLB(glTFPrefix: string): GLTFData;
+        _generateGLBAsync(glTFPrefix: string): Promise<GLTFData>;
         /**
          * Sets the TRS for each node
          * @param node glTF Node for storing the transformation data
@@ -298,10 +284,12 @@ declare module BABYLON.GLTF2 {
          * @param babylonScene Babylon scene to get the mesh data from
          * @param binaryWriter Buffer to write binary data to
          */
-        private createScene(babylonScene, binaryWriter);
+        private createSceneAsync(babylonScene, binaryWriter);
         /**
          * Creates a mapping of Node unique id to node index and handles animations
          * @param babylonScene Babylon Scene
+         * @param nodes Babylon transform nodes
+         * @param shouldExportTransformNode Callback specifying if a transform node should be exported
          * @param binaryWriter Buffer to write binary data to
          * @returns Node mapping of unique id to index
          */
@@ -441,12 +429,12 @@ declare module BABYLON.GLTF2 {
          * @param imageData mapping of texture names to base64 textures
          * @param hasTextureCoords specifies if texture coordinates are present on the material
          */
-        static _ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
+        static _ConvertMaterialsToGLTFAsync(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
             [fileName: string]: {
                 data: Uint8Array;
                 mimeType: ImageMimeType;
             };
-        }, hasTextureCoords: boolean): void;
+        }, hasTextureCoords: boolean): Promise<void>;
         /**
          * Makes a copy of the glTF material without the texture parameters
          * @param originalMaterial original glTF material
@@ -489,12 +477,19 @@ declare module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        static _ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
+        static _ConvertStandardMaterialAsync(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
             [fileName: string]: {
                 data: Uint8Array;
                 mimeType: ImageMimeType;
             };
-        }, hasTextureCoords: boolean): void;
+        }, hasTextureCoords: boolean): Promise<void>;
+        /**
+         *
+         * @param texture Texture with alpha to overwrite to one
+         * @param useAlpha Specifies if alpha should be preserved or not
+         * @returns Promise with texture
+         */
+        static _SetAlphaToOneAsync(texture: BaseTexture, useAlpha: boolean): Promise<Texture>;
         /**
          * Converts a Babylon PBR Metallic Roughness Material to a glTF Material
          * @param babylonPBRMetalRoughMaterial BJS PBR Metallic Roughness Material
@@ -505,12 +500,12 @@ declare module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        static _ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
+        static _ConvertPBRMetallicRoughnessMaterialAsync(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
             [fileName: string]: {
                 data: Uint8Array;
                 mimeType: ImageMimeType;
             };
-        }, hasTextureCoords: boolean): void;
+        }, hasTextureCoords: boolean): Promise<void>;
         /**
          * Converts an image typed array buffer to a base64 image
          * @param buffer typed array buffer
@@ -576,7 +571,7 @@ declare module BABYLON.GLTF2 {
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          * @returns glTF PBR Metallic Roughness factors
          */
-        private static _ConvertMetalRoughFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
+        private static _ConvertMetalRoughFactorsToMetallicRoughnessAsync(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
         private static _GetGLTFTextureSampler(texture);
         private static _GetGLTFTextureWrapMode(wrapMode);
         private static _GetGLTFTextureWrapModesSampler(texture);
@@ -602,12 +597,13 @@ declare module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        static _ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
+        static _ConvertPBRMaterialAsync(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
             [fileName: string]: {
                 data: Uint8Array;
                 mimeType: ImageMimeType;
             };
-        }, hasTextureCoords: boolean): void;
+        }, hasTextureCoords: boolean): Promise<void>;
+        private static SetMetallicRoughnessPbrMaterial(metallicRoughness, babylonPBRMaterial, glTFMaterial, glTFPbrMetallicRoughness, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
         private static GetPixelsFromTexture(babylonTexture);
         /**
          * Extracts a texture from a Babylon texture into file data and glTF data
@@ -618,7 +614,7 @@ declare module BABYLON.GLTF2 {
          * @param imageData map of image file name and data
          * @return glTF texture info, or null if the texture format is not supported
          */
-        private static _ExportTexture(babylonTexture, mimeType, images, textures, samplers, imageData);
+        private static _ExportTextureAsync(babylonTexture, mimeType, images, textures, samplers, imageData, useAlpha);
         /**
          * Builds a texture from base64 string
          * @param base64Texture base64 texture string

文件差異過大導致無法顯示
+ 453 - 407
dist/preview release/serializers/babylonjs.serializers.js


文件差異過大導致無法顯示
+ 2 - 2
dist/preview release/serializers/babylonjs.serializers.min.js


+ 27 - 31
dist/preview release/serializers/babylonjs.serializers.module.d.ts

@@ -35,15 +35,6 @@ declare module BABYLON {
      */
     class GLTF2Export {
         /**
-         * Exports the geometry of the scene to .gltf file format synchronously
-         * @param scene Babylon scene with scene hierarchy information
-         * @param filePrefix File prefix to use when generating the glTF file
-         * @param options Exporter options
-         * @returns Returns an object with a .gltf file and associates texture names
-         * as keys and their data and paths as values
-         */
-        private static GLTF(scene, filePrefix, options?);
-        /**
          * Exports the geometry of the scene to .gltf file format asynchronously
          * @param scene Babylon scene with scene hierarchy information
          * @param filePrefix File prefix to use when generating the glTF file
@@ -53,14 +44,6 @@ declare module BABYLON {
          */
         static GLTFAsync(scene: Scene, filePrefix: string, options?: IExportOptions): Promise<GLTFData>;
         /**
-         * Exports the geometry of the scene to .glb file format synchronously
-         * @param scene Babylon scene with scene hierarchy information
-         * @param filePrefix File prefix to use when generating glb file
-         * @param options Exporter options
-         * @returns Returns an object with a .glb filename as key and data as value
-         */
-        private static GLB(scene, filePrefix, options?);
-        /**
          * Exports the geometry of the scene to .glb file format asychronously
          * @param scene Babylon scene with scene hierarchy information
          * @param filePrefix File prefix to use when generating glb file
@@ -154,6 +137,9 @@ declare module BABYLON.GLTF2 {
          * Baked animation sample rate
          */
         private animationSampleRate;
+        /**
+         * Callback which specifies if a transform node should be exported or not
+         */
         private shouldExportTransformNode;
         /**
          * Creates a glTF Exporter instance, which can accept optional exporter options
@@ -241,12 +227,12 @@ declare module BABYLON.GLTF2 {
          * @param glTFPrefix Text to use when prefixing a glTF file
          * @returns GLTFData with glTF file data
          */
-        _generateGLTF(glTFPrefix: string): GLTFData;
+        _generateGLTFAsync(glTFPrefix: string): Promise<GLTFData>;
         /**
          * Creates a binary buffer for glTF
          * @returns array buffer for binary data
          */
-        private generateBinary();
+        private _generateBinaryAsync();
         /**
          * Pads the number to a multiple of 4
          * @param num number to pad
@@ -259,7 +245,7 @@ declare module BABYLON.GLTF2 {
          * @param glTFPrefix
          * @returns object with glb filename as key and data as value
          */
-        _generateGLB(glTFPrefix: string): GLTFData;
+        _generateGLBAsync(glTFPrefix: string): Promise<GLTFData>;
         /**
          * Sets the TRS for each node
          * @param node glTF Node for storing the transformation data
@@ -305,10 +291,12 @@ declare module BABYLON.GLTF2 {
          * @param babylonScene Babylon scene to get the mesh data from
          * @param binaryWriter Buffer to write binary data to
          */
-        private createScene(babylonScene, binaryWriter);
+        private createSceneAsync(babylonScene, binaryWriter);
         /**
          * Creates a mapping of Node unique id to node index and handles animations
          * @param babylonScene Babylon Scene
+         * @param nodes Babylon transform nodes
+         * @param shouldExportTransformNode Callback specifying if a transform node should be exported
          * @param binaryWriter Buffer to write binary data to
          * @returns Node mapping of unique id to index
          */
@@ -448,12 +436,12 @@ declare module BABYLON.GLTF2 {
          * @param imageData mapping of texture names to base64 textures
          * @param hasTextureCoords specifies if texture coordinates are present on the material
          */
-        static _ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
+        static _ConvertMaterialsToGLTFAsync(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
             [fileName: string]: {
                 data: Uint8Array;
                 mimeType: ImageMimeType;
             };
-        }, hasTextureCoords: boolean): void;
+        }, hasTextureCoords: boolean): Promise<void>;
         /**
          * Makes a copy of the glTF material without the texture parameters
          * @param originalMaterial original glTF material
@@ -496,12 +484,19 @@ declare module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        static _ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
+        static _ConvertStandardMaterialAsync(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
             [fileName: string]: {
                 data: Uint8Array;
                 mimeType: ImageMimeType;
             };
-        }, hasTextureCoords: boolean): void;
+        }, hasTextureCoords: boolean): Promise<void>;
+        /**
+         *
+         * @param texture Texture with alpha to overwrite to one
+         * @param useAlpha Specifies if alpha should be preserved or not
+         * @returns Promise with texture
+         */
+        static _SetAlphaToOneAsync(texture: BaseTexture, useAlpha: boolean): Promise<Texture>;
         /**
          * Converts a Babylon PBR Metallic Roughness Material to a glTF Material
          * @param babylonPBRMetalRoughMaterial BJS PBR Metallic Roughness Material
@@ -512,12 +507,12 @@ declare module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        static _ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
+        static _ConvertPBRMetallicRoughnessMaterialAsync(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
             [fileName: string]: {
                 data: Uint8Array;
                 mimeType: ImageMimeType;
             };
-        }, hasTextureCoords: boolean): void;
+        }, hasTextureCoords: boolean): Promise<void>;
         /**
          * Converts an image typed array buffer to a base64 image
          * @param buffer typed array buffer
@@ -583,7 +578,7 @@ declare module BABYLON.GLTF2 {
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          * @returns glTF PBR Metallic Roughness factors
          */
-        private static _ConvertMetalRoughFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
+        private static _ConvertMetalRoughFactorsToMetallicRoughnessAsync(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
         private static _GetGLTFTextureSampler(texture);
         private static _GetGLTFTextureWrapMode(wrapMode);
         private static _GetGLTFTextureWrapModesSampler(texture);
@@ -609,12 +604,13 @@ declare module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        static _ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
+        static _ConvertPBRMaterialAsync(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: {
             [fileName: string]: {
                 data: Uint8Array;
                 mimeType: ImageMimeType;
             };
-        }, hasTextureCoords: boolean): void;
+        }, hasTextureCoords: boolean): Promise<void>;
+        private static SetMetallicRoughnessPbrMaterial(metallicRoughness, babylonPBRMaterial, glTFMaterial, glTFPbrMetallicRoughness, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
         private static GetPixelsFromTexture(babylonTexture);
         /**
          * Extracts a texture from a Babylon texture into file data and glTF data
@@ -625,7 +621,7 @@ declare module BABYLON.GLTF2 {
          * @param imageData map of image file name and data
          * @return glTF texture info, or null if the texture format is not supported
          */
-        private static _ExportTexture(babylonTexture, mimeType, images, textures, samplers, imageData);
+        private static _ExportTextureAsync(babylonTexture, mimeType, images, textures, samplers, imageData, useAlpha);
         /**
          * Builds a texture from base64 string
          * @param base64Texture base64 texture string

+ 2 - 2
dist/preview release/serializers/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-serializers",
     "description": "The Babylon.js serializers library is an extension you can use to serialize Babylon scenes.",
-    "version": "3.3.0-alpha.1",
+    "version": "3.3.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs-gltf2interface": "3.3.0-alpha.1"
+        "babylonjs-gltf2interface": "3.3.0-alpha.2"
     },
     "peerDependencies": {
         "babylonjs": ">=3.2.0-alpha"

+ 2 - 40
dist/preview release/typedocValidationBaseline.json

@@ -1,7 +1,7 @@
 {
-  "errors": 4307,
+  "errors": 4301,
   "babylon.typedoc.json": {
-    "errors": 4307,
+    "errors": 4301,
     "Animatable": {
       "Class": {
         "Comments": {
@@ -22168,44 +22168,6 @@
         }
       }
     },
-    "Behavior": {
-      "Interface": {
-        "Comments": {
-          "MissingText": true
-        }
-      },
-      "Property": {
-        "name": {
-          "Comments": {
-            "MissingText": true
-          }
-        }
-      },
-      "Method": {
-        "attach": {
-          "Comments": {
-            "MissingText": true
-          },
-          "Parameter": {
-            "node": {
-              "Comments": {
-                "MissingText": true
-              }
-            }
-          }
-        },
-        "detach": {
-          "Comments": {
-            "MissingText": true
-          }
-        },
-        "init": {
-          "Comments": {
-            "MissingText": true
-          }
-        }
-      }
-    },
     "CameraInputsMap": {
       "Interface": {
         "Comments": {

文件差異過大導致無法顯示
+ 50 - 50
dist/preview release/viewer/babylon.viewer.js


+ 46 - 18
dist/preview release/viewer/babylon.viewer.max.js

@@ -12173,7 +12173,7 @@ var BABYLON;
              * Returns the current version of the framework
              */
             get: function () {
-                return "3.3.0-alpha.1";
+                return "3.3.0-alpha.2";
             },
             enumerable: true,
             configurable: true
@@ -17745,12 +17745,8 @@ var BABYLON;
             behavior.init();
             if (this._scene.isLoading) {
                 // We defer the attach when the scene will be loaded
-                var observer = this._scene.onDataLoadedObservable.add(function () {
+                this._scene.onDataLoadedObservable.addOnce(function () {
                     behavior.attach(_this);
-                    setTimeout(function () {
-                        // Need to use a timeout to avoid removing an observer while iterating the list of observers
-                        _this._scene.onDataLoadedObservable.remove(observer);
-                    }, 0);
                 });
             }
             else {
@@ -20245,6 +20241,16 @@ var BABYLON;
             this._edgesRenderer = new BABYLON.EdgesRenderer(this, epsilon, checkVerticesInsteadOfIndices);
             return this;
         };
+        Object.defineProperty(AbstractMesh.prototype, "edgesRenderer", {
+            /**
+             * Gets the edgesRenderer associated with the mesh
+             */
+            get: function () {
+                return this._edgesRenderer;
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(AbstractMesh.prototype, "isBlocked", {
             /**
              * Returns true if the mesh is blocked. Implemented by child classes
@@ -23682,7 +23688,7 @@ var BABYLON;
                 }
                 this._opaqueSubMeshes.push(subMesh); // Opaque
             }
-            if (mesh._edgesRenderer !== null && mesh._edgesRenderer !== undefined) {
+            if (mesh._edgesRenderer !== null && mesh._edgesRenderer !== undefined && mesh._edgesRenderer.isEnabled) {
                 this._edgesRenderers.push(mesh._edgesRenderer);
             }
         };
@@ -65205,9 +65211,11 @@ var BABYLON;
         /**
          * Updates the texture
          * @param invertY defines the direction for the Y axis (default is true - y increases downwards)
+         * @param premulAlpha defines if alpha is stored as premultiplied (default is false)
          */
-        DynamicTexture.prototype.update = function (invertY) {
-            this._engine.updateDynamicTexture(this._texture, this._canvas, invertY === undefined ? true : invertY, undefined, this._format || undefined);
+        DynamicTexture.prototype.update = function (invertY, premulAlpha) {
+            if (premulAlpha === void 0) { premulAlpha = false; }
+            this._engine.updateDynamicTexture(this._texture, this._canvas, invertY === undefined ? true : invertY, premulAlpha, this._format || undefined);
         };
         /**
          * Draws text onto the texture
@@ -69152,13 +69160,15 @@ var BABYLON;
                         BABYLON.Tools.ClearLogCache();
                     }
                     this._engine.stopRenderLoop();
-                    this._currentScene.dispose();
                 }
                 BABYLON.SceneLoader.LoadAsync("file:", this._sceneFileToLoad, this._engine, function (progress) {
                     if (_this._progressCallback) {
                         _this._progressCallback(progress);
                     }
                 }).then(function (scene) {
+                    if (_this._currentScene) {
+                        _this._currentScene.dispose();
+                    }
                     _this._currentScene = scene;
                     if (_this._sceneLoadedCallback) {
                         _this._sceneLoadedCallback(_this._sceneFileToLoad, _this._currentScene);
@@ -86481,6 +86491,13 @@ var BABYLON;
                 this._createInspector(config);
             }
         };
+        /**
+         * Gets the active tab
+         * @return the index of the active tab or -1 if the inspector is hidden
+         */
+        DebugLayer.prototype.getActiveTab = function () {
+            return this._inspector ? this._inspector.getActiveTabIndex() : -1;
+        };
         DebugLayer.InspectorURL = 'https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js';
         return DebugLayer;
     }());
@@ -92322,6 +92339,8 @@ var BABYLON;
             this._linesIndices = new Array();
             this._buffers = {};
             this._checkVerticesInsteadOfIndices = false;
+            /** Gets or sets a boolean indicating if the edgesRenderer is active */
+            this.isEnabled = true;
             this._source = source;
             this._checkVerticesInsteadOfIndices = checkVerticesInsteadOfIndices;
             this._epsilon = epsilon;
@@ -106058,6 +106077,10 @@ var BABYLON;
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onProgress) {
                 var _this = this;
                 return Promise.resolve().then(function () {
+                    _this._babylonScene = scene;
+                    _this._rootUrl = rootUrl;
+                    _this._progressCallback = onProgress;
+                    _this._loadData(data);
                     var nodes = null;
                     if (meshesNames) {
                         var nodeMap_1 = {};
@@ -106078,7 +106101,7 @@ var BABYLON;
                             return node;
                         });
                     }
-                    return _this._loadAsync(nodes, scene, data, rootUrl, onProgress).then(function () {
+                    return _this._loadAsync(nodes).then(function () {
                         return {
                             meshes: _this._getMeshes(),
                             particleSystems: [],
@@ -106097,16 +106120,19 @@ var BABYLON;
              * @returns a promise which completes when objects have been loaded to the scene
              */
             GLTFLoader.prototype.loadAsync = function (scene, data, rootUrl, onProgress) {
-                return this._loadAsync(null, scene, data, rootUrl, onProgress);
-            };
-            GLTFLoader.prototype._loadAsync = function (nodes, scene, data, rootUrl, onProgress) {
                 var _this = this;
                 return Promise.resolve().then(function () {
                     _this._babylonScene = scene;
                     _this._rootUrl = rootUrl;
                     _this._progressCallback = onProgress;
-                    _this._state = BABYLON.GLTFLoaderState.LOADING;
                     _this._loadData(data);
+                    return _this._loadAsync(null);
+                });
+            };
+            GLTFLoader.prototype._loadAsync = function (nodes) {
+                var _this = this;
+                return Promise.resolve().then(function () {
+                    _this._state = BABYLON.GLTFLoaderState.LOADING;
                     _this._loadExtensions();
                     _this._checkExtensions();
                     var promises = new Array();
@@ -106114,8 +106140,8 @@ var BABYLON;
                         promises.push(_this._loadNodesAsync(nodes));
                     }
                     else {
-                        var scene_1 = GLTFLoader._GetProperty("#/scene", _this._gltf.scenes, _this._gltf.scene || 0);
-                        promises.push(_this._loadSceneAsync("#/scenes/" + scene_1._index, scene_1));
+                        var scene = GLTFLoader._GetProperty("#/scene", _this._gltf.scenes, _this._gltf.scene || 0);
+                        promises.push(_this._loadSceneAsync("#/scenes/" + scene._index, scene));
                     }
                     if (_this.compileMaterials) {
                         promises.push(_this._compileMaterialsAsync());
@@ -106129,7 +106155,9 @@ var BABYLON;
                         _this._startAnimations();
                     });
                     resultPromise.then(function () {
-                        _this._rootBabylonMesh.setEnabled(true);
+                        if (_this._rootBabylonMesh) {
+                            _this._rootBabylonMesh.setEnabled(true);
+                        }
                         BABYLON.Tools.SetImmediate(function () {
                             if (!_this._disposed) {
                                 Promise.all(_this._completePromises).then(function () {

+ 1 - 1
dist/preview release/viewer/package.json

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-viewer",
     "description": "A simple-to-use viewer based on BabylonJS to display 3D elements natively",
-    "version": "3.3.0-alpha.1",
+    "version": "3.3.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

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

@@ -19,12 +19,14 @@
 - Added webVR constructor options: disable laser pointer toggle, teleportation floor meshes ([TrevorDev](https://github.com/TrevorDev))
 - Get a root mesh from an asset container, load a mesh from a file with a single string url ([TrevorDev](https://github.com/TrevorDev))
 - 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))
 
 ### glTF Loader
 
 - Added support for KHR_texture_transform ([bghgary](http://www.github.com/bghgary))
 - Added `onNodeLODsLoadedObservable` and `onMaterialLODsLoadedObservable` to MSFT_lod loader extension ([bghgary](http://www.github.com/bghgary))
+- Added glTF loader settings to the GLTF tab in the debug layer ([bghgary](http://www.github.com/bghgary))
 
 ### Viewer
 

+ 6 - 1
gui/src/2D/advancedDynamicTexture.ts

@@ -34,6 +34,11 @@ module BABYLON.GUI {
         private _blockNextFocusCheck = false;
         private _renderScale = 1;
 
+        /**
+         * Gets or sets a boolean defining if alpha is stored as premultiplied
+         */
+        public premulAlpha = false;
+
         public get renderScale(): number {
             return this._renderScale;
         }
@@ -382,7 +387,7 @@ module BABYLON.GUI {
             this._isDirty = false;
 
             this._render();
-            this.update();
+            this.update(true, this.premulAlpha);
         }
 
         private _render(): void {

+ 105 - 0
gui/src/3D/controls/button3D.ts

@@ -0,0 +1,105 @@
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON.GUI {
+    /**
+     * Class used to create a button in 3D
+     */
+    export class Button3D extends Control3D {
+        /** @hidden */
+        protected _currentMaterial: Material;
+        private _facadeTexture: AdvancedDynamicTexture;
+        private _content: Control;
+
+        /**
+         * Creates a new button
+         * @param name defines the control name
+         */
+        constructor(name?: string) {
+            super(name);
+
+            // Default animations
+
+            this.pointerEnterAnimation = () => {
+                if (!this.mesh) {
+                    return;
+                }
+                (<StandardMaterial>this._currentMaterial).emissiveColor = Color3.Red();
+            }
+
+            this.pointerOutAnimation = () => {
+                (<StandardMaterial>this._currentMaterial).emissiveColor = Color3.Black();
+            }    
+
+            this.pointerDownAnimation = () => {
+                if (!this.mesh) {
+                    return;
+                }
+
+                this.mesh.scaling.scaleInPlace(0.95);
+            }
+
+            this.pointerUpAnimation = () => {
+                if (!this.mesh) {
+                    return;
+                }
+
+                this.mesh.scaling.scaleInPlace(1.0 / 0.95);
+            }                     
+        }
+
+        /**
+         * Gets or sets the GUI 2D content used to display the button's facade
+         */
+        public get content(): Control {
+            return this._content;
+        }
+
+        public set content(value: Control) {
+            if (!this._host || !this._host.utilityLayer) {
+                return;
+            }
+
+            if (!this._facadeTexture) {
+                this._facadeTexture = new BABYLON.GUI.AdvancedDynamicTexture("Facade", 512, 512, this._host.utilityLayer.utilityLayerScene, true, BABYLON.Texture.TRILINEAR_SAMPLINGMODE);
+                this._facadeTexture.rootContainer.scaleX = 2;
+                this._facadeTexture.rootContainer.scaleY = 2;
+                this._facadeTexture.premulAlpha = true;
+            }
+            
+            this._facadeTexture.addControl(value);
+        
+            (<any>this._currentMaterial).emissiveTexture = this._facadeTexture;
+        }
+
+        protected _getTypeName(): string {
+            return "Button3D";
+        }        
+
+        // Mesh association
+        protected _createMesh(scene: Scene): Mesh {
+            var faceUV = new Array(6);
+
+            for (var i = 0; i < 6; i++) {
+                faceUV[i] = new BABYLON.Vector4(0, 0, 0, 0);
+            }
+            faceUV[1] = new BABYLON.Vector4(0, 0, 1, 1);
+
+            let mesh = MeshBuilder.CreateBox(this.name + "Mesh", {
+                width: 1.0, 
+                height: 1.0,
+                depth: 0.1,
+                faceUV: faceUV
+            }, scene); 
+           
+            return mesh;
+        }
+
+        protected _affectMaterial(mesh: Mesh) {
+            let material = new StandardMaterial(this.name + "Material", mesh.getScene());
+            material.specularColor = Color3.Black();
+
+            mesh.material = material;
+            this._currentMaterial = material;
+        }
+    }
+}

+ 82 - 0
gui/src/3D/controls/container3D.ts

@@ -0,0 +1,82 @@
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON.GUI {
+    /**
+     * Class used to create containers for controls
+     */
+    export class Container3D extends Control3D {
+        private _children = new Array<Control3D>();
+
+        /**
+         * Creates a new container
+         * @param name defines the container name
+         */
+        constructor(name?: string) {
+            super(name);
+        }
+
+        /**
+         * Gets a boolean indicating if the given control is in the children of this control
+         * @param control defines the control to check
+         * @returns true if the control is in the child list
+         */
+        public containsControl(control: Control3D): boolean {
+            return this._children.indexOf(control) !== -1;
+        }
+
+        /**
+         * Adds a control to the children of this control
+         * @param control defines the control to add
+         * @returns the current container
+         */
+        public addControl(control: Control3D): Container3D {
+           var index = this._children.indexOf(control);
+
+            if (index !== -1) {
+                return this;
+            }
+            control.parent = this;
+            control._host = this._host;
+
+            if (this._host.utilityLayer) {
+                control.prepareMesh(this._host.utilityLayer.utilityLayerScene);
+            }
+
+            return this;
+        }
+
+        /**
+         * Removes the control from the children of this control
+         * @param control defines the control to remove
+         * @returns the current container
+         */
+        public removeControl(control: Control3D): Container3D {
+            var index = this._children.indexOf(control);
+
+            if (index !== -1) {
+                this._children.splice(index, 1);
+
+                control.parent = null;
+            }
+
+            return this;
+        }
+
+        protected _getTypeName(): string {
+            return "Container3D";
+        }
+        
+        /**
+         * Releases all associated resources
+         */
+        public dispose() {
+            for (var control of this._children) {
+                control.dispose();
+            }
+
+            this._children = [];
+
+            super.dispose();
+        }
+    }
+}

+ 368 - 1
gui/src/3D/controls/control3D.ts

@@ -1,7 +1,179 @@
 /// <reference path="../../../../dist/preview release/babylon.d.ts"/>
 
 module BABYLON.GUI {
-    export class Control3D {
+    /**
+     * Class used as base class for controls
+     */
+    export class Control3D implements IDisposable, IBehaviorAware<Control3D> {
+        /** @hidden */
+        public _host: GUI3DManager;
+        private _mesh: Nullable<Mesh>;
+        private _downCount = 0;
+        private _enterCount = 0;
+        private _downPointerIds:{[id:number] : boolean} = {};
+        private _isVisible = true;
+
+        /** Callback used to start pointer enter animation */
+        public pointerEnterAnimation: () => void;
+        /** Callback used to start pointer out animation */
+        public pointerOutAnimation: () => void;
+        /** Callback used to start pointer down animation */
+        public pointerDownAnimation: () => void;
+        /** Callback used to start pointer up animation */
+        public pointerUpAnimation: () => void;
+
+        /**
+        * An event triggered when the pointer move over the control.
+        */
+        public onPointerMoveObservable = new Observable<Vector3>();
+
+        /**
+         * An event triggered when the pointer move out of the control.
+         */
+        public onPointerOutObservable = new Observable<Control3D>();
+
+        /**
+         * An event triggered when the pointer taps the control
+         */
+        public onPointerDownObservable = new Observable<Vector3WithInfo>();
+
+        /**
+         * An event triggered when pointer up
+         */
+        public onPointerUpObservable = new Observable<Vector3WithInfo>();
+
+        /**
+         * An event triggered when a control is clicked on
+         */
+        public onPointerClickObservable = new Observable<Vector3WithInfo>();
+
+        /**
+         * An event triggered when pointer enters the control
+         */
+        public onPointerEnterObservable = new Observable<Control3D>();
+        
+        /**
+         * Gets or sets the parent container
+         */
+        public parent: Nullable<Container3D>;
+
+        // Behaviors
+        private _behaviors = new Array<Behavior<Control3D>>();
+
+        /**
+         * Gets the list of attached behaviors
+         * @see http://doc.babylonjs.com/features/behaviour
+         */
+        public get behaviors(): Behavior<Control3D>[] {
+            return this._behaviors;
+        }        
+
+        /**
+         * Attach a behavior to the control
+         * @see http://doc.babylonjs.com/features/behaviour
+         * @param behavior defines the behavior to attach
+         * @returns the current control
+         */
+        public addBehavior(behavior: Behavior<Control3D>): Control3D {
+            var index = this._behaviors.indexOf(behavior);
+
+            if (index !== -1) {
+                return this;
+            }
+
+            behavior.init();
+            let scene = this._host.scene;
+            if (scene.isLoading) {
+                // We defer the attach when the scene will be loaded
+                scene.onDataLoadedObservable.addOnce(() => {
+                    behavior.attach(this);
+                });
+            } else {
+                behavior.attach(this);
+            }
+            this._behaviors.push(behavior);
+
+            return this;
+        }
+
+        /**
+         * Remove an attached behavior
+         * @see http://doc.babylonjs.com/features/behaviour
+         * @param behavior defines the behavior to attach
+         * @returns the current control
+         */
+        public removeBehavior(behavior: Behavior<Control3D>): Control3D {
+            var index = this._behaviors.indexOf(behavior);
+
+            if (index === -1) {
+                return this;
+            }
+
+            this._behaviors[index].detach();
+            this._behaviors.splice(index, 1);
+
+            return this;
+        }        
+
+        /**
+         * Gets an attached behavior by name
+         * @param name defines the name of the behavior to look for
+         * @see http://doc.babylonjs.com/features/behaviour
+         * @returns null if behavior was not found else the requested behavior
+         */
+        public getBehaviorByName(name: string): Nullable<Behavior<Control3D>> {
+            for (var behavior of this._behaviors) {
+                if (behavior.name === name) {
+                    return behavior;
+                }
+            }
+
+            return null;
+        }      
+        
+        /** Gets or sets a boolean indicating if the control is visible */
+        public get isVisible(): boolean {
+            return this._isVisible;
+        }
+
+        public set isVisible(value: boolean) {
+            if (this._isVisible === value) {
+                return;
+            }
+
+            this._isVisible = value;
+            if (this._mesh) {
+                this._mesh.isVisible = value;
+            }
+        }
+
+        /** Gets or sets the control position */
+        public get position(): Vector3 {
+            if (this._mesh) {
+                return this._mesh.position;
+            }
+
+            return Vector3.Zero();
+        }
+
+        public set position(value: Vector3) {
+            if (this._mesh) {
+                this._mesh.position = value;
+            }            
+        }
+
+        /**
+         * Creates a new control
+         * @param name defines the control name
+         */
+        constructor(
+            /** Defines the control name */
+            public name?: string) {
+        }
+
+        /**
+         * Gets a string representing the class name
+         */
         public get typeName(): string {
             return this._getTypeName();
         }
@@ -9,5 +181,200 @@ module BABYLON.GUI {
         protected _getTypeName(): string {
             return "Control3D";
         }
+
+        /**
+         * Gets the mesh used to render this control
+         */
+        public get mesh(): Nullable<Mesh> {
+            return this._mesh;
+        }
+
+        /**
+         * Link the control as child of the given mesh
+         * @param mesh defines the mesh to link to. Use null to unlink the control
+         * @returns the current control
+         */
+        public linkToMesh(mesh: Nullable<AbstractMesh>): Control3D {
+            if (this._mesh) {
+                this._mesh.parent = mesh;
+            }
+            return this;
+        }    
+
+        /**
+         * Get the attached mesh used to render the control
+         * @param scene defines the scene where the mesh must be attached
+         * @returns the attached mesh or null if none
+         */        
+        public prepareMesh(scene: Scene): Nullable<Mesh> {
+            if (!this._mesh) {
+                this._mesh = this._createMesh(scene);
+                this._mesh!.isPickable = true;
+                this._mesh!.metadata = this; // Store the control on the metadata field in order to get it when picking
+
+                this._affectMaterial(this._mesh!);
+            }
+
+            return this._mesh;
+        }
+
+        /**
+         * Mesh creation.
+         * Can be overriden by children
+         * @param scene defines the scene where the mesh must be attached
+         * @returns the attached mesh or null if none
+         */
+        protected _createMesh(scene: Scene): Nullable<Mesh> {
+            // Do nothing by default
+            return null;
+        }
+
+        /**
+         * Affect a material to the given mesh
+         * @param mesh defines the mesh which will represent the control
+         */
+        protected _affectMaterial(mesh: Mesh) {
+            mesh.material = null;
+        }
+
+
+        // Pointers
+
+        /** @hidden */
+        public _onPointerMove(target: Control3D, coordinates: Vector3): void {
+            this.onPointerMoveObservable.notifyObservers(coordinates, -1, target, this);
+        }
+
+        /** @hidden */
+        public _onPointerEnter(target: Control3D): boolean {
+            if (this._enterCount !== 0) {
+                return false;
+            }
+
+            this._enterCount++;
+
+            this.onPointerEnterObservable.notifyObservers(this, -1, target, this);
+
+            if (this.pointerEnterAnimation) {
+                this.pointerEnterAnimation();
+            }
+
+            return true;
+        }
+
+        /** @hidden */
+        public _onPointerOut(target: Control3D): void {
+            this._enterCount = 0;
+
+            this.onPointerOutObservable.notifyObservers(this, -1, target, this);
+
+            if (this.pointerOutAnimation) {
+                this.pointerOutAnimation();
+            }            
+        }
+
+        /** @hidden */
+        public _onPointerDown(target: Control3D, coordinates: Vector3, pointerId:number, buttonIndex: number): boolean {
+            if (this._downCount !== 0) {
+                return false;
+            }
+
+            this._downCount++;
+
+            this._downPointerIds[pointerId] = true;
+
+            this.onPointerDownObservable.notifyObservers(new Vector3WithInfo(coordinates, buttonIndex), -1, target, this);
+
+            if (this.pointerDownAnimation) {
+                this.pointerDownAnimation();
+            }                 
+
+            return true;
+        }
+
+        /** @hidden */
+        public _onPointerUp(target: Control3D, coordinates: Vector3, pointerId:number, buttonIndex: number, notifyClick: boolean): void {
+            this._downCount = 0;
+
+            delete this._downPointerIds[pointerId];
+
+			if (notifyClick && this._enterCount > 0) {
+				this.onPointerClickObservable.notifyObservers(new Vector3WithInfo(coordinates, buttonIndex), -1, target, this);
+			}
+			this.onPointerUpObservable.notifyObservers(new Vector3WithInfo(coordinates, buttonIndex), -1, target, this);            
+
+            if (this.pointerUpAnimation) {
+                this.pointerUpAnimation();
+            }          
+        }  
+
+        /** @hidden */
+        public forcePointerUp(pointerId: Nullable<number> = null) {
+            if(pointerId !== null){
+                this._onPointerUp(this, Vector3.Zero(), pointerId, 0, true);
+            }else{
+                for(var key in this._downPointerIds){
+                    this._onPointerUp(this, Vector3.Zero(), +key as number, 0, true);
+                }
+            }
+        }
+        
+        /** @hidden */
+        public _processObservables(type: number, pickedPoint: Vector3, pointerId:number, buttonIndex: number): boolean {
+            if (type === BABYLON.PointerEventTypes.POINTERMOVE) {
+                this._onPointerMove(this, pickedPoint);
+
+                var previousControlOver = this._host._lastControlOver[pointerId];
+                if (previousControlOver && previousControlOver !== this) {
+                    previousControlOver._onPointerOut(this);
+                }
+
+                if (previousControlOver !== this) {
+                    this._onPointerEnter(this);
+                }
+
+                this._host._lastControlOver[pointerId] = this;
+                return true;
+            }
+
+            if (type === BABYLON.PointerEventTypes.POINTERDOWN) {
+                this._onPointerDown(this, pickedPoint, pointerId, buttonIndex);
+                this._host._lastControlDown[pointerId] = this;
+                this._host._lastPickedControl = this;
+                return true;
+            }
+
+            if (type === BABYLON.PointerEventTypes.POINTERUP) {
+                if (this._host._lastControlDown[pointerId]) {
+                    this._host._lastControlDown[pointerId]._onPointerUp(this, pickedPoint, pointerId, buttonIndex, true);
+                }
+                delete this._host._lastControlDown[pointerId];
+                return true;
+            }
+
+            return false;
+        }        
+
+        /**
+         * Releases all associated resources
+         */
+        public dispose() {
+            this.onPointerDownObservable.clear();
+            this.onPointerEnterObservable.clear();
+            this.onPointerMoveObservable.clear();
+            this.onPointerOutObservable.clear();
+            this.onPointerUpObservable.clear();
+            this.onPointerClickObservable.clear();
+
+            if (this._mesh) {
+                this._mesh.dispose(false, true);
+                this._mesh = null;
+            }
+
+            // Behaviors
+            for (var behavior of this._behaviors) {
+                behavior.detach();
+            }
+        }
     }
 }

+ 56 - 0
gui/src/3D/controls/holographicButton.ts

@@ -0,0 +1,56 @@
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON.GUI {
+    /**
+     * Class used to create a holographic button in 3D
+     */
+    export class HolographicButton extends Button3D {
+        private _edgesRenderer: EdgesRenderer;
+        /**
+         * Creates a new button
+         * @param name defines the control name
+         */
+        constructor(name?: string) {
+            super(name);
+
+            // Default animations
+
+            this.pointerEnterAnimation = () => {
+                if (!this.mesh) {
+                    return;
+                }
+                this._edgesRenderer.isEnabled = true;
+            }
+
+            this.pointerOutAnimation = () => {
+                if (!this.mesh) {
+                    return;
+                }
+                this._edgesRenderer.isEnabled = false;
+            }                      
+        }
+    
+        protected _getTypeName(): string {
+            return "HolographicButton";
+        }        
+
+        // Mesh association
+        protected _createMesh(scene: Scene): Mesh {
+            var mesh = super._createMesh(scene);
+
+            mesh.edgesWidth = 0.5;
+            mesh.edgesColor = new Color4(1.0, 1.0, 1.0, 1.0);
+            mesh.enableEdgesRendering();
+            this._edgesRenderer = mesh.edgesRenderer!;
+            this._edgesRenderer.isEnabled = false
+            
+            return mesh;
+        }
+
+        protected _affectMaterial(mesh: Mesh) {
+            this._currentMaterial = new FluentMaterial(this.name + "Material", mesh.getScene());
+
+            mesh.material = this._currentMaterial;
+        }
+    }
+}

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

@@ -0,0 +1,181 @@
+/// <reference path="../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON.GUI {
+    /**
+     * Class used to manage 3D user interface
+     */
+    export class GUI3DManager implements BABYLON.IDisposable {
+        private _scene: Scene;
+        private _sceneDisposeObserver: Nullable<Observer<Scene>>;
+        private _utilityLayer: Nullable<UtilityLayerRenderer>;
+        private _rootContainer: Container3D;
+        private _pointerObserver: Nullable<Observer<PointerInfoPre>>;
+        public _lastPickedControl: Control3D;
+        /** @hidden */
+        public _lastControlOver: {[pointerId:number]: Control3D} = {};
+        /** @hidden */
+        public _lastControlDown: {[pointerId:number]: Control3D} = {};      
+
+        /** Gets the hosting scene */
+        public get scene(): Scene {
+            return this._scene;
+        }
+
+        public get utilityLayer(): Nullable<UtilityLayerRenderer> {
+            return this._utilityLayer;
+        }
+
+        /**
+         * Creates a new GUI3DManager
+         * @param scene 
+         */
+        public constructor(scene?: Scene) {
+            this._scene = scene || Engine.LastCreatedScene!;
+            this._sceneDisposeObserver = this._scene.onDisposeObservable.add(() => {
+                this._sceneDisposeObserver = null;
+                this._utilityLayer = null;
+                this.dispose();
+            })
+
+            this._utilityLayer = new UtilityLayerRenderer(this._scene);
+
+            // Root
+            this._rootContainer = new Container3D("RootContainer");
+            this._rootContainer._host = this;
+            
+            // Events
+            this._pointerObserver = this._scene.onPrePointerObservable.add((pi, state) => {
+                let pointerEvent = <PointerEvent>(pi.event);
+                if (this._scene.isPointerCaptured(pointerEvent.pointerId)) {
+                    return;
+                }
+
+                if (pi.type !== BABYLON.PointerEventTypes.POINTERMOVE
+                    && pi.type !== BABYLON.PointerEventTypes.POINTERUP
+                    && pi.type !== BABYLON.PointerEventTypes.POINTERDOWN) {
+                    return;
+                }
+
+                let camera = this._scene.cameraToUseForPointers || this._scene.activeCamera;
+
+                if (!camera) {
+                    return;
+                }
+
+                pi.skipOnPointerObservable = this._doPicking(pi.type, pointerEvent)
+            });
+
+            // Scene
+            this._utilityLayer.utilityLayerScene.autoClear = false;
+            this._utilityLayer.utilityLayerScene.autoClearDepthAndStencil = false;
+            new BABYLON.HemisphericLight("hemi", Vector3.Up(), this._utilityLayer.utilityLayerScene);
+        }
+
+        private _doPicking(type: number, pointerEvent: PointerEvent): boolean {
+            if (!this._utilityLayer || !this._utilityLayer.utilityLayerScene.activeCamera) {
+                return false;                
+            }
+
+            let pointerId = pointerEvent.pointerId || 0;
+            let buttonIndex = pointerEvent.button;
+            var utilityScene = this._utilityLayer.utilityLayerScene;
+
+            let pickingInfo = utilityScene.pick(this._scene.pointerX, this._scene.pointerY);
+            if (!pickingInfo || !pickingInfo.hit) {
+                var previousControlOver = this._lastControlOver[pointerId];
+                if (previousControlOver) {
+                    previousControlOver._onPointerOut(previousControlOver);
+                    delete this._lastControlOver[pointerId];
+                }               
+                
+                if (type === BABYLON.PointerEventTypes.POINTERUP) {
+                    if (this._lastControlDown[pointerEvent.pointerId]) {
+                        this._lastControlDown[pointerEvent.pointerId].forcePointerUp();
+                        delete this._lastControlDown[pointerEvent.pointerId];
+                    }
+                }                
+                return false;
+            }
+
+            let control = <Control3D>(pickingInfo.pickedMesh!.metadata);
+
+            if (!control._processObservables(type, pickingInfo.pickedPoint!, pointerId, buttonIndex)) {
+
+                if (type === BABYLON.PointerEventTypes.POINTERMOVE) {
+                    if (this._lastControlOver[pointerId]) {
+                        this._lastControlOver[pointerId]._onPointerOut(this._lastControlOver[pointerId]);
+                    }
+
+                    delete this._lastControlOver[pointerId];
+                }
+            }
+
+            if (type === BABYLON.PointerEventTypes.POINTERUP) {
+                if (this._lastControlDown[pointerEvent.pointerId]) {
+                    this._lastControlDown[pointerEvent.pointerId].forcePointerUp();
+                    delete this._lastControlDown[pointerEvent.pointerId];
+                }
+            }
+
+            return true;
+        }
+
+        /**
+         * Gets the root container
+         */
+        public get rootContainer(): Container3D {
+            return this._rootContainer;
+        }
+
+        /**
+         * Gets a boolean indicating if the given control is in the root child list
+         * @param control defines the control to check
+         * @returns true if the control is in the root child list
+         */
+        public containsControl(control: Control3D): boolean {
+            return this._rootContainer.containsControl(control);
+        }
+
+        /**
+         * Adds a control to the root child list
+         * @param control defines the control to add
+         * @returns the current manager
+         */
+        public addControl(control: Control3D): GUI3DManager {
+           this._rootContainer.addControl(control);
+           return this;
+        }
+
+        /**
+         * Removes the control from the root child list
+         * @param control defines the control to remove
+         * @returns the current container
+         */
+        public removeControl(control: Control3D): GUI3DManager {
+            this._rootContainer.removeControl(control);
+            return this;
+        }        
+
+        /**
+         * Releases all associated resources
+         */
+        public dispose() {
+            this._rootContainer.dispose();
+
+            if (this._scene) {
+                if (this._pointerObserver) {
+                    this._scene.onPrePointerObservable.remove(this._pointerObserver);
+                    this._pointerObserver = null;
+                }
+                if (this._sceneDisposeObserver) {
+                    this._scene.onDisposeObservable.remove(this._sceneDisposeObserver);
+                    this._sceneDisposeObserver = null;
+                }                
+            }
+
+            if (this._utilityLayer) {
+                this._utilityLayer.dispose();
+            }
+        }
+    }
+}

+ 173 - 0
gui/src/3D/materials/fluentMaterial.ts

@@ -0,0 +1,173 @@
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON.GUI {
+    /**
+     * Class used to render controls with fluent desgin
+     */
+    export class FluentMaterial extends PushMaterial {    
+        @serializeAsTexture("emissiveTexture")
+        private _emissiveTexture: BaseTexture;
+        @expandToProperty("_markAllSubMeshesAsTexturesDirty")
+        public emissiveTexture: BaseTexture;
+
+        private _renderId: number;
+
+        /**
+         * Creates a new Fluent material
+         * @param name defines the name of the material
+         * @param scene defines the hosting scene
+         */
+        constructor(name: string, scene: Scene) {
+            super(name, scene);
+        }
+
+        public needAlphaBlending(): boolean {
+            return false;
+        }
+
+        public needAlphaTesting(): boolean {
+            return false;
+        }
+
+        public getAlphaTestTexture(): Nullable<BaseTexture> {
+            return null;
+        } 
+        
+        public isReadyForSubMesh(mesh: AbstractMesh, subMesh: SubMesh, useInstances?: boolean): boolean {
+            if (this.isFrozen) {
+                if (this._wasPreviouslyReady && subMesh.effect) {
+                    return true;
+                }
+            }
+
+            var scene = this.getScene();
+
+            if (!this.checkReadyOnEveryCall && subMesh.effect) {
+                if (this._renderId === scene.getRenderId()) {
+                    return true;
+                }
+            }
+
+            var engine = scene.getEngine();
+
+            scene.resetCachedMaterial();
+
+            //Attributes
+            var attribs = [VertexBuffer.PositionKind];
+            attribs.push(VertexBuffer.NormalKind);
+            attribs.push(VertexBuffer.UVKind);
+
+            var shaderName = "fluent";
+
+            var uniforms = ["world", "viewProjection", "emissiveMatrix"];
+
+            var samplers = ["emissiveSampler"]
+            var uniformBuffers = new Array<string>();
+
+            MaterialHelper.PrepareUniformsAndSamplersList(<EffectCreationOptions>{
+                uniformsNames: uniforms,
+                uniformBuffersNames: uniformBuffers,
+                samplers: samplers,
+                defines: "",
+                maxSimultaneousLights: 4
+            });            
+
+            subMesh.setEffect(scene.getEngine().createEffect(shaderName,
+                <EffectCreationOptions>{
+                    attributes: attribs,
+                    uniformsNames: uniforms,
+                    uniformBuffersNames: uniformBuffers,
+                    samplers: samplers,
+                    defines: "",
+                    fallbacks: null,
+                    onCompiled: this.onCompiled,
+                    onError: this.onError,
+                    indexParameters: { maxSimultaneousLights: 4 }
+                }, engine));
+
+                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 effect = subMesh.effect;
+            if (!effect) {
+                return;
+            }
+            this._activeEffect = effect;
+
+            // Matrices        
+            this.bindOnlyWorldMatrix(world);
+            this._activeEffect.setMatrix("viewProjection", scene.getTransformMatrix());
+
+
+            if (this._mustRebind(scene, effect)) {
+                // Textures        
+                if (this._emissiveTexture && StandardMaterial.DiffuseTextureEnabled) {
+                    this._activeEffect.setTexture("emissiveSampler", this._emissiveTexture);
+
+                    this._activeEffect.setMatrix("emissiveMatrix", this._emissiveTexture.getTextureMatrix());
+                }                
+            }
+
+            this._afterBind(mesh, this._activeEffect);
+        }    
+
+        public getActiveTextures(): BaseTexture[] {
+            var activeTextures = super.getActiveTextures();
+
+            if (this._emissiveTexture) {
+                activeTextures.push(this._emissiveTexture);
+            }
+
+            return activeTextures;
+        }        
+
+        public hasTexture(texture: BaseTexture): boolean {
+            if (super.hasTexture(texture)) {
+                return true;
+            }
+
+            if (this._emissiveTexture === texture) {
+                return true;
+            }
+
+            return false;
+        }        
+        
+        public dispose(forceDisposeEffect?: boolean): void {
+            if (this._emissiveTexture) {
+                this._emissiveTexture.dispose();
+            }
+
+            super.dispose(forceDisposeEffect);
+        }
+
+        public clone(name: string): FluentMaterial {
+            return SerializationHelper.Clone(() => new FluentMaterial(name, this.getScene()), this);
+        }
+
+        public serialize(): any {
+            var serializationObject = SerializationHelper.Serialize(this);
+            serializationObject.customType = "BABYLON.GUI.FluentMaterial";
+            return serializationObject;
+        }
+
+        public getClassName(): string {
+            return "FluentMaterial";
+        }
+
+        // Statics
+        public static Parse(source: any, scene: Scene, rootUrl: string): FluentMaterial {
+            return SerializationHelper.Parse(() => new FluentMaterial(source.name, scene), source, scene, rootUrl);
+        }
+    }
+}

+ 10 - 0
gui/src/3D/materials/shaders/fluent.fragment.fx

@@ -0,0 +1,10 @@
+precision highp float;
+
+varying vec2 vEmissiveUV;
+uniform sampler2D emissiveSampler;
+
+
+void main(void) {
+	vec3 emissiveColor = texture2D(emissiveSampler, vEmissiveUV).rgb;
+	gl_FragColor = vec4(emissiveColor, 1.0);
+}

+ 19 - 0
gui/src/3D/materials/shaders/fluent.vertex.fx

@@ -0,0 +1,19 @@
+precision highp float;
+
+// Attributes
+attribute vec3 position;
+attribute vec3 normal;
+attribute vec2 uv;
+
+// Uniforms
+uniform mat4 world;
+uniform mat4 viewProjection;
+uniform mat4 emissiveMatrix;
+
+varying vec2 vEmissiveUV;
+
+void main(void) {
+	vEmissiveUV = vec2(emissiveMatrix * vec4(uv, 1.0, 0.0));
+
+	gl_Position = viewProjection * world * vec4(position, 1.0);
+}

+ 9 - 0
gui/src/3D/vector3WithInfo.ts

@@ -0,0 +1,9 @@
+/// <reference path="../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON.GUI {
+    export class Vector3WithInfo extends Vector3 {        
+        public constructor(source: Vector3, public buttonIndex: number = 0) {
+            super(source.x, source.y, source.z);
+        }
+    }
+}

+ 38 - 0
inspector/sass/tabs/_gltfTab.scss

@@ -10,6 +10,44 @@
             margin        : 10px 0 10px 0;
         }
 
+        .gltf-action {
+            height     : 20px;
+            line-height: 20px;
+            width      : 100%;
+            cursor     : pointer;
+            white-space: nowrap;
+
+            &:hover {
+                background-color: $background-lighter;
+            }
+        }
+
+        .gltf-checkbox {
+            @extend .gltf-action;
+            &:before {
+                width      : 1em;
+                height     : 1em;
+                line-height: 1em;
+                display    : inline-block;
+                font-family: 'FontAwesome', sans-serif;
+                content    : "\f096";
+                margin-right:10px;
+            }
+
+            &.active {
+                &:before {
+                    width      : 1em;
+                    height     : 1em;
+                    line-height: 1em;
+                    display    : inline-block;
+                    font-family: 'FontAwesome', sans-serif;
+                    content    : "\f14a";
+                    color      : $color-bot;
+                    margin-right:10px;
+                }
+            }
+        }
+
         .gltf-input {
             background-color: $background-lighter;
             border          : none;

+ 165 - 9
inspector/src/tabs/GLTFTab.ts

@@ -1,32 +1,186 @@
 /// <reference path="../../../dist/preview release/gltf2Interface/babylon.glTF2Interface.d.ts"/>
+/// <reference path="../../../dist/preview release/loaders/babylon.glTF2FileLoader.d.ts"/>
 /// <reference path="../../../dist/preview release/serializers/babylon.glTF2Serializer.d.ts"/>
 
+declare function Split(elements: HTMLElement[], options: any): any;
+
 module INSPECTOR {
+    interface ILoaderExtensionSettings {
+        [extensionName: string]: {
+            [settingName: string]: any
+        }
+    };
+
     export class GLTFTab extends Tab {
+        private static _LoaderExtensionSettings: ILoaderExtensionSettings | null = null;
+
+        private _inspector: Inspector;
+        private _actions: HTMLDivElement;
+        private _detailsPanel: DetailPanel | null = null;
+        private _split: any;
+
+        /** @hidden */
+        public static _Initialize(): void {
+            // Must register with OnPluginActivatedObservable as early as possible to
+            // override the default settings for each extension.
+            BABYLON.SceneLoader.OnPluginActivatedObservable.add((loader: BABYLON.GLTFFileLoader) => {
+                if (loader.name === "gltf" && GLTFTab._LoaderExtensionSettings) {
+                    loader.onExtensionLoadedObservable.add(extension => {
+                        const settings = GLTFTab._LoaderExtensionSettings![extension.name];
+                        for (const settingName in settings) {
+                            (extension as any)[settingName] = settings[settingName];
+                        }
+                    });
+                }
+            });
+        }
+
         constructor(tabbar: TabBar, inspector: Inspector) {
             super(tabbar, 'GLTF');
 
+            this._inspector = inspector;
             this._panel = Helpers.CreateDiv('tab-panel') as HTMLDivElement;
-            const actions = Helpers.CreateDiv('gltf-actions', this._panel) as HTMLDivElement;
-            this._addExport(inspector, actions);
+            this._actions = Helpers.CreateDiv('gltf-actions', this._panel) as HTMLDivElement;
+            this._actions.addEventListener('click', event => {
+                this._closeDetailsPanel();
+            });
+
+            this._addImport();
+            this._addExport();
         }
 
         public dispose() {
-            // Nothing to dispose
+            if (this._detailsPanel) {
+                this._detailsPanel.dispose();
+            }
         }
 
-        private _addExport(inspector: Inspector, actions: HTMLDivElement) {
-            const title = Helpers.CreateDiv('gltf-title', actions);
+        private _addImport() {
+            const importActions = Helpers.CreateDiv(null, this._actions) as HTMLDivElement;
+
+            this._getLoaderExtensionOverridesAsync().then(loaderExtensionSettings => {
+                const title = Helpers.CreateDiv('gltf-title', importActions);
+                title.textContent = 'Import';
+
+                const extensionActions = Helpers.CreateDiv('gltf-actions', importActions) as HTMLDivElement;
+
+                const extensionsTitle = Helpers.CreateDiv('gltf-title', extensionActions) as HTMLDivElement;
+                extensionsTitle.textContent = "Extensions";
+
+                for (const extensionName in loaderExtensionSettings) {
+                    const settings = loaderExtensionSettings[extensionName];
+
+                    const extensionAction = Helpers.CreateDiv('gltf-action', extensionActions);
+                    extensionAction.addEventListener('click', event => {
+                        if (this._updateLoaderExtensionDetails(settings)) {
+                            event.stopPropagation();
+                        }
+                    });
+
+                    const checkbox = Helpers.CreateElement('span', 'gltf-checkbox', extensionAction);
+
+                    if (settings.enabled) {
+                        checkbox.classList.add('action', 'active');
+                    }
+
+                    checkbox.addEventListener('click', () => {
+                        checkbox.classList.toggle('active');
+                        settings.enabled = checkbox.classList.contains('active');
+                    });
+
+                    const label = Helpers.CreateElement('span', null, extensionAction);
+                    label.textContent = extensionName;
+                }
+            });
+        }
+
+        private _getLoaderExtensionOverridesAsync(): Promise<ILoaderExtensionSettings> {
+            if (GLTFTab._LoaderExtensionSettings) {
+                return Promise.resolve(GLTFTab._LoaderExtensionSettings);
+            }
+
+            const loaderExtensionSettings: ILoaderExtensionSettings = {};
+
+            const engine = new BABYLON.NullEngine();
+            const scene = new BABYLON.Scene(engine);
+            const loader = new BABYLON.GLTF2.GLTFLoader();
+            loader.onExtensionLoadedObservable.add(extension => {
+                loaderExtensionSettings[extension.name] = {};
+                const settings = loaderExtensionSettings[extension.name];
+                for (const key of Object.keys(extension)) {
+                    if (key !== "name" && key[0] !== '_') {
+                        const value = (extension as any)[key];
+                        if (typeof value !== "object") {
+                            settings[key] = value;
+                        }
+                    }
+                }
+            });
+
+            const data = { json: {}, bin: null };
+            return loader.importMeshAsync([], scene, data, "").then(() => {
+                scene.dispose();
+                engine.dispose();
+
+                return (GLTFTab._LoaderExtensionSettings = loaderExtensionSettings);
+            });
+        }
+
+        private _updateLoaderExtensionDetails(settings: { [settingName: string]: any }): boolean {
+            if (Object.keys(settings).length === 1) {
+                return false;
+            }
+
+            if (!this._detailsPanel) {
+                this._detailsPanel = new DetailPanel();
+                this._panel.appendChild(this._detailsPanel.toHtml());
+
+                this._split = Split([this._actions, this._detailsPanel.toHtml()], {
+                    blockDrag: this._inspector.popupMode,
+                    sizes: [50, 50],
+                    direction: 'vertical'
+                });
+            }
+
+            this._detailsPanel.clean();
+
+            const details = new Array<PropertyLine>();
+            for (const key in settings) {
+                if (key !== "enabled") {
+                    details.push(new PropertyLine(new Property(key, settings)));
+                }
+            }
+            this._detailsPanel.details = details;
+
+            return true;
+        }
+
+        private _closeDetailsPanel(): void {
+            if (this._detailsPanel) {
+                this._detailsPanel.toHtml().remove();
+                this._detailsPanel.dispose();
+                this._detailsPanel = null;
+            }
+
+            if (this._split) {
+                this._split.destroy();
+                delete this._split;
+            }
+        }
+
+        private _addExport() {
+            const exportActions = Helpers.CreateDiv(null, this._actions) as HTMLDivElement;
+
+            const title = Helpers.CreateDiv('gltf-title', exportActions);
             title.textContent = 'Export';
 
-            const name = Helpers.CreateInput('gltf-input', actions);
+            const name = Helpers.CreateInput('gltf-input', exportActions);
             name.placeholder = "File name...";
 
-            const button = Helpers.CreateElement('button', 'gltf-button', actions) as HTMLButtonElement;
+            const button = Helpers.CreateElement('button', 'gltf-button', exportActions) as HTMLButtonElement;
             button.innerText = 'Export GLB';
-
             button.addEventListener('click', () => {
-                BABYLON.GLTF2Export.GLBAsync(inspector.scene, name.value || "scene", {
+                BABYLON.GLTF2Export.GLBAsync(this._inspector.scene, name.value || "scene", {
                     shouldExportTransformNode: transformNode => !GLTFTab._IsSkyBox(transformNode)
                 }).then((glb) => {
                     glb.downloadFiles();
@@ -48,4 +202,6 @@ module INSPECTOR {
             return false;
         }
     }
+
+    GLTFTab._Initialize();
 }

+ 4 - 3
inspector/src/tsconfig.json

@@ -1,12 +1,13 @@
 {
     "compilerOptions": {
         "experimentalDecorators": true,
-        "module": "commonjs", 
+        "module": "commonjs",
         "target": "es5",
         "noImplicitAny": true,
         "noImplicitReturns": true,
         "noImplicitThis": true,
-        "noUnusedLocals": true,    
-        "strictNullChecks": true
+        "noUnusedLocals": true,
+        "strictNullChecks": true,
+        "lib": ["dom", "es2015.promise", "es5"]
     }
 }

+ 17 - 8
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -164,6 +164,11 @@ module BABYLON.GLTF2 {
          */
         public importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<{ meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[], animationGroups: AnimationGroup[] }> {
             return Promise.resolve().then(() => {
+                this._babylonScene = scene;
+                this._rootUrl = rootUrl;
+                this._progressCallback = onProgress;
+                this._loadData(data);
+
                 let nodes: Nullable<Array<_ILoaderNode>> = null;
 
                 if (meshesNames) {
@@ -187,7 +192,7 @@ module BABYLON.GLTF2 {
                     });
                 }
 
-                return this._loadAsync(nodes, scene, data, rootUrl, onProgress).then(() => {
+                return this._loadAsync(nodes).then(() => {
                     return {
                         meshes: this._getMeshes(),
                         particleSystems: [],
@@ -207,17 +212,19 @@ module BABYLON.GLTF2 {
          * @returns a promise which completes when objects have been loaded to the scene
          */
         public loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void> {
-            return this._loadAsync(null, scene, data, rootUrl, onProgress);
-        }
-
-        private _loadAsync(nodes: Nullable<Array<_ILoaderNode>>, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onProgress?: (event: SceneLoaderProgressEvent) => void): Promise<void> {
             return Promise.resolve().then(() => {
                 this._babylonScene = scene;
                 this._rootUrl = rootUrl;
                 this._progressCallback = onProgress;
+                this._loadData(data);
+                return this._loadAsync(null);
+            });
+        }
+
+        private _loadAsync(nodes: Nullable<Array<_ILoaderNode>>): Promise<void> {
+            return Promise.resolve().then(() => {
                 this._state = GLTFLoaderState.LOADING;
 
-                this._loadData(data);
                 this._loadExtensions();
                 this._checkExtensions();
 
@@ -246,7 +253,9 @@ module BABYLON.GLTF2 {
                 });
 
                 resultPromise.then(() => {
-                    this._rootBabylonMesh.setEnabled(true);
+                    if (this._rootBabylonMesh) {
+                        this._rootBabylonMesh.setEnabled(true);
+                    }
 
                     Tools.SetImmediate(() => {
                         if (!this._disposed) {
@@ -375,7 +384,7 @@ module BABYLON.GLTF2 {
             return rootNode;
         }
 
-        private _loadNodesAsync(nodes: _ILoaderNode[], ): Promise<void> {
+        private _loadNodesAsync(nodes: _ILoaderNode[]): Promise<void> {
             const promises = new Array<Promise<void>>();
 
             for (let node of nodes) {

+ 2 - 2
package.json

@@ -9,7 +9,7 @@
     ],
     "name": "babylonjs",
     "description": "Babylon.js is a JavaScript 3D engine based on webgl.",
-    "version": "3.3.0-alpha.1",
+    "version": "3.3.0-alpha.2",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -43,4 +43,4 @@
         "base64-font-loader": "0.0.4",
         "typescript": "^2.8.1"
     }
-}
+}

+ 9 - 2
sandbox/index-local.html

@@ -27,11 +27,11 @@
             <div class="row">
                 <button id="playBtn" class="pause">
                     <img id="playImg" src="Assets/Icon_Play.svg">
-                    <img id="pauseImg" src="Assets/Icon_Pause.svg">    
+                    <img id="pauseImg" src="Assets/Icon_Pause.svg">
                 </button>
                 <input id="slider" type="range" min="0" max="100" value="0" step="any">
             </div>
-        </div>               
+        </div>
         <div class="footerRight">
             <a href="#" id="btnFullscreen" class="hidden"><img src="./Assets/Icon_Fullscreen.svg" alt="Switch the scene to full screen" title="Switch the scene to full screen" /></a> 
             <a href="#" id="btnInspector" class="hidden"><img src="./Assets/Icon_EditModel.svg" alt="Display inspector" title="Display inspector" /></a> 
@@ -44,6 +44,13 @@
     </div>
     <div id="errorZone"></div>
     <script>
+        // prevent drag and drop of file until local scripts are loaded
+        document.ondragover = function (e) {
+            e.dataTransfer.dropEffect = "none";
+            e.dataTransfer.effectAllowed = "none";
+            e.preventDefault();
+        };
+
         BABYLONDEVTOOLS.Loader
             .require('index.js')
             .load(function () {

+ 41 - 52
sandbox/index.js

@@ -52,9 +52,10 @@ if (BABYLON.Engine.isSupported()) {
     var filesInput;
     var currentScene;
     var currentSkybox;
-    var enableDebugLayer = false;
     var currentPluginName;
     var skyboxPath = "Assets/environment.dds";
+    var debugLayerEnabled = false;
+    var debugLayerLastActiveTab = 0;
 
     engine.loadingUIBackgroundColor = "#a9b5bc";
 
@@ -82,40 +83,24 @@ if (BABYLON.Engine.isSupported()) {
     });
 
     var sceneLoaded = function (sceneFile, babylonScene) {
-        function displayDebugLayerAndLogs() {
-            currentScene.debugLayer._displayLogs = true;
-            enableDebugLayer = true;
-            currentScene.debugLayer.show();
-        };
-        function hideDebugLayerAndLogs() {
-            currentScene.debugLayer._displayLogs = false;
-            enableDebugLayer = false;
-            currentScene.debugLayer.hide();
-        };
-    
-            if (enableDebugLayer) {
-            hideDebugLayerAndLogs();
-        }
-
         // Clear dropdown that contains animation names
         dropdownContent.innerHTML = "";
         animationBar.style.display = "none";
         currentGroup = null;
 
-        if(babylonScene.animationGroups.length > 0) {
+        if (babylonScene.animationGroups.length > 0) {
             animationBar.style.display = "flex";
             for (var index = 0; index < babylonScene.animationGroups.length; index++) {
                 var group = babylonScene.animationGroups[index];
-			    createDropdownLink(group,index);
+                createDropdownLink(group, index);
             }
             currentGroup = babylonScene.animationGroups[0];
             currentGroupIndex = 0;
-            document.getElementById( formatId(currentGroup.name+"-"+currentGroupIndex)).click();
+            document.getElementById(formatId(currentGroup.name + "-" + currentGroupIndex)).click();
         }
 
         // Sync the slider with the current frame
         babylonScene.registerBeforeRender(function () {
-            
             if (currentGroup != null && currentGroup.targetedAnimations[0].animation.runtimeAnimations[0] != null) {
                 var currentValue = slider.valueAsNumber;
                 var newValue = currentGroup.targetedAnimations[0].animation.runtimeAnimations[0].currentFrame;
@@ -130,7 +115,7 @@ if (BABYLON.Engine.isSupported()) {
         errorZone.style.display = 'none';
 
         btnFullScreen.classList.remove("hidden");
-        btnInspector.classList.remove("hidden");        
+        btnInspector.classList.remove("hidden");
 
         currentScene = babylonScene;
         document.title = "BabylonJS - " + sceneFile.name;
@@ -183,11 +168,11 @@ if (BABYLON.Engine.isSupported()) {
         if (currentScene.meshes.length === 0 && currentScene.clearColor.r === 1 && currentScene.clearColor.g === 0 && currentScene.clearColor.b === 0) {
             document.getElementById("logo").className = "";
             canvas.style.opacity = 0;
-            displayDebugLayerAndLogs();
+            debugLayerEnabled = true;
         }
         else {
             if (BABYLON.Tools.errorsCount > 0) {
-                displayDebugLayerAndLogs();
+                debugLayerEnabled = true;
             }
             document.getElementById("logo").className = "hidden";
             document.getElementById("droptext").className = "hidden";
@@ -203,6 +188,9 @@ if (BABYLON.Engine.isSupported()) {
             }
         }
 
+        if (debugLayerEnabled) {
+            currentScene.debugLayer.show({ initialTab: debugLayerLastActiveTab });
+        }
     };
 
     var sceneError = function (sceneFile, babylonScene, message) {
@@ -228,7 +216,7 @@ if (BABYLON.Engine.isSupported()) {
             sceneLoaded({ name: fileName }, scene);
             currentScene = scene;
             scene.whenReadyAsync().then(function () {
-                engine.runRenderLoop(function ()  {
+                engine.runRenderLoop(function () {
                     scene.render();
                 });
             });
@@ -248,9 +236,10 @@ if (BABYLON.Engine.isSupported()) {
         }).bind(this);
         filesInput.monitorElementForDragNDrop(canvas);
 
-        window.addEventListener("keydown", function (evt) {
+        window.addEventListener("keydown", function (event) {
             // Press R to reload
-            if (evt.keyCode === 82 && !enableDebugLayer) {
+            if (event.keyCode === 82 && event.target.nodeName !== "INPUT") {
+                debugLayerLastActiveTab = currentScene.debugLayer.getActiveTab();
                 filesInput.reload();
             }
         });
@@ -279,20 +268,21 @@ if (BABYLON.Engine.isSupported()) {
 
     btnInspector.addEventListener('click', function () {
         if (currentScene) {
-            if (!enableDebugLayer) {
-                currentScene.debugLayer.show();
-                enableDebugLayer = true;
-
-            } else {
+            if (currentScene.debugLayer.isVisible()) {
+                debugLayerEnabled = false;
+                debugLayerLastActiveTab = currentScene.debugLayer.getActiveTab();
                 currentScene.debugLayer.hide();
-                enableDebugLayer = false;
+            }
+            else {
+                currentScene.debugLayer.show({ initialTab: debugLayerLastActiveTab });
+                debugLayerEnabled = true;
             }
         }
     }, false);
 
-    window.addEventListener("keydown", function (evt) {
+    window.addEventListener("keydown", function (event) {
         // Press space to toggle footer
-        if (evt.keyCode === 32 && !enableDebugLayer) {
+        if (event.keyCode === 32 && event.target.nodeName !== "INPUT") {
             if (footer.style.display === "none") {
                 footer.style.display = "block";
             }
@@ -323,15 +313,14 @@ function sizeScene() {
     }
 }
 
-
 // animation
 // event on the dropdown
-function formatId(name){
-    return "data-" + name.replace(/\s/g,'');
+function formatId(name) {
+    return "data-" + name.replace(/\s/g, '');
 }
 
 function displayDropdownContent(display) {
-    if(display) {
+    if (display) {
         dropdownContent.style.display = "flex";
         chevronDown.style.display = "inline";
         chevronUp.style.display = "none";
@@ -342,8 +331,8 @@ function displayDropdownContent(display) {
         chevronUp.style.display = "inline";
     }
 }
-dropdownBtn.addEventListener("click", function() {
-    if(dropdownContent.style.display === "flex") {
+dropdownBtn.addEventListener("click", function () {
+    if (dropdownContent.style.display === "flex") {
         displayDropdownContent(false);
     }
     else {
@@ -351,15 +340,15 @@ dropdownBtn.addEventListener("click", function() {
     }
 });
 
-function createDropdownLink(group,index) {
+function createDropdownLink(group, index) {
     var animation = document.createElement("a");
     animation.innerHTML = group.name;
-    animation.setAttribute("id",  formatId(group.name+"-"+index));
-    animation.addEventListener("click", function() {
+    animation.setAttribute("id", formatId(group.name + "-" + index));
+    animation.addEventListener("click", function () {
         // stop the current animation group
         currentGroup.reset();
         currentGroup.stop();
-        document.getElementById( formatId(currentGroup.name+"-"+currentGroupIndex)).classList.remove("active");
+        document.getElementById(formatId(currentGroup.name + "-" + currentGroupIndex)).classList.remove("active");
         playBtn.classList.remove("play");
         playBtn.classList.add("pause");
 
@@ -383,9 +372,9 @@ function createDropdownLink(group,index) {
 }
 
 // event on the play/pause button
-playBtn.addEventListener("click", function() {
+playBtn.addEventListener("click", function () {
     // click on the button to run the animation
-    if( this.classList.contains("play") ) {
+    if (this.classList.contains("play")) {
         this.classList.remove("play");
         this.classList.add("pause");
         var currentFrame = slider.value;
@@ -400,8 +389,8 @@ playBtn.addEventListener("click", function() {
 });
 
 // event on the slider
-slider.addEventListener("input", function() {
-    if( playBtn.classList.contains("play") ) {
+slider.addEventListener("input", function () {
+    if (playBtn.classList.contains("play")) {
         currentGroup.play(true);
         currentGroup.goToFrame(this.value);
         currentGroup.pause();
@@ -411,15 +400,15 @@ slider.addEventListener("input", function() {
 });
 
 var sliderPause = false;
-slider.addEventListener("mousedown", function() {
-    if( playBtn.classList.contains("pause") ) {
+slider.addEventListener("mousedown", function () {
+    if (playBtn.classList.contains("pause")) {
         sliderPause = true;
         playBtn.click();
     }
 });
 
-slider.addEventListener("mouseup", function() {
-    if( sliderPause ) {
+slider.addEventListener("mouseup", function () {
+    if (sliderPause) {
         sliderPause = false;
         playBtn.click();
     }

+ 141 - 164
serializers/src/glTF/2.0/babylon.glTFExporter.ts

@@ -110,6 +110,9 @@ module BABYLON.GLTF2 {
          */
         private animationSampleRate: number;
 
+        /**
+         * Callback which specifies if a transform node should be exported or not
+         */
         private shouldExportTransformNode: ((babylonTransformNode: TransformNode) => boolean);
 
         /**
@@ -591,36 +594,39 @@ module BABYLON.GLTF2 {
          * @param glTFPrefix Text to use when prefixing a glTF file
          * @returns GLTFData with glTF file data
          */
-        public _generateGLTF(glTFPrefix: string): GLTFData {
-            const binaryBuffer = this.generateBinary();
-            const jsonText = this.generateJSON(false, glTFPrefix, true);
-            const bin = new Blob([binaryBuffer], { type: 'application/octet-stream' });
+        public _generateGLTFAsync(glTFPrefix: string): Promise<GLTFData> {
+            return this._generateBinaryAsync().then(binaryBuffer => {
+                const jsonText = this.generateJSON(false, glTFPrefix, true);
+                const bin = new Blob([binaryBuffer], { type: 'application/octet-stream' });
 
-            const glTFFileName = glTFPrefix + '.gltf';
-            const glTFBinFile = glTFPrefix + '.bin';
+                const glTFFileName = glTFPrefix + '.gltf';
+                const glTFBinFile = glTFPrefix + '.bin';
 
-            const container = new GLTFData();
+                const container = new GLTFData();
 
-            container.glTFFiles[glTFFileName] = jsonText;
-            container.glTFFiles[glTFBinFile] = bin;
+                container.glTFFiles[glTFFileName] = jsonText;
+                container.glTFFiles[glTFBinFile] = bin;
 
-            if (this.imageData) {
-                for (let image in this.imageData) {
-                    container.glTFFiles[image] = new Blob([this.imageData[image].data], { type: this.imageData[image].mimeType });
+                if (this.imageData) {
+                    for (let image in this.imageData) {
+                        container.glTFFiles[image] = new Blob([this.imageData[image].data], { type: this.imageData[image].mimeType });
+                    }
                 }
-            }
 
-            return container;
+                return container;
+            });
+
         }
 
         /**
          * Creates a binary buffer for glTF
          * @returns array buffer for binary data
          */
-        private generateBinary(): ArrayBuffer {
+        private _generateBinaryAsync(): Promise<ArrayBuffer> {
             let binaryWriter = new _BinaryWriter(4);
-            this.createScene(this.babylonScene, binaryWriter);
-            return binaryWriter.getArrayBuffer();
+            return this.createSceneAsync(this.babylonScene, binaryWriter).then(() => {
+                return binaryWriter.getArrayBuffer();
+            });
         }
 
         /**
@@ -641,84 +647,85 @@ module BABYLON.GLTF2 {
          * @param glTFPrefix 
          * @returns object with glb filename as key and data as value
          */
-        public _generateGLB(glTFPrefix: string): GLTFData {
-            const binaryBuffer = this.generateBinary();
-            const jsonText = this.generateJSON(true);
-            const glbFileName = glTFPrefix + '.glb';
-            const headerLength = 12;
-            const chunkLengthPrefix = 8;
-            const jsonLength = jsonText.length;
-            let imageByteLength = 0;
-
-            for (let key in this.imageData) {
-                imageByteLength += this.imageData[key].data.byteLength;
-            }
-            const jsonPadding = this._getPadding(jsonLength);
-            const binPadding = this._getPadding(binaryBuffer.byteLength);
-            const imagePadding = this._getPadding(imageByteLength);
-
-            const byteLength = headerLength + (2 * chunkLengthPrefix) + jsonLength + jsonPadding + binaryBuffer.byteLength + binPadding + imageByteLength + imagePadding;
-
-            //header
-            const headerBuffer = new ArrayBuffer(headerLength);
-            const headerBufferView = new DataView(headerBuffer);
-            headerBufferView.setUint32(0, 0x46546C67, true); //glTF
-            headerBufferView.setUint32(4, 2, true); // version
-            headerBufferView.setUint32(8, byteLength, true); // total bytes in file
-
-            //json chunk
-            const jsonChunkBuffer = new ArrayBuffer(chunkLengthPrefix + jsonLength + jsonPadding);
-            const jsonChunkBufferView = new DataView(jsonChunkBuffer);
-            jsonChunkBufferView.setUint32(0, jsonLength + jsonPadding, true);
-            jsonChunkBufferView.setUint32(4, 0x4E4F534A, true);
-
-            //json chunk bytes
-            const jsonData = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix);
-            for (let i = 0; i < jsonLength; ++i) {
-                jsonData[i] = jsonText.charCodeAt(i);
-            }
+        public _generateGLBAsync(glTFPrefix: string): Promise<GLTFData> {
+            return this._generateBinaryAsync().then(binaryBuffer => {
+                const jsonText = this.generateJSON(true);
+                const glbFileName = glTFPrefix + '.glb';
+                const headerLength = 12;
+                const chunkLengthPrefix = 8;
+                const jsonLength = jsonText.length;
+                let imageByteLength = 0;
+
+                for (let key in this.imageData) {
+                    imageByteLength += this.imageData[key].data.byteLength;
+                }
+                const jsonPadding = this._getPadding(jsonLength);
+                const binPadding = this._getPadding(binaryBuffer.byteLength);
+                const imagePadding = this._getPadding(imageByteLength);
+
+                const byteLength = headerLength + (2 * chunkLengthPrefix) + jsonLength + jsonPadding + binaryBuffer.byteLength + binPadding + imageByteLength + imagePadding;
+
+                //header
+                const headerBuffer = new ArrayBuffer(headerLength);
+                const headerBufferView = new DataView(headerBuffer);
+                headerBufferView.setUint32(0, 0x46546C67, true); //glTF
+                headerBufferView.setUint32(4, 2, true); // version
+                headerBufferView.setUint32(8, byteLength, true); // total bytes in file
+
+                //json chunk
+                const jsonChunkBuffer = new ArrayBuffer(chunkLengthPrefix + jsonLength + jsonPadding);
+                const jsonChunkBufferView = new DataView(jsonChunkBuffer);
+                jsonChunkBufferView.setUint32(0, jsonLength + jsonPadding, true);
+                jsonChunkBufferView.setUint32(4, 0x4E4F534A, true);
+
+                //json chunk bytes
+                const jsonData = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix);
+                for (let i = 0; i < jsonLength; ++i) {
+                    jsonData[i] = jsonText.charCodeAt(i);
+                }
 
-            //json padding
-            const jsonPaddingView = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix + jsonLength);
-            for (let i = 0; i < jsonPadding; ++i) {
-                jsonPaddingView[i] = 0x20;
-            }
+                //json padding
+                const jsonPaddingView = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix + jsonLength);
+                for (let i = 0; i < jsonPadding; ++i) {
+                    jsonPaddingView[i] = 0x20;
+                }
 
-            //binary chunk
-            const binaryChunkBuffer = new ArrayBuffer(chunkLengthPrefix);
-            const binaryChunkBufferView = new DataView(binaryChunkBuffer);
-            binaryChunkBufferView.setUint32(0, binaryBuffer.byteLength + imageByteLength + imagePadding, true);
-            binaryChunkBufferView.setUint32(4, 0x004E4942, true);
-
-            // binary padding
-            const binPaddingBuffer = new ArrayBuffer(binPadding);
-            const binPaddingView = new Uint8Array(binPaddingBuffer);
-            for (let i = 0; i < binPadding; ++i) {
-                binPaddingView[i] = 0;
-            }
+                //binary chunk
+                const binaryChunkBuffer = new ArrayBuffer(chunkLengthPrefix);
+                const binaryChunkBufferView = new DataView(binaryChunkBuffer);
+                binaryChunkBufferView.setUint32(0, binaryBuffer.byteLength + imageByteLength + imagePadding, true);
+                binaryChunkBufferView.setUint32(4, 0x004E4942, true);
+
+                // binary padding
+                const binPaddingBuffer = new ArrayBuffer(binPadding);
+                const binPaddingView = new Uint8Array(binPaddingBuffer);
+                for (let i = 0; i < binPadding; ++i) {
+                    binPaddingView[i] = 0;
+                }
 
-            const imagePaddingBuffer = new ArrayBuffer(imagePadding);
-            const imagePaddingView = new Uint8Array(imagePaddingBuffer);
-            for (let i = 0; i < imagePadding; ++i) {
-                imagePaddingView[i] = 0;
-            }
+                const imagePaddingBuffer = new ArrayBuffer(imagePadding);
+                const imagePaddingView = new Uint8Array(imagePaddingBuffer);
+                for (let i = 0; i < imagePadding; ++i) {
+                    imagePaddingView[i] = 0;
+                }
 
-            const glbData = [headerBuffer, jsonChunkBuffer, binaryChunkBuffer, binaryBuffer];
+                const glbData = [headerBuffer, jsonChunkBuffer, binaryChunkBuffer, binaryBuffer];
 
-            // binary data
-            for (let key in this.imageData) {
-                glbData.push(this.imageData[key].data.buffer);
-            }
-            glbData.push(binPaddingBuffer);
+                // binary data
+                for (let key in this.imageData) {
+                    glbData.push(this.imageData[key].data.buffer);
+                }
+                glbData.push(binPaddingBuffer);
 
-            glbData.push(imagePaddingBuffer);
+                glbData.push(imagePaddingBuffer);
 
-            const glbFile = new Blob(glbData, { type: 'application/octet-stream' });
+                const glbFile = new Blob(glbData, { type: 'application/octet-stream' });
 
-            const container = new GLTFData();
-            container.glTFFiles[glbFileName] = glbFile;
+                const container = new GLTFData();
+                container.glTFFiles[glbFileName] = glbFile;
 
-            return container;
+                return container;
+            });
         }
 
         /**
@@ -792,7 +799,7 @@ module BABYLON.GLTF2 {
          * @param babylonMesh The BabylonJS mesh
          */
         private getMeshPrimitiveMode(babylonMesh: AbstractMesh): number {
-            return babylonMesh.material ? babylonMesh.material.fillMode : Material.TriangleFanDrawMode;
+            return babylonMesh.material ? babylonMesh.material.fillMode : Material.TriangleFillMode;
         }
 
         /**
@@ -937,13 +944,35 @@ module BABYLON.GLTF2 {
                 }
 
                 if (bufferMesh.subMeshes) {
-                    uvCoordsPresent = false;
                     // go through all mesh primitives (submeshes)
                     for (const submesh of bufferMesh.subMeshes) {
+                        uvCoordsPresent = false;
+                        let babylonMaterial = submesh.getMaterial();
+
+                        let materialIndex: Nullable<number> = null;
+                        if (babylonMaterial) {
+                            if (babylonMaterial instanceof MultiMaterial) {
+                                babylonMaterial = babylonMaterial.subMaterials[submesh.materialIndex];
+                                if (babylonMaterial) {
+                                    materialIndex = this.babylonScene.materials.indexOf(babylonMaterial);
+                                }
+                            }
+                            else {
+                                materialIndex = this.babylonScene.materials.indexOf(babylonMaterial);
+                            }
+                        }
+
+                        let glTFMaterial: Nullable<IMaterial> = materialIndex != null ? this.materials[materialIndex] : null;
+
                         const meshPrimitive: IMeshPrimitive = { attributes: {} };
 
                         for (const attribute of attributeData) {
                             const attributeKind = attribute.kind;
+                            if (attributeKind === VertexBuffer.UVKind || attributeKind === VertexBuffer.UV2Kind) {
+                                if (glTFMaterial && !_GLTFMaterial._HasTexturesPresent(glTFMaterial)) {
+                                    continue;
+                                }
+                            }
                             let vertexData = bufferMesh.getVerticesData(attributeKind);
                             if (vertexData) {
                                 const vertexBuffer = this.getVertexBufferFromMesh(attributeKind, bufferMesh);
@@ -971,26 +1000,9 @@ module BABYLON.GLTF2 {
                             this.accessors.push(accessor);
                             meshPrimitive.indices = this.accessors.length - 1;
                         }
-                        if (bufferMesh.material) {
-                            let materialIndex: Nullable<number> = null;
-                            if (bufferMesh.material instanceof StandardMaterial || bufferMesh.material instanceof PBRMetallicRoughnessMaterial || bufferMesh.material instanceof PBRMaterial) {
-                                materialIndex = babylonTransformNode.getScene().materials.indexOf(bufferMesh.material);
-                            }
-                            else if (bufferMesh.material instanceof MultiMaterial) {
-                                const babylonMultiMaterial = bufferMesh.material as MultiMaterial;
-                                const material = babylonMultiMaterial.subMaterials[submesh.materialIndex];
-
-                                if (material) {
-                                    materialIndex = babylonTransformNode.getScene().materials.indexOf(material);
-                                }
-                            }
-                            else {
-                                Tools.Warn("Material type " + bufferMesh.material.getClassName() + " for material " + bufferMesh.material.name + " is not yet implemented in glTF serializer.");
-                            }
-
+                        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) {
@@ -1002,7 +1014,7 @@ module BABYLON.GLTF2 {
                                         babylonIndices = bufferMesh.getIndices();
                                     }
                                     if (babylonIndices) {
-                                        this.reorderIndicesBasedOnPrimitiveMode(submesh, primitiveMode, babylonIndices, byteOffset, binaryWriter);    
+                                        this.reorderIndicesBasedOnPrimitiveMode(submesh, primitiveMode, babylonIndices, byteOffset, binaryWriter);
                                     }
                                     else {
                                         for (let attribute of attributeData) {
@@ -1018,49 +1030,13 @@ module BABYLON.GLTF2 {
                                     }
                                 }
 
-                                if (uvCoordsPresent) {
-                                    if (!_GLTFMaterial._HasTexturesPresent(this.materials[materialIndex])) {
-                                        delete meshPrimitive.attributes.TEXCOORD_0;
-                                        delete meshPrimitive.attributes.TEXCOORD_1;
-                                    }
-                                    meshPrimitive.material = materialIndex;
-                                }
-                                else {
-                                    if (_GLTFMaterial._HasTexturesPresent(this.materials[materialIndex])) {
-                                        const newMat = _GLTFMaterial._StripTexturesFromMaterial(this.materials[materialIndex]);
-                                        this.materials.push(newMat);
-                                        meshPrimitive.material = this.materials.length - 1;
-                                    }
-                                    else {
-                                        meshPrimitive.material = materialIndex;
-                                    }
-                                }
-                            }
-                        }
-                        else {
-                            const sideOrientation = this.babylonScene.defaultMaterial.sideOrientation;
-                            let byteOffset = indexBufferViewIndex != null ? this.bufferViews[indexBufferViewIndex].byteOffset : null;
-                            if (byteOffset == null) { byteOffset = 0; }
-                            let babylonIndices: Nullable<IndicesArray> = null;
-                            if (indexBufferViewIndex != null) {
-                                babylonIndices = bufferMesh.getIndices();
-                            }
-                            if (babylonIndices) {
-                                if (sideOrientation === Material.ClockWiseSideOrientation) {
-                                    this.reorderIndicesBasedOnPrimitiveMode(submesh, primitiveMode, babylonIndices, byteOffset, binaryWriter);
-                                }
-                            }
-                            else {
-                                for (let attribute of attributeData) {
-                                    let vertexData = bufferMesh.getVerticesData(attribute.kind);
-                                    if (vertexData) {
-                                        let byteOffset = this.bufferViews[vertexAttributeBufferViews[attribute.kind]].byteOffset;
-                                        if (!byteOffset) {
-                                            byteOffset = 0
-                                        }
-                                        this.reorderVertexAttributeDataBasedOnPrimitiveMode(submesh, primitiveMode, sideOrientation, attribute.kind, vertexData, byteOffset, binaryWriter);
-                                    }
+                                if (!uvCoordsPresent && _GLTFMaterial._HasTexturesPresent(this.materials[materialIndex])) {
+                                    const newMat = _GLTFMaterial._StripTexturesFromMaterial(this.materials[materialIndex]);
+                                    this.materials.push(newMat);
+                                    materialIndex = this.materials.length - 1;
                                 }
+
+                                meshPrimitive.material = materialIndex;
                             }
                         }
                         mesh.primitives.push(meshPrimitive);
@@ -1075,15 +1051,14 @@ module BABYLON.GLTF2 {
          * @param babylonScene Babylon scene to get the mesh data from
          * @param binaryWriter Buffer to write binary data to
          */
-        private createScene(babylonScene: Scene, binaryWriter: _BinaryWriter): void {
-            if (this.setNodeTransformation.length) {
-                const scene: IScene = { nodes: [] };
-                let glTFNodeIndex: number;
-                let glTFNode: INode;
-                let directDescendents: Node[];
-                const nodes = [...babylonScene.transformNodes, ...babylonScene.meshes];
-
-                _GLTFMaterial._ConvertMaterialsToGLTF(babylonScene.materials, ImageMimeType.PNG, this.images, this.textures, this.samplers, this.materials, this.imageData, true);
+        private createSceneAsync(babylonScene: Scene, binaryWriter: _BinaryWriter): Promise<void> {
+            const scene: IScene = { nodes: [] };
+            let glTFNodeIndex: number;
+            let glTFNode: INode;
+            let directDescendents: Node[];
+            const nodes = [...babylonScene.transformNodes, ...babylonScene.meshes];
+
+            return _GLTFMaterial._ConvertMaterialsToGLTFAsync(babylonScene.materials, ImageMimeType.PNG, this.images, this.textures, this.samplers, this.materials, this.imageData, true).then(() => {
                 this.nodeMap = this.createNodeMapAndAnimations(babylonScene, nodes, this.shouldExportTransformNode, binaryWriter);
 
                 this.totalByteLength = binaryWriter.getByteOffset();
@@ -1100,11 +1075,11 @@ module BABYLON.GLTF2 {
                             }
                             else {
                                 if (this.convertToRightHandedSystem) {
-                                        if (glTFNode.translation) {
-                                            glTFNode.translation[2] *= -1;
-                                            glTFNode.translation[0] *= -1;
-                                        }
-                                        glTFNode.rotation = glTFNode.rotation ? Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(glTFNode.rotation)).asArray() : (Quaternion.FromArray([0, 1, 0, 0])).asArray();
+                                    if (glTFNode.translation) {
+                                        glTFNode.translation[2] *= -1;
+                                        glTFNode.translation[0] *= -1;
+                                    }
+                                    glTFNode.rotation = glTFNode.rotation ? Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(glTFNode.rotation)).asArray() : (Quaternion.FromArray([0, 1, 0, 0])).asArray();
                                 }
 
                                 scene.nodes.push(glTFNodeIndex);
@@ -1125,12 +1100,14 @@ module BABYLON.GLTF2 {
                 if (scene.nodes.length) {
                     this.scenes.push(scene);
                 }
-            }
+            });
         }
 
         /**
          * Creates a mapping of Node unique id to node index and handles animations
          * @param babylonScene Babylon Scene
+         * @param nodes Babylon transform nodes
+         * @param shouldExportTransformNode Callback specifying if a transform node should be exported
          * @param binaryWriter Buffer to write binary data to
          * @returns Node mapping of unique id to index
          */

+ 341 - 236
serializers/src/glTF/2.0/babylon.glTFMaterial.ts

@@ -89,21 +89,24 @@ module BABYLON.GLTF2 {
          * @param imageData mapping of texture names to base64 textures
          * @param hasTextureCoords specifies if texture coordinates are present on the material
          */
-        public static _ConvertMaterialsToGLTF(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+        public static _ConvertMaterialsToGLTFAsync(babylonMaterials: Material[], mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+            let promises: Promise<void>[] = [];
             for (let babylonMaterial of babylonMaterials) {
                 if (babylonMaterial instanceof StandardMaterial) {
-                    _GLTFMaterial._ConvertStandardMaterial(babylonMaterial, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
+                    promises.push(_GLTFMaterial._ConvertStandardMaterialAsync(babylonMaterial, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords));
                 }
                 else if (babylonMaterial instanceof PBRMetallicRoughnessMaterial) {
-                    _GLTFMaterial._ConvertPBRMetallicRoughnessMaterial(babylonMaterial, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
+                    promises.push(_GLTFMaterial._ConvertPBRMetallicRoughnessMaterialAsync(babylonMaterial, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords));
                 }
                 else if (babylonMaterial instanceof PBRMaterial) {
-                    _GLTFMaterial._ConvertPBRMaterial(babylonMaterial, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
+                    promises.push(_GLTFMaterial._ConvertPBRMaterialAsync(babylonMaterial, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords));
                 }
                 else {
-                    Tools.Error("Unsupported material type: " + babylonMaterial.name);
+                    Tools.Warn(`Unsupported material type: ${babylonMaterial.name}`);
                 }
             }
+
+            return Promise.all(promises).then(() => { /* do nothing */ });
         }
 
         /**
@@ -238,7 +241,7 @@ module BABYLON.GLTF2 {
         public static _GetAlphaMode(babylonMaterial: Material): Nullable<MaterialAlphaMode> {
             if (babylonMaterial instanceof StandardMaterial) {
                 const babylonStandardMaterial = babylonMaterial as StandardMaterial;
-                if ((babylonStandardMaterial.alpha != 1.0) ||
+                if ((babylonStandardMaterial.alpha !== 1.0) ||
                     (babylonStandardMaterial.diffuseTexture != null && babylonStandardMaterial.diffuseTexture.hasAlpha) ||
                     (babylonStandardMaterial.opacityTexture != null)) {
                     return MaterialAlphaMode.BLEND;
@@ -309,7 +312,10 @@ module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        public static _ConvertStandardMaterial(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+        public static _ConvertStandardMaterialAsync(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean): Promise<void> {
+            const alphaMode = this._GetAlphaMode(babylonStandardMaterial);
+            let useAlpha = alphaMode !== MaterialAlphaMode.OPAQUE ? true : false;
+            let promises = [];
             const glTFPbrMetallicRoughness = _GLTFMaterial._ConvertToGLTFPBRMetallicRoughness(babylonStandardMaterial);
 
             const glTFMaterial: IMaterial = { name: babylonStandardMaterial.name };
@@ -321,41 +327,48 @@ module BABYLON.GLTF2 {
             }
             if (hasTextureCoords) {
                 if (babylonStandardMaterial.diffuseTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.diffuseTexture, mimeType, images, textures, samplers, imageData);
-                    if (glTFTexture != null) {
-                        glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
-                    }
+                    let promise = _GLTFMaterial._ExportTextureAsync(babylonStandardMaterial.diffuseTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFTexture => {
+                        if (glTFTexture) {
+                            glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
+                        }
+                    });
+                    promises.push(promise);
                 }
                 if (babylonStandardMaterial.bumpTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.bumpTexture, mimeType, images, textures, samplers, imageData)
-                    if (glTFTexture) {
-                        glTFMaterial.normalTexture = glTFTexture;
-                        if (babylonStandardMaterial.bumpTexture.level !== 1) {
-                            glTFMaterial.normalTexture.scale = babylonStandardMaterial.bumpTexture.level;
+                    let promise = _GLTFMaterial._ExportTextureAsync(babylonStandardMaterial.bumpTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFTexture => {
+                        if (glTFTexture) {
+                            glTFMaterial.normalTexture = glTFTexture;
+                            if (babylonStandardMaterial.bumpTexture != null && babylonStandardMaterial.bumpTexture.level !== 1) {
+                                glTFMaterial.normalTexture.scale = babylonStandardMaterial.bumpTexture.level;
+                            }
                         }
-                    }
+                    });
+                    promises.push(promise);
                 }
                 if (babylonStandardMaterial.emissiveTexture) {
-                    const glTFEmissiveTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.emissiveTexture, mimeType, images, textures, samplers, imageData)
-                    if (glTFEmissiveTexture) {
-                        glTFMaterial.emissiveTexture = glTFEmissiveTexture;
-                    }
-                    glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0];
+                    let promise = _GLTFMaterial._ExportTextureAsync(babylonStandardMaterial.emissiveTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFEmissiveTexture => {
+                        if (glTFEmissiveTexture) {
+                            glTFMaterial.emissiveTexture = glTFEmissiveTexture;
+                        }
+                        glTFMaterial.emissiveFactor = [1.0, 1.0, 1.0];
+                    });
+                    promises.push(promise);
                 }
                 if (babylonStandardMaterial.ambientTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonStandardMaterial.ambientTexture, mimeType, images, textures, samplers, imageData);
-                    if (glTFTexture) {
-                        const occlusionTexture: IMaterialOcclusionTextureInfo = {
-                            index: glTFTexture.index
-                        };
-                        glTFMaterial.occlusionTexture = occlusionTexture;
-                        occlusionTexture.strength = 1.0;
-                    }
+                    let promise = _GLTFMaterial._ExportTextureAsync(babylonStandardMaterial.ambientTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFTexture => {
+                        if (glTFTexture) {
+                            const occlusionTexture: IMaterialOcclusionTextureInfo = {
+                                index: glTFTexture.index
+                            };
+                            glTFMaterial.occlusionTexture = occlusionTexture;
+                            occlusionTexture.strength = 1.0;
+                        }
+                    });
+                    promises.push(promise);
                 }
             }
 
             if (babylonStandardMaterial.alpha < 1.0 || babylonStandardMaterial.opacityTexture) {
-
                 if (babylonStandardMaterial.alphaMode === Engine.ALPHA_COMBINE) {
                     glTFMaterial.alphaMode = GLTF2.MaterialAlphaMode.BLEND;
                 }
@@ -370,6 +383,41 @@ module BABYLON.GLTF2 {
             glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
 
             materials.push(glTFMaterial);
+
+            return Promise.all(promises).then(() => { /* do nothing */ });
+        }
+
+        /**
+         * 
+         * @param texture Texture with alpha to overwrite to one
+         * @param useAlpha Specifies if alpha should be preserved or not
+         * @returns Promise with texture
+         */
+        public static _SetAlphaToOneAsync(texture: BaseTexture, useAlpha: boolean): Promise<Texture> {
+            return new Promise((resolve, reject) => {
+                if (useAlpha) {
+                    resolve(texture as Texture);
+                }
+                else {
+                    const scene = texture.getScene();
+                    if (scene) {
+                        const proceduralTexture = new ProceduralTexture('texture', texture.getSize(), 'setAlphaToOne', scene);
+                        
+                        if (proceduralTexture) {
+                            proceduralTexture.setTexture('textureSampler', texture as Texture);
+                            proceduralTexture.onLoadObservable.add(() => { resolve(proceduralTexture) });
+                        }
+                        else {
+                            reject(`Cannot create procedural texture for ${texture.name}!`);
+                        }
+                    }
+                    else {
+                        reject(`Scene not available for texture ${texture.name}`);
+                    }
+                }
+
+            })
+
         }
 
         /**
@@ -382,7 +430,8 @@ module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        public static _ConvertPBRMetallicRoughnessMaterial(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+        public static _ConvertPBRMetallicRoughnessMaterialAsync(babylonPBRMetalRoughMaterial: PBRMetallicRoughnessMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean): Promise<void> {
+            let promises: Promise<void>[] = [];
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
 
             if (babylonPBRMetalRoughMaterial.baseColor) {
@@ -407,58 +456,73 @@ module BABYLON.GLTF2 {
             if (babylonPBRMetalRoughMaterial.doubleSided) {
                 glTFMaterial.doubleSided = babylonPBRMetalRoughMaterial.doubleSided;
             }
+            let alphaMode: Nullable<MaterialAlphaMode> = null;
+            let useAlpha = false;
+            if (babylonPBRMetalRoughMaterial.transparencyMode != null) {
+                alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMetalRoughMaterial);
+                if (alphaMode) {
+                    if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
+                        glTFMaterial.alphaMode = alphaMode;
+                        if (alphaMode === MaterialAlphaMode.MASK) {
+                            glTFMaterial.alphaCutoff = babylonPBRMetalRoughMaterial.alphaCutOff;
+                        }
+                    }
+                }
+            }
+            if (alphaMode !== MaterialAlphaMode.OPAQUE) {
+                useAlpha = true;
+            }
 
             if (hasTextureCoords) {
                 if (babylonPBRMetalRoughMaterial.baseTexture != null) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.baseTexture, mimeType, images, textures, samplers, imageData);
-                    if (glTFTexture != null) {
-                        glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
-                    }
+                    let promise = _GLTFMaterial._ExportTextureAsync(babylonPBRMetalRoughMaterial.baseTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFTexture => {
+                        if (glTFTexture) {
+                            glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
+                        }
+                    });
+                    promises.push(promise);
                 }
                 if (babylonPBRMetalRoughMaterial.normalTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.normalTexture, mimeType, images, textures, samplers, imageData);
-                    if (glTFTexture) {
-                        glTFMaterial.normalTexture = glTFTexture;
-                        if (babylonPBRMetalRoughMaterial.normalTexture.level !== 1) {
-                            glTFMaterial.normalTexture.scale = babylonPBRMetalRoughMaterial.normalTexture.level;
+                    let promise = _GLTFMaterial._ExportTextureAsync(babylonPBRMetalRoughMaterial.normalTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFTexture => {
+                        if (glTFTexture) {
+                            glTFMaterial.normalTexture = glTFTexture;
+                            if (babylonPBRMetalRoughMaterial.normalTexture.level !== 1) {
+                                glTFMaterial.normalTexture.scale = babylonPBRMetalRoughMaterial.normalTexture.level;
+                            }
                         }
-                    }
+                    });
+                    promises.push(promise);
                 }
                 if (babylonPBRMetalRoughMaterial.occlusionTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.occlusionTexture, mimeType, images, textures, samplers, imageData);
-                    if (glTFTexture) {
-                        glTFMaterial.occlusionTexture = glTFTexture;
-                        if (babylonPBRMetalRoughMaterial.occlusionStrength != null) {
-                            glTFMaterial.occlusionTexture.strength = babylonPBRMetalRoughMaterial.occlusionStrength;
+                    let promise = _GLTFMaterial._ExportTextureAsync(babylonPBRMetalRoughMaterial.occlusionTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFTexture => {
+                        if (glTFTexture) {
+                            glTFMaterial.occlusionTexture = glTFTexture;
+                            if (babylonPBRMetalRoughMaterial.occlusionStrength != null) {
+                                glTFMaterial.occlusionTexture.strength = babylonPBRMetalRoughMaterial.occlusionStrength;
+                            }
                         }
-                    }
+                    });
+                    promises.push(promise);
                 }
                 if (babylonPBRMetalRoughMaterial.emissiveTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMetalRoughMaterial.emissiveTexture, mimeType, images, textures, samplers, imageData);
-                    if (glTFTexture != null) {
-                        glTFMaterial.emissiveTexture = glTFTexture;
-                    }
+                    let promise = _GLTFMaterial._ExportTextureAsync(babylonPBRMetalRoughMaterial.emissiveTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFTexture => {
+                        if (glTFTexture) {
+                            glTFMaterial.emissiveTexture = glTFTexture;
+                        }
+                    });
+                    promises.push(promise);
                 }
             }
 
             if (this.FuzzyEquals(babylonPBRMetalRoughMaterial.emissiveColor, Color3.Black(), this._epsilon)) {
                 glTFMaterial.emissiveFactor = babylonPBRMetalRoughMaterial.emissiveColor.asArray();
             }
-            if (babylonPBRMetalRoughMaterial.transparencyMode != null) {
-                const alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMetalRoughMaterial);
-                if (alphaMode) {
-                    if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
-                        glTFMaterial.alphaMode = alphaMode;
-                        if (alphaMode === MaterialAlphaMode.MASK) {
-                            glTFMaterial.alphaCutoff = babylonPBRMetalRoughMaterial.alphaCutOff;
-                        }
-                    }
-                }
-            }
 
             glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
 
             materials.push(glTFMaterial);
+
+            return Promise.all(promises).then(() => { /* do nothing */ });
         }
 
         /**
@@ -474,7 +538,7 @@ module BABYLON.GLTF2 {
             imageCanvas.width = width;
             imageCanvas.height = height;
             imageCanvas.id = "WriteCanvas";
- 
+
             const ctx = imageCanvas.getContext('2d') as CanvasRenderingContext2D;
 
             const imgData = ctx.createImageData(width, height);
@@ -558,135 +622,139 @@ module BABYLON.GLTF2 {
          */
         private static _ConvertSpecularGlossinessTexturesToMetallicRoughness(diffuseTexture: BaseTexture, specularGlossinessTexture: BaseTexture, factors: _IPBRSpecularGlossiness, mimeType: ImageMimeType): Nullable<_IPBRMetallicRoughness> {
             if (!(diffuseTexture || specularGlossinessTexture)) {
+                Tools.Warn('_ConvertSpecularGlosinessTexturesToMetallicRoughness: diffuse and specular glossiness textures are not defined!');
                 return null;
             }
 
-            const scene = diffuseTexture ? diffuseTexture.getScene() : specularGlossinessTexture.getScene();
-            if (!scene) {
-                Tools.Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Scene from textures is missing!");
-                return null;
-            }
+            const scene: Nullable<Scene> = diffuseTexture ? diffuseTexture.getScene() : specularGlossinessTexture ? specularGlossinessTexture.getScene() : null;
+            if (scene) {
+                const resizedTextures = this._ResizeTexturesToSameDimensions(diffuseTexture, specularGlossinessTexture, scene);
 
-            const resizedTextures = this._ResizeTexturesToSameDimensions(diffuseTexture, specularGlossinessTexture, scene);
+                let diffuseSize = resizedTextures.texture1.getSize();
 
-            let diffuseSize = resizedTextures.texture1.getSize();
+                let diffuseBuffer: Uint8Array;
+                let specularGlossinessBuffer: Uint8Array;
 
-            let diffuseBuffer: Uint8Array;
-            let specularGlossinessBuffer: Uint8Array;
+                const width = diffuseSize.width;
+                const height = diffuseSize.height;
 
-            const width = diffuseSize.width;
-            const height = diffuseSize.height;
+                let pixels = (resizedTextures.texture1.readPixels());
+                if (pixels instanceof Uint8Array) {
+                    diffuseBuffer = (resizedTextures.texture1.readPixels()) as Uint8Array;
 
-            let pixels = (resizedTextures.texture1.readPixels());
-            if (pixels instanceof Uint8Array) {
-                diffuseBuffer = (resizedTextures.texture1.readPixels()) as Uint8Array;
-            }
-            else {
-                Tools.Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture1.name);
-                return null;
-            }
-            pixels = resizedTextures.texture2.readPixels();
+                    pixels = resizedTextures.texture2.readPixels();
 
-            if (pixels instanceof Uint8Array) {
-                specularGlossinessBuffer = (resizedTextures.texture2.readPixels()) as Uint8Array;
-            }
-            else {
-                Tools.Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture2.name);
-                return null;
-            }
+                    if (pixels instanceof Uint8Array) {
+                        specularGlossinessBuffer = (resizedTextures.texture2.readPixels()) as Uint8Array;
 
-            const byteLength = specularGlossinessBuffer.byteLength;
-
-            const metallicRoughnessBuffer = new Uint8Array(byteLength);
-            const baseColorBuffer = new Uint8Array(byteLength);
-
-            const strideSize = 4;
-            const maxBaseColor = Color3.Black();
-            let maxMetallic = 0;
-            let maxRoughness = 0;
-
-            for (let h = 0; h < height; ++h) {
-                for (let w = 0; w < width; ++w) {
-                    const offset = (width * h + w) * strideSize;
-
-                    const diffuseColor = Color3.FromInts(diffuseBuffer[offset], diffuseBuffer[offset + 1], diffuseBuffer[offset + 2]).toLinearSpace().multiply(factors.diffuseColor);
-                    const specularColor = Color3.FromInts(specularGlossinessBuffer[offset], specularGlossinessBuffer[offset + 1], specularGlossinessBuffer[offset + 2]).toLinearSpace().multiply(factors.specularColor);
-                    const glossiness = (specularGlossinessBuffer[offset + 3] / 255) * factors.glossiness;
-
-                    const specularGlossiness: _IPBRSpecularGlossiness = {
-                        diffuseColor: diffuseColor,
-                        specularColor: specularColor,
-                        glossiness: glossiness
-                    };
-
-                    const metallicRoughness = this._ConvertSpecularGlossinessToMetallicRoughness(specularGlossiness);
-                    maxBaseColor.r = Math.max(maxBaseColor.r, metallicRoughness.baseColor.r);
-                    maxBaseColor.g = Math.max(maxBaseColor.g, metallicRoughness.baseColor.g);
-                    maxBaseColor.b = Math.max(maxBaseColor.b, metallicRoughness.baseColor.b);
-                    maxMetallic = Math.max(maxMetallic, metallicRoughness.metallic);
-                    maxRoughness = Math.max(maxRoughness, metallicRoughness.roughness);
-
-                    baseColorBuffer[offset] = metallicRoughness.baseColor.r * 255;
-                    baseColorBuffer[offset + 1] = metallicRoughness.baseColor.g * 255;
-                    baseColorBuffer[offset + 2] = metallicRoughness.baseColor.b * 255;
-                    baseColorBuffer[offset + 3] = resizedTextures.texture1.hasAlpha ? diffuseBuffer[offset + 3] : 255;
-
-                    metallicRoughnessBuffer[offset] = 0;
-                    metallicRoughnessBuffer[offset + 1] = metallicRoughness.roughness * 255;
-                    metallicRoughnessBuffer[offset + 2] = metallicRoughness.metallic * 255;
-                    metallicRoughnessBuffer[offset + 3] = 255;
-                }
-            }
+                        const byteLength = specularGlossinessBuffer.byteLength;
 
-            // Retrieves the metallic roughness factors from the maximum texture values.
-            const metallicRoughnessFactors: _IPBRMetallicRoughness = {
-                baseColor: maxBaseColor,
-                metallic: maxMetallic,
-                roughness: maxRoughness
-            };
+                        const metallicRoughnessBuffer = new Uint8Array(byteLength);
+                        const baseColorBuffer = new Uint8Array(byteLength);
 
-            let writeOutMetallicRoughnessTexture = false;
-            let writeOutBaseColorTexture = false;
+                        const strideSize = 4;
+                        const maxBaseColor = Color3.Black();
+                        let maxMetallic = 0;
+                        let maxRoughness = 0;
 
-            for (let h = 0; h < height; ++h) {
-                for (let w = 0; w < width; ++w) {
-                    const destinationOffset = (width * h + w) * strideSize;
+                        for (let h = 0; h < height; ++h) {
+                            for (let w = 0; w < width; ++w) {
+                                const offset = (width * h + w) * strideSize;
 
-                    baseColorBuffer[destinationOffset] /= metallicRoughnessFactors.baseColor.r > this._epsilon ? metallicRoughnessFactors.baseColor.r : 1;
-                    baseColorBuffer[destinationOffset + 1] /= metallicRoughnessFactors.baseColor.g > this._epsilon ? metallicRoughnessFactors.baseColor.g : 1;
-                    baseColorBuffer[destinationOffset + 2] /= metallicRoughnessFactors.baseColor.b > this._epsilon ? metallicRoughnessFactors.baseColor.b : 1;
+                                const diffuseColor = Color3.FromInts(diffuseBuffer[offset], diffuseBuffer[offset + 1], diffuseBuffer[offset + 2]).toLinearSpace().multiply(factors.diffuseColor);
+                                const specularColor = Color3.FromInts(specularGlossinessBuffer[offset], specularGlossinessBuffer[offset + 1], specularGlossinessBuffer[offset + 2]).toLinearSpace().multiply(factors.specularColor);
+                                const glossiness = (specularGlossinessBuffer[offset + 3] / 255) * factors.glossiness;
 
-                    const linearBaseColorPixel = Color3.FromInts(baseColorBuffer[destinationOffset], baseColorBuffer[destinationOffset + 1], baseColorBuffer[destinationOffset + 2]);
-                    const sRGBBaseColorPixel = linearBaseColorPixel.toGammaSpace();
-                    baseColorBuffer[destinationOffset] = sRGBBaseColorPixel.r * 255;
-                    baseColorBuffer[destinationOffset + 1] = sRGBBaseColorPixel.g * 255;
-                    baseColorBuffer[destinationOffset + 2] = sRGBBaseColorPixel.b * 255;
+                                const specularGlossiness: _IPBRSpecularGlossiness = {
+                                    diffuseColor: diffuseColor,
+                                    specularColor: specularColor,
+                                    glossiness: glossiness
+                                };
 
-                    if (!this.FuzzyEquals(sRGBBaseColorPixel, Color3.White(), this._epsilon)) {
-                        writeOutBaseColorTexture = true;
-                    }
+                                const metallicRoughness = this._ConvertSpecularGlossinessToMetallicRoughness(specularGlossiness);
+                                maxBaseColor.r = Math.max(maxBaseColor.r, metallicRoughness.baseColor.r);
+                                maxBaseColor.g = Math.max(maxBaseColor.g, metallicRoughness.baseColor.g);
+                                maxBaseColor.b = Math.max(maxBaseColor.b, metallicRoughness.baseColor.b);
+                                maxMetallic = Math.max(maxMetallic, metallicRoughness.metallic);
+                                maxRoughness = Math.max(maxRoughness, metallicRoughness.roughness);
+
+                                baseColorBuffer[offset] = metallicRoughness.baseColor.r * 255;
+                                baseColorBuffer[offset + 1] = metallicRoughness.baseColor.g * 255;
+                                baseColorBuffer[offset + 2] = metallicRoughness.baseColor.b * 255;
+                                baseColorBuffer[offset + 3] = resizedTextures.texture1.hasAlpha ? diffuseBuffer[offset + 3] : 255;
+
+                                metallicRoughnessBuffer[offset] = 0;
+                                metallicRoughnessBuffer[offset + 1] = metallicRoughness.roughness * 255;
+                                metallicRoughnessBuffer[offset + 2] = metallicRoughness.metallic * 255;
+                                metallicRoughnessBuffer[offset + 3] = 255;
+
+                            }
+                        }
+
+                        // Retrieves the metallic roughness factors from the maximum texture values.
+                        const metallicRoughnessFactors: _IPBRMetallicRoughness = {
+                            baseColor: maxBaseColor,
+                            metallic: maxMetallic,
+                            roughness: maxRoughness
+                        };
+
+                        let writeOutMetallicRoughnessTexture = false;
+                        let writeOutBaseColorTexture = false;
+
+                        for (let h = 0; h < height; ++h) {
+                            for (let w = 0; w < width; ++w) {
+                                const destinationOffset = (width * h + w) * strideSize;
+
+                                baseColorBuffer[destinationOffset] /= metallicRoughnessFactors.baseColor.r > this._epsilon ? metallicRoughnessFactors.baseColor.r : 1;
+                                baseColorBuffer[destinationOffset + 1] /= metallicRoughnessFactors.baseColor.g > this._epsilon ? metallicRoughnessFactors.baseColor.g : 1;
+                                baseColorBuffer[destinationOffset + 2] /= metallicRoughnessFactors.baseColor.b > this._epsilon ? metallicRoughnessFactors.baseColor.b : 1;
 
-                    metallicRoughnessBuffer[destinationOffset + 1] /= metallicRoughnessFactors.roughness > this._epsilon ? metallicRoughnessFactors.roughness : 1;
-                    metallicRoughnessBuffer[destinationOffset + 2] /= metallicRoughnessFactors.metallic > this._epsilon ? metallicRoughnessFactors.metallic : 1;
+                                const linearBaseColorPixel = Color3.FromInts(baseColorBuffer[destinationOffset], baseColorBuffer[destinationOffset + 1], baseColorBuffer[destinationOffset + 2]);
+                                const sRGBBaseColorPixel = linearBaseColorPixel.toGammaSpace();
+                                baseColorBuffer[destinationOffset] = sRGBBaseColorPixel.r * 255;
+                                baseColorBuffer[destinationOffset + 1] = sRGBBaseColorPixel.g * 255;
+                                baseColorBuffer[destinationOffset + 2] = sRGBBaseColorPixel.b * 255;
 
-                    const metallicRoughnessPixel = Color3.FromInts(255, metallicRoughnessBuffer[destinationOffset + 1], metallicRoughnessBuffer[destinationOffset + 2]);
+                                if (!this.FuzzyEquals(sRGBBaseColorPixel, Color3.White(), this._epsilon)) {
+                                    writeOutBaseColorTexture = true;
+                                }
 
-                    if (!this.FuzzyEquals(metallicRoughnessPixel, Color3.White(), this._epsilon)) {
-                        writeOutMetallicRoughnessTexture = true;
+                                metallicRoughnessBuffer[destinationOffset + 1] /= metallicRoughnessFactors.roughness > this._epsilon ? metallicRoughnessFactors.roughness : 1;
+                                metallicRoughnessBuffer[destinationOffset + 2] /= metallicRoughnessFactors.metallic > this._epsilon ? metallicRoughnessFactors.metallic : 1;
+
+                                const metallicRoughnessPixel = Color3.FromInts(255, metallicRoughnessBuffer[destinationOffset + 1], metallicRoughnessBuffer[destinationOffset + 2]);
+
+                                if (!this.FuzzyEquals(metallicRoughnessPixel, Color3.White(), this._epsilon)) {
+                                    writeOutMetallicRoughnessTexture = true;
+                                }
+                            }
+                        }
+
+                        if (writeOutMetallicRoughnessTexture) {
+                            const metallicRoughnessBase64 = this._CreateBase64FromCanvas(metallicRoughnessBuffer, width, height, mimeType);
+                            metallicRoughnessFactors.metallicRoughnessTextureBase64 = metallicRoughnessBase64;
+                        }
+                        if (writeOutBaseColorTexture) {
+                            const baseColorBase64 = this._CreateBase64FromCanvas(baseColorBuffer, width, height, mimeType);
+                            metallicRoughnessFactors.baseColorTextureBase64 = baseColorBase64;
+
+                        }
+
+                        return metallicRoughnessFactors;
+                    }
+                    else {
+                        Tools.Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture2.name);
                     }
                 }
+                else {
+                    Tools.Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Pixel array buffer type not supported for texture: " + resizedTextures.texture1.name);
+                }
             }
-
-            if (writeOutMetallicRoughnessTexture) {
-                const metallicRoughnessBase64 = this._CreateBase64FromCanvas(metallicRoughnessBuffer, width, height, mimeType);
-                metallicRoughnessFactors.metallicRoughnessTextureBase64 = metallicRoughnessBase64;
-            }
-            if (writeOutBaseColorTexture) {
-                const baseColorBase64 = this._CreateBase64FromCanvas(baseColorBuffer, width, height, mimeType);
-                metallicRoughnessFactors.baseColorTextureBase64 = baseColorBase64;
+            else {
+                Tools.Error("_ConvertSpecularGlossinessTexturesToMetallicRoughness: Scene from textures is missing!");
             }
 
-            return metallicRoughnessFactors;
+            return null;
         }
 
         /**
@@ -748,8 +816,11 @@ module BABYLON.GLTF2 {
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          * @returns glTF PBR Metallic Roughness factors
          */
-        private static _ConvertMetalRoughFactorsToMetallicRoughness(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean): _IPBRMetallicRoughness {
-            const metallicRoughness = {
+        private static _ConvertMetalRoughFactorsToMetallicRoughnessAsync(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean): Promise<_IPBRMetallicRoughness> {
+            const alphaMode = this._GetAlphaMode(babylonPBRMaterial);
+            const useAlpha = alphaMode !== MaterialAlphaMode.OPAQUE ? true : false;
+            const promises = [];
+            const metallicRoughness: _IPBRMetallicRoughness = {
                 baseColor: babylonPBRMaterial.albedoColor,
                 metallic: babylonPBRMaterial.metallic,
                 roughness: babylonPBRMaterial.roughness
@@ -757,19 +828,26 @@ module BABYLON.GLTF2 {
 
             if (hasTextureCoords) {
                 if (babylonPBRMaterial.albedoTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.albedoTexture, mimeType, images, textures, samplers, imageData);
-                    if (glTFTexture) {
-                        glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
-                    }
+                    let promise = _GLTFMaterial._ExportTextureAsync(babylonPBRMaterial.albedoTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFTexture => {
+                        if (glTFTexture) {
+                            glTFPbrMetallicRoughness.baseColorTexture = glTFTexture;
+                        }
+                    });
+                    promises.push(promise);
+
                 }
                 if (babylonPBRMaterial.metallicTexture) {
-                    const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.metallicTexture, mimeType, images, textures, samplers, imageData);
-                    if (glTFTexture != null) {
-                        glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFTexture;
-                    }
+                    let promise = _GLTFMaterial._ExportTextureAsync(babylonPBRMaterial.metallicTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFTexture => {
+                        if (glTFTexture) {
+                            glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFTexture;
+                        }
+                    });
+                    promises.push(promise);
                 }
             }
-            return metallicRoughness;
+            return Promise.all(promises).then(() => {
+                return metallicRoughness;
+            });
         }
 
         private static _GetGLTFTextureSampler(texture: BaseTexture): ISampler {
@@ -897,33 +975,31 @@ module BABYLON.GLTF2 {
                 samplerIndex = samplers.length - 1;
             }
             if (babylonPBRMaterial.reflectivityTexture && !babylonPBRMaterial.useMicroSurfaceFromReflectivityMapAlpha) {
-                Tools.Error("_ConvertPBRMaterial: Glossiness values not included in the reflectivity texture currently not supported");
+                Tools.Error("_ConvertPBRMaterial: Glossiness values not included in the reflectivity texture are currently not supported");
                 return null;
             }
 
-            let metallicRoughnessFactors = this._ConvertSpecularGlossinessTexturesToMetallicRoughness(babylonPBRMaterial.albedoTexture, babylonPBRMaterial.reflectivityTexture, specGloss, mimeType);
-
-
-            if (!metallicRoughnessFactors) {
-                metallicRoughnessFactors = this._ConvertSpecularGlossinessToMetallicRoughness(specGloss);
-            }
-            else {
+            const metallicRoughnessFactors = this._ConvertSpecularGlossinessTexturesToMetallicRoughness(babylonPBRMaterial.albedoTexture, babylonPBRMaterial.reflectivityTexture, specGloss, mimeType);
+            if (metallicRoughnessFactors) {
                 if (hasTextureCoords) {
                     if (metallicRoughnessFactors.baseColorTextureBase64) {
-                        const glTFBaseColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughnessFactors.baseColorTextureBase64, "bjsBaseColorTexture_" + (textures.length) + ".png", mimeType, images, textures, babylonPBRMaterial.albedoTexture.coordinatesIndex, samplerIndex, imageData);
+                        const glTFBaseColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughnessFactors.baseColorTextureBase64, "bjsBaseColorTexture_" + (textures.length) + ".png", mimeType, images, textures, babylonPBRMaterial.albedoTexture ? babylonPBRMaterial.albedoTexture.coordinatesIndex : null, samplerIndex, imageData);
                         if (glTFBaseColorTexture != null) {
                             glTFPbrMetallicRoughness.baseColorTexture = glTFBaseColorTexture;
                         }
                     }
                     if (metallicRoughnessFactors.metallicRoughnessTextureBase64) {
-                        const glTFMRColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughnessFactors.metallicRoughnessTextureBase64, "bjsMetallicRoughnessTexture_" + (textures.length) + ".png", mimeType, images, textures, babylonPBRMaterial.reflectivityTexture.coordinatesIndex, samplerIndex, imageData);
+                        const glTFMRColorTexture = _GLTFMaterial._GetTextureInfoFromBase64(metallicRoughnessFactors.metallicRoughnessTextureBase64, "bjsMetallicRoughnessTexture_" + (textures.length) + ".png", mimeType, images, textures, babylonPBRMaterial.reflectivityTexture ? babylonPBRMaterial.reflectivityTexture.coordinatesIndex : null, samplerIndex, imageData);
                         if (glTFMRColorTexture != null) {
                             glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFMRColorTexture;
                         }
                     }
+
+                    return metallicRoughnessFactors;
                 }
             }
-            return metallicRoughnessFactors
+            return this._ConvertSpecularGlossinessToMetallicRoughness(specGloss);
+
         }
 
         /**
@@ -936,21 +1012,50 @@ module BABYLON.GLTF2 {
          * @param imageData map of image file name to data
          * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied
          */
-        public static _ConvertPBRMaterial(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean) {
+        public static _ConvertPBRMaterialAsync(babylonPBRMaterial: PBRMaterial, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean): Promise<void> {
             const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {};
-            let metallicRoughness: Nullable<_IPBRMetallicRoughness>;
+            //  let metallicRoughness: Nullable<_IPBRMetallicRoughness>;
             const glTFMaterial: IMaterial = {
                 name: babylonPBRMaterial.name
             };
             const useMetallicRoughness = babylonPBRMaterial.isMetallicWorkflow();
 
             if (useMetallicRoughness) {
-                metallicRoughness = this._ConvertMetalRoughFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
+                if (babylonPBRMaterial.albedoColor) {
+                    glTFPbrMetallicRoughness.baseColorFactor = [
+                        babylonPBRMaterial.albedoColor.r,
+                        babylonPBRMaterial.albedoColor.g,
+                        babylonPBRMaterial.albedoColor.b,
+                        babylonPBRMaterial.alpha
+                    ]
+                }
+                return this._ConvertMetalRoughFactorsToMetallicRoughnessAsync(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords).then(metallicRoughness => {
+                    return _GLTFMaterial.SetMetallicRoughnessPbrMaterial(metallicRoughness, babylonPBRMaterial, glTFMaterial, glTFPbrMetallicRoughness, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
+                });
             }
             else {
-                metallicRoughness = this._ConvertSpecGlossFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
+                const metallicRoughness = this._ConvertSpecGlossFactorsToMetallicRoughness(babylonPBRMaterial, mimeType, images, textures, samplers, glTFPbrMetallicRoughness, imageData, hasTextureCoords);
+                return _GLTFMaterial.SetMetallicRoughnessPbrMaterial(metallicRoughness, babylonPBRMaterial, glTFMaterial, glTFPbrMetallicRoughness, mimeType, images, textures, samplers, materials, imageData, hasTextureCoords);
             }
+        }
+
+        private static SetMetallicRoughnessPbrMaterial(metallicRoughness: Nullable<_IPBRMetallicRoughness>, babylonPBRMaterial: PBRMaterial, glTFMaterial: IMaterial, glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], materials: IMaterial[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, hasTextureCoords: boolean): Promise<void> {
+            let promises = [];
             if (metallicRoughness) {
+                let alphaMode: Nullable<MaterialAlphaMode> = null;
+                let useAlpha = false;
+                if (babylonPBRMaterial.transparencyMode != null) {
+                    alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMaterial);
+                    if (alphaMode) {
+                        if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
+                            useAlpha = true;
+                            glTFMaterial.alphaMode = alphaMode;
+                            if (alphaMode === MaterialAlphaMode.MASK) {
+                                glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
+                            }
+                        }
+                    }
+                }
                 if (!(this.FuzzyEquals(metallicRoughness.baseColor, Color3.White(), this._epsilon) && babylonPBRMaterial.alpha >= this._epsilon)) {
                     glTFPbrMetallicRoughness.baseColorFactor = [
                         metallicRoughness.baseColor.r,
@@ -973,60 +1078,60 @@ module BABYLON.GLTF2 {
                     }
                     glTFMaterial.doubleSided = true;
                 }
+
                 if (hasTextureCoords) {
                     if (babylonPBRMaterial.bumpTexture) {
-                        const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.bumpTexture, mimeType, images, textures, samplers, imageData);
-                        if (glTFTexture) {
-                            glTFMaterial.normalTexture = glTFTexture;
-                            if (babylonPBRMaterial.bumpTexture.level !== 1) {
-                                glTFMaterial.normalTexture.scale = babylonPBRMaterial.bumpTexture.level;
+                        let promise = _GLTFMaterial._ExportTextureAsync(babylonPBRMaterial.bumpTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFTexture => {
+                            if (glTFTexture) {
+                                glTFMaterial.normalTexture = glTFTexture;
+                                if (babylonPBRMaterial.bumpTexture.level !== 1) {
+                                    glTFMaterial.normalTexture.scale = babylonPBRMaterial.bumpTexture.level;
+                                }
                             }
                         }
+                        );
+                        promises.push(promise);
+
 
                     }
                     if (babylonPBRMaterial.ambientTexture) {
-                        const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.ambientTexture, mimeType, images, textures, samplers, imageData);
-                        if (glTFTexture) {
-                            let occlusionTexture: IMaterialOcclusionTextureInfo = {
-                                index: glTFTexture.index
-                            };
+                        let promise = _GLTFMaterial._ExportTextureAsync(babylonPBRMaterial.ambientTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFTexture => {
+                            if (glTFTexture) {
+                                let occlusionTexture: IMaterialOcclusionTextureInfo = {
+                                    index: glTFTexture.index
+                                };
 
-                            glTFMaterial.occlusionTexture = occlusionTexture;
+                                glTFMaterial.occlusionTexture = occlusionTexture;
 
-                            if (babylonPBRMaterial.ambientTextureStrength) {
-                                occlusionTexture.strength = babylonPBRMaterial.ambientTextureStrength;
+                                if (babylonPBRMaterial.ambientTextureStrength) {
+                                    occlusionTexture.strength = babylonPBRMaterial.ambientTextureStrength;
+                                }
                             }
-                        }
+                        });
+                        promises.push(promise);
+
                     }
                     if (babylonPBRMaterial.emissiveTexture) {
-                        const glTFTexture = _GLTFMaterial._ExportTexture(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, samplers, imageData);
-                        if (glTFTexture != null) {
-                            glTFMaterial.emissiveTexture = glTFTexture;
-                        }
+                        let promise = _GLTFMaterial._ExportTextureAsync(babylonPBRMaterial.emissiveTexture, mimeType, images, textures, samplers, imageData, useAlpha).then(glTFTexture => {
+                            if (glTFTexture) {
+                                glTFMaterial.emissiveTexture = glTFTexture;
+                            }
+                        });
+                        promises.push(promise);
                     }
                 }
                 if (!this.FuzzyEquals(babylonPBRMaterial.emissiveColor, Color3.Black(), this._epsilon)) {
                     glTFMaterial.emissiveFactor = babylonPBRMaterial.emissiveColor.asArray();
                 }
-                if (babylonPBRMaterial.transparencyMode != null) {
-                    const alphaMode = _GLTFMaterial._GetAlphaMode(babylonPBRMaterial);
-                    if (alphaMode) {
-                        if (alphaMode !== MaterialAlphaMode.OPAQUE) { //glTF defaults to opaque
-                            glTFMaterial.alphaMode = alphaMode;
-                            if (alphaMode === MaterialAlphaMode.MASK) {
-                                glTFMaterial.alphaCutoff = babylonPBRMaterial.alphaCutOff;
-                            }
-                        }
-                    }
-                }
 
                 glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness;
                 materials.push(glTFMaterial);
             }
+            return Promise.all(promises).then(result => { /* do nothing */ });
         }
 
         private static GetPixelsFromTexture(babylonTexture: Texture): Uint8Array | Float32Array {
-            let pixels = babylonTexture.textureType === Engine.TEXTURETYPE_UNSIGNED_INT ? babylonTexture.readPixels() as Uint8Array : babylonTexture.readPixels() as Float32Array;
+            const pixels = babylonTexture.textureType === Engine.TEXTURETYPE_UNSIGNED_INT ? babylonTexture.readPixels() as Uint8Array : babylonTexture.readPixels() as Float32Array;
             return pixels;
         }
 
@@ -1040,7 +1145,7 @@ module BABYLON.GLTF2 {
          * @param imageData map of image file name and data
          * @return glTF texture info, or null if the texture format is not supported
          */
-        private static _ExportTexture(babylonTexture: BaseTexture, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
+        private static _ExportTextureAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], samplers: ISampler[], imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }, useAlpha: boolean): Promise<Nullable<ITextureInfo>> {
             const sampler = _GLTFMaterial._GetGLTFTextureSampler(babylonTexture);
             let samplerIndex: Nullable<number> = null;
 
@@ -1081,17 +1186,17 @@ module BABYLON.GLTF2 {
                 extension = ".png";
             }
             else {
-                Tools.Error("Unsupported mime type " + mimeType);
-                return null;
+                return Promise.reject("Unsupported mime type " + mimeType);
             }
             textureName = baseFile + extension;
 
-
-            const pixels = _GLTFMaterial.GetPixelsFromTexture(babylonTexture as Texture);
-            const size = babylonTexture.getSize();
-            const base64Data = this._CreateBase64FromCanvas(pixels, size.width, size.height, mimeType);
-
-            return this._GetTextureInfoFromBase64(base64Data, textureName, mimeType, images, textures, babylonTexture.coordinatesIndex, samplerIndex, imageData);
+            return this._SetAlphaToOneAsync(babylonTexture, useAlpha).then((texture) => {
+                const pixels = _GLTFMaterial.GetPixelsFromTexture(texture);
+                const size = babylonTexture.getSize();
+                const base64Data = this._CreateBase64FromCanvas(pixels, size.width, size.height, mimeType);
+                const textureInfo = this._GetTextureInfoFromBase64(base64Data, textureName, mimeType, images, textures, babylonTexture.coordinatesIndex, samplerIndex, imageData);
+                return textureInfo;
+            });
         }
 
         /**
@@ -1104,7 +1209,7 @@ module BABYLON.GLTF2 {
          * @param imageData map of image data
          * @returns glTF texture info, or null if the texture format is not supported
          */
-        private static _GetTextureInfoFromBase64(base64Texture: string, textureName: string, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], texCoordIndex: number, samplerIndex: Nullable<number>, imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
+        private static _GetTextureInfoFromBase64(base64Texture: string, textureName: string, mimeType: ImageMimeType, images: IImage[], textures: ITexture[], texCoordIndex: Nullable<number>, samplerIndex: Nullable<number>, imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } }): Nullable<ITextureInfo> {
             let textureInfo: Nullable<ITextureInfo> = null;
 
             const glTFTexture: ITexture = {
@@ -1149,7 +1254,7 @@ module BABYLON.GLTF2 {
                 textureInfo = {
                     index: textures.length - 1
                 }
-                if (texCoordIndex) {
+                if (texCoordIndex != null) {
                     textureInfo.texCoord = texCoordIndex;
                 }
             }

+ 8 - 31
serializers/src/glTF/2.0/babylon.glTFSerializer.ts

@@ -22,20 +22,6 @@ module BABYLON {
      */
     export class GLTF2Export {
         /**
-         * Exports the geometry of the scene to .gltf file format synchronously
-         * @param scene Babylon scene with scene hierarchy information
-         * @param filePrefix File prefix to use when generating the glTF file
-         * @param options Exporter options
-         * @returns Returns an object with a .gltf file and associates texture names
-         * as keys and their data and paths as values
-         */
-        private static GLTF(scene: Scene, filePrefix: string, options?: IExportOptions): GLTFData {
-            const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");
-            const gltfGenerator = new GLTF2._Exporter(scene, options);
-            return gltfGenerator._generateGLTF(glTFPrefix);
-        }
-
-        /**
          * Exports the geometry of the scene to .gltf file format asynchronously
          * @param scene Babylon scene with scene hierarchy information
          * @param filePrefix File prefix to use when generating the glTF file
@@ -44,25 +30,14 @@ module BABYLON {
          * as keys and their data and paths as values
          */
         public static GLTFAsync(scene: Scene, filePrefix: string, options?: IExportOptions): Promise<GLTFData> {
-            return Promise.resolve(scene.whenReadyAsync()).then(() => {
-                return GLTF2Export.GLTF(scene, filePrefix, options);
+            return scene.whenReadyAsync().then(() => {
+                const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");
+                const gltfGenerator = new GLTF2._Exporter(scene, options);
+                return gltfGenerator._generateGLTFAsync(glTFPrefix);
             });
         }
 
         /**
-         * Exports the geometry of the scene to .glb file format synchronously
-         * @param scene Babylon scene with scene hierarchy information
-         * @param filePrefix File prefix to use when generating glb file
-         * @param options Exporter options
-         * @returns Returns an object with a .glb filename as key and data as value
-         */
-        private static GLB(scene: Scene, filePrefix: string, options?: IExportOptions): GLTFData {
-            const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");
-            const gltfGenerator = new GLTF2._Exporter(scene, options);
-            return gltfGenerator._generateGLB(glTFPrefix);
-        }
-
-        /**
          * Exports the geometry of the scene to .glb file format asychronously
          * @param scene Babylon scene with scene hierarchy information
          * @param filePrefix File prefix to use when generating glb file
@@ -70,8 +45,10 @@ module BABYLON {
          * @returns Returns an object with a .glb filename as key and data as value
          */
         public static GLBAsync(scene: Scene, filePrefix: string, options?: IExportOptions): Promise<GLTFData> {
-            return Promise.resolve(scene.whenReadyAsync()).then(() => {
-                return GLTF2Export.GLB(scene, filePrefix, options);
+            return scene.whenReadyAsync().then(() => {
+                const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, "");
+                const gltfGenerator = new GLTF2._Exporter(scene, options);
+                return gltfGenerator._generateGLBAsync(glTFPrefix);
             });
         }
     }

+ 10 - 0
serializers/src/glTF/2.0/shaders/setAlphaToOne.fragment.fx

@@ -0,0 +1,10 @@
+precision highp float;
+
+uniform sampler2D textureSampler;
+
+varying vec2 vUV;
+
+void main(void) {
+    vec4 color = texture2D(textureSampler, vUV);
+    gl_FragColor = vec4(color.rgb, 1.0);
+}

+ 1 - 3
src/Animations/babylon.animatable.ts

@@ -195,7 +195,7 @@
             this._paused = false;
         }
 
-        private _raiseOnAnimationEnd() {            
+        private _raiseOnAnimationEnd() {
             if (this.onAnimationEnd) {
                 this.onAnimationEnd();
             }
@@ -299,9 +299,7 @@
                 for (index = 0; index < runtimeAnimations.length; index++) {
                     runtimeAnimations[index].dispose();
                 }
-            }
 
-            if (!running) {
                 this._raiseOnAnimationEnd();
                 this.onAnimationEnd = null
                 this.onAnimationEndObservable.clear();

+ 28 - 4
src/Animations/babylon.animationGroup.ts

@@ -23,12 +23,17 @@ module BABYLON {
         public onAnimationEndObservable = new Observable<TargetedAnimation>();
 
         /**
+         * This observable will notify when all animations have ended.
+         */
+        public onAnimationGroupEndObservable = new Observable<AnimationGroup>();
+
+        /**
          * Gets the first frame
          */
         public get from(): number {
             return this._from;
         }
-        
+
         /**
          * Gets the last frame
          */
@@ -171,9 +176,12 @@ module BABYLON {
             }
 
             for (const targetedAnimation of this._targetedAnimations) {
-                this._animatables.push(this._scene.beginDirectAnimation(targetedAnimation.target, [targetedAnimation.animation], from !== undefined ? from : this._from, to !== undefined ? to : this._to, loop, speedRatio, () => {
+                let animatable = this._scene.beginDirectAnimation(targetedAnimation.target, [targetedAnimation.animation], from !== undefined ? from : this._from, to !== undefined ? to : this._to, loop, speedRatio);
+                animatable.onAnimationEnd = () => {
                     this.onAnimationEndObservable.notifyObservers(targetedAnimation);
-                }));
+                    this._checkAnimationGroupEnded(animatable);
+                }
+                this._animatables.push(animatable);
             }
 
             this._speedRatio = speedRatio;
@@ -205,7 +213,8 @@ module BABYLON {
          * @param loop defines if animations must loop
          */
         public play(loop?: boolean): AnimationGroup {
-            if (this.isStarted) {
+            // only if all animatables are ready and exist
+            if (this.isStarted && this._animatables.length === this._targetedAnimations.length) {
                 if (loop !== undefined) {
                     for (var index = 0; index < this._animatables.length; index++) {
                         let animatable = this._animatables[index];
@@ -214,6 +223,7 @@ module BABYLON {
                 }
                 this.restart();
             } else {
+                this.stop();
                 this.start(loop, this._speedRatio);
             }
 
@@ -331,5 +341,19 @@ module BABYLON {
                 this._scene.animationGroups.splice(index, 1);
             }
         }
+
+        private _checkAnimationGroupEnded(animatable: Animatable) {
+            // animatable should be taken out of the array
+            let idx = this._animatables.indexOf(animatable);
+            if (idx > -1) {
+                this._animatables.splice(idx, 1);
+            }
+
+            // all animatables were removed? animation group ended!
+            if (this._animatables.length === 0) {
+                this._isStarted = false;
+                this.onAnimationGroupEndObservable.notifyObservers(this);
+            }
+        }
     }
 }

+ 40 - 2
src/Behaviors/babylon.behavior.ts

@@ -1,9 +1,47 @@
 module BABYLON {
-    export interface Behavior<T extends Node> {
+    /**
+     * Interface used to define a behavior
+     */
+    export interface Behavior<T> {
+        /** gets or sets behavior's name */
         name: string;
 
+        /**
+         * Function called when the behavior needs to be initialized (after attaching it to a target)
+         */
         init(): void
-        attach(node: T): void;
+        /**
+         * Called when the behavior is attached to a target
+         * @param target defines the target where the behavior is attached to
+         */
+        attach(target: T): void;
+        /**
+         * Called when the behavior is detached from its target
+         */
         detach(): void;
     }
+
+    /**
+     * Interface implemented by classes supporting behaviors
+     */
+    export interface IBehaviorAware<T> {
+        /**
+         * Attach a behavior
+         * @param behavior defines the behavior to attach
+         * @returns the current host
+         */
+        addBehavior(behavior: Behavior<T>): T
+        /**
+         * Remove a behavior from the current object
+         * @param behavior defines the behavior to detach
+         * @returns the current host
+         */
+        removeBehavior(behavior: Behavior<T>): T;
+        /**
+         * Gets a behavior using its name to search
+         * @param name defines the name to search
+         * @returns the behavior or null if not found
+         */
+        getBehaviorByName(name: string): Nullable<Behavior<T>>;
+    }
 }

+ 7 - 0
src/Debug/babylon.debugLayer.ts

@@ -84,5 +84,12 @@ module BABYLON {
             }
         }
 
+        /**
+         * Gets the active tab
+         * @return the index of the active tab or -1 if the inspector is hidden
+         */
+        public getActiveTab(): number {
+            return this._inspector ? this._inspector.getActiveTabIndex() : -1;
+        }
     }
 }

+ 1 - 1
src/Engine/babylon.engine.ts

@@ -726,7 +726,7 @@
          * Returns the current version of the framework
          */
         public static get Version(): string {
-            return "3.3.0-alpha.1";
+            return "3.3.0-alpha.2";
         }
 
         // Updatable statics so stick with vars here

+ 3 - 2
src/Materials/Textures/babylon.dynamicTexture.ts

@@ -111,9 +111,10 @@
         /**
          * Updates the texture
          * @param invertY defines the direction for the Y axis (default is true - y increases downwards)
+         * @param premulAlpha defines if alpha is stored as premultiplied (default is false)
          */
-        public update(invertY?: boolean): void {
-            this._engine.updateDynamicTexture(this._texture, this._canvas, invertY === undefined ? true : invertY, undefined, this._format || undefined);
+        public update(invertY?: boolean, premulAlpha = false): void {
+            this._engine.updateDynamicTexture(this._texture, this._canvas, invertY === undefined ? true : invertY, premulAlpha, this._format || undefined);
         }
 
         /**

+ 8 - 0
src/Mesh/babylon.abstractMesh.ts

@@ -777,6 +777,7 @@
             }
             return this;
         }
+
         /**
          * Enables the edge rendering mode on the mesh.  
          * This mode makes the mesh edges visible
@@ -792,6 +793,13 @@
         }
 
         /**
+         * Gets the edgesRenderer associated with the mesh
+         */
+        public get edgesRenderer(): Nullable<EdgesRenderer> {
+            return this._edgesRenderer;
+        }
+
+        /**
          * Returns true if the mesh is blocked. Implemented by child classes
          */
         public get isBlocked(): boolean {

+ 3 - 0
src/Rendering/babylon.edgesRenderer.ts

@@ -23,6 +23,9 @@
         private _buffers: { [key: string]: Nullable<VertexBuffer> } = {};
         private _checkVerticesInsteadOfIndices = false;
 
+        /** Gets or sets a boolean indicating if the edgesRenderer is active */
+        public isEnabled = true;
+
         // Beware when you use this class with complex objects as the adjacencies computation can be really long
         constructor(source: AbstractMesh, epsilon = 0.95, checkVerticesInsteadOfIndices = false) {
             this._source = source;

+ 1 - 1
src/Rendering/babylon.renderingGroup.ts

@@ -341,7 +341,7 @@
                 this._opaqueSubMeshes.push(subMesh); // Opaque
             }
 
-            if (mesh._edgesRenderer !== null && mesh._edgesRenderer !== undefined) {
+            if (mesh._edgesRenderer !== null && mesh._edgesRenderer !== undefined && mesh._edgesRenderer.isEnabled) {
                 this._edgesRenderers.push(mesh._edgesRenderer);
             }
         }

+ 4 - 1
src/Tools/babylon.filesInput.ts

@@ -217,7 +217,6 @@
                         Tools.ClearLogCache();
                     }
                     this._engine.stopRenderLoop();
-                    this._currentScene.dispose();
                 }
 
                 SceneLoader.LoadAsync("file:", this._sceneFileToLoad, this._engine, progress => {
@@ -225,6 +224,10 @@
                         this._progressCallback(progress);
                     }
                 }).then(scene => {
+                    if (this._currentScene) {
+                        this._currentScene.dispose();
+                    }
+
                     this._currentScene = scene;
 
                     if (this._sceneLoadedCallback) {

+ 2 - 6
src/babylon.node.ts

@@ -3,7 +3,7 @@
     /**
      * Node is the basic class for all scene objects (Mesh, Light Camera).
      */
-    export class Node {
+    export class Node implements IBehaviorAware<Node> {
         /**
          * Gets or sets the name of the node
          */
@@ -198,12 +198,8 @@
             behavior.init();
             if (this._scene.isLoading) {
                 // We defer the attach when the scene will be loaded
-                var observer = this._scene.onDataLoadedObservable.add(() => {
+                this._scene.onDataLoadedObservable.addOnce(() => {
                     behavior.attach(this);
-                    setTimeout(() => {
-                        // Need to use a timeout to avoid removing an observer while iterating the list of observers
-                        this._scene.onDataLoadedObservable.remove(observer);
-                    }, 0);
                 });
             } else {
                 behavior.attach(this);

+ 217 - 244
tests/unit/babylon/serializers/babylon.glTFSerializer.tests.ts

@@ -12,6 +12,8 @@ describe('Babylon glTF Serializer', () => {
         (BABYLONDEVTOOLS).Loader
             .useDist()
             .load(function () {
+                // Force apply promise polyfill for consistent behavior between PhantomJS, IE11, and other browsers.
+                BABYLON.PromisePolyfill.Apply(true);
                 done();
             });
     });
@@ -78,63 +80,53 @@ describe('Babylon glTF Serializer', () => {
             BABYLON.GLTF2._GLTFMaterial._SolveMetallic(0.0, 1.0, 1.0).should.be.approximately(1, 1e-6);
         });
 
-        it('should serialize empty Babylon scene to glTF with only asset property', (done) => {
-            mocha.timeout(10000);
-
+        it('should serialize empty Babylon scene to glTF with only asset property', () => {
             const scene = new BABYLON.Scene(subject);
-            scene.executeWhenReady(function () {
-                const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-                const glTFData = glTFExporter._generateGLTF('test');
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
                 const jsonString = glTFData.glTFFiles['test.gltf'] as string;
                 const jsonData = JSON.parse(jsonString);
 
                 Object.keys(jsonData).length.should.be.equal(1);
                 jsonData.asset.version.should.be.equal("2.0");
                 jsonData.asset.generator.should.be.equal("BabylonJS");
-
-                done();
             });
         });
 
-        it('should serialize sphere geometry in scene to glTF', (done) => {
-            mocha.timeout(10000);
+        it('should serialize sphere geometry in scene to glTF', () => {
             const scene = new BABYLON.Scene(subject);
             BABYLON.Mesh.CreateSphere('sphere', 16, 2, scene);
 
-            scene.executeWhenReady(function () {
-                const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-                const glTFData = glTFExporter._generateGLTF('test');
-                const jsonString = glTFData.glTFFiles['test.gltf'] as string;
-                const jsonData = JSON.parse(jsonString);
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test')
+                .then(glTFData => {
+                    const jsonString = glTFData.glTFFiles['test.gltf'] as string;
+                    const jsonData = JSON.parse(jsonString);
 
-                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials
-                Object.keys(jsonData).length.should.be.equal(9);
+                    // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials
+                    Object.keys(jsonData).length.should.be.equal(9);
 
-                // positions, normals, texture coords, indices
-                jsonData.accessors.length.should.be.equal(4);
+                    // positions, normals, indices
+                    jsonData.accessors.length.should.be.equal(3);
 
-                // generator, version
-                Object.keys(jsonData.asset).length.should.be.equal(2);
+                    // generator, version
+                    Object.keys(jsonData.asset).length.should.be.equal(2);
 
-                jsonData.buffers.length.should.be.equal(1);
+                    jsonData.buffers.length.should.be.equal(1);
 
-                // positions, normals, texture coords, indices
-                jsonData.bufferViews.length.should.be.equal(4);
+                    // positions, normals, texture coords, indices
+                    jsonData.bufferViews.length.should.be.equal(4);
 
-                jsonData.meshes.length.should.be.equal(1);
+                    jsonData.meshes.length.should.be.equal(1);
 
-                jsonData.nodes.length.should.be.equal(1);
+                    jsonData.nodes.length.should.be.equal(1);
 
-                jsonData.scenes.length.should.be.equal(1);
-
-                jsonData.scene.should.be.equal(0);
+                    jsonData.scenes.length.should.be.equal(1);
 
-                done();
-            });
+                    jsonData.scene.should.be.equal(0);
+                });
         });
 
-        it('should serialize alpha mode and cutoff', (done) => {
-            mocha.timeout(10000);
+        it('should serialize alpha mode and cutoff', () => {
             const scene = new BABYLON.Scene(subject);
 
             const plane = BABYLON.Mesh.CreatePlane('plane', 120, scene);
@@ -145,9 +137,8 @@ describe('Babylon glTF Serializer', () => {
 
             plane.material = babylonPBRMetalRoughMaterial;
 
-            scene.executeWhenReady(function () {
-                const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-                const glTFData = glTFExporter._generateGLTF('test');
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
                 const jsonString = glTFData.glTFFiles['test.gltf'] as string;
                 const jsonData = JSON.parse(jsonString);
 
@@ -159,243 +150,228 @@ describe('Babylon glTF Serializer', () => {
                 jsonData.materials[0].alphaMode.should.be.equal('MASK');
 
                 jsonData.materials[0].alphaCutoff.should.be.equal(alphaCutoff);
-
-                done();
             });
         });
-        it('should serialize single component translation animation to glTF', (done) => {
-            mocha.timeout(10000);
+        it('should serialize single component translation animation to glTF', () => {
             const scene = new BABYLON.Scene(subject);
             const box = BABYLON.Mesh.CreateBox('box', 1, scene);
             let keys: BABYLON.IAnimationKey[] = [];
             keys.push({
-            frame: 0,
-            value: 1
+                frame: 0,
+                value: 1
             });
             keys.push({
-            frame: 20,
-            value: 0.2
+                frame: 20,
+                value: 0.2
             });
             keys.push({
-            frame: 40,
-            value: 1
+                frame: 40,
+                value: 1
             });
             let animationBoxT = new BABYLON.Animation('boxAnimation_translation', 'position.y', 30, BABYLON.Animation.ANIMATIONTYPE_FLOAT, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
             animationBoxT.setKeys(keys);
             box.animations.push(animationBoxT);
-            scene.executeWhenReady(function () {
-            const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-            const glTFData = glTFExporter._generateGLTF('test');
-            const jsonString = glTFData.glTFFiles['test.gltf'] as string;
-            const jsonData = JSON.parse(jsonString);
-            jsonData.animations.length.should.be.equal(1);
-            const animation = jsonData.animations[0];
-            animation.channels.length.should.be.equal(1);
-            animation.channels[0].sampler.should.be.equal(0);
-            animation.channels[0].target.node.should.be.equal(0);
-            animation.channels[0].target.path.should.be.equal('translation');
-            jsonData.animations[0].samplers.length.should.be.equal(1);
-            // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
-            Object.keys(jsonData).length.should.be.equal(10);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.accessors.length.should.be.equal(6);
-            // generator, version
-            Object.keys(jsonData.asset).length.should.be.equal(2);
-            jsonData.buffers.length.should.be.equal(1);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.bufferViews.length.should.be.equal(6);
-            jsonData.meshes.length.should.be.equal(1);
-            jsonData.nodes.length.should.be.equal(1);
-            jsonData.scenes.length.should.be.equal(1);
-            jsonData.scene.should.be.equal(0);
-            done();
-            });
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
+                const jsonString = glTFData.glTFFiles['test.gltf'] as string;
+                const jsonData = JSON.parse(jsonString);
+                jsonData.animations.length.should.be.equal(1);
+                const animation = jsonData.animations[0];
+                animation.channels.length.should.be.equal(1);
+                animation.channels[0].sampler.should.be.equal(0);
+                animation.channels[0].target.node.should.be.equal(0);
+                animation.channels[0].target.path.should.be.equal('translation');
+                jsonData.animations[0].samplers.length.should.be.equal(1);
+                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
+                Object.keys(jsonData).length.should.be.equal(10);
+                // positions, normals, indices, animation keyframe data, animation data
+                jsonData.accessors.length.should.be.equal(5);
+                // generator, version
+                Object.keys(jsonData.asset).length.should.be.equal(2);
+                jsonData.buffers.length.should.be.equal(1);
+                // positions, normals, texture coords, indices, animation keyframe data, animation data
+                jsonData.bufferViews.length.should.be.equal(6);
+                jsonData.meshes.length.should.be.equal(1);
+                jsonData.nodes.length.should.be.equal(1);
+                jsonData.scenes.length.should.be.equal(1);
+                jsonData.scene.should.be.equal(0);
             });
-            it('should serialize translation animation to glTF', (done) => {
-            mocha.timeout(10000);
+        });
+        it('should serialize translation animation to glTF', () => {
             const scene = new BABYLON.Scene(subject);
             const box = BABYLON.Mesh.CreateBox('box', 1, scene);
             let keys: BABYLON.IAnimationKey[] = [];
             keys.push({
-            frame: 0,
-            value: new BABYLON.Vector3(0.1, 0.1, 0.1)
+                frame: 0,
+                value: new BABYLON.Vector3(0.1, 0.1, 0.1)
             });
             keys.push({
-            frame: 20,
-            value: BABYLON.Vector3.One()
+                frame: 20,
+                value: BABYLON.Vector3.One()
             });
             keys.push({
-            frame: 40,
-            value: new BABYLON.Vector3(0.1, 0.1, 0.1)
+                frame: 40,
+                value: new BABYLON.Vector3(0.1, 0.1, 0.1)
             });
             let animationBoxT = new BABYLON.Animation('boxAnimation_translation', 'position', 30, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
             animationBoxT.setKeys(keys);
             box.animations.push(animationBoxT);
-            scene.executeWhenReady(function () {
-            const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-            const glTFData = glTFExporter._generateGLTF('test');
-            const jsonString = glTFData.glTFFiles['test.gltf'] as string;
-            const jsonData = JSON.parse(jsonString);
-            jsonData.animations.length.should.be.equal(1);
-            const animation = jsonData.animations[0];
-            animation.channels.length.should.be.equal(1);
-            animation.channels[0].sampler.should.be.equal(0);
-            animation.channels[0].target.node.should.be.equal(0);
-            animation.channels[0].target.path.should.be.equal('translation');
-            animation.samplers.length.should.be.equal(1);
-            animation.samplers[0].interpolation.should.be.equal('LINEAR');
-            animation.samplers[0].input.should.be.equal(4);
-            animation.samplers[0].output.should.be.equal(5);
-            jsonData.animations[0].samplers.length.should.be.equal(1);
-            // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
-            Object.keys(jsonData).length.should.be.equal(10);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.accessors.length.should.be.equal(6);
-            // generator, version
-            Object.keys(jsonData.asset).length.should.be.equal(2);
-            jsonData.buffers.length.should.be.equal(1);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.bufferViews.length.should.be.equal(6);
-            jsonData.meshes.length.should.be.equal(1);
-            jsonData.nodes.length.should.be.equal(1);
-            jsonData.scenes.length.should.be.equal(1);
-            jsonData.scene.should.be.equal(0);
-            done();
-            });
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
+                const jsonString = glTFData.glTFFiles['test.gltf'] as string;
+                const jsonData = JSON.parse(jsonString);
+                jsonData.animations.length.should.be.equal(1);
+                const animation = jsonData.animations[0];
+                animation.channels.length.should.be.equal(1);
+                animation.channels[0].sampler.should.be.equal(0);
+                animation.channels[0].target.node.should.be.equal(0);
+                animation.channels[0].target.path.should.be.equal('translation');
+                animation.samplers.length.should.be.equal(1);
+                animation.samplers[0].interpolation.should.be.equal('LINEAR');
+                animation.samplers[0].input.should.be.equal(3);
+                animation.samplers[0].output.should.be.equal(4);
+                jsonData.animations[0].samplers.length.should.be.equal(1);
+                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
+                Object.keys(jsonData).length.should.be.equal(10);
+                // positions, normals, indices, animation keyframe data, animation data
+                jsonData.accessors.length.should.be.equal(5);
+                // generator, version
+                Object.keys(jsonData.asset).length.should.be.equal(2);
+                jsonData.buffers.length.should.be.equal(1);
+                // positions, normals, texture coords, indices, animation keyframe data, animation data
+                jsonData.bufferViews.length.should.be.equal(6);
+                jsonData.meshes.length.should.be.equal(1);
+                jsonData.nodes.length.should.be.equal(1);
+                jsonData.scenes.length.should.be.equal(1);
+                jsonData.scene.should.be.equal(0);
             });
-            it('should serialize scale animation to glTF', (done) => {
-            mocha.timeout(10000);
+        });
+        it('should serialize scale animation to glTF', () => {
             const scene = new BABYLON.Scene(subject);
             const box = BABYLON.Mesh.CreateBox('box', 1, scene);
             let keys: BABYLON.IAnimationKey[] = [];
             keys.push({
-            frame: 0,
-            value: new BABYLON.Vector3(0.1, 0.1, 0.1)
+                frame: 0,
+                value: new BABYLON.Vector3(0.1, 0.1, 0.1)
             });
             keys.push({
-            frame: 20,
-            value: BABYLON.Vector3.One()
+                frame: 20,
+                value: BABYLON.Vector3.One()
             });
             keys.push({
-            frame: 40,
-            value: new BABYLON.Vector3(0.1, 0.1, 0.1)
+                frame: 40,
+                value: new BABYLON.Vector3(0.1, 0.1, 0.1)
             });
             let animationBoxT = new BABYLON.Animation('boxAnimation_translation', 'scaling', 30, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
             animationBoxT.setKeys(keys);
             box.animations.push(animationBoxT);
-            scene.executeWhenReady(function () {
-            const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-            const glTFData = glTFExporter._generateGLTF('test');
-            const jsonString = glTFData.glTFFiles['test.gltf'] as string;
-            const jsonData = JSON.parse(jsonString);
-            jsonData.animations.length.should.be.equal(1);
-            const animation = jsonData.animations[0];
-            animation.channels.length.should.be.equal(1);
-            animation.channels[0].sampler.should.be.equal(0);
-            animation.channels[0].target.node.should.be.equal(0);
-            animation.channels[0].target.path.should.be.equal('scale');
-            animation.samplers.length.should.be.equal(1);
-            animation.samplers[0].interpolation.should.be.equal('LINEAR');
-            animation.samplers[0].input.should.be.equal(4);
-            animation.samplers[0].output.should.be.equal(5);
-            jsonData.animations[0].samplers.length.should.be.equal(1);
-            // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
-            Object.keys(jsonData).length.should.be.equal(10);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.accessors.length.should.be.equal(6);
-            // generator, version
-            Object.keys(jsonData.asset).length.should.be.equal(2);
-            jsonData.buffers.length.should.be.equal(1);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.bufferViews.length.should.be.equal(6);
-            jsonData.meshes.length.should.be.equal(1);
-            jsonData.nodes.length.should.be.equal(1);
-            jsonData.scenes.length.should.be.equal(1);
-            jsonData.scene.should.be.equal(0);
-            done();
-            });
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
+                const jsonString = glTFData.glTFFiles['test.gltf'] as string;
+                const jsonData = JSON.parse(jsonString);
+                jsonData.animations.length.should.be.equal(1);
+                const animation = jsonData.animations[0];
+                animation.channels.length.should.be.equal(1);
+                animation.channels[0].sampler.should.be.equal(0);
+                animation.channels[0].target.node.should.be.equal(0);
+                animation.channels[0].target.path.should.be.equal('scale');
+                animation.samplers.length.should.be.equal(1);
+                animation.samplers[0].interpolation.should.be.equal('LINEAR');
+                animation.samplers[0].input.should.be.equal(3);
+                animation.samplers[0].output.should.be.equal(4);
+                jsonData.animations[0].samplers.length.should.be.equal(1);
+                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
+                Object.keys(jsonData).length.should.be.equal(10);
+                // positions, normals, indices, animation keyframe data, animation data
+                jsonData.accessors.length.should.be.equal(5);
+                // generator, version
+                Object.keys(jsonData.asset).length.should.be.equal(2);
+                jsonData.buffers.length.should.be.equal(1);
+                // positions, normals, texture coords, indices, animation keyframe data, animation data
+                jsonData.bufferViews.length.should.be.equal(6);
+                jsonData.meshes.length.should.be.equal(1);
+                jsonData.nodes.length.should.be.equal(1);
+                jsonData.scenes.length.should.be.equal(1);
+                jsonData.scene.should.be.equal(0);
             });
-            it('should serialize rotation quaternion animation to glTF', (done) => {
-            mocha.timeout(10000);
+        });
+        it('should serialize rotation quaternion animation to glTF', () => {
             const scene = new BABYLON.Scene(subject);
             const box = BABYLON.Mesh.CreateBox('box', 1, scene);
             let keys: BABYLON.IAnimationKey[] = [];
             keys.push({
-            frame: 0,
-            value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
+                frame: 0,
+                value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
             });
             keys.push({
-            frame: 20,
-            value: BABYLON.Quaternion.Identity()
+                frame: 20,
+                value: BABYLON.Quaternion.Identity()
             });
             keys.push({
-            frame: 40,
-            value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
+                frame: 40,
+                value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
             });
             let animationBoxT = new BABYLON.Animation('boxAnimation_translation', 'rotationQuaternion', 30, BABYLON.Animation.ANIMATIONTYPE_QUATERNION, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
             animationBoxT.setKeys(keys);
             box.animations.push(animationBoxT);
-            scene.executeWhenReady(function () {
-            const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-            const glTFData = glTFExporter._generateGLTF('test');
-            const jsonString = glTFData.glTFFiles['test.gltf'] as string;
-            const jsonData = JSON.parse(jsonString);
-            jsonData.animations.length.should.be.equal(1);
-            const animation = jsonData.animations[0];
-            animation.channels.length.should.be.equal(1);
-            animation.channels[0].sampler.should.be.equal(0);
-            animation.channels[0].target.node.should.be.equal(0);
-            animation.channels[0].target.path.should.be.equal('rotation');
-            animation.samplers.length.should.be.equal(1);
-            animation.samplers[0].interpolation.should.be.equal('LINEAR');
-            animation.samplers[0].input.should.be.equal(4);
-            animation.samplers[0].output.should.be.equal(5);
-            jsonData.animations[0].samplers.length.should.be.equal(1);
-            // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
-            Object.keys(jsonData).length.should.be.equal(10);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.accessors.length.should.be.equal(6);
-            // generator, version
-            Object.keys(jsonData.asset).length.should.be.equal(2);
-            jsonData.buffers.length.should.be.equal(1);
-            // positions, normals, texture coords, indices, animation keyframe data, animation data
-            jsonData.bufferViews.length.should.be.equal(6);
-            jsonData.meshes.length.should.be.equal(1);
-            jsonData.nodes.length.should.be.equal(1);
-            jsonData.scenes.length.should.be.equal(1);
-            jsonData.scene.should.be.equal(0);
-            done();
-            });
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
+                const jsonString = glTFData.glTFFiles['test.gltf'] as string;
+                const jsonData = JSON.parse(jsonString);
+                jsonData.animations.length.should.be.equal(1);
+                const animation = jsonData.animations[0];
+                animation.channels.length.should.be.equal(1);
+                animation.channels[0].sampler.should.be.equal(0);
+                animation.channels[0].target.node.should.be.equal(0);
+                animation.channels[0].target.path.should.be.equal('rotation');
+                animation.samplers.length.should.be.equal(1);
+                animation.samplers[0].interpolation.should.be.equal('LINEAR');
+                animation.samplers[0].input.should.be.equal(3);
+                animation.samplers[0].output.should.be.equal(4);
+                jsonData.animations[0].samplers.length.should.be.equal(1);
+                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
+                Object.keys(jsonData).length.should.be.equal(10);
+                // positions, normals, indices, animation keyframe data, animation data
+                jsonData.accessors.length.should.be.equal(5);
+                // generator, version
+                Object.keys(jsonData.asset).length.should.be.equal(2);
+                jsonData.buffers.length.should.be.equal(1);
+                // positions, normals, texture coords, indices, animation keyframe data, animation data
+                jsonData.bufferViews.length.should.be.equal(6);
+                jsonData.meshes.length.should.be.equal(1);
+                jsonData.nodes.length.should.be.equal(1);
+                jsonData.scenes.length.should.be.equal(1);
+                jsonData.scene.should.be.equal(0);
             });
-            it('should serialize combined animations to glTF', (done) => {
-            mocha.timeout(10000);
+        });
+        it('should serialize combined animations to glTF', () => {
             const scene = new BABYLON.Scene(subject);
             const box = BABYLON.Mesh.CreateBox('box', 1, scene);
             const rotationKeyFrames: BABYLON.IAnimationKey[] = [];
             rotationKeyFrames.push({
-            frame: 0,
-            value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
+                frame: 0,
+                value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
             });
             rotationKeyFrames.push({
-            frame: 20,
-            value: BABYLON.Quaternion.Identity()
+                frame: 20,
+                value: BABYLON.Quaternion.Identity()
             });
             rotationKeyFrames.push({
-            frame: 40,
-            value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
+                frame: 40,
+                value: new BABYLON.Quaternion(0.707, 0.0, 0.0, 0.707)
             });
             const scaleKeyFrames: BABYLON.IAnimationKey[] = [];
             scaleKeyFrames.push({
-            frame: 0,
-            value: new BABYLON.Vector3(0.1, 0.1, 0.1)
+                frame: 0,
+                value: new BABYLON.Vector3(0.1, 0.1, 0.1)
             });
             scaleKeyFrames.push({
-            frame: 20,
-            value: BABYLON.Vector3.One()
+                frame: 20,
+                value: BABYLON.Vector3.One()
             });
             scaleKeyFrames.push({
-            frame: 40,
-            value: new BABYLON.Vector3(0.1, 0.1, 0.1)
+                frame: 40,
+                value: new BABYLON.Vector3(0.1, 0.1, 0.1)
             });
             let rotationAnimationBox = new BABYLON.Animation('boxAnimation_rotation', 'rotationQuaternion', 30, BABYLON.Animation.ANIMATIONTYPE_QUATERNION, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
             rotationAnimationBox.setKeys(rotationKeyFrames);
@@ -403,46 +379,43 @@ describe('Babylon glTF Serializer', () => {
             let scaleAnimationBox = new BABYLON.Animation('boxAnimation_scale', 'scaling', 30, BABYLON.Animation.ANIMATIONTYPE_VECTOR3, BABYLON.Animation.ANIMATIONLOOPMODE_CYCLE);
             scaleAnimationBox.setKeys(scaleKeyFrames);
             box.animations.push(scaleAnimationBox);
-            scene.executeWhenReady(function () {
-            const glTFExporter = new BABYLON.GLTF2._Exporter(scene);
-            const glTFData = glTFExporter._generateGLTF('test');
-            const jsonString = glTFData.glTFFiles['test.gltf'] as string;
-            const jsonData = JSON.parse(jsonString);
-            jsonData.animations.length.should.be.equal(2);
-            
-            let animation = jsonData.animations[0];
-            animation.channels.length.should.be.equal(1);
-            animation.channels[0].sampler.should.be.equal(0);
-            animation.channels[0].target.node.should.be.equal(0);
-            animation.channels[0].target.path.should.be.equal('rotation');
-            animation.samplers.length.should.be.equal(1);
-            animation.samplers[0].interpolation.should.be.equal('LINEAR');
-            animation.samplers[0].input.should.be.equal(4);
-            animation.samplers[0].output.should.be.equal(5);
-            animation = jsonData.animations[1];
-            animation.channels[0].sampler.should.be.equal(0);
-            animation.channels[0].target.node.should.be.equal(0);
-            animation.channels[0].target.path.should.be.equal('scale');
-            animation.samplers.length.should.be.equal(1);
-            animation.samplers[0].interpolation.should.be.equal('LINEAR');
-            animation.samplers[0].input.should.be.equal(6);
-            animation.samplers[0].output.should.be.equal(7);
-            // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
-            Object.keys(jsonData).length.should.be.equal(10);
-            // positions, normals, texture coords, indices, rotation animation keyframe data, rotation animation data, scale animation keyframe data, scale animation data
-            jsonData.accessors.length.should.be.equal(8);
-            // generator, version
-            Object.keys(jsonData.asset).length.should.be.equal(2);
-            jsonData.buffers.length.should.be.equal(1);
-            // positions, normals, texture coords, indices, rotation animation keyframe data, rotation animation data, scale animation keyframe data, scale animation data 
-            jsonData.bufferViews.length.should.be.equal(8);
-            jsonData.meshes.length.should.be.equal(1);
-            jsonData.nodes.length.should.be.equal(1);
-            jsonData.scenes.length.should.be.equal(1);
-            jsonData.scene.should.be.equal(0);
-            done();
-            });
+
+            return BABYLON.GLTF2Export.GLTFAsync(scene, 'test').then(glTFData => {
+                const jsonString = glTFData.glTFFiles['test.gltf'] as string;
+                const jsonData = JSON.parse(jsonString);
+                jsonData.animations.length.should.be.equal(2);
+
+                let animation = jsonData.animations[0];
+                animation.channels.length.should.be.equal(1);
+                animation.channels[0].sampler.should.be.equal(0);
+                animation.channels[0].target.node.should.be.equal(0);
+                animation.channels[0].target.path.should.be.equal('rotation');
+                animation.samplers.length.should.be.equal(1);
+                animation.samplers[0].interpolation.should.be.equal('LINEAR');
+                animation.samplers[0].input.should.be.equal(3);
+                animation.samplers[0].output.should.be.equal(4);
+                animation = jsonData.animations[1];
+                animation.channels[0].sampler.should.be.equal(0);
+                animation.channels[0].target.node.should.be.equal(0);
+                animation.channels[0].target.path.should.be.equal('scale');
+                animation.samplers.length.should.be.equal(1);
+                animation.samplers[0].interpolation.should.be.equal('LINEAR');
+                animation.samplers[0].input.should.be.equal(5);
+                animation.samplers[0].output.should.be.equal(6);
+                // accessors, asset, buffers, bufferViews, meshes, nodes, scene, scenes, materials, animations
+                Object.keys(jsonData).length.should.be.equal(10);
+                // positions, normals, indices, rotation animation keyframe data, rotation animation data, scale animation keyframe data, scale animation data
+                jsonData.accessors.length.should.be.equal(7);
+                // generator, version
+                Object.keys(jsonData.asset).length.should.be.equal(2);
+                jsonData.buffers.length.should.be.equal(1);
+                // positions, normals, texture coords, indices, rotation animation keyframe data, rotation animation data, scale animation keyframe data, scale animation data 
+                jsonData.bufferViews.length.should.be.equal(8);
+                jsonData.meshes.length.should.be.equal(1);
+                jsonData.nodes.length.should.be.equal(1);
+                jsonData.scenes.length.should.be.equal(1);
+                jsonData.scene.should.be.equal(0);
             });
-            
+        });
     });
 });

+ 16 - 1
tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts

@@ -66,6 +66,22 @@ describe('Babylon Scene Loader', function () {
             });
         });
 
+        it('Load TwoQuads with ImportMesh and one node name', () => {
+            const scene = new BABYLON.Scene(subject);
+            return BABYLON.SceneLoader.ImportMeshAsync("node0", "http://models.babylonjs.com/Tests/TwoQuads/", "TwoQuads.gltf", scene).then(() => {
+                expect(scene.getMeshByName("node0"), "node0").to.exist;
+                expect(scene.getMeshByName("node1"), "node1").to.not.exist;
+            });
+        });
+
+        it('Load TwoQuads with ImporMesh and two node names', () => {
+            const scene = new BABYLON.Scene(subject);
+            return BABYLON.SceneLoader.ImportMeshAsync(["node0", "node1"], "http://models.babylonjs.com/Tests/TwoQuads/", "TwoQuads.gltf", scene).then(() => {
+                expect(scene.getMeshByName("node0"), "node0").to.exist;
+                expect(scene.getMeshByName("node1"), "node1").to.exist;
+            });
+        });
+
         it('Load BoomBox with callbacks', () => {
             let parsedCount = 0;
             let meshCount = 0;
@@ -482,7 +498,6 @@ describe('Babylon Scene Loader', function () {
 
         // TODO: test animation group callback
         // TODO: test material instancing
-        // TODO: test ImportMesh with specific node name
         // TODO: test KHR_materials_pbrSpecularGlossiness
         // TODO: test KHR_lights
     });