Procházet zdrojové kódy

Merge pull request #2836 from BabylonJS/master

Nightly
David Catuhe před 8 roky
rodič
revize
ca087ca42b
31 změnil soubory, kde provedl 8204 přidání a 7512 odebrání
  1. 2 1
      Playground/scripts/scripts.txt
  2. 35 0
      Playground/scripts/webvr.js
  3. 1636 1606
      dist/preview release/babylon.d.ts
  4. 40 40
      dist/preview release/babylon.js
  5. 301 143
      dist/preview release/babylon.max.js
  6. 1636 1606
      dist/preview release/babylon.module.d.ts
  7. 44 44
      dist/preview release/babylon.worker.js
  8. 1714 1684
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts
  9. 42 42
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js
  10. 379 216
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js
  11. 1714 1684
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.module.d.ts
  12. 11 9
      dist/preview release/loaders/babylon.glTF2FileLoader.d.ts
  13. 78 73
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  14. 2 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  15. 11 9
      dist/preview release/loaders/babylon.glTFFileLoader.d.ts
  16. 78 73
      dist/preview release/loaders/babylon.glTFFileLoader.js
  17. 3 3
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  18. 94 86
      loaders/src/glTF/2.0/babylon.glTFLoader.ts
  19. 2 2
      loaders/src/glTF/2.0/babylon.glTFLoaderInterfaces.ts
  20. 5 1
      src/Animations/babylon.runtimeAnimation.ts
  21. 2 2
      src/Cameras/Inputs/babylon.arcRotateCameraKeyboardMoveInput.ts
  22. 81 36
      src/Cameras/Inputs/babylon.arcRotateCameraPointersInput.ts
  23. 129 36
      src/Cameras/VR/babylon.vrExperienceHelper.ts
  24. 24 26
      src/Cameras/VR/babylon.webVRCamera.ts
  25. 5 5
      src/Materials/babylon.effect.ts
  26. 12 3
      src/Mesh/babylon.meshBuilder.ts
  27. 97 66
      src/babylon.engine.ts
  28. binární
      tests/validation/ReferenceImages/gltfnormals.png
  29. binární
      tests/validation/ReferenceImages/normals.png
  30. binární
      tests/validation/ReferenceImages/pbrrough.png
  31. 27 15
      tests/validation/config.json

+ 2 - 1
Playground/scripts/scripts.txt

@@ -27,4 +27,5 @@ volumetric Light Scattering
 refraction and Reflection
 pbr
 instanced bones
-pointer events handling
+pointer events handling
+webvr

+ 35 - 0
Playground/scripts/webvr.js

@@ -0,0 +1,35 @@
+var createScene = function () {
+
+    // This creates a basic Babylon Scene object (non-mesh)
+    var scene = new BABYLON.Scene(engine);
+    scene.createDefaultVRExperience();
+
+    // This creates a light, aiming 0,1,0 - to the sky (non-mesh)
+    var light = new BABYLON.HemisphericLight("light1", new BABYLON.Vector3(0, 1, 0), scene);
+
+    // Default intensity is 1. Let's dim the light a small amount
+    light.intensity = 0.7;
+
+    // Create some spheres at the default eye level (2m)
+    createSphereBox(scene, 2, 2);
+    createSphereBox(scene, 3, 2);
+        
+    // Our built-in 'ground' shape. Params: name, width, depth, subdivs, scene
+    var ground = BABYLON.Mesh.CreateGround("ground1", 6, 6, 2, scene);
+
+    return scene;
+};
+
+function createSphereBox(scene, distance, height) {    
+    createSphere(scene,  distance, height,  distance);
+    createSphere(scene,  distance, height, -distance);
+    createSphere(scene, -distance, height,  distance);
+    createSphere(scene, -distance, height, -distance);    
+}
+function createSphere(scene, x, y, z) {
+    // Our built-in 'sphere' shape. Params: name, subdivs, size, scene
+    var sphere = BABYLON.Mesh.CreateSphere("sphere1", 4, 0.4, scene);
+    sphere.position.x = x;
+    sphere.position.y = y;
+    sphere.position.z = z;
+}

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1636 - 1606
dist/preview release/babylon.d.ts


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 40 - 40
dist/preview release/babylon.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 301 - 143
dist/preview release/babylon.max.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1636 - 1606
dist/preview release/babylon.module.d.ts


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 44 - 44
dist/preview release/babylon.worker.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1714 - 1684
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 42 - 42
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 379 - 216
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1714 - 1684
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.module.d.ts


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

@@ -213,9 +213,9 @@ declare module BABYLON.GLTF2 {
         indices?: number;
         material?: number;
         mode?: EMeshPrimitiveMode;
-        targets?: [{
+        targets?: {
             [name: string]: number;
-        }];
+        }[];
     }
     interface IGLTFMesh extends IGLTFChildRootProperty {
         primitives: IGLTFMeshPrimitive[];
@@ -234,7 +234,7 @@ declare module BABYLON.GLTF2 {
         index?: number;
         parent?: IGLTFNode;
         babylonMesh?: Mesh;
-        babylonSkinToBones?: {
+        babylonBones?: {
             [skin: number]: Bone;
         };
         babylonAnimationTargets?: Node[];
@@ -330,15 +330,17 @@ declare module BABYLON.GLTF2 {
         private _showMeshes();
         private _startAnimations();
         private _loadScene(nodeNames);
-        private _loadSkin(node);
-        private _updateBone(node, parentNode, skin, inverseBindMatrixData);
-        private _createBone(node, skin);
-        private _loadMesh(node);
-        private _loadMeshData(node, mesh, babylonMesh);
+        private _loadNode(node);
+        private _loadMesh(node, mesh);
         private _loadVertexDataAsync(primitive, onSuccess);
         private _createMorphTargets(node, mesh, primitive, babylonMesh);
         private _loadMorphTargetsData(mesh, primitive, vertexData, babylonMesh);
-        private _loadTransform(node, babylonMesh);
+        private _loadTransform(node);
+        private _loadSkin(skin);
+        private _createBone(node, skin, parent, localMatrix, baseMatrix, index);
+        private _loadBones(skin, inverseBindMatrixData);
+        private _loadBone(node, skin, inverseBindMatrixData, babylonBones);
+        private _getNodeMatrix(node);
         private _traverseNodes(indices, action, parentNode?);
         private _traverseNode(index, action, parentNode?);
         private _loadAnimations();

+ 78 - 73
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -547,85 +547,35 @@ var BABYLON;
                     });
                     nodeIndices = filteredNodeIndices;
                 }
-                this._traverseNodes(nodeIndices, function (node) { return _this._loadSkin(node); });
-                this._traverseNodes(nodeIndices, function (node) { return _this._loadMesh(node); });
+                this._traverseNodes(nodeIndices, function (node) { return _this._loadNode(node); });
             };
-            GLTFLoader.prototype._loadSkin = function (node) {
-                var _this = this;
-                if (node.skin !== undefined) {
-                    var skin = this._gltf.skins[node.skin];
-                    var skeletonId = "skeleton" + node.skin;
-                    skin.babylonSkeleton = new BABYLON.Skeleton(skin.name || skeletonId, skeletonId, this._babylonScene);
-                    skin.index = node.skin;
-                    for (var i = 0; i < skin.joints.length; i++) {
-                        this._createBone(this._gltf.nodes[skin.joints[i]], skin);
-                    }
-                    if (skin.skeleton === undefined) {
-                        // TODO: handle when skeleton is not defined
-                        throw new Error("Not implemented");
-                    }
-                    if (skin.inverseBindMatrices === undefined) {
-                        // TODO: handle when inverse bind matrices are not defined
-                        throw new Error("Not implemented");
-                    }
-                    var accessor = this._gltf.accessors[skin.inverseBindMatrices];
-                    this._loadAccessorAsync(accessor, function (data) {
-                        _this._traverseNode(skin.skeleton, function (node, index, parent) { return _this._updateBone(node, parent, skin, data); });
-                    });
-                }
-                return true;
-            };
-            GLTFLoader.prototype._updateBone = function (node, parentNode, skin, inverseBindMatrixData) {
-                var jointIndex = skin.joints.indexOf(node.index);
-                if (jointIndex === -1) {
-                    this._createBone(node, skin);
-                }
-                var babylonBone = node.babylonSkinToBones[skin.index];
-                // TODO: explain the math
-                var matrix = jointIndex === -1 ? BABYLON.Matrix.Identity() : BABYLON.Matrix.FromArray(inverseBindMatrixData, jointIndex * 16);
-                matrix.invertToRef(matrix);
-                if (parentNode) {
-                    babylonBone.setParent(parentNode.babylonSkinToBones[skin.index], false);
-                    matrix.multiplyToRef(babylonBone.getParent().getInvertedAbsoluteTransform(), matrix);
-                }
-                babylonBone.updateMatrix(matrix);
-                return true;
-            };
-            GLTFLoader.prototype._createBone = function (node, skin) {
-                var babylonBone = new BABYLON.Bone(node.name || "bone" + node.index, skin.babylonSkeleton);
-                node.babylonSkinToBones = node.babylonSkinToBones || {};
-                node.babylonSkinToBones[skin.index] = babylonBone;
-                node.babylonAnimationTargets = node.babylonAnimationTargets || [];
-                node.babylonAnimationTargets.push(babylonBone);
-                return babylonBone;
-            };
-            GLTFLoader.prototype._loadMesh = function (node) {
-                var babylonMesh = new BABYLON.Mesh(node.name || "mesh" + node.index, this._babylonScene);
-                babylonMesh.isVisible = false;
-                this._loadTransform(node, babylonMesh);
+            GLTFLoader.prototype._loadNode = function (node) {
+                node.babylonMesh = new BABYLON.Mesh(node.name || "mesh" + node.index, this._babylonScene);
+                node.babylonMesh.isVisible = false;
+                this._loadTransform(node);
                 if (node.mesh !== undefined) {
                     var mesh = this._gltf.meshes[node.mesh];
-                    this._loadMeshData(node, mesh, babylonMesh);
+                    this._loadMesh(node, mesh);
                 }
-                babylonMesh.parent = node.parent ? node.parent.babylonMesh : null;
-                node.babylonMesh = babylonMesh;
+                node.babylonMesh.parent = node.parent ? node.parent.babylonMesh : null;
                 node.babylonAnimationTargets = node.babylonAnimationTargets || [];
                 node.babylonAnimationTargets.push(node.babylonMesh);
                 if (node.skin !== undefined) {
                     var skin = this._gltf.skins[node.skin];
-                    babylonMesh.skeleton = skin.babylonSkeleton;
+                    skin.index = node.skin;
+                    node.babylonMesh.skeleton = this._loadSkin(skin);
                 }
                 if (node.camera !== undefined) {
                     // TODO: handle cameras
                 }
                 return true;
             };
-            GLTFLoader.prototype._loadMeshData = function (node, mesh, babylonMesh) {
+            GLTFLoader.prototype._loadMesh = function (node, mesh) {
                 var _this = this;
-                babylonMesh.name = mesh.name || babylonMesh.name;
-                var babylonMultiMaterial = new BABYLON.MultiMaterial(babylonMesh.name, this._babylonScene);
-                babylonMesh.material = babylonMultiMaterial;
-                var geometry = new BABYLON.Geometry(babylonMesh.name, this._babylonScene, null, false, babylonMesh);
+                node.babylonMesh.name = mesh.name || node.babylonMesh.name;
+                var babylonMultiMaterial = new BABYLON.MultiMaterial(node.babylonMesh.name, this._babylonScene);
+                node.babylonMesh.material = babylonMultiMaterial;
+                var geometry = new BABYLON.Geometry(node.babylonMesh.name, this._babylonScene, null, false, node.babylonMesh);
                 var vertexData = new BABYLON.VertexData();
                 vertexData.positions = [];
                 vertexData.indices = [];
@@ -638,9 +588,9 @@ var BABYLON;
                         // TODO: handle other primitive modes
                         throw new Error("Not implemented");
                     }
-                    this_1._createMorphTargets(node, mesh, primitive, babylonMesh);
+                    this_1._createMorphTargets(node, mesh, primitive, node.babylonMesh);
                     this_1._loadVertexDataAsync(primitive, function (subVertexData) {
-                        _this._loadMorphTargetsData(mesh, primitive, subVertexData, babylonMesh);
+                        _this._loadMorphTargetsData(mesh, primitive, subVertexData, node.babylonMesh);
                         subMeshInfos.push({
                             materialIndex: i,
                             verticesStart: vertexData.positions.length,
@@ -659,7 +609,7 @@ var BABYLON;
                                         }
                                         if (_this._parent.onBeforeMaterialReadyAsync) {
                                             _this.addLoaderPendingData(material);
-                                            _this._parent.onBeforeMaterialReadyAsync(babylonMaterial, babylonMesh, babylonMultiMaterial.subMaterials[i] != null, function () {
+                                            _this._parent.onBeforeMaterialReadyAsync(babylonMaterial, node.babylonMesh, babylonMultiMaterial.subMaterials[i] != null, function () {
                                                 babylonMultiMaterial.subMaterials[i] = babylonMaterial;
                                                 _this.removeLoaderPendingData(material);
                                             });
@@ -677,8 +627,8 @@ var BABYLON;
                             subMeshInfos.forEach(function (info) { return info.loadMaterial(); });
                             // TODO: optimize this so that sub meshes can be created without being overwritten after setting vertex data.
                             // Sub meshes must be cleared and created after setting vertex data because of mesh._createGlobalSubMesh.
-                            babylonMesh.subMeshes = [];
-                            subMeshInfos.forEach(function (info) { return new BABYLON.SubMesh(info.materialIndex, info.verticesStart, info.verticesCount, info.indicesStart, info.indicesCount, babylonMesh); });
+                            node.babylonMesh.subMeshes = [];
+                            subMeshInfos.forEach(function (info) { return new BABYLON.SubMesh(info.materialIndex, info.verticesStart, info.verticesCount, info.indicesStart, info.indicesCount, node.babylonMesh); });
                         }
                     });
                 };
@@ -816,7 +766,7 @@ var BABYLON;
                     _loop_3();
                 }
             };
-            GLTFLoader.prototype._loadTransform = function (node, babylonMesh) {
+            GLTFLoader.prototype._loadTransform = function (node) {
                 var position = BABYLON.Vector3.Zero();
                 var rotation = BABYLON.Quaternion.Identity();
                 var scaling = BABYLON.Vector3.One();
@@ -832,9 +782,64 @@ var BABYLON;
                     if (node.scale)
                         scaling = BABYLON.Vector3.FromArray(node.scale);
                 }
-                babylonMesh.position = position;
-                babylonMesh.rotationQuaternion = rotation;
-                babylonMesh.scaling = scaling;
+                node.babylonMesh.position = position;
+                node.babylonMesh.rotationQuaternion = rotation;
+                node.babylonMesh.scaling = scaling;
+            };
+            GLTFLoader.prototype._loadSkin = function (skin) {
+                var _this = this;
+                var skeletonId = "skeleton" + skin.index;
+                skin.babylonSkeleton = new BABYLON.Skeleton(skin.name || skeletonId, skeletonId, this._babylonScene);
+                if (skin.inverseBindMatrices === undefined) {
+                    this._loadBones(skin, null);
+                }
+                else {
+                    var accessor = this._gltf.accessors[skin.inverseBindMatrices];
+                    this._loadAccessorAsync(accessor, function (data) {
+                        _this._loadBones(skin, data);
+                    });
+                }
+                return skin.babylonSkeleton;
+            };
+            GLTFLoader.prototype._createBone = function (node, skin, parent, localMatrix, baseMatrix, index) {
+                var babylonBone = new BABYLON.Bone(node.name || "bone" + node.index, skin.babylonSkeleton, parent, localMatrix, null, baseMatrix, index);
+                node.babylonBones = node.babylonBones || {};
+                node.babylonBones[skin.index] = babylonBone;
+                node.babylonAnimationTargets = node.babylonAnimationTargets || [];
+                node.babylonAnimationTargets.push(babylonBone);
+                return babylonBone;
+            };
+            GLTFLoader.prototype._loadBones = function (skin, inverseBindMatrixData) {
+                var babylonBones = {};
+                for (var i = 0; i < skin.joints.length; i++) {
+                    var node = this._gltf.nodes[skin.joints[i]];
+                    this._loadBone(node, skin, inverseBindMatrixData, babylonBones);
+                }
+            };
+            GLTFLoader.prototype._loadBone = function (node, skin, inverseBindMatrixData, babylonBones) {
+                var babylonBone = babylonBones[node.index];
+                if (babylonBone) {
+                    return babylonBone;
+                }
+                var boneIndex = skin.joints.indexOf(node.index);
+                var baseMatrix = BABYLON.Matrix.Identity();
+                if (inverseBindMatrixData && boneIndex !== -1) {
+                    baseMatrix = BABYLON.Matrix.FromArray(inverseBindMatrixData, boneIndex * 16);
+                    baseMatrix.invertToRef(baseMatrix);
+                }
+                var babylonParentBone;
+                if (node.index != skin.skeleton && node.parent) {
+                    babylonParentBone = this._loadBone(node.parent, skin, inverseBindMatrixData, babylonBones);
+                    baseMatrix.multiplyToRef(babylonParentBone.getInvertedAbsoluteTransform(), baseMatrix);
+                }
+                babylonBone = this._createBone(node, skin, babylonParentBone, this._getNodeMatrix(node), baseMatrix, boneIndex);
+                babylonBones[node.index] = babylonBone;
+                return babylonBone;
+            };
+            GLTFLoader.prototype._getNodeMatrix = function (node) {
+                return node.matrix ?
+                    BABYLON.Matrix.FromArray(node.matrix) :
+                    BABYLON.Matrix.Compose(node.scale ? BABYLON.Vector3.FromArray(node.scale) : BABYLON.Vector3.One(), node.rotation ? BABYLON.Quaternion.FromArray(node.rotation) : BABYLON.Quaternion.Identity(), node.translation ? BABYLON.Vector3.FromArray(node.translation) : BABYLON.Vector3.Zero());
             };
             GLTFLoader.prototype._traverseNodes = function (indices, action, parentNode) {
                 if (parentNode === void 0) { parentNode = null; }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 2 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


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

@@ -709,9 +709,9 @@ declare module BABYLON.GLTF2 {
         indices?: number;
         material?: number;
         mode?: EMeshPrimitiveMode;
-        targets?: [{
+        targets?: {
             [name: string]: number;
-        }];
+        }[];
     }
     interface IGLTFMesh extends IGLTFChildRootProperty {
         primitives: IGLTFMeshPrimitive[];
@@ -730,7 +730,7 @@ declare module BABYLON.GLTF2 {
         index?: number;
         parent?: IGLTFNode;
         babylonMesh?: Mesh;
-        babylonSkinToBones?: {
+        babylonBones?: {
             [skin: number]: Bone;
         };
         babylonAnimationTargets?: Node[];
@@ -826,15 +826,17 @@ declare module BABYLON.GLTF2 {
         private _showMeshes();
         private _startAnimations();
         private _loadScene(nodeNames);
-        private _loadSkin(node);
-        private _updateBone(node, parentNode, skin, inverseBindMatrixData);
-        private _createBone(node, skin);
-        private _loadMesh(node);
-        private _loadMeshData(node, mesh, babylonMesh);
+        private _loadNode(node);
+        private _loadMesh(node, mesh);
         private _loadVertexDataAsync(primitive, onSuccess);
         private _createMorphTargets(node, mesh, primitive, babylonMesh);
         private _loadMorphTargetsData(mesh, primitive, vertexData, babylonMesh);
-        private _loadTransform(node, babylonMesh);
+        private _loadTransform(node);
+        private _loadSkin(skin);
+        private _createBone(node, skin, parent, localMatrix, baseMatrix, index);
+        private _loadBones(skin, inverseBindMatrixData);
+        private _loadBone(node, skin, inverseBindMatrixData, babylonBones);
+        private _getNodeMatrix(node);
         private _traverseNodes(indices, action, parentNode?);
         private _traverseNode(index, action, parentNode?);
         private _loadAnimations();

+ 78 - 73
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -2705,85 +2705,35 @@ var BABYLON;
                     });
                     nodeIndices = filteredNodeIndices;
                 }
-                this._traverseNodes(nodeIndices, function (node) { return _this._loadSkin(node); });
-                this._traverseNodes(nodeIndices, function (node) { return _this._loadMesh(node); });
+                this._traverseNodes(nodeIndices, function (node) { return _this._loadNode(node); });
             };
-            GLTFLoader.prototype._loadSkin = function (node) {
-                var _this = this;
-                if (node.skin !== undefined) {
-                    var skin = this._gltf.skins[node.skin];
-                    var skeletonId = "skeleton" + node.skin;
-                    skin.babylonSkeleton = new BABYLON.Skeleton(skin.name || skeletonId, skeletonId, this._babylonScene);
-                    skin.index = node.skin;
-                    for (var i = 0; i < skin.joints.length; i++) {
-                        this._createBone(this._gltf.nodes[skin.joints[i]], skin);
-                    }
-                    if (skin.skeleton === undefined) {
-                        // TODO: handle when skeleton is not defined
-                        throw new Error("Not implemented");
-                    }
-                    if (skin.inverseBindMatrices === undefined) {
-                        // TODO: handle when inverse bind matrices are not defined
-                        throw new Error("Not implemented");
-                    }
-                    var accessor = this._gltf.accessors[skin.inverseBindMatrices];
-                    this._loadAccessorAsync(accessor, function (data) {
-                        _this._traverseNode(skin.skeleton, function (node, index, parent) { return _this._updateBone(node, parent, skin, data); });
-                    });
-                }
-                return true;
-            };
-            GLTFLoader.prototype._updateBone = function (node, parentNode, skin, inverseBindMatrixData) {
-                var jointIndex = skin.joints.indexOf(node.index);
-                if (jointIndex === -1) {
-                    this._createBone(node, skin);
-                }
-                var babylonBone = node.babylonSkinToBones[skin.index];
-                // TODO: explain the math
-                var matrix = jointIndex === -1 ? BABYLON.Matrix.Identity() : BABYLON.Matrix.FromArray(inverseBindMatrixData, jointIndex * 16);
-                matrix.invertToRef(matrix);
-                if (parentNode) {
-                    babylonBone.setParent(parentNode.babylonSkinToBones[skin.index], false);
-                    matrix.multiplyToRef(babylonBone.getParent().getInvertedAbsoluteTransform(), matrix);
-                }
-                babylonBone.updateMatrix(matrix);
-                return true;
-            };
-            GLTFLoader.prototype._createBone = function (node, skin) {
-                var babylonBone = new BABYLON.Bone(node.name || "bone" + node.index, skin.babylonSkeleton);
-                node.babylonSkinToBones = node.babylonSkinToBones || {};
-                node.babylonSkinToBones[skin.index] = babylonBone;
-                node.babylonAnimationTargets = node.babylonAnimationTargets || [];
-                node.babylonAnimationTargets.push(babylonBone);
-                return babylonBone;
-            };
-            GLTFLoader.prototype._loadMesh = function (node) {
-                var babylonMesh = new BABYLON.Mesh(node.name || "mesh" + node.index, this._babylonScene);
-                babylonMesh.isVisible = false;
-                this._loadTransform(node, babylonMesh);
+            GLTFLoader.prototype._loadNode = function (node) {
+                node.babylonMesh = new BABYLON.Mesh(node.name || "mesh" + node.index, this._babylonScene);
+                node.babylonMesh.isVisible = false;
+                this._loadTransform(node);
                 if (node.mesh !== undefined) {
                     var mesh = this._gltf.meshes[node.mesh];
-                    this._loadMeshData(node, mesh, babylonMesh);
+                    this._loadMesh(node, mesh);
                 }
-                babylonMesh.parent = node.parent ? node.parent.babylonMesh : null;
-                node.babylonMesh = babylonMesh;
+                node.babylonMesh.parent = node.parent ? node.parent.babylonMesh : null;
                 node.babylonAnimationTargets = node.babylonAnimationTargets || [];
                 node.babylonAnimationTargets.push(node.babylonMesh);
                 if (node.skin !== undefined) {
                     var skin = this._gltf.skins[node.skin];
-                    babylonMesh.skeleton = skin.babylonSkeleton;
+                    skin.index = node.skin;
+                    node.babylonMesh.skeleton = this._loadSkin(skin);
                 }
                 if (node.camera !== undefined) {
                     // TODO: handle cameras
                 }
                 return true;
             };
-            GLTFLoader.prototype._loadMeshData = function (node, mesh, babylonMesh) {
+            GLTFLoader.prototype._loadMesh = function (node, mesh) {
                 var _this = this;
-                babylonMesh.name = mesh.name || babylonMesh.name;
-                var babylonMultiMaterial = new BABYLON.MultiMaterial(babylonMesh.name, this._babylonScene);
-                babylonMesh.material = babylonMultiMaterial;
-                var geometry = new BABYLON.Geometry(babylonMesh.name, this._babylonScene, null, false, babylonMesh);
+                node.babylonMesh.name = mesh.name || node.babylonMesh.name;
+                var babylonMultiMaterial = new BABYLON.MultiMaterial(node.babylonMesh.name, this._babylonScene);
+                node.babylonMesh.material = babylonMultiMaterial;
+                var geometry = new BABYLON.Geometry(node.babylonMesh.name, this._babylonScene, null, false, node.babylonMesh);
                 var vertexData = new BABYLON.VertexData();
                 vertexData.positions = [];
                 vertexData.indices = [];
@@ -2796,9 +2746,9 @@ var BABYLON;
                         // TODO: handle other primitive modes
                         throw new Error("Not implemented");
                     }
-                    this_1._createMorphTargets(node, mesh, primitive, babylonMesh);
+                    this_1._createMorphTargets(node, mesh, primitive, node.babylonMesh);
                     this_1._loadVertexDataAsync(primitive, function (subVertexData) {
-                        _this._loadMorphTargetsData(mesh, primitive, subVertexData, babylonMesh);
+                        _this._loadMorphTargetsData(mesh, primitive, subVertexData, node.babylonMesh);
                         subMeshInfos.push({
                             materialIndex: i,
                             verticesStart: vertexData.positions.length,
@@ -2817,7 +2767,7 @@ var BABYLON;
                                         }
                                         if (_this._parent.onBeforeMaterialReadyAsync) {
                                             _this.addLoaderPendingData(material);
-                                            _this._parent.onBeforeMaterialReadyAsync(babylonMaterial, babylonMesh, babylonMultiMaterial.subMaterials[i] != null, function () {
+                                            _this._parent.onBeforeMaterialReadyAsync(babylonMaterial, node.babylonMesh, babylonMultiMaterial.subMaterials[i] != null, function () {
                                                 babylonMultiMaterial.subMaterials[i] = babylonMaterial;
                                                 _this.removeLoaderPendingData(material);
                                             });
@@ -2835,8 +2785,8 @@ var BABYLON;
                             subMeshInfos.forEach(function (info) { return info.loadMaterial(); });
                             // TODO: optimize this so that sub meshes can be created without being overwritten after setting vertex data.
                             // Sub meshes must be cleared and created after setting vertex data because of mesh._createGlobalSubMesh.
-                            babylonMesh.subMeshes = [];
-                            subMeshInfos.forEach(function (info) { return new BABYLON.SubMesh(info.materialIndex, info.verticesStart, info.verticesCount, info.indicesStart, info.indicesCount, babylonMesh); });
+                            node.babylonMesh.subMeshes = [];
+                            subMeshInfos.forEach(function (info) { return new BABYLON.SubMesh(info.materialIndex, info.verticesStart, info.verticesCount, info.indicesStart, info.indicesCount, node.babylonMesh); });
                         }
                     });
                 };
@@ -2974,7 +2924,7 @@ var BABYLON;
                     _loop_3();
                 }
             };
-            GLTFLoader.prototype._loadTransform = function (node, babylonMesh) {
+            GLTFLoader.prototype._loadTransform = function (node) {
                 var position = BABYLON.Vector3.Zero();
                 var rotation = BABYLON.Quaternion.Identity();
                 var scaling = BABYLON.Vector3.One();
@@ -2990,9 +2940,64 @@ var BABYLON;
                     if (node.scale)
                         scaling = BABYLON.Vector3.FromArray(node.scale);
                 }
-                babylonMesh.position = position;
-                babylonMesh.rotationQuaternion = rotation;
-                babylonMesh.scaling = scaling;
+                node.babylonMesh.position = position;
+                node.babylonMesh.rotationQuaternion = rotation;
+                node.babylonMesh.scaling = scaling;
+            };
+            GLTFLoader.prototype._loadSkin = function (skin) {
+                var _this = this;
+                var skeletonId = "skeleton" + skin.index;
+                skin.babylonSkeleton = new BABYLON.Skeleton(skin.name || skeletonId, skeletonId, this._babylonScene);
+                if (skin.inverseBindMatrices === undefined) {
+                    this._loadBones(skin, null);
+                }
+                else {
+                    var accessor = this._gltf.accessors[skin.inverseBindMatrices];
+                    this._loadAccessorAsync(accessor, function (data) {
+                        _this._loadBones(skin, data);
+                    });
+                }
+                return skin.babylonSkeleton;
+            };
+            GLTFLoader.prototype._createBone = function (node, skin, parent, localMatrix, baseMatrix, index) {
+                var babylonBone = new BABYLON.Bone(node.name || "bone" + node.index, skin.babylonSkeleton, parent, localMatrix, null, baseMatrix, index);
+                node.babylonBones = node.babylonBones || {};
+                node.babylonBones[skin.index] = babylonBone;
+                node.babylonAnimationTargets = node.babylonAnimationTargets || [];
+                node.babylonAnimationTargets.push(babylonBone);
+                return babylonBone;
+            };
+            GLTFLoader.prototype._loadBones = function (skin, inverseBindMatrixData) {
+                var babylonBones = {};
+                for (var i = 0; i < skin.joints.length; i++) {
+                    var node = this._gltf.nodes[skin.joints[i]];
+                    this._loadBone(node, skin, inverseBindMatrixData, babylonBones);
+                }
+            };
+            GLTFLoader.prototype._loadBone = function (node, skin, inverseBindMatrixData, babylonBones) {
+                var babylonBone = babylonBones[node.index];
+                if (babylonBone) {
+                    return babylonBone;
+                }
+                var boneIndex = skin.joints.indexOf(node.index);
+                var baseMatrix = BABYLON.Matrix.Identity();
+                if (inverseBindMatrixData && boneIndex !== -1) {
+                    baseMatrix = BABYLON.Matrix.FromArray(inverseBindMatrixData, boneIndex * 16);
+                    baseMatrix.invertToRef(baseMatrix);
+                }
+                var babylonParentBone;
+                if (node.index != skin.skeleton && node.parent) {
+                    babylonParentBone = this._loadBone(node.parent, skin, inverseBindMatrixData, babylonBones);
+                    baseMatrix.multiplyToRef(babylonParentBone.getInvertedAbsoluteTransform(), baseMatrix);
+                }
+                babylonBone = this._createBone(node, skin, babylonParentBone, this._getNodeMatrix(node), baseMatrix, boneIndex);
+                babylonBones[node.index] = babylonBone;
+                return babylonBone;
+            };
+            GLTFLoader.prototype._getNodeMatrix = function (node) {
+                return node.matrix ?
+                    BABYLON.Matrix.FromArray(node.matrix) :
+                    BABYLON.Matrix.Compose(node.scale ? BABYLON.Vector3.FromArray(node.scale) : BABYLON.Vector3.One(), node.rotation ? BABYLON.Quaternion.FromArray(node.rotation) : BABYLON.Quaternion.Identity(), node.translation ? BABYLON.Vector3.FromArray(node.translation) : BABYLON.Vector3.Zero());
             };
             GLTFLoader.prototype._traverseNodes = function (indices, action, parentNode) {
                 if (parentNode === void 0) { parentNode = null; }

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 3 - 3
dist/preview release/loaders/babylon.glTFFileLoader.min.js


+ 94 - 86
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -286,92 +286,29 @@ module BABYLON.GLTF2 {
                 nodeIndices = filteredNodeIndices;
             }
 
-            this._traverseNodes(nodeIndices, node => this._loadSkin(node));
-            this._traverseNodes(nodeIndices, node => this._loadMesh(node));
+            this._traverseNodes(nodeIndices, node => this._loadNode(node));
         }
 
-        private _loadSkin(node: IGLTFNode): boolean {
-            if (node.skin !== undefined) {
-                var skin = this._gltf.skins[node.skin];
-                var skeletonId = "skeleton" + node.skin;
-                skin.babylonSkeleton = new Skeleton(skin.name || skeletonId, skeletonId, this._babylonScene);
-                skin.index = node.skin;
-
-                for (var i = 0; i < skin.joints.length; i++) {
-                    this._createBone(this._gltf.nodes[skin.joints[i]], skin);
-                }
-
-                if (skin.skeleton === undefined) {
-                    // TODO: handle when skeleton is not defined
-                    throw new Error("Not implemented");
-                }
-
-                if (skin.inverseBindMatrices === undefined) {
-                    // TODO: handle when inverse bind matrices are not defined
-                    throw new Error("Not implemented");
-                }
-
-                var accessor = this._gltf.accessors[skin.inverseBindMatrices];
-                this._loadAccessorAsync(accessor, data => {
-                    this._traverseNode(skin.skeleton, (node, index, parent) => this._updateBone(node, parent, skin, <Float32Array>data));
-                });
-            }
-
-            return true;
-        }
-
-        private _updateBone(node: IGLTFNode, parentNode: IGLTFNode, skin: IGLTFSkin, inverseBindMatrixData: Float32Array): boolean {
-            var jointIndex = skin.joints.indexOf(node.index);
-            if (jointIndex === -1) {
-                this._createBone(node, skin);
-            }
-
-            var babylonBone = node.babylonSkinToBones[skin.index];
-
-            // TODO: explain the math
-            var matrix = jointIndex === -1 ? Matrix.Identity() : Matrix.FromArray(inverseBindMatrixData, jointIndex * 16);
-            matrix.invertToRef(matrix);
-            if (parentNode) {
-                babylonBone.setParent(parentNode.babylonSkinToBones[skin.index], false);
-                matrix.multiplyToRef(babylonBone.getParent().getInvertedAbsoluteTransform(), matrix);
-            }
-
-            babylonBone.updateMatrix(matrix);
-            return true;
-        }
-
-        private _createBone(node: IGLTFNode, skin: IGLTFSkin): Bone {
-            var babylonBone = new Bone(node.name || "bone" + node.index, skin.babylonSkeleton);
-
-            node.babylonSkinToBones = node.babylonSkinToBones || {};
-            node.babylonSkinToBones[skin.index] = babylonBone;
-
-            node.babylonAnimationTargets = node.babylonAnimationTargets || [];
-            node.babylonAnimationTargets.push(babylonBone);
-
-            return babylonBone;
-        }
+        private _loadNode(node: IGLTFNode): boolean {
+            node.babylonMesh = new Mesh(node.name || "mesh" + node.index, this._babylonScene);
+            node.babylonMesh.isVisible = false;
 
-        private _loadMesh(node: IGLTFNode): boolean {
-            var babylonMesh = new Mesh(node.name || "mesh" + node.index, this._babylonScene);
-            babylonMesh.isVisible = false;
-
-            this._loadTransform(node, babylonMesh);
+            this._loadTransform(node);
 
             if (node.mesh !== undefined) {
                 var mesh = this._gltf.meshes[node.mesh];
-                this._loadMeshData(node, mesh, babylonMesh);
+                this._loadMesh(node, mesh);
             }
 
-            babylonMesh.parent = node.parent ? node.parent.babylonMesh : null;
-            node.babylonMesh = babylonMesh;
+            node.babylonMesh.parent = node.parent ? node.parent.babylonMesh : null;
 
             node.babylonAnimationTargets = node.babylonAnimationTargets || [];
             node.babylonAnimationTargets.push(node.babylonMesh);
 
             if (node.skin !== undefined) {
                 var skin = this._gltf.skins[node.skin];
-                babylonMesh.skeleton = skin.babylonSkeleton;
+                skin.index = node.skin;
+                node.babylonMesh.skeleton = this._loadSkin(skin);
             }
 
             if (node.camera !== undefined) {
@@ -381,13 +318,13 @@ module BABYLON.GLTF2 {
             return true;
         }
 
-        private _loadMeshData(node: IGLTFNode, mesh: IGLTFMesh, babylonMesh: Mesh): void {
-            babylonMesh.name = mesh.name || babylonMesh.name;
+        private _loadMesh(node: IGLTFNode, mesh: IGLTFMesh): void {
+            node.babylonMesh.name = mesh.name || node.babylonMesh.name;
 
-            var babylonMultiMaterial = new MultiMaterial(babylonMesh.name, this._babylonScene);
-            babylonMesh.material = babylonMultiMaterial;
+            var babylonMultiMaterial = new MultiMaterial(node.babylonMesh.name, this._babylonScene);
+            node.babylonMesh.material = babylonMultiMaterial;
 
-            var geometry = new Geometry(babylonMesh.name, this._babylonScene, null, false, babylonMesh);
+            var geometry = new Geometry(node.babylonMesh.name, this._babylonScene, null, false, node.babylonMesh);
             var vertexData = new VertexData();
             vertexData.positions = [];
             vertexData.indices = [];
@@ -403,10 +340,10 @@ module BABYLON.GLTF2 {
                     throw new Error("Not implemented");
                 }
 
-                this._createMorphTargets(node, mesh, primitive, babylonMesh);
+                this._createMorphTargets(node, mesh, primitive, node.babylonMesh);
 
                 this._loadVertexDataAsync(primitive, subVertexData => {
-                    this._loadMorphTargetsData(mesh, primitive, subVertexData, babylonMesh);
+                    this._loadMorphTargetsData(mesh, primitive, subVertexData, node.babylonMesh);
 
                     subMeshInfos.push({
                         materialIndex: i,
@@ -427,7 +364,7 @@ module BABYLON.GLTF2 {
                                     
                                     if (this._parent.onBeforeMaterialReadyAsync) {
                                         this.addLoaderPendingData(material);
-                                        this._parent.onBeforeMaterialReadyAsync(babylonMaterial, babylonMesh, babylonMultiMaterial.subMaterials[i] != null, () => {
+                                        this._parent.onBeforeMaterialReadyAsync(babylonMaterial, node.babylonMesh, babylonMultiMaterial.subMaterials[i] != null, () => {
                                             babylonMultiMaterial.subMaterials[i] = babylonMaterial;
                                             this.removeLoaderPendingData(material);
                                         });
@@ -448,8 +385,8 @@ module BABYLON.GLTF2 {
 
                         // TODO: optimize this so that sub meshes can be created without being overwritten after setting vertex data.
                         // Sub meshes must be cleared and created after setting vertex data because of mesh._createGlobalSubMesh.
-                        babylonMesh.subMeshes = [];
-                        subMeshInfos.forEach(info => new SubMesh(info.materialIndex, info.verticesStart, info.verticesCount, info.indicesStart, info.indicesCount, babylonMesh));
+                        node.babylonMesh.subMeshes = [];
+                        subMeshInfos.forEach(info => new SubMesh(info.materialIndex, info.verticesStart, info.verticesCount, info.indicesStart, info.indicesCount, node.babylonMesh));
                     }
                 });
             }
@@ -582,7 +519,7 @@ module BABYLON.GLTF2 {
             }
         }
 
-        private _loadTransform(node: IGLTFNode, babylonMesh: Mesh): void {
+        private _loadTransform(node: IGLTFNode): void {
             var position: Vector3 = Vector3.Zero();
             var rotation: Quaternion = Quaternion.Identity();
             var scaling: Vector3 = Vector3.One();
@@ -597,9 +534,80 @@ module BABYLON.GLTF2 {
                 if (node.scale) scaling = Vector3.FromArray(node.scale);
             }
 
-            babylonMesh.position = position;
-            babylonMesh.rotationQuaternion = rotation;
-            babylonMesh.scaling = scaling;
+            node.babylonMesh.position = position;
+            node.babylonMesh.rotationQuaternion = rotation;
+            node.babylonMesh.scaling = scaling;
+        }
+
+        private _loadSkin(skin: IGLTFSkin): Skeleton {
+            var skeletonId = "skeleton" + skin.index;
+            skin.babylonSkeleton = new Skeleton(skin.name || skeletonId, skeletonId, this._babylonScene);
+
+            if (skin.inverseBindMatrices === undefined) {
+                this._loadBones(skin, null);
+            }
+            else {
+                var accessor = this._gltf.accessors[skin.inverseBindMatrices];
+                this._loadAccessorAsync(accessor, data => {
+                    this._loadBones(skin, <Float32Array>data);
+                });
+            }
+
+            return skin.babylonSkeleton;
+        }
+
+        private _createBone(node: IGLTFNode, skin: IGLTFSkin, parent: Bone, localMatrix: Matrix, baseMatrix: Matrix, index: number): Bone {
+            var babylonBone = new Bone(node.name || "bone" + node.index, skin.babylonSkeleton, parent, localMatrix, null, baseMatrix, index);
+
+            node.babylonBones = node.babylonBones || {};
+            node.babylonBones[skin.index] = babylonBone;
+
+            node.babylonAnimationTargets = node.babylonAnimationTargets || [];
+            node.babylonAnimationTargets.push(babylonBone);
+
+            return babylonBone;
+        }
+
+        private _loadBones(skin: IGLTFSkin, inverseBindMatrixData: Float32Array): void {
+            var babylonBones: { [index: number]: Bone } = {};
+            for (var i = 0; i < skin.joints.length; i++) {
+                var node = this._gltf.nodes[skin.joints[i]];
+                this._loadBone(node, skin, inverseBindMatrixData, babylonBones);
+            }
+        }
+
+        private _loadBone(node: IGLTFNode, skin: IGLTFSkin, inverseBindMatrixData: Float32Array, babylonBones: { [index: number]: Bone }): Bone {
+            var babylonBone = babylonBones[node.index];
+            if (babylonBone) {
+                return babylonBone;
+            }
+
+            var boneIndex = skin.joints.indexOf(node.index);
+
+            var baseMatrix = Matrix.Identity();
+            if (inverseBindMatrixData && boneIndex !== -1) {
+                baseMatrix = Matrix.FromArray(inverseBindMatrixData, boneIndex * 16);
+                baseMatrix.invertToRef(baseMatrix);
+            }
+
+            var babylonParentBone: Bone;
+            if (node.index != skin.skeleton && node.parent) {
+                babylonParentBone = this._loadBone(node.parent, skin, inverseBindMatrixData, babylonBones);
+                baseMatrix.multiplyToRef(babylonParentBone.getInvertedAbsoluteTransform(), baseMatrix);
+            }
+
+            babylonBone = this._createBone(node, skin, babylonParentBone, this._getNodeMatrix(node), baseMatrix, boneIndex);
+            babylonBones[node.index] = babylonBone;
+            return babylonBone;
+        }
+
+        private _getNodeMatrix(node: IGLTFNode): Matrix {
+            return node.matrix ?
+                Matrix.FromArray(node.matrix) :
+                Matrix.Compose(
+                    node.scale ? Vector3.FromArray(node.scale) : Vector3.One(),
+                    node.rotation ? Quaternion.FromArray(node.rotation) : Quaternion.Identity(),
+                    node.translation ? Vector3.FromArray(node.translation) : Vector3.Zero());
         }
 
         private _traverseNodes(indices: number[], action: (node: IGLTFNode, index: number, parentNode: IGLTFNode) => boolean, parentNode: IGLTFNode = null): void {

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

@@ -193,7 +193,7 @@ module BABYLON.GLTF2 {
         indices?: number;
         material?: number;
         mode?: EMeshPrimitiveMode;
-        targets?: [ { [name: string]: number } ];
+        targets?: { [name: string]: number }[];
     }
 
     export interface IGLTFMesh extends IGLTFChildRootProperty {
@@ -216,7 +216,7 @@ module BABYLON.GLTF2 {
         index?: number;
         parent?: IGLTFNode;
         babylonMesh?: Mesh;
-        babylonSkinToBones?: { [skin: number]: Bone };
+        babylonBones?: { [skin: number]: Bone };
         babylonAnimationTargets?: Node[];
     }
 

+ 5 - 1
src/Animations/babylon.runtimeAnimation.ts

@@ -274,7 +274,11 @@
 
             //to and from cannot be the same key
             if(from === to) {
-                from++;
+                if (from > keys[0].frame) {
+                    from--;
+                } else if (to < keys[keys.length - 1].frame) {
+                    to++;
+                }
             }
             
             // Compute ratio

+ 2 - 2
src/Cameras/Inputs/babylon.arcRotateCameraKeyboardMoveInput.ts

@@ -19,10 +19,10 @@ module BABYLON {
         public keysReset = [220];
 
         @serialize()
-        public panningSensibility: number = 300.0;
+        public panningSensibility: number = 50.0;
 
         @serialize()
-        public zoomingSensibility: number = 50.0;
+        public zoomingSensibility: number = 25.0;
 
         @serialize()
         public useAltToZoom: boolean = true;

+ 81 - 36
src/Cameras/Inputs/babylon.arcRotateCameraPointersInput.ts

@@ -14,17 +14,19 @@ module BABYLON {
         public angularSensibilityY = 1000.0;
 
         @serialize()
-        public pinchPrecision = 6.0;
+        public pinchPrecision = 12.0;
 
         @serialize()
-        public panningSensibility: number = 3000.0;
+        public panningSensibility: number = 1000.0;
 
         @serialize()
         public multiTouchPanning: boolean = true;
 
+        @serialize()
+        public multiTouchPanAndZoom: boolean = true;
+
         private _isPanClick: boolean = false;
         public pinchInwards = true;
-        
 
         private _pointerInput: (p: PointerInfo, s: EventState) => void;
         private _observer: Observer<PointerInfo>;
@@ -90,16 +92,34 @@ module BABYLON {
 
                     cacheSoloPointer = null;
                     previousPinchSquaredDistance = 0;
-                    previousMultiTouchPanPosition.isPaning = false;    
-                    previousMultiTouchPanPosition.isPinching = false;     
-                    twoFingerActivityCount = 0;  
-                    initialDistance = 0;            
+                    previousMultiTouchPanPosition.isPaning = false;
+                    previousMultiTouchPanPosition.isPinching = false;
+                    twoFingerActivityCount = 0;
+                    initialDistance = 0;
 
                     //would be better to use pointers.remove(evt.pointerId) for multitouch gestures, 
                     //but emptying completly pointers collection is required to fix a bug on iPhone : 
                     //when changing orientation while pinching camera, one pointer stay pressed forever if we don't release all pointers  
                     //will be ok to put back pointers.remove(evt.pointerId); when iPhone bug corrected
-                    pointA = pointB = undefined;
+                    if (engine.badOS) {
+                        pointA = pointB = undefined;
+                    }
+                    else {
+                        //only remove the impacted pointer in case of multitouch allowing on most 
+                        //platforms switching from rotate to zoom and pan seamlessly.
+                        if (pointB && pointA && pointA.pointerId == evt.pointerId) {
+                            pointA = pointB;
+                            pointB = undefined;
+                            cacheSoloPointer = { x: pointA.x, y: pointA.y, pointerId: pointA.pointerId, type: evt.pointerType };
+                        }
+                        else if (pointA && pointB && pointB.pointerId == evt.pointerId) {
+                            pointB = undefined;
+                            cacheSoloPointer = { x: pointA.x, y: pointA.y, pointerId: pointA.pointerId, type: evt.pointerType };
+                        }
+                        else {
+                            pointA = pointB = undefined;
+                        }
+                    }
 
                     if (!noPreventDefault) {
                         evt.preventDefault();
@@ -137,41 +157,66 @@ module BABYLON {
                         var distY = pointA.y - pointB.y;
                         var pinchSquaredDistance = (distX * distX) + (distY * distY);
                         var pinchDistance = Math.sqrt(pinchSquaredDistance);
+
                         if (previousPinchSquaredDistance === 0) {
                             initialDistance = pinchDistance;
                             previousPinchSquaredDistance = pinchSquaredDistance;
+                            previousMultiTouchPanPosition.x = (pointA.x + pointB.x) / 2;
+                            previousMultiTouchPanPosition.y = (pointA.y + pointB.y) / 2;
                             return;
                         }
 
-                        twoFingerActivityCount++;
-
-                        if (previousMultiTouchPanPosition.isPinching || (twoFingerActivityCount < 20 && Math.abs(pinchDistance - initialDistance) > this.camera.pinchToPanMaxDistance)) {                   
+                        if (this.multiTouchPanAndZoom) {
                             this.camera
-                            .inertialRadiusOffset += (pinchSquaredDistance - previousPinchSquaredDistance) /
-                            (this.pinchPrecision *
-                                ((this.angularSensibilityX + this.angularSensibilityY) / 2) *
-                                direction);
-                            previousMultiTouchPanPosition.isPaning = false;
-                            previousMultiTouchPanPosition.isPinching = true;
+                                .inertialRadiusOffset += (pinchSquaredDistance - previousPinchSquaredDistance) /
+                                (this.pinchPrecision *
+                                    ((this.angularSensibilityX + this.angularSensibilityY) / 2) *
+                                    direction);
+                            
+                            if (this.panningSensibility !== 0) {
+                                var pointersCenterX = (pointA.x + pointB.x) / 2;
+                                var pointersCenterY = (pointA.y + pointB.y) / 2;
+                                var pointersCenterDistX = pointersCenterX - previousMultiTouchPanPosition.x;
+                                var pointersCenterDistY = pointersCenterY - previousMultiTouchPanPosition.y;
+
+                                previousMultiTouchPanPosition.x = pointersCenterX;
+                                previousMultiTouchPanPosition.y = pointersCenterY;
+
+                                this.camera.inertialPanningX += -(pointersCenterDistX) / (this.panningSensibility);
+                                this.camera.inertialPanningY += (pointersCenterDistY) / (this.panningSensibility);
+                            }
                         }
                         else {
-                            if (cacheSoloPointer.pointerId === ed.pointerId && this.panningSensibility !== 0 && this.multiTouchPanning) {
-                                if (!previousMultiTouchPanPosition.isPaning) {
-                                    previousMultiTouchPanPosition.isPaning = true;
-                                    previousMultiTouchPanPosition.isPinching = false;
-                                    previousMultiTouchPanPosition.x = ed.x;
-                                    previousMultiTouchPanPosition.y = ed.y;
-                                    return;
+                            twoFingerActivityCount++;
+
+                            if (previousMultiTouchPanPosition.isPinching || (twoFingerActivityCount < 20 && Math.abs(pinchDistance - initialDistance) > this.camera.pinchToPanMaxDistance)) {                   
+                                this.camera
+                                .inertialRadiusOffset += (pinchSquaredDistance - previousPinchSquaredDistance) /
+                                (this.pinchPrecision *
+                                    ((this.angularSensibilityX + this.angularSensibilityY) / 2) *
+                                    direction);
+                                previousMultiTouchPanPosition.isPaning = false;
+                                previousMultiTouchPanPosition.isPinching = true;
+                            }
+                            else {
+                                if (cacheSoloPointer.pointerId === ed.pointerId && this.panningSensibility !== 0 && this.multiTouchPanning) {
+                                    if (!previousMultiTouchPanPosition.isPaning) {
+                                        previousMultiTouchPanPosition.isPaning = true;
+                                        previousMultiTouchPanPosition.isPinching = false;
+                                        previousMultiTouchPanPosition.x = ed.x;
+                                        previousMultiTouchPanPosition.y = ed.y;
+                                        return;
+                                    }
+
+                                    this.camera.inertialPanningX += -(ed.x - previousMultiTouchPanPosition.x) / (this.panningSensibility);
+                                    this.camera.inertialPanningY += (ed.y - previousMultiTouchPanPosition.y) / (this.panningSensibility);
                                 }
-
-                                this.camera.inertialPanningX += -(ed.x - previousMultiTouchPanPosition.x) / (this.panningSensibility * 0.5);
-                                this.camera.inertialPanningY += (ed.y - previousMultiTouchPanPosition.y) / (this.panningSensibility * 0.5);
                             }
-                        }
 
-                        if (cacheSoloPointer.pointerId === evt.pointerId) {
-                            previousMultiTouchPanPosition.x = ed.x;
-                            previousMultiTouchPanPosition.y = ed.y;
+                            if (cacheSoloPointer.pointerId === evt.pointerId) {
+                                previousMultiTouchPanPosition.x = ed.x;
+                                previousMultiTouchPanPosition.y = ed.y;
+                            }
                         }
 
                         previousPinchSquaredDistance = pinchSquaredDistance;
@@ -193,11 +238,11 @@ module BABYLON {
                 //this._keys = [];
                 pointA = pointB = undefined;
                 previousPinchSquaredDistance = 0;
-                previousMultiTouchPanPosition.isPaning = false;    
-                previousMultiTouchPanPosition.isPinching = false;     
-                twoFingerActivityCount = 0;                       
-                cacheSoloPointer = null;     
-                initialDistance = 0;        
+                previousMultiTouchPanPosition.isPaning = false;
+                previousMultiTouchPanPosition.isPinching = false;
+                twoFingerActivityCount = 0;
+                cacheSoloPointer = null;
+                initialDistance = 0;
             };
 
             this._onMouseMove = evt => {

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 129 - 36
src/Cameras/VR/babylon.vrExperienceHelper.ts


+ 24 - 26
src/Cameras/VR/babylon.webVRCamera.ts

@@ -40,7 +40,7 @@ module BABYLON {
     export class WebVRFreeCamera extends FreeCamera implements PoseControlled {
         public _vrDevice = null;
         public rawPose: DevicePose = null;
-        private _vrEnabled = false;
+        private _onVREnabled: (success: boolean) => void;
         private _specsVersion: number = 1.1;
         private _attached: boolean = false;
 
@@ -95,7 +95,20 @@ module BABYLON {
             }
 
             //enable VR
-            this.getEngine().initWebVR();
+            var engine = this.getEngine();
+            this._onVREnabled = (success:boolean) => { if (success) { this.initControllers(); } };
+            engine.onVRRequestPresentComplete.add(this._onVREnabled);
+            engine.initWebVR().add((event:IDisplayChangedEventArgs) => {
+                
+                this._vrDevice = event.vrDisplay;
+
+                //reset the rig parameters.
+                this.setCameraRigMode(Camera.RIG_MODE_WEBVR, { parentCamera: this, vrDisplay: this._vrDevice, frameData: this._frameData, specs: this._specsVersion });
+
+                if (this._attached && this._vrDevice) {
+                    this.getEngine().enableVR();
+                }
+            });
 
             //check specs version
             if (!window.VRFrameData) {
@@ -106,22 +119,6 @@ module BABYLON {
                 this._frameData = new VRFrameData();
             }
 
-            this.getEngine().getVRDevice(this.webVROptions.displayName, device => {
-                if (!device) {
-                    return;
-                }
-
-                this._vrEnabled = true;               
-                this._vrDevice = device;
-
-                //reset the rig parameters.
-                this.setCameraRigMode(Camera.RIG_MODE_WEBVR, { parentCamera: this, vrDisplay: this._vrDevice, frameData: this._frameData, specs: this._specsVersion });
-
-                if (this._attached) {
-                    this.getEngine().enableVR(this._vrDevice)
-                }
-            });                
-
             /**
              * The idea behind the following lines:
              * objects that have the camera as parent should actually have the rig cameras as a parent.
@@ -155,6 +152,11 @@ module BABYLON {
                 }
             });
         }
+        
+        public dispose(): void {
+            this.getEngine().onVRRequestPresentComplete.removeCallback(this._onVREnabled);
+            super.dispose();
+        }
 
         public getControllerByName(name: string): WebVRController {
             for (var gp of this.controllers) {
@@ -194,11 +196,11 @@ module BABYLON {
         } 
 
         public _checkInputs(): void {
-            if (this._vrEnabled) {
+            if (this._vrDevice && this._vrDevice.isPresenting) {
                 if (this._specsVersion === 1.1) {
                     this._vrDevice.getFrameData(this._frameData);
                 } else {
-                    //backwards comp
+                    // TODO: Does backwards comp need to be here any more? The Engine class doesn't support it any more.
                     let pose = this._vrDevice.getPose();
                     this._frameData.pose = pose;
                 }
@@ -244,12 +246,9 @@ module BABYLON {
 
             noPreventDefault = Camera.ForceAttachControlToAlwaysPreventDefault ? false : noPreventDefault;
 
-            if (this._vrEnabled) {
-                this.getEngine().enableVR(this._vrDevice);
+            if (this._vrDevice) {
+                this.getEngine().enableVR();
             }
-
-            // try to attach the controllers, if found.
-            this.initControllers();
         }
 
         public detachControl(element: HTMLElement): void {
@@ -257,7 +256,6 @@ module BABYLON {
             this.getScene().gamepadManager.onGamepadDisconnectedObservable.remove(this._onGamepadDisconnectedObserver);
             
             super.detachControl(element);
-            this._vrEnabled = false;
             this._attached = false;
             this.getEngine().disableVR();
         }

+ 5 - 5
src/Materials/babylon.effect.ts

@@ -398,14 +398,14 @@
             result = result.replace(/[ \t]attribute/g, " in");
             
             if (isFragment) {
-                result = result.replace(/texture2DLodEXT\(/g, "textureLod(");
-                result = result.replace(/textureCubeLodEXT\(/g, "textureLod(");
-                result = result.replace(/texture2D\(/g, "texture(");
-                result = result.replace(/textureCube\(/g, "texture(");
+                result = result.replace(/texture2DLodEXT\s*\(/g, "textureLod(");
+                result = result.replace(/textureCubeLodEXT\s*\(/g, "textureLod(");
+                result = result.replace(/texture2D\s*\(/g, "texture(");
+                result = result.replace(/textureCube\s*\(/g, "texture(");
                 result = result.replace(/gl_FragDepthEXT/g, "gl_FragDepth");
                 result = result.replace(/gl_FragColor/g, "glFragColor");
                 result = result.replace(/gl_FragData/g, "glFragData");
-                result = result.replace(/void\s+?main\(/g, (hasDrawBuffersExtension ? "" : "out vec4 glFragColor;\n") + "void main(");
+                result = result.replace(/void\s+?main\s*\(/g, (hasDrawBuffersExtension ? "" : "out vec4 glFragColor;\n") + "void main(");
             }
             
             callback(result);

+ 12 - 3
src/Mesh/babylon.meshBuilder.ts

@@ -859,14 +859,20 @@
          */
         public static CreateTube(name: string, options: { path: Vector3[], radius?: number, tessellation?: number, radiusFunction?: { (i: number, distance: number): number; }, cap?: number, arc?: number, updatable?: boolean, sideOrientation?: number, frontUVs?: Vector4, backUVs?: Vector4, instance?: Mesh, invertUV?: boolean }, scene: Scene): Mesh {
             var path = options.path;
-            var radius = options.radius || 1.0;
+            var instance = options.instance;
+            var radius = 1.0;
+            if (instance) {
+                radius = (<any>instance).radius;
+            }
+            if (options.radius !== undefined) {
+                radius = options.radius;
+            };
             var tessellation = options.tessellation || 64|0;
             var radiusFunction = options.radiusFunction;
             var cap = options.cap || Mesh.NO_CAP;
             var invertUV = options.invertUV || false;
             var updatable = options.updatable;
             var sideOrientation = MeshBuilder.updateSideOrientation(options.sideOrientation, scene);
-            var instance = options.instance;
             options.arc = (options.arc <= 0.0 || options.arc > 1.0) ? 1.0 : options.arc || 1.0;
 
             // tube geometry
@@ -929,6 +935,7 @@
                 }
                 return circlePaths;
             };
+
             var path3D;
             var pathArray;
             if (instance) { // tube update
@@ -939,10 +946,11 @@
                 (<any>instance).path3D = path3D;
                 (<any>instance).pathArray = pathArray;
                 (<any>instance).arc = arc;
+                (<any>instance).radius = radius;
 
                 return instance;
-
             }
+
             // tube creation
             path3D = <any>new Path3D(path);
             var newPathArray = new Array<Array<Vector3>>();
@@ -954,6 +962,7 @@
             (<any>tube).tessellation = tessellation;
             (<any>tube).cap = cap;
             (<any>tube).arc = options.arc;
+            (<any>tube).radius = radius;
 
             return tube;
         }

+ 97 - 66
src/babylon.engine.ts

@@ -254,6 +254,11 @@
         doNotHandleContextLost?: boolean;
     }
 
+    export interface IDisplayChangedEventArgs {
+        vrDisplay: any;
+        vrSupported: boolean;
+    }
+
     /**
      * The engine class is responsible for interfacing with all lower-level APIs such as WebGL and Audio.
      */
@@ -556,12 +561,8 @@
 
         //WebVR
 
-        //The new WebVR uses promises.
-        //this promise resolves with the current devices available.
-        public vrDisplaysPromise;
-
-        private _vrDisplays;
-        private _vrDisplayEnabled;
+        private _vrDisplay: any = undefined;
+        private _vrSupported: boolean = false;
         private _oldSize: BABYLON.Size;
         private _oldHardwareScaleFactor: number;
         private _vrExclusivePointerMode = false;
@@ -613,6 +614,14 @@
         private _onVRDisplayPointerRestricted: () => void;
         private _onVRDisplayPointerUnrestricted: () => void;
 
+        // VRDisplay connection
+        private _onVrDisplayConnect: (display: any) => void;
+        private _onVrDisplayDisconnect: () => void;
+        private _onVrDisplayPresentChange: () => void;
+        public onVRDisplayChangedObservable = new Observable<IDisplayChangedEventArgs>();
+        public onVRRequestPresentComplete = new Observable<boolean>();
+        public onVRRequestPresentStart = new Observable<Engine>();
+
         private _hardwareScalingLevel: number;
         private _caps: EngineCapabilities;
         private _pointerLockRequested: boolean;
@@ -946,12 +955,10 @@
                 document.addEventListener("webkitpointerlockchange", this._onPointerLockChange, false);
 
                 this._onVRDisplayPointerRestricted = () => {
-                    this._vrExclusivePointerMode = true;
                     canvas.requestPointerLock();
                 }
 
                 this._onVRDisplayPointerUnrestricted = () => {
-                    this._vrExclusivePointerMode = false;
                     document.exitPointerLock();
                 }
 
@@ -1401,7 +1408,10 @@
 
             if (this._activeRenderLoops.length > 0) {
                 // Register new frame
-                this._frameHandler = Tools.QueueNewFrame(this._bindedRenderFunction, this._vrDisplayEnabled);
+                var requester = window;
+                if (this._vrDisplay && this._vrDisplay.isPresenting)
+                    requester = this._vrDisplay;
+                this._frameHandler = Tools.QueueNewFrame(this._bindedRenderFunction, requester);
             } else {
                 this._renderingQueueLaunched = false;
             }
@@ -1528,8 +1538,9 @@
             }
 
             //submit frame to the vr device, if enabled
-            if (this._vrDisplayEnabled && this._vrDisplayEnabled.isPresenting) {
-                this._vrDisplayEnabled.submitFrame()
+            if (this._vrDisplay && this._vrDisplay.isPresenting) {
+                // TODO: We should only submit the frame if we read frameData successfully.
+                this._vrDisplay.submitFrame();
             }
         }
 
@@ -1542,7 +1553,7 @@
          */
         public resize(): void {
             // We're not resizing the size of the canvas while in VR mode & presenting
-            if (!(this._vrDisplayEnabled && this._vrDisplayEnabled.isPresenting)) {
+            if (!(this._vrDisplay && this._vrDisplay.isPresenting)) {
                 var width = navigator.isCocoonJS ? window.innerWidth : this._renderingCanvas.clientWidth;
                 var height = navigator.isCocoonJS ? window.innerHeight : this._renderingCanvas.clientHeight;
 
@@ -1578,94 +1589,106 @@
             }
         }
 
+        // WebVR functions
+        public isVRDevicePresent() : boolean {
+            return !!this._vrDisplay;
+        }
 
-        //WebVR functions
-        public isVRDevicePresent(callback: (result: boolean) => void) {
-            this.getVRDevice(null, (device) => {
-                callback(device !== null);
-            });
+        public getVRDevice() : any {
+            return this._vrDisplay;
         }
 
-        public getVRDevice(name: string, callback: (device: any) => void) {
-            if (!this.vrDisplaysPromise) {
-                callback(null);
-                return;
+        public initWebVR(): Observable<any> {
+            var notifyObservers = () => {
+                var eventArgs = {
+                    vrDisplay: this._vrDisplay,
+                    vrSupported: this._vrSupported
+                };
+                this.onVRDisplayChangedObservable.notifyObservers(eventArgs);
             }
 
-            this.vrDisplaysPromise.then((devices) => {
-                if (devices.length > 0) {
-                    if (name) {
-                        var found = devices.some(device => {
-                            if (device.displayName === name) {
-                                callback(device);
-                                return true;
-                            } else {
-                                return false;
-                            }
-                        });
-                        if (!found) {
-                            Tools.Warn("Display " + name + " was not found. Using " + devices[0].displayName);
-                            callback(devices[0]);
-                        }
-                    } else {
-                        //choose the first one
-                        callback(devices[0]);
-                    }
-                } else {
-                    Tools.Error("No WebVR devices found!");
-                    callback(null);
+            if (!this._onVrDisplayConnect) {
+                this._onVrDisplayConnect = (event) => {
+                    this._vrDisplay = event.display;
+                    notifyObservers();
+                };
+                this._onVrDisplayDisconnect = () => {
+                    this._vrDisplay.cancelAnimationFrame(this._frameHandler);
+                    this._vrDisplay = undefined;
+                    this._frameHandler = Tools.QueueNewFrame(this._bindedRenderFunction);
+                    notifyObservers();
+                };
+                this._onVrDisplayPresentChange = () => {                    
+                    this._vrExclusivePointerMode = this._vrDisplay && this._vrDisplay.isPresenting;
                 }
-            });
-        }
-
-        public initWebVR(): void {
-            if (!this.vrDisplaysPromise) {
-                this._getVRDisplays();
+                window.addEventListener('vrdisplayconnect', this._onVrDisplayConnect);
+                window.addEventListener('vrdisplaydisconnect', this._onVrDisplayDisconnect);
+                window.addEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
             }
+            
+            this._getVRDisplays(notifyObservers);
+
+            return this.onVRDisplayChangedObservable;
         }
 
-        public enableVR(vrDevice) {
-            this._vrDisplayEnabled = vrDevice;
-            this._vrDisplayEnabled.requestPresent([{ source: this.getRenderingCanvas() }]).then(this._onVRFullScreenTriggered);
+        public enableVR() {
+            if (this._vrDisplay && !this._vrDisplay.isPresenting) {
+                var onResolved = () => {
+                    this.onVRRequestPresentComplete.notifyObservers(true);
+                    this._onVRFullScreenTriggered();
+                };
+                var onRejected = () => {
+                    this.onVRRequestPresentComplete.notifyObservers(false);
+                };
+                
+                this.onVRRequestPresentStart.notifyObservers(this);
+                this._vrDisplay.requestPresent([{ source: this.getRenderingCanvas() }]).then(onResolved).catch(onRejected);
+            }
         }
 
         public disableVR() {
-            if (this._vrDisplayEnabled) {
-                this._vrDisplayEnabled.exitPresent().then(this._onVRFullScreenTriggered);
+            if (this._vrDisplay && this._vrDisplay.isPresenting) {
+                this._vrDisplay.exitPresent().then(this._onVRFullScreenTriggered).catch(this._onVRFullScreenTriggered);
             }
         }
 
         private _onVRFullScreenTriggered = () => {
-            if (this._vrDisplayEnabled && this._vrDisplayEnabled.isPresenting) {
+            if (this._vrDisplay && this._vrDisplay.isPresenting) {
                 //get the old size before we change
                 this._oldSize = new BABYLON.Size(this.getRenderWidth(), this.getRenderHeight());
                 this._oldHardwareScaleFactor = this.getHardwareScalingLevel();
 
                 //get the width and height, change the render size
-                var leftEye = this._vrDisplayEnabled.getEyeParameters('left');
+                var leftEye = this._vrDisplay.getEyeParameters('left');
                 var width, height;
                 this.setHardwareScalingLevel(1);
                 this.setSize(leftEye.renderWidth * 2, leftEye.renderHeight);
             } else {
-                //When the specs are implemented, need to uncomment this.
                 this.setHardwareScalingLevel(this._oldHardwareScaleFactor);
                 this.setSize(this._oldSize.width, this._oldSize.height);
-                this._vrDisplayEnabled = undefined;
             }
         }
 
-        private _getVRDisplays() {
+        private _getVRDisplays(callback) {
             var getWebVRDevices = (devices: Array<any>) => {
-
-                this._vrDisplays = devices.filter(function (device) {
-                    return device instanceof VRDisplay;
-                });
-
-                return this._vrDisplays;
+                this._vrSupported = true;
+                // note that devices may actually be an empty array. This is fine;
+                // we expect this._vrDisplay to be undefined in this case.
+                return this._vrDisplay = devices[0];
             }
 
             if (navigator.getVRDisplays) {
-                this.vrDisplaysPromise = navigator.getVRDisplays().then(getWebVRDevices);
+                // TODO: Backwards compatible for 1.0?
+                navigator.getVRDisplays().then(getWebVRDevices).then(callback).catch((error) => {
+                    // TODO: System CANNOT support WebVR, despite API presence.
+                    this._vrSupported = false;
+                    callback();
+                });
+            } else {
+                // TODO: Browser does not support WebVR
+                this._vrDisplay = undefined;
+                this._vrSupported = false;
+                callback();
             }
         }
 
@@ -4416,6 +4439,14 @@
             document.removeEventListener("mspointerlockchange", this._onPointerLockChange);
             document.removeEventListener("mozpointerlockchange", this._onPointerLockChange);
             document.removeEventListener("webkitpointerlockchange", this._onPointerLockChange);
+            
+            if (this._onVrDisplayConnect) {
+                window.removeEventListener('vrdisplayconnect', this._onVrDisplayConnect);
+                window.removeEventListener('vrdisplaydisconnect', this._onVrDisplayDisconnect);
+                window.removeEventListener('vrdisplaypresentchange', this._onVrDisplayPresentChange);
+                this._onVrDisplayConnect = undefined;
+                this._onVrDisplayDisconnect = undefined;                
+            }
 
             // Remove from Instances
             var index = Engine.Instances.indexOf(this);

binární
tests/validation/ReferenceImages/gltfnormals.png


binární
tests/validation/ReferenceImages/normals.png


binární
tests/validation/ReferenceImages/pbrrough.png


+ 27 - 15
tests/validation/config.json

@@ -1,12 +1,12 @@
 {
   "root": "https://rawgit.com/BabylonJS/Website/master",
-  "tests": [   
+  "tests": [
     {
       "title": "Sponza",
       "sceneFolder": "/Scenes/Sponza/",
       "sceneFilename": "Sponza.babylon",
       "referenceImage": "Sponza.png"
-    },    
+    },
     {
       "title": "Windows cafe",
       "sceneFolder": "/Scenes/WCafe/",
@@ -14,7 +14,7 @@
       "referenceImage": "WCafe.png"
     },
     {
-      "title": "Espilit",      
+      "title": "Espilit",
       "renderCount": 10,
       "sceneFolder": "/Scenes/Espilit/",
       "sceneFilename": "Espilit.binary.babylon",
@@ -91,7 +91,7 @@
       "scriptToRun": "/Demos/Polygon/polygon.js",
       "functionToCall": "CreatePolygonScene",
       "referenceImage": "polygon.png"
-    },        
+    },
     {
       "title": "Soft Shadows",
       "renderCount": 5,
@@ -104,7 +104,7 @@
       "scriptToRun": "/Demos/Fresnel/fresnel.js",
       "functionToCall": "CreateFresnelTestScene",
       "referenceImage": "fresnel.png"
-    }, 
+    },
     {
       "title": "Highlights",
       "renderCount": 10,
@@ -173,7 +173,20 @@
       "referenceImage": "displacementMap.png",
       "replace": "/Scenes/Customs/skybox/, https://cdn.rawgit.com/BabylonJS/Website/06ecbea7/Assets/skybox/"
     },
-     {
+    {
+      "title": "Normals",
+      "scriptToRun": "/Demos/Normals/index.js",
+      "functionToCall": "createScene",
+      "referenceImage": "normals.png"
+    },
+    {
+      "title": "GLTF Normals",
+      "renderCount": 10,
+      "scriptToRun": "/Demos/GLTFNormals/index.js",
+      "functionToCall": "createScene",
+      "referenceImage": "gltfnormals.png"
+    },
+    {
       "title": "PBR glossy",
       "renderCount": 10,
       "scriptToRun": "/Demos/PBRGlossy/index.js",
@@ -193,41 +206,41 @@
       "scriptToRun": "/Demos/RefProbe/reflectionProbe.js",
       "functionToCall": "CreateReflectionProbeTestScene ",
       "referenceImage": "refprobe.png"
-    },   
+    },
     {
       "title": "PBRMetallicRoughnessMaterial",
       "playgroundId": "#2FDQT5#13",
       "referenceImage": "PBRMetallicRoughnessMaterial.png"
-    },   
+    },
     {
       "title": "PBRSpecularGlossinessMaterial",
       "playgroundId": "#Z1VL3V#4",
       "referenceImage": "PBRSpecularGlossinessMaterial.png"
-    },   
+    },
     {
       "title": "PBR",
       "playgroundId": "#LCA0Q4",
       "referenceImage": "pbr.png"
-    },   
+    },
     {
       "title": "MultiSample render targets",
       "renderCount": 10,
       "playgroundId": "#12MKMN#0",
       "referenceImage": "MultiSample render targets.png"
-    },      
+    },
     {
       "title": "Default rendering pipeline",
       "renderCount": 20,
       "playgroundId": "#5XB8YT#2",
       "referenceImage": "DefaultRenderingPipeline.png"
-    },             
+    },
     {
       "title": "Water material (only visual check)",
       "renderCount": 10,
       "scriptToRun": "/Demos/WaterMaterial/water.js",
       "functionToCall": "CreateWaterTestScene",
       "referenceImage": "waterMaterial.png",
-      "onlyVisual": true 
+      "onlyVisual": true
     },
     {
       "title": "Instances (only visual check)",
@@ -257,5 +270,4 @@
       "onlyVisual": true
     }
   ]
-}
-
+}