Переглянути джерело

Merge branch 'master' into DeepImmutable

# Conflicts:
#	src/Math/babylon.math.ts
Julien Barrois 6 роки тому
батько
коміт
9174640c4f
85 змінених файлів з 12501 додано та 23017 видалено
  1. 4333 4178
      Playground/babylon.d.txt
  2. 8 8
      Playground/js/index.js
  3. BIN
      Playground/scenes/miniBar2.glb
  4. 2 1
      Tools/Gulp/gulpfile.js
  5. 1 0
      Tools/Gulp/package.json
  6. 1 12317
      dist/preview release/Oimo.js
  7. 4325 4174
      dist/preview release/babylon.d.ts
  8. 1 1
      dist/preview release/babylon.js
  9. 561 251
      dist/preview release/babylon.max.js
  10. 561 251
      dist/preview release/babylon.no-module.max.js
  11. 1 1
      dist/preview release/babylon.worker.js
  12. 563 253
      dist/preview release/es6.js
  13. 1 1
      dist/preview release/glTF2Interface/package.json
  14. 5 1
      dist/preview release/gui/babylon.gui.d.ts
  15. 1 1
      dist/preview release/gui/babylon.gui.js
  16. 1 1
      dist/preview release/gui/babylon.gui.min.js
  17. 1 1
      dist/preview release/gui/babylon.gui.min.js.map
  18. 10 2
      dist/preview release/gui/babylon.gui.module.d.ts
  19. 2 2
      dist/preview release/gui/package.json
  20. 5 5
      dist/preview release/inspector/package.json
  21. 21 15
      dist/preview release/loaders/babylon.glTF2FileLoader.d.ts
  22. 178 139
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  23. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  24. 21 15
      dist/preview release/loaders/babylon.glTFFileLoader.d.ts
  25. 178 139
      dist/preview release/loaders/babylon.glTFFileLoader.js
  26. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  27. 21 15
      dist/preview release/loaders/babylonjs.loaders.d.ts
  28. 178 139
      dist/preview release/loaders/babylonjs.loaders.js
  29. 1 1
      dist/preview release/loaders/babylonjs.loaders.min.js
  30. 21 15
      dist/preview release/loaders/babylonjs.loaders.module.d.ts
  31. 3 3
      dist/preview release/loaders/package.json
  32. 2 2
      dist/preview release/materialsLibrary/package.json
  33. 2 2
      dist/preview release/postProcessesLibrary/package.json
  34. 2 2
      dist/preview release/proceduralTexturesLibrary/package.json
  35. 3 3
      dist/preview release/serializers/package.json
  36. 0 24
      dist/preview release/viewer/babylon.viewer.d.ts
  37. 1 1
      dist/preview release/viewer/babylon.viewer.js
  38. 2 2
      dist/preview release/viewer/babylon.viewer.max.js
  39. 1 24
      dist/preview release/viewer/babylon.viewer.module.d.ts
  40. 19 1
      dist/preview release/what's new.md
  41. 1 2
      gui/src/2D/advancedDynamicTexture.ts
  42. 14 3
      gui/src/2D/controls/control.ts
  43. 2 2
      loaders/src/glTF/2.0/Extensions/KHR_lights_punctual.ts
  44. 2 2
      loaders/src/glTF/2.0/Extensions/MSFT_audio_emitter.ts
  45. 21 19
      loaders/src/glTF/2.0/Extensions/MSFT_lod.ts
  46. 181 129
      loaders/src/glTF/2.0/babylon.glTFLoader.ts
  47. 2 2
      loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts
  48. 15 10
      loaders/src/glTF/2.0/babylon.glTFLoaderInterfaces.ts
  49. 1 1
      package.json
  50. 23 23
      src/Audio/babylon.sound.ts
  51. 1 1
      src/Bones/babylon.bone.ts
  52. 19 6
      src/Cameras/VR/babylon.vrExperienceHelper.ts
  53. 2 0
      src/Cameras/XR/babylon.webXRCamera.ts
  54. 12 1
      src/Cameras/XR/babylon.webXREnterExitUI.ts
  55. 29 0
      src/Cameras/XR/babylon.webXRExperienceHelper.ts
  56. 2 1
      src/Cameras/XR/babylon.webXRSessionManager.ts
  57. 16 4
      src/Cameras/babylon.targetCamera.ts
  58. 15 3
      src/Debug/babylon.skeletonViewer.ts
  59. 16 9
      src/Engine/babylon.engine.ts
  60. 1 1
      src/Gamepad/Controllers/babylon.poseEnabledController.ts
  61. 9 9
      src/Gamepad/Controllers/babylon.windowsMotionController.ts
  62. 1 1
      src/Gizmos/babylon.gizmo.ts
  63. 1 0
      src/Materials/Textures/babylon.internalTexture.ts
  64. 103 1
      src/Math/babylon.math.ts
  65. 95 30
      src/Mesh/babylon.abstractMesh.ts
  66. 3 0
      src/Mesh/babylon.geometry.ts
  67. 9 11
      src/Mesh/babylon.instancedMesh.ts
  68. 36 17
      src/Mesh/babylon.linesMesh.ts
  69. 10 79
      src/Mesh/babylon.mesh.ts
  70. 2 4
      src/Mesh/babylon.subMesh.ts
  71. 32 7
      src/Mesh/babylon.transformNode.ts
  72. 8 1
      src/Physics/Plugins/babylon.cannonJSPlugin.ts
  73. 0 8
      src/Physics/Plugins/babylon.oimoJSPlugin.ts
  74. 1 1
      src/Physics/babylon.physicsImpostor.ts
  75. 7 4
      src/PostProcess/RenderPipeline/babylon.postProcessRenderEffect.ts
  76. 5 10
      src/Rendering/babylon.edgesRenderer.ts
  77. 0 4
      src/Shaders/background.vertex.fx
  78. 11 1
      src/Tools/babylon.tools.ts
  79. 10 6
      src/babylon.node.ts
  80. 72 17
      src/babylon.scene.ts
  81. 9 10
      tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts
  82. 72 0
      tests/unit/babylon/src/Mesh/babylon.positionAndRotation.tests.ts
  83. 1 0
      tests/unit/karma.conf.js
  84. BIN
      tests/validation/ReferenceImages/xrCameraContainerRotation.png
  85. 590 584
      tests/validation/config.json

Різницю між файлами не показано, бо вона завелика
+ 4333 - 4178
Playground/babylon.d.txt


+ 8 - 8
Playground/js/index.js

@@ -582,25 +582,25 @@ function showError(errorMessage, errorEvent) {
                         //create scene
                         eval("scene = " + createSceneFunction + "()");
 
-                        // if scene returns a promise avoid checks
-                        if (scene.then) {
-                            checkCamera = false
-                            checkSceneCount = false
-                        }
-
                         if (!scene) {
                             showError(createSceneFunction + " function must return a scene.", null);
                             return;
                         }
 
+                        // if scene returns a promise avoid checks
+                        if (scene.then) {
+                            checkCamera = false;
+                            checkSceneCount = false;
+                        }
+
                         var createEngineZip = (createEngineFunction === "createEngine")
                             ? "createEngine()"
-                            : defaultEngineZip
+                            : defaultEngineZip;
 
                         zipCode =
                             code + "\r\n\r\n" +
                             "var engine = " + createEngineZip + ";\r\n" +
-                            "var scene = " + createSceneFunction + "();"
+                            "var scene = " + createSceneFunction + "();";
 
                     }
 

BIN
Playground/scenes/miniBar2.glb


+ 2 - 1
Tools/Gulp/gulpfile.js

@@ -33,6 +33,7 @@ var karmaServer = require('karma').Server;
 var gulpTslint = require("gulp-tslint");
 var tslint = require("tslint");
 const filter = require('gulp-filter');
+var cors = require('cors');
 
 //viewer declaration
 var processDeclaration = require('./processViewerDeclaration');
@@ -843,7 +844,7 @@ gulp.task("webserver", function() {
     var options = {
         port: 1338,
         livereload: false,
-
+        middleware: [cors()]
     };
 
     if (commandLineOptions.public) {

+ 1 - 0
Tools/Gulp/package.json

@@ -29,6 +29,7 @@
         "gulp-typescript": "4.0.2",
         "gulp-uglify": "^3.0.1",
         "gulp-webserver": "^0.9.1",
+        "cors": "^2.8.4",
         "karma": "^2.0.5",
         "karma-browserstack-launcher": "^1.3.0",
         "karma-chai": "^0.1.0",

Різницю між файлами не показано, бо вона завелика
+ 1 - 12317
dist/preview release/Oimo.js


Різницю між файлами не показано, бо вона завелика
+ 4325 - 4174
dist/preview release/babylon.d.ts


Різницю між файлами не показано, бо вона завелика
+ 1 - 1
dist/preview release/babylon.js


Різницю між файлами не показано, бо вона завелика
+ 561 - 251
dist/preview release/babylon.max.js


Різницю між файлами не показано, бо вона завелика
+ 561 - 251
dist/preview release/babylon.no-module.max.js


Різницю між файлами не показано, бо вона завелика
+ 1 - 1
dist/preview release/babylon.worker.js


Різницю між файлами не показано, бо вона завелика
+ 563 - 253
dist/preview release/es6.js


+ 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": "4.0.0-alpha.4",
+    "version": "4.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

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

@@ -819,6 +819,10 @@ declare module BABYLON.GUI {
     export class Control {
             /** defines the name of the control */
             name?: string | undefined;
+            /**
+                * Gets or sets a boolean indicating if alpha must be an inherited value (false by default)
+                */
+            static AllowAlphaInheritance: boolean;
             /** @hidden */
             _root: BABYLON.Nullable<Container>;
             /** @hidden */
@@ -1484,7 +1488,7 @@ declare module BABYLON.GUI {
                 */
             keepsFocusWith(): BABYLON.Nullable<Control[]>;
             /** @hidden */
-            processKey(keyCode: number, key?: string): void;
+            processKey(keyCode: number, key?: string, evt?: KeyboardEvent): void;
             /** @hidden */
             processKeyboard(evt: KeyboardEvent): void;
             _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;

Різницю між файлами не показано, бо вона завелика
+ 1 - 1
dist/preview release/gui/babylon.gui.js


Різницю між файлами не показано, бо вона завелика
+ 1 - 1
dist/preview release/gui/babylon.gui.min.js


Різницю між файлами не показано, бо вона завелика
+ 1 - 1
dist/preview release/gui/babylon.gui.min.js.map


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

@@ -926,6 +926,10 @@ declare module 'babylonjs-gui/2D/controls/control' {
     export class Control {
             /** defines the name of the control */
             name?: string | undefined;
+            /**
+                * Gets or sets a boolean indicating if alpha must be an inherited value (false by default)
+                */
+            static AllowAlphaInheritance: boolean;
             /** @hidden */
             _root: Nullable<Container>;
             /** @hidden */
@@ -1609,7 +1613,7 @@ declare module 'babylonjs-gui/2D/controls/inputText' {
                 */
             keepsFocusWith(): Nullable<Control[]>;
             /** @hidden */
-            processKey(keyCode: number, key?: string): void;
+            processKey(keyCode: number, key?: string, evt?: KeyboardEvent): void;
             /** @hidden */
             processKeyboard(evt: KeyboardEvent): void;
             _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;
@@ -3763,6 +3767,10 @@ declare module BABYLON.GUI {
     export class Control {
             /** defines the name of the control */
             name?: string | undefined;
+            /**
+                * Gets or sets a boolean indicating if alpha must be an inherited value (false by default)
+                */
+            static AllowAlphaInheritance: boolean;
             /** @hidden */
             _root: BABYLON.Nullable<Container>;
             /** @hidden */
@@ -4428,7 +4436,7 @@ declare module BABYLON.GUI {
                 */
             keepsFocusWith(): BABYLON.Nullable<Control[]>;
             /** @hidden */
-            processKey(keyCode: number, key?: string): void;
+            processKey(keyCode: number, key?: string, evt?: KeyboardEvent): void;
             /** @hidden */
             processKeyboard(evt: KeyboardEvent): void;
             _draw(parentMeasure: Measure, context: CanvasRenderingContext2D): void;

+ 2 - 2
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": "4.0.0-alpha.4",
+    "version": "4.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.4"
+        "babylonjs": "4.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

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

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-inspector",
     "description": "The Babylon.js inspector.",
-    "version": "4.0.0-alpha.4",
+    "version": "4.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -28,10 +28,10 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.4",
-        "babylonjs-gui": "4.0.0-alpha.4",
-        "babylonjs-loaders": "4.0.0-alpha.4",
-        "babylonjs-serializers": "4.0.0-alpha.4"
+        "babylonjs": "4.0.0-alpha.6",
+        "babylonjs-gui": "4.0.0-alpha.6",
+        "babylonjs-loaders": "4.0.0-alpha.6",
+        "babylonjs-serializers": "4.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

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

@@ -451,10 +451,10 @@ declare module BABYLON.GLTF2.Loader {
         occlusionTexture?: IMaterialOcclusionTextureInfo;
         emissiveTexture?: ITextureInfo;
         /** @hidden */
-        _babylonData?: {
-            [drawMode: number]: {
-                material: Material;
-                meshes: AbstractMesh[];
+        _data?: {
+            [babylonDrawMode: number]: {
+                babylonMaterial: Material;
+                babylonMeshes: AbstractMesh[];
                 promise: Promise<void>;
             };
         };
@@ -469,6 +469,11 @@ declare module BABYLON.GLTF2.Loader {
      * Loader interface with additional members.
      */
     interface IMeshPrimitive extends GLTF2.IMeshPrimitive, IArrayItem {
+        /** @hidden */
+        _instanceData?: {
+            babylonSourceMesh: Mesh;
+            promise: Promise<any>;
+        };
     }
     /**
      * Loader interface with additional members.
@@ -479,9 +484,9 @@ declare module BABYLON.GLTF2.Loader {
          */
         parent?: INode;
         /** @hidden */
-        _babylonMesh?: Mesh;
+        _babylonTransformNode?: TransformNode;
         /** @hidden */
-        _primitiveBabylonMeshes?: Mesh[];
+        _primitiveBabylonMeshes?: AbstractMesh[];
         /** @hidden */
         _babylonBones?: Bone[];
         /** @hidden */
@@ -511,9 +516,10 @@ declare module BABYLON.GLTF2.Loader {
      */
     interface ISkin extends GLTF2.ISkin, IArrayItem {
         /** @hidden */
-        _babylonSkeleton?: Skeleton;
-        /** @hidden */
-        _promise?: Promise<void>;
+        _data?: {
+            babylonSkeleton: Skeleton;
+            promise: Promise<void>;
+        };
     }
     /**
      * Loader interface with additional members.
@@ -647,7 +653,7 @@ declare module BABYLON.GLTF2 {
          * @param assign A function called synchronously after parsing the glTF properties
          * @returns A promise that resolves with the loaded Babylon mesh when the load is complete
          */
-        loadNodeAsync(context: string, node: Loader.INode, assign?: (babylonMesh: Mesh) => void): Promise<Mesh>;
+        loadNodeAsync(context: string, node: Loader.INode, assign?: (babylonTransformNode: TransformNode) => void): Promise<TransformNode>;
         private _loadMeshAsync;
         private _loadMeshPrimitiveAsync;
         private _loadVertexDataAsync;
@@ -845,9 +851,9 @@ declare module BABYLON.GLTF2 {
          * @param context The context when loading the asset
          * @param node The glTF node property
          * @param assign A function called synchronously after parsing the glTF properties
-         * @returns A promise that resolves with the loaded Babylon mesh when the load is complete or null if not handled
+         * @returns A promise that resolves with the loaded Babylon transform node when the load is complete or null if not handled
          */
-        loadNodeAsync?(context: string, node: Loader.INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync?(context: string, node: Loader.INode, assign: (babylonMesh: TransformNode) => void): Nullable<Promise<TransformNode>>;
         /**
          * Define this method to modify the default behavior when loading cameras.
          * @param context The context when loading the asset
@@ -957,7 +963,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
         /** @hidden */
         onReady(): void;
         /** @hidden */
-        loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>>;
         /** @hidden */
         _loadMaterialAsync(context: string, material: IMaterial, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<Material>>;
         /** @hidden */
@@ -1018,7 +1024,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
         /** @hidden */
         loadSceneAsync(context: string, scene: IScene): Nullable<Promise<void>>;
         /** @hidden */
-        loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>>;
         /** @hidden */
         loadAnimationAsync(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>>;
         private _loadClipAsync;
@@ -1110,7 +1116,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
         /** @hidden */
         onLoading(): void;
         /** @hidden */
-        loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>>;
     }
 }
 

+ 178 - 139
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -954,6 +954,9 @@ var BABYLON;
                         promises.push(_this._compileShadowGeneratorsAsync());
                     }
                     var resultPromise = Promise.all(promises).then(function () {
+                        if (_this._rootBabylonMesh) {
+                            _this._rootBabylonMesh.setEnabled(true);
+                        }
                         _this._setState(BABYLON.GLTFLoaderState.READY);
                         _this._extensionsOnReady();
                         _this._startAnimations();
@@ -1063,8 +1066,9 @@ var BABYLON;
             };
             GLTFLoader.prototype._createRootNode = function () {
                 this._rootBabylonMesh = new BABYLON.Mesh("__root__", this.babylonScene);
+                this._rootBabylonMesh.setEnabled(false);
                 var rootNode = {
-                    _babylonMesh: this._rootBabylonMesh,
+                    _babylonTransformNode: this._rootBabylonMesh,
                     index: -1
                 };
                 switch (this._parent.coordinateSystemMode) {
@@ -1121,8 +1125,8 @@ var BABYLON;
                         callback(babylonMesh);
                     }
                 }
-                else {
-                    callback(node._babylonMesh);
+                else if (node._babylonTransformNode instanceof BABYLON.AbstractMesh) {
+                    callback(node._babylonTransformNode);
                 }
             };
             GLTFLoader.prototype._getMeshes = function () {
@@ -1133,15 +1137,9 @@ var BABYLON;
                 if (nodes) {
                     for (var _i = 0, nodes_1 = nodes; _i < nodes_1.length; _i++) {
                         var node = nodes_1[_i];
-                        if (node._babylonMesh) {
-                            meshes.push(node._babylonMesh);
-                        }
-                        if (node._primitiveBabylonMeshes) {
-                            for (var _a = 0, _b = node._primitiveBabylonMeshes; _a < _b.length; _a++) {
-                                var babylonMesh = _b[_a];
-                                meshes.push(babylonMesh);
-                            }
-                        }
+                        this._forEachPrimitive(node, function (babylonMesh) {
+                            meshes.push(babylonMesh);
+                        });
                     }
                 }
                 return meshes;
@@ -1152,8 +1150,8 @@ var BABYLON;
                 if (skins) {
                     for (var _i = 0, skins_1 = skins; _i < skins_1.length; _i++) {
                         var skin = skins_1[_i];
-                        if (skin._babylonSkeleton) {
-                            skeletons.push(skin._babylonSkeleton);
+                        if (skin._data) {
+                            skeletons.push(skin._data.babylonSkeleton);
                         }
                     }
                 }
@@ -1213,113 +1211,145 @@ var BABYLON;
                 if (extensionPromise) {
                     return extensionPromise;
                 }
-                if (node._babylonMesh) {
+                if (node._babylonTransformNode) {
                     throw new Error(context + ": Invalid recursive node hierarchy");
                 }
                 var promises = new Array();
                 this.logOpen(context + " " + (node.name || ""));
-                var babylonMesh = new BABYLON.Mesh(node.name || "node" + node.index, this.babylonScene);
-                node._babylonMesh = babylonMesh;
-                babylonMesh.setEnabled(false);
-                GLTFLoader._LoadTransform(node, babylonMesh);
-                if (node.mesh != undefined) {
-                    var mesh = ArrayItem.Get(context + "/mesh", this.gltf.meshes, node.mesh);
-                    promises.push(this._loadMeshAsync("#/meshes/" + mesh.index, node, mesh, babylonMesh));
-                }
-                if (node.camera != undefined) {
-                    var camera = ArrayItem.Get(context + "/camera", this.gltf.cameras, node.camera);
-                    promises.push(this.loadCameraAsync("#/cameras/" + camera.index, camera, function (babylonCamera) {
-                        babylonCamera.parent = babylonMesh;
-                    }));
-                }
-                if (node.children) {
-                    var _loop_1 = function (index) {
-                        var childNode = ArrayItem.Get(context + "/children/" + index, this_1.gltf.nodes, index);
-                        promises.push(this_1.loadNodeAsync("#/nodes/" + node.index, childNode, function (childBabylonMesh) {
-                            // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
-                            if (childNode.skin != undefined) {
-                                childBabylonMesh.parent = _this._rootBabylonMesh;
-                                return;
-                            }
-                            childBabylonMesh.parent = babylonMesh;
+                var loadNode = function (babylonTransformNode) {
+                    GLTFLoader._LoadTransform(node, babylonTransformNode);
+                    if (node.camera != undefined) {
+                        var camera = ArrayItem.Get(context + "/camera", _this.gltf.cameras, node.camera);
+                        promises.push(_this.loadCameraAsync("#/cameras/" + camera.index, camera, function (babylonCamera) {
+                            babylonCamera.parent = babylonTransformNode;
                         }));
-                    };
-                    var this_1 = this;
-                    for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
-                        var index = _a[_i];
-                        _loop_1(index);
                     }
+                    if (node.children) {
+                        var _loop_1 = function (index) {
+                            var childNode = ArrayItem.Get(context + "/children/" + index, _this.gltf.nodes, index);
+                            promises.push(_this.loadNodeAsync("#/nodes/" + node.index, childNode, function (childBabylonMesh) {
+                                // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
+                                if (childNode.skin != undefined) {
+                                    childBabylonMesh.parent = _this._rootBabylonMesh;
+                                    return;
+                                }
+                                childBabylonMesh.parent = babylonTransformNode;
+                            }));
+                        };
+                        for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
+                            var index = _a[_i];
+                            _loop_1(index);
+                        }
+                    }
+                    assign(babylonTransformNode);
+                };
+                if (node.mesh == undefined) {
+                    var nodeName = node.name || "node" + node.index;
+                    node._babylonTransformNode = new BABYLON.TransformNode(nodeName, this.babylonScene);
+                    loadNode(node._babylonTransformNode);
+                }
+                else {
+                    var mesh = ArrayItem.Get(context + "/mesh", this.gltf.meshes, node.mesh);
+                    promises.push(this._loadMeshAsync("#/meshes/" + mesh.index, node, mesh, loadNode));
                 }
-                assign(babylonMesh);
-                this._parent.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 this.logClose();
                 return Promise.all(promises).then(function () {
-                    babylonMesh.setEnabled(true);
-                    return babylonMesh;
+                    _this._forEachPrimitive(node, function (babylonMesh) {
+                        babylonMesh.refreshBoundingInfo(true);
+                    });
+                    return node._babylonTransformNode;
                 });
             };
-            GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh, babylonMesh) {
-                var _this = this;
-                var promises = new Array();
-                this.logOpen(context + " " + (mesh.name || ""));
+            GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh, assign) {
                 var primitives = mesh.primitives;
-                if (!primitives || primitives.length === 0) {
+                if (!primitives || !primitives.length) {
                     throw new Error(context + ": Primitives are missing");
                 }
-                ArrayItem.Assign(primitives);
+                if (primitives[0].index == undefined) {
+                    ArrayItem.Assign(primitives);
+                }
+                var promises = new Array();
+                this.logOpen(context + " " + (mesh.name || ""));
+                var name = node.name || "node" + node.index;
                 if (primitives.length === 1) {
-                    var primitive = primitives[0];
-                    promises.push(this._loadMeshPrimitiveAsync(context + "/primitives/" + primitive.index, node, mesh, primitive, babylonMesh));
+                    var primitive = mesh.primitives[0];
+                    promises.push(this._loadMeshPrimitiveAsync(context + "/primitives/" + primitive.index + "}", name, node, mesh, primitive, function (babylonMesh) {
+                        node._babylonTransformNode = babylonMesh;
+                    }));
                 }
                 else {
-                    node._primitiveBabylonMeshes = [];
+                    var babylonTransformNode_1 = new BABYLON.TransformNode(name, this.babylonScene);
+                    node._babylonTransformNode = babylonTransformNode_1;
                     for (var _i = 0, primitives_1 = primitives; _i < primitives_1.length; _i++) {
                         var primitive = primitives_1[_i];
-                        var primitiveBabylonMesh = new BABYLON.Mesh((mesh.name || babylonMesh.name) + "_" + primitive.index, this.babylonScene, babylonMesh);
-                        node._primitiveBabylonMeshes.push(primitiveBabylonMesh);
-                        promises.push(this._loadMeshPrimitiveAsync(context + "/primitives/" + primitive.index, node, mesh, primitive, primitiveBabylonMesh));
-                        this._parent.onMeshLoadedObservable.notifyObservers(babylonMesh);
+                        promises.push(this._loadMeshPrimitiveAsync(context + "/primitives/" + primitive.index + "}", name + "_primitive" + primitive.index, node, mesh, primitive, function (babylonMesh) {
+                            babylonMesh.parent = babylonTransformNode_1;
+                            node._primitiveBabylonMeshes = node._primitiveBabylonMeshes || [];
+                            node._primitiveBabylonMeshes.push(babylonMesh);
+                        }));
                     }
                 }
                 if (node.skin != undefined) {
                     var skin = ArrayItem.Get(context + "/skin", this.gltf.skins, node.skin);
                     promises.push(this._loadSkinAsync("#/skins/" + skin.index, node, skin));
                 }
+                assign(node._babylonTransformNode);
                 this.logClose();
                 return Promise.all(promises).then(function () {
-                    _this._forEachPrimitive(node, function (babylonMesh) {
-                        babylonMesh._refreshBoundingInfo(true);
-                    });
+                    return node._babylonTransformNode;
                 });
             };
-            GLTFLoader.prototype._loadMeshPrimitiveAsync = function (context, node, mesh, primitive, babylonMesh) {
+            GLTFLoader.prototype._loadMeshPrimitiveAsync = function (context, name, node, mesh, primitive, assign) {
                 var _this = this;
-                var promises = new Array();
                 this.logOpen("" + context);
-                this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
-                promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then(function (babylonGeometry) {
-                    return _this._loadMorphTargetsAsync(context, primitive, babylonMesh, babylonGeometry).then(function () {
-                        babylonGeometry.applyToMesh(babylonMesh);
-                    });
-                }));
-                var babylonDrawMode = GLTFLoader._GetDrawMode(context, primitive.mode);
-                if (primitive.material == undefined) {
-                    var babylonMaterial = this._defaultBabylonMaterialData[babylonDrawMode];
-                    if (!babylonMaterial) {
-                        babylonMaterial = this._createDefaultMaterial("__gltf_default", babylonDrawMode);
-                        this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                        this._defaultBabylonMaterialData[babylonDrawMode] = babylonMaterial;
-                    }
-                    babylonMesh.material = babylonMaterial;
+                var canInstance = (node.skin == undefined && !mesh.primitives[0].targets);
+                var babylonAbstractMesh;
+                var promise;
+                var instanceData = primitive._instanceData;
+                if (canInstance && instanceData) {
+                    babylonAbstractMesh = instanceData.babylonSourceMesh.createInstance(name);
+                    promise = instanceData.promise;
                 }
                 else {
-                    var material = ArrayItem.Get(context + "/material", this.gltf.materials, primitive.material);
-                    promises.push(this._loadMaterialAsync("#/materials/" + material.index, material, babylonMesh, babylonDrawMode, function (babylonMaterial) {
-                        babylonMesh.material = babylonMaterial;
+                    var promises = new Array();
+                    var babylonMesh_1 = new BABYLON.Mesh(name, this.babylonScene);
+                    this._createMorphTargets(context, node, mesh, primitive, babylonMesh_1);
+                    promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh_1).then(function (babylonGeometry) {
+                        return _this._loadMorphTargetsAsync(context, primitive, babylonMesh_1, babylonGeometry).then(function () {
+                            babylonGeometry.applyToMesh(babylonMesh_1);
+                        });
                     }));
+                    var babylonDrawMode = GLTFLoader._GetDrawMode(context, primitive.mode);
+                    if (primitive.material == undefined) {
+                        var babylonMaterial = this._defaultBabylonMaterialData[babylonDrawMode];
+                        if (!babylonMaterial) {
+                            babylonMaterial = this._createDefaultMaterial("__gltf_default", babylonDrawMode);
+                            this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                            this._defaultBabylonMaterialData[babylonDrawMode] = babylonMaterial;
+                        }
+                        babylonMesh_1.material = babylonMaterial;
+                    }
+                    else {
+                        var material = ArrayItem.Get(context + "/material", this.gltf.materials, primitive.material);
+                        promises.push(this._loadMaterialAsync("#/materials/" + material.index, material, babylonMesh_1, babylonDrawMode, function (babylonMaterial) {
+                            babylonMesh_1.material = babylonMaterial;
+                        }));
+                    }
+                    promise = Promise.all(promises);
+                    if (canInstance) {
+                        primitive._instanceData = {
+                            babylonSourceMesh: babylonMesh_1,
+                            promise: promise
+                        };
+                    }
+                    babylonAbstractMesh = babylonMesh_1;
                 }
+                this._parent.onMeshLoadedObservable.notifyObservers(babylonAbstractMesh);
+                assign(babylonAbstractMesh);
                 this.logClose();
-                return Promise.all(promises).then(function () { });
+                return promise.then(function () {
+                    return babylonAbstractMesh;
+                });
             };
             GLTFLoader.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) {
                 var _this = this;
@@ -1446,6 +1476,11 @@ var BABYLON;
                 return Promise.all(promises).then(function () { });
             };
             GLTFLoader._LoadTransform = function (node, babylonNode) {
+                // Ignore the TRS of skinned nodes.
+                // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
+                if (node.skin != undefined) {
+                    return;
+                }
                 var position = BABYLON.Vector3.Zero();
                 var rotation = BABYLON.Quaternion.Identity();
                 var scaling = BABYLON.Vector3.One();
@@ -1474,45 +1509,45 @@ var BABYLON;
                     _this._forEachPrimitive(node, function (babylonMesh) {
                         babylonMesh.skeleton = skeleton;
                     });
-                    // Ignore the TRS of skinned nodes.
-                    // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
-                    node._babylonMesh.position = BABYLON.Vector3.Zero();
-                    node._babylonMesh.rotationQuaternion = BABYLON.Quaternion.Identity();
-                    node._babylonMesh.scaling = BABYLON.Vector3.One();
                 };
-                if (skin._promise) {
-                    return skin._promise.then(function () {
-                        assignSkeleton(skin._babylonSkeleton);
+                if (skin._data) {
+                    var data_1 = skin._data;
+                    return data_1.promise.then(function () {
+                        assignSkeleton(data_1.babylonSkeleton);
                     });
                 }
                 var skeletonId = "skeleton" + skin.index;
                 var babylonSkeleton = new BABYLON.Skeleton(skin.name || skeletonId, skeletonId, this.babylonScene);
-                skin._babylonSkeleton = babylonSkeleton;
-                this._loadBones(context, skin);
+                this._loadBones(context, skin, babylonSkeleton);
                 assignSkeleton(babylonSkeleton);
-                return (skin._promise = this._loadSkinInverseBindMatricesDataAsync(context, skin).then(function (inverseBindMatricesData) {
+                var promise = this._loadSkinInverseBindMatricesDataAsync(context, skin).then(function (inverseBindMatricesData) {
                     _this._updateBoneMatrices(babylonSkeleton, inverseBindMatricesData);
-                }));
+                });
+                skin._data = {
+                    babylonSkeleton: babylonSkeleton,
+                    promise: promise
+                };
+                return promise;
             };
-            GLTFLoader.prototype._loadBones = function (context, skin) {
+            GLTFLoader.prototype._loadBones = function (context, skin, babylonSkeleton) {
                 var babylonBones = {};
                 for (var _i = 0, _a = skin.joints; _i < _a.length; _i++) {
                     var index = _a[_i];
                     var node = ArrayItem.Get(context + "/joints/" + index, this.gltf.nodes, index);
-                    this._loadBone(node, skin, babylonBones);
+                    this._loadBone(node, skin, babylonSkeleton, babylonBones);
                 }
             };
-            GLTFLoader.prototype._loadBone = function (node, skin, babylonBones) {
+            GLTFLoader.prototype._loadBone = function (node, skin, babylonSkeleton, babylonBones) {
                 var babylonBone = babylonBones[node.index];
                 if (babylonBone) {
                     return babylonBone;
                 }
                 var babylonParentBone = null;
-                if (node.parent && node.parent._babylonMesh !== this._rootBabylonMesh) {
-                    babylonParentBone = this._loadBone(node.parent, skin, babylonBones);
+                if (node.parent && node.parent._babylonTransformNode !== this._rootBabylonMesh) {
+                    babylonParentBone = this._loadBone(node.parent, skin, babylonSkeleton, babylonBones);
                 }
                 var boneIndex = skin.joints.indexOf(node.index);
-                babylonBone = new BABYLON.Bone(node.name || "joint" + node.index, skin._babylonSkeleton, babylonParentBone, this._getNodeMatrix(node), null, null, boneIndex);
+                babylonBone = new BABYLON.Bone(node.name || "joint" + node.index, babylonSkeleton, babylonParentBone, this._getNodeMatrix(node), null, null, boneIndex);
                 babylonBones[node.index] = babylonBone;
                 node._babylonBones = node._babylonBones || [];
                 node._babylonBones.push(babylonBone);
@@ -1643,7 +1678,7 @@ var BABYLON;
                 var targetNode = ArrayItem.Get(context + "/target/node", this.gltf.nodes, channel.target.node);
                 // Ignore animations that have no animation targets.
                 if ((channel.target.path === "weights" /* WEIGHTS */ && !targetNode._numMorphTargets) ||
-                    (channel.target.path !== "weights" /* WEIGHTS */ && !targetNode._babylonMesh)) {
+                    (channel.target.path !== "weights" /* WEIGHTS */ && !targetNode._babylonTransformNode)) {
                     return Promise.resolve();
                 }
                 // Ignore animations targeting TRS of skinned nodes.
@@ -1774,8 +1809,10 @@ var BABYLON;
                         var animationName = babylonAnimationGroup.name + "_channel" + babylonAnimationGroup.targetedAnimations.length;
                         var babylonAnimation = new BABYLON.Animation(animationName, targetPath, 1, animationType);
                         babylonAnimation.setKeys(keys);
-                        if (targetNode._babylonBones) {
-                            var babylonAnimationTargets = [targetNode._babylonMesh].concat(targetNode._babylonBones);
+                        var babylonTransformNode = targetNode._babylonTransformNode;
+                        var babylonBones = targetNode._babylonBones;
+                        if (babylonBones) {
+                            var babylonAnimationTargets = [babylonTransformNode].concat(babylonBones);
                             for (var _i = 0, babylonAnimationTargets_1 = babylonAnimationTargets; _i < babylonAnimationTargets_1.length; _i++) {
                                 var babylonAnimationTarget = babylonAnimationTargets_1[_i];
                                 babylonAnimationTarget.animations.push(babylonAnimation);
@@ -1783,8 +1820,8 @@ var BABYLON;
                             babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonAnimationTargets);
                         }
                         else {
-                            targetNode._babylonMesh.animations.push(babylonAnimation);
-                            babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonMesh);
+                            babylonTransformNode.animations.push(babylonAnimation);
+                            babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonTransformNode);
                         }
                     }
                 });
@@ -1987,30 +2024,30 @@ var BABYLON;
                 if (extensionPromise) {
                     return extensionPromise;
                 }
-                material._babylonData = material._babylonData || {};
-                var babylonData = material._babylonData[babylonDrawMode];
+                material._data = material._data || {};
+                var babylonData = material._data[babylonDrawMode];
                 if (!babylonData) {
                     this.logOpen(context + " " + (material.name || ""));
                     var babylonMaterial = this.createMaterial(context, material, babylonDrawMode);
                     babylonData = {
-                        material: babylonMaterial,
-                        meshes: [],
+                        babylonMaterial: babylonMaterial,
+                        babylonMeshes: [],
                         promise: this.loadMaterialPropertiesAsync(context, material, babylonMaterial)
                     };
-                    material._babylonData[babylonDrawMode] = babylonData;
+                    material._data[babylonDrawMode] = babylonData;
                     this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                     this.logClose();
                 }
-                babylonData.meshes.push(babylonMesh);
+                babylonData.babylonMeshes.push(babylonMesh);
                 babylonMesh.onDisposeObservable.addOnce(function () {
-                    var index = babylonData.meshes.indexOf(babylonMesh);
+                    var index = babylonData.babylonMeshes.indexOf(babylonMesh);
                     if (index !== -1) {
-                        babylonData.meshes.splice(index, 1);
+                        babylonData.babylonMeshes.splice(index, 1);
                     }
                 });
-                assign(babylonData.material);
+                assign(babylonData.babylonMaterial);
                 return babylonData.promise.then(function () {
-                    return babylonData.material;
+                    return babylonData.babylonMaterial;
                 });
             };
             GLTFLoader.prototype._createDefaultMaterial = function (name, babylonDrawMode) {
@@ -2403,14 +2440,14 @@ var BABYLON;
                 if (this.gltf.materials) {
                     for (var _i = 0, _a = this.gltf.materials; _i < _a.length; _i++) {
                         var material = _a[_i];
-                        if (material._babylonData) {
-                            for (var babylonDrawMode in material._babylonData) {
-                                var babylonData = material._babylonData[babylonDrawMode];
-                                for (var _b = 0, _c = babylonData.meshes; _b < _c.length; _b++) {
+                        if (material._data) {
+                            for (var babylonDrawMode in material._data) {
+                                var babylonData = material._data[babylonDrawMode];
+                                for (var _b = 0, _c = babylonData.babylonMeshes; _b < _c.length; _b++) {
                                     var babylonMesh = _c[_b];
                                     // Ensure nonUniformScaling is set if necessary.
                                     babylonMesh.computeWorldMatrix(true);
-                                    var babylonMaterial = babylonData.material;
+                                    var babylonMaterial = babylonData.babylonMaterial;
                                     promises.push(babylonMaterial.forceCompilationAsync(babylonMesh));
                                     if (this._parent.useClipPlane) {
                                         promises.push(babylonMaterial.forceCompilationAsync(babylonMesh, { clipPlane: true }));
@@ -2710,16 +2747,18 @@ var BABYLON;
                                     _this._nodeIndexLOD = indexLOD;
                                     _this._nodeSignalLODs[indexLOD] = _this._nodeSignalLODs[indexLOD] || new BABYLON.Deferred();
                                 }
-                                var promise = _this._loader.loadNodeAsync("#/nodes/" + nodeLOD.index, nodeLOD).then(function (babylonMesh) {
+                                var assign_1 = function (babylonTransformNode) { babylonTransformNode.setEnabled(false); };
+                                var promise = _this._loader.loadNodeAsync("#/nodes/" + nodeLOD.index, nodeLOD, assign_1).then(function (babylonMesh) {
                                     if (indexLOD !== 0) {
                                         // TODO: should not rely on _babylonMesh
                                         var previousNodeLOD = nodeLODs[indexLOD - 1];
-                                        if (previousNodeLOD._babylonMesh) {
-                                            previousNodeLOD._babylonMesh.dispose();
-                                            delete previousNodeLOD._babylonMesh;
+                                        if (previousNodeLOD._babylonTransformNode) {
+                                            previousNodeLOD._babylonTransformNode.dispose();
+                                            delete previousNodeLOD._babylonTransformNode;
                                             _this._disposeUnusedMaterials();
                                         }
                                     }
+                                    babylonMesh.setEnabled(true);
                                     return babylonMesh;
                                 });
                                 if (indexLOD === 0) {
@@ -2761,11 +2800,11 @@ var BABYLON;
                                 }).then(function (babylonMaterial) {
                                     if (indexLOD !== 0) {
                                         assign(babylonMaterial);
-                                        // TODO: should not rely on _babylonData
-                                        var previousBabylonDataLOD = materialLODs[indexLOD - 1]._babylonData;
-                                        if (previousBabylonDataLOD[babylonDrawMode]) {
-                                            previousBabylonDataLOD[babylonDrawMode].material.dispose();
-                                            delete previousBabylonDataLOD[babylonDrawMode];
+                                        // TODO: should not rely on _data
+                                        var previousDataLOD = materialLODs[indexLOD - 1]._data;
+                                        if (previousDataLOD[babylonDrawMode]) {
+                                            previousDataLOD[babylonDrawMode].babylonMaterial.dispose();
+                                            delete previousDataLOD[babylonDrawMode];
                                         }
                                     }
                                     return babylonMaterial;
@@ -2826,17 +2865,17 @@ var BABYLON;
                         return properties;
                     };
                     MSFT_lod.prototype._disposeUnusedMaterials = function () {
-                        // TODO: should not rely on _babylonData
+                        // TODO: should not rely on _data
                         var materials = this._loader.gltf.materials;
                         if (materials) {
                             for (var _i = 0, materials_1 = materials; _i < materials_1.length; _i++) {
                                 var material = materials_1[_i];
-                                if (material._babylonData) {
-                                    for (var drawMode in material._babylonData) {
-                                        var babylonData = material._babylonData[drawMode];
-                                        if (babylonData.meshes.length === 0) {
-                                            babylonData.material.dispose(false, true);
-                                            delete material._babylonData[drawMode];
+                                if (material._data) {
+                                    for (var drawMode in material._data) {
+                                        var data = material._data[drawMode];
+                                        if (data.babylonMeshes.length === 0) {
+                                            data.babylonMaterial.dispose(false, true);
+                                            delete material._data[drawMode];
                                         }
                                     }
                                 }

Різницю між файлами не показано, бо вона завелика
+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


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

@@ -1013,10 +1013,10 @@ declare module BABYLON.GLTF2.Loader {
         occlusionTexture?: IMaterialOcclusionTextureInfo;
         emissiveTexture?: ITextureInfo;
         /** @hidden */
-        _babylonData?: {
-            [drawMode: number]: {
-                material: Material;
-                meshes: AbstractMesh[];
+        _data?: {
+            [babylonDrawMode: number]: {
+                babylonMaterial: Material;
+                babylonMeshes: AbstractMesh[];
                 promise: Promise<void>;
             };
         };
@@ -1031,6 +1031,11 @@ declare module BABYLON.GLTF2.Loader {
      * Loader interface with additional members.
      */
     interface IMeshPrimitive extends GLTF2.IMeshPrimitive, IArrayItem {
+        /** @hidden */
+        _instanceData?: {
+            babylonSourceMesh: Mesh;
+            promise: Promise<any>;
+        };
     }
     /**
      * Loader interface with additional members.
@@ -1041,9 +1046,9 @@ declare module BABYLON.GLTF2.Loader {
          */
         parent?: INode;
         /** @hidden */
-        _babylonMesh?: Mesh;
+        _babylonTransformNode?: TransformNode;
         /** @hidden */
-        _primitiveBabylonMeshes?: Mesh[];
+        _primitiveBabylonMeshes?: AbstractMesh[];
         /** @hidden */
         _babylonBones?: Bone[];
         /** @hidden */
@@ -1073,9 +1078,10 @@ declare module BABYLON.GLTF2.Loader {
      */
     interface ISkin extends GLTF2.ISkin, IArrayItem {
         /** @hidden */
-        _babylonSkeleton?: Skeleton;
-        /** @hidden */
-        _promise?: Promise<void>;
+        _data?: {
+            babylonSkeleton: Skeleton;
+            promise: Promise<void>;
+        };
     }
     /**
      * Loader interface with additional members.
@@ -1209,7 +1215,7 @@ declare module BABYLON.GLTF2 {
          * @param assign A function called synchronously after parsing the glTF properties
          * @returns A promise that resolves with the loaded Babylon mesh when the load is complete
          */
-        loadNodeAsync(context: string, node: Loader.INode, assign?: (babylonMesh: Mesh) => void): Promise<Mesh>;
+        loadNodeAsync(context: string, node: Loader.INode, assign?: (babylonTransformNode: TransformNode) => void): Promise<TransformNode>;
         private _loadMeshAsync;
         private _loadMeshPrimitiveAsync;
         private _loadVertexDataAsync;
@@ -1407,9 +1413,9 @@ declare module BABYLON.GLTF2 {
          * @param context The context when loading the asset
          * @param node The glTF node property
          * @param assign A function called synchronously after parsing the glTF properties
-         * @returns A promise that resolves with the loaded Babylon mesh when the load is complete or null if not handled
+         * @returns A promise that resolves with the loaded Babylon transform node when the load is complete or null if not handled
          */
-        loadNodeAsync?(context: string, node: Loader.INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync?(context: string, node: Loader.INode, assign: (babylonMesh: TransformNode) => void): Nullable<Promise<TransformNode>>;
         /**
          * Define this method to modify the default behavior when loading cameras.
          * @param context The context when loading the asset
@@ -1519,7 +1525,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
         /** @hidden */
         onReady(): void;
         /** @hidden */
-        loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>>;
         /** @hidden */
         _loadMaterialAsync(context: string, material: IMaterial, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<Material>>;
         /** @hidden */
@@ -1580,7 +1586,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
         /** @hidden */
         loadSceneAsync(context: string, scene: IScene): Nullable<Promise<void>>;
         /** @hidden */
-        loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>>;
         /** @hidden */
         loadAnimationAsync(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>>;
         private _loadClipAsync;
@@ -1672,7 +1678,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
         /** @hidden */
         onLoading(): void;
         /** @hidden */
-        loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>>;
     }
 }
 

+ 178 - 139
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -3162,6 +3162,9 @@ var BABYLON;
                         promises.push(_this._compileShadowGeneratorsAsync());
                     }
                     var resultPromise = Promise.all(promises).then(function () {
+                        if (_this._rootBabylonMesh) {
+                            _this._rootBabylonMesh.setEnabled(true);
+                        }
                         _this._setState(BABYLON.GLTFLoaderState.READY);
                         _this._extensionsOnReady();
                         _this._startAnimations();
@@ -3271,8 +3274,9 @@ var BABYLON;
             };
             GLTFLoader.prototype._createRootNode = function () {
                 this._rootBabylonMesh = new BABYLON.Mesh("__root__", this.babylonScene);
+                this._rootBabylonMesh.setEnabled(false);
                 var rootNode = {
-                    _babylonMesh: this._rootBabylonMesh,
+                    _babylonTransformNode: this._rootBabylonMesh,
                     index: -1
                 };
                 switch (this._parent.coordinateSystemMode) {
@@ -3329,8 +3333,8 @@ var BABYLON;
                         callback(babylonMesh);
                     }
                 }
-                else {
-                    callback(node._babylonMesh);
+                else if (node._babylonTransformNode instanceof BABYLON.AbstractMesh) {
+                    callback(node._babylonTransformNode);
                 }
             };
             GLTFLoader.prototype._getMeshes = function () {
@@ -3341,15 +3345,9 @@ var BABYLON;
                 if (nodes) {
                     for (var _i = 0, nodes_1 = nodes; _i < nodes_1.length; _i++) {
                         var node = nodes_1[_i];
-                        if (node._babylonMesh) {
-                            meshes.push(node._babylonMesh);
-                        }
-                        if (node._primitiveBabylonMeshes) {
-                            for (var _a = 0, _b = node._primitiveBabylonMeshes; _a < _b.length; _a++) {
-                                var babylonMesh = _b[_a];
-                                meshes.push(babylonMesh);
-                            }
-                        }
+                        this._forEachPrimitive(node, function (babylonMesh) {
+                            meshes.push(babylonMesh);
+                        });
                     }
                 }
                 return meshes;
@@ -3360,8 +3358,8 @@ var BABYLON;
                 if (skins) {
                     for (var _i = 0, skins_1 = skins; _i < skins_1.length; _i++) {
                         var skin = skins_1[_i];
-                        if (skin._babylonSkeleton) {
-                            skeletons.push(skin._babylonSkeleton);
+                        if (skin._data) {
+                            skeletons.push(skin._data.babylonSkeleton);
                         }
                     }
                 }
@@ -3421,113 +3419,145 @@ var BABYLON;
                 if (extensionPromise) {
                     return extensionPromise;
                 }
-                if (node._babylonMesh) {
+                if (node._babylonTransformNode) {
                     throw new Error(context + ": Invalid recursive node hierarchy");
                 }
                 var promises = new Array();
                 this.logOpen(context + " " + (node.name || ""));
-                var babylonMesh = new BABYLON.Mesh(node.name || "node" + node.index, this.babylonScene);
-                node._babylonMesh = babylonMesh;
-                babylonMesh.setEnabled(false);
-                GLTFLoader._LoadTransform(node, babylonMesh);
-                if (node.mesh != undefined) {
-                    var mesh = ArrayItem.Get(context + "/mesh", this.gltf.meshes, node.mesh);
-                    promises.push(this._loadMeshAsync("#/meshes/" + mesh.index, node, mesh, babylonMesh));
-                }
-                if (node.camera != undefined) {
-                    var camera = ArrayItem.Get(context + "/camera", this.gltf.cameras, node.camera);
-                    promises.push(this.loadCameraAsync("#/cameras/" + camera.index, camera, function (babylonCamera) {
-                        babylonCamera.parent = babylonMesh;
-                    }));
-                }
-                if (node.children) {
-                    var _loop_1 = function (index) {
-                        var childNode = ArrayItem.Get(context + "/children/" + index, this_1.gltf.nodes, index);
-                        promises.push(this_1.loadNodeAsync("#/nodes/" + node.index, childNode, function (childBabylonMesh) {
-                            // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
-                            if (childNode.skin != undefined) {
-                                childBabylonMesh.parent = _this._rootBabylonMesh;
-                                return;
-                            }
-                            childBabylonMesh.parent = babylonMesh;
+                var loadNode = function (babylonTransformNode) {
+                    GLTFLoader._LoadTransform(node, babylonTransformNode);
+                    if (node.camera != undefined) {
+                        var camera = ArrayItem.Get(context + "/camera", _this.gltf.cameras, node.camera);
+                        promises.push(_this.loadCameraAsync("#/cameras/" + camera.index, camera, function (babylonCamera) {
+                            babylonCamera.parent = babylonTransformNode;
                         }));
-                    };
-                    var this_1 = this;
-                    for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
-                        var index = _a[_i];
-                        _loop_1(index);
                     }
+                    if (node.children) {
+                        var _loop_1 = function (index) {
+                            var childNode = ArrayItem.Get(context + "/children/" + index, _this.gltf.nodes, index);
+                            promises.push(_this.loadNodeAsync("#/nodes/" + node.index, childNode, function (childBabylonMesh) {
+                                // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
+                                if (childNode.skin != undefined) {
+                                    childBabylonMesh.parent = _this._rootBabylonMesh;
+                                    return;
+                                }
+                                childBabylonMesh.parent = babylonTransformNode;
+                            }));
+                        };
+                        for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
+                            var index = _a[_i];
+                            _loop_1(index);
+                        }
+                    }
+                    assign(babylonTransformNode);
+                };
+                if (node.mesh == undefined) {
+                    var nodeName = node.name || "node" + node.index;
+                    node._babylonTransformNode = new BABYLON.TransformNode(nodeName, this.babylonScene);
+                    loadNode(node._babylonTransformNode);
+                }
+                else {
+                    var mesh = ArrayItem.Get(context + "/mesh", this.gltf.meshes, node.mesh);
+                    promises.push(this._loadMeshAsync("#/meshes/" + mesh.index, node, mesh, loadNode));
                 }
-                assign(babylonMesh);
-                this._parent.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 this.logClose();
                 return Promise.all(promises).then(function () {
-                    babylonMesh.setEnabled(true);
-                    return babylonMesh;
+                    _this._forEachPrimitive(node, function (babylonMesh) {
+                        babylonMesh.refreshBoundingInfo(true);
+                    });
+                    return node._babylonTransformNode;
                 });
             };
-            GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh, babylonMesh) {
-                var _this = this;
-                var promises = new Array();
-                this.logOpen(context + " " + (mesh.name || ""));
+            GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh, assign) {
                 var primitives = mesh.primitives;
-                if (!primitives || primitives.length === 0) {
+                if (!primitives || !primitives.length) {
                     throw new Error(context + ": Primitives are missing");
                 }
-                ArrayItem.Assign(primitives);
+                if (primitives[0].index == undefined) {
+                    ArrayItem.Assign(primitives);
+                }
+                var promises = new Array();
+                this.logOpen(context + " " + (mesh.name || ""));
+                var name = node.name || "node" + node.index;
                 if (primitives.length === 1) {
-                    var primitive = primitives[0];
-                    promises.push(this._loadMeshPrimitiveAsync(context + "/primitives/" + primitive.index, node, mesh, primitive, babylonMesh));
+                    var primitive = mesh.primitives[0];
+                    promises.push(this._loadMeshPrimitiveAsync(context + "/primitives/" + primitive.index + "}", name, node, mesh, primitive, function (babylonMesh) {
+                        node._babylonTransformNode = babylonMesh;
+                    }));
                 }
                 else {
-                    node._primitiveBabylonMeshes = [];
+                    var babylonTransformNode_1 = new BABYLON.TransformNode(name, this.babylonScene);
+                    node._babylonTransformNode = babylonTransformNode_1;
                     for (var _i = 0, primitives_1 = primitives; _i < primitives_1.length; _i++) {
                         var primitive = primitives_1[_i];
-                        var primitiveBabylonMesh = new BABYLON.Mesh((mesh.name || babylonMesh.name) + "_" + primitive.index, this.babylonScene, babylonMesh);
-                        node._primitiveBabylonMeshes.push(primitiveBabylonMesh);
-                        promises.push(this._loadMeshPrimitiveAsync(context + "/primitives/" + primitive.index, node, mesh, primitive, primitiveBabylonMesh));
-                        this._parent.onMeshLoadedObservable.notifyObservers(babylonMesh);
+                        promises.push(this._loadMeshPrimitiveAsync(context + "/primitives/" + primitive.index + "}", name + "_primitive" + primitive.index, node, mesh, primitive, function (babylonMesh) {
+                            babylonMesh.parent = babylonTransformNode_1;
+                            node._primitiveBabylonMeshes = node._primitiveBabylonMeshes || [];
+                            node._primitiveBabylonMeshes.push(babylonMesh);
+                        }));
                     }
                 }
                 if (node.skin != undefined) {
                     var skin = ArrayItem.Get(context + "/skin", this.gltf.skins, node.skin);
                     promises.push(this._loadSkinAsync("#/skins/" + skin.index, node, skin));
                 }
+                assign(node._babylonTransformNode);
                 this.logClose();
                 return Promise.all(promises).then(function () {
-                    _this._forEachPrimitive(node, function (babylonMesh) {
-                        babylonMesh._refreshBoundingInfo(true);
-                    });
+                    return node._babylonTransformNode;
                 });
             };
-            GLTFLoader.prototype._loadMeshPrimitiveAsync = function (context, node, mesh, primitive, babylonMesh) {
+            GLTFLoader.prototype._loadMeshPrimitiveAsync = function (context, name, node, mesh, primitive, assign) {
                 var _this = this;
-                var promises = new Array();
                 this.logOpen("" + context);
-                this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
-                promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then(function (babylonGeometry) {
-                    return _this._loadMorphTargetsAsync(context, primitive, babylonMesh, babylonGeometry).then(function () {
-                        babylonGeometry.applyToMesh(babylonMesh);
-                    });
-                }));
-                var babylonDrawMode = GLTFLoader._GetDrawMode(context, primitive.mode);
-                if (primitive.material == undefined) {
-                    var babylonMaterial = this._defaultBabylonMaterialData[babylonDrawMode];
-                    if (!babylonMaterial) {
-                        babylonMaterial = this._createDefaultMaterial("__gltf_default", babylonDrawMode);
-                        this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                        this._defaultBabylonMaterialData[babylonDrawMode] = babylonMaterial;
-                    }
-                    babylonMesh.material = babylonMaterial;
+                var canInstance = (node.skin == undefined && !mesh.primitives[0].targets);
+                var babylonAbstractMesh;
+                var promise;
+                var instanceData = primitive._instanceData;
+                if (canInstance && instanceData) {
+                    babylonAbstractMesh = instanceData.babylonSourceMesh.createInstance(name);
+                    promise = instanceData.promise;
                 }
                 else {
-                    var material = ArrayItem.Get(context + "/material", this.gltf.materials, primitive.material);
-                    promises.push(this._loadMaterialAsync("#/materials/" + material.index, material, babylonMesh, babylonDrawMode, function (babylonMaterial) {
-                        babylonMesh.material = babylonMaterial;
+                    var promises = new Array();
+                    var babylonMesh_1 = new BABYLON.Mesh(name, this.babylonScene);
+                    this._createMorphTargets(context, node, mesh, primitive, babylonMesh_1);
+                    promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh_1).then(function (babylonGeometry) {
+                        return _this._loadMorphTargetsAsync(context, primitive, babylonMesh_1, babylonGeometry).then(function () {
+                            babylonGeometry.applyToMesh(babylonMesh_1);
+                        });
                     }));
+                    var babylonDrawMode = GLTFLoader._GetDrawMode(context, primitive.mode);
+                    if (primitive.material == undefined) {
+                        var babylonMaterial = this._defaultBabylonMaterialData[babylonDrawMode];
+                        if (!babylonMaterial) {
+                            babylonMaterial = this._createDefaultMaterial("__gltf_default", babylonDrawMode);
+                            this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                            this._defaultBabylonMaterialData[babylonDrawMode] = babylonMaterial;
+                        }
+                        babylonMesh_1.material = babylonMaterial;
+                    }
+                    else {
+                        var material = ArrayItem.Get(context + "/material", this.gltf.materials, primitive.material);
+                        promises.push(this._loadMaterialAsync("#/materials/" + material.index, material, babylonMesh_1, babylonDrawMode, function (babylonMaterial) {
+                            babylonMesh_1.material = babylonMaterial;
+                        }));
+                    }
+                    promise = Promise.all(promises);
+                    if (canInstance) {
+                        primitive._instanceData = {
+                            babylonSourceMesh: babylonMesh_1,
+                            promise: promise
+                        };
+                    }
+                    babylonAbstractMesh = babylonMesh_1;
                 }
+                this._parent.onMeshLoadedObservable.notifyObservers(babylonAbstractMesh);
+                assign(babylonAbstractMesh);
                 this.logClose();
-                return Promise.all(promises).then(function () { });
+                return promise.then(function () {
+                    return babylonAbstractMesh;
+                });
             };
             GLTFLoader.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) {
                 var _this = this;
@@ -3654,6 +3684,11 @@ var BABYLON;
                 return Promise.all(promises).then(function () { });
             };
             GLTFLoader._LoadTransform = function (node, babylonNode) {
+                // Ignore the TRS of skinned nodes.
+                // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
+                if (node.skin != undefined) {
+                    return;
+                }
                 var position = BABYLON.Vector3.Zero();
                 var rotation = BABYLON.Quaternion.Identity();
                 var scaling = BABYLON.Vector3.One();
@@ -3682,45 +3717,45 @@ var BABYLON;
                     _this._forEachPrimitive(node, function (babylonMesh) {
                         babylonMesh.skeleton = skeleton;
                     });
-                    // Ignore the TRS of skinned nodes.
-                    // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
-                    node._babylonMesh.position = BABYLON.Vector3.Zero();
-                    node._babylonMesh.rotationQuaternion = BABYLON.Quaternion.Identity();
-                    node._babylonMesh.scaling = BABYLON.Vector3.One();
                 };
-                if (skin._promise) {
-                    return skin._promise.then(function () {
-                        assignSkeleton(skin._babylonSkeleton);
+                if (skin._data) {
+                    var data_1 = skin._data;
+                    return data_1.promise.then(function () {
+                        assignSkeleton(data_1.babylonSkeleton);
                     });
                 }
                 var skeletonId = "skeleton" + skin.index;
                 var babylonSkeleton = new BABYLON.Skeleton(skin.name || skeletonId, skeletonId, this.babylonScene);
-                skin._babylonSkeleton = babylonSkeleton;
-                this._loadBones(context, skin);
+                this._loadBones(context, skin, babylonSkeleton);
                 assignSkeleton(babylonSkeleton);
-                return (skin._promise = this._loadSkinInverseBindMatricesDataAsync(context, skin).then(function (inverseBindMatricesData) {
+                var promise = this._loadSkinInverseBindMatricesDataAsync(context, skin).then(function (inverseBindMatricesData) {
                     _this._updateBoneMatrices(babylonSkeleton, inverseBindMatricesData);
-                }));
+                });
+                skin._data = {
+                    babylonSkeleton: babylonSkeleton,
+                    promise: promise
+                };
+                return promise;
             };
-            GLTFLoader.prototype._loadBones = function (context, skin) {
+            GLTFLoader.prototype._loadBones = function (context, skin, babylonSkeleton) {
                 var babylonBones = {};
                 for (var _i = 0, _a = skin.joints; _i < _a.length; _i++) {
                     var index = _a[_i];
                     var node = ArrayItem.Get(context + "/joints/" + index, this.gltf.nodes, index);
-                    this._loadBone(node, skin, babylonBones);
+                    this._loadBone(node, skin, babylonSkeleton, babylonBones);
                 }
             };
-            GLTFLoader.prototype._loadBone = function (node, skin, babylonBones) {
+            GLTFLoader.prototype._loadBone = function (node, skin, babylonSkeleton, babylonBones) {
                 var babylonBone = babylonBones[node.index];
                 if (babylonBone) {
                     return babylonBone;
                 }
                 var babylonParentBone = null;
-                if (node.parent && node.parent._babylonMesh !== this._rootBabylonMesh) {
-                    babylonParentBone = this._loadBone(node.parent, skin, babylonBones);
+                if (node.parent && node.parent._babylonTransformNode !== this._rootBabylonMesh) {
+                    babylonParentBone = this._loadBone(node.parent, skin, babylonSkeleton, babylonBones);
                 }
                 var boneIndex = skin.joints.indexOf(node.index);
-                babylonBone = new BABYLON.Bone(node.name || "joint" + node.index, skin._babylonSkeleton, babylonParentBone, this._getNodeMatrix(node), null, null, boneIndex);
+                babylonBone = new BABYLON.Bone(node.name || "joint" + node.index, babylonSkeleton, babylonParentBone, this._getNodeMatrix(node), null, null, boneIndex);
                 babylonBones[node.index] = babylonBone;
                 node._babylonBones = node._babylonBones || [];
                 node._babylonBones.push(babylonBone);
@@ -3851,7 +3886,7 @@ var BABYLON;
                 var targetNode = ArrayItem.Get(context + "/target/node", this.gltf.nodes, channel.target.node);
                 // Ignore animations that have no animation targets.
                 if ((channel.target.path === "weights" /* WEIGHTS */ && !targetNode._numMorphTargets) ||
-                    (channel.target.path !== "weights" /* WEIGHTS */ && !targetNode._babylonMesh)) {
+                    (channel.target.path !== "weights" /* WEIGHTS */ && !targetNode._babylonTransformNode)) {
                     return Promise.resolve();
                 }
                 // Ignore animations targeting TRS of skinned nodes.
@@ -3982,8 +4017,10 @@ var BABYLON;
                         var animationName = babylonAnimationGroup.name + "_channel" + babylonAnimationGroup.targetedAnimations.length;
                         var babylonAnimation = new BABYLON.Animation(animationName, targetPath, 1, animationType);
                         babylonAnimation.setKeys(keys);
-                        if (targetNode._babylonBones) {
-                            var babylonAnimationTargets = [targetNode._babylonMesh].concat(targetNode._babylonBones);
+                        var babylonTransformNode = targetNode._babylonTransformNode;
+                        var babylonBones = targetNode._babylonBones;
+                        if (babylonBones) {
+                            var babylonAnimationTargets = [babylonTransformNode].concat(babylonBones);
                             for (var _i = 0, babylonAnimationTargets_1 = babylonAnimationTargets; _i < babylonAnimationTargets_1.length; _i++) {
                                 var babylonAnimationTarget = babylonAnimationTargets_1[_i];
                                 babylonAnimationTarget.animations.push(babylonAnimation);
@@ -3991,8 +4028,8 @@ var BABYLON;
                             babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonAnimationTargets);
                         }
                         else {
-                            targetNode._babylonMesh.animations.push(babylonAnimation);
-                            babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonMesh);
+                            babylonTransformNode.animations.push(babylonAnimation);
+                            babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonTransformNode);
                         }
                     }
                 });
@@ -4195,30 +4232,30 @@ var BABYLON;
                 if (extensionPromise) {
                     return extensionPromise;
                 }
-                material._babylonData = material._babylonData || {};
-                var babylonData = material._babylonData[babylonDrawMode];
+                material._data = material._data || {};
+                var babylonData = material._data[babylonDrawMode];
                 if (!babylonData) {
                     this.logOpen(context + " " + (material.name || ""));
                     var babylonMaterial = this.createMaterial(context, material, babylonDrawMode);
                     babylonData = {
-                        material: babylonMaterial,
-                        meshes: [],
+                        babylonMaterial: babylonMaterial,
+                        babylonMeshes: [],
                         promise: this.loadMaterialPropertiesAsync(context, material, babylonMaterial)
                     };
-                    material._babylonData[babylonDrawMode] = babylonData;
+                    material._data[babylonDrawMode] = babylonData;
                     this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                     this.logClose();
                 }
-                babylonData.meshes.push(babylonMesh);
+                babylonData.babylonMeshes.push(babylonMesh);
                 babylonMesh.onDisposeObservable.addOnce(function () {
-                    var index = babylonData.meshes.indexOf(babylonMesh);
+                    var index = babylonData.babylonMeshes.indexOf(babylonMesh);
                     if (index !== -1) {
-                        babylonData.meshes.splice(index, 1);
+                        babylonData.babylonMeshes.splice(index, 1);
                     }
                 });
-                assign(babylonData.material);
+                assign(babylonData.babylonMaterial);
                 return babylonData.promise.then(function () {
-                    return babylonData.material;
+                    return babylonData.babylonMaterial;
                 });
             };
             GLTFLoader.prototype._createDefaultMaterial = function (name, babylonDrawMode) {
@@ -4611,14 +4648,14 @@ var BABYLON;
                 if (this.gltf.materials) {
                     for (var _i = 0, _a = this.gltf.materials; _i < _a.length; _i++) {
                         var material = _a[_i];
-                        if (material._babylonData) {
-                            for (var babylonDrawMode in material._babylonData) {
-                                var babylonData = material._babylonData[babylonDrawMode];
-                                for (var _b = 0, _c = babylonData.meshes; _b < _c.length; _b++) {
+                        if (material._data) {
+                            for (var babylonDrawMode in material._data) {
+                                var babylonData = material._data[babylonDrawMode];
+                                for (var _b = 0, _c = babylonData.babylonMeshes; _b < _c.length; _b++) {
                                     var babylonMesh = _c[_b];
                                     // Ensure nonUniformScaling is set if necessary.
                                     babylonMesh.computeWorldMatrix(true);
-                                    var babylonMaterial = babylonData.material;
+                                    var babylonMaterial = babylonData.babylonMaterial;
                                     promises.push(babylonMaterial.forceCompilationAsync(babylonMesh));
                                     if (this._parent.useClipPlane) {
                                         promises.push(babylonMaterial.forceCompilationAsync(babylonMesh, { clipPlane: true }));
@@ -4918,16 +4955,18 @@ var BABYLON;
                                     _this._nodeIndexLOD = indexLOD;
                                     _this._nodeSignalLODs[indexLOD] = _this._nodeSignalLODs[indexLOD] || new BABYLON.Deferred();
                                 }
-                                var promise = _this._loader.loadNodeAsync("#/nodes/" + nodeLOD.index, nodeLOD).then(function (babylonMesh) {
+                                var assign_1 = function (babylonTransformNode) { babylonTransformNode.setEnabled(false); };
+                                var promise = _this._loader.loadNodeAsync("#/nodes/" + nodeLOD.index, nodeLOD, assign_1).then(function (babylonMesh) {
                                     if (indexLOD !== 0) {
                                         // TODO: should not rely on _babylonMesh
                                         var previousNodeLOD = nodeLODs[indexLOD - 1];
-                                        if (previousNodeLOD._babylonMesh) {
-                                            previousNodeLOD._babylonMesh.dispose();
-                                            delete previousNodeLOD._babylonMesh;
+                                        if (previousNodeLOD._babylonTransformNode) {
+                                            previousNodeLOD._babylonTransformNode.dispose();
+                                            delete previousNodeLOD._babylonTransformNode;
                                             _this._disposeUnusedMaterials();
                                         }
                                     }
+                                    babylonMesh.setEnabled(true);
                                     return babylonMesh;
                                 });
                                 if (indexLOD === 0) {
@@ -4969,11 +5008,11 @@ var BABYLON;
                                 }).then(function (babylonMaterial) {
                                     if (indexLOD !== 0) {
                                         assign(babylonMaterial);
-                                        // TODO: should not rely on _babylonData
-                                        var previousBabylonDataLOD = materialLODs[indexLOD - 1]._babylonData;
-                                        if (previousBabylonDataLOD[babylonDrawMode]) {
-                                            previousBabylonDataLOD[babylonDrawMode].material.dispose();
-                                            delete previousBabylonDataLOD[babylonDrawMode];
+                                        // TODO: should not rely on _data
+                                        var previousDataLOD = materialLODs[indexLOD - 1]._data;
+                                        if (previousDataLOD[babylonDrawMode]) {
+                                            previousDataLOD[babylonDrawMode].babylonMaterial.dispose();
+                                            delete previousDataLOD[babylonDrawMode];
                                         }
                                     }
                                     return babylonMaterial;
@@ -5034,17 +5073,17 @@ var BABYLON;
                         return properties;
                     };
                     MSFT_lod.prototype._disposeUnusedMaterials = function () {
-                        // TODO: should not rely on _babylonData
+                        // TODO: should not rely on _data
                         var materials = this._loader.gltf.materials;
                         if (materials) {
                             for (var _i = 0, materials_1 = materials; _i < materials_1.length; _i++) {
                                 var material = materials_1[_i];
-                                if (material._babylonData) {
-                                    for (var drawMode in material._babylonData) {
-                                        var babylonData = material._babylonData[drawMode];
-                                        if (babylonData.meshes.length === 0) {
-                                            babylonData.material.dispose(false, true);
-                                            delete material._babylonData[drawMode];
+                                if (material._data) {
+                                    for (var drawMode in material._data) {
+                                        var data = material._data[drawMode];
+                                        if (data.babylonMeshes.length === 0) {
+                                            data.babylonMaterial.dispose(false, true);
+                                            delete material._data[drawMode];
                                         }
                                     }
                                 }

Різницю між файлами не показано, бо вона завелика
+ 1 - 1
dist/preview release/loaders/babylon.glTFFileLoader.min.js


+ 21 - 15
dist/preview release/loaders/babylonjs.loaders.d.ts

@@ -1143,10 +1143,10 @@ declare module BABYLON.GLTF2.Loader {
         occlusionTexture?: IMaterialOcclusionTextureInfo;
         emissiveTexture?: ITextureInfo;
         /** @hidden */
-        _babylonData?: {
-            [drawMode: number]: {
-                material: Material;
-                meshes: AbstractMesh[];
+        _data?: {
+            [babylonDrawMode: number]: {
+                babylonMaterial: Material;
+                babylonMeshes: AbstractMesh[];
                 promise: Promise<void>;
             };
         };
@@ -1161,6 +1161,11 @@ declare module BABYLON.GLTF2.Loader {
      * Loader interface with additional members.
      */
     interface IMeshPrimitive extends GLTF2.IMeshPrimitive, IArrayItem {
+        /** @hidden */
+        _instanceData?: {
+            babylonSourceMesh: Mesh;
+            promise: Promise<any>;
+        };
     }
     /**
      * Loader interface with additional members.
@@ -1171,9 +1176,9 @@ declare module BABYLON.GLTF2.Loader {
          */
         parent?: INode;
         /** @hidden */
-        _babylonMesh?: Mesh;
+        _babylonTransformNode?: TransformNode;
         /** @hidden */
-        _primitiveBabylonMeshes?: Mesh[];
+        _primitiveBabylonMeshes?: AbstractMesh[];
         /** @hidden */
         _babylonBones?: Bone[];
         /** @hidden */
@@ -1203,9 +1208,10 @@ declare module BABYLON.GLTF2.Loader {
      */
     interface ISkin extends GLTF2.ISkin, IArrayItem {
         /** @hidden */
-        _babylonSkeleton?: Skeleton;
-        /** @hidden */
-        _promise?: Promise<void>;
+        _data?: {
+            babylonSkeleton: Skeleton;
+            promise: Promise<void>;
+        };
     }
     /**
      * Loader interface with additional members.
@@ -1339,7 +1345,7 @@ declare module BABYLON.GLTF2 {
          * @param assign A function called synchronously after parsing the glTF properties
          * @returns A promise that resolves with the loaded Babylon mesh when the load is complete
          */
-        loadNodeAsync(context: string, node: Loader.INode, assign?: (babylonMesh: Mesh) => void): Promise<Mesh>;
+        loadNodeAsync(context: string, node: Loader.INode, assign?: (babylonTransformNode: TransformNode) => void): Promise<TransformNode>;
         private _loadMeshAsync;
         private _loadMeshPrimitiveAsync;
         private _loadVertexDataAsync;
@@ -1537,9 +1543,9 @@ declare module BABYLON.GLTF2 {
          * @param context The context when loading the asset
          * @param node The glTF node property
          * @param assign A function called synchronously after parsing the glTF properties
-         * @returns A promise that resolves with the loaded Babylon mesh when the load is complete or null if not handled
+         * @returns A promise that resolves with the loaded Babylon transform node when the load is complete or null if not handled
          */
-        loadNodeAsync?(context: string, node: Loader.INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync?(context: string, node: Loader.INode, assign: (babylonMesh: TransformNode) => void): Nullable<Promise<TransformNode>>;
         /**
          * Define this method to modify the default behavior when loading cameras.
          * @param context The context when loading the asset
@@ -1649,7 +1655,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
         /** @hidden */
         onReady(): void;
         /** @hidden */
-        loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>>;
         /** @hidden */
         _loadMaterialAsync(context: string, material: IMaterial, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<Material>>;
         /** @hidden */
@@ -1710,7 +1716,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
         /** @hidden */
         loadSceneAsync(context: string, scene: IScene): Nullable<Promise<void>>;
         /** @hidden */
-        loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>>;
         /** @hidden */
         loadAnimationAsync(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>>;
         private _loadClipAsync;
@@ -1802,7 +1808,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
         /** @hidden */
         onLoading(): void;
         /** @hidden */
-        loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>>;
     }
 }
 

+ 178 - 139
dist/preview release/loaders/babylonjs.loaders.js

@@ -4224,6 +4224,9 @@ var BABYLON;
                         promises.push(_this._compileShadowGeneratorsAsync());
                     }
                     var resultPromise = Promise.all(promises).then(function () {
+                        if (_this._rootBabylonMesh) {
+                            _this._rootBabylonMesh.setEnabled(true);
+                        }
                         _this._setState(BABYLON.GLTFLoaderState.READY);
                         _this._extensionsOnReady();
                         _this._startAnimations();
@@ -4333,8 +4336,9 @@ var BABYLON;
             };
             GLTFLoader.prototype._createRootNode = function () {
                 this._rootBabylonMesh = new BABYLON.Mesh("__root__", this.babylonScene);
+                this._rootBabylonMesh.setEnabled(false);
                 var rootNode = {
-                    _babylonMesh: this._rootBabylonMesh,
+                    _babylonTransformNode: this._rootBabylonMesh,
                     index: -1
                 };
                 switch (this._parent.coordinateSystemMode) {
@@ -4391,8 +4395,8 @@ var BABYLON;
                         callback(babylonMesh);
                     }
                 }
-                else {
-                    callback(node._babylonMesh);
+                else if (node._babylonTransformNode instanceof BABYLON.AbstractMesh) {
+                    callback(node._babylonTransformNode);
                 }
             };
             GLTFLoader.prototype._getMeshes = function () {
@@ -4403,15 +4407,9 @@ var BABYLON;
                 if (nodes) {
                     for (var _i = 0, nodes_1 = nodes; _i < nodes_1.length; _i++) {
                         var node = nodes_1[_i];
-                        if (node._babylonMesh) {
-                            meshes.push(node._babylonMesh);
-                        }
-                        if (node._primitiveBabylonMeshes) {
-                            for (var _a = 0, _b = node._primitiveBabylonMeshes; _a < _b.length; _a++) {
-                                var babylonMesh = _b[_a];
-                                meshes.push(babylonMesh);
-                            }
-                        }
+                        this._forEachPrimitive(node, function (babylonMesh) {
+                            meshes.push(babylonMesh);
+                        });
                     }
                 }
                 return meshes;
@@ -4422,8 +4420,8 @@ var BABYLON;
                 if (skins) {
                     for (var _i = 0, skins_1 = skins; _i < skins_1.length; _i++) {
                         var skin = skins_1[_i];
-                        if (skin._babylonSkeleton) {
-                            skeletons.push(skin._babylonSkeleton);
+                        if (skin._data) {
+                            skeletons.push(skin._data.babylonSkeleton);
                         }
                     }
                 }
@@ -4483,113 +4481,145 @@ var BABYLON;
                 if (extensionPromise) {
                     return extensionPromise;
                 }
-                if (node._babylonMesh) {
+                if (node._babylonTransformNode) {
                     throw new Error(context + ": Invalid recursive node hierarchy");
                 }
                 var promises = new Array();
                 this.logOpen(context + " " + (node.name || ""));
-                var babylonMesh = new BABYLON.Mesh(node.name || "node" + node.index, this.babylonScene);
-                node._babylonMesh = babylonMesh;
-                babylonMesh.setEnabled(false);
-                GLTFLoader._LoadTransform(node, babylonMesh);
-                if (node.mesh != undefined) {
-                    var mesh = ArrayItem.Get(context + "/mesh", this.gltf.meshes, node.mesh);
-                    promises.push(this._loadMeshAsync("#/meshes/" + mesh.index, node, mesh, babylonMesh));
-                }
-                if (node.camera != undefined) {
-                    var camera = ArrayItem.Get(context + "/camera", this.gltf.cameras, node.camera);
-                    promises.push(this.loadCameraAsync("#/cameras/" + camera.index, camera, function (babylonCamera) {
-                        babylonCamera.parent = babylonMesh;
-                    }));
-                }
-                if (node.children) {
-                    var _loop_1 = function (index) {
-                        var childNode = ArrayItem.Get(context + "/children/" + index, this_1.gltf.nodes, index);
-                        promises.push(this_1.loadNodeAsync("#/nodes/" + node.index, childNode, function (childBabylonMesh) {
-                            // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
-                            if (childNode.skin != undefined) {
-                                childBabylonMesh.parent = _this._rootBabylonMesh;
-                                return;
-                            }
-                            childBabylonMesh.parent = babylonMesh;
+                var loadNode = function (babylonTransformNode) {
+                    GLTFLoader._LoadTransform(node, babylonTransformNode);
+                    if (node.camera != undefined) {
+                        var camera = ArrayItem.Get(context + "/camera", _this.gltf.cameras, node.camera);
+                        promises.push(_this.loadCameraAsync("#/cameras/" + camera.index, camera, function (babylonCamera) {
+                            babylonCamera.parent = babylonTransformNode;
                         }));
-                    };
-                    var this_1 = this;
-                    for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
-                        var index = _a[_i];
-                        _loop_1(index);
                     }
+                    if (node.children) {
+                        var _loop_1 = function (index) {
+                            var childNode = ArrayItem.Get(context + "/children/" + index, _this.gltf.nodes, index);
+                            promises.push(_this.loadNodeAsync("#/nodes/" + node.index, childNode, function (childBabylonMesh) {
+                                // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
+                                if (childNode.skin != undefined) {
+                                    childBabylonMesh.parent = _this._rootBabylonMesh;
+                                    return;
+                                }
+                                childBabylonMesh.parent = babylonTransformNode;
+                            }));
+                        };
+                        for (var _i = 0, _a = node.children; _i < _a.length; _i++) {
+                            var index = _a[_i];
+                            _loop_1(index);
+                        }
+                    }
+                    assign(babylonTransformNode);
+                };
+                if (node.mesh == undefined) {
+                    var nodeName = node.name || "node" + node.index;
+                    node._babylonTransformNode = new BABYLON.TransformNode(nodeName, this.babylonScene);
+                    loadNode(node._babylonTransformNode);
+                }
+                else {
+                    var mesh = ArrayItem.Get(context + "/mesh", this.gltf.meshes, node.mesh);
+                    promises.push(this._loadMeshAsync("#/meshes/" + mesh.index, node, mesh, loadNode));
                 }
-                assign(babylonMesh);
-                this._parent.onMeshLoadedObservable.notifyObservers(babylonMesh);
                 this.logClose();
                 return Promise.all(promises).then(function () {
-                    babylonMesh.setEnabled(true);
-                    return babylonMesh;
+                    _this._forEachPrimitive(node, function (babylonMesh) {
+                        babylonMesh.refreshBoundingInfo(true);
+                    });
+                    return node._babylonTransformNode;
                 });
             };
-            GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh, babylonMesh) {
-                var _this = this;
-                var promises = new Array();
-                this.logOpen(context + " " + (mesh.name || ""));
+            GLTFLoader.prototype._loadMeshAsync = function (context, node, mesh, assign) {
                 var primitives = mesh.primitives;
-                if (!primitives || primitives.length === 0) {
+                if (!primitives || !primitives.length) {
                     throw new Error(context + ": Primitives are missing");
                 }
-                ArrayItem.Assign(primitives);
+                if (primitives[0].index == undefined) {
+                    ArrayItem.Assign(primitives);
+                }
+                var promises = new Array();
+                this.logOpen(context + " " + (mesh.name || ""));
+                var name = node.name || "node" + node.index;
                 if (primitives.length === 1) {
-                    var primitive = primitives[0];
-                    promises.push(this._loadMeshPrimitiveAsync(context + "/primitives/" + primitive.index, node, mesh, primitive, babylonMesh));
+                    var primitive = mesh.primitives[0];
+                    promises.push(this._loadMeshPrimitiveAsync(context + "/primitives/" + primitive.index + "}", name, node, mesh, primitive, function (babylonMesh) {
+                        node._babylonTransformNode = babylonMesh;
+                    }));
                 }
                 else {
-                    node._primitiveBabylonMeshes = [];
+                    var babylonTransformNode_1 = new BABYLON.TransformNode(name, this.babylonScene);
+                    node._babylonTransformNode = babylonTransformNode_1;
                     for (var _i = 0, primitives_1 = primitives; _i < primitives_1.length; _i++) {
                         var primitive = primitives_1[_i];
-                        var primitiveBabylonMesh = new BABYLON.Mesh((mesh.name || babylonMesh.name) + "_" + primitive.index, this.babylonScene, babylonMesh);
-                        node._primitiveBabylonMeshes.push(primitiveBabylonMesh);
-                        promises.push(this._loadMeshPrimitiveAsync(context + "/primitives/" + primitive.index, node, mesh, primitive, primitiveBabylonMesh));
-                        this._parent.onMeshLoadedObservable.notifyObservers(babylonMesh);
+                        promises.push(this._loadMeshPrimitiveAsync(context + "/primitives/" + primitive.index + "}", name + "_primitive" + primitive.index, node, mesh, primitive, function (babylonMesh) {
+                            babylonMesh.parent = babylonTransformNode_1;
+                            node._primitiveBabylonMeshes = node._primitiveBabylonMeshes || [];
+                            node._primitiveBabylonMeshes.push(babylonMesh);
+                        }));
                     }
                 }
                 if (node.skin != undefined) {
                     var skin = ArrayItem.Get(context + "/skin", this.gltf.skins, node.skin);
                     promises.push(this._loadSkinAsync("#/skins/" + skin.index, node, skin));
                 }
+                assign(node._babylonTransformNode);
                 this.logClose();
                 return Promise.all(promises).then(function () {
-                    _this._forEachPrimitive(node, function (babylonMesh) {
-                        babylonMesh._refreshBoundingInfo(true);
-                    });
+                    return node._babylonTransformNode;
                 });
             };
-            GLTFLoader.prototype._loadMeshPrimitiveAsync = function (context, node, mesh, primitive, babylonMesh) {
+            GLTFLoader.prototype._loadMeshPrimitiveAsync = function (context, name, node, mesh, primitive, assign) {
                 var _this = this;
-                var promises = new Array();
                 this.logOpen("" + context);
-                this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
-                promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then(function (babylonGeometry) {
-                    return _this._loadMorphTargetsAsync(context, primitive, babylonMesh, babylonGeometry).then(function () {
-                        babylonGeometry.applyToMesh(babylonMesh);
-                    });
-                }));
-                var babylonDrawMode = GLTFLoader._GetDrawMode(context, primitive.mode);
-                if (primitive.material == undefined) {
-                    var babylonMaterial = this._defaultBabylonMaterialData[babylonDrawMode];
-                    if (!babylonMaterial) {
-                        babylonMaterial = this._createDefaultMaterial("__gltf_default", babylonDrawMode);
-                        this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                        this._defaultBabylonMaterialData[babylonDrawMode] = babylonMaterial;
-                    }
-                    babylonMesh.material = babylonMaterial;
+                var canInstance = (node.skin == undefined && !mesh.primitives[0].targets);
+                var babylonAbstractMesh;
+                var promise;
+                var instanceData = primitive._instanceData;
+                if (canInstance && instanceData) {
+                    babylonAbstractMesh = instanceData.babylonSourceMesh.createInstance(name);
+                    promise = instanceData.promise;
                 }
                 else {
-                    var material = ArrayItem.Get(context + "/material", this.gltf.materials, primitive.material);
-                    promises.push(this._loadMaterialAsync("#/materials/" + material.index, material, babylonMesh, babylonDrawMode, function (babylonMaterial) {
-                        babylonMesh.material = babylonMaterial;
+                    var promises = new Array();
+                    var babylonMesh_1 = new BABYLON.Mesh(name, this.babylonScene);
+                    this._createMorphTargets(context, node, mesh, primitive, babylonMesh_1);
+                    promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh_1).then(function (babylonGeometry) {
+                        return _this._loadMorphTargetsAsync(context, primitive, babylonMesh_1, babylonGeometry).then(function () {
+                            babylonGeometry.applyToMesh(babylonMesh_1);
+                        });
                     }));
+                    var babylonDrawMode = GLTFLoader._GetDrawMode(context, primitive.mode);
+                    if (primitive.material == undefined) {
+                        var babylonMaterial = this._defaultBabylonMaterialData[babylonDrawMode];
+                        if (!babylonMaterial) {
+                            babylonMaterial = this._createDefaultMaterial("__gltf_default", babylonDrawMode);
+                            this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                            this._defaultBabylonMaterialData[babylonDrawMode] = babylonMaterial;
+                        }
+                        babylonMesh_1.material = babylonMaterial;
+                    }
+                    else {
+                        var material = ArrayItem.Get(context + "/material", this.gltf.materials, primitive.material);
+                        promises.push(this._loadMaterialAsync("#/materials/" + material.index, material, babylonMesh_1, babylonDrawMode, function (babylonMaterial) {
+                            babylonMesh_1.material = babylonMaterial;
+                        }));
+                    }
+                    promise = Promise.all(promises);
+                    if (canInstance) {
+                        primitive._instanceData = {
+                            babylonSourceMesh: babylonMesh_1,
+                            promise: promise
+                        };
+                    }
+                    babylonAbstractMesh = babylonMesh_1;
                 }
+                this._parent.onMeshLoadedObservable.notifyObservers(babylonAbstractMesh);
+                assign(babylonAbstractMesh);
                 this.logClose();
-                return Promise.all(promises).then(function () { });
+                return promise.then(function () {
+                    return babylonAbstractMesh;
+                });
             };
             GLTFLoader.prototype._loadVertexDataAsync = function (context, primitive, babylonMesh) {
                 var _this = this;
@@ -4716,6 +4746,11 @@ var BABYLON;
                 return Promise.all(promises).then(function () { });
             };
             GLTFLoader._LoadTransform = function (node, babylonNode) {
+                // Ignore the TRS of skinned nodes.
+                // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
+                if (node.skin != undefined) {
+                    return;
+                }
                 var position = BABYLON.Vector3.Zero();
                 var rotation = BABYLON.Quaternion.Identity();
                 var scaling = BABYLON.Vector3.One();
@@ -4744,45 +4779,45 @@ var BABYLON;
                     _this._forEachPrimitive(node, function (babylonMesh) {
                         babylonMesh.skeleton = skeleton;
                     });
-                    // Ignore the TRS of skinned nodes.
-                    // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
-                    node._babylonMesh.position = BABYLON.Vector3.Zero();
-                    node._babylonMesh.rotationQuaternion = BABYLON.Quaternion.Identity();
-                    node._babylonMesh.scaling = BABYLON.Vector3.One();
                 };
-                if (skin._promise) {
-                    return skin._promise.then(function () {
-                        assignSkeleton(skin._babylonSkeleton);
+                if (skin._data) {
+                    var data_1 = skin._data;
+                    return data_1.promise.then(function () {
+                        assignSkeleton(data_1.babylonSkeleton);
                     });
                 }
                 var skeletonId = "skeleton" + skin.index;
                 var babylonSkeleton = new BABYLON.Skeleton(skin.name || skeletonId, skeletonId, this.babylonScene);
-                skin._babylonSkeleton = babylonSkeleton;
-                this._loadBones(context, skin);
+                this._loadBones(context, skin, babylonSkeleton);
                 assignSkeleton(babylonSkeleton);
-                return (skin._promise = this._loadSkinInverseBindMatricesDataAsync(context, skin).then(function (inverseBindMatricesData) {
+                var promise = this._loadSkinInverseBindMatricesDataAsync(context, skin).then(function (inverseBindMatricesData) {
                     _this._updateBoneMatrices(babylonSkeleton, inverseBindMatricesData);
-                }));
+                });
+                skin._data = {
+                    babylonSkeleton: babylonSkeleton,
+                    promise: promise
+                };
+                return promise;
             };
-            GLTFLoader.prototype._loadBones = function (context, skin) {
+            GLTFLoader.prototype._loadBones = function (context, skin, babylonSkeleton) {
                 var babylonBones = {};
                 for (var _i = 0, _a = skin.joints; _i < _a.length; _i++) {
                     var index = _a[_i];
                     var node = ArrayItem.Get(context + "/joints/" + index, this.gltf.nodes, index);
-                    this._loadBone(node, skin, babylonBones);
+                    this._loadBone(node, skin, babylonSkeleton, babylonBones);
                 }
             };
-            GLTFLoader.prototype._loadBone = function (node, skin, babylonBones) {
+            GLTFLoader.prototype._loadBone = function (node, skin, babylonSkeleton, babylonBones) {
                 var babylonBone = babylonBones[node.index];
                 if (babylonBone) {
                     return babylonBone;
                 }
                 var babylonParentBone = null;
-                if (node.parent && node.parent._babylonMesh !== this._rootBabylonMesh) {
-                    babylonParentBone = this._loadBone(node.parent, skin, babylonBones);
+                if (node.parent && node.parent._babylonTransformNode !== this._rootBabylonMesh) {
+                    babylonParentBone = this._loadBone(node.parent, skin, babylonSkeleton, babylonBones);
                 }
                 var boneIndex = skin.joints.indexOf(node.index);
-                babylonBone = new BABYLON.Bone(node.name || "joint" + node.index, skin._babylonSkeleton, babylonParentBone, this._getNodeMatrix(node), null, null, boneIndex);
+                babylonBone = new BABYLON.Bone(node.name || "joint" + node.index, babylonSkeleton, babylonParentBone, this._getNodeMatrix(node), null, null, boneIndex);
                 babylonBones[node.index] = babylonBone;
                 node._babylonBones = node._babylonBones || [];
                 node._babylonBones.push(babylonBone);
@@ -4913,7 +4948,7 @@ var BABYLON;
                 var targetNode = ArrayItem.Get(context + "/target/node", this.gltf.nodes, channel.target.node);
                 // Ignore animations that have no animation targets.
                 if ((channel.target.path === "weights" /* WEIGHTS */ && !targetNode._numMorphTargets) ||
-                    (channel.target.path !== "weights" /* WEIGHTS */ && !targetNode._babylonMesh)) {
+                    (channel.target.path !== "weights" /* WEIGHTS */ && !targetNode._babylonTransformNode)) {
                     return Promise.resolve();
                 }
                 // Ignore animations targeting TRS of skinned nodes.
@@ -5044,8 +5079,10 @@ var BABYLON;
                         var animationName = babylonAnimationGroup.name + "_channel" + babylonAnimationGroup.targetedAnimations.length;
                         var babylonAnimation = new BABYLON.Animation(animationName, targetPath, 1, animationType);
                         babylonAnimation.setKeys(keys);
-                        if (targetNode._babylonBones) {
-                            var babylonAnimationTargets = [targetNode._babylonMesh].concat(targetNode._babylonBones);
+                        var babylonTransformNode = targetNode._babylonTransformNode;
+                        var babylonBones = targetNode._babylonBones;
+                        if (babylonBones) {
+                            var babylonAnimationTargets = [babylonTransformNode].concat(babylonBones);
                             for (var _i = 0, babylonAnimationTargets_1 = babylonAnimationTargets; _i < babylonAnimationTargets_1.length; _i++) {
                                 var babylonAnimationTarget = babylonAnimationTargets_1[_i];
                                 babylonAnimationTarget.animations.push(babylonAnimation);
@@ -5053,8 +5090,8 @@ var BABYLON;
                             babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonAnimationTargets);
                         }
                         else {
-                            targetNode._babylonMesh.animations.push(babylonAnimation);
-                            babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonMesh);
+                            babylonTransformNode.animations.push(babylonAnimation);
+                            babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonTransformNode);
                         }
                     }
                 });
@@ -5257,30 +5294,30 @@ var BABYLON;
                 if (extensionPromise) {
                     return extensionPromise;
                 }
-                material._babylonData = material._babylonData || {};
-                var babylonData = material._babylonData[babylonDrawMode];
+                material._data = material._data || {};
+                var babylonData = material._data[babylonDrawMode];
                 if (!babylonData) {
                     this.logOpen(context + " " + (material.name || ""));
                     var babylonMaterial = this.createMaterial(context, material, babylonDrawMode);
                     babylonData = {
-                        material: babylonMaterial,
-                        meshes: [],
+                        babylonMaterial: babylonMaterial,
+                        babylonMeshes: [],
                         promise: this.loadMaterialPropertiesAsync(context, material, babylonMaterial)
                     };
-                    material._babylonData[babylonDrawMode] = babylonData;
+                    material._data[babylonDrawMode] = babylonData;
                     this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                     this.logClose();
                 }
-                babylonData.meshes.push(babylonMesh);
+                babylonData.babylonMeshes.push(babylonMesh);
                 babylonMesh.onDisposeObservable.addOnce(function () {
-                    var index = babylonData.meshes.indexOf(babylonMesh);
+                    var index = babylonData.babylonMeshes.indexOf(babylonMesh);
                     if (index !== -1) {
-                        babylonData.meshes.splice(index, 1);
+                        babylonData.babylonMeshes.splice(index, 1);
                     }
                 });
-                assign(babylonData.material);
+                assign(babylonData.babylonMaterial);
                 return babylonData.promise.then(function () {
-                    return babylonData.material;
+                    return babylonData.babylonMaterial;
                 });
             };
             GLTFLoader.prototype._createDefaultMaterial = function (name, babylonDrawMode) {
@@ -5673,14 +5710,14 @@ var BABYLON;
                 if (this.gltf.materials) {
                     for (var _i = 0, _a = this.gltf.materials; _i < _a.length; _i++) {
                         var material = _a[_i];
-                        if (material._babylonData) {
-                            for (var babylonDrawMode in material._babylonData) {
-                                var babylonData = material._babylonData[babylonDrawMode];
-                                for (var _b = 0, _c = babylonData.meshes; _b < _c.length; _b++) {
+                        if (material._data) {
+                            for (var babylonDrawMode in material._data) {
+                                var babylonData = material._data[babylonDrawMode];
+                                for (var _b = 0, _c = babylonData.babylonMeshes; _b < _c.length; _b++) {
                                     var babylonMesh = _c[_b];
                                     // Ensure nonUniformScaling is set if necessary.
                                     babylonMesh.computeWorldMatrix(true);
-                                    var babylonMaterial = babylonData.material;
+                                    var babylonMaterial = babylonData.babylonMaterial;
                                     promises.push(babylonMaterial.forceCompilationAsync(babylonMesh));
                                     if (this._parent.useClipPlane) {
                                         promises.push(babylonMaterial.forceCompilationAsync(babylonMesh, { clipPlane: true }));
@@ -5980,16 +6017,18 @@ var BABYLON;
                                     _this._nodeIndexLOD = indexLOD;
                                     _this._nodeSignalLODs[indexLOD] = _this._nodeSignalLODs[indexLOD] || new BABYLON.Deferred();
                                 }
-                                var promise = _this._loader.loadNodeAsync("#/nodes/" + nodeLOD.index, nodeLOD).then(function (babylonMesh) {
+                                var assign_1 = function (babylonTransformNode) { babylonTransformNode.setEnabled(false); };
+                                var promise = _this._loader.loadNodeAsync("#/nodes/" + nodeLOD.index, nodeLOD, assign_1).then(function (babylonMesh) {
                                     if (indexLOD !== 0) {
                                         // TODO: should not rely on _babylonMesh
                                         var previousNodeLOD = nodeLODs[indexLOD - 1];
-                                        if (previousNodeLOD._babylonMesh) {
-                                            previousNodeLOD._babylonMesh.dispose();
-                                            delete previousNodeLOD._babylonMesh;
+                                        if (previousNodeLOD._babylonTransformNode) {
+                                            previousNodeLOD._babylonTransformNode.dispose();
+                                            delete previousNodeLOD._babylonTransformNode;
                                             _this._disposeUnusedMaterials();
                                         }
                                     }
+                                    babylonMesh.setEnabled(true);
                                     return babylonMesh;
                                 });
                                 if (indexLOD === 0) {
@@ -6031,11 +6070,11 @@ var BABYLON;
                                 }).then(function (babylonMaterial) {
                                     if (indexLOD !== 0) {
                                         assign(babylonMaterial);
-                                        // TODO: should not rely on _babylonData
-                                        var previousBabylonDataLOD = materialLODs[indexLOD - 1]._babylonData;
-                                        if (previousBabylonDataLOD[babylonDrawMode]) {
-                                            previousBabylonDataLOD[babylonDrawMode].material.dispose();
-                                            delete previousBabylonDataLOD[babylonDrawMode];
+                                        // TODO: should not rely on _data
+                                        var previousDataLOD = materialLODs[indexLOD - 1]._data;
+                                        if (previousDataLOD[babylonDrawMode]) {
+                                            previousDataLOD[babylonDrawMode].babylonMaterial.dispose();
+                                            delete previousDataLOD[babylonDrawMode];
                                         }
                                     }
                                     return babylonMaterial;
@@ -6096,17 +6135,17 @@ var BABYLON;
                         return properties;
                     };
                     MSFT_lod.prototype._disposeUnusedMaterials = function () {
-                        // TODO: should not rely on _babylonData
+                        // TODO: should not rely on _data
                         var materials = this._loader.gltf.materials;
                         if (materials) {
                             for (var _i = 0, materials_1 = materials; _i < materials_1.length; _i++) {
                                 var material = materials_1[_i];
-                                if (material._babylonData) {
-                                    for (var drawMode in material._babylonData) {
-                                        var babylonData = material._babylonData[drawMode];
-                                        if (babylonData.meshes.length === 0) {
-                                            babylonData.material.dispose(false, true);
-                                            delete material._babylonData[drawMode];
+                                if (material._data) {
+                                    for (var drawMode in material._data) {
+                                        var data = material._data[drawMode];
+                                        if (data.babylonMeshes.length === 0) {
+                                            data.babylonMaterial.dispose(false, true);
+                                            delete material._data[drawMode];
                                         }
                                     }
                                 }

Різницю між файлами не показано, бо вона завелика
+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.min.js


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

@@ -1150,10 +1150,10 @@ declare module BABYLON.GLTF2.Loader {
         occlusionTexture?: IMaterialOcclusionTextureInfo;
         emissiveTexture?: ITextureInfo;
         /** @hidden */
-        _babylonData?: {
-            [drawMode: number]: {
-                material: Material;
-                meshes: AbstractMesh[];
+        _data?: {
+            [babylonDrawMode: number]: {
+                babylonMaterial: Material;
+                babylonMeshes: AbstractMesh[];
                 promise: Promise<void>;
             };
         };
@@ -1168,6 +1168,11 @@ declare module BABYLON.GLTF2.Loader {
      * Loader interface with additional members.
      */
     interface IMeshPrimitive extends GLTF2.IMeshPrimitive, IArrayItem {
+        /** @hidden */
+        _instanceData?: {
+            babylonSourceMesh: Mesh;
+            promise: Promise<any>;
+        };
     }
     /**
      * Loader interface with additional members.
@@ -1178,9 +1183,9 @@ declare module BABYLON.GLTF2.Loader {
          */
         parent?: INode;
         /** @hidden */
-        _babylonMesh?: Mesh;
+        _babylonTransformNode?: TransformNode;
         /** @hidden */
-        _primitiveBabylonMeshes?: Mesh[];
+        _primitiveBabylonMeshes?: AbstractMesh[];
         /** @hidden */
         _babylonBones?: Bone[];
         /** @hidden */
@@ -1210,9 +1215,10 @@ declare module BABYLON.GLTF2.Loader {
      */
     interface ISkin extends GLTF2.ISkin, IArrayItem {
         /** @hidden */
-        _babylonSkeleton?: Skeleton;
-        /** @hidden */
-        _promise?: Promise<void>;
+        _data?: {
+            babylonSkeleton: Skeleton;
+            promise: Promise<void>;
+        };
     }
     /**
      * Loader interface with additional members.
@@ -1346,7 +1352,7 @@ declare module BABYLON.GLTF2 {
          * @param assign A function called synchronously after parsing the glTF properties
          * @returns A promise that resolves with the loaded Babylon mesh when the load is complete
          */
-        loadNodeAsync(context: string, node: Loader.INode, assign?: (babylonMesh: Mesh) => void): Promise<Mesh>;
+        loadNodeAsync(context: string, node: Loader.INode, assign?: (babylonTransformNode: TransformNode) => void): Promise<TransformNode>;
         private _loadMeshAsync;
         private _loadMeshPrimitiveAsync;
         private _loadVertexDataAsync;
@@ -1544,9 +1550,9 @@ declare module BABYLON.GLTF2 {
          * @param context The context when loading the asset
          * @param node The glTF node property
          * @param assign A function called synchronously after parsing the glTF properties
-         * @returns A promise that resolves with the loaded Babylon mesh when the load is complete or null if not handled
+         * @returns A promise that resolves with the loaded Babylon transform node when the load is complete or null if not handled
          */
-        loadNodeAsync?(context: string, node: Loader.INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync?(context: string, node: Loader.INode, assign: (babylonMesh: TransformNode) => void): Nullable<Promise<TransformNode>>;
         /**
          * Define this method to modify the default behavior when loading cameras.
          * @param context The context when loading the asset
@@ -1656,7 +1662,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
         /** @hidden */
         onReady(): void;
         /** @hidden */
-        loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>>;
         /** @hidden */
         _loadMaterialAsync(context: string, material: IMaterial, babylonMesh: Mesh, babylonDrawMode: number, assign: (babylonMaterial: Material) => void): Nullable<Promise<Material>>;
         /** @hidden */
@@ -1717,7 +1723,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
         /** @hidden */
         loadSceneAsync(context: string, scene: IScene): Nullable<Promise<void>>;
         /** @hidden */
-        loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>>;
         /** @hidden */
         loadAnimationAsync(context: string, animation: IAnimation): Nullable<Promise<AnimationGroup>>;
         private _loadClipAsync;
@@ -1809,7 +1815,7 @@ declare module BABYLON.GLTF2.Loader.Extensions {
         /** @hidden */
         onLoading(): void;
         /** @hidden */
-        loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>>;
     }
 }
 

+ 3 - 3
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": "4.0.0-alpha.4",
+    "version": "4.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,8 +27,8 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs-gltf2interface": "4.0.0-alpha.4",
-        "babylonjs": "4.0.0-alpha.4"
+        "babylonjs-gltf2interface": "4.0.0-alpha.6",
+        "babylonjs": "4.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

+ 2 - 2
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": "4.0.0-alpha.4",
+    "version": "4.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.4"
+        "babylonjs": "4.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

+ 2 - 2
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": "4.0.0-alpha.4",
+    "version": "4.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.4"
+        "babylonjs": "4.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

+ 2 - 2
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": "4.0.0-alpha.4",
+    "version": "4.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,7 +27,7 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.4"
+        "babylonjs": "4.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

+ 3 - 3
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": "4.0.0-alpha.4",
+    "version": "4.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"
@@ -27,8 +27,8 @@
     ],
     "license": "Apache-2.0",
     "dependencies": {
-        "babylonjs": "4.0.0-alpha.4",
-        "babylonjs-gltf2interface": "4.0.0-alpha.4"
+        "babylonjs": "4.0.0-alpha.6",
+        "babylonjs-gltf2interface": "4.0.0-alpha.6"
     },
     "engines": {
         "node": "*"

+ 0 - 24
dist/preview release/viewer/babylon.viewer.d.ts

@@ -1865,30 +1865,6 @@ declare module BabylonViewer {
     }
 }
 declare module BabylonViewer {
-    export interface IGroundConfiguration {
-        size?: number;
-        receiveShadows?: boolean;
-        shadowLevel?: number;
-        shadowOnly?: boolean;
-        mirror?: boolean | {
-            sizeRatio?: number;
-            blurKernel?: number;
-            amount?: number;
-            fresnelWeight?: number;
-            fallOffDistance?: number;
-            textureType?: number;
-        };
-        texture?: string;
-        color?: {
-            r: number;
-            g: number;
-            b: number;
-        };
-        opacity?: number;
-        material?: {
-            [propName: string]: any;
-        };
-    }
 }
 declare module BabylonViewer {
     export interface IImageProcessingConfiguration {

Різницю між файлами не показано, бо вона завелика
+ 1 - 1
dist/preview release/viewer/babylon.viewer.js


Різницю між файлами не показано, бо вона завелика
+ 2 - 2
dist/preview release/viewer/babylon.viewer.max.js


+ 1 - 24
dist/preview release/viewer/babylon.viewer.module.d.ts

@@ -2013,30 +2013,7 @@ declare module 'babylonjs-viewer/configuration/interfaces/defaultRenderingPipeli
 }
 
 declare module 'babylonjs-viewer/configuration/interfaces/groundConfiguration' {
-    export interface IGroundConfiguration {
-        size?: number;
-        receiveShadows?: boolean;
-        shadowLevel?: number;
-        shadowOnly?: boolean;
-        mirror?: boolean | {
-            sizeRatio?: number;
-            blurKernel?: number;
-            amount?: number;
-            fresnelWeight?: number;
-            fallOffDistance?: number;
-            textureType?: number;
-        };
-        texture?: string;
-        color?: {
-            r: number;
-            g: number;
-            b: number;
-        };
-        opacity?: number;
-        material?: {
-            [propName: string]: any;
-        };
-    }
+    
 }
 
 declare module 'babylonjs-viewer/configuration/interfaces/imageProcessingConfiguration' {

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

@@ -13,6 +13,7 @@
   - webXRExperienceHelper to setup a default XR experience ([TrevorDev](https://github.com/TrevorDev))
   - WebXREnterExitUI and WebXRManagedOutputCanvas classes to configure the XR experience ([TrevorDev](https://github.com/TrevorDev))
   - WebXRInput manage controllers for the XR experience ([TrevorDev](https://github.com/TrevorDev))
+  - WebXR camera rotation using parent container ([TrevorDev](https://github.com/TrevorDev))
 - GUI:
   - Added new [ImageBasedSlider](http://doc.babylonjs.com/how_to/gui#imagebasedslider) to let users customize sliders using images ([Deltakosh](https://github.com/deltakosh))
 
@@ -23,9 +24,11 @@
 - Added `button.image` and `button.textBlock` to simplify access to button internal parts ([Deltakosh](https://github.com/deltakosh))
 - Added `sldier.displayThumb` to show/hide slider's thumb ([Deltakosh](https://github.com/deltakosh))
 - Added `grid.rowCount`, `grid.columnCount` and `grid.getChildrenAt()` ([Deltakosh](https://github.com/deltakosh))
+- Added `Control.AllowAlphaInheritance` to let users control the way alpha is used (inherited or not) ([Deltakosh](https://github.com/deltakosh))
 
 ### Core Engine
 
+- Added support for utility layer for SkeletonViewer ([Deltakosh](https://github.com/deltakosh))
 - Improved shader precision detection ([Deltakosh](https://github.com/deltakosh))
 - Added support for bone matrix texture. Now skeletons will use a texture instead of uniforms when possible ([Deltakosh](https://github.com/deltakosh))
 - Refactored of the SolidParticleSystem code for performance and code quality improvement ([barroij](https://github.com/barroij))
@@ -46,9 +49,13 @@
   - Added an `Vector3.UnprojectRayToRef` static function to avoid computing and inverting the projection matrix twice when updating a Ray.
 - Align `BoundingBox` and `BoundingSphere` API and behavior for clarity and simplicity. As a consequence, the `BoundingBox`'s method `setWorldMatrix` has been removed and the underlying world matrix cannot be modified but by calling `reConstruct` or `update`. ([barroij](https://github.com/barroij))
 - Make sure that `Material.markAsDirty` and all the `markXXXDirty` methods early out when `scene.blockMaterialDirtyMechanism` is true. ([barroij](https://github.com/barroij))
+- Add updateUpVectorFromRotation to target camera to allow the up vector to be computed from rotation ([TrevorDev](https://github.com/TrevorDev))
 
 ### glTF Loader
 
+- Added support for mesh instancing for improved performance when multiple nodes point to the same mesh ([bghgary](https://github.com/bghgary))
+- Create `TransformNode` objects instead of `Mesh` objects for glTF nodes without geometry ([bghgary](https://github.com/bghgary))
+
 ### glTF Serializer
 
 ### Viewer
@@ -56,13 +63,19 @@
 ### Materials Library
 
 ## Bug fixes
+- Removed bones from rootNodes where they should never have been ([Deltakosh](https://github.com/deltakosh))
 - Refocusing on input gui with pointer events ([TrevorDev](https://github.com/TrevorDev))
+- Gizmo scaling not consistent when camera is parented ([TrevorDev](https://github.com/TrevorDev))
+- Context loss causing unexpected results with dynamic textures ([TrevorDev](https://github.com/TrevorDev))
+- CreateScreenshotUsingRenderTarget stretches mirror textures when setting both width and height ([TrevorDev](https://github.com/TrevorDev))
+- VR helper only updating vr cameras position when entering vr, rotation was missing ([TrevorDev](https://github.com/TrevorDev))
+- Fix VR controllers after gltfLoader transformNode change ([TrevorDev](https://github.com/TrevorDev))
 
 ### Core Engine
 - Fixed a bug with `mesh.alwaysSelectAsActiveMesh` preventing layerMask to be taken in account ([Deltakosh](https://github.com/deltakosh))
 - Fixed a bug with pointer up being fire twice ([Deltakosh](https://github.com/deltakosh))
 - Fixed a bug with particle systems being update once per camera instead of once per frame ([Deltakosh](https://github.com/deltakosh))
-- Handle properly the `LinesMesh` `intersectionThreshold` by using its value directly when the intersection against a `Ray` is checked, instead of extending the `BoundingInfo` accordingly ([barroij](https://github.com/barroij))
+- Handle properly the `LinesMesh` `intersectionThreshold` by using its value directly when the intersection against a `Ray` is checked, instead of extending the `BoundingInfo` accordingly + Addded an `InstancesLinesMesh` class used to create instance of `LinesMesh` so that each instance can have its own `intersectionThreshold` value ([barroij](https://github.com/barroij))
 - Fixed the `LineEdgesRenderer` used for edge rendering of `LinesMesh` handle properly LinesMesh made of disconnected lines + Make it work for instance of `LinesMesh` ([barroij](https://github.com/barroij))
 - Fixed `Matrix.toNormalMatrix`function ([barroij](https://github.com/barroij))
 - Add missing effect layer to asset container ([TrevorDev](https://github.com/TrevorDev))
@@ -97,3 +110,8 @@
   - `engine.drawCallsPerfCounter`: use SceneInstrumentation class instead
   - `shadowGenerator.useVarianceShadowMap`: use useExponentialShadowMap instead
   - `shadowGenerator.useBlurVarianceShadowMap`: use useBlurExponentialShadowMap instead
+- The glTF loader now creates `InstancedMesh` objects when two nodes point to the same mesh ([bghgary](https://github.com/bghgary))
+- The glTF loader now creates `TransformNode` objects instead of `Mesh` objects for glTF nodes without geometry ([bghgary](https://github.com/bghgary))
+  - _Note: The root node is still a `Mesh` object and is still the first in the returned list of meshes_
+  - `TransformNode` objects are excluded from the returned list of meshes when importing mesh
+  - `TransformNode` objects do not raise `onMeshLoaded` events

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

@@ -553,9 +553,8 @@ export class AdvancedDynamicTexture extends DynamicTexture {
             if (type === PointerEventTypes.POINTERMOVE) {
                 if (this._lastControlOver[pointerId]) {
                     this._lastControlOver[pointerId]._onPointerOut(this._lastControlOver[pointerId]);
+                    delete this._lastControlOver[pointerId];
                 }
-
-                delete this._lastControlOver[pointerId];
             }
         }
 

+ 14 - 3
gui/src/2D/controls/control.ts

@@ -11,6 +11,11 @@ import { Matrix2D, Vector2WithInfo } from "../math2D";
  * @see http://doc.babylonjs.com/how_to/gui#controls
  */
 export class Control {
+    /**
+     * Gets or sets a boolean indicating if alpha must be an inherited value (false by default)
+     */
+    public static AllowAlphaInheritance = false;
+
     private _alpha = 1;
     private _alphaSet = false;
     private _zIndex = 0;
@@ -985,7 +990,9 @@ export class Control {
             context.fillStyle = this._color;
         }
 
-        if (this._alphaSet) {
+        if (Control.AllowAlphaInheritance) {
+            context.globalAlpha *= this._alpha;
+        } else if (this._alphaSet) {
             context.globalAlpha = this.parent ? this.parent.alpha * this._alpha : this._alpha;
         }
     }
@@ -1269,12 +1276,16 @@ export class Control {
 
     /** @hidden */
     public _onPointerOut(target: Control): void {
-        if (!this._isEnabled) {
+        if (!this._isEnabled || target === this) {
             return;
         }
         this._enterCount = 0;
 
-        var canNotify: boolean = this.onPointerOutObservable.notifyObservers(this, -1, target, this);
+        var canNotify: boolean = true;
+
+        if (!target.isAscendant(this)) {
+            canNotify = this.onPointerOutObservable.notifyObservers(this, -1, target, this);
+        }
 
         if (canNotify && this.parent != null) { this.parent._onPointerOut(target); }
     }

+ 2 - 2
loaders/src/glTF/2.0/Extensions/KHR_lights_punctual.ts

@@ -62,8 +62,8 @@ module BABYLON.GLTF2.Loader.Extensions {
         }
 
         /** @hidden */
-        public loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>> {
-            return GLTFLoader.LoadExtensionAsync<ILightReference, Mesh>(context, node, this.name, (extensionContext, extension) => {
+        public loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>> {
+            return GLTFLoader.LoadExtensionAsync<ILightReference, TransformNode>(context, node, this.name, (extensionContext, extension) => {
                 return this._loader.loadNodeAsync(context, node, (babylonMesh) => {
                     let babylonLight: Light;
 

+ 2 - 2
loaders/src/glTF/2.0/Extensions/MSFT_audio_emitter.ts

@@ -140,8 +140,8 @@ module BABYLON.GLTF2.Loader.Extensions {
         }
 
         /** @hidden */
-        public loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>> {
-            return GLTFLoader.LoadExtensionAsync<IEmittersReference, Mesh>(context, node, this.name, (extensionContext, extension) => {
+        public loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>> {
+            return GLTFLoader.LoadExtensionAsync<IEmittersReference, TransformNode>(context, node, this.name, (extensionContext, extension) => {
                 const promises = new Array<Promise<any>>();
 
                 return this._loader.loadNodeAsync(extensionContext, node, (babylonMesh) => {

+ 21 - 19
loaders/src/glTF/2.0/Extensions/MSFT_lod.ts

@@ -111,9 +111,9 @@ module BABYLON.GLTF2.Loader.Extensions {
         }
 
         /** @hidden */
-        public loadNodeAsync(context: string, node: INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>> {
-            return GLTFLoader.LoadExtensionAsync<IMSFTLOD, Mesh>(context, node, this.name, (extensionContext, extension) => {
-                let firstPromise: Promise<Mesh>;
+        public loadNodeAsync(context: string, node: INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>> {
+            return GLTFLoader.LoadExtensionAsync<IMSFTLOD, TransformNode>(context, node, this.name, (extensionContext, extension) => {
+                let firstPromise: Promise<TransformNode>;
 
                 const nodeLODs = this._getLODs(extensionContext, node, this._loader.gltf.nodes, extension.ids);
                 this._loader.logOpen(`${extensionContext}`);
@@ -126,17 +126,19 @@ module BABYLON.GLTF2.Loader.Extensions {
                         this._nodeSignalLODs[indexLOD] = this._nodeSignalLODs[indexLOD] || new Deferred();
                     }
 
-                    const promise = this._loader.loadNodeAsync(`#/nodes/${nodeLOD.index}`, nodeLOD).then((babylonMesh) => {
+                    const assign = (babylonTransformNode: TransformNode) => { babylonTransformNode.setEnabled(false); };
+                    const promise = this._loader.loadNodeAsync(`#/nodes/${nodeLOD.index}`, nodeLOD, assign).then((babylonMesh) => {
                         if (indexLOD !== 0) {
                             // TODO: should not rely on _babylonMesh
                             const previousNodeLOD = nodeLODs[indexLOD - 1];
-                            if (previousNodeLOD._babylonMesh) {
-                                previousNodeLOD._babylonMesh.dispose();
-                                delete previousNodeLOD._babylonMesh;
+                            if (previousNodeLOD._babylonTransformNode) {
+                                previousNodeLOD._babylonTransformNode.dispose();
+                                delete previousNodeLOD._babylonTransformNode;
                                 this._disposeUnusedMaterials();
                             }
                         }
 
+                        babylonMesh.setEnabled(true);
                         return babylonMesh;
                     });
 
@@ -184,11 +186,11 @@ module BABYLON.GLTF2.Loader.Extensions {
                         if (indexLOD !== 0) {
                             assign(babylonMaterial);
 
-                            // TODO: should not rely on _babylonData
-                            const previousBabylonDataLOD = materialLODs[indexLOD - 1]._babylonData!;
-                            if (previousBabylonDataLOD[babylonDrawMode]) {
-                                previousBabylonDataLOD[babylonDrawMode].material.dispose();
-                                delete previousBabylonDataLOD[babylonDrawMode];
+                            // TODO: should not rely on _data
+                            const previousDataLOD = materialLODs[indexLOD - 1]._data!;
+                            if (previousDataLOD[babylonDrawMode]) {
+                                previousDataLOD[babylonDrawMode].babylonMaterial.dispose();
+                                delete previousDataLOD[babylonDrawMode];
                             }
                         }
 
@@ -256,16 +258,16 @@ module BABYLON.GLTF2.Loader.Extensions {
         }
 
         private _disposeUnusedMaterials(): void {
-            // TODO: should not rely on _babylonData
+            // TODO: should not rely on _data
             const materials = this._loader.gltf.materials;
             if (materials) {
                 for (const material of materials) {
-                    if (material._babylonData) {
-                        for (const drawMode in material._babylonData) {
-                            const babylonData = material._babylonData[drawMode];
-                            if (babylonData.meshes.length === 0) {
-                                babylonData.material.dispose(false, true);
-                                delete material._babylonData[drawMode];
+                    if (material._data) {
+                        for (const drawMode in material._data) {
+                            const data = material._data[drawMode];
+                            if (data.babylonMeshes.length === 0) {
+                                data.babylonMaterial.dispose(false, true);
+                                delete material._data[drawMode];
                             }
                         }
                     }

+ 181 - 129
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -247,6 +247,10 @@ module BABYLON.GLTF2 {
                 }
 
                 const resultPromise = Promise.all(promises).then(() => {
+                    if (this._rootBabylonMesh) {
+                        this._rootBabylonMesh.setEnabled(true);
+                    }
+
                     this._setState(GLTFLoaderState.READY);
                     this._extensionsOnReady();
 
@@ -374,9 +378,10 @@ module BABYLON.GLTF2 {
 
         private _createRootNode(): Loader.INode {
             this._rootBabylonMesh = new Mesh("__root__", this.babylonScene);
+            this._rootBabylonMesh.setEnabled(false);
 
             const rootNode: Loader.INode = {
-                _babylonMesh: this._rootBabylonMesh,
+                _babylonTransformNode: this._rootBabylonMesh,
                 index: -1
             };
 
@@ -434,19 +439,19 @@ module BABYLON.GLTF2 {
             return Promise.all(promises).then(() => { });
         }
 
-        private _forEachPrimitive(node: Loader.INode, callback: (babylonMesh: Mesh) => void): void {
+        private _forEachPrimitive(node: Loader.INode, callback: (babylonMesh: AbstractMesh) => void): void {
             if (node._primitiveBabylonMeshes) {
                 for (const babylonMesh of node._primitiveBabylonMeshes) {
                     callback(babylonMesh);
                 }
             }
-            else {
-                callback(node._babylonMesh!);
+            else if (node._babylonTransformNode instanceof AbstractMesh) {
+                callback(node._babylonTransformNode);
             }
         }
 
-        private _getMeshes(): Mesh[] {
-            const meshes = new Array<Mesh>();
+        private _getMeshes(): AbstractMesh[] {
+            const meshes = new Array<AbstractMesh>();
 
             // Root mesh is always first.
             meshes.push(this._rootBabylonMesh);
@@ -454,15 +459,9 @@ module BABYLON.GLTF2 {
             const nodes = this.gltf.nodes;
             if (nodes) {
                 for (const node of nodes) {
-                    if (node._babylonMesh) {
-                        meshes.push(node._babylonMesh);
-                    }
-
-                    if (node._primitiveBabylonMeshes) {
-                        for (const babylonMesh of node._primitiveBabylonMeshes) {
-                            meshes.push(babylonMesh);
-                        }
-                    }
+                    this._forEachPrimitive(node, (babylonMesh) => {
+                        meshes.push(babylonMesh);
+                    });
                 }
             }
 
@@ -475,8 +474,8 @@ module BABYLON.GLTF2 {
             const skins = this.gltf.skins;
             if (skins) {
                 for (const skin of skins) {
-                    if (skin._babylonSkeleton) {
-                        skeletons.push(skin._babylonSkeleton);
+                    if (skin._data) {
+                        skeletons.push(skin._data.babylonSkeleton);
                     }
                 }
             }
@@ -533,13 +532,13 @@ module BABYLON.GLTF2 {
          * @param assign A function called synchronously after parsing the glTF properties
          * @returns A promise that resolves with the loaded Babylon mesh when the load is complete
          */
-        public loadNodeAsync(context: string, node: Loader.INode, assign: (babylonMesh: Mesh) => void = () => { }): Promise<Mesh> {
+        public loadNodeAsync(context: string, node: Loader.INode, assign: (babylonTransformNode: TransformNode) => void = () => { }): Promise<TransformNode> {
             const extensionPromise = this._extensionsLoadNodeAsync(context, node, assign);
             if (extensionPromise) {
                 return extensionPromise;
             }
 
-            if (node._babylonMesh) {
+            if (node._babylonTransformNode) {
                 throw new Error(`${context}: Invalid recursive node hierarchy`);
             }
 
@@ -547,72 +546,86 @@ module BABYLON.GLTF2 {
 
             this.logOpen(`${context} ${node.name || ""}`);
 
-            const babylonMesh = new Mesh(node.name || `node${node.index}`, this.babylonScene);
-            node._babylonMesh = babylonMesh;
+            const loadNode = (babylonTransformNode: TransformNode) => {
+                GLTFLoader._LoadTransform(node, babylonTransformNode);
 
-            babylonMesh.setEnabled(false);
-            GLTFLoader._LoadTransform(node, babylonMesh);
+                if (node.camera != undefined) {
+                    const camera = ArrayItem.Get(`${context}/camera`, this.gltf.cameras, node.camera);
+                    promises.push(this.loadCameraAsync(`#/cameras/${camera.index}`, camera, (babylonCamera) => {
+                        babylonCamera.parent = babylonTransformNode;
+                    }));
+                }
 
-            if (node.mesh != undefined) {
-                const mesh = ArrayItem.Get(`${context}/mesh`, this.gltf.meshes, node.mesh);
-                promises.push(this._loadMeshAsync(`#/meshes/${mesh.index}`, node, mesh, babylonMesh));
-            }
+                if (node.children) {
+                    for (const index of node.children) {
+                        const childNode = ArrayItem.Get(`${context}/children/${index}`, this.gltf.nodes, index);
+                        promises.push(this.loadNodeAsync(`#/nodes/${node.index}`, childNode, (childBabylonMesh) => {
+                            // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
+                            if (childNode.skin != undefined) {
+                                childBabylonMesh.parent = this._rootBabylonMesh;
+                                return;
+                            }
 
-            if (node.camera != undefined) {
-                const camera = ArrayItem.Get(`${context}/camera`, this.gltf.cameras, node.camera);
-                promises.push(this.loadCameraAsync(`#/cameras/${camera.index}`, camera, (babylonCamera) => {
-                    babylonCamera.parent = babylonMesh;
-                }));
-            }
+                            childBabylonMesh.parent = babylonTransformNode;
+                        }));
+                    }
+                }
 
-            if (node.children) {
-                for (const index of node.children) {
-                    const childNode = ArrayItem.Get(`${context}/children/${index}`, this.gltf.nodes, index);
-                    promises.push(this.loadNodeAsync(`#/nodes/${node.index}`, childNode, (childBabylonMesh) => {
-                        // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
-                        if (childNode.skin != undefined) {
-                            childBabylonMesh.parent = this._rootBabylonMesh;
-                            return;
-                        }
+                assign(babylonTransformNode);
+            };
 
-                        childBabylonMesh.parent = babylonMesh;
-                    }));
-                }
+            if (node.mesh == undefined) {
+                const nodeName = node.name || `node${node.index}`;
+                node._babylonTransformNode = new TransformNode(nodeName, this.babylonScene);
+                loadNode(node._babylonTransformNode);
+            }
+            else {
+                const mesh = ArrayItem.Get(`${context}/mesh`, this.gltf.meshes, node.mesh);
+                promises.push(this._loadMeshAsync(`#/meshes/${mesh.index}`, node, mesh, loadNode));
             }
-
-            assign(babylonMesh);
-            this._parent.onMeshLoadedObservable.notifyObservers(babylonMesh);
 
             this.logClose();
 
             return Promise.all(promises).then(() => {
-                babylonMesh.setEnabled(true);
-                return babylonMesh;
+                this._forEachPrimitive(node, (babylonMesh) => {
+                    babylonMesh.refreshBoundingInfo(true);
+                });
+
+                return node._babylonTransformNode!;
             });
         }
 
-        private _loadMeshAsync(context: string, node: Loader.INode, mesh: Loader.IMesh, babylonMesh: Mesh): Promise<void> {
+        private _loadMeshAsync(context: string, node: Loader.INode, mesh: Loader.IMesh, assign: (babylonTransformNode: TransformNode) => void): Promise<TransformNode> {
+            const primitives = mesh.primitives;
+            if (!primitives || !primitives.length) {
+                throw new Error(`${context}: Primitives are missing`);
+            }
+
+            if (primitives[0].index == undefined) {
+                ArrayItem.Assign(primitives);
+            }
+
             const promises = new Array<Promise<any>>();
 
             this.logOpen(`${context} ${mesh.name || ""}`);
 
-            const primitives = mesh.primitives;
-            if (!primitives || primitives.length === 0) {
-                throw new Error(`${context}: Primitives are missing`);
-            }
+            const name = node.name || `node${node.index}`;
 
-            ArrayItem.Assign(primitives);
             if (primitives.length === 1) {
-                const primitive = primitives[0];
-                promises.push(this._loadMeshPrimitiveAsync(`${context}/primitives/${primitive.index}`, node, mesh, primitive, babylonMesh));
+                const primitive = mesh.primitives[0];
+                promises.push(this._loadMeshPrimitiveAsync(`${context}/primitives/${primitive.index}}`, name, node, mesh, primitive, (babylonMesh) => {
+                    node._babylonTransformNode = babylonMesh;
+                }));
             }
             else {
-                node._primitiveBabylonMeshes = [];
+                const babylonTransformNode = new TransformNode(name, this.babylonScene);
+                node._babylonTransformNode = babylonTransformNode;
                 for (const primitive of primitives) {
-                    const primitiveBabylonMesh = new Mesh(`${mesh.name || babylonMesh.name}_${primitive.index}`, this.babylonScene, babylonMesh);
-                    node._primitiveBabylonMeshes.push(primitiveBabylonMesh);
-                    promises.push(this._loadMeshPrimitiveAsync(`${context}/primitives/${primitive.index}`, node, mesh, primitive, primitiveBabylonMesh));
-                    this._parent.onMeshLoadedObservable.notifyObservers(babylonMesh);
+                    promises.push(this._loadMeshPrimitiveAsync(`${context}/primitives/${primitive.index}}`, `${name}_primitive${primitive.index}`, node, mesh, primitive, (babylonMesh) => {
+                        babylonMesh.parent = babylonTransformNode;
+                        node._primitiveBabylonMeshes = node._primitiveBabylonMeshes || [];
+                        node._primitiveBabylonMeshes.push(babylonMesh);
+                    }));
                 }
             }
 
@@ -621,47 +634,77 @@ module BABYLON.GLTF2 {
                 promises.push(this._loadSkinAsync(`#/skins/${skin.index}`, node, skin));
             }
 
+            assign(node._babylonTransformNode!);
+
             this.logClose();
 
             return Promise.all(promises).then(() => {
-                this._forEachPrimitive(node, (babylonMesh) => {
-                    babylonMesh._refreshBoundingInfo(true);
-                });
+                return node._babylonTransformNode!;
             });
         }
 
-        private _loadMeshPrimitiveAsync(context: string, node: Loader.INode, mesh: Loader.IMesh, primitive: Loader.IMeshPrimitive, babylonMesh: Mesh): Promise<void> {
-            const promises = new Array<Promise<any>>();
-
+        private _loadMeshPrimitiveAsync(context: string, name: string, node: Loader.INode, mesh: Loader.IMesh, primitive: Loader.IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh> {
             this.logOpen(`${context}`);
 
-            this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
-            promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then((babylonGeometry) => {
-                return this._loadMorphTargetsAsync(context, primitive, babylonMesh, babylonGeometry).then(() => {
-                    babylonGeometry.applyToMesh(babylonMesh);
-                });
-            }));
+            const canInstance = (node.skin == undefined && !mesh.primitives[0].targets);
 
-            const babylonDrawMode = GLTFLoader._GetDrawMode(context, primitive.mode);
-            if (primitive.material == undefined) {
-                let babylonMaterial = this._defaultBabylonMaterialData[babylonDrawMode];
-                if (!babylonMaterial) {
-                    babylonMaterial = this._createDefaultMaterial("__gltf_default", babylonDrawMode);
-                    this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
-                    this._defaultBabylonMaterialData[babylonDrawMode] = babylonMaterial;
-                }
-                babylonMesh.material = babylonMaterial;
+            let babylonAbstractMesh: AbstractMesh;
+            let promise: Promise<any>;
+
+            const instanceData = primitive._instanceData;
+            if (canInstance && instanceData) {
+                babylonAbstractMesh = instanceData.babylonSourceMesh.createInstance(name);
+                promise = instanceData.promise;
             }
             else {
-                const material = ArrayItem.Get(`${context}/material`, this.gltf.materials, primitive.material);
-                promises.push(this._loadMaterialAsync(`#/materials/${material.index}`, material, babylonMesh, babylonDrawMode, (babylonMaterial) => {
-                    babylonMesh.material = babylonMaterial;
+                const promises = new Array<Promise<any>>();
+
+                const babylonMesh = new Mesh(name, this.babylonScene);
+
+                this._createMorphTargets(context, node, mesh, primitive, babylonMesh);
+                promises.push(this._loadVertexDataAsync(context, primitive, babylonMesh).then((babylonGeometry) => {
+                    return this._loadMorphTargetsAsync(context, primitive, babylonMesh, babylonGeometry).then(() => {
+                        babylonGeometry.applyToMesh(babylonMesh);
+                    });
                 }));
+
+                const babylonDrawMode = GLTFLoader._GetDrawMode(context, primitive.mode);
+                if (primitive.material == undefined) {
+                    let babylonMaterial = this._defaultBabylonMaterialData[babylonDrawMode];
+                    if (!babylonMaterial) {
+                        babylonMaterial = this._createDefaultMaterial("__gltf_default", babylonDrawMode);
+                        this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
+                        this._defaultBabylonMaterialData[babylonDrawMode] = babylonMaterial;
+                    }
+                    babylonMesh.material = babylonMaterial;
+                }
+                else {
+                    const material = ArrayItem.Get(`${context}/material`, this.gltf.materials, primitive.material);
+                    promises.push(this._loadMaterialAsync(`#/materials/${material.index}`, material, babylonMesh, babylonDrawMode, (babylonMaterial) => {
+                        babylonMesh.material = babylonMaterial;
+                    }));
+                }
+
+                promise = Promise.all(promises);
+
+                if (canInstance) {
+                    primitive._instanceData = {
+                        babylonSourceMesh: babylonMesh,
+                        promise: promise
+                    };
+                }
+
+                babylonAbstractMesh = babylonMesh;
             }
 
+            this._parent.onMeshLoadedObservable.notifyObservers(babylonAbstractMesh);
+            assign(babylonAbstractMesh);
+
             this.logClose();
 
-            return Promise.all(promises).then(() => { });
+            return promise.then(() => {
+                return babylonAbstractMesh;
+            });
         }
 
         private _loadVertexDataAsync(context: string, primitive: Loader.IMeshPrimitive, babylonMesh: Mesh): Promise<Geometry> {
@@ -815,6 +858,12 @@ module BABYLON.GLTF2 {
         }
 
         private static _LoadTransform(node: Loader.INode, babylonNode: TransformNode): void {
+            // Ignore the TRS of skinned nodes.
+            // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
+            if (node.skin != undefined) {
+                return;
+            }
+
             let position = Vector3.Zero();
             let rotation = Quaternion.Identity();
             let scaling = Vector3.One();
@@ -839,53 +888,54 @@ module BABYLON.GLTF2 {
                 this._forEachPrimitive(node, (babylonMesh) => {
                     babylonMesh.skeleton = skeleton;
                 });
-
-                // Ignore the TRS of skinned nodes.
-                // See https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#skins (second implementation note)
-                node._babylonMesh!.position = Vector3.Zero();
-                node._babylonMesh!.rotationQuaternion = Quaternion.Identity();
-                node._babylonMesh!.scaling = Vector3.One();
             };
 
-            if (skin._promise) {
-                return skin._promise.then(() => {
-                    assignSkeleton(skin._babylonSkeleton!);
+            if (skin._data) {
+                const data = skin._data;
+                return data.promise.then(() => {
+                    assignSkeleton(data.babylonSkeleton);
                 });
             }
 
             const skeletonId = `skeleton${skin.index}`;
             const babylonSkeleton = new Skeleton(skin.name || skeletonId, skeletonId, this.babylonScene);
-            skin._babylonSkeleton = babylonSkeleton;
-            this._loadBones(context, skin);
+            this._loadBones(context, skin, babylonSkeleton);
             assignSkeleton(babylonSkeleton);
 
-            return (skin._promise = this._loadSkinInverseBindMatricesDataAsync(context, skin).then((inverseBindMatricesData) => {
+            const promise = this._loadSkinInverseBindMatricesDataAsync(context, skin).then((inverseBindMatricesData) => {
                 this._updateBoneMatrices(babylonSkeleton, inverseBindMatricesData);
-            }));
+            });
+
+            skin._data = {
+                babylonSkeleton: babylonSkeleton,
+                promise: promise
+            };
+
+            return promise;
         }
 
-        private _loadBones(context: string, skin: Loader.ISkin): void {
+        private _loadBones(context: string, skin: Loader.ISkin, babylonSkeleton: Skeleton): void {
             const babylonBones: { [index: number]: Bone } = {};
             for (const index of skin.joints) {
                 const node = ArrayItem.Get(`${context}/joints/${index}`, this.gltf.nodes, index);
-                this._loadBone(node, skin, babylonBones);
+                this._loadBone(node, skin, babylonSkeleton, babylonBones);
             }
         }
 
-        private _loadBone(node: Loader.INode, skin: Loader.ISkin, babylonBones: { [index: number]: Bone }): Bone {
+        private _loadBone(node: Loader.INode, skin: Loader.ISkin, babylonSkeleton: Skeleton, babylonBones: { [index: number]: Bone }): Bone {
             let babylonBone = babylonBones[node.index];
             if (babylonBone) {
                 return babylonBone;
             }
 
             let babylonParentBone: Nullable<Bone> = null;
-            if (node.parent && node.parent._babylonMesh !== this._rootBabylonMesh) {
-                babylonParentBone = this._loadBone(node.parent, skin, babylonBones);
+            if (node.parent && node.parent._babylonTransformNode !== this._rootBabylonMesh) {
+                babylonParentBone = this._loadBone(node.parent, skin, babylonSkeleton, babylonBones);
             }
 
             const boneIndex = skin.joints.indexOf(node.index);
 
-            babylonBone = new Bone(node.name || `joint${node.index}`, skin._babylonSkeleton!, babylonParentBone, this._getNodeMatrix(node), null, null, boneIndex);
+            babylonBone = new Bone(node.name || `joint${node.index}`, babylonSkeleton, babylonParentBone, this._getNodeMatrix(node), null, null, boneIndex);
             babylonBones[node.index] = babylonBone;
 
             node._babylonBones = node._babylonBones || [];
@@ -1045,7 +1095,7 @@ module BABYLON.GLTF2 {
 
             // Ignore animations that have no animation targets.
             if ((channel.target.path === AnimationChannelTargetPath.WEIGHTS && !targetNode._numMorphTargets) ||
-                (channel.target.path !== AnimationChannelTargetPath.WEIGHTS && !targetNode._babylonMesh)) {
+                (channel.target.path !== AnimationChannelTargetPath.WEIGHTS && !targetNode._babylonTransformNode)) {
                 return Promise.resolve();
             }
 
@@ -1168,7 +1218,7 @@ module BABYLON.GLTF2 {
                             outTangent: key.outTangent ? key.outTangent[targetIndex] : undefined
                         })));
 
-                        this._forEachPrimitive(targetNode, (babylonMesh) => {
+                        this._forEachPrimitive(targetNode, (babylonMesh: Mesh) => {
                             const morphTarget = babylonMesh.morphTargetManager!.getTarget(targetIndex);
                             const babylonAnimationClone = babylonAnimation.clone();
                             morphTarget.animations.push(babylonAnimationClone);
@@ -1181,16 +1231,18 @@ module BABYLON.GLTF2 {
                     const babylonAnimation = new Animation(animationName, targetPath, 1, animationType);
                     babylonAnimation.setKeys(keys);
 
-                    if (targetNode._babylonBones) {
-                        const babylonAnimationTargets = [targetNode._babylonMesh!, ...targetNode._babylonBones];
+                    const babylonTransformNode = targetNode._babylonTransformNode!;
+                    const babylonBones = targetNode._babylonBones;
+                    if (babylonBones) {
+                        const babylonAnimationTargets = [babylonTransformNode, ...babylonBones];
                         for (const babylonAnimationTarget of babylonAnimationTargets) {
                             babylonAnimationTarget.animations.push(babylonAnimation);
                         }
                         babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonAnimationTargets);
                     }
                     else {
-                        targetNode._babylonMesh!.animations.push(babylonAnimation);
-                        babylonAnimationGroup.addTargetedAnimation(babylonAnimation, targetNode._babylonMesh);
+                        babylonTransformNode.animations.push(babylonAnimation);
+                        babylonAnimationGroup.addTargetedAnimation(babylonAnimation, babylonTransformNode);
                     }
                 }
             });
@@ -1429,39 +1481,39 @@ module BABYLON.GLTF2 {
                 return extensionPromise;
             }
 
-            material._babylonData = material._babylonData || {};
-            let babylonData = material._babylonData[babylonDrawMode];
+            material._data = material._data || {};
+            let babylonData = material._data[babylonDrawMode];
             if (!babylonData) {
                 this.logOpen(`${context} ${material.name || ""}`);
 
                 const babylonMaterial = this.createMaterial(context, material, babylonDrawMode);
 
                 babylonData = {
-                    material: babylonMaterial,
-                    meshes: [],
+                    babylonMaterial: babylonMaterial,
+                    babylonMeshes: [],
                     promise: this.loadMaterialPropertiesAsync(context, material, babylonMaterial)
                 };
 
-                material._babylonData[babylonDrawMode] = babylonData;
+                material._data[babylonDrawMode] = babylonData;
 
                 this._parent.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
 
                 this.logClose();
             }
 
-            babylonData.meshes.push(babylonMesh);
+            babylonData.babylonMeshes.push(babylonMesh);
 
             babylonMesh.onDisposeObservable.addOnce(() => {
-                const index = babylonData.meshes.indexOf(babylonMesh);
+                const index = babylonData.babylonMeshes.indexOf(babylonMesh);
                 if (index !== -1) {
-                    babylonData.meshes.splice(index, 1);
+                    babylonData.babylonMeshes.splice(index, 1);
                 }
             });
 
-            assign(babylonData.material);
+            assign(babylonData.babylonMaterial);
 
             return babylonData.promise.then(() => {
-                return babylonData.material;
+                return babylonData.babylonMaterial;
             });
         }
 
@@ -1914,14 +1966,14 @@ module BABYLON.GLTF2 {
 
             if (this.gltf.materials) {
                 for (const material of this.gltf.materials) {
-                    if (material._babylonData) {
-                        for (const babylonDrawMode in material._babylonData) {
-                            const babylonData = material._babylonData[babylonDrawMode];
-                            for (const babylonMesh of babylonData.meshes) {
+                    if (material._data) {
+                        for (const babylonDrawMode in material._data) {
+                            const babylonData = material._data[babylonDrawMode];
+                            for (const babylonMesh of babylonData.babylonMeshes) {
                                 // Ensure nonUniformScaling is set if necessary.
                                 babylonMesh.computeWorldMatrix(true);
 
-                                const babylonMaterial = babylonData.material;
+                                const babylonMaterial = babylonData.babylonMaterial;
                                 promises.push(babylonMaterial.forceCompilationAsync(babylonMesh));
                                 if (this._parent.useClipPlane) {
                                     promises.push(babylonMaterial.forceCompilationAsync(babylonMesh, { clipPlane: true }));
@@ -2002,7 +2054,7 @@ module BABYLON.GLTF2 {
             return this._applyExtensions(scene, (extension) => extension.loadSceneAsync && extension.loadSceneAsync(context, scene));
         }
 
-        private _extensionsLoadNodeAsync(context: string, node: Loader.INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>> {
+        private _extensionsLoadNodeAsync(context: string, node: Loader.INode, assign: (babylonTransformNode: TransformNode) => void): Nullable<Promise<TransformNode>> {
             return this._applyExtensions(node, (extension) => extension.loadNodeAsync && extension.loadNodeAsync(context, node, assign));
         }
 

+ 2 - 2
loaders/src/glTF/2.0/babylon.glTFLoaderExtension.ts

@@ -28,9 +28,9 @@ module BABYLON.GLTF2 {
          * @param context The context when loading the asset
          * @param node The glTF node property
          * @param assign A function called synchronously after parsing the glTF properties
-         * @returns A promise that resolves with the loaded Babylon mesh when the load is complete or null if not handled
+         * @returns A promise that resolves with the loaded Babylon transform node when the load is complete or null if not handled
          */
-        loadNodeAsync?(context: string, node: Loader.INode, assign: (babylonMesh: Mesh) => void): Nullable<Promise<Mesh>>;
+        loadNodeAsync?(context: string, node: Loader.INode, assign: (babylonMesh: TransformNode) => void): Nullable<Promise<TransformNode>>;
 
         /**
          * Define this method to modify the default behavior when loading cameras.

+ 15 - 10
loaders/src/glTF/2.0/babylon.glTFLoaderInterfaces.ts

@@ -117,10 +117,10 @@ module BABYLON.GLTF2.Loader {
         emissiveTexture?: ITextureInfo;
 
         /** @hidden */
-        _babylonData?: {
-            [drawMode: number]: {
-                material: Material;
-                meshes: AbstractMesh[];
+        _data?: {
+            [babylonDrawMode: number]: {
+                babylonMaterial: Material;
+                babylonMeshes: AbstractMesh[];
                 promise: Promise<void>;
             }
         };
@@ -137,6 +137,11 @@ module BABYLON.GLTF2.Loader {
      * Loader interface with additional members.
      */
     export interface IMeshPrimitive extends GLTF2.IMeshPrimitive, IArrayItem {
+        /** @hidden */
+        _instanceData?: {
+            babylonSourceMesh: Mesh;
+            promise: Promise<any>;
+        };
     }
 
     /**
@@ -149,10 +154,10 @@ module BABYLON.GLTF2.Loader {
         parent?: INode;
 
         /** @hidden */
-        _babylonMesh?: Mesh;
+        _babylonTransformNode?: TransformNode;
 
         /** @hidden */
-        _primitiveBabylonMeshes?: Mesh[];
+        _primitiveBabylonMeshes?: AbstractMesh[];
 
         /** @hidden */
         _babylonBones?: Bone[];
@@ -188,10 +193,10 @@ module BABYLON.GLTF2.Loader {
      */
     export interface ISkin extends GLTF2.ISkin, IArrayItem {
         /** @hidden */
-        _babylonSkeleton?: Skeleton;
-
-        /** @hidden */
-        _promise?: Promise<void>;
+        _data?: {
+            babylonSkeleton: Skeleton;
+            promise: Promise<void>;
+        };
     }
 
     /**

+ 1 - 1
package.json

@@ -9,7 +9,7 @@
     ],
     "name": "babylonjs",
     "description": "Babylon.js is a JavaScript 3D engine based on webgl.",
-    "version": "4.0.0-alpha.4",
+    "version": "4.0.0-alpha.6",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 23 - 23
src/Audio/babylon.sound.ts

@@ -97,7 +97,7 @@ module BABYLON {
         private _coneOuterAngle: number = 360;
         private _coneOuterGain: number = 0;
         private _scene: Scene;
-        private _connectedMesh: Nullable<AbstractMesh>;
+        private _connectedTransformNode: Nullable<TransformNode>;
         private _customAttenuationFunction: (currentVolume: number, currentDistance: number, maxDistance: number, refDistance: number, rolloffFactor: number) => number;
         private _registerFunc: Nullable<(connectedMesh: TransformNode) => void>;
         private _isOutputConnected = false;
@@ -334,9 +334,9 @@ module BABYLON {
                     this._streamingSource.disconnect();
                 }
 
-                if (this._connectedMesh && this._registerFunc) {
-                    this._connectedMesh.unregisterAfterWorldMatrixUpdate(this._registerFunc);
-                    this._connectedMesh = null;
+                if (this._connectedTransformNode && this._registerFunc) {
+                    this._connectedTransformNode.unregisterAfterWorldMatrixUpdate(this._registerFunc);
+                    this._connectedTransformNode = null;
                 }
             }
         }
@@ -560,17 +560,17 @@ module BABYLON {
         public setLocalDirectionToMesh(newLocalDirection: Vector3): void {
             this._localDirection = newLocalDirection;
 
-            if (Engine.audioEngine.canUseWebAudio && this._connectedMesh && this.isPlaying) {
+            if (Engine.audioEngine.canUseWebAudio && this._connectedTransformNode && this.isPlaying) {
                 this._updateDirection();
             }
         }
 
         private _updateDirection() {
-            if (!this._connectedMesh || !this._soundPanner) {
+            if (!this._connectedTransformNode || !this._soundPanner) {
                 return;
             }
 
-            var mat = this._connectedMesh.getWorldMatrix();
+            var mat = this._connectedTransformNode.getWorldMatrix();
             var direction = Vector3.TransformNormal(this._localDirection, mat);
             direction.normalize();
             this._soundPanner.setOrientation(direction.x, direction.y, direction.z);
@@ -578,8 +578,8 @@ module BABYLON {
 
         /** @hidden */
         public updateDistanceFromListener() {
-            if (Engine.audioEngine.canUseWebAudio && this._connectedMesh && this.useCustomAttenuation && this._soundGain && this._scene.activeCamera) {
-                var distance = this._connectedMesh.getDistanceToCamera(this._scene.activeCamera);
+            if (Engine.audioEngine.canUseWebAudio && this._connectedTransformNode && this.useCustomAttenuation && this._soundGain && this._scene.activeCamera) {
+                var distance = this._connectedTransformNode.getDistanceToCamera(this._scene.activeCamera);
                 this._soundGain.gain.value = this._customAttenuationFunction(this._volume, distance, this.maxDistance, this.refDistance, this.rolloffFactor);
             }
         }
@@ -615,7 +615,7 @@ module BABYLON {
                                 this._soundPanner.coneInnerAngle = this._coneInnerAngle;
                                 this._soundPanner.coneOuterAngle = this._coneOuterAngle;
                                 this._soundPanner.coneOuterGain = this._coneOuterGain;
-                                if (this._connectedMesh) {
+                                if (this._connectedTransformNode) {
                                     this._updateDirection();
                                 }
                                 else {
@@ -782,15 +782,15 @@ module BABYLON {
 
         /**
          * Attach the sound to a dedicated mesh
-         * @param meshToConnectTo The mesh to connect the sound with
+         * @param transformNode The transform node to connect the sound with
          * @see http://doc.babylonjs.com/how_to/playing_sounds_and_music#attaching-a-sound-to-a-mesh
          */
-        public attachToMesh(meshToConnectTo: AbstractMesh): void {
-            if (this._connectedMesh && this._registerFunc) {
-                this._connectedMesh.unregisterAfterWorldMatrixUpdate(this._registerFunc);
+        public attachToMesh(transformNode: TransformNode): void {
+            if (this._connectedTransformNode && this._registerFunc) {
+                this._connectedTransformNode.unregisterAfterWorldMatrixUpdate(this._registerFunc);
                 this._registerFunc = null;
             }
-            this._connectedMesh = meshToConnectTo;
+            this._connectedTransformNode = transformNode;
             if (!this.spatialSound) {
                 this.spatialSound = true;
                 this._createSpatialParameters();
@@ -799,9 +799,9 @@ module BABYLON {
                     this.play();
                 }
             }
-            this._onRegisterAfterWorldMatrixUpdate(this._connectedMesh);
-            this._registerFunc = (connectedMesh: TransformNode) => this._onRegisterAfterWorldMatrixUpdate(connectedMesh);
-            meshToConnectTo.registerAfterWorldMatrixUpdate(this._registerFunc);
+            this._onRegisterAfterWorldMatrixUpdate(this._connectedTransformNode);
+            this._registerFunc = (transformNode: TransformNode) => this._onRegisterAfterWorldMatrixUpdate(transformNode);
+            this._connectedTransformNode.registerAfterWorldMatrixUpdate(this._registerFunc);
         }
 
         /**
@@ -809,10 +809,10 @@ module BABYLON {
          * @see http://doc.babylonjs.com/how_to/playing_sounds_and_music#attaching-a-sound-to-a-mesh
          */
         public detachFromMesh() {
-            if (this._connectedMesh && this._registerFunc) {
-                this._connectedMesh.unregisterAfterWorldMatrixUpdate(this._registerFunc);
+            if (this._connectedTransformNode && this._registerFunc) {
+                this._connectedTransformNode.unregisterAfterWorldMatrixUpdate(this._registerFunc);
                 this._registerFunc = null;
-                this._connectedMesh = null;
+                this._connectedTransformNode = null;
             }
         }
 
@@ -905,8 +905,8 @@ module BABYLON {
             };
 
             if (this.spatialSound) {
-                if (this._connectedMesh) {
-                    serializationObject.connectedMeshId = this._connectedMesh.id;
+                if (this._connectedTransformNode) {
+                    serializationObject.connectedMeshId = this._connectedTransformNode.id;
                 }
 
                 serializationObject.position = this._position.asArray();

+ 1 - 1
src/Bones/babylon.bone.ts

@@ -73,7 +73,7 @@ module BABYLON {
              */
             public name: string, skeleton: Skeleton, parentBone: Nullable<Bone> = null, localMatrix: Nullable<Matrix> = null,
             restPose: Nullable<Matrix> = null, baseMatrix: Nullable<Matrix> = null, index: Nullable<number> = null) {
-            super(name, skeleton.getScene());
+            super(name, skeleton.getScene(), false);
             this._skeleton = skeleton;
             this._localMatrix = localMatrix ? localMatrix.clone() : Matrix.Identity();
             this._restPose = restPose ? restPose : this._localMatrix.clone();

+ 19 - 6
src/Cameras/VR/babylon.vrExperienceHelper.ts

@@ -197,17 +197,18 @@ module BABYLON {
                 });
             };
             makeNotPick(mesh);
-            var childMeshes = mesh.getChildMeshes();
+            var meshChildren = mesh.getChildren(undefined, false);
 
+            let laserParent: TransformNode = mesh;
             this.webVRController._pointingPoseNode = null;
-            for (var i = 0; i < childMeshes.length; i++) {
-                if (childMeshes[i].name && childMeshes[i].name.indexOf(PoseEnabledController.POINTING_POSE) >= 0) {
-                    mesh = childMeshes[i];
-                    this.webVRController._pointingPoseNode = mesh;
+            for (var i = 0; i < meshChildren.length; i++) {
+                if (meshChildren[i].name && meshChildren[i].name.indexOf(PoseEnabledController.POINTING_POSE) >= 0) {
+                    laserParent = <TransformNode>meshChildren[i];
+                    this.webVRController._pointingPoseNode = laserParent;
                     break;
                 }
             }
-            this._laserPointer.parent = mesh;
+            this._laserPointer.parent = laserParent;
         }
 
         public _updatePointerDistance(distance: number = 100) {
@@ -875,6 +876,18 @@ module BABYLON {
 
             if (this._scene.activeCamera) {
                 this._position = this._scene.activeCamera.position.clone();
+
+                if (this.vrDeviceOrientationCamera) {
+                    this.vrDeviceOrientationCamera.rotation = BABYLON.Quaternion.FromRotationMatrix(this._scene.activeCamera.getWorldMatrix().getRotationMatrix()).toEulerAngles();
+                }
+                if (this.webVRCamera) {
+                    var currentYRotation = this.webVRCamera.deviceRotationQuaternion.toEulerAngles().y;
+                    var desiredYRotation = BABYLON.Quaternion.FromRotationMatrix(this._scene.activeCamera.getWorldMatrix().getRotationMatrix()).toEulerAngles().y;
+                    var delta = desiredYRotation - currentYRotation;
+                    var currentGlobalRotation = this.webVRCamera.rotationQuaternion.toEulerAngles().y;
+                    this.webVRCamera.rotationQuaternion = Quaternion.FromEulerAngles(0, currentGlobalRotation + delta, 0);
+                }
+
                 // make sure that we return to the last active camera
                 this._existingCamera = this._scene.activeCamera;
             }

+ 2 - 0
src/Cameras/XR/babylon.webXRCamera.ts

@@ -18,6 +18,7 @@ module BABYLON {
             this.minZ = 0;
             this.rotationQuaternion = new BABYLON.Quaternion();
             this.cameraRigMode = BABYLON.Camera.RIG_MODE_CUSTOM;
+            this.updateUpVectorFromRotation = true;
             this._updateNumberOfRigCameras(1);
         }
 
@@ -26,6 +27,7 @@ module BABYLON {
                 var newCamera = new BABYLON.TargetCamera("view: " + this.rigCameras.length, BABYLON.Vector3.Zero(), this.getScene());
                 newCamera.minZ = 0;
                 newCamera.parent = this;
+                newCamera.rotationQuaternion = new BABYLON.Quaternion();
                 this.rigCameras.push(newCamera);
             }
             while (this.rigCameras.length > viewCount) {

+ 12 - 1
src/Cameras/XR/babylon.webXREnterExitUI.ts

@@ -44,13 +44,21 @@ module BABYLON {
         private _buttons: Array<WebXREnterExitUIButton> = [];
         private _activeButton: Nullable<WebXREnterExitUIButton> = null;
         /**
+         * Fired every time the active button is changed.
+         *
+         * When xr is entered via a button that launches xr that button will be the callback parameter
+         *
+         * When exiting xr the callback parameter will be null)
+         */
+        public activeButtonChangedObservable = new BABYLON.Observable<Nullable<WebXREnterExitUIButton>>();
+        /**
          * Creates UI to allow the user to enter/exit XR mode
          * @param scene the scene to add the ui to
          * @param helper the xr experience helper to enter/exit xr with
          * @param options options to configure the UI
          * @returns the created ui
          */
-        public static CreateAsync(scene: BABYLON.Scene, helper: WebXRExperienceHelper, options: WebXREnterExitUIOptions) {
+        public static CreateAsync(scene: BABYLON.Scene, helper: WebXRExperienceHelper, options: WebXREnterExitUIOptions): Promise<WebXREnterExitUI> {
             var ui = new WebXREnterExitUI(scene, options);
             var supportedPromises = ui._buttons.map((btn) => {
                 return helper.supportsSessionAsync(btn.initializationOptions);
@@ -76,6 +84,7 @@ module BABYLON {
                         };
                     }
                 });
+                return ui;
             });
         }
 
@@ -120,6 +129,7 @@ module BABYLON {
             this._buttons.forEach((b) => {
                 b.update(this._activeButton);
             });
+            this.activeButtonChangedObservable.notifyObservers(this._activeButton);
         }
 
         /**
@@ -130,6 +140,7 @@ module BABYLON {
             if (renderCanvas && renderCanvas.parentNode && renderCanvas.parentNode.contains(this._overlay)) {
                 renderCanvas.parentNode.removeChild(this._overlay);
             }
+            this.activeButtonChangedObservable.clear();
         }
     }
 }

+ 29 - 0
src/Cameras/XR/babylon.webXRExperienceHelper.ts

@@ -44,6 +44,8 @@ module BABYLON {
             this.onStateChangedObservable.notifyObservers(this.state);
         }
 
+        private static _TmpVector = new BABYLON.Vector3();
+
         /**
          * Fires when the state of the experience helper has changed
          */
@@ -80,6 +82,7 @@ module BABYLON {
             this.camera = new BABYLON.WebXRCamera("", scene);
             this._sessionManager = new BABYLON.WebXRSessionManager(scene);
             this.container = new AbstractMesh("", scene);
+            this.camera.parent = this.container;
         }
 
         /**
@@ -98,6 +101,9 @@ module BABYLON {
          * @returns promise that resolves after xr mode has entered
          */
         public enterXRAsync(sessionCreationOptions: XRSessionCreationOptions, frameOfReference: string) {
+            if (!this._supported) {
+                throw "XR session not supported by this browser";
+            }
             this._setState(WebXRState.ENTERING_XR);
 
             return this._sessionManager.enterXRAsync(sessionCreationOptions, frameOfReference).then(() => {
@@ -140,6 +146,29 @@ module BABYLON {
         }
 
         /**
+         * Updates the global position of the camera by moving the camera's container
+         * This should be used instead of modifying the camera's position as it will be overwritten by an xrSessions's update frame
+         * @param position The desired global position of the camera
+         */
+        public setPositionOfCameraUsingContainer(position: Vector3) {
+            this.camera.globalPosition.subtractToRef(position, WebXRExperienceHelper._TmpVector);
+            this.container.position.subtractInPlace(WebXRExperienceHelper._TmpVector);
+        }
+
+        /**
+         * Rotates the xr camera by rotating the camera's container around the camera's position
+         * This should be used instead of modifying the camera's rotation as it will be overwritten by an xrSessions's update frame
+         * @param rotation the desired quaternion rotation to apply to the camera
+         */
+        public rotateCameraByQuaternionUsingContainer(rotation: Quaternion) {
+            if (!this.container.rotationQuaternion) {
+                this.container.rotationQuaternion = Quaternion.FromEulerVector(this.container.rotation);
+            }
+            this.container.rotationQuaternion.multiplyInPlace(rotation);
+            this.container.position.rotateByQuaternionAroundPointToRef(rotation, this.camera.globalPosition, this.container.position);
+        }
+
+        /**
          * Checks if the creation options are supported by the xr session
          * @param options creation options
          * @returns true if supported

+ 2 - 1
src/Cameras/XR/babylon.webXRSessionManager.ts

@@ -39,7 +39,8 @@ module BABYLON {
          * @returns Promise which resolves after it is initialized
          */
         public initializeAsync(): Promise<void> {
-             // Check if the browser supports webXR
+            Tools.Warn("The WebXR APIs are still under development and are subject to change in the future.");
+            // Check if the browser supports webXR
             this._xrNavigator = navigator;
             if (!this._xrNavigator.xr) {
                 return Promise.reject("webXR not supported by this browser");

+ 16 - 4
src/Cameras/babylon.targetCamera.ts

@@ -14,6 +14,11 @@ module BABYLON {
          * Define the current rotation the camera is rotating to
          */
         public cameraRotation = new Vector2(0, 0);
+        /**
+         * When set, the up vector of the camera will be updated by the rotation of the camera
+         */
+        public updateUpVectorFromRotation = false;
+        private _tmpQuaternion = new Quaternion();
 
         /**
          * Define the current rotation of the camera
@@ -178,7 +183,7 @@ module BABYLON {
 
             this._cache.rotation.copyFrom(this.rotation);
             if (this.rotationQuaternion) {
-                this._cache.rotationQuaternion.copyFrom(this.rotationQuaternion);
+                this._cache.rotationQuaternion.copyFrom(this.rotationQuaternion);
             }
         }
 
@@ -294,10 +299,10 @@ module BABYLON {
                     var limit = (Math.PI / 2) * 0.95;
 
                     if (this.rotation.x > limit) {
-                        this.rotation.x = limit;
+                        this.rotation.x = limit;
                     }
                     if (this.rotation.x < -limit) {
-                        this.rotation.x = -limit;
+                        this.rotation.x = -limit;
                     }
                 }
             }
@@ -373,7 +378,14 @@ module BABYLON {
 
             // Computing target and final matrix
             this.position.addToRef(this._transformedReferencePoint, this._currentTarget);
-
+            if (this.updateUpVectorFromRotation) {
+                if (this.rotationQuaternion) {
+                    Axis.Y.rotateByQuaternionToRef(this.rotationQuaternion, this.upVector);
+                } else {
+                    Quaternion.FromEulerVectorToRef(this.rotation, this._tmpQuaternion);
+                    Axis.Y.rotateByQuaternionToRef(this._tmpQuaternion, this.upVector);
+                }
+            }
             this._computeViewMatrix(this.position, this._currentTarget, this.upVector);
             return this._viewMatrix;
         }

+ 15 - 3
src/Debug/babylon.skeletonViewer.ts

@@ -14,12 +14,20 @@ module BABYLON.Debug {
         private _renderFunction: () => void;
 
         /**
+         * Returns the mesh used to render the bones
+         */
+        public get debugMesh(): Nullable<LinesMesh> {
+            return this._debugMesh;
+        }
+
+        /**
          * Creates a new SkeletonViewer
          * @param skeleton defines the skeleton to render
          * @param mesh defines the mesh attached to the skeleton
          * @param scene defines the hosting scene
          * @param autoUpdateBonesMatrices defines a boolean indicating if bones matrices must be forced to update before rendering (true by default)
          * @param renderingGroupId defines the rendering group id to use with the viewer
+         * @param utilityLayerRenderer defines an optional utility layer to render the helper on
          */
         constructor(
             /** defines the skeleton to render */
@@ -30,7 +38,10 @@ module BABYLON.Debug {
             /** defines a boolean indicating if bones matrices must be forced to update before rendering (true by default)  */
             public autoUpdateBonesMatrices = true,
             /** defines the rendering group id to use with the viewer */
-            public renderingGroupId = 1) {
+            public renderingGroupId = 1,
+            /** defines an optional utility layer to render the helper on */
+            public utilityLayerRenderer?: UtilityLayerRenderer
+        ) {
             this._scene = scene;
 
             this.update();
@@ -131,12 +142,13 @@ module BABYLON.Debug {
             } else {
                 this._getLinesForBonesWithLength(this.skeleton.bones, this.mesh.getWorldMatrix());
             }
+            const targetScene = this.utilityLayerRenderer ? this.utilityLayerRenderer.utilityLayerScene : this._scene;
 
             if (!this._debugMesh) {
-                this._debugMesh = BABYLON.MeshBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: null }, this._scene);
+                this._debugMesh = BABYLON.MeshBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: null }, targetScene);
                 this._debugMesh.renderingGroupId = this.renderingGroupId;
             } else {
-                BABYLON.MeshBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: this._debugMesh }, this._scene);
+                BABYLON.MeshBuilder.CreateLineSystem("", { lines: this._debugLines, updatable: true, instance: this._debugMesh }, targetScene);
             }
             this._debugMesh.position.copyFrom(this.mesh.position);
             this._debugMesh.color = this.color;

+ 16 - 9
src/Engine/babylon.engine.ts

@@ -481,7 +481,7 @@ module BABYLON {
          * Returns the current version of the framework
          */
         public static get Version(): string {
-            return "4.0.0-alpha.4";
+            return "4.0.0-alpha.6";
         }
 
         /**
@@ -1096,8 +1096,10 @@ module BABYLON {
                     this.onCanvasPointerOutObservable.notifyObservers(ev);
                 };
 
-                window.addEventListener("blur", this._onBlur);
-                window.addEventListener("focus", this._onFocus);
+                if (Tools.IsWindowObjectExist()) {
+                    window.addEventListener("blur", this._onBlur);
+                    window.addEventListener("focus", this._onFocus);
+                }
 
                 canvas.addEventListener("pointerout", this._onCanvasPointerOut);
 
@@ -1155,8 +1157,10 @@ module BABYLON {
             }
 
             // Viewport
-            var limitDeviceRatio = options.limitDeviceRatio || window.devicePixelRatio || 1.0;
-            this._hardwareScalingLevel = adaptToDeviceRatio ? 1.0 / Math.min(limitDeviceRatio, window.devicePixelRatio || 1.0) : 1.0;
+            const devicePixelRatio = Tools.IsWindowObjectExist() ? (window.devicePixelRatio || 1.0) : 1.0;
+
+            var limitDeviceRatio = options.limitDeviceRatio || devicePixelRatio;
+            this._hardwareScalingLevel = adaptToDeviceRatio ? 1.0 / Math.min(limitDeviceRatio, devicePixelRatio) : 1.0;
             this.resize();
 
             this._isStencilEnable = options.stencil ? true : false;
@@ -1217,8 +1221,10 @@ module BABYLON {
                     document.exitPointerLock();
                 };
 
-                window.addEventListener('vrdisplaypointerrestricted', this._onVRDisplayPointerRestricted, false);
-                window.addEventListener('vrdisplaypointerunrestricted', this._onVRDisplayPointerUnrestricted, false);
+                if (Tools.IsWindowObjectExist()) {
+                    window.addEventListener('vrdisplaypointerrestricted', this._onVRDisplayPointerRestricted, false);
+                    window.addEventListener('vrdisplaypointerunrestricted', this._onVRDisplayPointerUnrestricted, false);
+                }
             }
 
             // Create Audio Engine if needed.
@@ -4633,13 +4639,14 @@ module BABYLON {
          * @param invertY defines if data must be stored with Y axis inverted
          * @param premulAlpha defines if alpha is stored as premultiplied
          * @param format defines the format of the data
+         * @param forceBindTexture if the texture should be forced to be bound eg. after a graphics context loss (Default: false)
          */
-        public updateDynamicTexture(texture: Nullable<InternalTexture>, canvas: HTMLCanvasElement, invertY: boolean, premulAlpha: boolean = false, format?: number): void {
+        public updateDynamicTexture(texture: Nullable<InternalTexture>, canvas: HTMLCanvasElement, invertY: boolean, premulAlpha: boolean = false, format?: number, forceBindTexture: boolean = false): void {
             if (!texture) {
                 return;
             }
 
-            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture, true);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture, true, forceBindTexture);
             this._unpackFlipY(invertY);
             if (premulAlpha) {
                 this._gl.pixelStorei(this._gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);

+ 1 - 1
src/Gamepad/Controllers/babylon.poseEnabledController.ts

@@ -178,7 +178,7 @@ module BABYLON {
          * Node to be used when casting a ray from the controller
          * @hidden
          */
-        public _pointingPoseNode: Nullable<AbstractMesh> = null;
+        public _pointingPoseNode: Nullable<TransformNode> = null;
         /**
          * Name of the child mesh that can be used to cast a ray from the controller
          */

+ 9 - 9
src/Gamepad/Controllers/babylon.windowsMotionController.ts

@@ -10,7 +10,7 @@ module BABYLON {
         /**
          * Node of the mesh corrisponding to the direction the ray should be cast from the controller
          */
-        public pointingPoseNode: AbstractMesh;
+        public pointingPoseNode: TransformNode;
         /**
          * Map of the button meshes contained in the controller
          */
@@ -32,7 +32,7 @@ module BABYLON {
         /**
          * The mesh
          */
-        value: AbstractMesh;
+        value: TransformNode;
     }
 
     /**
@@ -42,11 +42,11 @@ module BABYLON {
         /**
          * The mesh that should be displayed when pressed
          */
-        pressed: AbstractMesh;
+        pressed: TransformNode;
         /**
          * The mesh that should be displayed when not pressed
          */
-        unpressed: AbstractMesh;
+        unpressed: TransformNode;
     }
 
     /**
@@ -56,11 +56,11 @@ module BABYLON {
         /**
          * The mesh that should be set when at its min
          */
-        min: AbstractMesh;
+        min: TransformNode;
         /**
          * The mesh that should be set when at its max
          */
-        max: AbstractMesh;
+        max: TransformNode;
     }
 
     /**
@@ -483,11 +483,11 @@ module BABYLON {
 
             // Look through all children recursively. This will return null if no mesh exists with the given name.
             function getChildByName(node: Node, name: string) {
-                return node.getChildMeshes(false, (n) => n.name === name)[0];
+                return <TransformNode>node.getChildren((n) => n.name === name, false)[0];
             }
             // Look through only immediate children. This will return null if no mesh exists with the given name.
-            function getImmediateChildByName(node: Node, name: string): AbstractMesh {
-                return node.getChildMeshes(true, (n) => n.name == name)[0];
+            function getImmediateChildByName(node: Node, name: string): TransformNode {
+                return <TransformNode>node.getChildren((n) => n.name == name, true)[0];
             }
         }
 

+ 1 - 1
src/Gizmos/babylon.gizmo.ts

@@ -111,7 +111,7 @@ module BABYLON {
                     this._rootMesh.position.copyFrom(this.attachedMesh.absolutePosition);
                 }
                 if (this._updateScale && this.gizmoLayer.utilityLayerScene.activeCamera && this.attachedMesh) {
-                    var cameraPosition = this.gizmoLayer.utilityLayerScene.activeCamera.position;
+                    var cameraPosition = this.gizmoLayer.utilityLayerScene.activeCamera.globalPosition;
                     if ((<WebVRFreeCamera>this.gizmoLayer.utilityLayerScene.activeCamera).devicePosition) {
                         cameraPosition = (<WebVRFreeCamera>this.gizmoLayer.utilityLayerScene.activeCamera).devicePosition;
                     }

+ 1 - 0
src/Materials/Textures/babylon.internalTexture.ts

@@ -311,6 +311,7 @@ module BABYLON {
                 case InternalTexture.DATASOURCE_DYNAMIC:
                     proxy = this._engine.createDynamicTexture(this.baseWidth, this.baseHeight, this.generateMipMaps, this.samplingMode);
                     proxy._swapAndDie(this);
+                    this._engine.updateDynamicTexture(this, this._engine.getRenderingCanvas()!, this.invertY, undefined, undefined, true);
 
                     // The engine will make sure to update content so no need to flag it as isReady = true
                     return;

+ 103 - 1
src/Math/babylon.math.ts

@@ -1969,6 +1969,49 @@ module BABYLON {
         }
 
         /**
+         * Reorders the x y z properties of the vector in place
+         * @param order new ordering of the properties (eg. for vector 1,2,3 with "ZYX" will produce 3,2,1)
+         * @returns the current updated vector
+         */
+        public reorderInPlace(order: string) {
+            order = order.toLowerCase();
+            if (order === "xyz") {
+                return this;
+            }
+            MathTmp.Vector3[0].copyFrom(this);
+            ["x", "y", "z"].forEach((val, i) => {
+                (<any>this)[val] = (<any>MathTmp.Vector3[0])[order[i]];
+            });
+            return this;
+        }
+
+        /**
+         * Rotates the vector around 0,0,0 by a quaternion
+         * @param quaternion the rotation quaternion
+         * @param result vector to store the result
+         * @returns the resulting vector
+         */
+        public rotateByQuaternionToRef(quaternion: Quaternion, result: Vector3) {
+            quaternion.toRotationMatrix(MathTmp.Matrix[0]);
+            Vector3.TransformCoordinatesToRef(this, MathTmp.Matrix[0], result);
+            return result;
+        }
+
+        /**
+         * Rotates a vector around a given point
+         * @param quaternion the rotation quaternion
+         * @param point the point to rotate around
+         * @param result vector to store the result
+         * @returns the resulting vector
+         */
+        public rotateByQuaternionAroundPointToRef(quaternion: Quaternion, point: Vector3, result: Vector3) {
+            this.subtractToRef(point, MathTmp.Vector3[0]);
+            MathTmp.Vector3[0].rotateByQuaternionToRef(quaternion, MathTmp.Vector3[0]);
+            point.addToRef(MathTmp.Vector3[0], result);
+            return result;
+        }
+
+        /**
          * Normalize the current Vector3 with the given input length.
          * Please note that this is an in place operation.
          * @param len the length of the vector
@@ -2229,7 +2272,7 @@ module BABYLON {
          * @param result defines the Vector3 where to store the result
          */
         public static TransformCoordinatesToRef(vector: DeepImmutable<Vector3>, transformation: DeepImmutable<Matrix>, result: Vector3): void {
-            return Vector3.TransformCoordinatesFromFloatsToRef(vector.x, vector.y, vector.z, transformation, result);
+            Vector3.TransformCoordinatesFromFloatsToRef(vector.x, vector.y, vector.z, transformation, result);
         }
 
         /**
@@ -3998,6 +4041,17 @@ module BABYLON {
         }
 
         /**
+         * Inverse a given quaternion
+         * @param q defines the source quaternion
+         * @param result the quaternion the result will be stored in
+         * @returns the result quaternion
+         */
+        public static InverseToRef(q: Quaternion, result: Quaternion): Quaternion {
+            result.set(-q.x, -q.y, -q.z, q.w);
+            return result;
+        }
+
+        /**
          * Creates an identity quaternion
          * @returns the identity quaternion
          */
@@ -4055,6 +4109,54 @@ module BABYLON {
         }
 
         /**
+         * Create a quaternion from Euler rotation angles
+         * @param x Pitch
+         * @param y Yaw
+         * @param z Roll
+         * @returns the new Quaternion
+         */
+        public static FromEulerAngles(x: number, y: number, z: number): Quaternion {
+            var q = new Quaternion();
+            Quaternion.RotationYawPitchRollToRef(y, x, z, q);
+            return q;
+        }
+
+        /**
+         * Updates a quaternion from Euler rotation angles
+         * @param x Pitch
+         * @param y Yaw
+         * @param z Roll
+         * @param result the quaternion to store the result
+         * @returns the updated quaternion
+         */
+        public static FromEulerAnglesToRef(x: number, y: number, z: number, result: Quaternion): Quaternion {
+            Quaternion.RotationYawPitchRollToRef(y, x, z, result);
+            return result;
+        }
+
+        /**
+         * Create a quaternion from Euler rotation vector
+         * @param vec the Euler vector (x Pitch, y Yaw, z Roll)
+         * @returns the new Quaternion
+         */
+        public static FromEulerVector(vec: DeepImmutable<Vector3>): Quaternion {
+            var q = new Quaternion();
+            Quaternion.RotationYawPitchRollToRef(vec.y, vec.x, vec.z, q);
+            return q;
+        }
+
+        /**
+         * Updates a quaternion from Euler rotation vector
+         * @param vec the Euler vector (x Pitch, y Yaw, z Roll)
+         * @param result the quaternion to store the result
+         * @returns the updated quaternion
+         */
+        public static FromEulerVectorToRef(vec: DeepImmutable<Vector3>, result: Quaternion): Quaternion {
+            Quaternion.RotationYawPitchRollToRef(vec.y, vec.x, vec.z, result);
+            return result;
+        }
+
+        /**
          * Creates a new quaternion from the given Euler float angles (y, x, z)
          * @param yaw defines the rotation around Y axis
          * @param pitch defines the rotation around X axis

+ 95 - 30
src/Mesh/babylon.abstractMesh.ts

@@ -1038,6 +1038,94 @@ module BABYLON {
             };
         }
 
+        /**
+         * This method recomputes and sets a new BoundingInfo to the mesh unless it is locked.
+         * This means the mesh underlying bounding box and sphere are recomputed.
+         * @param applySkeleton defines whether to apply the skeleton before computing the bounding info
+         * @returns the current mesh
+         */
+        public refreshBoundingInfo(applySkeleton: boolean = false): AbstractMesh {
+            if (this._boundingInfo && this._boundingInfo.isLocked) {
+                return this;
+            }
+
+            this._refreshBoundingInfo(this._getPositionData(applySkeleton), null);
+            return this;
+        }
+
+        /** @hidden */
+        public _refreshBoundingInfo(data: Nullable<FloatArray>, bias: Nullable<Vector2>): void {
+            if (data) {
+                var extend = Tools.ExtractMinAndMax(data, 0, this.getTotalVertices(), bias);
+                if (this._boundingInfo) {
+                    this._boundingInfo.reConstruct(extend.minimum, extend.maximum);
+                }
+                else {
+                    this._boundingInfo = new BoundingInfo(extend.minimum, extend.maximum);
+                }
+            }
+
+            if (this.subMeshes) {
+                for (var index = 0; index < this.subMeshes.length; index++) {
+                    this.subMeshes[index].refreshBoundingInfo();
+                }
+            }
+
+            this._updateBoundingInfo();
+        }
+
+        /** @hidden */
+        public _getPositionData(applySkeleton: boolean): Nullable<FloatArray> {
+            var data = this.getVerticesData(VertexBuffer.PositionKind);
+
+            if (data && applySkeleton && this.skeleton) {
+                data = Tools.Slice(data);
+
+                var matricesIndicesData = this.getVerticesData(VertexBuffer.MatricesIndicesKind);
+                var matricesWeightsData = this.getVerticesData(VertexBuffer.MatricesWeightsKind);
+                if (matricesWeightsData && matricesIndicesData) {
+                    var needExtras = this.numBoneInfluencers > 4;
+                    var matricesIndicesExtraData = needExtras ? this.getVerticesData(VertexBuffer.MatricesIndicesExtraKind) : null;
+                    var matricesWeightsExtraData = needExtras ? this.getVerticesData(VertexBuffer.MatricesWeightsExtraKind) : null;
+
+                    var skeletonMatrices = this.skeleton.getTransformMatrices(this);
+
+                    var tempVector = Tmp.Vector3[0];
+                    var finalMatrix = Tmp.Matrix[0];
+                    var tempMatrix = Tmp.Matrix[1];
+
+                    var matWeightIdx = 0;
+                    for (var index = 0; index < data.length; index += 3, matWeightIdx += 4) {
+                        finalMatrix.reset();
+
+                        var inf: number;
+                        var weight: number;
+                        for (inf = 0; inf < 4; inf++) {
+                            weight = matricesWeightsData[matWeightIdx + inf];
+                            if (weight > 0) {
+                                Matrix.FromFloat32ArrayToRefScaled(skeletonMatrices, Math.floor(matricesIndicesData[matWeightIdx + inf] * 16), weight, tempMatrix);
+                                finalMatrix.addToSelf(tempMatrix);
+                            }
+                        }
+                        if (needExtras) {
+                            for (inf = 0; inf < 4; inf++) {
+                                weight = matricesWeightsExtraData![matWeightIdx + inf];
+                                if (weight > 0) {
+                                    Matrix.FromFloat32ArrayToRefScaled(skeletonMatrices, Math.floor(matricesIndicesExtraData![matWeightIdx + inf] * 16), weight, tempMatrix);
+                                    finalMatrix.addToSelf(tempMatrix);
+                                }
+                            }
+                        }
+
+                        Vector3.TransformCoordinatesFromFloatsToRef(data[index], data[index + 1], data[index + 2], finalMatrix, tempVector);
+                        tempVector.toArray(data, index);
+                    }
+                }
+            }
+
+            return data;
+        }
+
         /** @hidden */
         public _updateBoundingInfo(): AbstractMesh {
             if (this._boundingInfo) {
@@ -1131,31 +1219,6 @@ module BABYLON {
             return this._boundingInfo.intersectsPoint(point);
         }
 
-        /**
-         * Gets the position of the current mesh in camera space
-         * @param camera defines the camera to use
-         * @returns a position
-         */
-        public getPositionInCameraSpace(camera: Nullable<Camera> = null): Vector3 {
-            if (!camera) {
-                camera = (<Camera>this.getScene().activeCamera);
-            }
-
-            return Vector3.TransformCoordinates(this.absolutePosition, camera.getViewMatrix());
-        }
-
-        /**
-         * Returns the distance from the mesh to the active camera
-         * @param camera defines the camera to use
-         * @returns the distance
-         */
-        public getDistanceToCamera(camera: Nullable<Camera> = null): number {
-            if (!camera) {
-                camera = (<Camera>this.getScene().activeCamera);
-            }
-            return this.absolutePosition.subtract(camera.position).length();
-        }
-
         // Collisions
 
         /**
@@ -1298,9 +1361,9 @@ module BABYLON {
          */
         public intersects(ray: Ray, fastCheck?: boolean): PickingInfo {
             var pickingInfo = new PickingInfo();
-            const intersectionTreshold = this.getClassName() === "LinesMesh" ? (<LinesMesh>(this as any)).intersectionThreshold : 0;
+            const intersectionThreshold = this.getClassName() === "InstancedLinesMesh" || this.getClassName() === "LinesMesh" ? (this as any).intersectionThreshold : 0;
             const boundingInfo = this._boundingInfo;
-            if (!this.subMeshes || !boundingInfo || !ray.intersectsSphere(boundingInfo.boundingSphere, intersectionTreshold) || !ray.intersectsBox(boundingInfo.boundingBox, intersectionTreshold)) {
+            if (!this.subMeshes || !boundingInfo || !ray.intersectsSphere(boundingInfo.boundingSphere, intersectionThreshold) || !ray.intersectsBox(boundingInfo.boundingBox, intersectionThreshold)) {
                 return pickingInfo;
             }
 
@@ -1337,8 +1400,10 @@ module BABYLON {
             if (intersectInfo) {
                 // Get picked point
                 const world = this.getWorldMatrix();
-                const worldOrigin = Vector3.TransformCoordinates(ray.origin, world);
-                const direction = ray.direction.scaleToRef(intersectInfo.distance, Tmp.Vector3[0]);
+                const worldOrigin = Tmp.Vector3[0];
+                const direction = Tmp.Vector3[1];
+                Vector3.TransformCoordinatesToRef(ray.origin, world, worldOrigin);
+                ray.direction.scaleToRef(intersectInfo.distance, direction);
                 const worldDirection = Vector3.TransformNormal(direction, world);
                 const pickedPoint = worldDirection.addInPlace(worldOrigin);
 
@@ -1446,7 +1511,7 @@ module BABYLON {
             });
 
             // SubMeshes
-            if (this.getClassName() !== "InstancedMesh") {
+            if (this.getClassName() !== "InstancedMesh" || this.getClassName() !== "InstancedLinesMesh") {
                 this.releaseSubMeshes();
             }
 

+ 3 - 0
src/Mesh/babylon.geometry.ts

@@ -701,6 +701,9 @@ module BABYLON {
 
             // morphTargets
             mesh._syncGeometryWithMorphTargetManager();
+
+            // instances
+            mesh.synchronizeInstances();
         }
 
         private notifyUpdate(kind?: string) {

+ 9 - 11
src/Mesh/babylon.instancedMesh.ts

@@ -214,20 +214,18 @@ module BABYLON {
         }
 
         /**
-         * reconstructs and updates the BoundingInfo of the mesh.
-         * @returns the mesh.
+         * This method recomputes and sets a new BoundingInfo to the mesh unless it is locked.
+         * This means the mesh underlying bounding box and sphere are recomputed.
+         * @param applySkeleton defines whether to apply the skeleton before computing the bounding info
+         * @returns the current mesh
          */
-        public refreshBoundingInfo(): InstancedMesh {
-            var meshBB = this._sourceMesh.getBoundingInfo();
-
-            if (this._boundingInfo) {
-                this._boundingInfo.reConstruct(meshBB.minimum, meshBB.maximum);
-            }
-            else {
-                this._boundingInfo = new BoundingInfo(meshBB.minimum, meshBB.maximum);
+        public refreshBoundingInfo(applySkeleton: boolean = false): InstancedMesh {
+            if (this._boundingInfo && this._boundingInfo.isLocked) {
+                return this;
             }
 
-            this._updateBoundingInfo();
+            const bias = this._sourceMesh.geometry ? this._sourceMesh.geometry.boundingBias : null;
+            this._refreshBoundingInfo(this._sourceMesh._getPositionData(applySkeleton), bias);
             return this;
         }
 

+ 36 - 17
src/Mesh/babylon.linesMesh.ts

@@ -17,24 +17,9 @@ module BABYLON {
          * The intersection Threshold is the margin applied when intersection a segment of the LinesMesh with a Ray.
          * This margin is expressed in world space coordinates, so its value may vary.
          * Default value is 0.1
-         * @returns the intersection Threshold value.
          */
-        public get intersectionThreshold(): number {
-            return this._intersectionThreshold;
-        }
-
-        /**
-         * The intersection Threshold is the margin applied when intersection a segment of the LinesMesh with a Ray.
-         * This margin is expressed in world space coordinates, so its value may vary.
-         */
-        public set intersectionThreshold(value: number) {
-            if (this._intersectionThreshold === value) {
-                return;
-            }
-            this._intersectionThreshold = value;
-        }
+        public intersectionThreshold: number;
 
-        private _intersectionThreshold: number;
         private _colorShader: ShaderMaterial;
 
         /**
@@ -73,7 +58,7 @@ module BABYLON {
                 this.useVertexAlpha = source.useVertexAlpha;
             }
 
-            this._intersectionThreshold = 0.1;
+            this.intersectionThreshold = 0.1;
 
             var defines: string[] = [];
             var options = {
@@ -169,5 +154,39 @@ module BABYLON {
         public clone(name: string, newParent?: Node, doNotCloneChildren?: boolean): LinesMesh {
             return new LinesMesh(name, this.getScene(), newParent, this, doNotCloneChildren);
         }
+
+        /**
+         * Creates a new InstancedLinesMesh object from the mesh model.
+         * @see http://doc.babylonjs.com/how_to/how_to_use_instances
+         * @param name defines the name of the new instance
+         * @returns a new InstancedLinesMesh
+         */
+        public createInstance(name: string): InstancedLinesMesh {
+            return new InstancedLinesMesh(name, this);
+        }
+    }
+
+    /**
+     * Creates an instance based on a source LinesMesh
+     */
+    export class InstancedLinesMesh extends InstancedMesh {
+        /**
+         * The intersection Threshold is the margin applied when intersection a segment of the LinesMesh with a Ray.
+         * This margin is expressed in world space coordinates, so its value may vary.
+         * Initilized with the intersectionThreshold value of the source LinesMesh
+         */
+        public intersectionThreshold: number;
+
+        constructor(name: string, source: LinesMesh) {
+            super(name, source);
+            this.intersectionThreshold = source.intersectionThreshold;
+        }
+
+        /**
+         * Returns the string "InstancedLinesMesh".
+         */
+        public getClassName(): string {
+            return "InstancedLinesMesh";
+        }
     }
 }

+ 10 - 79
src/Mesh/babylon.mesh.ts

@@ -216,7 +216,7 @@ module BABYLON {
         // Will be used to save a source mesh reference, If any
         private _source: Nullable<Mesh> = null;
         // Will be used to for fast cloned mesh lookup
-        private meshMap: Nullable<{[id: string]: Mesh | undefined}>;
+        private meshMap: Nullable<{ [id: string]: Mesh | undefined }>;
 
         /**
          * Gets the source mesh (the one used to clone this one from)
@@ -888,91 +888,19 @@ module BABYLON {
         /**
          * This method recomputes and sets a new BoundingInfo to the mesh unless it is locked.
          * This means the mesh underlying bounding box and sphere are recomputed.
+         * @param applySkeleton defines whether to apply the skeleton before computing the bounding info
          * @returns the current mesh
          */
-        public refreshBoundingInfo(): Mesh {
-            return this._refreshBoundingInfo(false);
-        }
-
-        /** @hidden */
-        public _refreshBoundingInfo(applySkeleton: boolean): Mesh {
+        public refreshBoundingInfo(applySkeleton: boolean = false): Mesh {
             if (this._boundingInfo && this._boundingInfo.isLocked) {
                 return this;
             }
 
-            var data = this._getPositionData(applySkeleton);
-            if (data) {
-                const bias = this.geometry ? this.geometry.boundingBias : null;
-                var extend = Tools.ExtractMinAndMax(data, 0, this.getTotalVertices(), bias);
-                if (this._boundingInfo) {
-                    this._boundingInfo.reConstruct(extend.minimum, extend.maximum);
-                }
-                else {
-                    this._boundingInfo = new BoundingInfo(extend.minimum, extend.maximum);
-                }
-            }
-
-            if (this.subMeshes) {
-                for (var index = 0; index < this.subMeshes.length; index++) {
-                    this.subMeshes[index].refreshBoundingInfo();
-                }
-            }
-
-            this._updateBoundingInfo();
+            const bias = this.geometry ? this.geometry.boundingBias : null;
+            this._refreshBoundingInfo(this._getPositionData(applySkeleton), bias);
             return this;
         }
 
-        private _getPositionData(applySkeleton: boolean): Nullable<FloatArray> {
-            var data = this.getVerticesData(VertexBuffer.PositionKind);
-
-            if (data && applySkeleton && this.skeleton) {
-                data = Tools.Slice(data);
-
-                var matricesIndicesData = this.getVerticesData(VertexBuffer.MatricesIndicesKind);
-                var matricesWeightsData = this.getVerticesData(VertexBuffer.MatricesWeightsKind);
-                if (matricesWeightsData && matricesIndicesData) {
-                    var needExtras = this.numBoneInfluencers > 4;
-                    var matricesIndicesExtraData = needExtras ? this.getVerticesData(VertexBuffer.MatricesIndicesExtraKind) : null;
-                    var matricesWeightsExtraData = needExtras ? this.getVerticesData(VertexBuffer.MatricesWeightsExtraKind) : null;
-
-                    var skeletonMatrices = this.skeleton.getTransformMatrices(this);
-
-                    var tempVector = Tmp.Vector3[0];
-                    var finalMatrix = Tmp.Matrix[0];
-                    var tempMatrix = Tmp.Matrix[1];
-
-                    var matWeightIdx = 0;
-                    for (var index = 0; index < data.length; index += 3, matWeightIdx += 4) {
-                        finalMatrix.reset();
-
-                        var inf: number;
-                        var weight: number;
-                        for (inf = 0; inf < 4; inf++) {
-                            weight = matricesWeightsData[matWeightIdx + inf];
-                            if (weight > 0) {
-                                Matrix.FromFloat32ArrayToRefScaled(skeletonMatrices, Math.floor(matricesIndicesData[matWeightIdx + inf] * 16), weight, tempMatrix);
-                                finalMatrix.addToSelf(tempMatrix);
-                            }
-                        }
-                        if (needExtras) {
-                            for (inf = 0; inf < 4; inf++) {
-                                weight = matricesWeightsExtraData![matWeightIdx + inf];
-                                if (weight > 0) {
-                                    Matrix.FromFloat32ArrayToRefScaled(skeletonMatrices, Math.floor(matricesIndicesExtraData![matWeightIdx + inf] * 16), weight, tempMatrix);
-                                    finalMatrix.addToSelf(tempMatrix);
-                                }
-                            }
-                        }
-
-                        Vector3.TransformCoordinatesFromFloatsToRef(data[index], data[index + 1], data[index + 2], finalMatrix, tempVector);
-                        tempVector.toArray(data, index);
-                    }
-                }
-            }
-
-            return data;
-        }
-
         /** @hidden */
         public _createGlobalSubMesh(force: boolean): Nullable<SubMesh> {
             var totalVertices = this.getTotalVertices();
@@ -2380,7 +2308,6 @@ module BABYLON {
 
         /**
          * Creates a new InstancedMesh object from the mesh model.
-         * Warning : this method is not supported for Line mesh and LineSystem
          * @see http://doc.babylonjs.com/how_to/how_to_use_instances
          * @param name defines the name of the new instance
          * @returns a new InstancedMesh
@@ -2471,7 +2398,11 @@ module BABYLON {
             }
 
             serializationObject.scaling = this.scaling.asArray();
-            serializationObject.localMatrix = this.getPivotMatrix().asArray();
+            if (this._postMultiplyPivotMatrix) {
+                serializationObject.pivotMatrix = this.getPivotMatrix().asArray();
+            } else {
+                serializationObject.localMatrix = this.getPivotMatrix().asArray();
+            }
 
             serializationObject.isEnabled = this.isEnabled(false);
             serializationObject.isVisible = this.isVisible;

+ 2 - 4
src/Mesh/babylon.subMesh.ts

@@ -344,10 +344,8 @@ module BABYLON {
 
             // LineMesh first as it's also a Mesh...
             if (LinesMesh) {
-                const mesh = this._mesh instanceof InstancedMesh ? (<InstancedMesh>this._mesh).sourceMesh : this._mesh;
-                if (mesh instanceof LinesMesh) {
-                    const linesMesh = <LinesMesh>mesh;
-                    return this._intersectLines(ray, positions, indices, linesMesh.intersectionThreshold, fastCheck);
+                if (this._mesh.getClassName() === "InstancedLinesMesh" || this._mesh.getClassName() === "LinesMesh") {
+                    return this._intersectLines(ray, positions, indices, (this._mesh as any).intersectionThreshold, fastCheck);
                 }
             }
 

+ 32 - 7
src/Mesh/babylon.transformNode.ts

@@ -89,7 +89,7 @@ module BABYLON {
         private _absolutePosition = Vector3.Zero();
         private _pivotMatrix = Matrix.Identity();
         private _pivotMatrixInverse: Matrix;
-        private _postMultiplyPivotMatrix = false;
+        protected _postMultiplyPivotMatrix = false;
 
         protected _isWorldMatrixFrozen = false;
 
@@ -120,7 +120,7 @@ module BABYLON {
         /**
           * Gets or set the node position (default is (0.0, 0.0, 0.0))
           */
-         public get position(): Vector3 {
+        public get position(): Vector3 {
             return this._position;
         }
 
@@ -269,11 +269,11 @@ module BABYLON {
             this._cache.billboardMode = -1;
         }
 
-         /**
-         * Flag the transform node as dirty (Forcing it to update everything)
-         * @param property if set to "rotation" the objects rotationQuaternion will be set to null
-         * @returns this transform node
-         */
+        /**
+        * Flag the transform node as dirty (Forcing it to update everything)
+        * @param property if set to "rotation" the objects rotationQuaternion will be set to null
+        * @returns this transform node
+        */
         public markAsDirty(property: string): TransformNode {
             if (property === "rotation") {
                 this.rotationQuaternion = null;
@@ -993,6 +993,31 @@ module BABYLON {
         }
 
         /**
+         * Gets the position of the current mesh in camera space
+         * @param camera defines the camera to use
+         * @returns a position
+         */
+        public getPositionInCameraSpace(camera: Nullable<Camera> = null): Vector3 {
+            if (!camera) {
+                camera = (<Camera>this.getScene().activeCamera);
+            }
+
+            return Vector3.TransformCoordinates(this.absolutePosition, camera.getViewMatrix());
+        }
+
+        /**
+         * Returns the distance from the mesh to the active camera
+         * @param camera defines the camera to use
+         * @returns the distance
+         */
+        public getDistanceToCamera(camera: Nullable<Camera> = null): number {
+            if (!camera) {
+                camera = (<Camera>this.getScene().activeCamera);
+            }
+            return this.absolutePosition.subtract(camera.position).length();
+        }
+
+        /**
          * Clone the current transform node
          * @param name Name of the new clone
          * @param newParent New parent for the clone

+ 8 - 1
src/Physics/Plugins/babylon.cannonJSPlugin.ts

@@ -435,7 +435,14 @@ module BABYLON {
                 //get original center with no rotation
                 var c = center.clone();
 
-                var oldPivot = mesh.getPivotMatrix() || Matrix.Translation(0, 0, 0);
+                var oldPivot = mesh.getPivotMatrix();
+                if (oldPivot) {
+                    // create a copy the pivot Matrix as it is modified in place
+                    oldPivot = oldPivot.clone();
+                }
+                else {
+                    oldPivot = Matrix.Identity();
+                }
 
                 //calculate the new center using a pivot (since this.BJSCANNON.js doesn't center height maps)
                 var p = Matrix.Translation(boundingInfo.boundingBox.extendSizeWorld.x, 0, -boundingInfo.boundingBox.extendSizeWorld.z);

+ 0 - 8
src/Physics/Plugins/babylon.oimoJSPlugin.ts

@@ -149,10 +149,6 @@ module BABYLON {
                         bodyConfig.pos.push(center.z);
                         bodyConfig.posShape.push(0, 0, 0);
 
-                        //tmp solution
-                        bodyConfig.rot.push(0);
-                        bodyConfig.rot.push(0);
-                        bodyConfig.rot.push(0);
                         bodyConfig.rotShape.push(0, 0, 0);
                     } else {
                         let localPosition = i.object.getAbsolutePosition().subtract(impostor.object.getAbsolutePosition());
@@ -161,10 +157,6 @@ module BABYLON {
                         bodyConfig.posShape.push(localPosition.z);
                         bodyConfig.pos.push(0, 0, 0);
 
-                        //tmp solution until https://github.com/lo-th/OIMO.js/pull/37 is merged
-                        bodyConfig.rot.push(0);
-                        bodyConfig.rot.push(0);
-                        bodyConfig.rot.push(0);
                         bodyConfig.rotShape.push(rot.x * radToDeg);
                         bodyConfig.rotShape.push(rot.y * radToDeg);
                         bodyConfig.rotShape.push(rot.z * radToDeg);

+ 1 - 1
src/Physics/babylon.physicsImpostor.ts

@@ -256,7 +256,7 @@ module BABYLON {
                 return;
             }
 
-            //legacy support for old syntax.
+            // Legacy support for old syntax.
             if (!this._scene && object.getScene) {
                 this._scene = object.getScene();
             }

+ 7 - 4
src/PostProcess/RenderPipeline/babylon.postProcessRenderEffect.ts

@@ -151,12 +151,15 @@ module BABYLON {
             for (var i = 0; i < cams.length; i++) {
                 var camera: Camera = cams[i];
                 var cameraName: string = camera.name;
-                this._postProcesses[this._singleInstance ? 0 : cameraName].forEach((postProcess: PostProcess) => {
-                    camera.detachPostProcess(postProcess);
-                });
+                const postProcesses = this._postProcesses[this._singleInstance ? 0 : cameraName];
+
+                if (postProcesses) {
+                    postProcesses.forEach((postProcess: PostProcess) => {
+                        camera.detachPostProcess(postProcess);
+                    });
+                }
 
                 if (this._cameras[cameraName]) {
-                    //this._indicesForCamera.splice(index, 1);
                     this._cameras[cameraName] = null;
                 }
             }

+ 5 - 10
src/Rendering/babylon.edgesRenderer.ts

@@ -62,25 +62,20 @@ module BABYLON {
         return this;
     };
 
-    export interface InstancedMesh {
+    export interface InstancedLinesMesh {
         /**
          * Enables the edge rendering mode on the mesh.
          * This mode makes the mesh edges visible
          * @param epsilon defines the maximal distance between two angles to detect a face
          * @param checkVerticesInsteadOfIndices indicates that we should check vertex list directly instead of faces
-         * @returns the currentInstancedMesh
+         * @returns the current InstancedLinesMesh
          * @see https://www.babylonjs-playground.com/#19O9TU#0
          */
-        enableEdgesRendering(epsilon?: number, checkVerticesInsteadOfIndices?: boolean): InstancedMesh;
+        enableEdgesRendering(epsilon?: number, checkVerticesInsteadOfIndices?: boolean): InstancedLinesMesh;
     }
 
-    InstancedMesh.prototype.enableEdgesRendering = function(epsilon = 0.95, checkVerticesInsteadOfIndices = false): InstancedMesh {
-        if (this.sourceMesh.getClassName() === 'LinesMesh') {
-            LinesMesh.prototype.enableEdgesRendering.apply(this, arguments);
-        }
-        else {
-            AbstractMesh.prototype.enableEdgesRendering.apply(this, arguments);
-        }
+    InstancedLinesMesh.prototype.enableEdgesRendering = function(epsilon = 0.95, checkVerticesInsteadOfIndices = false): InstancedLinesMesh {
+        LinesMesh.prototype.enableEdgesRendering.apply(this, arguments);
         return this;
     };
 

+ 0 - 4
src/Shaders/background.vertex.fx

@@ -15,10 +15,6 @@ attribute vec3 normal;
 // Uniforms
 #include<instancesDeclaration>
 
-#ifdef POINTSIZE
-uniform float pointSize;
-#endif
-
 // Output
 varying vec3 vPositionW;
 #ifdef NORMAL

+ 11 - 1
src/Tools/babylon.tools.ts

@@ -1517,6 +1517,16 @@ module BABYLON {
                 scene.activeCamera = camera;
             }
 
+            var renderCanvas = engine.getRenderingCanvas();
+            if (!renderCanvas) {
+                Tools.Error("No rendering canvas found !");
+                return;
+            }
+
+            var originalSize = {width: renderCanvas.width, height: renderCanvas.height};
+            engine.setSize(width, height);
+            scene.render();
+
             // At this point size can be a number, or an object (according to engine.prototype.createRenderTargetTexture method)
             var texture = new RenderTargetTexture("screenShot", size, scene, false, false, Engine.TEXTURETYPE_UNSIGNED_INT, false, Texture.NEAREST_SAMPLINGMODE);
             texture.renderList = null;
@@ -1536,7 +1546,7 @@ module BABYLON {
             if (previousCamera) {
                 scene.activeCamera = previousCamera;
             }
-
+            engine.setSize(originalSize.width, originalSize.height);
             camera.getProjectionMatrix(true); // Force cache refresh;
         }
 

+ 10 - 6
src/babylon.node.ts

@@ -9,7 +9,7 @@ module BABYLON {
      * Node is the basic class for all scene objects (Mesh, Light, Camera.)
      */
     export class Node implements IBehaviorAware<Node> {
-        private static _NodeConstructors: {[key: string]: any} = {};
+        private static _NodeConstructors: { [key: string]: any } = {};
 
         /**
          * Add a new node constructor
@@ -228,15 +228,18 @@ module BABYLON {
          * Creates a new Node
          * @param name the name and id to be given to this node
          * @param scene the scene this node will be added to
+         * @param addToRootNodes the node will be added to scene.rootNodes
          */
-        constructor(name: string, scene: Nullable<Scene> = null) {
+        constructor(name: string, scene: Nullable<Scene> = null, addToRootNodes = true) {
             this.name = name;
             this.id = name;
             this._scene = <Scene>(scene || Engine.LastCreatedScene);
             this.uniqueId = this._scene.getUniqueId();
             this._initCache();
 
-            this.addToSceneRootNodes();
+            if (addToRootNodes) {
+                this.addToSceneRootNodes();
+            }
         }
 
         /**
@@ -364,7 +367,7 @@ module BABYLON {
         /** @hidden */
         public updateCache(force?: boolean): void {
             if (!force && this.isSynchronized()) {
-                return;
+                return;
             }
 
             this._cache.parent = this.parent;
@@ -547,10 +550,11 @@ module BABYLON {
         /**
          * Get all direct children of this node
          * @param predicate defines an optional predicate that will be called on every evaluated child, the predicate must return true for a given child to be part of the result, otherwise it will be ignored
+         * @param directDescendantsOnly defines if true only direct descendants of 'this' will be considered, if false direct and also indirect (children of children, an so on in a recursive manner) descendants of 'this' will be considered (Default: true)
          * @returns an array of Node
          */
-        public getChildren(predicate?: (node: Node) => boolean): Node[] {
-            return this.getDescendants(true, predicate);
+        public getChildren(predicate?: (node: Node) => boolean, directDescendantsOnly = true): Node[] {
+            return this.getDescendants(directDescendantsOnly, predicate);
         }
 
         /** @hidden */

+ 72 - 17
src/babylon.scene.ts

@@ -507,6 +507,11 @@ module BABYLON {
         public onAfterStepObservable = new Observable<Scene>();
 
         /**
+         * An event triggered when the activeCamera property is updated
+         */
+        public onActiveCameraChanged = new Observable<Scene>();
+
+        /**
          * This Observable will be triggered before rendering each renderingGroup of each rendered camera.
          * The RenderinGroupInfo class contains all the information about the context in which the observable is called
          * If you wish to register an Observer only for a given set of renderingGroup, use the mask with a combination of the renderingGroup index elevated to the power of two (1 for renderingGroup 0, 2 for renderingrOup1, 4 for 2 and 8 for 3)
@@ -776,8 +781,21 @@ module BABYLON {
 
         /** All of the active cameras added to this scene. */
         public activeCameras = new Array<Camera>();
-        /** The current active camera */
-        public activeCamera: Nullable<Camera>;
+
+        private _activeCamera: Nullable<Camera>;
+        /** Gets or sets the current active camera */
+        public get activeCamera(): Nullable<Camera> {
+            return this._activeCamera;
+        }
+
+        public set activeCamera(value: Nullable<Camera>) {
+            if (value === this._activeCamera) {
+                return;
+            }
+
+            this._activeCamera = value;
+            this.onActiveCameraChanged.notifyObservers(this);
+        }
 
         private _defaultMaterial: Material;
 
@@ -2213,7 +2231,7 @@ module BABYLON {
                     return false;
                 }
 
-                let hardwareInstancedRendering = mesh.getClassName() === "InstancedMesh" || engine.getCaps().instancedArrays && (<Mesh>mesh).instances.length > 0;
+                let hardwareInstancedRendering = mesh.getClassName() === "InstancedMesh" || mesh.getClassName() === "InstancedLinesMesh" || engine.getCaps().instancedArrays && (<Mesh>mesh).instances.length > 0;
                 // Is Ready For Mesh
                 for (let step of this._isReadyForMeshStage) {
                     if (!step.action(mesh, hardwareInstancedRendering)) {
@@ -2468,6 +2486,32 @@ module BABYLON {
         }
 
         /**
+         * Will start the animation sequence of a given target and its hierarchy
+         * @param target defines the target
+         * @param directDescendantsOnly if true only direct descendants will be used, if false direct and also indirect (children of children, an so on in a recursive manner) descendants will be used.
+         * @param from defines from which frame should animation start
+         * @param to defines until which frame should animation run.
+         * @param loop defines if the animation loops
+         * @param speedRatio defines the speed in which to run the animation (1.0 by default)
+         * @param onAnimationEnd defines the function to be executed when the animation ends
+         * @param animatable defines an animatable object. If not provided a new one will be created from the given params
+         * @param stopCurrent defines if the current animations must be stopped first (true by default)
+         * @param targetMask defines if the target should be animated if animations are present (this is called recursively on descendant animatables regardless of return value)
+         * @returns the list of created animatables
+         */
+        public beginHierarchyAnimation(target: any, directDescendantsOnly: boolean, from: number, to: number, loop?: boolean, speedRatio: number = 1.0, onAnimationEnd?: () => void, animatable?: Animatable, stopCurrent = true, targetMask?: (target: any) => boolean): Animatable[] {
+            let children = target.getDescendants(directDescendantsOnly);
+
+            let result = [];
+            result.push(this.beginAnimation(target, from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask));
+            for (var child of children) {
+                result.push(this.beginAnimation(child, from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask));
+            }
+
+            return result;
+        }
+
+        /**
          * Begin a new animation on a given node
          * @param target defines the target where the animation will take place
          * @param animations defines the list of animations to start
@@ -3760,27 +3804,32 @@ module BABYLON {
          * @return the found node or null if not found at all
          */
         public getNodeByID(id: string): Nullable<Node> {
-            var mesh = this.getMeshByID(id);
-
+            const mesh = this.getMeshByID(id);
             if (mesh) {
                 return mesh;
             }
 
-            var light = this.getLightByID(id);
+            const transformNode = this.getTransformNodeByID(id);
+            if (transformNode) {
+                return transformNode;
+            }
 
+            const light = this.getLightByID(id);
             if (light) {
                 return light;
             }
 
-            var camera = this.getCameraByID(id);
-
+            const camera = this.getCameraByID(id);
             if (camera) {
                 return camera;
             }
 
-            var bone = this.getBoneByID(id);
+            const bone = this.getBoneByID(id);
+            if (bone) {
+                return bone;
+            }
 
-            return bone;
+            return null;
         }
 
         /**
@@ -3789,27 +3838,32 @@ module BABYLON {
          * @return the found node or null if not found at all.
          */
         public getNodeByName(name: string): Nullable<Node> {
-            var mesh = this.getMeshByName(name);
-
+            const mesh = this.getMeshByName(name);
             if (mesh) {
                 return mesh;
             }
 
-            var light = this.getLightByName(name);
+            const transformNode = this.getTransformNodeByName(name);
+            if (transformNode) {
+                return transformNode;
+            }
 
+            const light = this.getLightByName(name);
             if (light) {
                 return light;
             }
 
-            var camera = this.getCameraByName(name);
-
+            const camera = this.getCameraByName(name);
             if (camera) {
                 return camera;
             }
 
-            var bone = this.getBoneByName(name);
+            const bone = this.getBoneByName(name);
+            if (bone) {
+                return bone;
+            }
 
-            return bone;
+            return null;
         }
 
         /**
@@ -4796,6 +4850,7 @@ module BABYLON {
             this.onPointerObservable.clear();
             this.onPreKeyboardObservable.clear();
             this.onKeyboardObservable.clear();
+            this.onActiveCameraChanged.clear();
 
             this.detachControl();
 

+ 9 - 10
tests/unit/babylon/src/Loading/babylon.sceneLoader.tests.ts

@@ -417,18 +417,14 @@ describe('Babylon Scene Loader', function () {
         it('Load MultiPrimitive', () => {
             const scene = new BABYLON.Scene(subject);
             return BABYLON.SceneLoader.ImportMeshAsync(null, "http://models.babylonjs.com/Tests/MultiPrimitive/", "MultiPrimitive.gltf", scene).then(result => {
-                expect(result.meshes, "meshes").to.have.lengthOf(4);
+                expect(result.meshes, "meshes").to.have.lengthOf(3);
 
-                const node = scene.getMeshByName("node");
+                const node = scene.getNodeByName("node");
                 expect(node, "node").to.exist;
-                expect(node, "node").to.be.an.instanceof(BABYLON.Mesh);
+                expect(node, "node").to.be.an.instanceof(BABYLON.TransformNode);
 
-                const mesh = node as BABYLON.Mesh;
-                expect(mesh.geometry).to.not.exist;
-                expect(mesh.material).to.not.exist;
-
-                expect(mesh.getChildren(), "mesh children").to.have.lengthOf(2);
-                for (const childNode of mesh.getChildren()) {
+                expect(node.getChildren(), "node children").to.have.lengthOf(2);
+                for (const childNode of node.getChildren()) {
                     expect(childNode, "child node").to.be.an.instanceof(BABYLON.Mesh);
                     const childMesh = childNode as BABYLON.Mesh;
                     expect(childMesh.geometry).to.exist;
@@ -442,7 +438,10 @@ describe('Babylon Scene Loader', function () {
             return BABYLON.SceneLoader.ImportMeshAsync(null, "/Playground/scenes/BrainStem/", "BrainStem.gltf", scene).then(result => {
                 expect(result.skeletons, "skeletons").to.have.lengthOf(1);
 
-                const node1 = scene.getMeshByName("node1");
+                const node1 = scene.getNodeByName("node1");
+                expect(node1, "node1").to.exist;
+                expect(node1, "node1").to.be.an.instanceof(BABYLON.TransformNode);
+
                 for (const childMesh of node1.getChildMeshes()) {
                     expect(childMesh.skeleton, "mesh skeleton").to.exist;
                     expect(childMesh.skeleton.name, "mesh skeleton name").to.equal(result.skeletons[0].name);

+ 72 - 0
tests/unit/babylon/src/Mesh/babylon.positionAndRotation.tests.ts

@@ -0,0 +1,72 @@
+/**
+ * Describes the test suite.
+ */
+describe('Babylon position and rotation', () => {
+    let subject: BABYLON.Engine;
+
+    /**
+     * Loads the dependencies.
+     */
+    before(function (done) {
+        this.timeout(180000);
+        (BABYLONDEVTOOLS).Loader
+            .useDist()
+            .load(function () {
+                // Force apply promise polyfill for consistent behavior between PhantomJS, IE11, and other browsers.
+                BABYLON.PromisePolyfill.Apply(true);
+                done();
+            });
+    });
+
+    /**
+     * Create a new engine subject before each test.
+     */
+    beforeEach(function () {
+        subject = new BABYLON.NullEngine({
+            renderHeight: 256,
+            renderWidth: 256,
+            textureSize: 256,
+            deterministicLockstep: false,
+            lockstepMaxSteps: 1
+        });
+    });
+
+    describe('#position and rotation:', () => {
+        it('converts between quaternions/euler', () => {
+            // Converting between quaternions/euler
+            var originalRotation = new BABYLON.Vector3(0.1,0.2,0.3);
+            var v = originalRotation.clone()
+            var q = BABYLON.Quaternion.FromEulerVector(v)
+            q.toEulerAnglesToRef(v)
+            expect(v.subtract(originalRotation).length() < 0.00001).to.equal(true)
+        });
+        it('reorders vector in place', () => {
+            var originalRotation = new BABYLON.Vector3(0.1,0.2,0.3);
+            var v = originalRotation.clone()
+            v.reorderInPlace("ZYX")
+            expect(v.subtract(new BABYLON.Vector3(0.3,0.2,0.1)).length() < 0.00001).to.equal(true)
+        });
+        it('handles parenting', () => {
+            // Parent child positions
+            const scene = new BABYLON.Scene(subject);
+            var child = new BABYLON.AbstractMesh("", scene)
+            var parent = new BABYLON.AbstractMesh("", scene)
+            parent.position.set(0,0,1)
+            child.position.set(0,0,-1)
+            child.parent = parent
+            child.computeWorldMatrix()
+            expect(child.absolutePosition.equals(new BABYLON.Vector3(0,0,0))).to.equal(true)
+
+            //Rotate parent around child
+            parent.rotationQuaternion = new BABYLON.Quaternion()
+            var eulerRotation = new BABYLON.Vector3(0,Math.PI/2,0)
+            var rotation = new BABYLON.Quaternion()
+            BABYLON.Quaternion.RotationYawPitchRollToRef(eulerRotation.y, eulerRotation.x, eulerRotation.z, rotation)
+            parent.rotationQuaternion.multiplyInPlace(rotation);
+            parent.position.rotateByQuaternionAroundPointToRef(rotation, child.absolutePosition, parent.position)
+            expect(parent.position.subtract(new BABYLON.Vector3(1,0,0)).length() < 0.00001).to.equal(true)
+            expect(parent.rotationQuaternion.toEulerAngles().subtract(eulerRotation).length() < 0.00001).to.equal(true)
+            expect(child.absolutePosition.subtract(new BABYLON.Vector3(0,0,0)).length() < 0.00001).to.equal(true)
+        });
+    });
+});

+ 1 - 0
tests/unit/karma.conf.js

@@ -15,6 +15,7 @@ module.exports = function (config) {
             '!./**/*.d.ts',
             './Tools/DevLoader/BabylonLoader.js',
             './tests/unit/babylon/babylon.example.tests.js',
+            './tests/unit/babylon/src/Mesh/babylon.positionAndRotation.tests.js',
             './tests/unit/babylon/serializers/babylon.glTFSerializer.tests.js',
             './tests/unit/babylon/src/babylon.node.tests.js',
             './tests/unit/babylon/src/Animations/babylon.animationGroup.tests.js',

BIN
tests/validation/ReferenceImages/xrCameraContainerRotation.png


Різницю між файлами не показано, бо вона завелика
+ 590 - 584
tests/validation/config.json