Quellcode durchsuchen

Merge pull request #5411 from bghgary/instanced-mesh

Add mesh instancing support to glTF loader
David Catuhe vor 6 Jahren
Ursprung
Commit
e94b0f8ceb

+ 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 + "();";
 
                     }
 

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

@@ -51,6 +51,9 @@
 
 ### 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
@@ -103,3 +106,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

+ 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>;
+        };
     }
 
     /**

+ 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();

+ 88 - 25
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
 
         /**

+ 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;
         }
 

+ 4 - 76
src/Mesh/babylon.mesh.ts

@@ -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();

+ 25 - 0
src/Mesh/babylon.transformNode.ts

@@ -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

+ 24 - 14
src/babylon.scene.ts

@@ -3778,27 +3778,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;
         }
 
         /**
@@ -3807,27 +3812,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;
         }
 
         /**

+ 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);