Explorar o código

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

# Conflicts:
#	src/Debug/babylon.axesViewer.ts
#	src/Engine/babylon.engine.ts
Raanan Weber %!s(int64=7) %!d(string=hai) anos
pai
achega
1060c8ba49
Modificáronse 100 ficheiros con 19794 adicións e 13513 borrados
  1. 16 8
      .travis.yml
  2. 3727 3552
      Playground/babylon.d.txt
  3. 167 72
      Playground/debug.html
  4. 23 21
      Playground/frame.html
  5. 2 0
      Playground/index.html
  6. BIN=BIN
      Playground/textures/normal.png
  7. 9 1
      Tools/DevLoader/BabylonLoader.js
  8. 12 1
      Tools/Gulp/config.json
  9. 29 11
      Tools/Gulp/gulpfile.js
  10. 10 1
      Tools/Gulp/package.json
  11. 2459 2279
      dist/preview release/babylon.d.ts
  12. 49 48
      dist/preview release/babylon.js
  13. 1031 321
      dist/preview release/babylon.max.js
  14. 49 48
      dist/preview release/babylon.worker.js
  15. 5948 5599
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts
  16. 52 51
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js
  17. 1223 402
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js
  18. 1 1
      dist/preview release/gui/package.json
  19. 2 2
      dist/preview release/inspector/babylon.inspector.bundle.js
  20. 6 0
      dist/preview release/inspector/babylon.inspector.js
  21. 3 3
      dist/preview release/inspector/babylon.inspector.min.js
  22. 1 1
      dist/preview release/inspector/package.json
  23. 48 12
      dist/preview release/loaders/babylon.glTF1FileLoader.d.ts
  24. 138 19
      dist/preview release/loaders/babylon.glTF1FileLoader.js
  25. 2 2
      dist/preview release/loaders/babylon.glTF1FileLoader.min.js
  26. 50 15
      dist/preview release/loaders/babylon.glTF2FileLoader.d.ts
  27. 179 78
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  28. 2 2
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  29. 62 17
      dist/preview release/loaders/babylon.glTFFileLoader.d.ts
  30. 192 81
      dist/preview release/loaders/babylon.glTFFileLoader.js
  31. 3 3
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  32. 192 81
      dist/preview release/loaders/babylonjs.loaders.js
  33. 3 3
      dist/preview release/loaders/babylonjs.loaders.min.js
  34. 62 17
      dist/preview release/loaders/babylonjs.loaders.module.d.ts
  35. 1 1
      dist/preview release/loaders/package.json
  36. 1 1
      dist/preview release/materialsLibrary/package.json
  37. 1 1
      dist/preview release/postProcessesLibrary/package.json
  38. 1 1
      dist/preview release/proceduralTexturesLibrary/package.json
  39. 162 0
      dist/preview release/serializers/babylon.glTF2Serializer.d.ts
  40. 649 0
      dist/preview release/serializers/babylon.glTF2Serializer.js
  41. 1 0
      dist/preview release/serializers/babylon.glTF2Serializer.min.js
  42. 650 0
      dist/preview release/serializers/babylonjs.serializers.js
  43. 1 1
      dist/preview release/serializers/babylonjs.serializers.min.js
  44. 163 0
      dist/preview release/serializers/babylonjs.serializers.module.d.ts
  45. 1 1
      dist/preview release/serializers/package.json
  46. 53 52
      dist/preview release/viewer/babylon.viewer.js
  47. 1 1
      dist/preview release/viewer/package.json
  48. 6 0
      dist/preview release/what's new.md
  49. 7 0
      inspector/src/tabs/StatsTab.ts
  50. 19 7
      loaders/src/glTF/1.0/babylon.glTFLoader.ts
  51. 62 72
      loaders/src/glTF/2.0/babylon.glTFLoader.ts
  52. 112 32
      loaders/src/glTF/babylon.glTFFileLoader.ts
  53. 3 2
      package.json
  54. 0 1
      sandbox/index.js
  55. 728 0
      serializers/src/glTF/2.0/babylon.glTFExporter.ts
  56. 72 0
      serializers/src/glTF/2.0/babylon.glTFSerializer.ts
  57. 6 5
      src/Animations/babylon.animatable.ts
  58. 44 0
      src/Animations/babylon.animation.ts
  59. 40 4
      src/Animations/babylon.animationGroup.ts
  60. 1 1
      src/Audio/babylon.sound.ts
  61. 38 10
      src/Cameras/VR/babylon.vrExperienceHelper.ts
  62. 16 8
      src/Cameras/VR/babylon.webVRCamera.ts
  63. 1 0
      src/Cameras/babylon.targetCamera.ts
  64. 323 116
      src/Engine/babylon.engine.ts
  65. 11 7
      src/Helpers/babylon.environmentHelper.ts
  66. 109 101
      src/Instrumentation/babylon.sceneInstrumentation.ts
  67. 64 25
      src/Loading/babylon.sceneLoader.ts
  68. 8 1
      src/Materials/Textures/babylon.colorGradingTexture.ts
  69. 2 1
      src/Materials/Textures/babylon.internalTexture.ts
  70. 26 22
      src/Materials/Textures/babylon.multiRenderTarget.ts
  71. 13 5
      src/Materials/Textures/babylon.renderTargetTexture.ts
  72. 1 1
      src/Materials/Textures/babylon.videoTexture.ts
  73. 22 5
      src/Materials/babylon.effect.ts
  74. 24 23
      src/Materials/babylon.materialHelper.ts
  75. 14 1
      src/Materials/babylon.shaderMaterial.ts
  76. 5 5
      src/Mesh/babylon.abstractMesh.ts
  77. 5 4
      src/Mesh/babylon.geometry.ts
  78. 2 2
      src/Mesh/babylon.mesh.ts
  79. 40 39
      src/Mesh/babylon.mesh.vertexData.ts
  80. 3 5
      src/Mesh/babylon.subMesh.ts
  81. 5 2
      src/Mesh/babylon.transformNode.ts
  82. 54 0
      src/Particles/babylon.boxParticleEmitter.ts
  83. 29 0
      src/Particles/babylon.coneParticleEmitter.ts
  84. 6 0
      src/Particles/babylon.iParticleEmitterType.ts
  85. 60 33
      src/Particles/babylon.particleSystem.ts
  86. 35 0
      src/Particles/babylon.sphereParticleEmitter.ts
  87. 15 5
      src/Rendering/babylon.geometryBufferRenderer.ts
  88. 12 5
      src/Rendering/babylon.renderingGroup.ts
  89. 11 4
      src/Rendering/babylon.renderingManager.ts
  90. 20 14
      src/Tools/babylon.assetsManager.ts
  91. 2 2
      src/Tools/babylon.filesInput.ts
  92. 5 0
      src/Tools/babylon.sceneSerializer.ts
  93. 144 58
      src/Tools/babylon.tools.ts
  94. 14 43
      src/babylon.mixins.ts
  95. 15 7
      src/babylon.node.ts
  96. 103 24
      src/babylon.scene.ts
  97. BIN=BIN
      tests/validation/ReferenceImages/Flat2009.png
  98. BIN=BIN
      tests/validation/ReferenceImages/GLTF LOD.png
  99. BIN=BIN
      tests/validation/ReferenceImages/SpaceDeK.png
  100. 0 0
      tests/validation/ReferenceImages/instancedBones.png

+ 16 - 8
.travis.yml

@@ -1,8 +1,16 @@
-language: node_js
-node_js:
-  - "6"
-before_script:
-  - npm install -g gulp
-  - cd ./Tools/Gulp
-  - npm install
-script: gulp typescript
+language: node_js
+node_js:
+- '6'
+addons:
+  sauce_connect:
+    username: vandenberghe.sebastien@gmail.com
+  jwt:
+    secure: kbaJ3o6cVqpB8Gzfd0yDSgGwH+SQXukevtpjMQ8Gip+N6FOumQxihPDqsqYKxc8svvR024nDmkIhbjPjz/YguvC3WUgYW4xk3Za7P7C9cpj2fYdrFuO7pD4sd/fNdUCqvKQ8jcxlIq4eEdBuoBTOHsP9J5KH7Z1M7e58atkx0o8=
+before_script:
+- npm install -g gulp
+- cd ./Tools/Gulp
+- npm install
+script: gulp
+notifications:
+  slack:
+    secure: TBYDAN8Dlkx3dM+Q5ClAZem7agAhQ1oB/fGT665qn7D+j2YfWChvlfXegvXL4LPDmQgbI0UfazcjWId5a0EwmmPkRb+kMJItPiMt5jiIp2WKoZQ+qob6H9tBCRJbbpWM430wiPeKfBfbcZP/XSlpVMWhgU5ogAFDSUKjvHT7IuE=

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


+ 167 - 72
Playground/debug.html

@@ -6,24 +6,24 @@
     <meta charset='utf-8' />
     <meta name="viewport" content="width=device-width, user-scalable=no">
     <link rel="shortcut icon" href="https://www.babylonjs.com/img/favicon/favicon.ico">
-	<link rel="apple-touch-icon" sizes="57x57" href="https://www.babylonjs.com/img/favicon/apple-icon-57x57.png">
-	<link rel="apple-touch-icon" sizes="60x60" href="https://www.babylonjs.com/img/favicon/apple-icon-60x60.png">
-	<link rel="apple-touch-icon" sizes="72x72" href="https://www.babylonjs.com/img/favicon/apple-icon-72x72.png">
-	<link rel="apple-touch-icon" sizes="76x76" href="https://www.babylonjs.com/img/favicon/apple-icon-76x76.png">
-	<link rel="apple-touch-icon" sizes="114x114" href="https://www.babylonjs.com/img/favicon/apple-icon-114x114.png">
-	<link rel="apple-touch-icon" sizes="120x120" href="https://www.babylonjs.com/img/favicon/apple-icon-120x120.png">
-	<link rel="apple-touch-icon" sizes="144x144" href="https://www.babylonjs.com/img/favicon/apple-icon-144x144.png">
-	<link rel="apple-touch-icon" sizes="152x152" href="https://www.babylonjs.com/img/favicon/apple-icon-152x152.png">
-	<link rel="apple-touch-icon" sizes="180x180" href="https://www.babylonjs.com/img/favicon/apple-icon-180x180.png">
-	<link rel="icon" type="image/png" sizes="192x192"  href="https://www.babylonjs.com/img/favicon/android-icon-192x192.png">
-	<link rel="icon" type="image/png" sizes="32x32" href="https://www.babylonjs.com/img/favicon/favicon-32x32.png">
-	<link rel="icon" type="image/png" sizes="96x96" href="https://www.babylonjs.com/img/favicon/favicon-96x96.png">
-	<link rel="icon" type="image/png" sizes="16x16" href="https://www.babylonjs.com/img/favicon/favicon-16x16.png">
-	<link rel="manifest" href="https://www.babylonjs.com/img/favicon/manifest.json">
-	<meta name="msapplication-TileColor" content="#ffffff">
-	<meta name="msapplication-TileImage" content="https://www.babylonjs.com/img/favicon/ms-icon-144x144.png">
-	<meta name="msapplication-config" content="https://www.babylonjs.com/img/favicon/browserconfig.xml">
-	<meta name="theme-color" content="#ffffff">
+    <link rel="apple-touch-icon" sizes="57x57" href="https://www.babylonjs.com/img/favicon/apple-icon-57x57.png">
+    <link rel="apple-touch-icon" sizes="60x60" href="https://www.babylonjs.com/img/favicon/apple-icon-60x60.png">
+    <link rel="apple-touch-icon" sizes="72x72" href="https://www.babylonjs.com/img/favicon/apple-icon-72x72.png">
+    <link rel="apple-touch-icon" sizes="76x76" href="https://www.babylonjs.com/img/favicon/apple-icon-76x76.png">
+    <link rel="apple-touch-icon" sizes="114x114" href="https://www.babylonjs.com/img/favicon/apple-icon-114x114.png">
+    <link rel="apple-touch-icon" sizes="120x120" href="https://www.babylonjs.com/img/favicon/apple-icon-120x120.png">
+    <link rel="apple-touch-icon" sizes="144x144" href="https://www.babylonjs.com/img/favicon/apple-icon-144x144.png">
+    <link rel="apple-touch-icon" sizes="152x152" href="https://www.babylonjs.com/img/favicon/apple-icon-152x152.png">
+    <link rel="apple-touch-icon" sizes="180x180" href="https://www.babylonjs.com/img/favicon/apple-icon-180x180.png">
+    <link rel="icon" type="image/png" sizes="192x192" href="https://www.babylonjs.com/img/favicon/android-icon-192x192.png">
+    <link rel="icon" type="image/png" sizes="32x32" href="https://www.babylonjs.com/img/favicon/favicon-32x32.png">
+    <link rel="icon" type="image/png" sizes="96x96" href="https://www.babylonjs.com/img/favicon/favicon-96x96.png">
+    <link rel="icon" type="image/png" sizes="16x16" href="https://www.babylonjs.com/img/favicon/favicon-16x16.png">
+    <link rel="manifest" href="https://www.babylonjs.com/img/favicon/manifest.json">
+    <meta name="msapplication-TileColor" content="#ffffff">
+    <meta name="msapplication-TileImage" content="https://www.babylonjs.com/img/favicon/ms-icon-144x144.png">
+    <meta name="msapplication-config" content="https://www.babylonjs.com/img/favicon/browserconfig.xml">
+    <meta name="theme-color" content="#ffffff">
 
     <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
     <!--For canvas/code separator-->
@@ -38,7 +38,7 @@
     <!-- Babylon.js -->
     <script src="https://preview.babylonjs.com/cannon.js"></script>
     <script src="https://preview.babylonjs.com/Oimo.js"></script>
-    <script src="https://preview.babylonjs.com/babylon.max.js"></script>    
+    <script src="https://preview.babylonjs.com/babylon.max.js"></script>
     <script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>
 
     <script src="https://preview.babylonjs.com/materialsLibrary/babylon.fireMaterial.min.js"></script>
@@ -73,6 +73,8 @@
 
     <script src="https://preview.babylonjs.com/gui/babylon.gui.js"></script>
 
+    <script src="https://preview.babylonjs.com/serializers/babylonjs.serializers.min.js"></script>
+
     <script src="https://rawgit.com/BabylonJS/Extensions/master/ClonerSystem/src/babylonx.cloner.js"></script>
     <script src="https://rawgit.com/BabylonJS/Extensions/master/canvas2D/dist/preview%20release/babylon.canvas2d.js"></script>
     <script src="https://rawgit.com/BabylonJS/Extensions/master/CompoundShader/src/babylonx.CompoundShader.js"></script>
@@ -90,30 +92,43 @@
         </div>
 
         <div class="category">
-            <div class="button run" id="runButton1600">Run <i class="fa fa-play" aria-hidden="true"></i></div>
+            <div class="button run" id="runButton1600">Run
+                <i class="fa fa-play" aria-hidden="true"></i>
+            </div>
         </div>
 
 
         <div class="category">
-            <div class="button" id="newButton1600">New<i class="fa fa-file" aria-hidden="true"></i></div>
-            <div class="button removeOnPhone" id="clearButton1600">Clear<i class="fa fa-trash" aria-hidden="true"></i></div>
+            <div class="button" id="newButton1600">New
+                <i class="fa fa-file" aria-hidden="true"></i>
+            </div>
+            <div class="button removeOnPhone" id="clearButton1600">Clear
+                <i class="fa fa-trash" aria-hidden="true"></i>
+            </div>
         </div>
 
         <div class="category">
-            <div class="button" id="saveButton1600">Save <i class="fa fa-floppy-o" aria-hidden="true"></i></div>
-            <div class="button removeOnPhone" id="zipButton1600">Zip<i class="fa fa-download" aria-hidden="true"></i></div>
+            <div class="button" id="saveButton1600">Save
+                <i class="fa fa-floppy-o" aria-hidden="true"></i>
+            </div>
+            <div class="button removeOnPhone" id="zipButton1600">Zip
+                <i class="fa fa-download" aria-hidden="true"></i>
+            </div>
         </div>
 
         <div class="category">
             <div class="button select">Settings
                 <div class="toDisplay">
-                    <div class="option subSelect">Theme <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                    <div class="option subSelect">Theme
+                        <i class="fa fa-chevron-right" aria-hidden="true"></i>
                         <div class="toDisplaySub">
                             <div class="option" id="darkTheme1600">Dark</div>
                             <div class="option" id="lightTheme1600">Light</div>
                         </div>
                     </div>
-                    <div class="option subSelect"><span id="currentFontSize1600">Font: 14</span><i class="fa fa-chevron-right" aria-hidden="true"></i>
+                    <div class="option subSelect">
+                        <span id="currentFontSize1600">Font: 14</span>
+                        <i class="fa fa-chevron-right" aria-hidden="true"></i>
                         <div class="toDisplaySub">
                             <div class="option" onclick="setFontSize(12);">12</div>
                             <div class="option" onclick="setFontSize(14);">14</div>
@@ -123,8 +138,11 @@
                             <div class="option" onclick="setFontSize(22);">22</div>
                         </div>
                     </div>
-                    <div class="option" id="safemodeToggle1600">Safe mode <i class="fa fa-square-o" aria-hidden="true"></i></div>
-                    <div class="option checked" id="editorButton1600">Editor <i class="fa fa-check-square" aria-hidden="true"></i>
+                    <div class="option" id="safemodeToggle1600">Safe mode
+                        <i class="fa fa-square-o" aria-hidden="true"></i>
+                    </div>
+                    <div class="option checked" id="editorButton1600">Editor
+                        <i class="fa fa-check-square" aria-hidden="true"></i>
                     </div>
                     <div class="option" id="fullscreenButton1600">Fullscreen</div>
                     <div class="option" id="editorFullscreenButton1600">Editor Fullscreen</div>
@@ -135,20 +153,24 @@
                 </div>
             </div>
 
-            <div class="button uncheck" id="debugButton1600">Debug layer <i class="fa fa-square-o" aria-hidden="true"></i></div>
+            <div class="button uncheck" id="debugButton1600">Debug layer
+                <i class="fa fa-square-o" aria-hidden="true"></i>
+            </div>
             <div class="button" id="metadataButton1600">Metadata</div>
         </div>
 
 
 
         <div class="category right">
-            <div class="button select"><span id="currentVersion1600">Version: Latest</span>
+            <div class="button select">
+                <span id="currentVersion1600">Version: Latest</span>
                 <div class="toDisplay">
                     <div class="option" onclick="setVersion('latest');">Latest</div>
                     <div class="option" onclick="setVersion('2.5');">2.5</div>
                 </div>
             </div>
-            <div class="button select"> <span id="currentScript1600">Scenes</span>
+            <div class="button select">
+                <span id="currentScript1600">Scenes</span>
                 <div class="toDisplayBig">
                     <ul id="scriptsList1600">
                     </ul>
@@ -169,30 +191,43 @@
         </div>
 
         <div class="category">
-            <div class="button run" id="runButton1475">Run <i class="fa fa-play" aria-hidden="true"></i></div>
+            <div class="button run" id="runButton1475">Run
+                <i class="fa fa-play" aria-hidden="true"></i>
+            </div>
         </div>
 
 
         <div class="category">
-            <div class="button" id="newButton1475">New<i class="fa fa-file" aria-hidden="true"></i></div>
-            <div class="button removeOnPhone" id="clearButton1475">Clear<i class="fa fa-trash" aria-hidden="true"></i></div>
+            <div class="button" id="newButton1475">New
+                <i class="fa fa-file" aria-hidden="true"></i>
+            </div>
+            <div class="button removeOnPhone" id="clearButton1475">Clear
+                <i class="fa fa-trash" aria-hidden="true"></i>
+            </div>
         </div>
 
         <div class="category">
-            <div class="button" id="saveButton1475">Save <i class="fa fa-floppy-o" aria-hidden="true"></i></div>
-            <div class="button removeOnPhone" id="zipButton1475">Zip<i class="fa fa-download" aria-hidden="true"></i></div>
+            <div class="button" id="saveButton1475">Save
+                <i class="fa fa-floppy-o" aria-hidden="true"></i>
+            </div>
+            <div class="button removeOnPhone" id="zipButton1475">Zip
+                <i class="fa fa-download" aria-hidden="true"></i>
+            </div>
         </div>
 
         <div class="category">
             <div class="button select">Settings
                 <div class="toDisplay">
-                    <div class="option subSelect">Theme <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                    <div class="option subSelect">Theme
+                        <i class="fa fa-chevron-right" aria-hidden="true"></i>
                         <div class="toDisplaySub">
                             <div class="option" id="darkTheme1475">Dark</div>
                             <div class="option" id="lightTheme1475">Light</div>
                         </div>
                     </div>
-                    <div class="option subSelect"><span id="currentFontSize1475">Font: 14</span><i class="fa fa-chevron-right" aria-hidden="true"></i>
+                    <div class="option subSelect">
+                        <span id="currentFontSize1475">Font: 14</span>
+                        <i class="fa fa-chevron-right" aria-hidden="true"></i>
                         <div class="toDisplaySub">
                             <div class="option" onclick="setFontSize(12);">12</div>
                             <div class="option" onclick="setFontSize(14);">14</div>
@@ -202,8 +237,11 @@
                             <div class="option" onclick="setFontSize(22);">22</div>
                         </div>
                     </div>
-                    <div class="option" id='safemodeToggle1475'>Safe mode <i class="fa fa-square-o" aria-hidden="true"></i></div>
-                    <div class="option checked" id="editorButton1475">Editor <i class="fa fa-check-square" aria-hidden="true"></i>
+                    <div class="option" id='safemodeToggle1475'>Safe mode
+                        <i class="fa fa-square-o" aria-hidden="true"></i>
+                    </div>
+                    <div class="option checked" id="editorButton1475">Editor
+                        <i class="fa fa-check-square" aria-hidden="true"></i>
                     </div>
                     <div class="option" id="fullscreenButton1475">Fullscreen</div>
                     <div class="option" id="editorFullscreenButton1475">Editor Fullscreen</div>
@@ -211,9 +249,13 @@
                     <div class="option" id="minimapToggle1475">Minimap
                         <i class="fa fa-square-o" aria-hidden="true"></i>
                     </div>
-                    <div class="option" id="debugButton1475">Debug layer<i class="fa fa-square-o" aria-hidden="true"></i></div>
+                    <div class="option" id="debugButton1475">Debug layer
+                        <i class="fa fa-square-o" aria-hidden="true"></i>
+                    </div>
                     <div class="option" id="metadataButton1475">Metadata</div>
-                    <div class="option subSelect"><span id="currentVersion1475">Vers. : Latest</span><i class="fa fa-chevron-right" aria-hidden="true"></i>
+                    <div class="option subSelect">
+                        <span id="currentVersion1475">Vers. : Latest</span>
+                        <i class="fa fa-chevron-right" aria-hidden="true"></i>
                         <div class="toDisplaySub">
                             <div class="option" onclick="setVersion('latest');">Latest</div>
                             <div class="option" onclick="setVersion('2.5');">2.5</div>
@@ -224,7 +266,8 @@
         </div>
 
         <div class="category right">
-            <div class="button select"> <span id="currentScript1475">Scenes</span>
+            <div class="button select">
+                <span id="currentScript1475">Scenes</span>
                 <div class="toDisplayBig">
                     <ul id="scriptsList1475">
                     </ul>
@@ -238,30 +281,43 @@
 
     <div class="navbar navBar1030">
         <div class="category">
-            <div class="button run" id="runButton1030">Run <i class="fa fa-play" aria-hidden="true"></i></div>
+            <div class="button run" id="runButton1030">Run
+                <i class="fa fa-play" aria-hidden="true"></i>
+            </div>
         </div>
 
 
         <div class="category">
-            <div class="button" id="newButton1030">New<i class="fa fa-file" aria-hidden="true"></i></div>
-            <div class="button removeOnPhone" id="clearButton1030">Clear<i class="fa fa-trash" aria-hidden="true"></i></div>
+            <div class="button" id="newButton1030">New
+                <i class="fa fa-file" aria-hidden="true"></i>
+            </div>
+            <div class="button removeOnPhone" id="clearButton1030">Clear
+                <i class="fa fa-trash" aria-hidden="true"></i>
+            </div>
         </div>
 
         <div class="category">
-            <div class="button" id="saveButton1030">Save <i class="fa fa-floppy-o" aria-hidden="true"></i></div>
-            <div class="button removeOnPhone" id="zipButton1030">Zip<i class="fa fa-download" aria-hidden="true"></i></div>
+            <div class="button" id="saveButton1030">Save
+                <i class="fa fa-floppy-o" aria-hidden="true"></i>
+            </div>
+            <div class="button removeOnPhone" id="zipButton1030">Zip
+                <i class="fa fa-download" aria-hidden="true"></i>
+            </div>
         </div>
 
         <div class="category">
             <div class="button select">Settings
                 <div class="toDisplay">
-                    <div class="option subSelect">Theme <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                    <div class="option subSelect">Theme
+                        <i class="fa fa-chevron-right" aria-hidden="true"></i>
                         <div class="toDisplaySub">
                             <div class="option" id="darkTheme1030">Dark</div>
                             <div class="option" id="lightTheme1030">Light</div>
                         </div>
                     </div>
-                    <div class="option subSelect"><span id="currentFontSize1030">Font: 14</span><i class="fa fa-chevron-right" aria-hidden="true"></i>
+                    <div class="option subSelect">
+                        <span id="currentFontSize1030">Font: 14</span>
+                        <i class="fa fa-chevron-right" aria-hidden="true"></i>
                         <div class="toDisplaySub">
                             <div class="option" onclick="setFontSize(12);">12</div>
                             <div class="option" onclick="setFontSize(14);">14</div>
@@ -271,8 +327,11 @@
                             <div class="option" onclick="setFontSize(22);">22</div>
                         </div>
                     </div>
-                    <div class="option" id="safemodeToggle1030">Safe mode <i class="fa fa-square-o" aria-hidden="true"></i></div>
-                    <div class="option checked" id="editorButton1030">Editor <i class="fa fa-check-square" aria-hidden="true"></i>
+                    <div class="option" id="safemodeToggle1030">Safe mode
+                        <i class="fa fa-square-o" aria-hidden="true"></i>
+                    </div>
+                    <div class="option checked" id="editorButton1030">Editor
+                        <i class="fa fa-check-square" aria-hidden="true"></i>
                     </div>
                     <div class="option" id="fullscreenButton1030">Fullscreen</div>
                     <div class="option" id="editorFullscreenButton1030">Editor Fullscreen</div>
@@ -280,9 +339,13 @@
                     <div class="option" id="minimapToggle1030">Minimap
                         <i class="fa fa-square-o" aria-hidden="true"></i>
                     </div>
-                    <div class="option" id="debugButton1030">Debug layer<i class="fa fa-square-o" aria-hidden="true"></i></div>
+                    <div class="option" id="debugButton1030">Debug layer
+                        <i class="fa fa-square-o" aria-hidden="true"></i>
+                    </div>
                     <div class="option" id="metadataButton1030">Metadata</div>
-                    <div class="option subSelect"><span id="currentVersion1030">Vers. : Latest</span><i class="fa fa-chevron-right" aria-hidden="true"></i>
+                    <div class="option subSelect">
+                        <span id="currentVersion1030">Vers. : Latest</span>
+                        <i class="fa fa-chevron-right" aria-hidden="true"></i>
                         <div class="toDisplaySub">
                             <div class="option" onclick="setVersion('latest');">Latest</div>
                             <div class="option" onclick="setVersion('2.5');">2.5</div>
@@ -293,7 +356,8 @@
         </div>
 
         <div class="category right">
-            <div class="button select"> <span id="currentScript1030">Scenes</span>
+            <div class="button select">
+                <span id="currentScript1030">Scenes</span>
                 <div class="toDisplayBig">
                     <ul id="scriptsList1030">
                     </ul>
@@ -309,11 +373,21 @@
         <div class="category">
             <div class="button select">File
                 <div class="toDisplay">
-                    <div class="option" id="runButton750">Run <i class="fa fa-play" aria-hidden="true"></i></div>
-                    <div class="option" id="newButton750">New <i class="fa fa-file" aria-hidden="true"></i></div>
-                    <div class="option" id="clearButton750">Clear <i class="fa fa-trash" aria-hidden="true"></i></div>
-                    <div class="option" id="saveButton750">Save <i class="fa fa-floppy-o" aria-hidden="true"></i></div>
-                    <div class="option" id="zipButton750">Zip <i class="fa fa-download" aria-hidden="true"></i></div>
+                    <div class="option" id="runButton750">Run
+                        <i class="fa fa-play" aria-hidden="true"></i>
+                    </div>
+                    <div class="option" id="newButton750">New
+                        <i class="fa fa-file" aria-hidden="true"></i>
+                    </div>
+                    <div class="option" id="clearButton750">Clear
+                        <i class="fa fa-trash" aria-hidden="true"></i>
+                    </div>
+                    <div class="option" id="saveButton750">Save
+                        <i class="fa fa-floppy-o" aria-hidden="true"></i>
+                    </div>
+                    <div class="option" id="zipButton750">Zip
+                        <i class="fa fa-download" aria-hidden="true"></i>
+                    </div>
                 </div>
             </div>
         </div>
@@ -321,13 +395,16 @@
         <div class="category">
             <div class="button select">Settings
                 <div class="toDisplay">
-                    <div class="option subSelect">Theme <i class="fa fa-chevron-right" aria-hidden="true"></i>
+                    <div class="option subSelect">Theme
+                        <i class="fa fa-chevron-right" aria-hidden="true"></i>
                         <div class="toDisplaySub">
                             <div class="option" id="darkTheme750">Dark</div>
                             <div class="option" id="lightTheme750">Light</div>
                         </div>
                     </div>
-                    <div class="option subSelect"><span id="currentFontSize750">Font: 14</span><i class="fa fa-chevron-right" aria-hidden="true"></i>
+                    <div class="option subSelect">
+                        <span id="currentFontSize750">Font: 14</span>
+                        <i class="fa fa-chevron-right" aria-hidden="true"></i>
                         <div class="toDisplaySub">
                             <div class="option" onclick="setFontSize(12);">12</div>
                             <div class="option" onclick="setFontSize(14);">14</div>
@@ -337,8 +414,11 @@
                             <div class="option" onclick="setFontSize(22);">22</div>
                         </div>
                     </div>
-                    <div class="option" id="safemodeToggle750">Safe mode <i class="fa fa-square-o" aria-hidden="true"></i></div>
-                    <div style="display:none;" class="option checked" id="editorButton750">Editor <i class="fa fa-check-square" aria-hidden="true"></i>
+                    <div class="option" id="safemodeToggle750">Safe mode
+                        <i class="fa fa-square-o" aria-hidden="true"></i>
+                    </div>
+                    <div style="display:none;" class="option checked" id="editorButton750">Editor
+                        <i class="fa fa-check-square" aria-hidden="true"></i>
                     </div>
                     <div class="option" id="fullscreenButton750">Fullscreen</div>
                     <div class="option" id="editorFullscreenButton750">Editor Fullscreen</div>
@@ -346,9 +426,13 @@
                     <div class="option" id="minimapToggle750">Minimap
                         <i class="fa fa-square-o" aria-hidden="true"></i>
                     </div>
-                    <div class="option" id="debugButton750">Debug layer<i class="fa fa-square-o" aria-hidden="true"></i></div>
+                    <div class="option" id="debugButton750">Debug layer
+                        <i class="fa fa-square-o" aria-hidden="true"></i>
+                    </div>
                     <div class="option" id="metadataButton750">Metadata</div>
-                    <div class="option subSelect"><span id="currentVersion750">Vers. : Latest</span><i class="fa fa-chevron-right" aria-hidden="true"></i>
+                    <div class="option subSelect">
+                        <span id="currentVersion750">Vers. : Latest</span>
+                        <i class="fa fa-chevron-right" aria-hidden="true"></i>
                         <div class="toDisplaySub">
                             <div class="option" onclick="setVersion('latest');">Latest</div>
                             <div class="option" onclick="setVersion('2.5');">2.5</div>
@@ -359,7 +443,8 @@
         </div>
 
         <div class="category right">
-            <div class="button select"> <span id="currentScript750">Scenes</span>
+            <div class="button select">
+                <span id="currentScript750">Scenes</span>
                 <div class="toDisplayBig">
                     <ul id="scriptsList750">
                     </ul>
@@ -386,11 +471,21 @@
     <div class="navbarBottom">
         <div id="statusBar"></div>
         <div class="links">
-            <div class='link'><a target='_new' href="https://www.netlify.com/">Deployed by Netlify</a></div>
-            <div class='link'> <a target='_new' href="http://www.html5gamedevs.com/forum/16-babylonjs/">Forum</a></div>
-            <div class='link'><a target='_new' href="https://www.babylonjs.com/sandbox">Sandbox</a></div>
-            <div class='link'><a target='_new' href="https://doc.babylonjs.com">Documentation</a></div>
-            <div class='link'><a target='_new' href="https://doc.babylonjs.com/playground">Search</a></div>
+            <div class='link'>
+                <a target='_new' href="https://www.netlify.com/">Deployed by Netlify</a>
+            </div>
+            <div class='link'>
+                <a target='_new' href="http://www.html5gamedevs.com/forum/16-babylonjs/">Forum</a>
+            </div>
+            <div class='link'>
+                <a target='_new' href="https://www.babylonjs.com/sandbox">Sandbox</a>
+            </div>
+            <div class='link'>
+                <a target='_new' href="https://doc.babylonjs.com">Documentation</a>
+            </div>
+            <div class='link'>
+                <a target='_new' href="https://doc.babylonjs.com/playground">Search</a>
+            </div>
         </div>
     </div>
 

+ 23 - 21
Playground/frame.html

@@ -4,30 +4,30 @@
 <head>
     <title>Babylon.js Playground</title>
     <link rel="shortcut icon" href="https://www.babylonjs.com/img/favicon/favicon.ico">
-	<link rel="apple-touch-icon" sizes="57x57" href="https://www.babylonjs.com/img/favicon/apple-icon-57x57.png">
-	<link rel="apple-touch-icon" sizes="60x60" href="https://www.babylonjs.com/img/favicon/apple-icon-60x60.png">
-	<link rel="apple-touch-icon" sizes="72x72" href="https://www.babylonjs.com/img/favicon/apple-icon-72x72.png">
-	<link rel="apple-touch-icon" sizes="76x76" href="https://www.babylonjs.com/img/favicon/apple-icon-76x76.png">
-	<link rel="apple-touch-icon" sizes="114x114" href="https://www.babylonjs.com/img/favicon/apple-icon-114x114.png">
-	<link rel="apple-touch-icon" sizes="120x120" href="https://www.babylonjs.com/img/favicon/apple-icon-120x120.png">
-	<link rel="apple-touch-icon" sizes="144x144" href="https://www.babylonjs.com/img/favicon/apple-icon-144x144.png">
-	<link rel="apple-touch-icon" sizes="152x152" href="https://www.babylonjs.com/img/favicon/apple-icon-152x152.png">
-	<link rel="apple-touch-icon" sizes="180x180" href="https://www.babylonjs.com/img/favicon/apple-icon-180x180.png">
-	<link rel="icon" type="image/png" sizes="192x192"  href="https://www.babylonjs.com/img/favicon/android-icon-192x192.png">
-	<link rel="icon" type="image/png" sizes="32x32" href="https://www.babylonjs.com/img/favicon/favicon-32x32.png">
-	<link rel="icon" type="image/png" sizes="96x96" href="https://www.babylonjs.com/img/favicon/favicon-96x96.png">
-	<link rel="icon" type="image/png" sizes="16x16" href="https://www.babylonjs.com/img/favicon/favicon-16x16.png">
-	<link rel="manifest" href="https://www.babylonjs.com/img/favicon/manifest.json">
-	<meta name="msapplication-TileColor" content="#ffffff">
-	<meta name="msapplication-TileImage" content="https://www.babylonjs.com/img/favicon/ms-icon-144x144.png">
-	<meta name="msapplication-config" content="https://www.babylonjs.com/img/favicon/browserconfig.xml">
-	<meta name="theme-color" content="#ffffff">
+    <link rel="apple-touch-icon" sizes="57x57" href="https://www.babylonjs.com/img/favicon/apple-icon-57x57.png">
+    <link rel="apple-touch-icon" sizes="60x60" href="https://www.babylonjs.com/img/favicon/apple-icon-60x60.png">
+    <link rel="apple-touch-icon" sizes="72x72" href="https://www.babylonjs.com/img/favicon/apple-icon-72x72.png">
+    <link rel="apple-touch-icon" sizes="76x76" href="https://www.babylonjs.com/img/favicon/apple-icon-76x76.png">
+    <link rel="apple-touch-icon" sizes="114x114" href="https://www.babylonjs.com/img/favicon/apple-icon-114x114.png">
+    <link rel="apple-touch-icon" sizes="120x120" href="https://www.babylonjs.com/img/favicon/apple-icon-120x120.png">
+    <link rel="apple-touch-icon" sizes="144x144" href="https://www.babylonjs.com/img/favicon/apple-icon-144x144.png">
+    <link rel="apple-touch-icon" sizes="152x152" href="https://www.babylonjs.com/img/favicon/apple-icon-152x152.png">
+    <link rel="apple-touch-icon" sizes="180x180" href="https://www.babylonjs.com/img/favicon/apple-icon-180x180.png">
+    <link rel="icon" type="image/png" sizes="192x192" href="https://www.babylonjs.com/img/favicon/android-icon-192x192.png">
+    <link rel="icon" type="image/png" sizes="32x32" href="https://www.babylonjs.com/img/favicon/favicon-32x32.png">
+    <link rel="icon" type="image/png" sizes="96x96" href="https://www.babylonjs.com/img/favicon/favicon-96x96.png">
+    <link rel="icon" type="image/png" sizes="16x16" href="https://www.babylonjs.com/img/favicon/favicon-16x16.png">
+    <link rel="manifest" href="https://www.babylonjs.com/img/favicon/manifest.json">
+    <meta name="msapplication-TileColor" content="#ffffff">
+    <meta name="msapplication-TileImage" content="https://www.babylonjs.com/img/favicon/ms-icon-144x144.png">
+    <meta name="msapplication-config" content="https://www.babylonjs.com/img/favicon/browserconfig.xml">
+    <meta name="theme-color" content="#ffffff">
 
     <script src="https://code.jquery.com/pep/0.4.2/pep.min.js"></script>
     <!-- Babylon.js -->
     <script src="https://preview.babylonjs.com/cannon.js"></script>
     <script src="https://preview.babylonjs.com/Oimo.js"></script>
-    <script src="https://preview.babylonjs.com/babylon.js"></script>    
+    <script src="https://preview.babylonjs.com/babylon.js"></script>
     <script src="https://preview.babylonjs.com/inspector/babylon.inspector.bundle.js"></script>
 
     <script src="https://preview.babylonjs.com/materialsLibrary/babylon.fireMaterial.min.js"></script>
@@ -60,9 +60,11 @@
     <script src="https://preview.babylonjs.com/loaders/babylon.objFileLoader.js"></script>
     <script src="https://preview.babylonjs.com/loaders/babylon.stlFileLoader.js"></script>
 
+    <script src="https://preview.babylonjs.com/serializers/babylonjs.serializers.min.js"></script>
+
     <script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script>
-    
-    <script src="https://rawgit.com/BabylonJS/Extensions/master/ClonerSystem/src/babylonx.cloner.js"></script>    
+
+    <script src="https://rawgit.com/BabylonJS/Extensions/master/ClonerSystem/src/babylonx.cloner.js"></script>
     <script src="https://rawgit.com/BabylonJS/Extensions/master/canvas2D/dist/preview%20release/babylon.canvas2d.min.js"></script>
     <script src="https://rawgit.com/BabylonJS/Extensions/master/CompoundShader/src/babylonx.CompoundShader.js"></script>
     <link href="frame.css" rel="stylesheet" />

+ 2 - 0
Playground/index.html

@@ -49,6 +49,8 @@
 
     <script src="https://preview.babylonjs.com/loaders/babylonjs.loaders.js"></script>
 
+    <script src="https://preview.babylonjs.com/serializers/babylonjs.serializers.min.js"></script>
+
     <script src="https://preview.babylonjs.com/gui/babylon.gui.min.js"></script>
 
     <script src="https://rawgit.com/BabylonJS/Extensions/master/ClonerSystem/src/babylonx.cloner.js"></script>

BIN=BIN
Playground/textures/normal.png


+ 9 - 1
Tools/DevLoader/BabylonLoader.js

@@ -39,7 +39,7 @@ var BABYLONDEVTOOLS;
             dependencies = [];
             callback = null;
             min = (document.location.href.toLowerCase().indexOf('dist=min') > 0);
-            useDist = (min || document.location.href.toLowerCase().indexOf('dist=true') > 0);            
+            useDist = (min || useDist || document.location.href.toLowerCase().indexOf('dist=true') > 0);            
             babylonJSPath = '';
         }
 
@@ -91,6 +91,11 @@ var BABYLONDEVTOOLS;
             return this;
         }
 
+        Loader.prototype.useDist = function() {
+            useDist = true;
+            return this;
+        }
+
         Loader.prototype.dequeue = function () {
             if (queue.length == 0) {
                 console.log('Scripts loaded');
@@ -236,6 +241,9 @@ var BABYLONDEVTOOLS;
             // Modules
             if (loadModules) {
                 for (var i = 0; i< settings.modules.length; i++) {
+                    if (settings.modules[i] === "viewer") {
+                        continue;
+                    }
                     this.loadModule(settings[settings.modules[i]]);
                 }
             }

+ 12 - 1
Tools/Gulp/config.json

@@ -230,7 +230,11 @@
         "particles": {
             "files": [
                 "../../src/Particles/babylon.particle.js",
-                "../../src/Particles/babylon.particleSystem.js"
+                "../../src/Particles/babylon.particleSystem.js",
+                "../../src/Particles/babylon.boxParticleEmitter.js",
+                "../../src/Particles/babylon.coneParticleEmitter.js",
+                "../../src/Particles/babylon.sphereParticleEmitter.js",
+                "../../src/Particles/babylon.iParticleEmitterType.js"
             ],
             "dependUpon": [
                 "core"
@@ -1557,6 +1561,13 @@
                     "../../serializers/src/OBJ/babylon.objSerializer.ts"
                 ],
                 "output": "babylon.objSerializer.js"
+            },
+            {
+                "files": [
+                    "../../serializers/src/glTF/2.0/babylon.glTFSerializer.ts",
+                    "../../serializers/src/glTF/2.0/babylon.glTFExporter.ts"
+                ],
+                "output": "babylon.glTF2Serializer.js"
             }
         ],
         "build": {

+ 29 - 11
Tools/Gulp/gulpfile.js

@@ -28,6 +28,8 @@ var config = require("./config.json");
 
 var del = require("del");
 
+var karmaServer = require('karma').Server;
+
 var debug = require("gulp-debug");
 var includeShadersStream;
 var shadersStream;
@@ -229,14 +231,7 @@ gulp.task("build", ["shaders"], function () {
 /*
 * Compiles all typescript files and creating a js and a declaration file.
 */
-var alreadyCompiled = false;
-var forceCompile = false;
 gulp.task("typescript-compile", function () {
-    if (!forceCompile && alreadyCompiled) {
-        return;
-    }
-    alreadyCompiled = true;
-
     var tsResult = gulp.src(config.typescript)
         .pipe(sourcemaps.init())
         .pipe(tsProject());
@@ -428,7 +423,7 @@ var buildExternalLibrary = function (library, settings, watch) {
  * The default task, concat and min the main BJS files.
  */
 gulp.task("default", function (cb) {
-    runSequence("typescript-all", "intellisense", cb);
+    runSequence("typescript-all", "intellisense", "tests-saucelabs", cb);
 });
 
 gulp.task("mainBuild", function (cb) {
@@ -498,11 +493,8 @@ gulp.task("srcTscWatch", function () {
  * Watch ts files and fire repective tasks.
  */
 gulp.task("watch", ["srcTscWatch"], function () {
-    forceCompile = true;
     var interval = 1000;
 
-    // Keep during Prod Tests of srcTscWatch.
-    // var tasks = [gulp.watch(config.typescript, { interval: interval }, ["typescript-compile"])];
     var tasks = [];
 
     config.modules.map(function (module) {
@@ -570,6 +562,32 @@ gulp.task("webserver", function () {
 gulp.task("run", ["watch", "webserver"], function () {
 });
 
+
+gulp.task("tests-integration", function (done) {
+    var kamaServerOptions = {
+        configFile: __dirname + "/../../tests/validation/karma.conf.js",
+        singleRun: false
+    };
+
+    var server = new karmaServer(kamaServerOptions, done);
+    server.start();
+});
+
+gulp.task("tests-saucelabs", function (done) {
+    if (!process.env.TRAVIS) {
+        done();
+        return;
+    }
+
+    var kamaServerOptions = {
+        configFile: __dirname + "/../../tests/validation/karma.conf.saucelabs.js",
+        singleRun: true
+    };
+
+    var server = new karmaServer(kamaServerOptions, done);
+    server.start();
+});
+
 gulp.task("clean-JS-MAP", function () {
     return del([
         "../../src/**/*.js.map", "../../src/**/*.js"

+ 10 - 1
Tools/Gulp/package.json

@@ -43,7 +43,16 @@
         "through2": "~0.6.5",
         "ts-loader": "^2.3.7",
         "typescript": "^2.6.2",
-        "webpack-stream": "^4.0.0"
+        "webpack-stream": "^4.0.0",
+        "karma": "^2.0.0",
+        "karma-chrome-launcher": "^2.2.0",
+        "karma-sauce-launcher": "^1.2.0",
+        "mocha": "^4.0.1",
+        "chai": "^4.1.2",
+        "sinon": "^4.1.3",
+        "karma-mocha": "^1.3.0",
+        "karma-chai": "^0.1.0",
+        "karma-sinon": "^1.0.5"
     },
     "scripts": {
         "install": "npm --prefix ../../Playground/ install ../../Playground/ && gulp typescript-compile && gulp typescript-libraries && gulp deployLocalDev"

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


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


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


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


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 5948 - 5599
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 52 - 51
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 1223 - 402
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js


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

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

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


+ 6 - 0
dist/preview release/inspector/babylon.inspector.js

@@ -3567,6 +3567,12 @@ var INSPECTOR;
                     elem: elemValue,
                     updateFct: function () { return _this._sceneInstrumentation.drawCallsCounter.current.toString(); }
                 });
+                _this._createStatLabel("Texture collisions", _this._panel);
+                elemValue = INSPECTOR.Helpers.CreateDiv('stat-value', _this._panel);
+                _this._updatableProperties.push({
+                    elem: elemValue,
+                    updateFct: function () { return _this._sceneInstrumentation.textureCollisionsCounter.current.toString(); }
+                });
                 _this._createStatLabel("Total lights", _this._panel);
                 elemValue = INSPECTOR.Helpers.CreateDiv('stat-value', _this._panel);
                 _this._updatableProperties.push({

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


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

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

+ 48 - 12
dist/preview release/loaders/babylon.glTF1FileLoader.d.ts

@@ -29,18 +29,30 @@ declare module BABYLON {
         bin: Nullable<ArrayBufferView>;
     }
     interface IGLTFLoader extends IDisposable {
-        importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
-        loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
+        animationStartMode: GLTFLoaderAnimationStartMode;
+        compileMaterials: boolean;
+        useClipPlane: boolean;
+        compileShadowGenerators: boolean;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        onMaterialLoadedObservable: Observable<Material>;
+        onCompleteObservable: Observable<IGLTFLoader>;
+        importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
     }
     class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
-        static CreateGLTFLoaderV1: (parent: GLTFFileLoader) => IGLTFLoader;
-        static CreateGLTFLoaderV2: (parent: GLTFFileLoader) => IGLTFLoader;
+        static CreateGLTFLoaderV1: () => IGLTFLoader;
+        static CreateGLTFLoaderV2: () => IGLTFLoader;
         /**
          * Raised when the asset has been parsed.
          * The data.json property stores the glTF JSON.
          * The data.bin property stores the BIN chunk from a glTF binary or null if the input is not a glTF binary.
          */
-        onParsed: (data: IGLTFLoaderData) => void;
+        onParsedObservable: Observable<IGLTFLoaderData>;
+        private _onParsedObserver;
+        onParsed: (loaderData: IGLTFLoaderData) => void;
         static IncrementalLoading: boolean;
         static HomogeneousCoordinates: boolean;
         /**
@@ -66,21 +78,35 @@ declare module BABYLON {
         /**
          * Raised when the loader creates a mesh after parsing the glTF properties of the mesh.
          */
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        private _onMeshLoadedObserver;
         onMeshLoaded: (mesh: AbstractMesh) => void;
         /**
          * Raised when the loader creates a texture after parsing the glTF properties of the texture.
          */
-        onTextureLoaded: (texture: BaseTexture) => void;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        private _onTextureLoadedObserver;
+        onTextureLoaded: (Texture: BaseTexture) => void;
         /**
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          */
-        onMaterialLoaded: (material: Material) => void;
+        onMaterialLoadedObservable: Observable<Material>;
+        private _onMaterialLoadedObserver;
+        onMaterialLoaded: (Material: Material) => void;
         /**
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * For assets with LODs, raised when all of the LODs are complete.
          * For assets without LODs, raised when the model is complete, immediately after onSuccess.
          */
+        onCompleteObservable: Observable<GLTFFileLoader>;
+        private _onCompleteObserver;
         onComplete: () => void;
+        /**
+        * Raised when the loader is disposed.
+        */
+        onDisposeObservable: Observable<GLTFFileLoader>;
+        private _onDisposeObserver;
+        onDispose: () => void;
         private _loader;
         name: string;
         extensions: ISceneLoaderPluginExtensions;
@@ -88,12 +114,12 @@ declare module BABYLON {
          * Disposes the loader, releases resources during load, and cancels any outstanding requests.
          */
         dispose(): void;
-        importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
-        loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
         canDirectLoad(data: string): boolean;
         rewriteRootURL: (rootUrl: string, responseURL?: string) => string;
         createPlugin(): ISceneLoaderPlugin | ISceneLoaderPluginAsync;
-        private static _parse(data);
+        private _parse(data);
         private _getLoader(loaderData);
         private static _parseBinary(data);
         private static _parseV1(binaryReader);
@@ -497,9 +523,19 @@ declare module BABYLON.GLTF1 {
             [name: string]: GLTFLoaderExtension;
         };
         static RegisterExtension(extension: GLTFLoaderExtension): void;
+        coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
+        animationStartMode: GLTFLoaderAnimationStartMode;
+        compileMaterials: boolean;
+        useClipPlane: boolean;
+        compileShadowGenerators: boolean;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        onMaterialLoadedObservable: Observable<Material>;
+        onCompleteObservable: Observable<IGLTFLoader>;
         dispose(): void;
-        importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void): boolean;
-        loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: () => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void): void;
+        importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress: (event: SceneLoaderProgressEvent) => void, onError: (message: string) => void): boolean;
+        loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: () => void, onProgress: (event: SceneLoaderProgressEvent) => void, onError: (message: string) => void): void;
         private _loadShadersAsync(gltfRuntime, onload);
         private _loadBuffersAsync(gltfRuntime, onLoad, onProgress?);
         private _createNodes(gltfRuntime);

+ 138 - 19
dist/preview release/loaders/babylon.glTF1FileLoader.js

@@ -29,6 +29,13 @@ var BABYLON;
     })(GLTFLoaderAnimationStartMode = BABYLON.GLTFLoaderAnimationStartMode || (BABYLON.GLTFLoaderAnimationStartMode = {}));
     var GLTFFileLoader = /** @class */ (function () {
         function GLTFFileLoader() {
+            // #region Common options
+            /**
+             * Raised when the asset has been parsed.
+             * The data.json property stores the glTF JSON.
+             * The data.bin property stores the BIN chunk from a glTF binary or null if the input is not a glTF binary.
+             */
+            this.onParsedObservable = new BABYLON.Observable();
             // #endregion
             // #region V2 options
             /**
@@ -51,26 +58,115 @@ var BABYLON;
              * Set to true to compile shadow generators before raising the success callback.
              */
             this.compileShadowGenerators = false;
+            /**
+             * Raised when the loader creates a mesh after parsing the glTF properties of the mesh.
+             */
+            this.onMeshLoadedObservable = new BABYLON.Observable();
+            /**
+             * Raised when the loader creates a texture after parsing the glTF properties of the texture.
+             */
+            this.onTextureLoadedObservable = new BABYLON.Observable();
+            /**
+             * Raised when the loader creates a material after parsing the glTF properties of the material.
+             */
+            this.onMaterialLoadedObservable = new BABYLON.Observable();
+            /**
+             * Raised when the asset is completely loaded, immediately before the loader is disposed.
+             * For assets with LODs, raised when all of the LODs are complete.
+             * For assets without LODs, raised when the model is complete, immediately after onSuccess.
+             */
+            this.onCompleteObservable = new BABYLON.Observable();
+            /**
+            * Raised when the loader is disposed.
+            */
+            this.onDisposeObservable = new BABYLON.Observable();
+            // #endregion
+            this._loader = null;
             this.name = "gltf";
             this.extensions = {
                 ".gltf": { isBinary: false },
                 ".glb": { isBinary: true }
             };
         }
+        Object.defineProperty(GLTFFileLoader.prototype, "onParsed", {
+            set: function (callback) {
+                if (this._onParsedObserver) {
+                    this.onParsedObservable.remove(this._onParsedObserver);
+                }
+                this._onParsedObserver = this.onParsedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onMeshLoaded", {
+            set: function (callback) {
+                if (this._onMeshLoadedObserver) {
+                    this.onMeshLoadedObservable.remove(this._onMeshLoadedObserver);
+                }
+                this._onMeshLoadedObserver = this.onMeshLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onTextureLoaded", {
+            set: function (callback) {
+                if (this._onTextureLoadedObserver) {
+                    this.onTextureLoadedObservable.remove(this._onTextureLoadedObserver);
+                }
+                this._onTextureLoadedObserver = this.onTextureLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onMaterialLoaded", {
+            set: function (callback) {
+                if (this._onMaterialLoadedObserver) {
+                    this.onMaterialLoadedObservable.remove(this._onMaterialLoadedObserver);
+                }
+                this._onMaterialLoadedObserver = this.onMaterialLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onComplete", {
+            set: function (callback) {
+                if (this._onCompleteObserver) {
+                    this.onCompleteObservable.remove(this._onCompleteObserver);
+                }
+                this._onCompleteObserver = this.onCompleteObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onDispose", {
+            set: function (callback) {
+                if (this._onDisposeObserver) {
+                    this.onDisposeObservable.remove(this._onDisposeObserver);
+                }
+                this._onDisposeObserver = this.onDisposeObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
         /**
          * Disposes the loader, releases resources during load, and cancels any outstanding requests.
          */
         GLTFFileLoader.prototype.dispose = function () {
             if (this._loader) {
                 this._loader.dispose();
-            }
+                this._loader = null;
+            }
+            this.onParsedObservable.clear();
+            this.onMeshLoadedObservable.clear();
+            this.onTextureLoadedObservable.clear();
+            this.onMaterialLoadedObservable.clear();
+            this.onCompleteObservable.clear();
+            this.onDisposeObservable.notifyObservers(this);
+            this.onDisposeObservable.clear();
         };
         GLTFFileLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError) {
             try {
-                var loaderData = GLTFFileLoader._parse(data);
-                if (this.onParsed) {
-                    this.onParsed(loaderData);
-                }
+                var loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
                 this._loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onSuccess, onProgress, onError);
             }
@@ -85,10 +181,7 @@ var BABYLON;
         };
         GLTFFileLoader.prototype.loadAsync = function (scene, data, rootUrl, onSuccess, onProgress, onError) {
             try {
-                var loaderData = GLTFFileLoader._parse(data);
-                if (this.onParsed) {
-                    this.onParsed(loaderData);
-                }
+                var loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
                 this._loader.loadAsync(scene, loaderData, rootUrl, onSuccess, onProgress, onError);
             }
@@ -107,16 +200,22 @@ var BABYLON;
         GLTFFileLoader.prototype.createPlugin = function () {
             return new GLTFFileLoader();
         };
-        GLTFFileLoader._parse = function (data) {
+        GLTFFileLoader.prototype._parse = function (data) {
+            var parsedData;
             if (data instanceof ArrayBuffer) {
-                return GLTFFileLoader._parseBinary(data);
+                parsedData = GLTFFileLoader._parseBinary(data);
             }
-            return {
-                json: JSON.parse(data),
-                bin: null
-            };
+            else {
+                parsedData = {
+                    json: JSON.parse(data),
+                    bin: null
+                };
+            }
+            this.onParsedObservable.notifyObservers(parsedData);
+            return parsedData;
         };
         GLTFFileLoader.prototype._getLoader = function (loaderData) {
+            var _this = this;
             var loaderVersion = { major: 2, minor: 0 };
             var asset = loaderData.json.asset || {};
             var version = GLTFFileLoader._parseVersion(asset.version);
@@ -140,7 +239,17 @@ var BABYLON;
             if (!createLoader) {
                 throw new Error("Unsupported version: " + asset.version);
             }
-            return createLoader(this);
+            var loader = createLoader();
+            loader.coordinateSystemMode = this.coordinateSystemMode;
+            loader.animationStartMode = this.animationStartMode;
+            loader.compileMaterials = this.compileMaterials;
+            loader.useClipPlane = this.useClipPlane;
+            loader.compileShadowGenerators = this.compileShadowGenerators;
+            loader.onMeshLoadedObservable.add(function (mesh) { return _this.onMeshLoadedObservable.notifyObservers(mesh); });
+            loader.onTextureLoadedObservable.add(function (texture) { return _this.onTextureLoadedObservable.notifyObservers(texture); });
+            loader.onMaterialLoadedObservable.add(function (material) { return _this.onMaterialLoadedObservable.notifyObservers(material); });
+            loader.onCompleteObservable.add(function () { return _this.onCompleteObservable.notifyObservers(_this); });
+            return loader;
         };
         GLTFFileLoader._parseBinary = function (data) {
             var Binary = {
@@ -1676,6 +1785,17 @@ var BABYLON;
         */
         var GLTFLoader = /** @class */ (function () {
             function GLTFLoader() {
+                // #region Stubs for IGLTFLoader interface
+                this.coordinateSystemMode = BABYLON.GLTFLoaderCoordinateSystemMode.AUTO;
+                this.animationStartMode = BABYLON.GLTFLoaderAnimationStartMode.FIRST;
+                this.compileMaterials = false;
+                this.useClipPlane = false;
+                this.compileShadowGenerators = false;
+                this.onDisposeObservable = new BABYLON.Observable();
+                this.onMeshLoadedObservable = new BABYLON.Observable();
+                this.onTextureLoadedObservable = new BABYLON.Observable();
+                this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onCompleteObservable = new BABYLON.Observable();
             }
             GLTFLoader.RegisterExtension = function (extension) {
                 if (GLTFLoader.Extensions[extension.name]) {
@@ -1684,9 +1804,8 @@ var BABYLON;
                 }
                 GLTFLoader.Extensions[extension.name] = extension;
             };
-            GLTFLoader.prototype.dispose = function () {
-                // do nothing
-            };
+            GLTFLoader.prototype.dispose = function () { };
+            // #endregion
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError) {
                 var _this = this;
                 scene.useRightHandedSystem = true;

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


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

@@ -29,18 +29,30 @@ declare module BABYLON {
         bin: Nullable<ArrayBufferView>;
     }
     interface IGLTFLoader extends IDisposable {
-        importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
-        loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
+        animationStartMode: GLTFLoaderAnimationStartMode;
+        compileMaterials: boolean;
+        useClipPlane: boolean;
+        compileShadowGenerators: boolean;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        onMaterialLoadedObservable: Observable<Material>;
+        onCompleteObservable: Observable<IGLTFLoader>;
+        importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
     }
     class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
-        static CreateGLTFLoaderV1: (parent: GLTFFileLoader) => IGLTFLoader;
-        static CreateGLTFLoaderV2: (parent: GLTFFileLoader) => IGLTFLoader;
+        static CreateGLTFLoaderV1: () => IGLTFLoader;
+        static CreateGLTFLoaderV2: () => IGLTFLoader;
         /**
          * Raised when the asset has been parsed.
          * The data.json property stores the glTF JSON.
          * The data.bin property stores the BIN chunk from a glTF binary or null if the input is not a glTF binary.
          */
-        onParsed: (data: IGLTFLoaderData) => void;
+        onParsedObservable: Observable<IGLTFLoaderData>;
+        private _onParsedObserver;
+        onParsed: (loaderData: IGLTFLoaderData) => void;
         static IncrementalLoading: boolean;
         static HomogeneousCoordinates: boolean;
         /**
@@ -66,21 +78,35 @@ declare module BABYLON {
         /**
          * Raised when the loader creates a mesh after parsing the glTF properties of the mesh.
          */
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        private _onMeshLoadedObserver;
         onMeshLoaded: (mesh: AbstractMesh) => void;
         /**
          * Raised when the loader creates a texture after parsing the glTF properties of the texture.
          */
-        onTextureLoaded: (texture: BaseTexture) => void;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        private _onTextureLoadedObserver;
+        onTextureLoaded: (Texture: BaseTexture) => void;
         /**
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          */
-        onMaterialLoaded: (material: Material) => void;
+        onMaterialLoadedObservable: Observable<Material>;
+        private _onMaterialLoadedObserver;
+        onMaterialLoaded: (Material: Material) => void;
         /**
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * For assets with LODs, raised when all of the LODs are complete.
          * For assets without LODs, raised when the model is complete, immediately after onSuccess.
          */
+        onCompleteObservable: Observable<GLTFFileLoader>;
+        private _onCompleteObserver;
         onComplete: () => void;
+        /**
+        * Raised when the loader is disposed.
+        */
+        onDisposeObservable: Observable<GLTFFileLoader>;
+        private _onDisposeObserver;
+        onDispose: () => void;
         private _loader;
         name: string;
         extensions: ISceneLoaderPluginExtensions;
@@ -88,12 +114,12 @@ declare module BABYLON {
          * Disposes the loader, releases resources during load, and cancels any outstanding requests.
          */
         dispose(): void;
-        importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
-        loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
         canDirectLoad(data: string): boolean;
         rewriteRootURL: (rootUrl: string, responseURL?: string) => string;
         createPlugin(): ISceneLoaderPlugin | ISceneLoaderPluginAsync;
-        private static _parse(data);
+        private _parse(data);
         private _getLoader(loaderData);
         private static _parseBinary(data);
         private static _parseV1(binaryReader);
@@ -361,7 +387,6 @@ declare module BABYLON.GLTF2 {
         _gltf: IGLTF;
         _babylonScene: Scene;
         private _disposed;
-        private _parent;
         private _rootUrl;
         private _defaultMaterial;
         private _defaultSampler;
@@ -379,11 +404,19 @@ declare module BABYLON.GLTF2 {
             [name: string]: GLTFLoaderExtension;
         };
         static RegisterExtension(extension: GLTFLoaderExtension): void;
-        private static _createProgressEvent;
-        constructor(parent: GLTFFileLoader);
+        coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
+        animationStartMode: GLTFLoaderAnimationStartMode;
+        compileMaterials: boolean;
+        useClipPlane: boolean;
+        compileShadowGenerators: boolean;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        onMaterialLoadedObservable: Observable<Material>;
+        onCompleteObservable: Observable<IGLTFLoader>;
         dispose(): void;
-        importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
-        loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
         private _loadAsync(nodeNames, scene, data, rootUrl, onSuccess?, onProgress?, onError?);
         private _onProgress();
         _executeWhenRenderReady(func: () => void): void;
@@ -454,6 +487,8 @@ declare module BABYLON.GLTF2 {
         private _compileMaterialAsync(babylonMaterial, babylonMesh, onSuccess);
         private _compileMaterialsAsync(onSuccess);
         private _compileShadowGeneratorsAsync(onSuccess);
+        private _abortRequests();
+        private _releaseResources();
     }
 }
 

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

@@ -29,6 +29,13 @@ var BABYLON;
     })(GLTFLoaderAnimationStartMode = BABYLON.GLTFLoaderAnimationStartMode || (BABYLON.GLTFLoaderAnimationStartMode = {}));
     var GLTFFileLoader = /** @class */ (function () {
         function GLTFFileLoader() {
+            // #region Common options
+            /**
+             * Raised when the asset has been parsed.
+             * The data.json property stores the glTF JSON.
+             * The data.bin property stores the BIN chunk from a glTF binary or null if the input is not a glTF binary.
+             */
+            this.onParsedObservable = new BABYLON.Observable();
             // #endregion
             // #region V2 options
             /**
@@ -51,26 +58,115 @@ var BABYLON;
              * Set to true to compile shadow generators before raising the success callback.
              */
             this.compileShadowGenerators = false;
+            /**
+             * Raised when the loader creates a mesh after parsing the glTF properties of the mesh.
+             */
+            this.onMeshLoadedObservable = new BABYLON.Observable();
+            /**
+             * Raised when the loader creates a texture after parsing the glTF properties of the texture.
+             */
+            this.onTextureLoadedObservable = new BABYLON.Observable();
+            /**
+             * Raised when the loader creates a material after parsing the glTF properties of the material.
+             */
+            this.onMaterialLoadedObservable = new BABYLON.Observable();
+            /**
+             * Raised when the asset is completely loaded, immediately before the loader is disposed.
+             * For assets with LODs, raised when all of the LODs are complete.
+             * For assets without LODs, raised when the model is complete, immediately after onSuccess.
+             */
+            this.onCompleteObservable = new BABYLON.Observable();
+            /**
+            * Raised when the loader is disposed.
+            */
+            this.onDisposeObservable = new BABYLON.Observable();
+            // #endregion
+            this._loader = null;
             this.name = "gltf";
             this.extensions = {
                 ".gltf": { isBinary: false },
                 ".glb": { isBinary: true }
             };
         }
+        Object.defineProperty(GLTFFileLoader.prototype, "onParsed", {
+            set: function (callback) {
+                if (this._onParsedObserver) {
+                    this.onParsedObservable.remove(this._onParsedObserver);
+                }
+                this._onParsedObserver = this.onParsedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onMeshLoaded", {
+            set: function (callback) {
+                if (this._onMeshLoadedObserver) {
+                    this.onMeshLoadedObservable.remove(this._onMeshLoadedObserver);
+                }
+                this._onMeshLoadedObserver = this.onMeshLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onTextureLoaded", {
+            set: function (callback) {
+                if (this._onTextureLoadedObserver) {
+                    this.onTextureLoadedObservable.remove(this._onTextureLoadedObserver);
+                }
+                this._onTextureLoadedObserver = this.onTextureLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onMaterialLoaded", {
+            set: function (callback) {
+                if (this._onMaterialLoadedObserver) {
+                    this.onMaterialLoadedObservable.remove(this._onMaterialLoadedObserver);
+                }
+                this._onMaterialLoadedObserver = this.onMaterialLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onComplete", {
+            set: function (callback) {
+                if (this._onCompleteObserver) {
+                    this.onCompleteObservable.remove(this._onCompleteObserver);
+                }
+                this._onCompleteObserver = this.onCompleteObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onDispose", {
+            set: function (callback) {
+                if (this._onDisposeObserver) {
+                    this.onDisposeObservable.remove(this._onDisposeObserver);
+                }
+                this._onDisposeObserver = this.onDisposeObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
         /**
          * Disposes the loader, releases resources during load, and cancels any outstanding requests.
          */
         GLTFFileLoader.prototype.dispose = function () {
             if (this._loader) {
                 this._loader.dispose();
+                this._loader = null;
             }
+            this.onParsedObservable.clear();
+            this.onMeshLoadedObservable.clear();
+            this.onTextureLoadedObservable.clear();
+            this.onMaterialLoadedObservable.clear();
+            this.onCompleteObservable.clear();
+            this.onDisposeObservable.notifyObservers(this);
+            this.onDisposeObservable.clear();
         };
         GLTFFileLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError) {
             try {
-                var loaderData = GLTFFileLoader._parse(data);
-                if (this.onParsed) {
-                    this.onParsed(loaderData);
-                }
+                var loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
                 this._loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onSuccess, onProgress, onError);
             }
@@ -85,10 +181,7 @@ var BABYLON;
         };
         GLTFFileLoader.prototype.loadAsync = function (scene, data, rootUrl, onSuccess, onProgress, onError) {
             try {
-                var loaderData = GLTFFileLoader._parse(data);
-                if (this.onParsed) {
-                    this.onParsed(loaderData);
-                }
+                var loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
                 this._loader.loadAsync(scene, loaderData, rootUrl, onSuccess, onProgress, onError);
             }
@@ -107,16 +200,22 @@ var BABYLON;
         GLTFFileLoader.prototype.createPlugin = function () {
             return new GLTFFileLoader();
         };
-        GLTFFileLoader._parse = function (data) {
+        GLTFFileLoader.prototype._parse = function (data) {
+            var parsedData;
             if (data instanceof ArrayBuffer) {
-                return GLTFFileLoader._parseBinary(data);
+                parsedData = GLTFFileLoader._parseBinary(data);
             }
-            return {
-                json: JSON.parse(data),
-                bin: null
-            };
+            else {
+                parsedData = {
+                    json: JSON.parse(data),
+                    bin: null
+                };
+            }
+            this.onParsedObservable.notifyObservers(parsedData);
+            return parsedData;
         };
         GLTFFileLoader.prototype._getLoader = function (loaderData) {
+            var _this = this;
             var loaderVersion = { major: 2, minor: 0 };
             var asset = loaderData.json.asset || {};
             var version = GLTFFileLoader._parseVersion(asset.version);
@@ -140,7 +239,17 @@ var BABYLON;
             if (!createLoader) {
                 throw new Error("Unsupported version: " + asset.version);
             }
-            return createLoader(this);
+            var loader = createLoader();
+            loader.coordinateSystemMode = this.coordinateSystemMode;
+            loader.animationStartMode = this.animationStartMode;
+            loader.compileMaterials = this.compileMaterials;
+            loader.useClipPlane = this.useClipPlane;
+            loader.compileShadowGenerators = this.compileShadowGenerators;
+            loader.onMeshLoadedObservable.add(function (mesh) { return _this.onMeshLoadedObservable.notifyObservers(mesh); });
+            loader.onTextureLoadedObservable.add(function (texture) { return _this.onTextureLoadedObservable.notifyObservers(texture); });
+            loader.onMaterialLoadedObservable.add(function (material) { return _this.onMaterialLoadedObservable.notifyObservers(material); });
+            loader.onCompleteObservable.add(function () { return _this.onCompleteObservable.notifyObservers(_this); });
+            return loader;
         };
         GLTFFileLoader._parseBinary = function (data) {
             var Binary = {
@@ -375,7 +484,7 @@ var BABYLON;
             return GLTFLoaderTracker;
         }());
         var GLTFLoader = /** @class */ (function () {
-            function GLTFLoader(parent) {
+            function GLTFLoader() {
                 this._disposed = false;
                 this._defaultSampler = {};
                 this._renderReady = false;
@@ -386,7 +495,16 @@ var BABYLON;
                 // Count of pending work that needs to complete before the loader is disposed.
                 this._loaderPendingCount = 0;
                 this._loaderTrackers = new Array();
-                this._parent = parent;
+                this.coordinateSystemMode = BABYLON.GLTFLoaderCoordinateSystemMode.AUTO;
+                this.animationStartMode = BABYLON.GLTFLoaderAnimationStartMode.FIRST;
+                this.compileMaterials = false;
+                this.useClipPlane = false;
+                this.compileShadowGenerators = false;
+                this.onDisposeObservable = new BABYLON.Observable();
+                this.onMeshLoadedObservable = new BABYLON.Observable();
+                this.onTextureLoadedObservable = new BABYLON.Observable();
+                this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onCompleteObservable = new BABYLON.Observable();
             }
             GLTFLoader.RegisterExtension = function (extension) {
                 if (GLTFLoader.Extensions[extension.name]) {
@@ -402,22 +520,9 @@ var BABYLON;
                     return;
                 }
                 this._disposed = true;
-                // Abort requests that are not complete
-                for (var _i = 0, _a = this._requests; _i < _a.length; _i++) {
-                    var request = _a[_i];
-                    if (request.readyState !== (XMLHttpRequest.DONE || 4)) {
-                        request.abort();
-                    }
-                }
-                // Revoke object urls created during load
-                if (this._gltf.textures) {
-                    for (var _b = 0, _c = this._gltf.textures; _b < _c.length; _b++) {
-                        var texture = _c[_b];
-                        if (texture.url) {
-                            URL.revokeObjectURL(texture.url);
-                        }
-                    }
-                }
+                this._abortRequests();
+                this._releaseResources();
+                this.onDisposeObservable.notifyObservers(this);
             };
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError) {
                 var _this = this;
@@ -461,7 +566,7 @@ var BABYLON;
                     loaded += request._loaded;
                     total += request._total;
                 }
-                this._progressCallback(GLTFLoader._createProgressEvent(lengthComputable, loaded, lengthComputable ? total : 0));
+                this._progressCallback(new BABYLON.SceneLoaderProgressEvent(lengthComputable, loaded, lengthComputable ? total : 0));
             };
             GLTFLoader.prototype._executeWhenRenderReady = function (func) {
                 if (this._renderReady) {
@@ -480,10 +585,9 @@ var BABYLON;
                 this._renderReadyObservable.notifyObservers(this);
             };
             GLTFLoader.prototype._onComplete = function () {
-                if (this._parent.onComplete) {
-                    this._parent.onComplete();
-                }
-                this.dispose();
+                this._abortRequests();
+                this._releaseResources();
+                this.onCompleteObservable.notifyObservers(this);
             };
             GLTFLoader.prototype._loadData = function (data) {
                 this._gltf = data.json;
@@ -547,7 +651,7 @@ var BABYLON;
                 if (!animations) {
                     return;
                 }
-                switch (this._parent.animationStartMode) {
+                switch (this.animationStartMode) {
                     case BABYLON.GLTFLoaderAnimationStartMode.NONE: {
                         // do nothing
                         break;
@@ -571,7 +675,7 @@ var BABYLON;
                         break;
                     }
                     default: {
-                        BABYLON.Tools.Error("Invalid animation start mode " + this._parent.animationStartMode);
+                        BABYLON.Tools.Error("Invalid animation start mode " + this.animationStartMode);
                         return;
                     }
                 }
@@ -586,7 +690,7 @@ var BABYLON;
             GLTFLoader.prototype._loadScene = function (context, scene, nodeNames) {
                 var _this = this;
                 this._rootNode = { babylonMesh: new BABYLON.Mesh("__root__", this._babylonScene) };
-                switch (this._parent.coordinateSystemMode) {
+                switch (this.coordinateSystemMode) {
                     case BABYLON.GLTFLoaderCoordinateSystemMode.AUTO: {
                         if (!this._babylonScene.useRightHandedSystem) {
                             this._rootNode.rotation = [0, 1, 0, 0];
@@ -600,13 +704,11 @@ var BABYLON;
                         break;
                     }
                     default: {
-                        BABYLON.Tools.Error("Invalid coordinate system mode " + this._parent.coordinateSystemMode);
+                        BABYLON.Tools.Error("Invalid coordinate system mode " + this.coordinateSystemMode);
                         return;
                     }
                 }
-                if (this._parent.onMeshLoaded) {
-                    this._parent.onMeshLoaded(this._rootNode.babylonMesh);
-                }
+                this.onMeshLoadedObservable.notifyObservers(this._rootNode.babylonMesh);
                 var nodeIndices = scene.nodes;
                 this._traverseNodes(context, nodeIndices, function (node, parentNode) {
                     node.parent = parentNode;
@@ -682,9 +784,7 @@ var BABYLON;
                         this._loadNode("#/nodes/" + index, childNode);
                     }
                 }
-                if (this._parent.onMeshLoaded) {
-                    this._parent.onMeshLoaded(node.babylonMesh);
-                }
+                this.onMeshLoadedObservable.notifyObservers(node.babylonMesh);
             };
             GLTFLoader.prototype._loadMesh = function (context, node, mesh) {
                 var _this = this;
@@ -727,8 +827,8 @@ var BABYLON;
                             throw new Error(context + ": Failed to find material " + primitive.material);
                         }
                         this._loadMaterial("#/materials/" + material.index, material, function (babylonMaterial, isNew) {
-                            if (isNew && _this._parent.onMaterialLoaded) {
-                                _this._parent.onMaterialLoaded(babylonMaterial);
+                            if (isNew) {
+                                _this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                             }
                             node.babylonMesh.material = babylonMaterial;
                         });
@@ -749,8 +849,8 @@ var BABYLON;
                                 throw new Error(context + ": Failed to find material " + primitive.material);
                             }
                             this_1._loadMaterial("#/materials/" + material.index, material, function (babylonMaterial, isNew) {
-                                if (isNew && _this._parent.onMaterialLoaded) {
-                                    _this._parent.onMaterialLoaded(babylonMaterial);
+                                if (isNew) {
+                                    _this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                                 }
                                 subMaterials_1[index] = babylonMaterial;
                             });
@@ -1708,9 +1808,7 @@ var BABYLON;
                 babylonTexture.wrapU = sampler.wrapU;
                 babylonTexture.wrapV = sampler.wrapV;
                 babylonTexture.name = texture.name || "texture" + texture.index;
-                if (this._parent.onTextureLoaded) {
-                    this._parent.onTextureLoaded(babylonTexture);
-                }
+                this.onTextureLoadedObservable.notifyObservers(babylonTexture);
                 return babylonTexture;
             };
             GLTFLoader.prototype._loadSampler = function (context, sampler) {
@@ -1760,12 +1858,11 @@ var BABYLON;
                     _this._tryCatchOnError(function () {
                         throw new BABYLON.LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request);
                     });
-                }, function (oldRequest, newRequest) {
-                    _this._requests.splice(_this._requests.indexOf(oldRequest), 1, newRequest);
                 });
-                if (request) {
-                    this._requests.push(request);
-                }
+                this._requests.push(request);
+                request.onCompleteObservable.add(function () {
+                    _this._requests.splice(_this._requests.indexOf(request), 1);
+                });
             };
             GLTFLoader.prototype._tryCatchOnError = function (handler) {
                 if (this._disposed) {
@@ -1855,7 +1952,7 @@ var BABYLON;
             };
             GLTFLoader.prototype._compileMaterialAsync = function (babylonMaterial, babylonMesh, onSuccess) {
                 var _this = this;
-                if (this._parent.useClipPlane) {
+                if (this.useClipPlane) {
                     babylonMaterial.forceCompilation(babylonMesh, function () {
                         babylonMaterial.forceCompilation(babylonMesh, function () {
                             _this._tryCatchOnError(onSuccess);
@@ -1869,7 +1966,7 @@ var BABYLON;
                 }
             };
             GLTFLoader.prototype._compileMaterialsAsync = function (onSuccess) {
-                if (!this._parent.compileMaterials || !this._gltf.materials) {
+                if (!this.compileMaterials || !this._gltf.materials) {
                     onSuccess();
                     return;
                 }
@@ -1918,7 +2015,7 @@ var BABYLON;
             };
             GLTFLoader.prototype._compileShadowGeneratorsAsync = function (onSuccess) {
                 var _this = this;
-                if (!this._parent.compileShadowGenerators) {
+                if (!this.compileShadowGenerators) {
                     onSuccess();
                     return;
                 }
@@ -1947,25 +2044,29 @@ var BABYLON;
                     }
                 }
             };
-            GLTFLoader.Extensions = {};
-            // IE 11 Compatibility.
-            GLTFLoader._createProgressEvent = (typeof window["ProgressEvent"] === "function")
-                ? function (lengthComputable, loaded, total) {
-                    return new ProgressEvent("GLTFLoaderProgress", {
-                        lengthComputable: lengthComputable,
-                        loaded: loaded,
-                        total: total
-                    });
+            GLTFLoader.prototype._abortRequests = function () {
+                for (var _i = 0, _a = this._requests; _i < _a.length; _i++) {
+                    var request = _a[_i];
+                    request.abort();
                 }
-                : function (lengthComputable, loaded, total) {
-                    var event = document.createEvent("ProgressEvent");
-                    event.initProgressEvent("GLTFLoaderProgress", false, false, lengthComputable, loaded, total);
-                    return event;
-                };
+                this._requests.length = 0;
+            };
+            GLTFLoader.prototype._releaseResources = function () {
+                if (this._gltf.textures) {
+                    for (var _i = 0, _a = this._gltf.textures; _i < _a.length; _i++) {
+                        var texture = _a[_i];
+                        if (texture.url) {
+                            URL.revokeObjectURL(texture.url);
+                            texture.url = undefined;
+                        }
+                    }
+                }
+            };
+            GLTFLoader.Extensions = {};
             return GLTFLoader;
         }());
         GLTF2.GLTFLoader = GLTFLoader;
-        BABYLON.GLTFFileLoader.CreateGLTFLoaderV2 = function (parent) { return new GLTFLoader(parent); };
+        BABYLON.GLTFFileLoader.CreateGLTFLoaderV2 = function () { return new GLTFLoader(); };
     })(GLTF2 = BABYLON.GLTF2 || (BABYLON.GLTF2 = {}));
 })(BABYLON || (BABYLON = {}));
 

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


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

@@ -29,18 +29,30 @@ declare module BABYLON {
         bin: Nullable<ArrayBufferView>;
     }
     interface IGLTFLoader extends IDisposable {
-        importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
-        loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
+        animationStartMode: GLTFLoaderAnimationStartMode;
+        compileMaterials: boolean;
+        useClipPlane: boolean;
+        compileShadowGenerators: boolean;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        onMaterialLoadedObservable: Observable<Material>;
+        onCompleteObservable: Observable<IGLTFLoader>;
+        importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
     }
     class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
-        static CreateGLTFLoaderV1: (parent: GLTFFileLoader) => IGLTFLoader;
-        static CreateGLTFLoaderV2: (parent: GLTFFileLoader) => IGLTFLoader;
+        static CreateGLTFLoaderV1: () => IGLTFLoader;
+        static CreateGLTFLoaderV2: () => IGLTFLoader;
         /**
          * Raised when the asset has been parsed.
          * The data.json property stores the glTF JSON.
          * The data.bin property stores the BIN chunk from a glTF binary or null if the input is not a glTF binary.
          */
-        onParsed: (data: IGLTFLoaderData) => void;
+        onParsedObservable: Observable<IGLTFLoaderData>;
+        private _onParsedObserver;
+        onParsed: (loaderData: IGLTFLoaderData) => void;
         static IncrementalLoading: boolean;
         static HomogeneousCoordinates: boolean;
         /**
@@ -66,21 +78,35 @@ declare module BABYLON {
         /**
          * Raised when the loader creates a mesh after parsing the glTF properties of the mesh.
          */
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        private _onMeshLoadedObserver;
         onMeshLoaded: (mesh: AbstractMesh) => void;
         /**
          * Raised when the loader creates a texture after parsing the glTF properties of the texture.
          */
-        onTextureLoaded: (texture: BaseTexture) => void;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        private _onTextureLoadedObserver;
+        onTextureLoaded: (Texture: BaseTexture) => void;
         /**
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          */
-        onMaterialLoaded: (material: Material) => void;
+        onMaterialLoadedObservable: Observable<Material>;
+        private _onMaterialLoadedObserver;
+        onMaterialLoaded: (Material: Material) => void;
         /**
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * For assets with LODs, raised when all of the LODs are complete.
          * For assets without LODs, raised when the model is complete, immediately after onSuccess.
          */
+        onCompleteObservable: Observable<GLTFFileLoader>;
+        private _onCompleteObserver;
         onComplete: () => void;
+        /**
+        * Raised when the loader is disposed.
+        */
+        onDisposeObservable: Observable<GLTFFileLoader>;
+        private _onDisposeObserver;
+        onDispose: () => void;
         private _loader;
         name: string;
         extensions: ISceneLoaderPluginExtensions;
@@ -88,12 +114,12 @@ declare module BABYLON {
          * Disposes the loader, releases resources during load, and cancels any outstanding requests.
          */
         dispose(): void;
-        importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
-        loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
         canDirectLoad(data: string): boolean;
         rewriteRootURL: (rootUrl: string, responseURL?: string) => string;
         createPlugin(): ISceneLoaderPlugin | ISceneLoaderPluginAsync;
-        private static _parse(data);
+        private _parse(data);
         private _getLoader(loaderData);
         private static _parseBinary(data);
         private static _parseV1(binaryReader);
@@ -497,9 +523,19 @@ declare module BABYLON.GLTF1 {
             [name: string]: GLTFLoaderExtension;
         };
         static RegisterExtension(extension: GLTFLoaderExtension): void;
+        coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
+        animationStartMode: GLTFLoaderAnimationStartMode;
+        compileMaterials: boolean;
+        useClipPlane: boolean;
+        compileShadowGenerators: boolean;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        onMaterialLoadedObservable: Observable<Material>;
+        onCompleteObservable: Observable<IGLTFLoader>;
         dispose(): void;
-        importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void): boolean;
-        loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: () => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void): void;
+        importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress: (event: SceneLoaderProgressEvent) => void, onError: (message: string) => void): boolean;
+        loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: () => void, onProgress: (event: SceneLoaderProgressEvent) => void, onError: (message: string) => void): void;
         private _loadShadersAsync(gltfRuntime, onload);
         private _loadBuffersAsync(gltfRuntime, onLoad, onProgress?);
         private _createNodes(gltfRuntime);
@@ -908,7 +944,6 @@ declare module BABYLON.GLTF2 {
         _gltf: IGLTF;
         _babylonScene: Scene;
         private _disposed;
-        private _parent;
         private _rootUrl;
         private _defaultMaterial;
         private _defaultSampler;
@@ -926,11 +961,19 @@ declare module BABYLON.GLTF2 {
             [name: string]: GLTFLoaderExtension;
         };
         static RegisterExtension(extension: GLTFLoaderExtension): void;
-        private static _createProgressEvent;
-        constructor(parent: GLTFFileLoader);
+        coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
+        animationStartMode: GLTFLoaderAnimationStartMode;
+        compileMaterials: boolean;
+        useClipPlane: boolean;
+        compileShadowGenerators: boolean;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        onMaterialLoadedObservable: Observable<Material>;
+        onCompleteObservable: Observable<IGLTFLoader>;
         dispose(): void;
-        importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
-        loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
         private _loadAsync(nodeNames, scene, data, rootUrl, onSuccess?, onProgress?, onError?);
         private _onProgress();
         _executeWhenRenderReady(func: () => void): void;
@@ -1001,6 +1044,8 @@ declare module BABYLON.GLTF2 {
         private _compileMaterialAsync(babylonMaterial, babylonMesh, onSuccess);
         private _compileMaterialsAsync(onSuccess);
         private _compileShadowGeneratorsAsync(onSuccess);
+        private _abortRequests();
+        private _releaseResources();
     }
 }
 

+ 192 - 81
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -29,6 +29,13 @@ var BABYLON;
     })(GLTFLoaderAnimationStartMode = BABYLON.GLTFLoaderAnimationStartMode || (BABYLON.GLTFLoaderAnimationStartMode = {}));
     var GLTFFileLoader = /** @class */ (function () {
         function GLTFFileLoader() {
+            // #region Common options
+            /**
+             * Raised when the asset has been parsed.
+             * The data.json property stores the glTF JSON.
+             * The data.bin property stores the BIN chunk from a glTF binary or null if the input is not a glTF binary.
+             */
+            this.onParsedObservable = new BABYLON.Observable();
             // #endregion
             // #region V2 options
             /**
@@ -51,26 +58,115 @@ var BABYLON;
              * Set to true to compile shadow generators before raising the success callback.
              */
             this.compileShadowGenerators = false;
+            /**
+             * Raised when the loader creates a mesh after parsing the glTF properties of the mesh.
+             */
+            this.onMeshLoadedObservable = new BABYLON.Observable();
+            /**
+             * Raised when the loader creates a texture after parsing the glTF properties of the texture.
+             */
+            this.onTextureLoadedObservable = new BABYLON.Observable();
+            /**
+             * Raised when the loader creates a material after parsing the glTF properties of the material.
+             */
+            this.onMaterialLoadedObservable = new BABYLON.Observable();
+            /**
+             * Raised when the asset is completely loaded, immediately before the loader is disposed.
+             * For assets with LODs, raised when all of the LODs are complete.
+             * For assets without LODs, raised when the model is complete, immediately after onSuccess.
+             */
+            this.onCompleteObservable = new BABYLON.Observable();
+            /**
+            * Raised when the loader is disposed.
+            */
+            this.onDisposeObservable = new BABYLON.Observable();
+            // #endregion
+            this._loader = null;
             this.name = "gltf";
             this.extensions = {
                 ".gltf": { isBinary: false },
                 ".glb": { isBinary: true }
             };
         }
+        Object.defineProperty(GLTFFileLoader.prototype, "onParsed", {
+            set: function (callback) {
+                if (this._onParsedObserver) {
+                    this.onParsedObservable.remove(this._onParsedObserver);
+                }
+                this._onParsedObserver = this.onParsedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onMeshLoaded", {
+            set: function (callback) {
+                if (this._onMeshLoadedObserver) {
+                    this.onMeshLoadedObservable.remove(this._onMeshLoadedObserver);
+                }
+                this._onMeshLoadedObserver = this.onMeshLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onTextureLoaded", {
+            set: function (callback) {
+                if (this._onTextureLoadedObserver) {
+                    this.onTextureLoadedObservable.remove(this._onTextureLoadedObserver);
+                }
+                this._onTextureLoadedObserver = this.onTextureLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onMaterialLoaded", {
+            set: function (callback) {
+                if (this._onMaterialLoadedObserver) {
+                    this.onMaterialLoadedObservable.remove(this._onMaterialLoadedObserver);
+                }
+                this._onMaterialLoadedObserver = this.onMaterialLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onComplete", {
+            set: function (callback) {
+                if (this._onCompleteObserver) {
+                    this.onCompleteObservable.remove(this._onCompleteObserver);
+                }
+                this._onCompleteObserver = this.onCompleteObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onDispose", {
+            set: function (callback) {
+                if (this._onDisposeObserver) {
+                    this.onDisposeObservable.remove(this._onDisposeObserver);
+                }
+                this._onDisposeObserver = this.onDisposeObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
         /**
          * Disposes the loader, releases resources during load, and cancels any outstanding requests.
          */
         GLTFFileLoader.prototype.dispose = function () {
             if (this._loader) {
                 this._loader.dispose();
+                this._loader = null;
             }
+            this.onParsedObservable.clear();
+            this.onMeshLoadedObservable.clear();
+            this.onTextureLoadedObservable.clear();
+            this.onMaterialLoadedObservable.clear();
+            this.onCompleteObservable.clear();
+            this.onDisposeObservable.notifyObservers(this);
+            this.onDisposeObservable.clear();
         };
         GLTFFileLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError) {
             try {
-                var loaderData = GLTFFileLoader._parse(data);
-                if (this.onParsed) {
-                    this.onParsed(loaderData);
-                }
+                var loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
                 this._loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onSuccess, onProgress, onError);
             }
@@ -85,10 +181,7 @@ var BABYLON;
         };
         GLTFFileLoader.prototype.loadAsync = function (scene, data, rootUrl, onSuccess, onProgress, onError) {
             try {
-                var loaderData = GLTFFileLoader._parse(data);
-                if (this.onParsed) {
-                    this.onParsed(loaderData);
-                }
+                var loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
                 this._loader.loadAsync(scene, loaderData, rootUrl, onSuccess, onProgress, onError);
             }
@@ -107,16 +200,22 @@ var BABYLON;
         GLTFFileLoader.prototype.createPlugin = function () {
             return new GLTFFileLoader();
         };
-        GLTFFileLoader._parse = function (data) {
+        GLTFFileLoader.prototype._parse = function (data) {
+            var parsedData;
             if (data instanceof ArrayBuffer) {
-                return GLTFFileLoader._parseBinary(data);
+                parsedData = GLTFFileLoader._parseBinary(data);
             }
-            return {
-                json: JSON.parse(data),
-                bin: null
-            };
+            else {
+                parsedData = {
+                    json: JSON.parse(data),
+                    bin: null
+                };
+            }
+            this.onParsedObservable.notifyObservers(parsedData);
+            return parsedData;
         };
         GLTFFileLoader.prototype._getLoader = function (loaderData) {
+            var _this = this;
             var loaderVersion = { major: 2, minor: 0 };
             var asset = loaderData.json.asset || {};
             var version = GLTFFileLoader._parseVersion(asset.version);
@@ -140,7 +239,17 @@ var BABYLON;
             if (!createLoader) {
                 throw new Error("Unsupported version: " + asset.version);
             }
-            return createLoader(this);
+            var loader = createLoader();
+            loader.coordinateSystemMode = this.coordinateSystemMode;
+            loader.animationStartMode = this.animationStartMode;
+            loader.compileMaterials = this.compileMaterials;
+            loader.useClipPlane = this.useClipPlane;
+            loader.compileShadowGenerators = this.compileShadowGenerators;
+            loader.onMeshLoadedObservable.add(function (mesh) { return _this.onMeshLoadedObservable.notifyObservers(mesh); });
+            loader.onTextureLoadedObservable.add(function (texture) { return _this.onTextureLoadedObservable.notifyObservers(texture); });
+            loader.onMaterialLoadedObservable.add(function (material) { return _this.onMaterialLoadedObservable.notifyObservers(material); });
+            loader.onCompleteObservable.add(function () { return _this.onCompleteObservable.notifyObservers(_this); });
+            return loader;
         };
         GLTFFileLoader._parseBinary = function (data) {
             var Binary = {
@@ -1676,6 +1785,17 @@ var BABYLON;
         */
         var GLTFLoader = /** @class */ (function () {
             function GLTFLoader() {
+                // #region Stubs for IGLTFLoader interface
+                this.coordinateSystemMode = BABYLON.GLTFLoaderCoordinateSystemMode.AUTO;
+                this.animationStartMode = BABYLON.GLTFLoaderAnimationStartMode.FIRST;
+                this.compileMaterials = false;
+                this.useClipPlane = false;
+                this.compileShadowGenerators = false;
+                this.onDisposeObservable = new BABYLON.Observable();
+                this.onMeshLoadedObservable = new BABYLON.Observable();
+                this.onTextureLoadedObservable = new BABYLON.Observable();
+                this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onCompleteObservable = new BABYLON.Observable();
             }
             GLTFLoader.RegisterExtension = function (extension) {
                 if (GLTFLoader.Extensions[extension.name]) {
@@ -1684,9 +1804,8 @@ var BABYLON;
                 }
                 GLTFLoader.Extensions[extension.name] = extension;
             };
-            GLTFLoader.prototype.dispose = function () {
-                // do nothing
-            };
+            GLTFLoader.prototype.dispose = function () { };
+            // #endregion
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError) {
                 var _this = this;
                 scene.useRightHandedSystem = true;
@@ -2531,7 +2650,7 @@ var BABYLON;
             return GLTFLoaderTracker;
         }());
         var GLTFLoader = /** @class */ (function () {
-            function GLTFLoader(parent) {
+            function GLTFLoader() {
                 this._disposed = false;
                 this._defaultSampler = {};
                 this._renderReady = false;
@@ -2542,7 +2661,16 @@ var BABYLON;
                 // Count of pending work that needs to complete before the loader is disposed.
                 this._loaderPendingCount = 0;
                 this._loaderTrackers = new Array();
-                this._parent = parent;
+                this.coordinateSystemMode = BABYLON.GLTFLoaderCoordinateSystemMode.AUTO;
+                this.animationStartMode = BABYLON.GLTFLoaderAnimationStartMode.FIRST;
+                this.compileMaterials = false;
+                this.useClipPlane = false;
+                this.compileShadowGenerators = false;
+                this.onDisposeObservable = new BABYLON.Observable();
+                this.onMeshLoadedObservable = new BABYLON.Observable();
+                this.onTextureLoadedObservable = new BABYLON.Observable();
+                this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onCompleteObservable = new BABYLON.Observable();
             }
             GLTFLoader.RegisterExtension = function (extension) {
                 if (GLTFLoader.Extensions[extension.name]) {
@@ -2558,22 +2686,9 @@ var BABYLON;
                     return;
                 }
                 this._disposed = true;
-                // Abort requests that are not complete
-                for (var _i = 0, _a = this._requests; _i < _a.length; _i++) {
-                    var request = _a[_i];
-                    if (request.readyState !== (XMLHttpRequest.DONE || 4)) {
-                        request.abort();
-                    }
-                }
-                // Revoke object urls created during load
-                if (this._gltf.textures) {
-                    for (var _b = 0, _c = this._gltf.textures; _b < _c.length; _b++) {
-                        var texture = _c[_b];
-                        if (texture.url) {
-                            URL.revokeObjectURL(texture.url);
-                        }
-                    }
-                }
+                this._abortRequests();
+                this._releaseResources();
+                this.onDisposeObservable.notifyObservers(this);
             };
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError) {
                 var _this = this;
@@ -2617,7 +2732,7 @@ var BABYLON;
                     loaded += request._loaded;
                     total += request._total;
                 }
-                this._progressCallback(GLTFLoader._createProgressEvent(lengthComputable, loaded, lengthComputable ? total : 0));
+                this._progressCallback(new BABYLON.SceneLoaderProgressEvent(lengthComputable, loaded, lengthComputable ? total : 0));
             };
             GLTFLoader.prototype._executeWhenRenderReady = function (func) {
                 if (this._renderReady) {
@@ -2636,10 +2751,9 @@ var BABYLON;
                 this._renderReadyObservable.notifyObservers(this);
             };
             GLTFLoader.prototype._onComplete = function () {
-                if (this._parent.onComplete) {
-                    this._parent.onComplete();
-                }
-                this.dispose();
+                this._abortRequests();
+                this._releaseResources();
+                this.onCompleteObservable.notifyObservers(this);
             };
             GLTFLoader.prototype._loadData = function (data) {
                 this._gltf = data.json;
@@ -2703,7 +2817,7 @@ var BABYLON;
                 if (!animations) {
                     return;
                 }
-                switch (this._parent.animationStartMode) {
+                switch (this.animationStartMode) {
                     case BABYLON.GLTFLoaderAnimationStartMode.NONE: {
                         // do nothing
                         break;
@@ -2727,7 +2841,7 @@ var BABYLON;
                         break;
                     }
                     default: {
-                        BABYLON.Tools.Error("Invalid animation start mode " + this._parent.animationStartMode);
+                        BABYLON.Tools.Error("Invalid animation start mode " + this.animationStartMode);
                         return;
                     }
                 }
@@ -2742,7 +2856,7 @@ var BABYLON;
             GLTFLoader.prototype._loadScene = function (context, scene, nodeNames) {
                 var _this = this;
                 this._rootNode = { babylonMesh: new BABYLON.Mesh("__root__", this._babylonScene) };
-                switch (this._parent.coordinateSystemMode) {
+                switch (this.coordinateSystemMode) {
                     case BABYLON.GLTFLoaderCoordinateSystemMode.AUTO: {
                         if (!this._babylonScene.useRightHandedSystem) {
                             this._rootNode.rotation = [0, 1, 0, 0];
@@ -2756,13 +2870,11 @@ var BABYLON;
                         break;
                     }
                     default: {
-                        BABYLON.Tools.Error("Invalid coordinate system mode " + this._parent.coordinateSystemMode);
+                        BABYLON.Tools.Error("Invalid coordinate system mode " + this.coordinateSystemMode);
                         return;
                     }
                 }
-                if (this._parent.onMeshLoaded) {
-                    this._parent.onMeshLoaded(this._rootNode.babylonMesh);
-                }
+                this.onMeshLoadedObservable.notifyObservers(this._rootNode.babylonMesh);
                 var nodeIndices = scene.nodes;
                 this._traverseNodes(context, nodeIndices, function (node, parentNode) {
                     node.parent = parentNode;
@@ -2838,9 +2950,7 @@ var BABYLON;
                         this._loadNode("#/nodes/" + index, childNode);
                     }
                 }
-                if (this._parent.onMeshLoaded) {
-                    this._parent.onMeshLoaded(node.babylonMesh);
-                }
+                this.onMeshLoadedObservable.notifyObservers(node.babylonMesh);
             };
             GLTFLoader.prototype._loadMesh = function (context, node, mesh) {
                 var _this = this;
@@ -2883,8 +2993,8 @@ var BABYLON;
                             throw new Error(context + ": Failed to find material " + primitive.material);
                         }
                         this._loadMaterial("#/materials/" + material.index, material, function (babylonMaterial, isNew) {
-                            if (isNew && _this._parent.onMaterialLoaded) {
-                                _this._parent.onMaterialLoaded(babylonMaterial);
+                            if (isNew) {
+                                _this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                             }
                             node.babylonMesh.material = babylonMaterial;
                         });
@@ -2905,8 +3015,8 @@ var BABYLON;
                                 throw new Error(context + ": Failed to find material " + primitive.material);
                             }
                             this_1._loadMaterial("#/materials/" + material.index, material, function (babylonMaterial, isNew) {
-                                if (isNew && _this._parent.onMaterialLoaded) {
-                                    _this._parent.onMaterialLoaded(babylonMaterial);
+                                if (isNew) {
+                                    _this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                                 }
                                 subMaterials_1[index] = babylonMaterial;
                             });
@@ -3864,9 +3974,7 @@ var BABYLON;
                 babylonTexture.wrapU = sampler.wrapU;
                 babylonTexture.wrapV = sampler.wrapV;
                 babylonTexture.name = texture.name || "texture" + texture.index;
-                if (this._parent.onTextureLoaded) {
-                    this._parent.onTextureLoaded(babylonTexture);
-                }
+                this.onTextureLoadedObservable.notifyObservers(babylonTexture);
                 return babylonTexture;
             };
             GLTFLoader.prototype._loadSampler = function (context, sampler) {
@@ -3916,12 +4024,11 @@ var BABYLON;
                     _this._tryCatchOnError(function () {
                         throw new BABYLON.LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request);
                     });
-                }, function (oldRequest, newRequest) {
-                    _this._requests.splice(_this._requests.indexOf(oldRequest), 1, newRequest);
                 });
-                if (request) {
-                    this._requests.push(request);
-                }
+                this._requests.push(request);
+                request.onCompleteObservable.add(function () {
+                    _this._requests.splice(_this._requests.indexOf(request), 1);
+                });
             };
             GLTFLoader.prototype._tryCatchOnError = function (handler) {
                 if (this._disposed) {
@@ -4011,7 +4118,7 @@ var BABYLON;
             };
             GLTFLoader.prototype._compileMaterialAsync = function (babylonMaterial, babylonMesh, onSuccess) {
                 var _this = this;
-                if (this._parent.useClipPlane) {
+                if (this.useClipPlane) {
                     babylonMaterial.forceCompilation(babylonMesh, function () {
                         babylonMaterial.forceCompilation(babylonMesh, function () {
                             _this._tryCatchOnError(onSuccess);
@@ -4025,7 +4132,7 @@ var BABYLON;
                 }
             };
             GLTFLoader.prototype._compileMaterialsAsync = function (onSuccess) {
-                if (!this._parent.compileMaterials || !this._gltf.materials) {
+                if (!this.compileMaterials || !this._gltf.materials) {
                     onSuccess();
                     return;
                 }
@@ -4074,7 +4181,7 @@ var BABYLON;
             };
             GLTFLoader.prototype._compileShadowGeneratorsAsync = function (onSuccess) {
                 var _this = this;
-                if (!this._parent.compileShadowGenerators) {
+                if (!this.compileShadowGenerators) {
                     onSuccess();
                     return;
                 }
@@ -4103,25 +4210,29 @@ var BABYLON;
                     }
                 }
             };
-            GLTFLoader.Extensions = {};
-            // IE 11 Compatibility.
-            GLTFLoader._createProgressEvent = (typeof window["ProgressEvent"] === "function")
-                ? function (lengthComputable, loaded, total) {
-                    return new ProgressEvent("GLTFLoaderProgress", {
-                        lengthComputable: lengthComputable,
-                        loaded: loaded,
-                        total: total
-                    });
+            GLTFLoader.prototype._abortRequests = function () {
+                for (var _i = 0, _a = this._requests; _i < _a.length; _i++) {
+                    var request = _a[_i];
+                    request.abort();
                 }
-                : function (lengthComputable, loaded, total) {
-                    var event = document.createEvent("ProgressEvent");
-                    event.initProgressEvent("GLTFLoaderProgress", false, false, lengthComputable, loaded, total);
-                    return event;
-                };
+                this._requests.length = 0;
+            };
+            GLTFLoader.prototype._releaseResources = function () {
+                if (this._gltf.textures) {
+                    for (var _i = 0, _a = this._gltf.textures; _i < _a.length; _i++) {
+                        var texture = _a[_i];
+                        if (texture.url) {
+                            URL.revokeObjectURL(texture.url);
+                            texture.url = undefined;
+                        }
+                    }
+                }
+            };
+            GLTFLoader.Extensions = {};
             return GLTFLoader;
         }());
         GLTF2.GLTFLoader = GLTFLoader;
-        BABYLON.GLTFFileLoader.CreateGLTFLoaderV2 = function (parent) { return new GLTFLoader(parent); };
+        BABYLON.GLTFFileLoader.CreateGLTFLoaderV2 = function () { return new GLTFLoader(); };
     })(GLTF2 = BABYLON.GLTF2 || (BABYLON.GLTF2 = {}));
 })(BABYLON || (BABYLON = {}));
 

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


+ 192 - 81
dist/preview release/loaders/babylonjs.loaders.js

@@ -1003,6 +1003,13 @@ var BABYLON;
     })(GLTFLoaderAnimationStartMode = BABYLON.GLTFLoaderAnimationStartMode || (BABYLON.GLTFLoaderAnimationStartMode = {}));
     var GLTFFileLoader = /** @class */ (function () {
         function GLTFFileLoader() {
+            // #region Common options
+            /**
+             * Raised when the asset has been parsed.
+             * The data.json property stores the glTF JSON.
+             * The data.bin property stores the BIN chunk from a glTF binary or null if the input is not a glTF binary.
+             */
+            this.onParsedObservable = new BABYLON.Observable();
             // #endregion
             // #region V2 options
             /**
@@ -1025,26 +1032,115 @@ var BABYLON;
              * Set to true to compile shadow generators before raising the success callback.
              */
             this.compileShadowGenerators = false;
+            /**
+             * Raised when the loader creates a mesh after parsing the glTF properties of the mesh.
+             */
+            this.onMeshLoadedObservable = new BABYLON.Observable();
+            /**
+             * Raised when the loader creates a texture after parsing the glTF properties of the texture.
+             */
+            this.onTextureLoadedObservable = new BABYLON.Observable();
+            /**
+             * Raised when the loader creates a material after parsing the glTF properties of the material.
+             */
+            this.onMaterialLoadedObservable = new BABYLON.Observable();
+            /**
+             * Raised when the asset is completely loaded, immediately before the loader is disposed.
+             * For assets with LODs, raised when all of the LODs are complete.
+             * For assets without LODs, raised when the model is complete, immediately after onSuccess.
+             */
+            this.onCompleteObservable = new BABYLON.Observable();
+            /**
+            * Raised when the loader is disposed.
+            */
+            this.onDisposeObservable = new BABYLON.Observable();
+            // #endregion
+            this._loader = null;
             this.name = "gltf";
             this.extensions = {
                 ".gltf": { isBinary: false },
                 ".glb": { isBinary: true }
             };
         }
+        Object.defineProperty(GLTFFileLoader.prototype, "onParsed", {
+            set: function (callback) {
+                if (this._onParsedObserver) {
+                    this.onParsedObservable.remove(this._onParsedObserver);
+                }
+                this._onParsedObserver = this.onParsedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onMeshLoaded", {
+            set: function (callback) {
+                if (this._onMeshLoadedObserver) {
+                    this.onMeshLoadedObservable.remove(this._onMeshLoadedObserver);
+                }
+                this._onMeshLoadedObserver = this.onMeshLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onTextureLoaded", {
+            set: function (callback) {
+                if (this._onTextureLoadedObserver) {
+                    this.onTextureLoadedObservable.remove(this._onTextureLoadedObserver);
+                }
+                this._onTextureLoadedObserver = this.onTextureLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onMaterialLoaded", {
+            set: function (callback) {
+                if (this._onMaterialLoadedObserver) {
+                    this.onMaterialLoadedObservable.remove(this._onMaterialLoadedObserver);
+                }
+                this._onMaterialLoadedObserver = this.onMaterialLoadedObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onComplete", {
+            set: function (callback) {
+                if (this._onCompleteObserver) {
+                    this.onCompleteObservable.remove(this._onCompleteObserver);
+                }
+                this._onCompleteObserver = this.onCompleteObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(GLTFFileLoader.prototype, "onDispose", {
+            set: function (callback) {
+                if (this._onDisposeObserver) {
+                    this.onDisposeObservable.remove(this._onDisposeObserver);
+                }
+                this._onDisposeObserver = this.onDisposeObservable.add(callback);
+            },
+            enumerable: true,
+            configurable: true
+        });
         /**
          * Disposes the loader, releases resources during load, and cancels any outstanding requests.
          */
         GLTFFileLoader.prototype.dispose = function () {
             if (this._loader) {
                 this._loader.dispose();
+                this._loader = null;
             }
+            this.onParsedObservable.clear();
+            this.onMeshLoadedObservable.clear();
+            this.onTextureLoadedObservable.clear();
+            this.onMaterialLoadedObservable.clear();
+            this.onCompleteObservable.clear();
+            this.onDisposeObservable.notifyObservers(this);
+            this.onDisposeObservable.clear();
         };
         GLTFFileLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError) {
             try {
-                var loaderData = GLTFFileLoader._parse(data);
-                if (this.onParsed) {
-                    this.onParsed(loaderData);
-                }
+                var loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
                 this._loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onSuccess, onProgress, onError);
             }
@@ -1059,10 +1155,7 @@ var BABYLON;
         };
         GLTFFileLoader.prototype.loadAsync = function (scene, data, rootUrl, onSuccess, onProgress, onError) {
             try {
-                var loaderData = GLTFFileLoader._parse(data);
-                if (this.onParsed) {
-                    this.onParsed(loaderData);
-                }
+                var loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
                 this._loader.loadAsync(scene, loaderData, rootUrl, onSuccess, onProgress, onError);
             }
@@ -1081,16 +1174,22 @@ var BABYLON;
         GLTFFileLoader.prototype.createPlugin = function () {
             return new GLTFFileLoader();
         };
-        GLTFFileLoader._parse = function (data) {
+        GLTFFileLoader.prototype._parse = function (data) {
+            var parsedData;
             if (data instanceof ArrayBuffer) {
-                return GLTFFileLoader._parseBinary(data);
+                parsedData = GLTFFileLoader._parseBinary(data);
             }
-            return {
-                json: JSON.parse(data),
-                bin: null
-            };
+            else {
+                parsedData = {
+                    json: JSON.parse(data),
+                    bin: null
+                };
+            }
+            this.onParsedObservable.notifyObservers(parsedData);
+            return parsedData;
         };
         GLTFFileLoader.prototype._getLoader = function (loaderData) {
+            var _this = this;
             var loaderVersion = { major: 2, minor: 0 };
             var asset = loaderData.json.asset || {};
             var version = GLTFFileLoader._parseVersion(asset.version);
@@ -1114,7 +1213,17 @@ var BABYLON;
             if (!createLoader) {
                 throw new Error("Unsupported version: " + asset.version);
             }
-            return createLoader(this);
+            var loader = createLoader();
+            loader.coordinateSystemMode = this.coordinateSystemMode;
+            loader.animationStartMode = this.animationStartMode;
+            loader.compileMaterials = this.compileMaterials;
+            loader.useClipPlane = this.useClipPlane;
+            loader.compileShadowGenerators = this.compileShadowGenerators;
+            loader.onMeshLoadedObservable.add(function (mesh) { return _this.onMeshLoadedObservable.notifyObservers(mesh); });
+            loader.onTextureLoadedObservable.add(function (texture) { return _this.onTextureLoadedObservable.notifyObservers(texture); });
+            loader.onMaterialLoadedObservable.add(function (material) { return _this.onMaterialLoadedObservable.notifyObservers(material); });
+            loader.onCompleteObservable.add(function () { return _this.onCompleteObservable.notifyObservers(_this); });
+            return loader;
         };
         GLTFFileLoader._parseBinary = function (data) {
             var Binary = {
@@ -2650,6 +2759,17 @@ var BABYLON;
         */
         var GLTFLoader = /** @class */ (function () {
             function GLTFLoader() {
+                // #region Stubs for IGLTFLoader interface
+                this.coordinateSystemMode = BABYLON.GLTFLoaderCoordinateSystemMode.AUTO;
+                this.animationStartMode = BABYLON.GLTFLoaderAnimationStartMode.FIRST;
+                this.compileMaterials = false;
+                this.useClipPlane = false;
+                this.compileShadowGenerators = false;
+                this.onDisposeObservable = new BABYLON.Observable();
+                this.onMeshLoadedObservable = new BABYLON.Observable();
+                this.onTextureLoadedObservable = new BABYLON.Observable();
+                this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onCompleteObservable = new BABYLON.Observable();
             }
             GLTFLoader.RegisterExtension = function (extension) {
                 if (GLTFLoader.Extensions[extension.name]) {
@@ -2658,9 +2778,8 @@ var BABYLON;
                 }
                 GLTFLoader.Extensions[extension.name] = extension;
             };
-            GLTFLoader.prototype.dispose = function () {
-                // do nothing
-            };
+            GLTFLoader.prototype.dispose = function () { };
+            // #endregion
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError) {
                 var _this = this;
                 scene.useRightHandedSystem = true;
@@ -3487,7 +3606,7 @@ var BABYLON;
             return GLTFLoaderTracker;
         }());
         var GLTFLoader = /** @class */ (function () {
-            function GLTFLoader(parent) {
+            function GLTFLoader() {
                 this._disposed = false;
                 this._defaultSampler = {};
                 this._renderReady = false;
@@ -3498,7 +3617,16 @@ var BABYLON;
                 // Count of pending work that needs to complete before the loader is disposed.
                 this._loaderPendingCount = 0;
                 this._loaderTrackers = new Array();
-                this._parent = parent;
+                this.coordinateSystemMode = BABYLON.GLTFLoaderCoordinateSystemMode.AUTO;
+                this.animationStartMode = BABYLON.GLTFLoaderAnimationStartMode.FIRST;
+                this.compileMaterials = false;
+                this.useClipPlane = false;
+                this.compileShadowGenerators = false;
+                this.onDisposeObservable = new BABYLON.Observable();
+                this.onMeshLoadedObservable = new BABYLON.Observable();
+                this.onTextureLoadedObservable = new BABYLON.Observable();
+                this.onMaterialLoadedObservable = new BABYLON.Observable();
+                this.onCompleteObservable = new BABYLON.Observable();
             }
             GLTFLoader.RegisterExtension = function (extension) {
                 if (GLTFLoader.Extensions[extension.name]) {
@@ -3514,22 +3642,9 @@ var BABYLON;
                     return;
                 }
                 this._disposed = true;
-                // Abort requests that are not complete
-                for (var _i = 0, _a = this._requests; _i < _a.length; _i++) {
-                    var request = _a[_i];
-                    if (request.readyState !== (XMLHttpRequest.DONE || 4)) {
-                        request.abort();
-                    }
-                }
-                // Revoke object urls created during load
-                if (this._gltf.textures) {
-                    for (var _b = 0, _c = this._gltf.textures; _b < _c.length; _b++) {
-                        var texture = _c[_b];
-                        if (texture.url) {
-                            URL.revokeObjectURL(texture.url);
-                        }
-                    }
-                }
+                this._abortRequests();
+                this._releaseResources();
+                this.onDisposeObservable.notifyObservers(this);
             };
             GLTFLoader.prototype.importMeshAsync = function (meshesNames, scene, data, rootUrl, onSuccess, onProgress, onError) {
                 var _this = this;
@@ -3573,7 +3688,7 @@ var BABYLON;
                     loaded += request._loaded;
                     total += request._total;
                 }
-                this._progressCallback(GLTFLoader._createProgressEvent(lengthComputable, loaded, lengthComputable ? total : 0));
+                this._progressCallback(new BABYLON.SceneLoaderProgressEvent(lengthComputable, loaded, lengthComputable ? total : 0));
             };
             GLTFLoader.prototype._executeWhenRenderReady = function (func) {
                 if (this._renderReady) {
@@ -3592,10 +3707,9 @@ var BABYLON;
                 this._renderReadyObservable.notifyObservers(this);
             };
             GLTFLoader.prototype._onComplete = function () {
-                if (this._parent.onComplete) {
-                    this._parent.onComplete();
-                }
-                this.dispose();
+                this._abortRequests();
+                this._releaseResources();
+                this.onCompleteObservable.notifyObservers(this);
             };
             GLTFLoader.prototype._loadData = function (data) {
                 this._gltf = data.json;
@@ -3659,7 +3773,7 @@ var BABYLON;
                 if (!animations) {
                     return;
                 }
-                switch (this._parent.animationStartMode) {
+                switch (this.animationStartMode) {
                     case BABYLON.GLTFLoaderAnimationStartMode.NONE: {
                         // do nothing
                         break;
@@ -3683,7 +3797,7 @@ var BABYLON;
                         break;
                     }
                     default: {
-                        BABYLON.Tools.Error("Invalid animation start mode " + this._parent.animationStartMode);
+                        BABYLON.Tools.Error("Invalid animation start mode " + this.animationStartMode);
                         return;
                     }
                 }
@@ -3698,7 +3812,7 @@ var BABYLON;
             GLTFLoader.prototype._loadScene = function (context, scene, nodeNames) {
                 var _this = this;
                 this._rootNode = { babylonMesh: new BABYLON.Mesh("__root__", this._babylonScene) };
-                switch (this._parent.coordinateSystemMode) {
+                switch (this.coordinateSystemMode) {
                     case BABYLON.GLTFLoaderCoordinateSystemMode.AUTO: {
                         if (!this._babylonScene.useRightHandedSystem) {
                             this._rootNode.rotation = [0, 1, 0, 0];
@@ -3712,13 +3826,11 @@ var BABYLON;
                         break;
                     }
                     default: {
-                        BABYLON.Tools.Error("Invalid coordinate system mode " + this._parent.coordinateSystemMode);
+                        BABYLON.Tools.Error("Invalid coordinate system mode " + this.coordinateSystemMode);
                         return;
                     }
                 }
-                if (this._parent.onMeshLoaded) {
-                    this._parent.onMeshLoaded(this._rootNode.babylonMesh);
-                }
+                this.onMeshLoadedObservable.notifyObservers(this._rootNode.babylonMesh);
                 var nodeIndices = scene.nodes;
                 this._traverseNodes(context, nodeIndices, function (node, parentNode) {
                     node.parent = parentNode;
@@ -3794,9 +3906,7 @@ var BABYLON;
                         this._loadNode("#/nodes/" + index, childNode);
                     }
                 }
-                if (this._parent.onMeshLoaded) {
-                    this._parent.onMeshLoaded(node.babylonMesh);
-                }
+                this.onMeshLoadedObservable.notifyObservers(node.babylonMesh);
             };
             GLTFLoader.prototype._loadMesh = function (context, node, mesh) {
                 var _this = this;
@@ -3839,8 +3949,8 @@ var BABYLON;
                             throw new Error(context + ": Failed to find material " + primitive.material);
                         }
                         this._loadMaterial("#/materials/" + material.index, material, function (babylonMaterial, isNew) {
-                            if (isNew && _this._parent.onMaterialLoaded) {
-                                _this._parent.onMaterialLoaded(babylonMaterial);
+                            if (isNew) {
+                                _this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                             }
                             node.babylonMesh.material = babylonMaterial;
                         });
@@ -3861,8 +3971,8 @@ var BABYLON;
                                 throw new Error(context + ": Failed to find material " + primitive.material);
                             }
                             this_1._loadMaterial("#/materials/" + material.index, material, function (babylonMaterial, isNew) {
-                                if (isNew && _this._parent.onMaterialLoaded) {
-                                    _this._parent.onMaterialLoaded(babylonMaterial);
+                                if (isNew) {
+                                    _this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                                 }
                                 subMaterials_1[index] = babylonMaterial;
                             });
@@ -4820,9 +4930,7 @@ var BABYLON;
                 babylonTexture.wrapU = sampler.wrapU;
                 babylonTexture.wrapV = sampler.wrapV;
                 babylonTexture.name = texture.name || "texture" + texture.index;
-                if (this._parent.onTextureLoaded) {
-                    this._parent.onTextureLoaded(babylonTexture);
-                }
+                this.onTextureLoadedObservable.notifyObservers(babylonTexture);
                 return babylonTexture;
             };
             GLTFLoader.prototype._loadSampler = function (context, sampler) {
@@ -4872,12 +4980,11 @@ var BABYLON;
                     _this._tryCatchOnError(function () {
                         throw new BABYLON.LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request);
                     });
-                }, function (oldRequest, newRequest) {
-                    _this._requests.splice(_this._requests.indexOf(oldRequest), 1, newRequest);
                 });
-                if (request) {
-                    this._requests.push(request);
-                }
+                this._requests.push(request);
+                request.onCompleteObservable.add(function () {
+                    _this._requests.splice(_this._requests.indexOf(request), 1);
+                });
             };
             GLTFLoader.prototype._tryCatchOnError = function (handler) {
                 if (this._disposed) {
@@ -4967,7 +5074,7 @@ var BABYLON;
             };
             GLTFLoader.prototype._compileMaterialAsync = function (babylonMaterial, babylonMesh, onSuccess) {
                 var _this = this;
-                if (this._parent.useClipPlane) {
+                if (this.useClipPlane) {
                     babylonMaterial.forceCompilation(babylonMesh, function () {
                         babylonMaterial.forceCompilation(babylonMesh, function () {
                             _this._tryCatchOnError(onSuccess);
@@ -4981,7 +5088,7 @@ var BABYLON;
                 }
             };
             GLTFLoader.prototype._compileMaterialsAsync = function (onSuccess) {
-                if (!this._parent.compileMaterials || !this._gltf.materials) {
+                if (!this.compileMaterials || !this._gltf.materials) {
                     onSuccess();
                     return;
                 }
@@ -5030,7 +5137,7 @@ var BABYLON;
             };
             GLTFLoader.prototype._compileShadowGeneratorsAsync = function (onSuccess) {
                 var _this = this;
-                if (!this._parent.compileShadowGenerators) {
+                if (!this.compileShadowGenerators) {
                     onSuccess();
                     return;
                 }
@@ -5059,25 +5166,29 @@ var BABYLON;
                     }
                 }
             };
-            GLTFLoader.Extensions = {};
-            // IE 11 Compatibility.
-            GLTFLoader._createProgressEvent = (typeof window["ProgressEvent"] === "function")
-                ? function (lengthComputable, loaded, total) {
-                    return new ProgressEvent("GLTFLoaderProgress", {
-                        lengthComputable: lengthComputable,
-                        loaded: loaded,
-                        total: total
-                    });
+            GLTFLoader.prototype._abortRequests = function () {
+                for (var _i = 0, _a = this._requests; _i < _a.length; _i++) {
+                    var request = _a[_i];
+                    request.abort();
                 }
-                : function (lengthComputable, loaded, total) {
-                    var event = document.createEvent("ProgressEvent");
-                    event.initProgressEvent("GLTFLoaderProgress", false, false, lengthComputable, loaded, total);
-                    return event;
-                };
+                this._requests.length = 0;
+            };
+            GLTFLoader.prototype._releaseResources = function () {
+                if (this._gltf.textures) {
+                    for (var _i = 0, _a = this._gltf.textures; _i < _a.length; _i++) {
+                        var texture = _a[_i];
+                        if (texture.url) {
+                            URL.revokeObjectURL(texture.url);
+                            texture.url = undefined;
+                        }
+                    }
+                }
+            };
+            GLTFLoader.Extensions = {};
             return GLTFLoader;
         }());
         GLTF2.GLTFLoader = GLTFLoader;
-        BABYLON.GLTFFileLoader.CreateGLTFLoaderV2 = function (parent) { return new GLTFLoader(parent); };
+        BABYLON.GLTFFileLoader.CreateGLTFLoaderV2 = function () { return new GLTFLoader(); };
     })(GLTF2 = BABYLON.GLTF2 || (BABYLON.GLTF2 = {}));
 })(BABYLON || (BABYLON = {}));
 

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


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

@@ -127,18 +127,30 @@ declare module BABYLON {
         bin: Nullable<ArrayBufferView>;
     }
     interface IGLTFLoader extends IDisposable {
-        importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
-        loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
+        animationStartMode: GLTFLoaderAnimationStartMode;
+        compileMaterials: boolean;
+        useClipPlane: boolean;
+        compileShadowGenerators: boolean;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        onMaterialLoadedObservable: Observable<Material>;
+        onCompleteObservable: Observable<IGLTFLoader>;
+        importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
     }
     class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
-        static CreateGLTFLoaderV1: (parent: GLTFFileLoader) => IGLTFLoader;
-        static CreateGLTFLoaderV2: (parent: GLTFFileLoader) => IGLTFLoader;
+        static CreateGLTFLoaderV1: () => IGLTFLoader;
+        static CreateGLTFLoaderV2: () => IGLTFLoader;
         /**
          * Raised when the asset has been parsed.
          * The data.json property stores the glTF JSON.
          * The data.bin property stores the BIN chunk from a glTF binary or null if the input is not a glTF binary.
          */
-        onParsed: (data: IGLTFLoaderData) => void;
+        onParsedObservable: Observable<IGLTFLoaderData>;
+        private _onParsedObserver;
+        onParsed: (loaderData: IGLTFLoaderData) => void;
         static IncrementalLoading: boolean;
         static HomogeneousCoordinates: boolean;
         /**
@@ -164,21 +176,35 @@ declare module BABYLON {
         /**
          * Raised when the loader creates a mesh after parsing the glTF properties of the mesh.
          */
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        private _onMeshLoadedObserver;
         onMeshLoaded: (mesh: AbstractMesh) => void;
         /**
          * Raised when the loader creates a texture after parsing the glTF properties of the texture.
          */
-        onTextureLoaded: (texture: BaseTexture) => void;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        private _onTextureLoadedObserver;
+        onTextureLoaded: (Texture: BaseTexture) => void;
         /**
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          */
-        onMaterialLoaded: (material: Material) => void;
+        onMaterialLoadedObservable: Observable<Material>;
+        private _onMaterialLoadedObserver;
+        onMaterialLoaded: (Material: Material) => void;
         /**
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * For assets with LODs, raised when all of the LODs are complete.
          * For assets without LODs, raised when the model is complete, immediately after onSuccess.
          */
+        onCompleteObservable: Observable<GLTFFileLoader>;
+        private _onCompleteObserver;
         onComplete: () => void;
+        /**
+        * Raised when the loader is disposed.
+        */
+        onDisposeObservable: Observable<GLTFFileLoader>;
+        private _onDisposeObserver;
+        onDispose: () => void;
         private _loader;
         name: string;
         extensions: ISceneLoaderPluginExtensions;
@@ -186,12 +212,12 @@ declare module BABYLON {
          * Disposes the loader, releases resources during load, and cancels any outstanding requests.
          */
         dispose(): void;
-        importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
-        loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
         canDirectLoad(data: string): boolean;
         rewriteRootURL: (rootUrl: string, responseURL?: string) => string;
         createPlugin(): ISceneLoaderPlugin | ISceneLoaderPluginAsync;
-        private static _parse(data);
+        private _parse(data);
         private _getLoader(loaderData);
         private static _parseBinary(data);
         private static _parseV1(binaryReader);
@@ -595,9 +621,19 @@ declare module BABYLON.GLTF1 {
             [name: string]: GLTFLoaderExtension;
         };
         static RegisterExtension(extension: GLTFLoaderExtension): void;
+        coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
+        animationStartMode: GLTFLoaderAnimationStartMode;
+        compileMaterials: boolean;
+        useClipPlane: boolean;
+        compileShadowGenerators: boolean;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        onMaterialLoadedObservable: Observable<Material>;
+        onCompleteObservable: Observable<IGLTFLoader>;
         dispose(): void;
-        importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void): boolean;
-        loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: () => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void): void;
+        importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress: (event: SceneLoaderProgressEvent) => void, onError: (message: string) => void): boolean;
+        loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: () => void, onProgress: (event: SceneLoaderProgressEvent) => void, onError: (message: string) => void): void;
         private _loadShadersAsync(gltfRuntime, onload);
         private _loadBuffersAsync(gltfRuntime, onLoad, onProgress?);
         private _createNodes(gltfRuntime);
@@ -1006,7 +1042,6 @@ declare module BABYLON.GLTF2 {
         _gltf: IGLTF;
         _babylonScene: Scene;
         private _disposed;
-        private _parent;
         private _rootUrl;
         private _defaultMaterial;
         private _defaultSampler;
@@ -1024,11 +1059,19 @@ declare module BABYLON.GLTF2 {
             [name: string]: GLTFLoaderExtension;
         };
         static RegisterExtension(extension: GLTFLoaderExtension): void;
-        private static _createProgressEvent;
-        constructor(parent: GLTFFileLoader);
+        coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
+        animationStartMode: GLTFLoaderAnimationStartMode;
+        compileMaterials: boolean;
+        useClipPlane: boolean;
+        compileShadowGenerators: boolean;
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        onMaterialLoadedObservable: Observable<Material>;
+        onCompleteObservable: Observable<IGLTFLoader>;
         dispose(): void;
-        importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
-        loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
+        loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void;
         private _loadAsync(nodeNames, scene, data, rootUrl, onSuccess?, onProgress?, onError?);
         private _onProgress();
         _executeWhenRenderReady(func: () => void): void;
@@ -1099,6 +1142,8 @@ declare module BABYLON.GLTF2 {
         private _compileMaterialAsync(babylonMaterial, babylonMesh, onSuccess);
         private _compileMaterialsAsync(onSuccess);
         private _compileShadowGeneratorsAsync(onSuccess);
+        private _abortRequests();
+        private _releaseResources();
     }
 }
 

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

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-loaders",
     "description": "The Babylon.js file loaders library is an extension you can use to load different 3D file types into a Babylon scene.",
-    "version": "3.1.1",
+    "version": "3.2.0-alpha0",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

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

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

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

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

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

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

+ 162 - 0
dist/preview release/serializers/babylon.glTF2Serializer.d.ts

@@ -0,0 +1,162 @@
+
+declare module BABYLON {
+    class GLTF2Export {
+        /**
+         * Exports the geometry of a Mesh array in .gltf file format.
+         * If glb is set to true, exports as .glb.
+         * @param meshes
+         * @param materials
+         *
+         * @returns {[fileName: string]: string | Blob} Returns an object with a .gltf, .glb and associates textures
+         * as keys and their data and paths as values.
+         */
+        static GLTF(scene: BABYLON.Scene, filename: string): {
+            [fileName: string]: string | Blob;
+        };
+        /**
+         *
+         * @param meshes
+         * @param filename
+         *
+         * @returns {[fileName: string]: string | Blob} Returns an object with a .glb filename as key and data as value
+         */
+        static GLB(scene: BABYLON.Scene, filename: string): {
+            [fileName: string]: string | Blob;
+        };
+        /**
+         * Downloads data from glTF object.
+         *
+         * @param gltfData glTF object with keys being file names and values being data
+         */
+        static downloadFiles(gltfData: {
+            [fileName: string]: string | Blob;
+        }): void;
+    }
+}
+
+declare module BABYLON {
+    class _GLTF2Exporter {
+        private bufferViews;
+        private accessors;
+        private nodes;
+        private asset;
+        private scenes;
+        private meshes;
+        private totalByteLength;
+        private babylonScene;
+        constructor(babylonScene: BABYLON.Scene);
+        /**
+         * Creates a buffer view based on teh supplied arguments
+         * @param bufferIndex
+         * @param byteOffset
+         * @param byteLength
+         *
+         * @returns {_IGLTFBufferView}
+         */
+        private createBufferView(bufferIndex, byteOffset, byteLength);
+        /**
+         * Creates an accessor based on the supplied arguments
+         * @param bufferviewIndex
+         * @param name
+         * @param type
+         * @param componentType
+         * @param count
+         * @param min
+         * @param max
+         *
+         * @returns {_IGLTFAccessor}
+         */
+        private createAccessor(bufferviewIndex, name, type, componentType, count, min?, max?);
+        /**
+         * Calculates the minimum and maximum values of an array of floats, based on stride
+         * @param buff
+         * @param vertexStart
+         * @param vertexCount
+         * @param arrayOffset
+         * @param stride
+         *
+         * @returns {min: number[], max: number[]} min number array and max number array
+         */
+        private calculateMinMax(buff, vertexStart, vertexCount, arrayOffset, stride);
+        /**
+         * Write mesh attribute data to buffer.
+         * Returns the bytelength of the data.
+         * @param vertexBufferType
+         * @param submesh
+         * @param meshAttributeArray
+         * @param strideSize
+         * @param byteOffset
+         * @param dataBuffer
+         * @param useRightHandedSystem
+         *
+         * @returns {number} byte length
+         */
+        private writeAttributeData(vertexBufferType, submesh, meshAttributeArray, strideSize, byteOffset, dataBuffer, useRightHandedSystem);
+        /**
+         * Generates glTF json data
+         * @param glb
+         * @param glTFPrefix
+         * @param prettyPrint
+         *
+         * @returns {string} json data as string
+         */
+        private generateJSON(glb, glTFPrefix?, prettyPrint?);
+        /**
+         * Generates data for .gltf and .bin files based on the glTF prefix string
+         * @param glTFPrefix
+         *
+         * @returns {[x: string]: string | Blob} object with glTF json tex filename
+         * and binary file name as keys and their data as values
+         */
+        _generateGLTF(glTFPrefix: string): {
+            [x: string]: string | Blob;
+        };
+        /**
+         * Creates a binary buffer for glTF
+         *
+         * @returns {ArrayBuffer}
+         */
+        private generateBinary();
+        /**
+         * Generates a glb file from the json and binary data.
+         * Returns an object with the glb file name as the key and data as the value.
+         * @param jsonText
+         * @param binaryBuffer
+         * @param glTFPrefix
+         *
+         * @returns {[glbFileName: string]: Blob} object with glb filename as key and data as value
+         */
+        _generateGLB(glTFPrefix: string): {
+            [glbFileName: string]: Blob;
+        };
+        /**
+         * Sets the TRS for each node
+         * @param node
+         * @param babylonMesh
+         * @param useRightHandedSystem
+         */
+        private setNodeTransformation(node, babylonMesh, useRightHandedSystem);
+        /**
+         * Sets data for the primitive attributes of each submesh
+         * @param mesh
+         * @param babylonMesh
+         * @param byteOffset
+         * @param useRightHandedSystem
+         * @param dataBuffer
+         *
+         * @returns {number} bytelength of the primitive attributes plus the passed in byteOffset
+         */
+        private setPrimitiveAttributes(mesh, babylonMesh, byteOffset, useRightHandedSystem, dataBuffer?);
+        /**
+         * Creates a glTF scene based on the array of meshes.
+         * Returns the the total byte offset.
+         * @param gltf
+         * @param byteOffset
+         * @param buffer
+         * @param dataBuffer
+         *
+         * @returns {number} bytelength + byteoffset
+         */
+        private createScene(babylonScene, byteOffset, dataBuffer?);
+    }
+}

+ 649 - 0
dist/preview release/serializers/babylon.glTF2Serializer.js

@@ -0,0 +1,649 @@
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+var BABYLON;
+(function (BABYLON) {
+    var GLTF2Export = /** @class */ (function () {
+        function GLTF2Export() {
+        }
+        /**
+         * Exports the geometry of a Mesh array in .gltf file format.
+         * If glb is set to true, exports as .glb.
+         * @param meshes
+         * @param materials
+         *
+         * @returns {[fileName: string]: string | Blob} Returns an object with a .gltf, .glb and associates textures
+         * as keys and their data and paths as values.
+         */
+        GLTF2Export.GLTF = function (scene, filename) {
+            var glTFPrefix = filename.replace(/\.[^/.]+$/, "");
+            var gltfGenerator = new BABYLON._GLTF2Exporter(scene);
+            return gltfGenerator._generateGLTF(glTFPrefix);
+        };
+        /**
+         *
+         * @param meshes
+         * @param filename
+         *
+         * @returns {[fileName: string]: string | Blob} Returns an object with a .glb filename as key and data as value
+         */
+        GLTF2Export.GLB = function (scene, filename) {
+            var glTFPrefix = filename.replace(/\.[^/.]+$/, "");
+            var gltfGenerator = new BABYLON._GLTF2Exporter(scene);
+            return gltfGenerator._generateGLB(glTFPrefix);
+        };
+        /**
+         * Downloads data from glTF object.
+         *
+         * @param gltfData glTF object with keys being file names and values being data
+         */
+        GLTF2Export.downloadFiles = function (gltfData) {
+            /**
+             * Checks for a matching suffix at the end of a string (for ES5 and lower)
+             * @param str
+             * @param suffix
+             *
+             * @returns {boolean} indicating whether the suffix matches or not
+             */
+            function endsWith(str, suffix) {
+                return str.indexOf(suffix, str.length - suffix.length) !== -1;
+            }
+            for (var key in gltfData) {
+                var link = document.createElement('a');
+                document.body.appendChild(link);
+                link.setAttribute("type", "hidden");
+                link.download = key;
+                var blob = gltfData[key];
+                var mimeType = void 0;
+                if (endsWith(key, ".glb")) {
+                    mimeType = { type: "model/gltf-binary" };
+                }
+                else if (endsWith(key, ".bin")) {
+                    mimeType = { type: "application/octet-stream" };
+                }
+                else if (endsWith(key, ".gltf")) {
+                    mimeType = { type: "model/gltf+json" };
+                }
+                link.href = window.URL.createObjectURL(new Blob([blob], mimeType));
+                link.click();
+            }
+        };
+        return GLTF2Export;
+    }());
+    BABYLON.GLTF2Export = GLTF2Export;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.glTFSerializer.js.map
+
+var BABYLON;
+(function (BABYLON) {
+    var _GLTF2Exporter = /** @class */ (function () {
+        function _GLTF2Exporter(babylonScene) {
+            this.asset = { generator: "BabylonJS", version: "2.0" };
+            this.babylonScene = babylonScene;
+            this.bufferViews = new Array();
+            this.accessors = new Array();
+            this.meshes = new Array();
+            this.scenes = new Array();
+            this.nodes = new Array();
+            var totalByteLength = 0;
+            totalByteLength = this.createScene(this.babylonScene, totalByteLength);
+            this.totalByteLength = totalByteLength;
+        }
+        /**
+         * Creates a buffer view based on teh supplied arguments
+         * @param bufferIndex
+         * @param byteOffset
+         * @param byteLength
+         *
+         * @returns {_IGLTFBufferView}
+         */
+        _GLTF2Exporter.prototype.createBufferView = function (bufferIndex, byteOffset, byteLength) {
+            var bufferview = { buffer: bufferIndex, byteLength: byteLength };
+            if (byteOffset > 0) {
+                bufferview.byteOffset = byteOffset;
+            }
+            return bufferview;
+        };
+        /**
+         * Creates an accessor based on the supplied arguments
+         * @param bufferviewIndex
+         * @param name
+         * @param type
+         * @param componentType
+         * @param count
+         * @param min
+         * @param max
+         *
+         * @returns {_IGLTFAccessor}
+         */
+        _GLTF2Exporter.prototype.createAccessor = function (bufferviewIndex, name, type, componentType, count, min, max) {
+            var accessor = { name: name, bufferView: bufferviewIndex, componentType: componentType, count: count, type: type };
+            if (min) {
+                accessor.min = min;
+            }
+            if (max) {
+                accessor.max = max;
+            }
+            return accessor;
+        };
+        /**
+         * Calculates the minimum and maximum values of an array of floats, based on stride
+         * @param buff
+         * @param vertexStart
+         * @param vertexCount
+         * @param arrayOffset
+         * @param stride
+         *
+         * @returns {min: number[], max: number[]} min number array and max number array
+         */
+        _GLTF2Exporter.prototype.calculateMinMax = function (buff, vertexStart, vertexCount, arrayOffset, stride) {
+            var min = [Infinity, Infinity, Infinity];
+            var max = [-Infinity, -Infinity, -Infinity];
+            var end = vertexStart + vertexCount;
+            if (vertexCount > 0) {
+                for (var i = vertexStart; i < end; ++i) {
+                    var index = stride * i;
+                    for (var j = 0; j < stride; ++j) {
+                        if (buff[index] < min[j]) {
+                            min[j] = buff[index];
+                        }
+                        if (buff[index] > max[j]) {
+                            max[j] = buff[index];
+                        }
+                        ++index;
+                    }
+                }
+            }
+            return { min: min, max: max };
+        };
+        /**
+         * Write mesh attribute data to buffer.
+         * Returns the bytelength of the data.
+         * @param vertexBufferType
+         * @param submesh
+         * @param meshAttributeArray
+         * @param strideSize
+         * @param byteOffset
+         * @param dataBuffer
+         * @param useRightHandedSystem
+         *
+         * @returns {number} byte length
+         */
+        _GLTF2Exporter.prototype.writeAttributeData = function (vertexBufferType, submesh, meshAttributeArray, strideSize, byteOffset, dataBuffer, useRightHandedSystem) {
+            var byteOff = byteOffset;
+            var end = submesh.verticesStart + submesh.verticesCount;
+            var byteLength = 0;
+            switch (vertexBufferType) {
+                case BABYLON.VertexBuffer.PositionKind: {
+                    for (var k = submesh.verticesStart; k < end; ++k) {
+                        var index = k * strideSize;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 1], true);
+                        byteOff += 4;
+                        if (useRightHandedSystem) {
+                            dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 2], true);
+                        }
+                        else {
+                            dataBuffer.setFloat32(byteOff, -meshAttributeArray[index + 2], true);
+                        }
+                        byteOff += 4;
+                    }
+                    byteLength = submesh.verticesCount * 12;
+                    break;
+                }
+                case BABYLON.VertexBuffer.NormalKind: {
+                    for (var k = submesh.verticesStart; k < end; ++k) {
+                        var index = k * strideSize;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 1], true);
+                        byteOff += 4;
+                        if (useRightHandedSystem) {
+                            dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 2], true);
+                        }
+                        else {
+                            dataBuffer.setFloat32(byteOff, -meshAttributeArray[index + 2], true);
+                        }
+                        byteOff += 4;
+                    }
+                    byteLength = submesh.verticesCount * 12;
+                    break;
+                }
+                case BABYLON.VertexBuffer.TangentKind: {
+                    for (var k = submesh.indexStart; k < end; ++k) {
+                        var index = k * strideSize;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 1], true);
+                        byteOff += 4;
+                        if (useRightHandedSystem) {
+                            dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 2], true);
+                        }
+                        else {
+                            dataBuffer.setFloat32(byteOff, -meshAttributeArray[index + 2], true);
+                        }
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 3], true);
+                        byteOff += 4;
+                    }
+                    byteLength = submesh.verticesCount * 16;
+                    break;
+                }
+                case BABYLON.VertexBuffer.ColorKind: {
+                    for (var k = submesh.verticesStart; k < end; ++k) {
+                        var index = k * strideSize;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 1], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 2], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 3], true);
+                        byteOff += 4;
+                    }
+                    byteLength = submesh.verticesCount * 16;
+                    break;
+                }
+                case BABYLON.VertexBuffer.UVKind: {
+                    for (var k = submesh.verticesStart; k < end; ++k) {
+                        var index = k * strideSize;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 1], true);
+                        byteOff += 4;
+                    }
+                    byteLength = submesh.verticesCount * 8;
+                    break;
+                }
+                case BABYLON.VertexBuffer.UV2Kind: {
+                    for (var k = submesh.verticesStart; k < end; ++k) {
+                        var index = k * strideSize;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 1], true);
+                        byteOff += 4;
+                    }
+                    byteLength = submesh.verticesCount * 8;
+                    break;
+                }
+                default: {
+                    throw new Error("Unsupported vertex buffer type: " + vertexBufferType);
+                }
+            }
+            return byteLength;
+        };
+        /**
+         * Generates glTF json data
+         * @param glb
+         * @param glTFPrefix
+         * @param prettyPrint
+         *
+         * @returns {string} json data as string
+         */
+        _GLTF2Exporter.prototype.generateJSON = function (glb, glTFPrefix, prettyPrint) {
+            var buffer = { byteLength: this.totalByteLength };
+            var glTf = {
+                buffers: [buffer],
+                asset: this.asset,
+                meshes: this.meshes,
+                scenes: this.scenes,
+                nodes: this.nodes,
+                bufferViews: this.bufferViews,
+                accessors: this.accessors
+            };
+            if (this.scenes.length > 0) {
+                glTf.scene = 0;
+            }
+            if (!glb) {
+                buffer.uri = glTFPrefix + ".bin";
+            }
+            var jsonText = prettyPrint ? JSON.stringify(glTf, null, 2) : JSON.stringify(glTf);
+            return jsonText;
+        };
+        /**
+         * Generates data for .gltf and .bin files based on the glTF prefix string
+         * @param glTFPrefix
+         *
+         * @returns {[x: string]: string | Blob} object with glTF json tex filename
+         * and binary file name as keys and their data as values
+         */
+        _GLTF2Exporter.prototype._generateGLTF = function (glTFPrefix) {
+            var jsonText = this.generateJSON(false, glTFPrefix, true);
+            var binaryBuffer = this.generateBinary();
+            var bin = new Blob([binaryBuffer], { type: 'application/octet-stream' });
+            var glTFFileName = glTFPrefix + '.gltf';
+            var glTFBinFile = glTFPrefix + '.bin';
+            return _a = {},
+                _a[glTFFileName] = jsonText,
+                _a[glTFBinFile] = bin,
+                _a;
+            var _a;
+        };
+        /**
+         * Creates a binary buffer for glTF
+         *
+         * @returns {ArrayBuffer}
+         */
+        _GLTF2Exporter.prototype.generateBinary = function () {
+            var byteOffset = 0;
+            var binaryBuffer = new ArrayBuffer(this.totalByteLength);
+            var dataBuffer = new DataView(binaryBuffer);
+            byteOffset = this.createScene(this.babylonScene, byteOffset, dataBuffer);
+            return binaryBuffer;
+        };
+        /**
+         * Generates a glb file from the json and binary data.
+         * Returns an object with the glb file name as the key and data as the value.
+         * @param jsonText
+         * @param binaryBuffer
+         * @param glTFPrefix
+         *
+         * @returns {[glbFileName: string]: Blob} object with glb filename as key and data as value
+         */
+        _GLTF2Exporter.prototype._generateGLB = function (glTFPrefix) {
+            var jsonText = this.generateJSON(true);
+            var binaryBuffer = this.generateBinary();
+            var glbFileName = glTFPrefix + '.glb';
+            var headerLength = 12;
+            var chunkLengthPrefix = 8;
+            var jsonLength = jsonText.length;
+            var jsonRemainder = jsonLength % 4;
+            var binRemainder = binaryBuffer.byteLength % 4;
+            var jsonPadding = jsonRemainder === 0 ? jsonRemainder : 4 - jsonRemainder;
+            var binPadding = binRemainder === 0 ? binRemainder : 4 - binRemainder;
+            var byteLength = headerLength + (2 * chunkLengthPrefix) + jsonLength + jsonPadding + binaryBuffer.byteLength + binPadding;
+            //header
+            var headerBuffer = new ArrayBuffer(headerLength);
+            var headerBufferView = new DataView(headerBuffer);
+            headerBufferView.setUint32(0, 0x46546C67, true); //glTF
+            headerBufferView.setUint32(4, 2, true); // version
+            headerBufferView.setUint32(8, byteLength, true); // total bytes in file
+            //json chunk
+            var jsonChunkBuffer = new ArrayBuffer(chunkLengthPrefix + jsonLength + jsonPadding);
+            var jsonChunkBufferView = new DataView(jsonChunkBuffer);
+            jsonChunkBufferView.setUint32(0, jsonLength + jsonPadding, true);
+            jsonChunkBufferView.setUint32(4, 0x4E4F534A, true);
+            //json chunk bytes
+            var jsonData = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix);
+            for (var i = 0; i < jsonLength; ++i) {
+                jsonData[i] = jsonText.charCodeAt(i);
+            }
+            //json padding
+            var jsonPaddingView = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix + jsonLength);
+            for (var i = 0; i < jsonPadding; ++i) {
+                jsonPaddingView[i] = 0x20;
+            }
+            //binary chunk
+            var binaryChunkBuffer = new ArrayBuffer(chunkLengthPrefix);
+            var binaryChunkBufferView = new DataView(binaryChunkBuffer);
+            binaryChunkBufferView.setUint32(0, binaryBuffer.byteLength, true);
+            binaryChunkBufferView.setUint32(4, 0x004E4942, true);
+            // binary padding
+            var binPaddingBuffer = new ArrayBuffer(binPadding);
+            var binPaddingView = new Uint8Array(binPaddingBuffer);
+            for (var i = 0; i < binPadding; ++i) {
+                binPaddingView[i] = 0;
+            }
+            // binary data
+            var glbFile = new Blob([headerBuffer, jsonChunkBuffer, binaryChunkBuffer, binaryBuffer, binPaddingBuffer], { type: 'application/octet-stream' });
+            return _a = {},
+                _a[glbFileName] = glbFile,
+                _a;
+            var _a;
+        };
+        /**
+         * Sets the TRS for each node
+         * @param node
+         * @param babylonMesh
+         * @param useRightHandedSystem
+         */
+        _GLTF2Exporter.prototype.setNodeTransformation = function (node, babylonMesh, useRightHandedSystem) {
+            if (!(babylonMesh.position.x === 0 && babylonMesh.position.y === 0 && babylonMesh.position.z === 0)) {
+                if (useRightHandedSystem) {
+                    node.translation = babylonMesh.position.asArray();
+                }
+                else {
+                    node.translation = [babylonMesh.position.x, babylonMesh.position.y, -babylonMesh.position.z];
+                }
+            }
+            if (!(babylonMesh.scaling.x === 1 && babylonMesh.scaling.y === 1 && babylonMesh.scaling.z === 1)) {
+                if (useRightHandedSystem) {
+                    node.scale = babylonMesh.scaling.asArray();
+                }
+                else {
+                    node.scale = [babylonMesh.scaling.x, babylonMesh.scaling.y, -babylonMesh.scaling.z];
+                }
+            }
+            var rotationQuaternion = BABYLON.Quaternion.RotationYawPitchRoll(babylonMesh.rotation.y, babylonMesh.rotation.x, babylonMesh.rotation.z);
+            if (babylonMesh.rotationQuaternion) {
+                rotationQuaternion = rotationQuaternion.multiply(babylonMesh.rotationQuaternion);
+            }
+            if (!(rotationQuaternion.x === 0 && rotationQuaternion.y === 0 && rotationQuaternion.z === 0 && rotationQuaternion.w === 1)) {
+                if (useRightHandedSystem) {
+                    node.rotation = rotationQuaternion.asArray();
+                }
+                else {
+                    node.rotation = [-rotationQuaternion.x, -rotationQuaternion.y, rotationQuaternion.z, rotationQuaternion.w];
+                }
+            }
+        };
+        /**
+         * Sets data for the primitive attributes of each submesh
+         * @param mesh
+         * @param babylonMesh
+         * @param byteOffset
+         * @param useRightHandedSystem
+         * @param dataBuffer
+         *
+         * @returns {number} bytelength of the primitive attributes plus the passed in byteOffset
+         */
+        _GLTF2Exporter.prototype.setPrimitiveAttributes = function (mesh, babylonMesh, byteOffset, useRightHandedSystem, dataBuffer) {
+            // go through all mesh primitives (submeshes)
+            for (var j = 0; j < babylonMesh.subMeshes.length; ++j) {
+                var bufferMesh = null;
+                var submesh = babylonMesh.subMeshes[j];
+                var meshPrimitive = { attributes: {} };
+                if (babylonMesh instanceof BABYLON.Mesh) {
+                    bufferMesh = babylonMesh;
+                }
+                else if (babylonMesh instanceof BABYLON.InstancedMesh) {
+                    bufferMesh = babylonMesh.sourceMesh;
+                }
+                // Loop through each attribute of the submesh (mesh primitive)
+                if (bufferMesh.isVerticesDataPresent(BABYLON.VertexBuffer.PositionKind)) {
+                    var positionVertexBuffer = bufferMesh.getVertexBuffer(BABYLON.VertexBuffer.PositionKind);
+                    var positionVertexBufferOffset = positionVertexBuffer.getOffset();
+                    var positions = positionVertexBuffer.getData();
+                    var positionStrideSize = positionVertexBuffer.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(BABYLON.VertexBuffer.PositionKind, submesh, positions, positionStrideSize, byteOffset, dataBuffer, useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        var byteLength = submesh.verticesCount * 12;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var result = this.calculateMinMax(positions, submesh.verticesStart, submesh.verticesCount, positionVertexBufferOffset, positionStrideSize);
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Position", "VEC3", 5126, submesh.verticesCount, result.min, result.max);
+                        this.accessors.push(accessor);
+                        meshPrimitive.attributes.POSITION = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.isVerticesDataPresent(BABYLON.VertexBuffer.NormalKind)) {
+                    var normalVertexBuffer = bufferMesh.getVertexBuffer(BABYLON.VertexBuffer.NormalKind);
+                    var normals = normalVertexBuffer.getData();
+                    var normalStrideSize = normalVertexBuffer.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(BABYLON.VertexBuffer.NormalKind, submesh, normals, normalStrideSize, byteOffset, dataBuffer, useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        var byteLength = submesh.verticesCount * 12;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Normal", "VEC3", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+                        meshPrimitive.attributes.NORMAL = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.isVerticesDataPresent(BABYLON.VertexBuffer.TangentKind)) {
+                    var tangentVertexBuffer = bufferMesh.getVertexBuffer(BABYLON.VertexBuffer.TangentKind);
+                    var tangents = tangentVertexBuffer.getData();
+                    var tangentStrideSize = tangentVertexBuffer.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(BABYLON.VertexBuffer.TangentKind, submesh, tangents, tangentStrideSize, byteOffset, dataBuffer, useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        var byteLength = submesh.verticesCount * 16;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Tangent", "VEC4", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+                        meshPrimitive.attributes.TANGENT = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.isVerticesDataPresent(BABYLON.VertexBuffer.ColorKind)) {
+                    var colorVertexBuffer = bufferMesh.getVertexBuffer(BABYLON.VertexBuffer.ColorKind);
+                    var colors = colorVertexBuffer.getData();
+                    var colorStrideSize = colorVertexBuffer.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(BABYLON.VertexBuffer.ColorKind, submesh, colors, colorStrideSize, byteOffset, dataBuffer, useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        var byteLength = submesh.verticesCount * 16;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Color", "VEC4", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+                        meshPrimitive.attributes.COLOR_0 = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.isVerticesDataPresent(BABYLON.VertexBuffer.UVKind)) {
+                    var texCoord0VertexBuffer = bufferMesh.getVertexBuffer(BABYLON.VertexBuffer.UVKind);
+                    var texCoords0 = texCoord0VertexBuffer.getData();
+                    var texCoord0StrideSize = texCoord0VertexBuffer.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(BABYLON.VertexBuffer.UVKind, submesh, texCoords0, texCoord0StrideSize, byteOffset, dataBuffer, useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        var byteLength = submesh.verticesCount * 8;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Texture Coords", "VEC2", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+                        meshPrimitive.attributes.TEXCOORD_0 = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.isVerticesDataPresent(BABYLON.VertexBuffer.UV2Kind)) {
+                    var texCoord1VertexBuffer = bufferMesh.getVertexBuffer(BABYLON.VertexBuffer.UV2Kind);
+                    var texCoords1 = texCoord1VertexBuffer.getData();
+                    var texCoord1StrideSize = texCoord1VertexBuffer.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(BABYLON.VertexBuffer.UV2Kind, submesh, texCoords1, texCoord1StrideSize, byteOffset, dataBuffer, useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        var byteLength = submesh.verticesCount * 8;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Texture Coords", "VEC2", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+                        meshPrimitive.attributes.TEXCOORD_1 = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.getTotalIndices() > 0) {
+                    if (dataBuffer) {
+                        var indices = bufferMesh.getIndices();
+                        var start = submesh.indexStart;
+                        var end = submesh.indexCount + start;
+                        var byteOff = byteOffset;
+                        for (var k = start; k < end; k = k + 3) {
+                            dataBuffer.setUint32(byteOff, indices[k], true);
+                            byteOff += 4;
+                            dataBuffer.setUint32(byteOff, indices[k + 1], true);
+                            byteOff += 4;
+                            dataBuffer.setUint32(byteOff, indices[k + 2], true);
+                            byteOff += 4;
+                        }
+                        var byteLength = submesh.indexCount * 4;
+                        byteOffset += byteLength;
+                    }
+                    else {
+                        // Create bufferview
+                        var indicesCount = submesh.indexCount;
+                        var byteLength = indicesCount * 4;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Indices", "SCALAR", 5125, indicesCount);
+                        this.accessors.push(accessor);
+                        meshPrimitive.indices = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.material) {
+                    //TODO: Implement Material
+                }
+                mesh.primitives.push(meshPrimitive);
+            }
+            return byteOffset;
+        };
+        /**
+         * Creates a glTF scene based on the array of meshes.
+         * Returns the the total byte offset.
+         * @param gltf
+         * @param byteOffset
+         * @param buffer
+         * @param dataBuffer
+         *
+         * @returns {number} bytelength + byteoffset
+         */
+        _GLTF2Exporter.prototype.createScene = function (babylonScene, byteOffset, dataBuffer) {
+            if (babylonScene.meshes.length > 0) {
+                var babylonMeshes = babylonScene.meshes;
+                var scene = { nodes: new Array() };
+                for (var i = 0; i < babylonMeshes.length; ++i) {
+                    // create node to hold translation/rotation/scale and the mesh
+                    var node = { mesh: -1 };
+                    var babylonMesh = babylonMeshes[i];
+                    var useRightHandedSystem = babylonMesh.getScene().useRightHandedSystem;
+                    // Set transformation
+                    this.setNodeTransformation(node, babylonMesh, useRightHandedSystem);
+                    // create mesh
+                    var mesh = { primitives: new Array() };
+                    mesh.primitives = [];
+                    byteOffset = this.setPrimitiveAttributes(mesh, babylonMesh, byteOffset, useRightHandedSystem, dataBuffer);
+                    // go through all mesh primitives (submeshes)
+                    this.meshes.push(mesh);
+                    node.mesh = this.meshes.length - 1;
+                    if (babylonMesh.name) {
+                        node.name = babylonMesh.name;
+                    }
+                    this.nodes.push(node);
+                    scene.nodes.push(this.nodes.length - 1);
+                }
+                this.scenes.push(scene);
+            }
+            return byteOffset;
+        };
+        return _GLTF2Exporter;
+    }());
+    BABYLON._GLTF2Exporter = _GLTF2Exporter;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.glTFExporter.js.map

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


+ 650 - 0
dist/preview release/serializers/babylonjs.serializers.js

@@ -143,6 +143,656 @@ var BABYLON;
 //# sourceMappingURL=babylon.objSerializer.js.map
 
 
+var BABYLON;
+(function (BABYLON) {
+    var GLTF2Export = /** @class */ (function () {
+        function GLTF2Export() {
+        }
+        /**
+         * Exports the geometry of a Mesh array in .gltf file format.
+         * If glb is set to true, exports as .glb.
+         * @param meshes
+         * @param materials
+         *
+         * @returns {[fileName: string]: string | Blob} Returns an object with a .gltf, .glb and associates textures
+         * as keys and their data and paths as values.
+         */
+        GLTF2Export.GLTF = function (scene, filename) {
+            var glTFPrefix = filename.replace(/\.[^/.]+$/, "");
+            var gltfGenerator = new BABYLON._GLTF2Exporter(scene);
+            return gltfGenerator._generateGLTF(glTFPrefix);
+        };
+        /**
+         *
+         * @param meshes
+         * @param filename
+         *
+         * @returns {[fileName: string]: string | Blob} Returns an object with a .glb filename as key and data as value
+         */
+        GLTF2Export.GLB = function (scene, filename) {
+            var glTFPrefix = filename.replace(/\.[^/.]+$/, "");
+            var gltfGenerator = new BABYLON._GLTF2Exporter(scene);
+            return gltfGenerator._generateGLB(glTFPrefix);
+        };
+        /**
+         * Downloads data from glTF object.
+         *
+         * @param gltfData glTF object with keys being file names and values being data
+         */
+        GLTF2Export.downloadFiles = function (gltfData) {
+            /**
+             * Checks for a matching suffix at the end of a string (for ES5 and lower)
+             * @param str
+             * @param suffix
+             *
+             * @returns {boolean} indicating whether the suffix matches or not
+             */
+            function endsWith(str, suffix) {
+                return str.indexOf(suffix, str.length - suffix.length) !== -1;
+            }
+            for (var key in gltfData) {
+                var link = document.createElement('a');
+                document.body.appendChild(link);
+                link.setAttribute("type", "hidden");
+                link.download = key;
+                var blob = gltfData[key];
+                var mimeType = void 0;
+                if (endsWith(key, ".glb")) {
+                    mimeType = { type: "model/gltf-binary" };
+                }
+                else if (endsWith(key, ".bin")) {
+                    mimeType = { type: "application/octet-stream" };
+                }
+                else if (endsWith(key, ".gltf")) {
+                    mimeType = { type: "model/gltf+json" };
+                }
+                link.href = window.URL.createObjectURL(new Blob([blob], mimeType));
+                link.click();
+            }
+        };
+        return GLTF2Export;
+    }());
+    BABYLON.GLTF2Export = GLTF2Export;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.glTFSerializer.js.map
+
+var BABYLON;
+(function (BABYLON) {
+    var _GLTF2Exporter = /** @class */ (function () {
+        function _GLTF2Exporter(babylonScene) {
+            this.asset = { generator: "BabylonJS", version: "2.0" };
+            this.babylonScene = babylonScene;
+            this.bufferViews = new Array();
+            this.accessors = new Array();
+            this.meshes = new Array();
+            this.scenes = new Array();
+            this.nodes = new Array();
+            var totalByteLength = 0;
+            totalByteLength = this.createScene(this.babylonScene, totalByteLength);
+            this.totalByteLength = totalByteLength;
+        }
+        /**
+         * Creates a buffer view based on teh supplied arguments
+         * @param bufferIndex
+         * @param byteOffset
+         * @param byteLength
+         *
+         * @returns {_IGLTFBufferView}
+         */
+        _GLTF2Exporter.prototype.createBufferView = function (bufferIndex, byteOffset, byteLength) {
+            var bufferview = { buffer: bufferIndex, byteLength: byteLength };
+            if (byteOffset > 0) {
+                bufferview.byteOffset = byteOffset;
+            }
+            return bufferview;
+        };
+        /**
+         * Creates an accessor based on the supplied arguments
+         * @param bufferviewIndex
+         * @param name
+         * @param type
+         * @param componentType
+         * @param count
+         * @param min
+         * @param max
+         *
+         * @returns {_IGLTFAccessor}
+         */
+        _GLTF2Exporter.prototype.createAccessor = function (bufferviewIndex, name, type, componentType, count, min, max) {
+            var accessor = { name: name, bufferView: bufferviewIndex, componentType: componentType, count: count, type: type };
+            if (min) {
+                accessor.min = min;
+            }
+            if (max) {
+                accessor.max = max;
+            }
+            return accessor;
+        };
+        /**
+         * Calculates the minimum and maximum values of an array of floats, based on stride
+         * @param buff
+         * @param vertexStart
+         * @param vertexCount
+         * @param arrayOffset
+         * @param stride
+         *
+         * @returns {min: number[], max: number[]} min number array and max number array
+         */
+        _GLTF2Exporter.prototype.calculateMinMax = function (buff, vertexStart, vertexCount, arrayOffset, stride) {
+            var min = [Infinity, Infinity, Infinity];
+            var max = [-Infinity, -Infinity, -Infinity];
+            var end = vertexStart + vertexCount;
+            if (vertexCount > 0) {
+                for (var i = vertexStart; i < end; ++i) {
+                    var index = stride * i;
+                    for (var j = 0; j < stride; ++j) {
+                        if (buff[index] < min[j]) {
+                            min[j] = buff[index];
+                        }
+                        if (buff[index] > max[j]) {
+                            max[j] = buff[index];
+                        }
+                        ++index;
+                    }
+                }
+            }
+            return { min: min, max: max };
+        };
+        /**
+         * Write mesh attribute data to buffer.
+         * Returns the bytelength of the data.
+         * @param vertexBufferType
+         * @param submesh
+         * @param meshAttributeArray
+         * @param strideSize
+         * @param byteOffset
+         * @param dataBuffer
+         * @param useRightHandedSystem
+         *
+         * @returns {number} byte length
+         */
+        _GLTF2Exporter.prototype.writeAttributeData = function (vertexBufferType, submesh, meshAttributeArray, strideSize, byteOffset, dataBuffer, useRightHandedSystem) {
+            var byteOff = byteOffset;
+            var end = submesh.verticesStart + submesh.verticesCount;
+            var byteLength = 0;
+            switch (vertexBufferType) {
+                case BABYLON.VertexBuffer.PositionKind: {
+                    for (var k = submesh.verticesStart; k < end; ++k) {
+                        var index = k * strideSize;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 1], true);
+                        byteOff += 4;
+                        if (useRightHandedSystem) {
+                            dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 2], true);
+                        }
+                        else {
+                            dataBuffer.setFloat32(byteOff, -meshAttributeArray[index + 2], true);
+                        }
+                        byteOff += 4;
+                    }
+                    byteLength = submesh.verticesCount * 12;
+                    break;
+                }
+                case BABYLON.VertexBuffer.NormalKind: {
+                    for (var k = submesh.verticesStart; k < end; ++k) {
+                        var index = k * strideSize;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 1], true);
+                        byteOff += 4;
+                        if (useRightHandedSystem) {
+                            dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 2], true);
+                        }
+                        else {
+                            dataBuffer.setFloat32(byteOff, -meshAttributeArray[index + 2], true);
+                        }
+                        byteOff += 4;
+                    }
+                    byteLength = submesh.verticesCount * 12;
+                    break;
+                }
+                case BABYLON.VertexBuffer.TangentKind: {
+                    for (var k = submesh.indexStart; k < end; ++k) {
+                        var index = k * strideSize;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 1], true);
+                        byteOff += 4;
+                        if (useRightHandedSystem) {
+                            dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 2], true);
+                        }
+                        else {
+                            dataBuffer.setFloat32(byteOff, -meshAttributeArray[index + 2], true);
+                        }
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 3], true);
+                        byteOff += 4;
+                    }
+                    byteLength = submesh.verticesCount * 16;
+                    break;
+                }
+                case BABYLON.VertexBuffer.ColorKind: {
+                    for (var k = submesh.verticesStart; k < end; ++k) {
+                        var index = k * strideSize;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 1], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 2], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 3], true);
+                        byteOff += 4;
+                    }
+                    byteLength = submesh.verticesCount * 16;
+                    break;
+                }
+                case BABYLON.VertexBuffer.UVKind: {
+                    for (var k = submesh.verticesStart; k < end; ++k) {
+                        var index = k * strideSize;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 1], true);
+                        byteOff += 4;
+                    }
+                    byteLength = submesh.verticesCount * 8;
+                    break;
+                }
+                case BABYLON.VertexBuffer.UV2Kind: {
+                    for (var k = submesh.verticesStart; k < end; ++k) {
+                        var index = k * strideSize;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index], true);
+                        byteOff += 4;
+                        dataBuffer.setFloat32(byteOff, meshAttributeArray[index + 1], true);
+                        byteOff += 4;
+                    }
+                    byteLength = submesh.verticesCount * 8;
+                    break;
+                }
+                default: {
+                    throw new Error("Unsupported vertex buffer type: " + vertexBufferType);
+                }
+            }
+            return byteLength;
+        };
+        /**
+         * Generates glTF json data
+         * @param glb
+         * @param glTFPrefix
+         * @param prettyPrint
+         *
+         * @returns {string} json data as string
+         */
+        _GLTF2Exporter.prototype.generateJSON = function (glb, glTFPrefix, prettyPrint) {
+            var buffer = { byteLength: this.totalByteLength };
+            var glTf = {
+                buffers: [buffer],
+                asset: this.asset,
+                meshes: this.meshes,
+                scenes: this.scenes,
+                nodes: this.nodes,
+                bufferViews: this.bufferViews,
+                accessors: this.accessors
+            };
+            if (this.scenes.length > 0) {
+                glTf.scene = 0;
+            }
+            if (!glb) {
+                buffer.uri = glTFPrefix + ".bin";
+            }
+            var jsonText = prettyPrint ? JSON.stringify(glTf, null, 2) : JSON.stringify(glTf);
+            return jsonText;
+        };
+        /**
+         * Generates data for .gltf and .bin files based on the glTF prefix string
+         * @param glTFPrefix
+         *
+         * @returns {[x: string]: string | Blob} object with glTF json tex filename
+         * and binary file name as keys and their data as values
+         */
+        _GLTF2Exporter.prototype._generateGLTF = function (glTFPrefix) {
+            var jsonText = this.generateJSON(false, glTFPrefix, true);
+            var binaryBuffer = this.generateBinary();
+            var bin = new Blob([binaryBuffer], { type: 'application/octet-stream' });
+            var glTFFileName = glTFPrefix + '.gltf';
+            var glTFBinFile = glTFPrefix + '.bin';
+            return _a = {},
+                _a[glTFFileName] = jsonText,
+                _a[glTFBinFile] = bin,
+                _a;
+            var _a;
+        };
+        /**
+         * Creates a binary buffer for glTF
+         *
+         * @returns {ArrayBuffer}
+         */
+        _GLTF2Exporter.prototype.generateBinary = function () {
+            var byteOffset = 0;
+            var binaryBuffer = new ArrayBuffer(this.totalByteLength);
+            var dataBuffer = new DataView(binaryBuffer);
+            byteOffset = this.createScene(this.babylonScene, byteOffset, dataBuffer);
+            return binaryBuffer;
+        };
+        /**
+         * Generates a glb file from the json and binary data.
+         * Returns an object with the glb file name as the key and data as the value.
+         * @param jsonText
+         * @param binaryBuffer
+         * @param glTFPrefix
+         *
+         * @returns {[glbFileName: string]: Blob} object with glb filename as key and data as value
+         */
+        _GLTF2Exporter.prototype._generateGLB = function (glTFPrefix) {
+            var jsonText = this.generateJSON(true);
+            var binaryBuffer = this.generateBinary();
+            var glbFileName = glTFPrefix + '.glb';
+            var headerLength = 12;
+            var chunkLengthPrefix = 8;
+            var jsonLength = jsonText.length;
+            var jsonRemainder = jsonLength % 4;
+            var binRemainder = binaryBuffer.byteLength % 4;
+            var jsonPadding = jsonRemainder === 0 ? jsonRemainder : 4 - jsonRemainder;
+            var binPadding = binRemainder === 0 ? binRemainder : 4 - binRemainder;
+            var byteLength = headerLength + (2 * chunkLengthPrefix) + jsonLength + jsonPadding + binaryBuffer.byteLength + binPadding;
+            //header
+            var headerBuffer = new ArrayBuffer(headerLength);
+            var headerBufferView = new DataView(headerBuffer);
+            headerBufferView.setUint32(0, 0x46546C67, true); //glTF
+            headerBufferView.setUint32(4, 2, true); // version
+            headerBufferView.setUint32(8, byteLength, true); // total bytes in file
+            //json chunk
+            var jsonChunkBuffer = new ArrayBuffer(chunkLengthPrefix + jsonLength + jsonPadding);
+            var jsonChunkBufferView = new DataView(jsonChunkBuffer);
+            jsonChunkBufferView.setUint32(0, jsonLength + jsonPadding, true);
+            jsonChunkBufferView.setUint32(4, 0x4E4F534A, true);
+            //json chunk bytes
+            var jsonData = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix);
+            for (var i = 0; i < jsonLength; ++i) {
+                jsonData[i] = jsonText.charCodeAt(i);
+            }
+            //json padding
+            var jsonPaddingView = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix + jsonLength);
+            for (var i = 0; i < jsonPadding; ++i) {
+                jsonPaddingView[i] = 0x20;
+            }
+            //binary chunk
+            var binaryChunkBuffer = new ArrayBuffer(chunkLengthPrefix);
+            var binaryChunkBufferView = new DataView(binaryChunkBuffer);
+            binaryChunkBufferView.setUint32(0, binaryBuffer.byteLength, true);
+            binaryChunkBufferView.setUint32(4, 0x004E4942, true);
+            // binary padding
+            var binPaddingBuffer = new ArrayBuffer(binPadding);
+            var binPaddingView = new Uint8Array(binPaddingBuffer);
+            for (var i = 0; i < binPadding; ++i) {
+                binPaddingView[i] = 0;
+            }
+            // binary data
+            var glbFile = new Blob([headerBuffer, jsonChunkBuffer, binaryChunkBuffer, binaryBuffer, binPaddingBuffer], { type: 'application/octet-stream' });
+            return _a = {},
+                _a[glbFileName] = glbFile,
+                _a;
+            var _a;
+        };
+        /**
+         * Sets the TRS for each node
+         * @param node
+         * @param babylonMesh
+         * @param useRightHandedSystem
+         */
+        _GLTF2Exporter.prototype.setNodeTransformation = function (node, babylonMesh, useRightHandedSystem) {
+            if (!(babylonMesh.position.x === 0 && babylonMesh.position.y === 0 && babylonMesh.position.z === 0)) {
+                if (useRightHandedSystem) {
+                    node.translation = babylonMesh.position.asArray();
+                }
+                else {
+                    node.translation = [babylonMesh.position.x, babylonMesh.position.y, -babylonMesh.position.z];
+                }
+            }
+            if (!(babylonMesh.scaling.x === 1 && babylonMesh.scaling.y === 1 && babylonMesh.scaling.z === 1)) {
+                if (useRightHandedSystem) {
+                    node.scale = babylonMesh.scaling.asArray();
+                }
+                else {
+                    node.scale = [babylonMesh.scaling.x, babylonMesh.scaling.y, -babylonMesh.scaling.z];
+                }
+            }
+            var rotationQuaternion = BABYLON.Quaternion.RotationYawPitchRoll(babylonMesh.rotation.y, babylonMesh.rotation.x, babylonMesh.rotation.z);
+            if (babylonMesh.rotationQuaternion) {
+                rotationQuaternion = rotationQuaternion.multiply(babylonMesh.rotationQuaternion);
+            }
+            if (!(rotationQuaternion.x === 0 && rotationQuaternion.y === 0 && rotationQuaternion.z === 0 && rotationQuaternion.w === 1)) {
+                if (useRightHandedSystem) {
+                    node.rotation = rotationQuaternion.asArray();
+                }
+                else {
+                    node.rotation = [-rotationQuaternion.x, -rotationQuaternion.y, rotationQuaternion.z, rotationQuaternion.w];
+                }
+            }
+        };
+        /**
+         * Sets data for the primitive attributes of each submesh
+         * @param mesh
+         * @param babylonMesh
+         * @param byteOffset
+         * @param useRightHandedSystem
+         * @param dataBuffer
+         *
+         * @returns {number} bytelength of the primitive attributes plus the passed in byteOffset
+         */
+        _GLTF2Exporter.prototype.setPrimitiveAttributes = function (mesh, babylonMesh, byteOffset, useRightHandedSystem, dataBuffer) {
+            // go through all mesh primitives (submeshes)
+            for (var j = 0; j < babylonMesh.subMeshes.length; ++j) {
+                var bufferMesh = null;
+                var submesh = babylonMesh.subMeshes[j];
+                var meshPrimitive = { attributes: {} };
+                if (babylonMesh instanceof BABYLON.Mesh) {
+                    bufferMesh = babylonMesh;
+                }
+                else if (babylonMesh instanceof BABYLON.InstancedMesh) {
+                    bufferMesh = babylonMesh.sourceMesh;
+                }
+                // Loop through each attribute of the submesh (mesh primitive)
+                if (bufferMesh.isVerticesDataPresent(BABYLON.VertexBuffer.PositionKind)) {
+                    var positionVertexBuffer = bufferMesh.getVertexBuffer(BABYLON.VertexBuffer.PositionKind);
+                    var positionVertexBufferOffset = positionVertexBuffer.getOffset();
+                    var positions = positionVertexBuffer.getData();
+                    var positionStrideSize = positionVertexBuffer.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(BABYLON.VertexBuffer.PositionKind, submesh, positions, positionStrideSize, byteOffset, dataBuffer, useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        var byteLength = submesh.verticesCount * 12;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var result = this.calculateMinMax(positions, submesh.verticesStart, submesh.verticesCount, positionVertexBufferOffset, positionStrideSize);
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Position", "VEC3", 5126, submesh.verticesCount, result.min, result.max);
+                        this.accessors.push(accessor);
+                        meshPrimitive.attributes.POSITION = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.isVerticesDataPresent(BABYLON.VertexBuffer.NormalKind)) {
+                    var normalVertexBuffer = bufferMesh.getVertexBuffer(BABYLON.VertexBuffer.NormalKind);
+                    var normals = normalVertexBuffer.getData();
+                    var normalStrideSize = normalVertexBuffer.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(BABYLON.VertexBuffer.NormalKind, submesh, normals, normalStrideSize, byteOffset, dataBuffer, useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        var byteLength = submesh.verticesCount * 12;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Normal", "VEC3", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+                        meshPrimitive.attributes.NORMAL = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.isVerticesDataPresent(BABYLON.VertexBuffer.TangentKind)) {
+                    var tangentVertexBuffer = bufferMesh.getVertexBuffer(BABYLON.VertexBuffer.TangentKind);
+                    var tangents = tangentVertexBuffer.getData();
+                    var tangentStrideSize = tangentVertexBuffer.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(BABYLON.VertexBuffer.TangentKind, submesh, tangents, tangentStrideSize, byteOffset, dataBuffer, useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        var byteLength = submesh.verticesCount * 16;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Tangent", "VEC4", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+                        meshPrimitive.attributes.TANGENT = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.isVerticesDataPresent(BABYLON.VertexBuffer.ColorKind)) {
+                    var colorVertexBuffer = bufferMesh.getVertexBuffer(BABYLON.VertexBuffer.ColorKind);
+                    var colors = colorVertexBuffer.getData();
+                    var colorStrideSize = colorVertexBuffer.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(BABYLON.VertexBuffer.ColorKind, submesh, colors, colorStrideSize, byteOffset, dataBuffer, useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        var byteLength = submesh.verticesCount * 16;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Color", "VEC4", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+                        meshPrimitive.attributes.COLOR_0 = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.isVerticesDataPresent(BABYLON.VertexBuffer.UVKind)) {
+                    var texCoord0VertexBuffer = bufferMesh.getVertexBuffer(BABYLON.VertexBuffer.UVKind);
+                    var texCoords0 = texCoord0VertexBuffer.getData();
+                    var texCoord0StrideSize = texCoord0VertexBuffer.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(BABYLON.VertexBuffer.UVKind, submesh, texCoords0, texCoord0StrideSize, byteOffset, dataBuffer, useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        var byteLength = submesh.verticesCount * 8;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Texture Coords", "VEC2", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+                        meshPrimitive.attributes.TEXCOORD_0 = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.isVerticesDataPresent(BABYLON.VertexBuffer.UV2Kind)) {
+                    var texCoord1VertexBuffer = bufferMesh.getVertexBuffer(BABYLON.VertexBuffer.UV2Kind);
+                    var texCoords1 = texCoord1VertexBuffer.getData();
+                    var texCoord1StrideSize = texCoord1VertexBuffer.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(BABYLON.VertexBuffer.UV2Kind, submesh, texCoords1, texCoord1StrideSize, byteOffset, dataBuffer, useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        var byteLength = submesh.verticesCount * 8;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Texture Coords", "VEC2", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+                        meshPrimitive.attributes.TEXCOORD_1 = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.getTotalIndices() > 0) {
+                    if (dataBuffer) {
+                        var indices = bufferMesh.getIndices();
+                        var start = submesh.indexStart;
+                        var end = submesh.indexCount + start;
+                        var byteOff = byteOffset;
+                        for (var k = start; k < end; k = k + 3) {
+                            dataBuffer.setUint32(byteOff, indices[k], true);
+                            byteOff += 4;
+                            dataBuffer.setUint32(byteOff, indices[k + 1], true);
+                            byteOff += 4;
+                            dataBuffer.setUint32(byteOff, indices[k + 2], true);
+                            byteOff += 4;
+                        }
+                        var byteLength = submesh.indexCount * 4;
+                        byteOffset += byteLength;
+                    }
+                    else {
+                        // Create bufferview
+                        var indicesCount = submesh.indexCount;
+                        var byteLength = indicesCount * 4;
+                        var bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+                        // Create accessor
+                        var accessor = this.createAccessor(this.bufferViews.length - 1, "Indices", "SCALAR", 5125, indicesCount);
+                        this.accessors.push(accessor);
+                        meshPrimitive.indices = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh.material) {
+                    //TODO: Implement Material
+                }
+                mesh.primitives.push(meshPrimitive);
+            }
+            return byteOffset;
+        };
+        /**
+         * Creates a glTF scene based on the array of meshes.
+         * Returns the the total byte offset.
+         * @param gltf
+         * @param byteOffset
+         * @param buffer
+         * @param dataBuffer
+         *
+         * @returns {number} bytelength + byteoffset
+         */
+        _GLTF2Exporter.prototype.createScene = function (babylonScene, byteOffset, dataBuffer) {
+            if (babylonScene.meshes.length > 0) {
+                var babylonMeshes = babylonScene.meshes;
+                var scene = { nodes: new Array() };
+                for (var i = 0; i < babylonMeshes.length; ++i) {
+                    // create node to hold translation/rotation/scale and the mesh
+                    var node = { mesh: -1 };
+                    var babylonMesh = babylonMeshes[i];
+                    var useRightHandedSystem = babylonMesh.getScene().useRightHandedSystem;
+                    // Set transformation
+                    this.setNodeTransformation(node, babylonMesh, useRightHandedSystem);
+                    // create mesh
+                    var mesh = { primitives: new Array() };
+                    mesh.primitives = [];
+                    byteOffset = this.setPrimitiveAttributes(mesh, babylonMesh, byteOffset, useRightHandedSystem, dataBuffer);
+                    // go through all mesh primitives (submeshes)
+                    this.meshes.push(mesh);
+                    node.mesh = this.meshes.length - 1;
+                    if (babylonMesh.name) {
+                        node.name = babylonMesh.name;
+                    }
+                    this.nodes.push(node);
+                    scene.nodes.push(this.nodes.length - 1);
+                }
+                this.scenes.push(scene);
+            }
+            return byteOffset;
+        };
+        return _GLTF2Exporter;
+    }());
+    BABYLON._GLTF2Exporter = _GLTF2Exporter;
+})(BABYLON || (BABYLON = {}));
+
+//# sourceMappingURL=babylon.glTFExporter.js.map
+
+
 (function universalModuleDefinition(root, factory) {
                 var f = factory();
                 if (root && root["BABYLON"]) {

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


+ 163 - 0
dist/preview release/serializers/babylonjs.serializers.module.d.ts

@@ -10,3 +10,166 @@ declare module BABYLON {
         static MTL(mesh: Mesh): string;
     }
 }
+
+
+declare module BABYLON {
+    class GLTF2Export {
+        /**
+         * Exports the geometry of a Mesh array in .gltf file format.
+         * If glb is set to true, exports as .glb.
+         * @param meshes
+         * @param materials
+         *
+         * @returns {[fileName: string]: string | Blob} Returns an object with a .gltf, .glb and associates textures
+         * as keys and their data and paths as values.
+         */
+        static GLTF(scene: BABYLON.Scene, filename: string): {
+            [fileName: string]: string | Blob;
+        };
+        /**
+         *
+         * @param meshes
+         * @param filename
+         *
+         * @returns {[fileName: string]: string | Blob} Returns an object with a .glb filename as key and data as value
+         */
+        static GLB(scene: BABYLON.Scene, filename: string): {
+            [fileName: string]: string | Blob;
+        };
+        /**
+         * Downloads data from glTF object.
+         *
+         * @param gltfData glTF object with keys being file names and values being data
+         */
+        static downloadFiles(gltfData: {
+            [fileName: string]: string | Blob;
+        }): void;
+    }
+}
+
+declare module BABYLON {
+    class _GLTF2Exporter {
+        private bufferViews;
+        private accessors;
+        private nodes;
+        private asset;
+        private scenes;
+        private meshes;
+        private totalByteLength;
+        private babylonScene;
+        constructor(babylonScene: BABYLON.Scene);
+        /**
+         * Creates a buffer view based on teh supplied arguments
+         * @param bufferIndex
+         * @param byteOffset
+         * @param byteLength
+         *
+         * @returns {_IGLTFBufferView}
+         */
+        private createBufferView(bufferIndex, byteOffset, byteLength);
+        /**
+         * Creates an accessor based on the supplied arguments
+         * @param bufferviewIndex
+         * @param name
+         * @param type
+         * @param componentType
+         * @param count
+         * @param min
+         * @param max
+         *
+         * @returns {_IGLTFAccessor}
+         */
+        private createAccessor(bufferviewIndex, name, type, componentType, count, min?, max?);
+        /**
+         * Calculates the minimum and maximum values of an array of floats, based on stride
+         * @param buff
+         * @param vertexStart
+         * @param vertexCount
+         * @param arrayOffset
+         * @param stride
+         *
+         * @returns {min: number[], max: number[]} min number array and max number array
+         */
+        private calculateMinMax(buff, vertexStart, vertexCount, arrayOffset, stride);
+        /**
+         * Write mesh attribute data to buffer.
+         * Returns the bytelength of the data.
+         * @param vertexBufferType
+         * @param submesh
+         * @param meshAttributeArray
+         * @param strideSize
+         * @param byteOffset
+         * @param dataBuffer
+         * @param useRightHandedSystem
+         *
+         * @returns {number} byte length
+         */
+        private writeAttributeData(vertexBufferType, submesh, meshAttributeArray, strideSize, byteOffset, dataBuffer, useRightHandedSystem);
+        /**
+         * Generates glTF json data
+         * @param glb
+         * @param glTFPrefix
+         * @param prettyPrint
+         *
+         * @returns {string} json data as string
+         */
+        private generateJSON(glb, glTFPrefix?, prettyPrint?);
+        /**
+         * Generates data for .gltf and .bin files based on the glTF prefix string
+         * @param glTFPrefix
+         *
+         * @returns {[x: string]: string | Blob} object with glTF json tex filename
+         * and binary file name as keys and their data as values
+         */
+        _generateGLTF(glTFPrefix: string): {
+            [x: string]: string | Blob;
+        };
+        /**
+         * Creates a binary buffer for glTF
+         *
+         * @returns {ArrayBuffer}
+         */
+        private generateBinary();
+        /**
+         * Generates a glb file from the json and binary data.
+         * Returns an object with the glb file name as the key and data as the value.
+         * @param jsonText
+         * @param binaryBuffer
+         * @param glTFPrefix
+         *
+         * @returns {[glbFileName: string]: Blob} object with glb filename as key and data as value
+         */
+        _generateGLB(glTFPrefix: string): {
+            [glbFileName: string]: Blob;
+        };
+        /**
+         * Sets the TRS for each node
+         * @param node
+         * @param babylonMesh
+         * @param useRightHandedSystem
+         */
+        private setNodeTransformation(node, babylonMesh, useRightHandedSystem);
+        /**
+         * Sets data for the primitive attributes of each submesh
+         * @param mesh
+         * @param babylonMesh
+         * @param byteOffset
+         * @param useRightHandedSystem
+         * @param dataBuffer
+         *
+         * @returns {number} bytelength of the primitive attributes plus the passed in byteOffset
+         */
+        private setPrimitiveAttributes(mesh, babylonMesh, byteOffset, useRightHandedSystem, dataBuffer?);
+        /**
+         * Creates a glTF scene based on the array of meshes.
+         * Returns the the total byte offset.
+         * @param gltf
+         * @param byteOffset
+         * @param buffer
+         * @param dataBuffer
+         *
+         * @returns {number} bytelength + byteoffset
+         */
+        private createScene(babylonScene, byteOffset, dataBuffer?);
+    }
+}

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

@@ -4,7 +4,7 @@
     },
     "name": "babylonjs-serializers",
     "description": "The Babylon.js serializers library is an extension you can use to serialize Babylon scenes.",
-    "version": "3.1.1",
+    "version": "3.2.0-alpha0",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

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


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

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

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

@@ -2,11 +2,17 @@
 
 ## Major updates
 - Introduced texture binding atlas. This optimization allows the engine to reuse texture bindings instead of rebinding textures when they are not on constant sampler indexes ([deltakosh](https://github.com/deltakosh))
+- New [AnimationGroup class](http://doc.babylonjs.com/how_to/group) to control simultaneously multiple animations with different targets ([deltakosh](https://github.com/deltakosh))
 
 ## Updates
 - New watcher configuration for VSCode. Now the task only compiles changed files ([sebavan](https://github.com/sebavan))
 - Added new draw modes to engine (points, lines, linesloop, linestrip, trianglestrip, trianglefan) ([benaadams](https://github.com/benaadams))
 - Added GUI Textblock.lineSpacing setter and getter to configure vertical space between lines in pixels or percentage values when working with text wrapping ([carloslanderas](github.com/carloslanderas))
+- VRHelper now has onSelectedMeshUnselected observable that will notify observers when the current selected mesh gets unselected
+  ([carloslanderas](github.com/carloslanderas))
+- VRHelper now has onBeforeCameraTeleport and onAfterCameraTeleport observables that will be notified before and after camera teleportation is triggered.
+  ([carloslanderas](github.com/carloslanderas))
+  
 
 ## Bug fixes
 

+ 7 - 0
inspector/src/tabs/StatsTab.ts

@@ -86,6 +86,13 @@ module INSPECTOR {
                     updateFct: () => { return this._sceneInstrumentation!.drawCallsCounter.current.toString() }
                 });
 
+                this._createStatLabel("Texture collisions", this._panel);
+                elemValue = Helpers.CreateDiv('stat-value', this._panel);
+                this._updatableProperties.push({
+                    elem: elemValue,
+                    updateFct: () => { return this._sceneInstrumentation!.textureCollisionsCounter.current.toString() }
+                });
+
                 this._createStatLabel("Total lights", this._panel);
                 elemValue = Helpers.CreateDiv('stat-value', this._panel);
                 this._updatableProperties.push({

+ 19 - 7
loaders/src/glTF/1.0/babylon.glTFLoader.ts

@@ -1557,11 +1557,23 @@ module BABYLON.GLTF1 {
             GLTFLoader.Extensions[extension.name] = extension;
         }
 
-        public dispose(): void {
-            // do nothing
-        }
-
-        public importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void): boolean {
+        // #region Stubs for IGLTFLoader interface
+        public coordinateSystemMode = GLTFLoaderCoordinateSystemMode.AUTO;
+        public animationStartMode = GLTFLoaderAnimationStartMode.FIRST;
+        public compileMaterials = false;
+        public useClipPlane = false;
+        public compileShadowGenerators = false;
+
+        public onDisposeObservable = new Observable<IGLTFLoader>();
+        public onMeshLoadedObservable = new Observable<AbstractMesh>();
+        public onTextureLoadedObservable = new Observable<BaseTexture>();
+        public onMaterialLoadedObservable = new Observable<Material>();
+        public onCompleteObservable = new Observable<IGLTFLoader>();
+
+        public dispose(): void {}
+        // #endregion
+
+        public importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress: (event: SceneLoaderProgressEvent) => void, onError: (message: string) => void): boolean {
             scene.useRightHandedSystem = true;
 
             GLTFLoaderExtension.LoadRuntimeAsync(scene, data, rootUrl, gltfRuntime => {
@@ -1624,7 +1636,7 @@ module BABYLON.GLTF1 {
             return true;
         }
 
-        public loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: () => void, onProgress: (event: ProgressEvent) => void, onError: (message: string) => void): void {
+        public loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess: () => void, onProgress: (event: SceneLoaderProgressEvent) => void, onError: (message: string) => void): void {
             scene.useRightHandedSystem = true;
 
             GLTFLoaderExtension.LoadRuntimeAsync(scene, data, rootUrl, gltfRuntime => {
@@ -1688,7 +1700,7 @@ module BABYLON.GLTF1 {
             }
         };
 
-        private _loadBuffersAsync(gltfRuntime: IGLTFRuntime, onLoad: () => void, onProgress?: (event: ProgressEvent) => void): void {
+        private _loadBuffersAsync(gltfRuntime: IGLTFRuntime, onLoad: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void): void {
             var hasBuffers = false;
 
             var processBuffer = (buf: string, buffer: IGLTFBuffer) => {

+ 62 - 72
loaders/src/glTF/2.0/babylon.glTFLoader.ts

@@ -33,7 +33,7 @@ module BABYLON.GLTF2 {
         readonly BYTES_PER_ELEMENT: number;
     }
 
-    interface GLTFLoaderRequest extends XMLHttpRequest {
+    interface IGLTFLoaderFileRequest extends IFileRequest {
         _lengthComputable?: boolean;
         _loaded?: number;
         _total?: number;
@@ -44,16 +44,15 @@ module BABYLON.GLTF2 {
         public _babylonScene: Scene;
 
         private _disposed = false;
-        private _parent: GLTFFileLoader;
         private _rootUrl: string;
         private _defaultMaterial: PBRMaterial;
         private _defaultSampler = {} as IGLTFSampler;
         private _rootNode: IGLTFNode;
         private _successCallback?: () => void;
-        private _progressCallback?: (event: ProgressEvent) => void;
+        private _progressCallback?: (event: SceneLoaderProgressEvent) => void;
         private _errorCallback?: (message: string, exception?: any) => void;
         private _renderReady = false;
-        private _requests = new Array<GLTFLoaderRequest>();
+        private _requests = new Array<IGLTFLoaderFileRequest>();
 
         private _renderReadyObservable = new Observable<GLTFLoader>();
 
@@ -79,25 +78,17 @@ module BABYLON.GLTF2 {
             GLTFLoaderExtension._Extensions.push(extension);
         }
 
-        // IE 11 Compatibility.
-        private static _createProgressEvent: (lengthComputable: boolean, loaded: number, total: number) => ProgressEvent =
-            (typeof (<any>window)["ProgressEvent"] === "function")
-                ? (lengthComputable, loaded, total) => {
-                    return new ProgressEvent("GLTFLoaderProgress", {
-                        lengthComputable: lengthComputable,
-                        loaded: loaded,
-                        total: total
-                    });
-                }
-                : (lengthComputable, loaded, total) => {
-                    const event = document.createEvent("ProgressEvent");
-                    event.initProgressEvent("GLTFLoaderProgress", false, false, lengthComputable, loaded, total);
-                    return event;
-                };
+        public coordinateSystemMode = GLTFLoaderCoordinateSystemMode.AUTO;
+        public animationStartMode = GLTFLoaderAnimationStartMode.FIRST;
+        public compileMaterials = false;
+        public useClipPlane = false;
+        public compileShadowGenerators = false;
 
-        public constructor(parent: GLTFFileLoader) {
-            this._parent = parent;
-        }
+        public onDisposeObservable = new Observable<IGLTFLoader>();
+        public onMeshLoadedObservable = new Observable<AbstractMesh>();
+        public onTextureLoadedObservable = new Observable<BaseTexture>();
+        public onMaterialLoadedObservable = new Observable<Material>();
+        public onCompleteObservable = new Observable<IGLTFLoader>();
 
         public dispose(): void {
             if (this._disposed) {
@@ -106,24 +97,13 @@ module BABYLON.GLTF2 {
 
             this._disposed = true;
 
-            // Abort requests that are not complete
-            for (const request of this._requests) {
-                if (request.readyState !== (XMLHttpRequest.DONE || 4)) {
-                    request.abort();
-                }
-            }
+            this._abortRequests();
+            this._releaseResources();
 
-            // Revoke object urls created during load
-            if (this._gltf.textures) {
-                for (const texture of this._gltf.textures) {
-                    if (texture.url) {
-                        URL.revokeObjectURL(texture.url);
-                    }
-                }
-            }
+            this.onDisposeObservable.notifyObservers(this);
         }
 
-        public importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
+        public importMeshAsync(meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
             this._loadAsync(meshesNames, scene, data, rootUrl, () => {
                 if (onSuccess) {
                     onSuccess(this._getMeshes(), [], this._getSkeletons());
@@ -131,11 +111,11 @@ module BABYLON.GLTF2 {
             }, onProgress, onError);
         }
 
-        public loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
+        public loadAsync(scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
             this._loadAsync(null, scene, data, rootUrl, onSuccess, onProgress, onError);
         }
 
-        private _loadAsync(nodeNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
+        private _loadAsync(nodeNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
             this._babylonScene = scene;
             this._rootUrl = rootUrl;
             this._successCallback = onSuccess;
@@ -170,7 +150,7 @@ module BABYLON.GLTF2 {
                 total += request._total;
             }
 
-            this._progressCallback(GLTFLoader._createProgressEvent(lengthComputable, loaded, lengthComputable ? total : 0));
+            this._progressCallback(new SceneLoaderProgressEvent(lengthComputable, loaded, lengthComputable ? total : 0));
         }
 
         public _executeWhenRenderReady(func: () => void): void {
@@ -195,11 +175,10 @@ module BABYLON.GLTF2 {
         }
 
         private _onComplete(): void {
-            if (this._parent.onComplete) {
-                this._parent.onComplete();
-            }
+            this._abortRequests();
+            this._releaseResources();
 
-            this.dispose();
+            this.onCompleteObservable.notifyObservers(this);
         }
 
         private _loadData(data: IGLTFLoaderData): void {
@@ -274,7 +253,7 @@ module BABYLON.GLTF2 {
                 return;
             }
 
-            switch (this._parent.animationStartMode) {
+            switch (this.animationStartMode) {
                 case GLTFLoaderAnimationStartMode.NONE: {
                     // do nothing
                     break;
@@ -295,7 +274,7 @@ module BABYLON.GLTF2 {
                     break;
                 }
                 default: {
-                    Tools.Error("Invalid animation start mode " + this._parent.animationStartMode);
+                    Tools.Error("Invalid animation start mode " + this.animationStartMode);
                     return;
                 }
             }
@@ -313,7 +292,7 @@ module BABYLON.GLTF2 {
         private _loadScene(context: string, scene: IGLTFScene, nodeNames: any): void {
             this._rootNode = { babylonMesh: new Mesh("__root__", this._babylonScene) } as IGLTFNode;
 
-            switch (this._parent.coordinateSystemMode) {
+            switch (this.coordinateSystemMode) {
                 case GLTFLoaderCoordinateSystemMode.AUTO: {
                     if (!this._babylonScene.useRightHandedSystem) {
                         this._rootNode.rotation = [0, 1, 0, 0];
@@ -327,14 +306,12 @@ module BABYLON.GLTF2 {
                     break;
                 }
                 default: {
-                    Tools.Error("Invalid coordinate system mode " + this._parent.coordinateSystemMode);
+                    Tools.Error("Invalid coordinate system mode " + this.coordinateSystemMode);
                     return;
                 }
             }
 
-            if (this._parent.onMeshLoaded) {
-                this._parent.onMeshLoaded(this._rootNode.babylonMesh);
-            }
+            this.onMeshLoadedObservable.notifyObservers(this._rootNode.babylonMesh);
 
             let nodeIndices = scene.nodes;
 
@@ -431,9 +408,7 @@ module BABYLON.GLTF2 {
                 }
             }
 
-            if (this._parent.onMeshLoaded) {
-                this._parent.onMeshLoaded(node.babylonMesh);
-            }
+            this.onMeshLoadedObservable.notifyObservers(node.babylonMesh);
         }
 
         private _loadMesh(context: string, node: IGLTFNode, mesh: IGLTFMesh): void {
@@ -482,8 +457,8 @@ module BABYLON.GLTF2 {
                     }
 
                     this._loadMaterial("#/materials/" + material.index, material, (babylonMaterial, isNew) => {
-                        if (isNew && this._parent.onMaterialLoaded) {
-                            this._parent.onMaterialLoaded(babylonMaterial);
+                        if (isNew) {
+                            this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                         }
                         node.babylonMesh.material = babylonMaterial;
                     });
@@ -506,8 +481,8 @@ module BABYLON.GLTF2 {
                         }
 
                         this._loadMaterial("#/materials/" + material.index, material, (babylonMaterial, isNew) => {
-                            if (isNew && this._parent.onMaterialLoaded) {
-                                this._parent.onMaterialLoaded(babylonMaterial);
+                            if (isNew) {
+                                this.onMaterialLoadedObservable.notifyObservers(babylonMaterial);
                             }
 
                             subMaterials[index] = babylonMaterial;
@@ -1575,10 +1550,7 @@ module BABYLON.GLTF2 {
             babylonTexture.wrapV = sampler.wrapV;
             babylonTexture.name = texture.name || "texture" + texture.index;
 
-            if (this._parent.onTextureLoaded) {
-                this._parent.onTextureLoaded(babylonTexture);
-            }
-
+            this.onTextureLoadedObservable.notifyObservers(babylonTexture);
             return babylonTexture;
         }
 
@@ -1634,13 +1606,12 @@ module BABYLON.GLTF2 {
                 this._tryCatchOnError(() => {
                     throw new LoadFileError(context + ": Failed to load '" + uri + "'" + (request ? ": " + request.status + " " + request.statusText : ""), request);
                 });
-            }, (oldRequest, newRequest) => {
-                this._requests.splice(this._requests.indexOf(oldRequest), 1, newRequest);
-            }) as GLTFLoaderRequest;
+            }) as IGLTFLoaderFileRequest;
 
-            if (request) {
-                this._requests.push(request);
-            }
+            this._requests.push(request);
+            request.onCompleteObservable.add(() => {
+                this._requests.splice(this._requests.indexOf(request), 1);
+            });
         }
 
         public _tryCatchOnError(handler: () => void): void {
@@ -1744,7 +1715,7 @@ module BABYLON.GLTF2 {
         }
 
         private _compileMaterialAsync(babylonMaterial: Material, babylonMesh: AbstractMesh, onSuccess: () => void): void {
-            if (this._parent.useClipPlane) {
+            if (this.useClipPlane) {
                 babylonMaterial.forceCompilation(babylonMesh, () => {
                     babylonMaterial.forceCompilation(babylonMesh, () => {
                         this._tryCatchOnError(onSuccess);
@@ -1759,7 +1730,7 @@ module BABYLON.GLTF2 {
         }
 
         private _compileMaterialsAsync(onSuccess: () => void): void {
-            if (!this._parent.compileMaterials || !this._gltf.materials) {
+            if (!this.compileMaterials || !this._gltf.materials) {
                 onSuccess();
                 return;
             }
@@ -1808,7 +1779,7 @@ module BABYLON.GLTF2 {
         }
 
         private _compileShadowGeneratorsAsync(onSuccess: () => void): void {
-            if (!this._parent.compileShadowGenerators) {
+            if (!this.compileShadowGenerators) {
                 onSuccess();
                 return;
             }
@@ -1839,7 +1810,26 @@ module BABYLON.GLTF2 {
                 }
             }
         }
+
+        private _abortRequests(): void {
+            for (const request of this._requests) {
+                request.abort();
+            }
+
+            this._requests.length = 0;
+        }
+
+        private _releaseResources(): void {
+            if (this._gltf.textures) {
+                for (const texture of this._gltf.textures) {
+                    if (texture.url) {
+                        URL.revokeObjectURL(texture.url);
+                        texture.url = undefined;
+                    }
+                }
+            }
+        }
     }
 
-    GLTFFileLoader.CreateGLTFLoaderV2 = parent => new GLTFLoader(parent);
+    GLTFFileLoader.CreateGLTFLoaderV2 = () => new GLTFLoader();
 }

+ 112 - 32
loaders/src/glTF/babylon.glTFFileLoader.ts

@@ -36,13 +36,25 @@ module BABYLON {
     }
 
     export interface IGLTFLoader extends IDisposable {
-        importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
-        loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        coordinateSystemMode: GLTFLoaderCoordinateSystemMode;
+        animationStartMode: GLTFLoaderAnimationStartMode;
+        compileMaterials: boolean;
+        useClipPlane: boolean;
+        compileShadowGenerators: boolean;
+
+        onDisposeObservable: Observable<IGLTFLoader>;
+        onMeshLoadedObservable: Observable<AbstractMesh>;
+        onTextureLoadedObservable: Observable<BaseTexture>;
+        onMaterialLoadedObservable: Observable<Material>;
+        onCompleteObservable: Observable<IGLTFLoader>;
+
+        importMeshAsync: (meshesNames: any, scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        loadAsync: (scene: Scene, data: IGLTFLoaderData, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
     }
 
     export class GLTFFileLoader implements IDisposable, ISceneLoaderPluginAsync, ISceneLoaderPluginFactory {
-        public static CreateGLTFLoaderV1: (parent: GLTFFileLoader) => IGLTFLoader;
-        public static CreateGLTFLoaderV2: (parent: GLTFFileLoader) => IGLTFLoader;
+        public static CreateGLTFLoaderV1: () => IGLTFLoader;
+        public static CreateGLTFLoaderV2: () => IGLTFLoader;
 
         // #region Common options
 
@@ -51,7 +63,15 @@ module BABYLON {
          * The data.json property stores the glTF JSON.
          * The data.bin property stores the BIN chunk from a glTF binary or null if the input is not a glTF binary.
          */
-        public onParsed: (data: IGLTFLoaderData) => void;
+        public onParsedObservable = new Observable<IGLTFLoaderData>();
+
+        private _onParsedObserver: Nullable<Observer<IGLTFLoaderData>>;
+        public set onParsed(callback: (loaderData: IGLTFLoaderData) => void) {
+            if (this._onParsedObserver) {
+                this.onParsedObservable.remove(this._onParsedObserver);
+            }
+            this._onParsedObserver = this.onParsedObservable.add(callback);
+        }
 
         // #endregion
 
@@ -93,28 +113,73 @@ module BABYLON {
         /**
          * Raised when the loader creates a mesh after parsing the glTF properties of the mesh.
          */
-        public onMeshLoaded: (mesh: AbstractMesh) => void;
+        public onMeshLoadedObservable = new Observable<AbstractMesh>();
+
+        private _onMeshLoadedObserver: Nullable<Observer<AbstractMesh>>;
+        public set onMeshLoaded(callback: (mesh: AbstractMesh) => void) {
+            if (this._onMeshLoadedObserver) {
+                this.onMeshLoadedObservable.remove(this._onMeshLoadedObserver);
+            }
+            this._onMeshLoadedObserver = this.onMeshLoadedObservable.add(callback);
+        }
 
         /**
          * Raised when the loader creates a texture after parsing the glTF properties of the texture.
          */
-        public onTextureLoaded: (texture: BaseTexture) => void;
+        public onTextureLoadedObservable = new Observable<BaseTexture>();
+
+        private _onTextureLoadedObserver: Nullable<Observer<BaseTexture>>;
+        public set onTextureLoaded(callback: (Texture: BaseTexture) => void) {
+            if (this._onTextureLoadedObserver) {
+                this.onTextureLoadedObservable.remove(this._onTextureLoadedObserver);
+            }
+            this._onTextureLoadedObserver = this.onTextureLoadedObservable.add(callback);
+        }
 
         /**
          * Raised when the loader creates a material after parsing the glTF properties of the material.
          */
-        public onMaterialLoaded: (material: Material) => void;
+        public onMaterialLoadedObservable = new Observable<Material>();
+
+        private _onMaterialLoadedObserver: Nullable<Observer<Material>>;
+        public set onMaterialLoaded(callback: (Material: Material) => void) {
+            if (this._onMaterialLoadedObserver) {
+                this.onMaterialLoadedObservable.remove(this._onMaterialLoadedObserver);
+            }
+            this._onMaterialLoadedObserver = this.onMaterialLoadedObservable.add(callback);
+        }
 
         /**
          * Raised when the asset is completely loaded, immediately before the loader is disposed.
          * For assets with LODs, raised when all of the LODs are complete.
          * For assets without LODs, raised when the model is complete, immediately after onSuccess.
          */
-        public onComplete: () => void;
+        public onCompleteObservable = new Observable<GLTFFileLoader>();
+
+        private _onCompleteObserver: Nullable<Observer<GLTFFileLoader>>;
+        public set onComplete(callback: () => void) {
+            if (this._onCompleteObserver) {
+                this.onCompleteObservable.remove(this._onCompleteObserver);
+            }
+            this._onCompleteObserver = this.onCompleteObservable.add(callback);
+        }
+
+        /**
+        * Raised when the loader is disposed.
+        */
+        public onDisposeObservable = new Observable<GLTFFileLoader>();
+
+        private _onDisposeObserver: Nullable<Observer<GLTFFileLoader>>;
+        public set onDispose(callback: () => void) {
+            if (this._onDisposeObserver) {
+                this.onDisposeObservable.remove(this._onDisposeObserver);
+            }
+            this._onDisposeObserver = this.onDisposeObservable.add(callback);
+        }
 
         // #endregion
 
-        private _loader: IGLTFLoader;
+        private _loader: Nullable<IGLTFLoader> = null;
 
         public name = "gltf";
 
@@ -129,17 +194,22 @@ module BABYLON {
         public dispose(): void {
             if (this._loader) {
                 this._loader.dispose();
+                this._loader = null;
             }
-        }
 
-        public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
-            try {
-                const loaderData = GLTFFileLoader._parse(data);
+            this.onParsedObservable.clear();
+            this.onMeshLoadedObservable.clear();
+            this.onTextureLoadedObservable.clear();
+            this.onMaterialLoadedObservable.clear();
+            this.onCompleteObservable.clear();
 
-                if (this.onParsed) {
-                    this.onParsed(loaderData);
-                }
+            this.onDisposeObservable.notifyObservers(this);
+            this.onDisposeObservable.clear();
+        }
 
+        public importMeshAsync(meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
+            try {
+                const loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
                 this._loader.importMeshAsync(meshesNames, scene, loaderData, rootUrl, onSuccess, onProgress, onError);
             }
@@ -153,14 +223,9 @@ module BABYLON {
             }
         }
 
-        public loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
+        public loadAsync(scene: Scene, data: string | ArrayBuffer, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void): void {
             try {
-                const loaderData = GLTFFileLoader._parse(data);
-
-                if (this.onParsed) {
-                    this.onParsed(loaderData);
-                }
-
+                const loaderData = this._parse(data);
                 this._loader = this._getLoader(loaderData);
                 this._loader.loadAsync(scene, loaderData, rootUrl, onSuccess, onProgress, onError);
             }
@@ -184,15 +249,20 @@ module BABYLON {
             return new GLTFFileLoader();
         }
 
-        private static _parse(data: string | ArrayBuffer): IGLTFLoaderData {
+        private _parse(data: string | ArrayBuffer): IGLTFLoaderData {
+            let parsedData: IGLTFLoaderData;
             if (data instanceof ArrayBuffer) {
-                return GLTFFileLoader._parseBinary(data);
+                parsedData = GLTFFileLoader._parseBinary(data);
+            }
+            else {
+                parsedData = {
+                    json: JSON.parse(data),
+                    bin: null
+                };
             }
 
-            return {
-                json: JSON.parse(data),
-                bin: null
-            };
+            this.onParsedObservable.notifyObservers(parsedData);
+            return parsedData;
         }
 
         private _getLoader(loaderData: IGLTFLoaderData): IGLTFLoader {
@@ -216,7 +286,7 @@ module BABYLON {
                 }
             }
 
-            const createLoaders: { [key: number]: (parent: GLTFFileLoader) => IGLTFLoader } = {
+            const createLoaders: { [key: number]: () => IGLTFLoader } = {
                 1: GLTFFileLoader.CreateGLTFLoaderV1,
                 2: GLTFFileLoader.CreateGLTFLoaderV2
             };
@@ -226,7 +296,17 @@ module BABYLON {
                 throw new Error("Unsupported version: " + asset.version);
             }
 
-            return createLoader(this);
+            const loader = createLoader();
+            loader.coordinateSystemMode = this.coordinateSystemMode;
+            loader.animationStartMode = this.animationStartMode;
+            loader.compileMaterials = this.compileMaterials;
+            loader.useClipPlane = this.useClipPlane;
+            loader.compileShadowGenerators = this.compileShadowGenerators;
+            loader.onMeshLoadedObservable.add(mesh => this.onMeshLoadedObservable.notifyObservers(mesh));
+            loader.onTextureLoadedObservable.add(texture => this.onTextureLoadedObservable.notifyObservers(texture));
+            loader.onMaterialLoadedObservable.add(material => this.onMaterialLoadedObservable.notifyObservers(material));
+            loader.onCompleteObservable.add(() => this.onCompleteObservable.notifyObservers(this));
+            return loader;
         }
 
         private static _parseBinary(data: ArrayBuffer): IGLTFLoaderData {

+ 3 - 2
package.json

@@ -4,11 +4,12 @@
     },
     "contributors": [
         "David ROUSSET",
-        "Sebastien VANDENBERGHE"
+        "Sebastien VANDENBERGHE",
+        "Raanan Weber"
     ],
     "name": "babylonjs",
     "description": "Babylon.js is a JavaScript 3D engine based on webgl.",
-    "version": "3.1.1",
+    "version": "3.2.0-alpha0",
     "repository": {
         "type": "git",
         "url": "https://github.com/BabylonJS/Babylon.js.git"

+ 0 - 1
sandbox/index.js

@@ -38,7 +38,6 @@ if (BABYLON.Engine.isSupported()) {
         currentPluginName = plugin.name;
 
         if (plugin.name === "gltf" && plugin instanceof BABYLON.GLTFFileLoader) {
-            plugin.animationStartMode = BABYLON.GLTFLoaderAnimationStartMode.ALL;
             plugin.compileMaterials = true;
         }
     });

+ 728 - 0
serializers/src/glTF/2.0/babylon.glTFExporter.ts

@@ -0,0 +1,728 @@
+module BABYLON {
+    interface _IGLTFAsset {
+        generator: string;
+        version: string;
+    }
+    interface _IGLTFScene {
+        nodes: number[];
+    }
+    interface _IGLTFNode {
+        mesh: number;
+        name?: string;
+        translation?: number[];
+        scale?: number[];
+        rotation?: number[];
+    }
+    interface _IGLTFMeshPrimitive {
+        attributes: { [index: string]: number };
+        indices?: number;
+        material?: number;
+    }
+    interface _IGLTFMesh {
+        primitives: _IGLTFMeshPrimitive[];
+    }
+    interface _IGLTFMaterial {
+    }
+    interface _IGLTFBuffer {
+        byteLength: number;
+        uri?: string;
+    }
+    interface _IGLTFBufferView {
+        buffer: number;
+        byteOffset?: number;
+        byteLength: number;
+    }
+    interface _IGLTFAccessor {
+        name: string;
+        bufferView: number;
+        componentType: number;
+        count: number;
+        type: string;
+        min?: number[];
+        max?: number[];
+    }
+    interface _IGLTF {
+        buffers: _IGLTFBuffer[];
+        asset: _IGLTFAsset;
+        meshes: _IGLTFMesh[];
+        materials?: _IGLTFMaterial[];
+        scenes: _IGLTFScene[];
+        scene?: number;
+        nodes: _IGLTFNode[];
+        bufferViews: _IGLTFBufferView[];
+        accessors: _IGLTFAccessor[];
+    }
+    export class _GLTF2Exporter {
+
+        private bufferViews: _IGLTFBufferView[];
+        private accessors: _IGLTFAccessor[];
+        private nodes: _IGLTFNode[];
+        private asset: _IGLTFAsset;
+        private scenes: _IGLTFScene[];
+        private meshes: _IGLTFMesh[];
+        private totalByteLength: number;
+        private babylonScene: BABYLON.Scene;
+
+        public constructor(babylonScene: BABYLON.Scene) {
+            this.asset = { generator: "BabylonJS", version: "2.0" };
+            this.babylonScene = babylonScene;
+            this.bufferViews = new Array<_IGLTFBufferView>();
+            this.accessors = new Array<_IGLTFAccessor>();
+            this.meshes = new Array<_IGLTFMesh>();
+            this.scenes = new Array<_IGLTFScene>();
+            this.nodes = new Array<_IGLTFNode>();
+
+            let totalByteLength = 0;
+
+            totalByteLength = this.createScene(this.babylonScene, totalByteLength);
+            
+            this.totalByteLength = totalByteLength;
+        }
+
+        /**
+         * Creates a buffer view based on teh supplied arguments
+         * @param bufferIndex 
+         * @param byteOffset 
+         * @param byteLength 
+         * 
+         * @returns {_IGLTFBufferView} 
+         */
+        private createBufferView(bufferIndex: number, byteOffset: number, byteLength: number): _IGLTFBufferView {
+            let bufferview: _IGLTFBufferView = { buffer: bufferIndex, byteLength: byteLength };
+            if (byteOffset > 0) {
+                bufferview.byteOffset = byteOffset;
+            }
+
+            return bufferview;
+        }
+
+        /**
+         * Creates an accessor based on the supplied arguments
+         * @param bufferviewIndex 
+         * @param name 
+         * @param type 
+         * @param componentType 
+         * @param count 
+         * @param min 
+         * @param max 
+         * 
+         * @returns {_IGLTFAccessor} 
+         */
+        private createAccessor(bufferviewIndex: number, name: string, type: string, componentType: number, count: number, min?: number[], max?: number[]): _IGLTFAccessor {
+            let accessor: _IGLTFAccessor = { name: name, bufferView: bufferviewIndex, componentType: componentType, count: count, type: type };
+
+            if (min) {
+                accessor.min = min;
+            }
+            if (max) {
+                accessor.max = max;
+            }
+
+            return accessor;
+        }
+        /**
+         * Calculates the minimum and maximum values of an array of floats, based on stride
+         * @param buff 
+         * @param vertexStart 
+         * @param vertexCount 
+         * @param arrayOffset 
+         * @param stride 
+         * 
+         * @returns {min: number[], max: number[]} min number array and max number array
+         */
+        private calculateMinMax(buff: FloatArray, vertexStart: number, vertexCount: number, arrayOffset: number, stride: number): { min: number[], max: number[] } {
+            let min = [Infinity, Infinity, Infinity];
+            let max = [-Infinity, -Infinity, -Infinity];
+            let end = vertexStart + vertexCount;
+            if (vertexCount > 0) {
+                for (let i = vertexStart; i < end; ++i) {
+                    let index = stride * i;
+                    for (let j = 0; j < stride; ++j) {
+                        if (buff[index] < min[j]) {
+                            min[j] = buff[index];
+                        }
+                        if (buff[index] > max[j]) {
+                            max[j] = buff[index];
+                        }
+                        ++index;
+                    }
+                }
+            }
+            return { min, max };
+        }
+        /**
+         * Write mesh attribute data to buffer.
+         * Returns the bytelength of the data.
+         * @param vertexBufferType 
+         * @param submesh 
+         * @param meshAttributeArray 
+         * @param strideSize 
+         * @param byteOffset 
+         * @param dataBuffer 
+         * @param useRightHandedSystem 
+         * 
+         * @returns {number} byte length
+         */
+        private writeAttributeData(vertexBufferType: string, submesh: BABYLON.SubMesh, meshAttributeArray: FloatArray, strideSize: number, byteOffset: number, dataBuffer: DataView, useRightHandedSystem: boolean): number {
+            let byteOff = byteOffset;
+            let end = submesh.verticesStart + submesh.verticesCount;
+            let byteLength = 0;
+
+            switch (vertexBufferType) {
+                case BABYLON.VertexBuffer.PositionKind: {
+                    for (let k = submesh.verticesStart; k < end; ++k) {
+                        let index = k * strideSize!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index], true);
+                        byteOff += 4!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index + 1], true);
+                        byteOff += 4!;
+                        if (useRightHandedSystem) {
+                            dataBuffer!.setFloat32(byteOff, meshAttributeArray![index + 2], true);
+                        }
+                        else {
+                            dataBuffer!.setFloat32(byteOff, -meshAttributeArray![index + 2], true);
+                        }
+
+                        byteOff += 4!;
+                    }
+                    byteLength = submesh.verticesCount * 12;
+                    break;
+                }
+                case BABYLON.VertexBuffer.NormalKind: {
+                    for (let k = submesh.verticesStart; k < end; ++k) {
+                        let index = k * strideSize!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index], true);
+                        byteOff += 4!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index + 1], true);
+                        byteOff += 4!;
+                        if (useRightHandedSystem) {
+                            dataBuffer!.setFloat32(byteOff, meshAttributeArray![index + 2], true);
+                        }
+                        else {
+                            dataBuffer!.setFloat32(byteOff, -meshAttributeArray![index + 2], true);
+                        }
+
+                        byteOff += 4!;
+                    }
+                    byteLength = submesh.verticesCount * 12;
+                    break;
+                }
+                case BABYLON.VertexBuffer.TangentKind: {
+                    for (let k = submesh.indexStart; k < end; ++k) {
+                        let index = k * strideSize!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index], true);
+                        byteOff += 4!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index + 1], true);
+                        byteOff += 4!;
+                        if (useRightHandedSystem) {
+                            dataBuffer!.setFloat32(byteOff, meshAttributeArray![index + 2], true);
+                        }
+                        else {
+                            dataBuffer!.setFloat32(byteOff, -meshAttributeArray![index + 2], true);
+                        }
+                        byteOff += 4!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index + 3], true);
+                        byteOff += 4!;
+                    }
+                    byteLength = submesh.verticesCount * 16;
+                    break;
+                }
+                case BABYLON.VertexBuffer.ColorKind: {
+                    for (let k = submesh.verticesStart; k < end; ++k) {
+                        let index = k * strideSize!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index], true);
+                        byteOff += 4!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index + 1], true);
+                        byteOff += 4!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index + 2], true);
+                        byteOff += 4!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index + 3], true);
+                        byteOff += 4!;
+                    }
+                    byteLength = submesh.verticesCount * 16;
+                    break;
+                }
+                case BABYLON.VertexBuffer.UVKind: {
+                    for (let k = submesh.verticesStart; k < end; ++k) {
+                        let index = k * strideSize!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index], true);
+                        byteOff += 4!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index + 1], true);
+                        byteOff += 4!;
+                    }
+                    byteLength = submesh.verticesCount * 8;
+                    break;
+                }
+                case BABYLON.VertexBuffer.UV2Kind: {
+                    for (let k = submesh.verticesStart; k < end; ++k) {
+                        let index = k * strideSize!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index], true);
+                        byteOff += 4!;
+                        dataBuffer!.setFloat32(byteOff, meshAttributeArray![index + 1], true);
+                        byteOff += 4!;
+                    }
+                    byteLength = submesh.verticesCount * 8;
+                    break;
+                }
+                default: {
+                    throw new Error("Unsupported vertex buffer type: " + vertexBufferType);
+                }
+            }
+
+            return byteLength;
+        }
+        /**
+         * Generates glTF json data
+         * @param glb 
+         * @param glTFPrefix 
+         * @param prettyPrint 
+         * 
+         * @returns {string} json data as string
+         */
+        private generateJSON(glb: boolean, glTFPrefix?: string, prettyPrint?: boolean): string {
+            let buffer: _IGLTFBuffer = { byteLength: this.totalByteLength };
+
+            let glTf: _IGLTF = {
+                buffers: [buffer],
+                asset: this.asset,
+                meshes: this.meshes,
+                scenes: this.scenes,
+                nodes: this.nodes,
+                bufferViews: this.bufferViews,
+                accessors: this.accessors
+            };
+            if (this.scenes.length > 0) {
+                glTf.scene = 0;
+            }
+
+            if (!glb) {
+                buffer.uri = glTFPrefix + ".bin";
+            }
+
+            let jsonText = prettyPrint ? JSON.stringify(glTf, null, 2) : JSON.stringify(glTf);
+
+            return jsonText;
+        }
+        /**
+         * Generates data for .gltf and .bin files based on the glTF prefix string
+         * @param glTFPrefix 
+         * 
+         * @returns {[x: string]: string | Blob} object with glTF json tex filename 
+         * and binary file name as keys and their data as values
+         */
+        public _generateGLTF(glTFPrefix: string): { [x: string]: string | Blob } {
+            const jsonText = this.generateJSON(false, glTFPrefix, true);
+            const binaryBuffer = this.generateBinary();
+            const bin = new Blob([binaryBuffer], { type: 'application/octet-stream' });
+
+            const glTFFileName = glTFPrefix + '.gltf';
+            const glTFBinFile = glTFPrefix + '.bin';
+
+            return {
+                [glTFFileName]: jsonText,
+                [glTFBinFile]: bin
+            };
+        }
+        /**
+         * Creates a binary buffer for glTF
+         * 
+         * @returns {ArrayBuffer}
+         */
+        private generateBinary(): ArrayBuffer {
+            let byteOffset = 0;
+            let binaryBuffer = new ArrayBuffer(this.totalByteLength);
+            let dataBuffer = new DataView(binaryBuffer);
+            byteOffset = this.createScene(this.babylonScene, byteOffset, dataBuffer);
+            
+            return binaryBuffer;
+        }
+        /**
+         * Generates a glb file from the json and binary data.  
+         * Returns an object with the glb file name as the key and data as the value.
+         * @param jsonText 
+         * @param binaryBuffer 
+         * @param glTFPrefix 
+         * 
+         * @returns {[glbFileName: string]: Blob} object with glb filename as key and data as value
+         */
+        public _generateGLB(glTFPrefix: string): { [glbFileName: string]: Blob } {
+            const jsonText = this.generateJSON(true);
+            const binaryBuffer = this.generateBinary();
+            let glbFileName = glTFPrefix + '.glb';
+            let headerLength = 12;
+            let chunkLengthPrefix = 8;
+            let jsonLength = jsonText.length;
+            let jsonRemainder = jsonLength % 4;
+            let binRemainder = binaryBuffer.byteLength % 4;
+            let jsonPadding = jsonRemainder === 0 ? jsonRemainder : 4 - jsonRemainder;
+            let binPadding = binRemainder === 0 ? binRemainder : 4 - binRemainder;
+            let byteLength = headerLength + (2 * chunkLengthPrefix) + jsonLength + jsonPadding + binaryBuffer.byteLength + binPadding;
+
+            //header
+            let headerBuffer = new ArrayBuffer(headerLength);
+            let headerBufferView = new DataView(headerBuffer);
+            headerBufferView.setUint32(0, 0x46546C67, true); //glTF
+            headerBufferView.setUint32(4, 2, true); // version
+            headerBufferView.setUint32(8, byteLength, true); // total bytes in file
+
+            //json chunk
+            let jsonChunkBuffer = new ArrayBuffer(chunkLengthPrefix + jsonLength + jsonPadding);
+            let jsonChunkBufferView = new DataView(jsonChunkBuffer);
+            jsonChunkBufferView.setUint32(0, jsonLength + jsonPadding, true);
+            jsonChunkBufferView.setUint32(4, 0x4E4F534A, true);
+
+            //json chunk bytes
+            let jsonData = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix);
+            for (let i = 0; i < jsonLength; ++i) {
+                jsonData[i] = jsonText.charCodeAt(i);
+            }
+
+            //json padding
+            let jsonPaddingView = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix + jsonLength);
+            for (let i = 0; i < jsonPadding; ++i) {
+                jsonPaddingView[i] = 0x20;
+            }
+
+            //binary chunk
+            let binaryChunkBuffer = new ArrayBuffer(chunkLengthPrefix);
+            let binaryChunkBufferView = new DataView(binaryChunkBuffer);
+            binaryChunkBufferView.setUint32(0, binaryBuffer.byteLength, true);
+            binaryChunkBufferView.setUint32(4, 0x004E4942, true);
+
+            // binary padding
+            let binPaddingBuffer = new ArrayBuffer(binPadding);
+            let binPaddingView = new Uint8Array(binPaddingBuffer);
+            for (let i = 0; i < binPadding; ++i) {
+                binPaddingView[i] = 0;
+            }
+
+            // binary data
+            let glbFile = new Blob([headerBuffer, jsonChunkBuffer, binaryChunkBuffer, binaryBuffer, binPaddingBuffer], { type: 'application/octet-stream' });
+
+            return {
+                [glbFileName]: glbFile
+            };
+        }
+        /**
+         * Sets the TRS for each node
+         * @param node 
+         * @param babylonMesh 
+         * @param useRightHandedSystem 
+         */
+        private setNodeTransformation(node: _IGLTFNode, babylonMesh: BABYLON.AbstractMesh, useRightHandedSystem: boolean): void {
+            if (!(babylonMesh.position.x === 0 && babylonMesh.position.y === 0 && babylonMesh.position.z === 0)) {
+                if (useRightHandedSystem) {
+                    node.translation = babylonMesh.position.asArray();
+                }
+                else {
+                    node.translation = [babylonMesh.position.x, babylonMesh.position.y, -babylonMesh.position.z];
+                }
+
+            }
+            if (!(babylonMesh.scaling.x === 1 && babylonMesh.scaling.y === 1 && babylonMesh.scaling.z === 1)) {
+                if (useRightHandedSystem) {
+                    node.scale = babylonMesh.scaling.asArray();
+                }
+                else {
+                    node.scale = [babylonMesh.scaling.x, babylonMesh.scaling.y, -babylonMesh.scaling.z];
+                }
+            }
+            let rotationQuaternion = BABYLON.Quaternion.RotationYawPitchRoll(babylonMesh.rotation.y, babylonMesh.rotation.x, babylonMesh.rotation.z);
+            if (babylonMesh.rotationQuaternion) {
+                rotationQuaternion = rotationQuaternion.multiply(babylonMesh.rotationQuaternion);
+            }
+            if (!(rotationQuaternion.x === 0 && rotationQuaternion.y === 0 && rotationQuaternion.z === 0 && rotationQuaternion.w === 1)) {
+                if (useRightHandedSystem) {
+                    node.rotation = rotationQuaternion.asArray();
+                }
+                else {
+                    node.rotation = [-rotationQuaternion.x, -rotationQuaternion.y, rotationQuaternion.z, rotationQuaternion.w];
+                }
+            }
+        }
+        /**
+         * Sets data for the primitive attributes of each submesh
+         * @param mesh 
+         * @param babylonMesh 
+         * @param byteOffset 
+         * @param useRightHandedSystem 
+         * @param dataBuffer 
+         * 
+         * @returns {number} bytelength of the primitive attributes plus the passed in byteOffset
+         */
+        private setPrimitiveAttributes(mesh: _IGLTFMesh, babylonMesh: BABYLON.AbstractMesh, byteOffset: number, useRightHandedSystem: boolean, dataBuffer?: DataView): number {
+            // go through all mesh primitives (submeshes)
+            for (let j = 0; j < babylonMesh.subMeshes.length; ++j) {
+                let bufferMesh = null;
+                let submesh = babylonMesh.subMeshes[j];
+                let meshPrimitive: _IGLTFMeshPrimitive = { attributes: {} };
+
+                if (babylonMesh instanceof BABYLON.Mesh) {
+                    bufferMesh = (babylonMesh as BABYLON.Mesh);
+                }
+                else if (babylonMesh instanceof BABYLON.InstancedMesh) {
+                    bufferMesh = (babylonMesh as BABYLON.InstancedMesh).sourceMesh;
+                }
+
+                // Loop through each attribute of the submesh (mesh primitive)
+                if (bufferMesh!.isVerticesDataPresent(BABYLON.VertexBuffer.PositionKind)) {
+                    const positionVertexBuffer = bufferMesh!.getVertexBuffer(BABYLON.VertexBuffer.PositionKind);
+                    const positionVertexBufferOffset = positionVertexBuffer!.getOffset();
+                    const positions = positionVertexBuffer!.getData();
+                    const positionStrideSize = positionVertexBuffer!.getStrideSize();
+                    if (dataBuffer) {
+
+                        byteOffset += this.writeAttributeData(
+                            BABYLON.VertexBuffer.PositionKind,
+                            submesh,
+                            positions!,
+                            positionStrideSize!,
+                            byteOffset,
+                            dataBuffer,
+                            useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        const byteLength = submesh.verticesCount * 12;
+                        const bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+
+                        // Create accessor
+                        const result = this.calculateMinMax(positions!, submesh.verticesStart, submesh.verticesCount, positionVertexBufferOffset!, positionStrideSize!);
+                        const accessor = this.createAccessor(this.bufferViews.length - 1, "Position", "VEC3", 5126, submesh.verticesCount, result.min, result.max);
+                        this.accessors.push(accessor);
+
+                        meshPrimitive.attributes.POSITION = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh!.isVerticesDataPresent(BABYLON.VertexBuffer.NormalKind)) {
+                    const normalVertexBuffer = bufferMesh!.getVertexBuffer(BABYLON.VertexBuffer.NormalKind);
+                    const normals = normalVertexBuffer!.getData();
+                    const normalStrideSize = normalVertexBuffer!.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(
+                            BABYLON.VertexBuffer.NormalKind,
+                            submesh,
+                            normals!,
+                            normalStrideSize!,
+                            byteOffset,
+                            dataBuffer,
+                            useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        const byteLength = submesh.verticesCount * 12;
+                        const bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+
+                        // Create accessor
+                        let accessor = this.createAccessor(this.bufferViews.length - 1, "Normal", "VEC3", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+
+                        meshPrimitive.attributes.NORMAL = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh!.isVerticesDataPresent(BABYLON.VertexBuffer.TangentKind)) {
+                    const tangentVertexBuffer = bufferMesh!.getVertexBuffer(BABYLON.VertexBuffer.TangentKind);
+                    const tangents = tangentVertexBuffer!.getData();
+                    const tangentStrideSize = tangentVertexBuffer!.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(
+                            BABYLON.VertexBuffer.TangentKind,
+                            submesh,
+                            tangents!,
+                            tangentStrideSize!,
+                            byteOffset,
+                            dataBuffer,
+                            useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        const byteLength = submesh.verticesCount * 16;
+                        const bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+
+                        // Create accessor
+                        const accessor = this.createAccessor(this.bufferViews.length - 1, "Tangent", "VEC4", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+
+                        meshPrimitive.attributes.TANGENT = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh!.isVerticesDataPresent(BABYLON.VertexBuffer.ColorKind)) {
+                    const colorVertexBuffer = bufferMesh!.getVertexBuffer(BABYLON.VertexBuffer.ColorKind);
+                    const colors = colorVertexBuffer!.getData();
+                    const colorStrideSize = colorVertexBuffer!.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(
+                            BABYLON.VertexBuffer.ColorKind,
+                            submesh,
+                            colors!,
+                            colorStrideSize!,
+                            byteOffset,
+                            dataBuffer,
+                            useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        let byteLength = submesh.verticesCount * 16;
+                        let bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+
+                        // Create accessor
+                        let accessor = this.createAccessor(this.bufferViews.length - 1, "Color", "VEC4", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+
+                        meshPrimitive.attributes.COLOR_0 = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh!.isVerticesDataPresent(BABYLON.VertexBuffer.UVKind)) {
+                    const texCoord0VertexBuffer = bufferMesh!.getVertexBuffer(BABYLON.VertexBuffer.UVKind);
+                    const texCoords0 = texCoord0VertexBuffer!.getData();
+                    const texCoord0StrideSize = texCoord0VertexBuffer!.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(
+                            BABYLON.VertexBuffer.UVKind,
+                            submesh,
+                            texCoords0!,
+                            texCoord0StrideSize!,
+                            byteOffset,
+                            dataBuffer,
+                            useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        let byteLength = submesh.verticesCount * 8;
+                        let bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+
+                        // Create accessor
+                        let accessor = this.createAccessor(this.bufferViews.length - 1, "Texture Coords", "VEC2", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+
+                        meshPrimitive.attributes.TEXCOORD_0 = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh!.isVerticesDataPresent(BABYLON.VertexBuffer.UV2Kind)) {
+                    let texCoord1VertexBuffer = bufferMesh!.getVertexBuffer(BABYLON.VertexBuffer.UV2Kind);
+                    let texCoords1 = texCoord1VertexBuffer!.getData();
+                    let texCoord1StrideSize = texCoord1VertexBuffer!.getStrideSize();
+                    if (dataBuffer) {
+                        byteOffset += this.writeAttributeData(
+                            BABYLON.VertexBuffer.UV2Kind,
+                            submesh,
+                            texCoords1!,
+                            texCoord1StrideSize!,
+                            byteOffset,
+                            dataBuffer,
+                            useRightHandedSystem);
+                    }
+                    else {
+                        // Create bufferview
+                        let byteLength = submesh.verticesCount * 8;
+                        let bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+
+                        // Create accessor
+                        let accessor = this.createAccessor(this.bufferViews.length - 1, "Texture Coords", "VEC2", 5126, submesh.verticesCount);
+                        this.accessors.push(accessor);
+
+                        meshPrimitive.attributes.TEXCOORD_1 = this.accessors.length - 1;
+                    }
+                }
+
+                if (bufferMesh!.getTotalIndices() > 0) {
+                    if (dataBuffer) {
+                        let indices = bufferMesh!.getIndices();
+                        let start = submesh.indexStart;
+                        let end = submesh.indexCount + start;
+                        let byteOff = byteOffset;
+
+                        for (let k = start; k < end; k = k + 3) {
+                            dataBuffer!.setUint32(byteOff, indices![k], true);
+                            byteOff += 4;
+                            dataBuffer!.setUint32(byteOff, indices![k + 1], true);
+                            byteOff += 4;
+                            dataBuffer!.setUint32(byteOff, indices![k + 2], true);
+                            byteOff += 4;
+                        }
+
+                        let byteLength = submesh.indexCount * 4;
+                        byteOffset += byteLength;
+                    }
+                    else {
+                        // Create bufferview
+                        let indicesCount = submesh.indexCount;
+                        let byteLength = indicesCount * 4;
+                        let bufferview = this.createBufferView(0, byteOffset, byteLength);
+                        byteOffset += byteLength;
+                        this.bufferViews.push(bufferview);
+
+                        // Create accessor
+                        let accessor = this.createAccessor(this.bufferViews.length - 1, "Indices", "SCALAR", 5125, indicesCount);
+                        this.accessors.push(accessor);
+
+                        meshPrimitive.indices = this.accessors.length - 1;
+                    }
+                }
+                if (bufferMesh!.material) {
+                    //TODO: Implement Material
+                }
+                mesh.primitives.push(meshPrimitive);
+            }
+            return byteOffset;
+        }
+        /**
+         * Creates a glTF scene based on the array of meshes.
+         * Returns the the total byte offset.
+         * @param gltf 
+         * @param byteOffset 
+         * @param buffer 
+         * @param dataBuffer 
+         * 
+         * @returns {number} bytelength + byteoffset
+         */
+        private createScene(babylonScene: BABYLON.Scene, byteOffset: number, dataBuffer?: DataView): number {
+            if (babylonScene.meshes.length > 0 ) {
+                let babylonMeshes = babylonScene.meshes;
+                let scene = { nodes: new Array<number>() };
+
+                for (let i = 0; i < babylonMeshes.length; ++i) {
+                    // create node to hold translation/rotation/scale and the mesh
+                    let node: _IGLTFNode = { mesh: -1 };
+                    let babylonMesh = babylonMeshes[i];
+                    let useRightHandedSystem = babylonMesh.getScene().useRightHandedSystem;
+
+                    // Set transformation
+                    this.setNodeTransformation(node, babylonMesh, useRightHandedSystem);
+
+                    // create mesh
+                    let mesh: _IGLTFMesh = { primitives: new Array<_IGLTFMeshPrimitive>() };
+                    mesh.primitives = [];
+                    byteOffset = this.setPrimitiveAttributes(mesh, babylonMesh, byteOffset, useRightHandedSystem, dataBuffer);
+                    // go through all mesh primitives (submeshes)
+
+                    this.meshes.push(mesh);
+
+                    node.mesh = this.meshes.length - 1;
+                    if (babylonMesh.name) {
+                        node.name = babylonMesh.name;
+                    }
+                    this.nodes.push(node);
+
+                    scene.nodes.push(this.nodes.length - 1);
+                }
+
+                this.scenes.push(scene);
+            }
+
+            return byteOffset;
+        }
+    }
+}

+ 72 - 0
serializers/src/glTF/2.0/babylon.glTFSerializer.ts

@@ -0,0 +1,72 @@
+/// <reference path="../../../../dist/preview release/babylon.d.ts"/>
+
+module BABYLON {
+    export class GLTF2Export {
+        /**
+         * Exports the geometry of a Mesh array in .gltf file format.
+         * If glb is set to true, exports as .glb.
+         * @param meshes 
+         * @param materials 
+         * 
+         * @returns {[fileName: string]: string | Blob} Returns an object with a .gltf, .glb and associates textures
+         * as keys and their data and paths as values.
+         */
+        public static GLTF(scene: BABYLON.Scene, filename: string): {[fileName: string]: string | Blob} {
+            let glTFPrefix = filename.replace(/\.[^/.]+$/, "");
+            let gltfGenerator = new _GLTF2Exporter(scene);
+
+            return gltfGenerator._generateGLTF(glTFPrefix);
+        }
+        /**
+         * 
+         * @param meshes 
+         * @param filename 
+         * 
+         * @returns {[fileName: string]: string | Blob} Returns an object with a .glb filename as key and data as value
+         */
+        public static GLB(scene: BABYLON.Scene, filename: string): {[fileName: string]: string | Blob} {
+            let glTFPrefix = filename.replace(/\.[^/.]+$/, "");        
+            let gltfGenerator = new _GLTF2Exporter(scene);
+
+            return gltfGenerator._generateGLB(glTFPrefix);
+        }
+        /**
+         * Downloads data from glTF object.
+         * 
+         * @param gltfData glTF object with keys being file names and values being data
+         */
+        public static downloadFiles(gltfData: {[fileName: string]: string | Blob} ): void {
+            /**
+             * Checks for a matching suffix at the end of a string (for ES5 and lower)
+             * @param str 
+             * @param suffix 
+             * 
+             * @returns {boolean} indicating whether the suffix matches or not
+             */
+            function endsWith(str: string, suffix: string): boolean {
+                return str.indexOf(suffix, str.length - suffix.length) !== -1;
+            }
+            for (let key in gltfData) {
+                let link = document.createElement('a');
+                document.body.appendChild(link);
+                link.setAttribute("type", "hidden");
+                link.download = key;
+                let blob = gltfData[key];
+                let mimeType;
+                
+                if (endsWith(key, ".glb")) {
+                    mimeType = {type: "model/gltf-binary"};
+                }
+                else if (endsWith(key, ".bin")) {
+                    mimeType = {type: "application/octet-stream"};
+                }
+                else if (endsWith(key, ".gltf")) {
+                    mimeType = {type: "model/gltf+json"};
+                }
+
+                link.href = window.URL.createObjectURL(new Blob([blob], mimeType));
+                link.click();
+            }
+        }
+    }
+}

+ 6 - 5
src/Animations/babylon.animatable.ts

@@ -76,13 +76,14 @@
                 runtimeAnimations[index].reset();
             }
 
+            // Reset to original value
+            for (index = 0; index < runtimeAnimations.length; index++) {
+                var animation = runtimeAnimations[index];
+                animation.animate(0, this.fromFrame, this.toFrame, false, this._speedRatio);
+            }
+
             this._localDelayOffset = null;
             this._pausedDelay = null;
-
-            let oldPauseState = this._paused;
-            this._paused = false;
-            this._animate(0);
-            this._paused = oldPauseState;
         }
 
         public enableBlending(blendingSpeed: number): void {

+ 44 - 0
src/Animations/babylon.animation.ts

@@ -151,6 +151,20 @@
             return animation;
         }
 
+        /**
+         * Create and start an animation on a node
+         * @param {string} name defines the name of the global animation that will be run on all nodes
+         * @param {BABYLON.Node} node defines the root node where the animation will take place
+         * @param {string} targetProperty defines property to animate
+         * @param {number} framePerSecond defines the number of frame per second yo use
+         * @param {number} totalFrame defines the number of frames in total
+         * @param {any} from defines the initial value
+         * @param {any} to defines the final value
+         * @param {number} loopMode defines which loop mode you want to use (off by default)
+         * @param {BABYLON.EasingFunction} easingFunction defines the easing function to use (linear by default)
+         * @param onAnimationEnd defines the callback to call when animation end
+         * @returns the animatable created for this animation
+         */
         public static CreateAndStartAnimation(name: string, node: Node, targetProperty: string,
             framePerSecond: number, totalFrame: number,
             from: any, to: any, loopMode?: number, easingFunction?: EasingFunction, onAnimationEnd?: () => void): Nullable<Animatable> {
@@ -164,6 +178,36 @@
             return node.getScene().beginDirectAnimation(node, [animation], 0, totalFrame, (animation.loopMode === 1), 1.0, onAnimationEnd);
         }
 
+        /**
+         * Create and start an animation on a node and its descendants
+         * @param {string} name defines the name of the global animation that will be run on all nodes
+         * @param {BABYLON.Node} node defines the root node where the animation will take place
+         * @param {boolean} directDescendantsOnly if true only direct descendants will be used, if false direct and also indirect (children of children, an so on in a recursive manner) descendants will be used.
+         * @param {string} targetProperty defines property to animate
+         * @param {number} framePerSecond defines the number of frame per second yo use
+         * @param {number} totalFrame defines the number of frames in total
+         * @param {any} from defines the initial value
+         * @param {any} to defines the final value
+         * @param {number} loopMode defines which loop mode you want to use (off by default)
+         * @param {BABYLON.EasingFunction} easingFunction defines the easing function to use (linear by default)
+         * @param onAnimationEnd defines the callback to call when an animation ends (will be called once per node)
+         * @returns the list of animatables created for all nodes
+         * @example https://www.babylonjs-playground.com/#MH0VLI
+         */
+        public static CreateAndStartHierarchyAnimation(name: string, node: Node, directDescendantsOnly: boolean, targetProperty: string,
+            framePerSecond: number, totalFrame: number,
+            from: any, to: any, loopMode?: number, easingFunction?: EasingFunction, onAnimationEnd?: () => void): Nullable<Animatable[]> {
+
+            var animation = Animation._PrepareAnimation(name, targetProperty, framePerSecond, totalFrame, from, to, loopMode, easingFunction);
+
+            if (!animation) {
+                return null;
+            }
+
+            let scene = node.getScene();
+            return scene.beginDirectHierarchyAnimation(node, directDescendantsOnly, [animation], 0, totalFrame, (animation.loopMode === 1), 1.0, onAnimationEnd);
+        }
+
         public static CreateMergeAndStartAnimation(name: string, node: Node, targetProperty: string,
             framePerSecond: number, totalFrame: number,
             from: any, to: any, loopMode?: number, easingFunction?: EasingFunction, onAnimationEnd?: () => void): Nullable<Animatable> {

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

@@ -18,13 +18,40 @@ module BABYLON {
         private _from = Number.MAX_VALUE;
         private _to = Number.MIN_VALUE;
         private _isStarted: boolean;
+        private _speedRatio = 1;
 
-        public onAnimationEndObservable = new Observable<Animation>();
+        public onAnimationEndObservable = new Observable<TargetedAnimation>();
 
+        /**
+         * Define if the animations are started
+         */
         public get isStarted(): boolean {
             return this._isStarted;
         }
 
+        /**
+         * Gets or sets the speed ratio to use for all animations
+         */
+        public get speedRatio(): number {
+            return this._speedRatio;
+        }
+
+        /**
+         * Gets or sets the speed ratio to use for all animations
+         */
+        public set speedRatio(value: number) {
+            if (this._speedRatio === value) {
+                return;
+            }
+
+            this._speedRatio = value;
+
+            for (var index = 0; index < this._animatables.length; index++) {
+                let animatable = this._animatables[index];
+                animatable.speedRatio = this._speedRatio;
+            }
+        }
+
         public constructor(public name: string, scene: Nullable<Scene> = null) {
             this._scene = scene || Engine.LastCreatedScene!;
 
@@ -110,10 +137,12 @@ module BABYLON {
             for (var index = 0; index < this._targetedAnimations.length; index++) {
                 let targetedAnimation = this._targetedAnimations[index];
                 this._animatables.push(this._scene.beginDirectAnimation(targetedAnimation.target, [targetedAnimation.animation], this._from, this._to, loop, speedRatio, () => {
-
+                    this.onAnimationEndObservable.notifyObservers(targetedAnimation);
                 }));
             }
 
+            this._speedRatio = speedRatio;
+
             this._isStarted = true;
 
             return this;
@@ -138,12 +167,19 @@ module BABYLON {
         /**
          * Play all animations to initial state
          * This function will start() the animations if they were not started or will restart() them if they were paused
+         * @param loop defines if animations must loop
          */
-        public play(loop = false): AnimationGroup {
+        public play(loop?: boolean): AnimationGroup {
             if (this.isStarted) {
+                if (loop !== undefined) {
+                    for (var index = 0; index < this._animatables.length; index++) {
+                        let animatable = this._animatables[index];
+                        animatable.loopAnimation = loop;
+                    }
+                }
                 this.restart();
             } else {
-                this.start(loop);
+                this.start(loop, this._speedRatio);
             }
 
             return this;

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

@@ -129,7 +129,7 @@ module BABYLON {
                                 if (codecSupportedFound) {
                                     // Loading sound using XHR2
                                     if (!this._streaming) {
-                                        Tools.LoadFile(url, (data) => { this._soundLoaded(data as ArrayBuffer); }, undefined, this._scene.database, true);
+                                        this._scene._loadFile(url, (data) => { this._soundLoaded(data as ArrayBuffer); }, undefined, true, true);
                                     }
                                     // Streaming sound using HTML5 Audio tag
                                     else {

+ 38 - 10
src/Cameras/VR/babylon.vrExperienceHelper.ts

@@ -112,6 +112,21 @@ module BABYLON {
         public onNewMeshSelected = new Observable<AbstractMesh>();
         private _circleEase: CircleEase;
 
+        /**
+         * Observable raised before camera teleportation        
+        */
+        public onBeforeCameraTeleport = new Observable<Vector3>();
+
+        /**
+         *  Observable raised after camera teleportation
+        */
+        public onAfterCameraTeleport = new Observable<Vector3>();
+
+        /**
+        * Observable raised when current selected mesh gets unselected
+        */
+        public onSelectedMeshUnselected = new Observable<AbstractMesh>();
+
         private _raySelectionPredicate: (mesh: AbstractMesh) => boolean;
 
         /**
@@ -483,6 +498,8 @@ module BABYLON {
 
             if (this._scene.activeCamera) {
                 this._position = this._scene.activeCamera.position.clone();
+                // make sure that we return to the last active camera
+                this._existingCamera = this._scene.activeCamera;
             }
 
             if (this._webVRrequesting)
@@ -505,6 +522,10 @@ module BABYLON {
             if (this._scene.activeCamera && this._canvas) {
                 this._scene.activeCamera.attachControl(this._canvas);
             }
+
+            if (this._interactionsEnabled) {
+                this._scene.registerBeforeRender(this.beforeRender);
+            }
         }
 
         /**
@@ -539,6 +560,10 @@ module BABYLON {
             }
 
             this.updateButtonVisibility();
+
+            if (this._interactionsEnabled) {
+                this._scene.unregisterBeforeRender(this.beforeRender);
+            }
         }
 
         public get position(): Vector3 {
@@ -584,8 +609,6 @@ module BABYLON {
                     return false;
                 }
 
-                this._scene.registerBeforeRender(this.beforeRender);
-
                 this._interactionsEnabled = true;
             }
         }
@@ -635,13 +658,11 @@ module BABYLON {
 
                 this.enableInteractions();
 
-                if (vrTeleportationOptions) {
-                    if (vrTeleportationOptions.floorMeshName) {
-                        this._floorMeshName = vrTeleportationOptions.floorMeshName;
-                    }
-                    if (vrTeleportationOptions.floorMeshes) {
-                        this._floorMeshesCollection = vrTeleportationOptions.floorMeshes;
-                    }
+                if (vrTeleportationOptions.floorMeshName) {
+                    this._floorMeshName = vrTeleportationOptions.floorMeshName;
+                }
+                if (vrTeleportationOptions.floorMeshes) {
+                    this._floorMeshesCollection = vrTeleportationOptions.floorMeshes;
                 }
 
                 if (this._leftControllerReady && this._webVRCamera.leftController) {
@@ -1212,6 +1233,8 @@ module BABYLON {
                 this._workingVector.y += this._defaultHeight;
             }
 
+            this.onBeforeCameraTeleport.notifyObservers(this._workingVector);
+
             // Create animation from the camera's position to the new location
             this.currentVRCamera.animations = [];
             var animationCameraTeleportation = new Animation("animationCameraTeleportation", "position", 90, Animation.ANIMATIONTYPE_VECTOR3, Animation.ANIMATIONLOOPMODE_CONSTANT);
@@ -1278,7 +1301,9 @@ module BABYLON {
             this._scene.beginAnimation(this._postProcessMove, 0, 11, false, 1, () => {
                 this._webVRCamera.detachPostProcess(this._postProcessMove)
             });
-            this._scene.beginAnimation(this.currentVRCamera, 0, 11, false, 1);
+            this._scene.beginAnimation(this.currentVRCamera, 0, 11, false, 1, () => {
+                this.onAfterCameraTeleport.notifyObservers(this._workingVector);
+            });
         }
 
         private _castRayAndSelectObject() {
@@ -1392,6 +1417,9 @@ module BABYLON {
                         }
                     }
                     else {
+                        if (this._currentMeshSelected) {
+                            this.onSelectedMeshUnselected.notifyObservers(this._currentMeshSelected);
+                        }
                         this._currentMeshSelected = null;
                         this.changeGazeColor(new Color3(0.7, 0.7, 0.7));
                         this.changeLaserColor(new Color3(0.7, 0.7, 0.7));

+ 16 - 8
src/Cameras/VR/babylon.webVRCamera.ts

@@ -325,40 +325,48 @@ module BABYLON {
         private _workingVector = Vector3.Zero();
         private _oneVector = Vector3.One();
         private _workingMatrix = Matrix.Identity();
+
+        private updateCacheCalled: boolean;
+
         public _updateCache(ignoreParentClass?: boolean): void {
             if (!this.rotationQuaternion.equals(this._cache.rotationQuaternion) || !this.position.equals(this._cache.position)) {
                 // Update to ensure devicePosition is up to date with most recent _deviceRoomPosition
-                this.update();
+                if (!this.updateCacheCalled) {
+                    // make sure it is only called once per loop. this.update() might cause an infinite loop.
+                    this.updateCacheCalled = true;
+                    this.update();
+                }
 
                 // Set working vector to the device position in room space rotated by the new rotation
                 this.rotationQuaternion.toRotationMatrix(this._workingMatrix);
                 Vector3.TransformCoordinatesToRef(this._deviceRoomPosition, this._workingMatrix, this._workingVector);
 
                 // Subtract this vector from the current device position in world to get the translation for the device world matrix
-                this.devicePosition.subtractToRef(this._workingVector, this._workingVector)
+                this.devicePosition.subtractToRef(this._workingVector, this._workingVector);
                 Matrix.ComposeToRef(this._oneVector, this.rotationQuaternion, this._workingVector, this._deviceToWorld);
 
                 // Add translation from anchor position
-                this._deviceToWorld.getTranslationToRef(this._workingVector)
+                this._deviceToWorld.getTranslationToRef(this._workingVector);
                 this._workingVector.addInPlace(this.position);
-                this._workingVector.subtractInPlace(this._cache.position)
-                this._deviceToWorld.setTranslation(this._workingVector)
+                this._workingVector.subtractInPlace(this._cache.position);
+                this._deviceToWorld.setTranslation(this._workingVector);
 
                 // Set an inverted matrix to be used when updating the camera
-                this._deviceToWorld.invertToRef(this._worldToDevice)
+                this._deviceToWorld.invertToRef(this._worldToDevice);
 
                 // Update the gamepad to ensure the mesh is updated on the same frame as camera
                 this.controllers.forEach((controller) => {
                     controller._deviceToWorld = this._deviceToWorld;
                     controller.update();
-                })
-                this.update();
+                });
             }
 
             if (!ignoreParentClass) {
                 super._updateCache();
             }
+            this.updateCacheCalled = false;
         }
+
         public update() {
             // Get current device position in babylon world
             Vector3.TransformCoordinatesToRef(this._deviceRoomPosition, this._deviceToWorld, this.devicePosition);

+ 1 - 0
src/Cameras/babylon.targetCamera.ts

@@ -37,6 +37,7 @@
         }
 
         public getFrontPosition(distance: number): Vector3 {
+            this.getWorldMatrix();
             var direction = this.getTarget().subtract(this.position);
             direction.normalize();
             direction.scaleInPlace(distance);

+ 323 - 116
src/Engine/babylon.engine.ts

@@ -161,38 +161,6 @@
         }
     };
 
-    var partialLoadFile = (url: string, index: number, loadedFiles: (string | ArrayBuffer)[], scene: Nullable<Scene>,
-        onfinish: (files: (string | ArrayBuffer)[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void> = null) => {
-
-        var onload = (data: string | ArrayBuffer) => {
-            loadedFiles[index] = data;
-            (<any>loadedFiles)._internalCount++;
-
-            if ((<any>loadedFiles)._internalCount === 6) {
-                onfinish(loadedFiles);
-            }
-        };
-
-        const onerror = (request?: XMLHttpRequest, exception?: any) => {
-            if (onErrorCallBack && request) {
-                onErrorCallBack(request.status + " " + request.statusText, exception);
-            }
-        };
-
-        Tools.LoadFile(url, onload, undefined, undefined, true, onerror);
-    }
-
-    var cascadeLoadFiles = (rootUrl: string, scene: Nullable<Scene>,
-        onfinish: (images: (string | ArrayBuffer)[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void> = null) => {
-
-        var loadedFiles: (string | ArrayBuffer)[] = [];
-        (<any>loadedFiles)._internalCount = 0;
-
-        for (let index = 0; index < 6; index++) {
-            partialLoadFile(files[index], index, loadedFiles, scene, onfinish, onError);
-        }
-    };
-
     class BufferPointer {
         public active: boolean;
         public index: number;
@@ -255,6 +223,7 @@
         /** The maximum textures image */
         public maxTexturesImageUnits: number;
         public maxVertexTextureImageUnits: number;
+        public maxCombinedTexturesImageUnits: number;
         /** The maximum texture size */
         public maxTextureSize: number;
         public maxCubemapTextureSize: number;
@@ -310,7 +279,10 @@
      */
     export class Engine {
         /** Use this array to turn off some WebGL2 features on known buggy browsers version */
-        public static WebGL2UniformBuffersExceptionList = ["Chrome/63"];
+        public static ExceptionList = [
+            { key: "Chrome/63.0", capture: "63\\.0\\.3239\\.(\\d+)", captureConstraint: 108, targets: ["uniformBuffer"] },
+            { key: "Firefox/58", capture: null, captureConstraint: null, targets: ["uniformBuffer"] }
+        ];
 
         public static Instances = new Array<Engine>();
 
@@ -566,7 +538,7 @@
         }
 
         public static get Version(): string {
-            return "3.2.0-alpha0";
+            return "3.2.0-alpha2";
         }
 
         // Updatable statics so stick with vars here
@@ -708,6 +680,7 @@
         private _loadingScreen: ILoadingScreen;
 
         public _drawCalls = new PerfCounter();
+        public _textureCollisions = new PerfCounter();
 
         private _glVersion: string;
         private _glRenderer: string;
@@ -753,7 +726,7 @@
         private _internalTexturesCache = new Array<InternalTexture>();
         protected _activeChannel: number;
         protected _boundTexturesCache: { [key: string]: Nullable<InternalTexture> } = {};
-        protected _boundTexturesOrder = new Array<InternalTexture>();
+        protected _boundTexturesStack = new Array<InternalTexture>();
         protected _currentEffect: Nullable<Effect>;
         protected _currentProgram: Nullable<WebGLProgram>;
         private _compiledEffects: { [key: string]: Effect } = {}
@@ -790,7 +763,9 @@
 
         private _frameHandler: number;
 
-        private _nextFreeTextureSlot = 0;
+        private _nextFreeTextureSlots = new Array<number>();
+
+        private _activeRequests = new Array<IFileRequest>();
 
         // Hardware supported Compressed Textures
         private _texturesSupported = new Array<string>();
@@ -882,15 +857,37 @@
                 this._doNotHandleContextLost = options.doNotHandleContextLost ? true : false;
 
                 // Exceptions
-                if (!options.disableWebGL2Support) {
-                    if (navigator && navigator.userAgent) {
-                        let ua = navigator.userAgent;
+                if (navigator && navigator.userAgent) {
+                    let ua = navigator.userAgent;
+
+                    for (var exception of Engine.ExceptionList) {
+                        let key = exception.key;
+                        let targets = exception.targets;
+
+                        if (ua.indexOf(key) > -1) {
+                            if (exception.capture && exception.captureConstraint) {
+                                let capture = exception.capture;
+                                let constraint = exception.captureConstraint;
+
+                                let regex = new RegExp(capture);
+                                let matches = regex.exec(ua);
+
+                                if (matches && matches.length > 0) {
+                                    let capturedValue = parseInt(matches[matches.length - 1]);
+                                    if (capturedValue >= constraint) {
+                                        continue;
+                                    }
+                                }
+                            }
 
-                        for (var exception of Engine.WebGL2UniformBuffersExceptionList) {
-                            if (ua.indexOf(exception) > -1) {
-                                this.disableUniformBuffers = true;
-                                break;
+                            for (var target of targets) {
+                                switch (target) {
+                                    case "uniformBuffer":
+                                        this.disableUniformBuffers = true;
+                                        break;
+                                }
                             }
+                            break;
                         }
                     }
                 }
@@ -1134,6 +1131,7 @@
             // Caps
             this._caps = new EngineCapabilities();
             this._caps.maxTexturesImageUnits = this._gl.getParameter(this._gl.MAX_TEXTURE_IMAGE_UNITS);
+            this._caps.maxCombinedTexturesImageUnits = this._gl.getParameter(this._gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
             this._caps.maxVertexTextureImageUnits = this._gl.getParameter(this._gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
             this._caps.maxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
             this._caps.maxCubemapTextureSize = this._gl.getParameter(this._gl.MAX_CUBE_MAP_TEXTURE_SIZE);
@@ -1295,6 +1293,11 @@
             this.setDepthBuffer(true);
             this.setDepthFunctionToLessOrEqual();
             this.setDepthWrite(true);
+
+            // Texture maps
+            for (let slot = 0; slot < this._caps.maxCombinedTexturesImageUnits; slot++) {
+                this._nextFreeTextureSlots.push(slot);
+            }
         }
 
         public get webGLVersion(): number {
@@ -1329,7 +1332,10 @@
                 }
                 this._boundTexturesCache[key] = null;
             }
-            this._nextFreeTextureSlot = 0;
+            this._nextFreeTextureSlots = [];
+            for (let slot = 0; slot < this._caps.maxCombinedTexturesImageUnits; slot++) {
+                this._nextFreeTextureSlots.push(slot);
+            }
             this._activeChannel = -1;
         }
 
@@ -1890,7 +1896,7 @@
             }
 
             if (texture.generateMipMaps && !disableGenerateMipMaps && !texture.isCube) {
-                this._bindTextureDirectly(gl.TEXTURE_2D, texture);
+                this._bindTextureDirectly(gl.TEXTURE_2D, texture, true);
                 gl.generateMipmap(gl.TEXTURE_2D);
                 this._bindTextureDirectly(gl.TEXTURE_2D, null);
             }
@@ -1906,10 +1912,68 @@
             this.bindUnboundFramebuffer(null);
         }
 
+        public unBindMultiColorAttachmentFramebuffer(textures: InternalTexture[], disableGenerateMipMaps = false, onBeforeUnbind?: () => void): void {
+            this._currentRenderTarget = null;
+
+            // If MSAA, we need to bitblt back to main texture
+            var gl = this._gl;
+
+
+            if (textures[0]._MSAAFramebuffer) {
+                gl.bindFramebuffer(gl.READ_FRAMEBUFFER, textures[0]._MSAAFramebuffer);
+                gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, textures[0]._framebuffer);
+
+                var attachments = textures[0]._attachments;
+                if (!attachments) {
+                    attachments = new Array(textures.length);
+                    textures[0]._attachments = attachments;
+                }
+
+                for (var i = 0; i < textures.length; i++) {
+                    var texture = textures[i];
+
+                    for (var j = 0; j < attachments.length; j++) {
+                        attachments[j] = gl.NONE;
+                    }
+
+                    attachments[i] = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+                    gl.readBuffer(attachments[i]);
+                    gl.drawBuffers(attachments);
+                    gl.blitFramebuffer(0, 0, texture.width, texture.height,
+                        0, 0, texture.width, texture.height,
+                        gl.COLOR_BUFFER_BIT, gl.NEAREST);
+
+                }
+                for (var i = 0; i < attachments.length; i++) {
+                    attachments[i] = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+                }
+                gl.drawBuffers(attachments);
+            }
+
+            for (var i = 0; i < textures.length; i++) {
+                var texture = textures[i];
+                if (texture.generateMipMaps && !disableGenerateMipMaps && !texture.isCube) {
+                    this._bindTextureDirectly(gl.TEXTURE_2D, texture);
+                    gl.generateMipmap(gl.TEXTURE_2D);
+                    this._bindTextureDirectly(gl.TEXTURE_2D, null);
+                }
+            }
+
+            if (onBeforeUnbind) {
+                if (textures[0]._MSAAFramebuffer) {
+                    // Bind the correct framebuffer
+                    this.bindUnboundFramebuffer(textures[0]._framebuffer);
+                }
+                onBeforeUnbind();
+            }
+
+            this.bindUnboundFramebuffer(null);
+        }
+
         public generateMipMapsForCubemap(texture: InternalTexture) {
             if (texture.generateMipMaps) {
                 var gl = this._gl;
-                this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
+                this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
                 gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
                 this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
             }
@@ -2778,6 +2842,13 @@
             this._gl.uniformMatrix2fv(uniform, false, matrix);
         }
 
+        public setInt(uniform: Nullable<WebGLUniformLocation>, value: number): void {
+            if (!uniform)
+                return;
+
+            this._gl.uniform1i(uniform, value);
+        }
+
         public setFloat(uniform: Nullable<WebGLUniformLocation>, value: number): void {
             if (!uniform)
                 return;
@@ -2956,12 +3027,12 @@
             if (this.preventCacheWipeBetweenFrames && !bruteForce) {
                 return;
             }
-            this.resetTextureCache();
             this._currentEffect = null;
 
             // 6/8/2017: deltakosh: Should not be required anymore.
-            // This message is then mostly for the future myself which will scream out loud when seeing that actually it was required :)
+            // This message is then mostly for the future myself who will scream out loud when seeing that it was actually required :)
             if (bruteForce) {
+                this.resetTextureCache();
                 this._currentProgram = null;
 
                 this._stencilState.reset();
@@ -2970,12 +3041,11 @@
                 this._alphaState.reset();
             }
 
-            this._cachedVertexBuffers = null;
+            this._resetVertexBufferBinding();
             this._cachedIndexBuffer = null;
             this._cachedEffectForVertexBuffers = null;
             this._unbindVertexArrayObject();
             this.bindIndexBuffer(null);
-            this.bindArrayBuffer(null);
         }
 
         /**
@@ -3143,7 +3213,7 @@
                 }
 
                 if (!buffer) {
-                    Tools.LoadFile(url, data => {
+                    this._loadFile(url, data => {
                         if (callback) {
                             callback(data);
                         }
@@ -3176,7 +3246,7 @@
 
                         // Using shaders to rescale because canvas.drawImage is lossy
                         let source = new InternalTexture(this, InternalTexture.DATASOURCE_TEMP);
-                        this._bindTextureDirectly(gl.TEXTURE_2D, source);
+                        this._bindTextureDirectly(gl.TEXTURE_2D, source, true);
                         gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, internalFormat, gl.UNSIGNED_BYTE, img);
 
                         gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
@@ -3186,7 +3256,7 @@
 
                         this._rescaleTexture(source, texture, scene, internalFormat, () => {
                             this._releaseTexture(source);
-                            this._bindTextureDirectly(gl.TEXTURE_2D, texture);
+                            this._bindTextureDirectly(gl.TEXTURE_2D, texture, true);
 
                             continuationCallback();
                         });
@@ -3239,7 +3309,7 @@
                 }
                 hostingScene.postProcessManager.directRender([this._rescalePostProcess], rtt, true);
 
-                this._bindTextureDirectly(this._gl.TEXTURE_2D, destination);
+                this._bindTextureDirectly(this._gl.TEXTURE_2D, destination, true);
                 this._gl.copyTexImage2D(this._gl.TEXTURE_2D, 0, internalFormat, 0, 0, destination.width, destination.height, 0);
 
                 this.unBindFramebuffer(rtt);
@@ -3282,7 +3352,7 @@
             var internalFormat = this._getInternalFormat(format);
             var internalSizedFomat = this._getRGBABufferInternalSizedFormat(type);
             var textureType = this._getWebGLTextureType(type);
-            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture, true);
             this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, invertY === undefined ? 1 : (invertY ? 1 : 0));
 
             if (!this._doNotHandleContextLost) {
@@ -3307,7 +3377,7 @@
                 this._gl.generateMipmap(this._gl.TEXTURE_2D);
             }
             this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
-            this.resetTextureCache();
+            //  this.resetTextureCache();
             texture.isReady = true;
         }
 
@@ -3329,7 +3399,7 @@
             }
 
             this.updateRawTexture(texture, data, format, invertY, compression, type);
-            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture, true);
 
             // Filters
             var filters = getSamplingParameters(samplingMode, generateMipMaps, this._gl);
@@ -3359,7 +3429,7 @@
                 height = this.needPOTTextures ? Tools.GetExponentOfTwo(height, this._caps.maxTextureSize) : height;
             }
 
-            this.resetTextureCache();
+            //  this.resetTextureCache();
             texture.width = width;
             texture.height = height;
             texture.isReady = false;
@@ -3377,19 +3447,19 @@
             var filters = getSamplingParameters(samplingMode, texture.generateMipMaps, this._gl);
 
             if (texture.isCube) {
-                this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, texture);
+                this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, texture, true);
 
                 this._gl.texParameteri(this._gl.TEXTURE_CUBE_MAP, this._gl.TEXTURE_MAG_FILTER, filters.mag);
                 this._gl.texParameteri(this._gl.TEXTURE_CUBE_MAP, this._gl.TEXTURE_MIN_FILTER, filters.min);
                 this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, null);
             } else if (texture.is3D) {
-                this._bindTextureDirectly(this._gl.TEXTURE_3D, texture);
+                this._bindTextureDirectly(this._gl.TEXTURE_3D, texture, true);
 
                 this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_MAG_FILTER, filters.mag);
                 this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_MIN_FILTER, filters.min);
                 this._bindTextureDirectly(this._gl.TEXTURE_3D, null);
             } else {
-                this._bindTextureDirectly(this._gl.TEXTURE_2D, texture);
+                this._bindTextureDirectly(this._gl.TEXTURE_2D, texture, true);
 
                 this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, filters.mag);
                 this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, filters.min);
@@ -3400,12 +3470,11 @@
         }
 
         public updateDynamicTexture(texture: Nullable<InternalTexture>, canvas: HTMLCanvasElement, invertY: boolean, premulAlpha: boolean = false, format?: number): void {
-
             if (!texture) {
                 return;
             }
 
-            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture, true);
             this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, invertY ? 1 : 0);
             if (premulAlpha) {
                 this._gl.pixelStorei(this._gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
@@ -3419,7 +3488,6 @@
             if (premulAlpha) {
                 this._gl.pixelStorei(this._gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
             }
-            this.resetTextureCache();
             texture.isReady = true;
         }
 
@@ -3428,7 +3496,7 @@
                 return;
             }
 
-            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture);
+            this._bindTextureDirectly(this._gl.TEXTURE_2D, texture, true);
             this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, invertY ? 0 : 1); // Video are upside down by default
 
             try {
@@ -3470,7 +3538,7 @@
                 }
 
                 this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
-                this.resetTextureCache();
+                //    this.resetTextureCache();
                 texture.isReady = true;
 
             } catch (ex) {
@@ -3508,7 +3576,7 @@
             var gl = this._gl;
 
             var texture = new InternalTexture(this, InternalTexture.DATASOURCE_RENDERTARGET);
-            this._bindTextureDirectly(gl.TEXTURE_2D, texture);
+            this._bindTextureDirectly(gl.TEXTURE_2D, texture, true);
 
             var width = (<{ width: number, height: number }>size).width || <number>size;
             var height = (<{ width: number, height: number }>size).height || <number>size;
@@ -3556,7 +3624,7 @@
             texture._generateDepthBuffer = fullOptions.generateDepthBuffer;
             texture._generateStencilBuffer = fullOptions.generateStencilBuffer ? true : false;
 
-            this.resetTextureCache();
+            // this.resetTextureCache();
 
             this._internalTexturesCache.push(texture);
 
@@ -3660,6 +3728,7 @@
                 texture.type = type;
                 texture._generateDepthBuffer = generateDepthBuffer;
                 texture._generateStencilBuffer = generateStencilBuffer;
+                texture._attachments = attachments;
 
                 this._internalTexturesCache.push(texture);
             }
@@ -3768,14 +3837,17 @@
             // Dispose previous render buffers
             if (texture._depthStencilBuffer) {
                 gl.deleteRenderbuffer(texture._depthStencilBuffer);
+                texture._depthStencilBuffer = null;
             }
 
             if (texture._MSAAFramebuffer) {
                 gl.deleteFramebuffer(texture._MSAAFramebuffer);
+                texture._MSAAFramebuffer = null;
             }
 
             if (texture._MSAARenderBuffer) {
                 gl.deleteRenderbuffer(texture._MSAARenderBuffer);
+                texture._MSAARenderBuffer = null;
             }
 
             if (samples > 1) {
@@ -3795,7 +3867,7 @@
                 }
 
                 gl.bindRenderbuffer(gl.RENDERBUFFER, colorRenderbuffer);
-                gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, gl.RGBA8, texture.width, texture.height);
+                gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, this._getRGBAMultiSampleBufferFormat(texture.type), texture.width, texture.height);
 
                 gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorRenderbuffer);
 
@@ -3813,6 +3885,82 @@
             return samples;
         }
 
+        public updateMultipleRenderTargetTextureSampleCount(textures: Nullable<InternalTexture[]>, samples: number): number {
+            if (this.webGLVersion < 2 || !textures || textures.length == 0) {
+                return 1;
+            }
+
+            if (textures[0].samples === samples) {
+                return samples;
+            }
+
+            var gl = this._gl;
+
+            samples = Math.min(samples, gl.getParameter(gl.MAX_SAMPLES));
+
+            // Dispose previous render buffers
+            if (textures[0]._depthStencilBuffer) {
+                gl.deleteRenderbuffer(textures[0]._depthStencilBuffer);
+                textures[0]._depthStencilBuffer = null;
+            }
+
+            if (textures[0]._MSAAFramebuffer) {
+                gl.deleteFramebuffer(textures[0]._MSAAFramebuffer);
+                textures[0]._MSAAFramebuffer = null;
+            }
+
+            for (var i = 0; i < textures.length; i++) {
+                if (textures[i]._MSAARenderBuffer) {
+                    gl.deleteRenderbuffer(textures[i]._MSAARenderBuffer);
+                    textures[i]._MSAARenderBuffer = null;
+                }
+            }
+
+            if (samples > 1) {
+                let framebuffer = gl.createFramebuffer();
+
+                if (!framebuffer) {
+                    throw new Error("Unable to create multi sampled framebuffer");
+                }
+
+                this.bindUnboundFramebuffer(framebuffer);
+
+                let depthStencilBuffer = this._setupFramebufferDepthAttachments(textures[0]._generateStencilBuffer, textures[0]._generateDepthBuffer, textures[0].width, textures[0].height, samples);
+
+                var attachments = [];
+
+                for (var i = 0; i < textures.length; i++) {
+                    var texture = textures[i];
+                    var attachment = (<any>gl)[this.webGLVersion > 1 ? "COLOR_ATTACHMENT" + i : "COLOR_ATTACHMENT" + i + "_WEBGL"];
+
+                    var colorRenderbuffer = gl.createRenderbuffer();
+
+                    if (!colorRenderbuffer) {
+                        throw new Error("Unable to create multi sampled framebuffer");
+                    }
+
+                    gl.bindRenderbuffer(gl.RENDERBUFFER, colorRenderbuffer);
+                    gl.renderbufferStorageMultisample(gl.RENDERBUFFER, samples, this._getRGBAMultiSampleBufferFormat(texture.type), texture.width, texture.height);
+
+                    gl.framebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, colorRenderbuffer);
+
+                    texture._MSAAFramebuffer = framebuffer;
+                    texture._MSAARenderBuffer = colorRenderbuffer;
+                    texture.samples = samples;
+                    texture._depthStencilBuffer = depthStencilBuffer;
+                    gl.bindRenderbuffer(gl.RENDERBUFFER, null);
+                    attachments.push(attachment);
+                }
+                gl.drawBuffers(attachments);
+            } else {
+                this.bindUnboundFramebuffer(textures[0]._framebuffer);
+            }
+
+            this.bindUnboundFramebuffer(null);
+
+            return samples;
+        }
+
         public _uploadDataToTexture(target: number, lod: number, internalFormat: number, width: number, height: number, format: number, type: number, data: ArrayBufferView) {
             this._gl.texImage2D(target, lod, internalFormat, width, height, 0, format, type, data);
         }
@@ -3848,7 +3996,7 @@
 
             var filters = getSamplingParameters(samplingMode, generateMipMaps, gl);
 
-            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
+            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
 
             for (var face = 0; face < 6; face++) {
                 gl.texImage2D((gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), 0, gl.RGBA, size, size, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
@@ -3867,7 +4015,6 @@
 
             // Mipmaps
             if (texture.generateMipMaps) {
-                this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
                 gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
             }
 
@@ -3881,7 +4028,7 @@
             texture.height = size;
             texture.isReady = true;
 
-            this.resetTextureCache();
+            //this.resetTextureCache();
 
             this._internalTexturesCache.push(texture);
 
@@ -3934,7 +4081,7 @@
 
                     var glTextureFromLod = new InternalTexture(this, InternalTexture.DATASOURCE_TEMP);
                     glTextureFromLod.isCube = true;
-                    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, glTextureFromLod);
+                    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, glTextureFromLod, true);
 
                     gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
                     gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
@@ -4007,12 +4154,12 @@
             }
 
             if (isKTX) {
-                Tools.LoadFile(rootUrl, data => {
+                this._loadFile(rootUrl, data => {
                     var ktx = new KhronosTextureContainer(data, 6);
 
                     var loadMipmap = ktx.numberOfMipmapLevels > 1 && !noMipmap;
 
-                    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
+                    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
                     gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
 
                     ktx.uploadLevels(this._gl, !noMipmap);
@@ -4025,7 +4172,7 @@
                 }, undefined, undefined, true, onerror);
             } else if (isDDS) {
                 if (files && files.length === 6) {
-                    cascadeLoadFiles(rootUrl,
+                    this._cascadeLoadFiles(rootUrl,
                         scene,
                         imgs => {
                             var info: DDSInfo | undefined;
@@ -4037,7 +4184,7 @@
 
                                 loadMipmap = (info.isRGB || info.isLuminance || info.mipmapCount > 1) && !noMipmap;
 
-                                this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
+                                this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
                                 gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, info.isCompressed ? 1 : 0);
 
                                 DDSTools.UploadDDSLevels(this, this._gl, data, info, loadMipmap, 6, -1, index);
@@ -4063,13 +4210,13 @@
                         onError);
 
                 } else {
-                    Tools.LoadFile(rootUrl,
+                    this._loadFile(rootUrl,
                         data => {
                             var info = DDSTools.GetDDSInfo(data);
 
                             var loadMipmap = (info.isRGB || info.isLuminance || info.mipmapCount > 1) && !noMipmap;
 
-                            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
+                            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
                             gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, info.isCompressed ? 1 : 0);
 
                             DDSTools.UploadDDSLevels(this, this._gl, data, info, loadMipmap, 6);
@@ -4115,7 +4262,7 @@
                         gl.TEXTURE_CUBE_MAP_NEGATIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z
                     ];
 
-                    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
+                    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
                     gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
 
                     let internalFormat = format ? this._getInternalFormat(format) : this._gl.RGBA;
@@ -4159,7 +4306,7 @@
 
             this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, null);
 
-            this.resetTextureCache();
+            //  this.resetTextureCache();
         }
 
         public updateRawCubeTexture(texture: InternalTexture, data: ArrayBufferView[], format: number, type: number, invertY: boolean, compression: Nullable<string> = null, level = 0): void {
@@ -4180,7 +4327,7 @@
                 needConversion = true;
             }
 
-            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
+            this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
             gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, invertY === undefined ? 1 : (invertY ? 1 : 0));
 
             if (texture.width % 4 !== 0) {
@@ -4207,7 +4354,7 @@
             }
             this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, null);
 
-            this.resetTextureCache();
+            // this.resetTextureCache();
             texture.isReady = true;
         }
 
@@ -4246,7 +4393,7 @@
                 this.updateRawCubeTexture(texture, data, format, type, invertY, compression);
             }
 
-            this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, texture);
+            this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, texture, true);
 
             // Filters
             if (data && generateMipMaps) {
@@ -4314,7 +4461,7 @@
                         needConversion = true;
                     }
 
-                    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture);
+                    this._bindTextureDirectly(gl.TEXTURE_CUBE_MAP, texture, true);
                     gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 0);
 
                     var mipData = mipmmapGenerator(faceDataArrays);
@@ -4338,7 +4485,7 @@
                 }
 
                 texture.isReady = true;
-                this.resetTextureCache();
+                // this.resetTextureCache();
                 scene._removePendingData(texture);
 
                 if (onLoad) {
@@ -4346,7 +4493,7 @@
                 }
             };
 
-            Tools.LoadFile(url, data => {
+            this._loadFile(url, data => {
                 internalCallback(data);
             }, undefined, scene.database, true, onerror);
 
@@ -4355,7 +4502,7 @@
 
         public updateRawTexture3D(texture: InternalTexture, data: Nullable<ArrayBufferView>, format: number, invertY: boolean, compression: Nullable<string> = null): void {
             var internalFormat = this._getInternalFormat(format);
-            this._bindTextureDirectly(this._gl.TEXTURE_3D, texture);
+            this._bindTextureDirectly(this._gl.TEXTURE_3D, texture, true);
             this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, invertY === undefined ? 1 : (invertY ? 1 : 0));
 
             if (!this._doNotHandleContextLost) {
@@ -4379,7 +4526,7 @@
                 this._gl.generateMipmap(this._gl.TEXTURE_3D);
             }
             this._bindTextureDirectly(this._gl.TEXTURE_3D, null);
-            this.resetTextureCache();
+            // this.resetTextureCache();
             texture.isReady = true;
         }
 
@@ -4401,7 +4548,7 @@
             }
 
             this.updateRawTexture3D(texture, data, format, invertY, compression);
-            this._bindTextureDirectly(this._gl.TEXTURE_3D, texture);
+            this._bindTextureDirectly(this._gl.TEXTURE_3D, texture, true);
 
             // Filters
             var filters = getSamplingParameters(samplingMode, generateMipMaps, this._gl);
@@ -4437,7 +4584,7 @@
 
             this._bindTextureDirectly(gl.TEXTURE_2D, null);
 
-            this.resetTextureCache();
+            // this.resetTextureCache();
             if (scene) {
                 scene._removePendingData(texture);
             }
@@ -4457,7 +4604,7 @@
             }
 
             if (!texture._webGLTexture) {
-                this.resetTextureCache();
+                //  this.resetTextureCache();
                 if (scene) {
                     scene._removePendingData(texture);
                 }
@@ -4465,7 +4612,7 @@
                 return;
             }
 
-            this._bindTextureDirectly(gl.TEXTURE_2D, texture);
+            this._bindTextureDirectly(gl.TEXTURE_2D, texture, true);
             gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, invertY === undefined ? 1 : (invertY ? 1 : 0));
 
             texture.baseWidth = width;
@@ -4588,34 +4735,38 @@
         }
 
         private _activateTextureChannel(channel: number): void {
-            if (this._activeChannel !== channel) {
+            if (this._activeChannel !== channel && channel > -1) {
                 this._gl.activeTexture(this._gl.TEXTURE0 + channel);
                 this._activeChannel = channel;
             }
         }
 
         private _moveBoundTextureOnTop(internalTexture: InternalTexture): void {
-            let index = this._boundTexturesOrder.indexOf(internalTexture);
+            let index = this._boundTexturesStack.indexOf(internalTexture);
 
-            if (index > -1 && index !== this._boundTexturesOrder.length - 1) {
-                this._boundTexturesOrder.splice(index, 1);
-                this._boundTexturesOrder.push(internalTexture);
+            if (index > -1 && index !== this._boundTexturesStack.length - 1) {
+                this._boundTexturesStack.splice(index, 1);
+                this._boundTexturesStack.push(internalTexture);
             }
         }
 
         private _removeDesignatedSlot(internalTexture: InternalTexture): number {
             let currentSlot = internalTexture._designatedSlot;
             internalTexture._designatedSlot = -1;
-            let index = this._boundTexturesOrder.indexOf(internalTexture);
+            let index = this._boundTexturesStack.indexOf(internalTexture);
 
             if (index > -1) {
-                this._boundTexturesOrder.splice(index, 1);
+                this._boundTexturesStack.splice(index, 1);
+                if (currentSlot > -1) {
+                    this._boundTexturesCache[currentSlot] = null;
+                    this._nextFreeTextureSlots.push(currentSlot);
+                }
             }
 
             return currentSlot;
         }
 
-        public _bindTextureDirectly(target: number, texture: Nullable<InternalTexture>, isPartOfTextureArray = false): void {
+        public _bindTextureDirectly(target: number, texture: Nullable<InternalTexture>, doNotBindUniformToTextureChannel = false): void {
             let currentTextureBound = this._boundTexturesCache[this._activeChannel];
             let isTextureForRendering = texture && texture._initialSlot > -1;
 
@@ -4630,14 +4781,18 @@
                     this._boundTexturesCache[this._activeChannel] = texture;
 
                     if (isTextureForRendering) {
-                        this._boundTexturesOrder.push(texture!);
+                        let slotIndex = this._nextFreeTextureSlots.indexOf(this._activeChannel);
+                        if (slotIndex > -1) {
+                            this._nextFreeTextureSlots.splice(slotIndex, 1);
+                        }
+                        this._boundTexturesStack.push(texture!);
                     }
                 }
             }
 
-            if (isTextureForRendering) {
+            if (isTextureForRendering && this._activeChannel > -1) {
                 texture!._designatedSlot = this._activeChannel;
-                if (!isPartOfTextureArray) {
+                if (!doNotBindUniformToTextureChannel) {
                     this._bindSamplerUniformToChannel(texture!._initialSlot, this._activeChannel);
                 }
             }
@@ -4661,7 +4816,7 @@
         }
 
         public unbindAllTextures(): void {
-            for (var channel = 0; channel < this._caps.maxTexturesImageUnits; channel++) {
+            for (var channel = 0; channel < this._caps.maxCombinedTexturesImageUnits; channel++) {
                 this._activateTextureChannel(channel);
                 this._bindTextureDirectly(this._gl.TEXTURE_2D, null);
                 this._bindTextureDirectly(this._gl.TEXTURE_CUBE_MAP, null);
@@ -4683,7 +4838,7 @@
             this._setTexture(channel, texture);
         }
 
-        private _getCorrectTextureChannel(channel: number, internalTexture: Nullable<InternalTexture>) {
+        private _getCorrectTextureChannel(channel: number, internalTexture: Nullable<InternalTexture>): number {
             if (!internalTexture) {
                 return -1;
             }
@@ -4692,17 +4847,16 @@
 
             if (channel !== internalTexture._designatedSlot) {
                 if (internalTexture._designatedSlot > -1) { // Texture is already assigned to a slot
-                    channel = internalTexture._designatedSlot;
-                } else { // Not slot for this texture, let's pick a new one
-                    if (this._nextFreeTextureSlot > -1) { // We can use a free slot
-                        channel = this._nextFreeTextureSlot;
-                        this._nextFreeTextureSlot++;
-                        if (this._nextFreeTextureSlot >= this._caps.maxTexturesImageUnits) {
-                            this._nextFreeTextureSlot = -1; // No more free slots, we will recycle
-                        }
-                    } else { // We need to recycle the oldest bound texture, sorry.
-                        channel = this._removeDesignatedSlot(this._boundTexturesOrder[0]);
+                    return internalTexture._designatedSlot;
+                } else {
+                    // No slot for this texture, let's pick a new one (if we find a free slot)
+                    if (this._nextFreeTextureSlots.length) {
+                        return this._nextFreeTextureSlots[0];
                     }
+
+                    // We need to recycle the oldest bound texture, sorry.
+                    this._textureCollisions.addCount(1, false);
+                    return this._removeDesignatedSlot(this._boundTexturesStack[0]);
                 }
             }
 
@@ -5112,6 +5266,11 @@
             this.onEndFrameObservable.clear();
 
             Effect.ResetCache();
+
+            // Abort active requests
+            for (let request of this._activeRequests) {
+                request.abort();
+            }
         }
 
         // Loading screen
@@ -5335,6 +5494,17 @@
             return this._gl.RGBA;
         };
 
+        public _getRGBAMultiSampleBufferFormat(type: number): number {
+            if (type === Engine.TEXTURETYPE_FLOAT) {
+                return this._gl.RGBA32F;
+            }
+            else if (type === Engine.TEXTURETYPE_HALF_FLOAT) {
+                return this._gl.RGBA16F;
+            }
+
+            return this._gl.RGBA8;
+        };
+
         public createQuery(): WebGLQuery {
             return this._gl.createQuery();
         }
@@ -5536,6 +5706,43 @@
             this._gl.bindBufferBase(this._gl.TRANSFORM_FEEDBACK_BUFFER, 0, value);
         }
 
+        public _loadFile(url: string, onSuccess: (data: string | ArrayBuffer, responseURL?: string) => void, onProgress?: (data: any) => void, database?: Database, useArrayBuffer?: boolean, onError?: (request?: XMLHttpRequest, exception?: any) => void): IFileRequest {
+            let request = Tools.LoadFile(url, onSuccess, onProgress, database, useArrayBuffer, onError);
+            this._activeRequests.push(request);
+            request.onCompleteObservable.add(request => {
+                this._activeRequests.splice(this._activeRequests.indexOf(request), 1);
+            });
+            return request;
+        }
+
+        private _partialLoadFile(url: string, index: number, loadedFiles: (string | ArrayBuffer)[], scene: Nullable<Scene>, onfinish: (files: (string | ArrayBuffer)[]) => void, onErrorCallBack: Nullable<(message?: string, exception?: any) => void> = null): void {
+            var onload = (data: string | ArrayBuffer) => {
+                loadedFiles[index] = data;
+                (<any>loadedFiles)._internalCount++;
+
+                if ((<any>loadedFiles)._internalCount === 6) {
+                    onfinish(loadedFiles);
+                }
+            };
+
+            const onerror = (request?: XMLHttpRequest, exception?: any) => {
+                if (onErrorCallBack && request) {
+                    onErrorCallBack(request.status + " " + request.statusText, exception);
+                }
+            };
+
+            this._loadFile(url, onload, undefined, undefined, true, onerror);
+        }
+
+        private _cascadeLoadFiles(rootUrl: string, scene: Nullable<Scene>, onfinish: (images: (string | ArrayBuffer)[]) => void, files: string[], onError: Nullable<(message?: string, exception?: any) => void> = null): void {
+            var loadedFiles: (string | ArrayBuffer)[] = [];
+            (<any>loadedFiles)._internalCount = 0;
+
+            for (let index = 0; index < 6; index++) {
+                this._partialLoadFile(files[index], index, loadedFiles, scene, onfinish, onError);
+            }
+        }
+
         // Statics
         public static isSupported(): boolean {
             try {

+ 11 - 7
src/Helpers/babylon.environmentHelper.ts

@@ -81,6 +81,11 @@ module BABYLON {
          * Unsigned Int by Default.
          */
         groundMirrorTextureType: number;
+        /**
+         * Specifies a bias applied to the ground vertical position to prevent z-fighyting with
+         * the shown objects.
+         */
+        groundYBias: number;
 
         /**
          * Specifies wether or not to create a skybox.
@@ -204,6 +209,8 @@ module BABYLON {
                 groundMirrorFallOffDistance: 0,
                 groundMirrorTextureType: Engine.TEXTURETYPE_UNSIGNED_INT,
 
+                groundYBias: 0.00001,
+
                 createSkybox: true,
                 skyboxSize: 20,
                 skyboxTexture: this._skyboxTextureCDNUrl,
@@ -471,28 +478,25 @@ module BABYLON {
 
             const sceneExtends = this._scene.getWorldExtends();
             const sceneDiagonal = sceneExtends.max.subtract(sceneExtends.min);
-            let bias = 0.0001;
 
             if (this._options.sizeAuto) {
                 if (this._scene.activeCamera instanceof ArcRotateCamera &&
                     this._scene.activeCamera.upperRadiusLimit) {
                     groundSize = this._scene.activeCamera.upperRadiusLimit * 2;
-                }
-
-                if (this._scene.activeCamera) {
-                    bias = (this._scene.activeCamera.maxZ - this._scene.activeCamera.minZ) / 10000;
+                    skyboxSize = groundSize;
                 }
 
                 const sceneDiagonalLenght = sceneDiagonal.length();
                 if (sceneDiagonalLenght > groundSize) {
                     groundSize = sceneDiagonalLenght * 2;
+                    skyboxSize = groundSize;
                 }
 
                 // 10 % bigger.
                 groundSize *= 1.1;
                 skyboxSize *= 1.5;
                 rootPosition = sceneExtends.min.add(sceneDiagonal.scale(0.5));
-                rootPosition.y = sceneExtends.min.y - bias;
+                rootPosition.y = sceneExtends.min.y - this._options.groundYBias;
             }
 
             return { groundSize, skyboxSize, rootPosition };
@@ -506,7 +510,7 @@ module BABYLON {
                 this._ground = Mesh.CreatePlane("BackgroundPlane", sceneSize.groundSize, this._scene);
                 this._ground.rotation.x = Math.PI / 2; // Face up by default.
                 this._ground.parent = this._rootMesh;
-                this._ground.onDisposeObservable.add(() => { this._ground = null; })
+                this._ground.onDisposeObservable.add(() => { this._ground = null; });
             }
 
             this._ground.receiveShadows = this._options.enableGroundShadow;

+ 109 - 101
src/Instrumentation/babylon.sceneInstrumentation.ts

@@ -4,31 +4,31 @@ module BABYLON {
      */
     export class SceneInstrumentation implements IDisposable {
         private _captureActiveMeshesEvaluationTime = false;
-        private _activeMeshesEvaluationTime = new PerfCounter();  
+        private _activeMeshesEvaluationTime = new PerfCounter();
 
         private _captureRenderTargetsRenderTime = false;
-        private _renderTargetsRenderTime = new PerfCounter();    
-        
+        private _renderTargetsRenderTime = new PerfCounter();
+
         private _captureFrameTime = false;
-        private _frameTime = new PerfCounter();        
+        private _frameTime = new PerfCounter();
 
         private _captureRenderTime = false;
-        private _renderTime = new PerfCounter();           
+        private _renderTime = new PerfCounter();
 
         private _captureInterFrameTime = false;
-        private _interFrameTime = new PerfCounter();    
-        
+        private _interFrameTime = new PerfCounter();
+
         private _captureParticlesRenderTime = false;
-        private _particlesRenderTime = new PerfCounter();       
-          
+        private _particlesRenderTime = new PerfCounter();
+
         private _captureSpritesRenderTime = false;
-        private _spritesRenderTime = new PerfCounter();   
+        private _spritesRenderTime = new PerfCounter();
 
         private _capturePhysicsTime = false;
-        private _physicsTime = new PerfCounter();     
-        
+        private _physicsTime = new PerfCounter();
+
         private _captureAnimationsTime = false;
-        private _animationsTime = new PerfCounter();            
+        private _animationsTime = new PerfCounter();
 
         // Observers
         private _onBeforeActiveMeshesEvaluationObserver: Nullable<Observer<Scene>> = null;
@@ -39,21 +39,21 @@ module BABYLON {
         private _onAfterRenderObserver: Nullable<Observer<Scene>> = null;
 
         private _onBeforeDrawPhaseObserver: Nullable<Observer<Scene>> = null;
-        private _onAfterDrawPhaseObserver: Nullable<Observer<Scene>> = null;        
-        
+        private _onAfterDrawPhaseObserver: Nullable<Observer<Scene>> = null;
+
         private _onBeforeAnimationsObserver: Nullable<Observer<Scene>> = null;
 
         private _onBeforeParticlesRenderingObserver: Nullable<Observer<Scene>> = null;
         private _onAfterParticlesRenderingObserver: Nullable<Observer<Scene>> = null;
 
         private _onBeforeSpritesRenderingObserver: Nullable<Observer<Scene>> = null;
-        private _onAfterSpritesRenderingObserver: Nullable<Observer<Scene>> = null;      
-        
+        private _onAfterSpritesRenderingObserver: Nullable<Observer<Scene>> = null;
+
         private _onBeforePhysicsObserver: Nullable<Observer<Scene>> = null;
-        private _onAfterPhysicsObserver: Nullable<Observer<Scene>> = null;     
-        
-        private _onAfterAnimationsObserver: Nullable<Observer<Scene>> = null;    
-                
+        private _onAfterPhysicsObserver: Nullable<Observer<Scene>> = null;
+
+        private _onAfterAnimationsObserver: Nullable<Observer<Scene>> = null;
+
         // Properties
         /**
          * Gets the perf counter used for active meshes evaluation time
@@ -64,28 +64,28 @@ module BABYLON {
 
         /**
          * Gets the active meshes evaluation time capture status
-         */        
+         */
         public get captureActiveMeshesEvaluationTime(): boolean {
             return this._captureActiveMeshesEvaluationTime;
-        }        
+        }
 
         /**
          * Enable or disable the active meshes evaluation time capture
-         */        
+         */
         public set captureActiveMeshesEvaluationTime(value: boolean) {
             if (value === this._captureActiveMeshesEvaluationTime) {
                 return;
             }
 
-            this._captureActiveMeshesEvaluationTime = value;            
+            this._captureActiveMeshesEvaluationTime = value;
 
             if (value) {
-                this._onBeforeActiveMeshesEvaluationObserver = this.scene.onBeforeActiveMeshesEvaluationObservable.add(()=>{
+                this._onBeforeActiveMeshesEvaluationObserver = this.scene.onBeforeActiveMeshesEvaluationObservable.add(() => {
                     Tools.StartPerformanceCounter("Active meshes evaluation");
                     this._activeMeshesEvaluationTime.beginMonitoring();
                 });
 
-                this._onAfterActiveMeshesEvaluationObserver = this.scene.onAfterActiveMeshesEvaluationObservable.add(()=>{                    
+                this._onAfterActiveMeshesEvaluationObserver = this.scene.onAfterActiveMeshesEvaluationObservable.add(() => {
                     Tools.EndPerformanceCounter("Active meshes evaluation");
                     this._activeMeshesEvaluationTime.endMonitoring();
                 });
@@ -110,11 +110,11 @@ module BABYLON {
          */
         public get captureRenderTargetsRenderTime(): boolean {
             return this._captureRenderTargetsRenderTime;
-        }        
+        }
 
         /**
          * Enable or disable the render targets render time capture
-         */        
+         */
         public set captureRenderTargetsRenderTime(value: boolean) {
             if (value === this._captureRenderTargetsRenderTime) {
                 return;
@@ -123,12 +123,12 @@ module BABYLON {
             this._captureRenderTargetsRenderTime = value;
 
             if (value) {
-                this._onBeforeRenderTargetsRenderObserver = this.scene.OnBeforeRenderTargetsRenderObservable.add(()=>{
+                this._onBeforeRenderTargetsRenderObserver = this.scene.OnBeforeRenderTargetsRenderObservable.add(() => {
                     Tools.StartPerformanceCounter("Render targets rendering");
                     this._renderTargetsRenderTime.beginMonitoring();
                 });
 
-                this._onAfterRenderTargetsRenderObserver = this.scene.OnAfterRenderTargetsRenderObservable.add(()=>{                    
+                this._onAfterRenderTargetsRenderObserver = this.scene.OnAfterRenderTargetsRenderObservable.add(() => {
                     Tools.EndPerformanceCounter("Render targets rendering");
                     this._renderTargetsRenderTime.endMonitoring(false);
                 });
@@ -139,7 +139,7 @@ module BABYLON {
                 this.scene.OnAfterRenderTargetsRenderObservable.remove(this._onAfterRenderTargetsRenderObserver);
                 this._onAfterRenderTargetsRenderObserver = null;
             }
-        }        
+        }
 
         /**
          * Gets the perf counter used for particles render time
@@ -153,11 +153,11 @@ module BABYLON {
          */
         public get captureParticlesRenderTime(): boolean {
             return this._captureParticlesRenderTime;
-        }        
+        }
 
         /**
          * Enable or disable the particles render time capture
-         */        
+         */
         public set captureParticlesRenderTime(value: boolean) {
             if (value === this._captureParticlesRenderTime) {
                 return;
@@ -166,12 +166,12 @@ module BABYLON {
             this._captureParticlesRenderTime = value;
 
             if (value) {
-                this._onBeforeParticlesRenderingObserver = this.scene.onBeforeParticlesRenderingObservable.add(()=>{                    
+                this._onBeforeParticlesRenderingObserver = this.scene.onBeforeParticlesRenderingObservable.add(() => {
                     Tools.StartPerformanceCounter("Particles");
                     this._particlesRenderTime.beginMonitoring();
                 });
 
-                this._onAfterParticlesRenderingObserver = this.scene.onAfterParticlesRenderingObservable.add(()=>{                                        
+                this._onAfterParticlesRenderingObserver = this.scene.onAfterParticlesRenderingObservable.add(() => {
                     Tools.EndPerformanceCounter("Particles");
                     this._particlesRenderTime.endMonitoring(false);
                 });
@@ -182,7 +182,7 @@ module BABYLON {
                 this.scene.onAfterParticlesRenderingObservable.remove(this._onAfterParticlesRenderingObserver);
                 this._onAfterParticlesRenderingObserver = null;
             }
-        }        
+        }
 
         /**
          * Gets the perf counter used for sprites render time
@@ -196,11 +196,11 @@ module BABYLON {
          */
         public get captureSpritesRenderTime(): boolean {
             return this._captureSpritesRenderTime;
-        }        
+        }
 
         /**
          * Enable or disable the sprites render time capture
-         */        
+         */
         public set captureSpritesRenderTime(value: boolean) {
             if (value === this._captureSpritesRenderTime) {
                 return;
@@ -209,12 +209,12 @@ module BABYLON {
             this._captureSpritesRenderTime = value;
 
             if (value) {
-                this._onBeforeSpritesRenderingObserver = this.scene.onBeforeSpritesRenderingObservable.add(()=>{                    
+                this._onBeforeSpritesRenderingObserver = this.scene.onBeforeSpritesRenderingObservable.add(() => {
                     Tools.StartPerformanceCounter("Sprites");
                     this._spritesRenderTime.beginMonitoring();
                 });
 
-                this._onAfterSpritesRenderingObserver = this.scene.onAfterSpritesRenderingObservable.add(()=>{                                        
+                this._onAfterSpritesRenderingObserver = this.scene.onAfterSpritesRenderingObservable.add(() => {
                     Tools.EndPerformanceCounter("Sprites");
                     this._spritesRenderTime.endMonitoring(false);
                 });
@@ -225,7 +225,7 @@ module BABYLON {
                 this.scene.onAfterSpritesRenderingObservable.remove(this._onAfterSpritesRenderingObserver);
                 this._onAfterSpritesRenderingObserver = null;
             }
-        }      
+        }
 
         /**
          * Gets the perf counter used for physics time
@@ -239,11 +239,11 @@ module BABYLON {
          */
         public get capturePhysicsTime(): boolean {
             return this._capturePhysicsTime;
-        }        
+        }
 
         /**
          * Enable or disable the physics time capture
-         */        
+         */
         public set capturePhysicsTime(value: boolean) {
             if (value === this._capturePhysicsTime) {
                 return;
@@ -252,12 +252,12 @@ module BABYLON {
             this._capturePhysicsTime = value;
 
             if (value) {
-                this._onBeforePhysicsObserver = this.scene.onBeforePhysicsObservable.add(()=>{                    
+                this._onBeforePhysicsObserver = this.scene.onBeforePhysicsObservable.add(() => {
                     Tools.StartPerformanceCounter("Physics");
                     this._physicsTime.beginMonitoring();
                 });
 
-                this._onAfterPhysicsObserver = this.scene.onAfterPhysicsObservable.add(()=>{                                        
+                this._onAfterPhysicsObserver = this.scene.onAfterPhysicsObservable.add(() => {
                     Tools.EndPerformanceCounter("Physics");
                     this._physicsTime.endMonitoring();
                 });
@@ -268,7 +268,7 @@ module BABYLON {
                 this.scene.onAfterPhysicsObservable.remove(this._onAfterPhysicsObserver);
                 this._onAfterPhysicsObserver = null;
             }
-        }      
+        }
 
 
         /**
@@ -283,11 +283,11 @@ module BABYLON {
          */
         public get captureAnimationsTime(): boolean {
             return this._captureAnimationsTime;
-        }        
+        }
 
         /**
          * Enable or disable the animations time capture
-         */        
+         */
         public set captureAnimationsTime(value: boolean) {
             if (value === this._captureAnimationsTime) {
                 return;
@@ -296,74 +296,74 @@ module BABYLON {
             this._captureAnimationsTime = value;
 
             if (value) {
-                this._onAfterAnimationsObserver = this.scene.onAfterAnimationsObservable.add(()=>{                    
+                this._onAfterAnimationsObserver = this.scene.onAfterAnimationsObservable.add(() => {
                     this._animationsTime.endMonitoring();
                 });
             } else {
                 this.scene.onAfterAnimationsObservable.remove(this._onAfterAnimationsObserver);
                 this._onAfterAnimationsObserver = null;
             }
-        }              
-        
+        }
+
         /**
          * Gets the perf counter used for frame time capture
          */
         public get frameTimeCounter(): PerfCounter {
             return this._frameTime;
-        }               
-       
+        }
+
         /**
          * Gets the frame time capture status
-         */        
+         */
         public get captureFrameTime(): boolean {
             return this._captureFrameTime;
-        }        
+        }
 
         /**
          * Enable or disable the frame time capture
-         */        
+         */
         public set captureFrameTime(value: boolean) {
             this._captureFrameTime = value;
-        }       
-     
+        }
+
         /**
          * Gets the perf counter used for inter-frames time capture
          */
         public get interFrameTimeCounter(): PerfCounter {
             return this._interFrameTime;
-        }               
-       
+        }
+
         /**
          * Gets the inter-frames time capture status
-         */        
+         */
         public get captureInterFrameTime(): boolean {
             return this._captureInterFrameTime;
-        }        
+        }
 
         /**
          * Enable or disable the inter-frames time capture
-         */        
+         */
         public set captureInterFrameTime(value: boolean) {
             this._captureInterFrameTime = value;
-        }     
+        }
 
         /**
          * Gets the perf counter used for render time capture
          */
         public get renderTimeCounter(): PerfCounter {
             return this._renderTime;
-        }               
-       
+        }
+
         /**
          * Gets the render time capture status
-         */        
+         */
         public get captureRenderTime(): boolean {
             return this._captureRenderTime;
-        }        
+        }
 
         /**
          * Enable or disable the render time capture
-         */        
+         */
         public set captureRenderTime(value: boolean) {
             if (value === this._captureRenderTime) {
                 return;
@@ -372,30 +372,37 @@ module BABYLON {
             this._captureRenderTime = value;
 
             if (value) {
-                this._onBeforeDrawPhaseObserver = this.scene.onBeforeDrawPhaseObservable.add(()=>{
-                    this._renderTime.beginMonitoring(); 
+                this._onBeforeDrawPhaseObserver = this.scene.onBeforeDrawPhaseObservable.add(() => {
+                    this._renderTime.beginMonitoring();
                     Tools.StartPerformanceCounter("Main render");
                 });
 
-                this._onAfterDrawPhaseObserver = this.scene.onAfterDrawPhaseObservable.add(()=>{
-                    this._renderTime.endMonitoring(false); 
+                this._onAfterDrawPhaseObserver = this.scene.onAfterDrawPhaseObservable.add(() => {
+                    this._renderTime.endMonitoring(false);
                     Tools.EndPerformanceCounter("Main render");
-                });                
+                });
             } else {
                 this.scene.onBeforeDrawPhaseObservable.remove(this._onBeforeDrawPhaseObserver);
                 this._onBeforeDrawPhaseObserver = null;
                 this.scene.onAfterDrawPhaseObservable.remove(this._onAfterDrawPhaseObserver);
                 this._onAfterDrawPhaseObserver = null;
             }
-        }            
+        }
 
         /**
-         * Gets the perf counter used for frame time capture
+         * Gets the perf counter used for draw calls
          */
         public get drawCallsCounter(): PerfCounter {
             return this.scene.getEngine()._drawCalls;
-        }            
-    
+        }
+
+        /**
+         * Gets the perf counter used for texture collisions 
+         */
+        public get textureCollisionsCounter(): PerfCounter {
+            return this.scene.getEngine()._textureCollisions;
+        }
+
         public constructor(public scene: Scene) {
             // Before render
             this._onBeforeAnimationsObserver = scene.onBeforeAnimationsObservable.add(() => {
@@ -410,11 +417,11 @@ module BABYLON {
                 if (this._captureFrameTime) {
                     Tools.StartPerformanceCounter("Scene rendering");
                     this._frameTime.beginMonitoring();
-                }   
+                }
 
                 if (this._captureInterFrameTime) {
-                    this._interFrameTime.endMonitoring(); 
-                }                   
+                    this._interFrameTime.endMonitoring();
+                }
 
                 if (this._captureParticlesRenderTime) {
                     this._particlesRenderTime.fetchNewFrame();
@@ -429,22 +436,23 @@ module BABYLON {
                 }
 
                 this.scene.getEngine()._drawCalls.fetchNewFrame();
+                this.scene.getEngine()._textureCollisions.fetchNewFrame();
             });
 
             // After render
             this._onAfterRenderObserver = scene.onAfterRenderObservable.add(() => {
                 if (this._captureFrameTime) {
                     Tools.EndPerformanceCounter("Scene rendering");
-                    this._frameTime.endMonitoring();                    
+                    this._frameTime.endMonitoring();
                 }
 
                 if (this._captureRenderTime) {
-                    this._renderTime.endMonitoring(false);                    
-                }                
+                    this._renderTime.endMonitoring(false);
+                }
 
                 if (this._captureInterFrameTime) {
-                    this._interFrameTime.beginMonitoring();  
-                }                
+                    this._interFrameTime.beginMonitoring();
+                }
             });
         }
 
@@ -459,11 +467,11 @@ module BABYLON {
             this._onAfterActiveMeshesEvaluationObserver = null;
 
             this.scene.OnBeforeRenderTargetsRenderObservable.remove(this._onBeforeRenderTargetsRenderObserver);
-            this._onBeforeRenderTargetsRenderObserver = null;   
-            
+            this._onBeforeRenderTargetsRenderObserver = null;
+
             this.scene.OnAfterRenderTargetsRenderObservable.remove(this._onAfterRenderTargetsRenderObserver);
-            this._onAfterRenderTargetsRenderObserver = null;      
-            
+            this._onAfterRenderTargetsRenderObserver = null;
+
             this.scene.onBeforeAnimationsObservable.remove(this._onBeforeAnimationsObserver);
             this._onBeforeAnimationsObserver = null;
 
@@ -471,29 +479,29 @@ module BABYLON {
             this._onBeforeParticlesRenderingObserver = null;
 
             this.scene.onAfterParticlesRenderingObservable.remove(this._onAfterParticlesRenderingObserver);
-            this._onAfterParticlesRenderingObserver = null;         
-            
+            this._onAfterParticlesRenderingObserver = null;
+
             this.scene.onBeforeSpritesRenderingObservable.remove(this._onBeforeSpritesRenderingObserver);
             this._onBeforeSpritesRenderingObserver = null;
 
             this.scene.onAfterSpritesRenderingObservable.remove(this._onAfterSpritesRenderingObserver);
-            this._onAfterSpritesRenderingObserver = null;       
-            
+            this._onAfterSpritesRenderingObserver = null;
+
             this.scene.onBeforeDrawPhaseObservable.remove(this._onBeforeDrawPhaseObserver);
             this._onBeforeDrawPhaseObserver = null;
 
             this.scene.onAfterDrawPhaseObservable.remove(this._onAfterDrawPhaseObserver);
-            this._onAfterDrawPhaseObserver = null;    
-            
+            this._onAfterDrawPhaseObserver = null;
+
             this.scene.onBeforePhysicsObservable.remove(this._onBeforePhysicsObserver);
             this._onBeforePhysicsObserver = null;
 
             this.scene.onAfterPhysicsObservable.remove(this._onAfterPhysicsObserver);
-            this._onAfterPhysicsObserver = null;    
-            
+            this._onAfterPhysicsObserver = null;
+
             this.scene.onAfterAnimationsObservable.remove(this._onAfterAnimationsObserver);
-            this._onAfterAnimationsObserver = null;            
-                
+            this._onAfterAnimationsObserver = null;
+
             (<any>this.scene) = null;
         }
     }

+ 64 - 25
src/Loading/babylon.sceneLoader.ts

@@ -1,4 +1,13 @@
 module BABYLON {
+    export class SceneLoaderProgressEvent {
+        constructor(public readonly lengthComputable: boolean, public readonly loaded: number, public readonly total: number) {
+        }
+
+        public static FromProgressEvent(event: ProgressEvent): SceneLoaderProgressEvent {
+            return new SceneLoaderProgressEvent(event.lengthComputable, event.loaded, event.total);
+        }
+    }
+
     export interface ISceneLoaderPluginExtensions {
         [extension: string]: {
             isBinary: boolean;
@@ -23,8 +32,8 @@
     export interface ISceneLoaderPluginAsync {
         name: string;
         extensions: string | ISceneLoaderPluginExtensions;
-        importMeshAsync: (meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
-        loadAsync: (scene: Scene, data: string, rootUrl: string, onSuccess?: () => void, onProgress?: (event: ProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        importMeshAsync: (meshesNames: any, scene: Scene, data: any, rootUrl: string, onSuccess?: (meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
+        loadAsync: (scene: Scene, data: string, rootUrl: string, onSuccess?: () => void, onProgress?: (event: SceneLoaderProgressEvent) => void, onError?: (message: string, exception?: any) => void) => void;
         canDirectLoad?: (data: string) => boolean;
         rewriteRootURL?: (rootUrl: string, responseURL?: string) => string;
     }
@@ -146,9 +155,9 @@
             return null;
         }
 
-        private static _loadData(rootUrl: string, sceneFilename: string, scene: Scene, onSuccess: (plugin: ISceneLoaderPlugin | ISceneLoaderPluginAsync, data: any, responseURL?: string) => void, onProgress: ((event: ProgressEvent) => void) | undefined, onError: (message: string, exception?: any) => void, pluginExtension: Nullable<string>): ISceneLoaderPlugin | ISceneLoaderPluginAsync {
-            var directLoad = SceneLoader._getDirectLoad(sceneFilename);
-            var registeredPlugin = pluginExtension ? SceneLoader._getPluginForExtension(pluginExtension) : (directLoad ? SceneLoader._getPluginForDirectLoad(sceneFilename) : SceneLoader._getPluginForFilename(sceneFilename));
+        private static _loadData(rootUrl: string, sceneFilename: string, scene: Scene, onSuccess: (plugin: ISceneLoaderPlugin | ISceneLoaderPluginAsync, data: any, responseURL?: string) => void, onProgress: ((event: SceneLoaderProgressEvent) => void) | undefined, onError: (message: string, exception?: any) => void, onDispose: () => void, pluginExtension: Nullable<string>): ISceneLoaderPlugin | ISceneLoaderPluginAsync {
+            let directLoad = SceneLoader._getDirectLoad(sceneFilename);
+            let registeredPlugin = pluginExtension ? SceneLoader._getPluginForExtension(pluginExtension) : (directLoad ? SceneLoader._getPluginForDirectLoad(sceneFilename) : SceneLoader._getPluginForFilename(sceneFilename));
 
             let plugin: ISceneLoaderPlugin | ISceneLoaderPluginAsync;
             if ((registeredPlugin.plugin as ISceneLoaderPluginFactory).createPlugin) {
@@ -158,12 +167,12 @@
                 plugin = <any>registeredPlugin.plugin;
             }
 
-            var useArrayBuffer = registeredPlugin.isBinary;
-            var database: Database;
+            let useArrayBuffer = registeredPlugin.isBinary;
+            let database: Database;
 
             SceneLoader.OnPluginActivatedObservable.notifyObservers(plugin);
 
-            var dataCallback = (data: any, responseURL?: string) => {
+            let dataCallback = (data: any, responseURL?: string) => {
                 if (scene.isDisposed) {
                     onError("Scene has been disposed");
                     return;
@@ -174,11 +183,32 @@
                 onSuccess(plugin, data, responseURL);
             };
 
-            var manifestChecked = (success: any) => {
-                Tools.LoadFile(rootUrl + sceneFilename, dataCallback, onProgress, database, useArrayBuffer, (request, exception) => {
+            let request: Nullable<IFileRequest> = null;
+            let pluginDisposed = false;
+            let onDisposeObservable = (plugin as any).onDisposeObservable as Observable<ISceneLoaderPlugin | ISceneLoaderPluginAsync>;
+            if (onDisposeObservable) {
+                onDisposeObservable.add(() => {
+                    pluginDisposed = true;
+
                     if (request) {
-                        onError(request.status + " " + request.statusText, exception);
+                        request.abort();
+                        request = null;
                     }
+
+                    onDispose();
+                });
+            }
+
+            let manifestChecked = () => {
+                if (pluginDisposed) {
+                    return;
+                }
+
+                let url = rootUrl + sceneFilename;
+                request = Tools.LoadFile(url, dataCallback, onProgress ? event => {
+                    onProgress(SceneLoaderProgressEvent.FromProgressEvent(event));
+                }: undefined, database, useArrayBuffer, (request, exception) => {
+                    onError("Failed to load scene." + (exception ? "" : " " + exception.message), exception);
                 });
             };
 
@@ -193,17 +223,17 @@
                     database = new Database(rootUrl + sceneFilename, manifestChecked);
                 }
                 else {
-                    manifestChecked(true);
+                    manifestChecked();
                 }
             }
             // Loading file from disk via input file or drag'n'drop
             else {
-                var fileOrString = <any>sceneFilename;
+                let fileOrString = <any>sceneFilename;
 
                 if (fileOrString.name) { // File
-                    Tools.ReadFile(fileOrString, dataCallback, onProgress, useArrayBuffer);
+                    request = Tools.ReadFile(fileOrString, dataCallback, onProgress, useArrayBuffer);
                 } else if (FilesInput.FilesToLoad[sceneFilename]) {
-                    Tools.ReadFile(FilesInput.FilesToLoad[sceneFilename], dataCallback, onProgress, useArrayBuffer);
+                    request = Tools.ReadFile(FilesInput.FilesToLoad[sceneFilename], dataCallback, onProgress, useArrayBuffer);
                 } else {
                     onError("Unable to find file named " + sceneFilename);
                 }
@@ -249,7 +279,7 @@
         * @param onProgress a callback with a progress event for each file being loaded
         * @param onError a callback with the scene, a message, and possibly an exception when import fails
         */
-        public static ImportMesh(meshNames: any, rootUrl: string, sceneFilename: string, scene: Scene, onSuccess: Nullable<(meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void> = null, onProgress: Nullable<(event: ProgressEvent) => void> = null, onError: Nullable<(scene: Scene, message: string, exception?: any) => void> = null, pluginExtension: Nullable<string> = null): Nullable<ISceneLoaderPlugin | ISceneLoaderPluginAsync> {
+        public static ImportMesh(meshNames: any, rootUrl: string, sceneFilename: string, scene: Scene, onSuccess: Nullable<(meshes: AbstractMesh[], particleSystems: ParticleSystem[], skeletons: Skeleton[]) => void> = null, onProgress: Nullable<(event: SceneLoaderProgressEvent) => void> = null, onError: Nullable<(scene: Scene, message: string, exception?: any) => void> = null, pluginExtension: Nullable<string> = null): Nullable<ISceneLoaderPlugin | ISceneLoaderPluginAsync> {
             if (sceneFilename.substr && sceneFilename.substr(0, 1) === "/") {
                 Tools.Error("Wrong sceneFilename parameter");
                 return null;
@@ -258,6 +288,10 @@
             var loadingToken = {};
             scene._addPendingData(loadingToken);
 
+            var disposeHandler = () => {
+                scene._removePendingData(loadingToken);
+            };
+
             var errorHandler = (message: string, exception?: any) => {
                 let errorMessage = "Unable to import meshes from " + rootUrl + sceneFilename + ": " + message;
 
@@ -268,10 +302,10 @@
                     // should the exception be thrown?
                 }
 
-                scene._removePendingData(loadingToken);
+                disposeHandler();
             };
 
-            var progressHandler = onProgress ? (event: ProgressEvent) => {
+            var progressHandler = onProgress ? (event: SceneLoaderProgressEvent) => {
                 try {
                     onProgress(event);
                 }
@@ -320,7 +354,7 @@
                         successHandler(meshes, particleSystems, skeletons);
                     }, progressHandler, errorHandler);
                 }
-            }, progressHandler, errorHandler, pluginExtension);
+            }, progressHandler, errorHandler, disposeHandler, pluginExtension);
         }
 
         /**
@@ -332,7 +366,7 @@
         * @param onProgress a callback with a progress event for each file being loaded
         * @param onError a callback with the scene, a message, and possibly an exception when import fails
         */
-        public static Load(rootUrl: string, sceneFilename: any, engine: Engine, onSuccess: Nullable<(scene: Scene) => void> = null, onProgress: Nullable<(event: ProgressEvent) => void> = null, onError: Nullable<(scene: Scene, message: string, exception?: any) => void> = null, pluginExtension: Nullable<string> = null): Nullable<ISceneLoaderPlugin | ISceneLoaderPluginAsync> {
+        public static Load(rootUrl: string, sceneFilename: any, engine: Engine, onSuccess: Nullable<(scene: Scene) => void> = null, onProgress: Nullable<(event: SceneLoaderProgressEvent) => void> = null, onError: Nullable<(scene: Scene, message: string, exception?: any) => void> = null, pluginExtension: Nullable<string> = null): Nullable<ISceneLoaderPlugin | ISceneLoaderPluginAsync> {
             return SceneLoader.Append(rootUrl, sceneFilename, new Scene(engine), onSuccess, onProgress, onError, pluginExtension);
         }
 
@@ -345,7 +379,7 @@
         * @param onProgress a callback with a progress event for each file being loaded
         * @param onError a callback with the scene, a message, and possibly an exception when import fails
         */
-        public static Append(rootUrl: string, sceneFilename: any, scene: Scene, onSuccess: Nullable<(scene: Scene) => void> = null, onProgress: Nullable<(event: ProgressEvent) => void> = null, onError: Nullable<(scene: Scene, message: string, exception?: any) => void> = null, pluginExtension: Nullable<string> = null): Nullable<ISceneLoaderPlugin | ISceneLoaderPluginAsync> {
+        public static Append(rootUrl: string, sceneFilename: any, scene: Scene, onSuccess: Nullable<(scene: Scene) => void> = null, onProgress: Nullable<(event: SceneLoaderProgressEvent) => void> = null, onError: Nullable<(scene: Scene, message: string, exception?: any) => void> = null, pluginExtension: Nullable<string> = null): Nullable<ISceneLoaderPlugin | ISceneLoaderPluginAsync> {
             if (sceneFilename.substr && sceneFilename.substr(0, 1) === "/") {
                 Tools.Error("Wrong sceneFilename parameter");
                 return null;
@@ -358,6 +392,11 @@
             var loadingToken = {};
             scene._addPendingData(loadingToken);
 
+            var disposeHandler = () => {
+                scene._removePendingData(loadingToken);
+                scene.getEngine().hideLoadingUI();
+            };
+
             var errorHandler = (message: Nullable<string>, exception?: any) => {
                 let errorMessage = "Unable to load from " + rootUrl + sceneFilename + (message ? ": " + message : "");
                 if (onError) {
@@ -366,11 +405,11 @@
                     Tools.Error(errorMessage);
                     // should the exception be thrown?
                 }
-                scene._removePendingData(loadingToken);
-                scene.getEngine().hideLoadingUI();
+
+                disposeHandler();
             };
 
-            var progressHandler = onProgress ? (event: ProgressEvent) => {
+            var progressHandler = onProgress ? (event: SceneLoaderProgressEvent) => {
                 try {
                     onProgress(event);
                 }
@@ -414,7 +453,7 @@
                         scene.getEngine().hideLoadingUI();
                     });
                 }
-            }, progressHandler, errorHandler, pluginExtension);
+            }, progressHandler, errorHandler, disposeHandler, pluginExtension);
         }
     };
 }

+ 8 - 1
src/Materials/Textures/babylon.colorGradingTexture.ts

@@ -169,7 +169,14 @@ module BABYLON {
                 }
             }
 
-            Tools.LoadFile(this.url, callback);
+            let scene = this.getScene();
+            if (scene) {
+                scene._loadFile(this.url, callback);
+            }
+            else {
+                this._engine._loadFile(this.url, callback);
+            }
+
             return this._texture;
         }
 

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

@@ -47,6 +47,7 @@ module BABYLON {
         public _depthStencilBuffer: Nullable<WebGLRenderbuffer>;
         public _MSAAFramebuffer: Nullable<WebGLFramebuffer>;
         public _MSAARenderBuffer: Nullable<WebGLRenderbuffer>;
+        public _attachments: Nullable<number[]>;
         public _cachedCoordinatesMode: Nullable<number>;
         public _cachedWrapU: Nullable<number>;
         public _cachedWrapV: Nullable<number>;
@@ -239,4 +240,4 @@ module BABYLON {
             }
         }
     }
-}
+}

+ 26 - 22
src/Materials/Textures/babylon.multiRenderTarget.ts

@@ -1,12 +1,14 @@
 module BABYLON {
     export interface IMultiRenderTargetOptions {
-        generateMipMaps: boolean,
-        types: number[],
-        samplingModes: number[],
-        generateDepthBuffer: boolean,
-        generateStencilBuffer: boolean,
-        generateDepthTexture: boolean,
-        textureCount: number
+        generateMipMaps?: boolean,
+        types?: number[],
+        samplingModes?: number[],
+        generateDepthBuffer?: boolean,
+        generateStencilBuffer?: boolean,
+        generateDepthTexture?: boolean,
+        textureCount?: number,
+        doNotChangeAspectRatio?: boolean,
+        defaultType?: number
     };
     export class MultiRenderTarget extends RenderTargetTexture {
 
@@ -43,12 +45,10 @@ module BABYLON {
             }
         }
 
-        constructor(name: string, size: any, count: number, scene: Scene, options?: any) {
-            options = options || {};
-
-            var generateMipMaps = options.generateMipMaps ? options.generateMipMaps : false;
-            var generateDepthTexture = options.generateDepthTexture ? options.generateDepthTexture : false;
-            var doNotChangeAspectRatio = options.doNotChangeAspectRatio === undefined ? true : options.doNotChangeAspectRatio;
+        constructor(name: string, size: any, count: number, scene: Scene, options?: IMultiRenderTargetOptions) {
+            var generateMipMaps = options && options.generateMipMaps ? options.generateMipMaps : false;
+            var generateDepthTexture = options && options.generateDepthTexture ? options.generateDepthTexture : false;
+            var doNotChangeAspectRatio = !options || options.doNotChangeAspectRatio === undefined ? true : options.doNotChangeAspectRatio;
 
             super(name, size, scene, generateMipMaps, doNotChangeAspectRatio);
 
@@ -63,21 +63,21 @@ module BABYLON {
             var samplingModes = [];
 
             for (var i = 0; i < count; i++) {
-                if (options.types && options.types[i]) {
+                if (options && options.types && options.types[i] !== undefined) {
                     types.push(options.types[i]);
                 } else {
-                    types.push(Engine.TEXTURETYPE_FLOAT);
+                    types.push(options && options.defaultType ? options.defaultType : Engine.TEXTURETYPE_UNSIGNED_INT);
                 }
 
-                if (options.samplingModes && options.samplingModes[i]) {
+                if (options && options.samplingModes && options.samplingModes[i] !== undefined) {
                     samplingModes.push(options.samplingModes[i]);
                 } else {
                     samplingModes.push(Texture.BILINEAR_SAMPLINGMODE);
                 }
             }
 
-            var generateDepthBuffer = options.generateDepthBuffer === undefined ? true : options.generateDepthBuffer;
-            var generateStencilBuffer = options.generateStencilBuffer === undefined ? false : options.generateStencilBuffer;
+            var generateDepthBuffer = !options || options.generateDepthBuffer === undefined ? true : options.generateDepthBuffer;
+            var generateStencilBuffer = !options || options.generateStencilBuffer === undefined ? false : options.generateStencilBuffer;
 
             this._size = size;
             this._multiRenderTargetOptions = {
@@ -132,9 +132,7 @@ module BABYLON {
                 return;
             }
 
-            for (var i = 0; i < this._internalTextures.length; i++) {
-                this._samples = this._engine.updateRenderTargetTextureSampleCount(this._internalTextures[i], value);
-            }
+            this._samples = this._engine.updateMultipleRenderTargetTextureSampleCount(this._internalTextures, value);
         }
 
         public resize(size: any) {
@@ -143,6 +141,12 @@ module BABYLON {
             this._createInternalTextures();
         }
 
+        protected unbindFrameBuffer(engine: Engine, faceIndex: number): void {
+            engine.unBindMultiColorAttachmentFramebuffer(this._internalTextures, this.isCube, () => {
+                this.onAfterRenderObservable.notifyObservers(faceIndex);
+            });
+        }
+
         public dispose(): void {
             this.releaseInternalTextures();
 
@@ -162,4 +166,4 @@ module BABYLON {
             }
         }
     }
-}
+}

+ 13 - 5
src/Materials/Textures/babylon.renderTargetTexture.ts

@@ -445,7 +445,7 @@
                         for (var subIndex = 0; subIndex < mesh.subMeshes.length; subIndex++) {
                             var subMesh = mesh.subMeshes[subIndex];
                             scene._activeIndices.addCount(subMesh.indexCount, false);
-                            this._renderingManager.dispatch(subMesh);
+                            this._renderingManager.dispatch(subMesh, mesh);
                         }
                     }
                 }
@@ -495,6 +495,15 @@
             return Math.min(Tools.FloorPOT(renderDimension), curved);
         }
 
+        protected unbindFrameBuffer(engine: Engine, faceIndex: number): void {
+            if (!this._texture) {
+                return;
+            }
+            engine.unBindFramebuffer(this._texture, this.isCube, () => {
+                this.onAfterRenderObservable.notifyObservers(faceIndex);
+            });
+        }
+
         private renderToTarget(faceIndex: number, currentRenderList: AbstractMesh[], currentRenderListLength: number, useCameraPostProcess: boolean, dumpForDebug: boolean): void {
             var scene = this.getScene();
             
@@ -559,9 +568,8 @@
                     }
                 }
 
-                engine.unBindFramebuffer(this._texture, this.isCube, () => {
-                    this.onAfterRenderObservable.notifyObservers(faceIndex);
-                });
+            this.unbindFrameBuffer(engine, faceIndex);
+
             } else {
                 this.onAfterRenderObservable.notifyObservers(faceIndex);
             }
@@ -702,4 +710,4 @@
             }
         }
     }
-}
+}

+ 1 - 1
src/Materials/Textures/babylon.videoTexture.ts

@@ -148,7 +148,7 @@
                     if (onReady) {
                         onReady(new VideoTexture("video", video, scene, true, true));
                     }
-                }, function (e: DOMException) {
+                }, function (e: MediaStreamError) {
                     Tools.Error(e.name);
                 });
             }

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

@@ -303,7 +303,7 @@
             }
 
             // Vertex shader
-            Tools.LoadFile(vertexShaderUrl + ".vertex.fx", callback);
+            this._engine._loadFile(vertexShaderUrl + ".vertex.fx", callback);
         }
 
         public _loadFragmentShader(fragment: any, callback: (data: any) => void): void {
@@ -343,7 +343,7 @@
             }
 
             // Fragment shader
-            Tools.LoadFile(fragmentShaderUrl + ".fragment.fx", callback);
+            this._engine._loadFile(fragmentShaderUrl + ".fragment.fx", callback);
         }
 
         private _dumpShadersSource(vertexCode: string, fragmentCode: string, defines: string): void {
@@ -491,7 +491,7 @@
                 } else {
                     var includeShaderUrl = Engine.ShadersRepository + "ShadersInclude/" + includeFile + ".fx";
 
-                    Tools.LoadFile(includeShaderUrl, (fileContent) => {
+                    this._engine._loadFile(includeShaderUrl, (fileContent) => {
                         Effect.IncludesShadersStore[includeFile] = fileContent as string;
                         this._processIncludes(<string>returnValue, callback);
                     });
@@ -544,6 +544,11 @@
             this._prepareEffect();
         }
 
+        public getSpecificUniformLocations(names: string[]): Nullable<WebGLUniformLocation>[] {
+            let engine = this._engine;
+            return engine.getUniforms(this._program, names);
+        }
+
         public _prepareEffect() {
             let attributesNames = this._attributesNames;
             let defines = this.defines;
@@ -553,7 +558,7 @@
             var previousProgram = this._program;
 
             try {
-                var engine = this._engine;
+                let engine = this._engine;
 
                 if (this._vertexSourceCodeOverride && this._fragmentSourceCodeOverride) {
                     this._program = engine.createRawShaderProgram(this._vertexSourceCodeOverride, this._fragmentSourceCodeOverride, undefined, this._transformFeedbackVaryings);
@@ -758,7 +763,7 @@
 
         public bindUniformBuffer(buffer: WebGLBuffer, name: string): void {
             let bufferName = this._uniformBuffersNames[name];
-            if (Effect._baseCache[bufferName] === buffer) {
+            if (bufferName === undefined || Effect._baseCache[bufferName] === buffer) {
                 return;
             }
             Effect._baseCache[bufferName] = buffer;
@@ -769,6 +774,18 @@
             this._engine.bindUniformBlock(this._program, blockName, index);
         }
 
+        public setInt(uniformName: string, value: number): Effect {
+            var cache = this._valueCache[uniformName];
+            if (cache !== undefined && cache === value)
+                return this;
+
+            this._valueCache[uniformName] = value;
+
+            this._engine.setInt(this.getUniform(uniformName), value);
+
+            return this;
+        }
+
         public setIntArray(uniformName: string, array: Int32Array): Effect {
             this._valueCache[uniformName] = null;
             this._engine.setIntArray(this.getUniform(uniformName), array);

+ 24 - 23
src/Materials/babylon.materialHelper.ts

@@ -3,10 +3,10 @@
 
         public static BindEyePosition(effect: Effect, scene: Scene): void {
             if (scene._forcedViewPosition) {
-                effect.setVector3("vEyePosition", scene._forcedViewPosition);            
+                effect.setVector3("vEyePosition", scene._forcedViewPosition);
                 return;
             }
-            effect.setVector3("vEyePosition", scene._mirroredCameraPosition ? scene._mirroredCameraPosition : scene.activeCamera!.globalPosition);            
+            effect.setVector3("vEyePosition", scene._mirroredCameraPosition ? scene._mirroredCameraPosition : scene.activeCamera!.globalPosition);
         }
 
         public static PrepareDefinesForMergedUV(texture: BaseTexture, defines: any, key: string): void {
@@ -57,13 +57,13 @@
             if (defines["DEPTHPREPASS"] !== !engine.getColorWrite()) {
                 defines["DEPTHPREPASS"] = !defines["DEPTHPREPASS"];
                 changed = true;
-            }            
+            }
 
             if (defines["INSTANCES"] !== useInstances) {
                 defines["INSTANCES"] = useInstances;
                 changed = true;
             }
-            
+
             if (changed) {
                 defines.markAsUnprocessed();
             }
@@ -76,7 +76,7 @@
 
             defines._normals = defines._needNormals;
             defines._uvs = defines._needUVs;
-            
+
             defines["NORMAL"] = (defines._needNormals && mesh.isVerticesDataPresent(VertexBuffer.NormalKind));
 
             if (defines._needNormals && mesh.isVerticesDataPresent(VertexBuffer.TangentKind)) {
@@ -111,7 +111,7 @@
                 var manager = (<Mesh>mesh).morphTargetManager;
                 if (manager) {
                     defines["MORPHTARGETS_TANGENT"] = manager.supportsTangents && defines["TANGENT"];
-                    defines["MORPHTARGETS_NORMAL"] = manager.supportsNormals && defines["NORMAL"] ;
+                    defines["MORPHTARGETS_NORMAL"] = manager.supportsNormals && defines["NORMAL"];
                     defines["MORPHTARGETS"] = (manager.numInfluencers > 0);
                     defines["NUM_MORPH_INFLUENCERS"] = manager.numInfluencers;
                 } else {
@@ -146,7 +146,7 @@
                     }
 
                     defines["LIGHT" + lightIndex] = true;
-                    
+
                     defines["SPOTLIGHT" + lightIndex] = false;
                     defines["HEMILIGHT" + lightIndex] = false;
                     defines["POINTLIGHT" + lightIndex] = false;
@@ -184,7 +184,7 @@
                         }
                     }
 
-                    if (light.lightmapMode != Light.LIGHTMAP_DEFAULT ) {
+                    if (light.lightmapMode != Light.LIGHTMAP_DEFAULT) {
                         lightmapMode = true;
                         defines["LIGHTMAPEXCLUDED" + lightIndex] = true;
                         defines["LIGHTMAPNOSPECULAR" + lightIndex] = (light.lightmapMode == Light.LIGHTMAP_SHADOWSONLY);
@@ -208,7 +208,7 @@
                     defines["LIGHT" + index] = false;
                     defines["HEMILIGHT" + lightIndex] = false;
                     defines["POINTLIGHT" + lightIndex] = false;
-                    defines["DIRLIGHT" + lightIndex] = false;                    
+                    defines["DIRLIGHT" + lightIndex] = false;
                     defines["SPOTLIGHT" + lightIndex] = false;
                     defines["SHADOW" + lightIndex] = false;
                 }
@@ -220,9 +220,9 @@
                 needRebuild = true;
             }
 
-            defines["SHADOWFLOAT"] = shadowEnabled && 
-                                    ((caps.textureFloatRender && caps.textureFloatLinearFiltering) ||
-                                         (caps.textureHalfFloatRender && caps.textureHalfFloatLinearFiltering));
+            defines["SHADOWFLOAT"] = shadowEnabled &&
+                ((caps.textureFloatRender && caps.textureFloatLinearFiltering) ||
+                    (caps.textureHalfFloatRender && caps.textureHalfFloatLinearFiltering));
             defines["LIGHTMAPEXCLUDED"] = lightmapMode;
 
             if (needRebuild) {
@@ -370,29 +370,30 @@
         }
 
         public static BindLights(scene: Scene, mesh: AbstractMesh, effect: Effect, defines: any, maxSimultaneousLights = 4, usePhysicalLightFalloff = false) {
-            var lightIndex = 0;
-            for (var light of mesh._lightSources) {
+            let len = Math.min(mesh._lightSources.length, maxSimultaneousLights);
+
+            for (var i = 0; i < len; i++) {
+
+                let light = mesh._lightSources[i];
+                let iAsString = i.toString();
+
                 let scaledIntensity = light.getScaledIntensity();
-                light._uniformBuffer.bindToEffect(effect, "Light" + lightIndex);
+                light._uniformBuffer.bindToEffect(effect, "Light" + i);
 
-                MaterialHelper.BindLightProperties(light, effect, lightIndex);
+                MaterialHelper.BindLightProperties(light, effect, i);
 
                 light.diffuse.scaleToRef(scaledIntensity, Tmp.Color3[0]);
-                light._uniformBuffer.updateColor4("vLightDiffuse", Tmp.Color3[0], usePhysicalLightFalloff ? light.radius : light.range, lightIndex + "");
+                light._uniformBuffer.updateColor4("vLightDiffuse", Tmp.Color3[0], usePhysicalLightFalloff ? light.radius : light.range, iAsString);
                 if (defines["SPECULARTERM"]) {
                     light.specular.scaleToRef(scaledIntensity, Tmp.Color3[1]);
-                    light._uniformBuffer.updateColor3("vLightSpecular", Tmp.Color3[1], lightIndex + "");
+                    light._uniformBuffer.updateColor3("vLightSpecular", Tmp.Color3[1], iAsString);
                 }
 
                 // Shadows
                 if (scene.shadowsEnabled) {
-                    this.BindLightShadow(light, scene, mesh, lightIndex + "", effect);
+                    this.BindLightShadow(light, scene, mesh, iAsString, effect);
                 }
                 light._uniformBuffer.update();
-                lightIndex++;
-
-                if (lightIndex === maxSimultaneousLights)
-                    break;
             }
         }
 

+ 14 - 1
src/Materials/babylon.shaderMaterial.ts

@@ -5,6 +5,7 @@
         private _textures: { [name: string]: Texture } = {};
         private _textureArrays: { [name: string]: Texture[] } = {};
         private _floats: { [name: string]: number } = {};
+        private _ints: { [name: string]: number } = {};
         private _floatsArrays: { [name: string]: number[] } = {};
         private _colors3: { [name: string]: Color3 } = {};
         private _colors3Arrays: { [name: string]: number[] } = {};
@@ -81,6 +82,13 @@
             return this;
         }
 
+        public setInt(name: string, value: number): ShaderMaterial {
+            this._checkUniform(name);
+            this._ints[name] = value;
+
+            return this;
+        }
+
         public setFloats(name: string, value: number[]): ShaderMaterial {
             this._checkUniform(name);
             this._floatsArrays[name] = value;
@@ -322,12 +330,17 @@
                     this._effect.setTextureArray(name, this._textureArrays[name]);
                 }
 
+                // Int    
+                for (name in this._ints) {
+                    this._effect.setInt(name, this._ints[name]);
+                }
+
                 // Float    
                 for (name in this._floats) {
                     this._effect.setFloat(name, this._floats[name]);
                 }
 
-                // Float s   
+                // Floats   
                 for (name in this._floatsArrays) {
                     this._effect.setArray(name, this._floatsArrays[name]);
                 }

+ 5 - 5
src/Mesh/babylon.abstractMesh.ts

@@ -904,14 +904,14 @@
                 for (var descendant of descendants) {
                     let childMesh = <AbstractMesh>descendant;
 
+                    childMesh.computeWorldMatrix(true);
+
                     //make sure we have the needed params to get mix and max
                     if (!childMesh.getBoundingInfo || childMesh.getTotalVertices() === 0) {
                         continue;
                     }
 
-                    childMesh.computeWorldMatrix(true);
                     let childBoundingInfo = childMesh.getBoundingInfo();
-
                     let boundingBox = childBoundingInfo.boundingBox;
 
                     var minBox = boundingBox.minimumWorld;
@@ -1322,7 +1322,7 @@
             var index: number;
 
             // Action manager
-            if (this.actionManager) {
+            if (this.actionManager !== undefined && this.actionManager !== null) {
                 this.actionManager.dispose();
                 this.actionManager = null;
             }
@@ -1388,8 +1388,8 @@
             }
 
             // Octree
-            var sceneOctree = this.getScene().selectionOctree;
-            if (sceneOctree) {
+            const sceneOctree = this.getScene().selectionOctree;
+            if (sceneOctree !== undefined && sceneOctree !== null) {
                 var index = sceneOctree.dynamicContent.indexOf(this);
 
                 if (index !== -1) {

+ 5 - 4
src/Mesh/babylon.geometry.ts

@@ -557,7 +557,7 @@
             }
 
             scene._addPendingData(this);
-            Tools.LoadFile(this.delayLoadingFile, data => {
+            scene._loadFile(this.delayLoadingFile, data => {
                 if (!this._delayLoadingFunction) {
                     return;
                 }
@@ -578,7 +578,7 @@
                 if (onLoaded) {
                     onLoaded();
                 }
-            }, () => { }, scene.database);
+            }, undefined, true);
         }
 
         /**
@@ -1059,8 +1059,9 @@
             mesh.computeWorldMatrix(true);
 
             // Octree
-            if (scene['_selectionOctree']) {
-                scene['_selectionOctree'].addMesh(<AbstractMesh>mesh);
+            const sceneOctree = scene.selectionOctree;
+            if (sceneOctree !== undefined && sceneOctree !== null) {
+                sceneOctree.addMesh(<AbstractMesh>mesh);
             }
         }
 

+ 2 - 2
src/Mesh/babylon.mesh.ts

@@ -427,7 +427,7 @@
          * Returns a positive integer : the total number of vertices within the mesh geometry or zero if the mesh has no geometry.
          */
         public getTotalVertices(): number {
-            if (!this._geometry) {
+            if (this._geometry === null || this._geometry === undefined) {
                 return 0;
             }
             return this._geometry.getTotalVertices();
@@ -2104,7 +2104,7 @@
             serializationObject.scaling = this.scaling.asArray();
             serializationObject.localMatrix = this.getPivotMatrix().asArray();
 
-            serializationObject.isEnabled = this.isEnabled();
+            serializationObject.isEnabled = this.isEnabled(false);
             serializationObject.isVisible = this.isVisible;
             serializationObject.infiniteDistance = this.infiniteDistance;
             serializationObject.pickable = this.isPickable;

+ 40 - 39
src/Mesh/babylon.mesh.vertexData.ts

@@ -1,6 +1,5 @@
 module BABYLON {
-    export interface IGetSetVerticesData
-    {
+    export interface IGetSetVerticesData {
         isVerticesDataPresent(kind: string): boolean;
         getVerticesData(kind: string, copyWhenShared?: boolean, forceCopy?: boolean): Nullable<FloatArray>;
         getIndices(copyWhenShared?: boolean): Nullable<IndicesArray>;
@@ -346,7 +345,7 @@
                 if (length === 0 || length === other.length) {
                     return other;
                 }
-                
+
                 var padding = new Float32Array(length - other.length);
                 padding.fill(defaultValue);
                 return this._mergeElement(padding, other, length);
@@ -744,7 +743,7 @@
             var positions32 = new Float32Array(positions);
             var normals32 = new Float32Array(normals);
             var uvs32 = new Float32Array(uvs);
-            
+
             vertexData.indices = indices;
             vertexData.positions = positions32;
             vertexData.normals = normals32;
@@ -1401,10 +1400,10 @@
          * Creates the VertexData of the TiledGround.  
          */
         public static CreateTiledGround(options: { xmin: number, zmin: number, xmax: number, zmax: number, subdivisions?: { w: number; h: number; }, precision?: { w: number; h: number; } }): VertexData {
-            var xmin = options.xmin || -1.0;
-            var zmin = options.zmin || -1.0;
-            var xmax = options.xmax || 1.0;
-            var zmax = options.zmax || 1.0;
+            var xmin = (options.xmin !== undefined && options.xmin !== null) ? options.xmin : -1.0;
+            var zmin = (options.zmin !== undefined && options.zmin !== null) ? options.zmin : -1.0;
+            var xmax = (options.xmax !== undefined && options.xmax !== null) ? options.xmax : 1.0;
+            var zmax = (options.zmax !== undefined && options.zmax !== null) ? options.zmax : 1.0;
             var subdivisions = options.subdivisions || { w: 1, h: 1 };
             var precision = options.precision || { w: 1, h: 1 };
 
@@ -1494,7 +1493,7 @@
             var uvs = [];
             var row, col;
             var filter = options.colorFilter || new Color3(0.3, 0.59, 0.11);
-            
+
             // Vertices
             for (row = 0; row <= options.subdivisions; row++) {
                 for (col = 0; col <= options.subdivisions; col++) {
@@ -1659,8 +1658,8 @@
         /**
          * Re-creates the VertexData of the Polygon for sideOrientation.  
          */
-        public static CreatePolygon(polygon: Mesh, sideOrientation: number, fUV?:Vector4[], fColors?: Color4[], frontUVs?: Vector4, backUVs?: Vector4) {
-			var faceUV: Vector4[] = fUV || new Array<Vector4>(3);
+        public static CreatePolygon(polygon: Mesh, sideOrientation: number, fUV?: Vector4[], fColors?: Color4[], frontUVs?: Vector4, backUVs?: Vector4) {
+            var faceUV: Vector4[] = fUV || new Array<Vector4>(3);
             var faceColors = fColors;
             var colors = [];
 
@@ -1673,39 +1672,39 @@
                     faceColors[f] = new Color4(1, 1, 1, 1);
                 }
             }
-            
+
             var positions = <FloatArray>polygon.getVerticesData(VertexBuffer.PositionKind);
-			var normals = <FloatArray>polygon.getVerticesData(VertexBuffer.NormalKind);
-			var uvs = <FloatArray>polygon.getVerticesData(VertexBuffer.UVKind);
+            var normals = <FloatArray>polygon.getVerticesData(VertexBuffer.NormalKind);
+            var uvs = <FloatArray>polygon.getVerticesData(VertexBuffer.UVKind);
             var indices = <IndicesArray>polygon.getIndices();
-            
+
             // set face colours and textures
             var idx: number = 0;
             var face: number = 0;
-            for (var index = 0; index < normals.length; index += 3) { 
+            for (var index = 0; index < normals.length; index += 3) {
                 //Edge Face  no. 1
-                if(Math.abs(normals[index + 1]) < 0.001) {
-                   face = 1; 
+                if (Math.abs(normals[index + 1]) < 0.001) {
+                    face = 1;
                 }
                 //Top Face  no. 0
-                if(Math.abs(normals[index + 1] - 1) < 0.001 ) {
-                   face = 0; 
+                if (Math.abs(normals[index + 1] - 1) < 0.001) {
+                    face = 0;
                 }
                 //Bottom Face  no. 2
-                if(Math.abs(normals[index + 1] + 1) < 0.001 ) {
-                   face = 2; 
+                if (Math.abs(normals[index + 1] + 1) < 0.001) {
+                    face = 2;
                 }
                 idx = index / 3;
-                uvs[2*idx] = (1 - uvs[2*idx])*faceUV[face].x + uvs[2*idx]*faceUV[face].z;
-                uvs[2*idx + 1] = (1 - uvs[2*idx + 1])*faceUV[face].y + uvs[2*idx + 1]*faceUV[face].w;
+                uvs[2 * idx] = (1 - uvs[2 * idx]) * faceUV[face].x + uvs[2 * idx] * faceUV[face].z;
+                uvs[2 * idx + 1] = (1 - uvs[2 * idx + 1]) * faceUV[face].y + uvs[2 * idx + 1] * faceUV[face].w;
                 if (faceColors) {
                     colors.push(faceColors[face].r, faceColors[face].g, faceColors[face].b, faceColors[face].a);
                 }
             }
 
-			// sides
+            // sides
             VertexData._ComputeSides(sideOrientation, positions, indices, normals, uvs, frontUVs, backUVs);
-            
+
             // Result
             var vertexData = new VertexData();
             vertexData.indices = indices;
@@ -1719,8 +1718,8 @@
             }
 
             return vertexData;
-			
-		}
+
+        }
 
         /**
          * Creates the VertexData of the IcoSphere.  
@@ -2234,8 +2233,10 @@
          * depthSortedFacets : optional array of depthSortedFacets to store the facet distances from the reference location
          */
         public static ComputeNormals(positions: any, indices: any, normals: any,
-            options?: { facetNormals?: any, facetPositions?: any, facetPartitioning?: any, ratio?: number, bInfo?: any, bbSize?: Vector3, subDiv?: any, 
-                useRightHandedSystem?: boolean, depthSort?: boolean, distanceTo?: Vector3, depthSortedFacets?: any }): void {
+            options?: {
+                facetNormals?: any, facetPositions?: any, facetPartitioning?: any, ratio?: number, bInfo?: any, bbSize?: Vector3, subDiv?: any,
+                useRightHandedSystem?: boolean, depthSort?: boolean, distanceTo?: Vector3, depthSortedFacets?: any
+            }): void {
 
             // temporary scalar variables
             var index = 0;                      // facet index     
@@ -2276,7 +2277,7 @@
                 if (computeDepthSort) {
                     if (distanceTo === undefined) {
                         distanceTo = Vector3.Zero();
-                    } 
+                    }
                     var depthSortedFacets = options.depthSortedFacets;
                 }
             }
@@ -2319,7 +2320,7 @@
             }
 
             // Loop : 1 indice triplet = 1 facet
-            var nbFaces = (indices.length / 3)|0;
+            var nbFaces = (indices.length / 3) | 0;
             for (index = 0; index < nbFaces; index++) {
 
                 // get the indexes of the coordinates of each vertex of the facet
@@ -2386,10 +2387,10 @@
                     block_idx_v3 = b3x + options.subDiv.max * b3y + subSq * b3z;
                     block_idx_o = ox + options.subDiv.max * oy + subSq * oz;
 
-                    options.facetPartitioning[block_idx_o] = options.facetPartitioning[block_idx_o] ? options.facetPartitioning[block_idx_o] :new Array();
-                    options.facetPartitioning[block_idx_v1] = options.facetPartitioning[block_idx_v1] ? options.facetPartitioning[block_idx_v1] :new Array();
-                    options.facetPartitioning[block_idx_v2] = options.facetPartitioning[block_idx_v2] ? options.facetPartitioning[block_idx_v2] :new Array();
-                    options.facetPartitioning[block_idx_v3] = options.facetPartitioning[block_idx_v3] ? options.facetPartitioning[block_idx_v3] :new Array();
+                    options.facetPartitioning[block_idx_o] = options.facetPartitioning[block_idx_o] ? options.facetPartitioning[block_idx_o] : new Array();
+                    options.facetPartitioning[block_idx_v1] = options.facetPartitioning[block_idx_v1] ? options.facetPartitioning[block_idx_v1] : new Array();
+                    options.facetPartitioning[block_idx_v2] = options.facetPartitioning[block_idx_v2] ? options.facetPartitioning[block_idx_v2] : new Array();
+                    options.facetPartitioning[block_idx_v3] = options.facetPartitioning[block_idx_v3] ? options.facetPartitioning[block_idx_v3] : new Array();
 
                     // push each facet index in each block containing the vertex
                     options.facetPartitioning[block_idx_v1].push(index);
@@ -2488,12 +2489,12 @@
                     var lu: number = uvs.length;
                     var u: number = 0;
                     for (u = 0; u < lu; u++) {
-                        uvs[u + lu] = uvs[u];                       
+                        uvs[u + lu] = uvs[u];
                     }
                     frontUVs = frontUVs ? frontUVs : new Vector4(0.0, 0.0, 1.0, 1.0);
-                    backUVs = backUVs ? backUVs : new Vector4(0.0, 0.0, 1.0, 1.0); 
+                    backUVs = backUVs ? backUVs : new Vector4(0.0, 0.0, 1.0, 1.0);
                     u = 0;
-                    for (i = 0; i < lu / 2; i++) {    
+                    for (i = 0; i < lu / 2; i++) {
                         uvs[u] = frontUVs.x + (frontUVs.z - frontUVs.x) * uvs[u];
                         uvs[u + 1] = frontUVs.y + (frontUVs.w - frontUVs.y) * uvs[u + 1];
                         uvs[u + lu] = backUVs.x + (backUVs.z - backUVs.x) * uvs[u + lu];

+ 3 - 5
src/Mesh/babylon.subMesh.ts

@@ -101,7 +101,9 @@
         public getMaterial(): Nullable<Material> {
             var rootMaterial = this._renderingMesh.material;
 
-            if (rootMaterial && (<MultiMaterial>rootMaterial).getSubMaterial) {
+            if (rootMaterial === null || rootMaterial === undefined) {
+                return this._mesh.getScene().defaultMaterial;
+            } else if ((<MultiMaterial>rootMaterial).getSubMaterial) {
                 var multiMaterial = <MultiMaterial>rootMaterial;
                 var effectiveMaterial = multiMaterial.getSubMaterial(this.materialIndex);
 
@@ -113,10 +115,6 @@
                 return effectiveMaterial;
             }
 
-            if (!rootMaterial) {
-                return this._mesh.getScene().defaultMaterial;
-            }
-
             return rootMaterial;
         }
 

+ 5 - 2
src/Mesh/babylon.transformNode.ts

@@ -119,7 +119,7 @@ module BABYLON {
          */
         protected _getWorldMatrixDeterminant(): number {
             if (this._currentRenderId !== this.getScene().getRenderId()) {
-                this._worldMatrixDeterminant = this.computeWorldMatrix().determinant();
+                this.computeWorldMatrix();
             }
             return this._worldMatrixDeterminant;
         }
@@ -463,7 +463,7 @@ module BABYLON {
          */
         public setParent(node: Nullable<Node>): TransformNode {
 
-            if (node == null) {
+            if (node === null) {
                 var rotation = Tmp.Quaternion[0];
                 var position = Tmp.Vector3[0];
                 var scale = Tmp.Vector3[1];
@@ -835,6 +835,9 @@ module BABYLON {
                 this._poseMatrix = Matrix.Invert(this._worldMatrix);
             }
 
+            // Cache the determinant
+            this._worldMatrixDeterminant = this._worldMatrix.determinant();
+
             return this._worldMatrix;
         }
 

+ 54 - 0
src/Particles/babylon.boxParticleEmitter.ts

@@ -0,0 +1,54 @@
+module BABYLON {
+    export class BoxParticleEmitter implements IParticleEmitterType {
+
+        public get direction1(): Vector3 {
+            return this._particleSystem.direction1;
+        }
+        public set direction1(value: Vector3) {
+            this._particleSystem.direction1 = value;
+        }
+
+        public get direction2(): Vector3 {
+            return this._particleSystem.direction2;
+        }
+        public set direction2(value: Vector3) {
+            this._particleSystem.direction2 = value;
+        }
+
+        public get minEmitBox(): Vector3 {
+            return this._particleSystem.minEmitBox;
+        }
+        public set minEmitBox(value: Vector3) {
+            this._particleSystem.minEmitBox = value;
+        }
+
+        public get maxEmitBox(): Vector3 {
+            return this._particleSystem.maxEmitBox;
+        }
+        public set maxEmitBox(value: Vector3) {
+            this._particleSystem.maxEmitBox = value;
+        }
+        
+        // to be updated like the rest of emitters when breaking changes.
+        // all property should be come public variables and passed through constructor.
+        constructor(private _particleSystem: ParticleSystem) {
+
+        }
+
+        startDirectionFunction(emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
+            var randX = ParticleSystem.randomNumber(this.direction1.x, this.direction2.x);
+            var randY = ParticleSystem.randomNumber(this.direction1.y, this.direction2.y);
+            var randZ = ParticleSystem.randomNumber(this.direction1.z, this.direction2.z);
+
+            Vector3.TransformNormalFromFloatsToRef(randX * emitPower, randY * emitPower, randZ * emitPower, worldMatrix, directionToUpdate);
+        }
+
+        startPositionFunction(worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle): void {
+            var randX = ParticleSystem.randomNumber(this.minEmitBox.x, this.maxEmitBox.x);
+            var randY = ParticleSystem.randomNumber(this.minEmitBox.y, this.maxEmitBox.y);
+            var randZ = ParticleSystem.randomNumber(this.minEmitBox.z, this.maxEmitBox.z);
+
+            Vector3.TransformCoordinatesFromFloatsToRef(randX, randY, randZ, worldMatrix, positionToUpdate);
+        }
+    }
+}

+ 29 - 0
src/Particles/babylon.coneParticleEmitter.ts

@@ -0,0 +1,29 @@
+module BABYLON {
+    export class ConeParticleEmitter implements IParticleEmitterType {
+        constructor(public radius: number, public angle: number) {
+        }
+
+        startDirectionFunction(emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
+            if (this.angle === 0) {
+                Vector3.TransformNormalFromFloatsToRef(0, emitPower, 0, worldMatrix, directionToUpdate);
+            }
+            else {
+                var phi = ParticleSystem.randomNumber(0, 2 * Math.PI);
+                var theta = ParticleSystem.randomNumber(0, this.angle);
+                var randX = Math.cos(phi) * Math.sin(theta);
+                var randY = Math.cos(theta);
+                var randZ = Math.sin(phi) * Math.sin(theta);
+                Vector3.TransformNormalFromFloatsToRef(randX * emitPower, randY * emitPower, randZ * emitPower, worldMatrix, directionToUpdate);
+            }
+        }
+
+        startPositionFunction(worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle): void {
+            var s = ParticleSystem.randomNumber(0, Math.PI * 2);
+            var radius = ParticleSystem.randomNumber(0, this.radius);
+            var randX = radius * Math.sin(s);
+            var randZ = radius * Math.cos(s);
+
+            Vector3.TransformCoordinatesFromFloatsToRef(randX, 0, randZ, worldMatrix, positionToUpdate);
+        }
+    }
+}

+ 6 - 0
src/Particles/babylon.iParticleEmitterType.ts

@@ -0,0 +1,6 @@
+module BABYLON {
+    export interface IParticleEmitterType {
+        startDirectionFunction(emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void;
+        startPositionFunction(worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle): void;
+    }
+}

+ 60 - 33
src/Particles/babylon.particleSystem.ts

@@ -1,18 +1,8 @@
 module BABYLON {
-    var randomNumber = (min: number, max: number): number => {
-        if (min === max) {
-            return (min);
-        }
-
-        var random = Math.random();
-
-        return ((random * (max - min)) + min);
-    }
-
     export interface IParticleSystem {
         id: string;
         name: string;
-        emitter: Nullable<AbstractMesh | Vector3>; 
+        emitter: Nullable<AbstractMesh | Vector3>;
         renderingGroupId: number;
         layerMask: number;
         isStarted(): boolean;
@@ -93,6 +83,7 @@
         public color2 = new Color4(1.0, 1.0, 1.0, 1.0);
         public colorDead = new Color4(0, 0, 0, 1.0);
         public textureMask = new Color4(1.0, 1.0, 1.0, 1.0);
+        public particleEmitterType: IParticleEmitterType;
         public startDirectionFunction: (emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle) => void;
         public startPositionFunction: (worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle) => void;
 
@@ -172,21 +163,7 @@
             this._vertexBuffers["options"] = options;
 
             // Default behaviors
-            this.startDirectionFunction = (emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void => {
-                var randX = randomNumber(this.direction1.x, this.direction2.x);
-                var randY = randomNumber(this.direction1.y, this.direction2.y);
-                var randZ = randomNumber(this.direction1.z, this.direction2.z);
-
-                Vector3.TransformNormalFromFloatsToRef(randX * emitPower, randY * emitPower, randZ * emitPower, worldMatrix, directionToUpdate);
-            }
-
-            this.startPositionFunction = (worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle): void => {
-                var randX = randomNumber(this.minEmitBox.x, this.maxEmitBox.x);
-                var randY = randomNumber(this.minEmitBox.y, this.maxEmitBox.y);
-                var randZ = randomNumber(this.minEmitBox.z, this.maxEmitBox.z);
-
-                Vector3.TransformCoordinatesFromFloatsToRef(randX, randY, randZ, worldMatrix, positionToUpdate);
-            }
+            this.particleEmitterType = new BoxParticleEmitter(this);
 
             this.updateFunction = (particles: Particle[]): void => {
                 for (var index = 0; index < particles.length; index++) {
@@ -345,18 +322,28 @@
 
                 this.particles.push(particle);
 
-                var emitPower = randomNumber(this.minEmitPower, this.maxEmitPower);
+                var emitPower = ParticleSystem.randomNumber(this.minEmitPower, this.maxEmitPower);
 
-                this.startDirectionFunction(emitPower, worldMatrix, particle.direction, particle);
+                if (this.startDirectionFunction) {
+                    this.startDirectionFunction(emitPower, worldMatrix, particle.direction, particle);
+                }
+                else {
+                    this.particleEmitterType.startDirectionFunction(emitPower, worldMatrix, particle.direction, particle);
+                }
 
-                particle.lifeTime = randomNumber(this.minLifeTime, this.maxLifeTime);
+                if (this.startPositionFunction) {
+                    this.startPositionFunction(worldMatrix, particle.position, particle);
+                }
+                else {
+                    this.particleEmitterType.startPositionFunction(worldMatrix, particle.position, particle);
+                }
 
-                particle.size = randomNumber(this.minSize, this.maxSize);
-                particle.angularSpeed = randomNumber(this.minAngularSpeed, this.maxAngularSpeed);
+                particle.lifeTime = ParticleSystem.randomNumber(this.minLifeTime, this.maxLifeTime);
 
-                this.startPositionFunction(worldMatrix, particle.position, particle);
+                particle.size = ParticleSystem.randomNumber(this.minSize, this.maxSize);
+                particle.angularSpeed = ParticleSystem.randomNumber(this.minAngularSpeed, this.maxAngularSpeed);
 
-                var step = randomNumber(0, 1.0);
+                var step = ParticleSystem.randomNumber(0, 1.0);
 
                 Color4.LerpToRef(this.color1, this.color2, step, particle.color);
 
@@ -593,6 +580,46 @@
             this.onDisposeObservable.clear();
         }
 
+        public createSphereEmitter(radius = 1): SphereParticleEmitter {
+            var particleEmitter = new SphereParticleEmitter(radius);
+            this.particleEmitterType = particleEmitter;
+            return particleEmitter;
+        }
+
+        public createDirectedSphereEmitter(radius = 1, direction1 = new Vector3(0, 1.0, 0), direction2 = new Vector3(0, 1.0, 0)): SphereDirectedParticleEmitter {
+            var particleEmitter = new SphereDirectedParticleEmitter(radius, direction1, direction2)
+            this.particleEmitterType = particleEmitter;
+            return particleEmitter;
+        }
+
+        public createConeEmitter(radius = 1, angle = Math.PI / 4): ConeParticleEmitter {
+            var particleEmitter = new ConeParticleEmitter(radius, angle);
+            this.particleEmitterType = particleEmitter;
+            return particleEmitter;
+        }
+
+        // this method needs to be changed when breaking changes will be allowed to match the sphere and cone methods and properties direction1,2 and minEmitBox,maxEmitBox to be removed from the system.
+        public createBoxEmitter(direction1: Vector3, direction2: Vector3, minEmitBox: Vector3, maxEmitBox: Vector3): BoxParticleEmitter {
+            var particleEmitter = new BoxParticleEmitter(this);
+            this.direction1 = direction1;
+            this.direction2 = direction2;
+            this.minEmitBox = minEmitBox;
+            this.maxEmitBox = maxEmitBox;
+            this.particleEmitterType = particleEmitter;
+            return particleEmitter;
+        }
+
+        public static randomNumber(min: number, max: number): number {
+            if (min === max) {
+                return (min);
+            }
+
+            var random = Math.random();
+
+            return ((random * (max - min)) + min);
+        }
+
+
         // Clone
         public clone(name: string, newEmitter: any): ParticleSystem {
             var custom: Nullable<Effect> = null;

+ 35 - 0
src/Particles/babylon.sphereParticleEmitter.ts

@@ -0,0 +1,35 @@
+module BABYLON {
+    export class SphereParticleEmitter implements IParticleEmitterType {
+        constructor(public radius: number) {
+
+        }
+
+        startDirectionFunction(emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
+            // measure the direction Vector from the emitter to the particle.
+            var direction = particle.position.subtract(worldMatrix.getTranslation());
+            Vector3.TransformNormalFromFloatsToRef(direction.x * emitPower, direction.y * emitPower, direction.z * emitPower, worldMatrix, directionToUpdate);
+        }
+
+        startPositionFunction(worldMatrix: Matrix, positionToUpdate: Vector3, particle: Particle): void {
+            var phi = ParticleSystem.randomNumber(0, 2 * Math.PI);
+            var theta = ParticleSystem.randomNumber(0, Math.PI);
+            var randX = this.radius * Math.cos(phi) * Math.sin(theta);
+            var randY = this.radius * Math.cos(theta);
+            var randZ = this.radius * Math.sin(phi) * Math.sin(theta);
+            Vector3.TransformCoordinatesFromFloatsToRef(randX, randY, randZ, worldMatrix, positionToUpdate);
+        }
+    }
+
+    export class SphereDirectedParticleEmitter extends SphereParticleEmitter {
+        constructor(radius: number, public direction1: Vector3, public direction2: Vector3) {
+            super(radius);
+        }
+
+        startDirectionFunction(emitPower: number, worldMatrix: Matrix, directionToUpdate: Vector3, particle: Particle): void {
+            var randX = ParticleSystem.randomNumber(this.direction1.x, this.direction2.x);
+            var randY = ParticleSystem.randomNumber(this.direction1.y, this.direction2.y);
+            var randZ = ParticleSystem.randomNumber(this.direction1.z, this.direction2.z);
+            Vector3.TransformNormalFromFloatsToRef(randX * emitPower, randY * emitPower, randZ * emitPower, worldMatrix, directionToUpdate);
+        }
+    }
+}

+ 15 - 5
src/Rendering/babylon.geometryBufferRenderer.ts

@@ -108,6 +108,14 @@ module BABYLON {
             return this._multiRenderTarget;
         }
 
+        public get samples(): number {
+            return this._multiRenderTarget.samples;
+        }
+
+        public set samples(value: number) {
+            this._multiRenderTarget.samples = value;
+        }
+
         // Methods
         public dispose(): void {
             this.getGBuffer().dispose();
@@ -117,7 +125,9 @@ module BABYLON {
             var engine = this._scene.getEngine();
             var count = this._enablePosition ? 3 : 2;
 
-            this._multiRenderTarget = new MultiRenderTarget("gBuffer", { width: engine.getRenderWidth() * this._ratio, height: engine.getRenderHeight() * this._ratio }, count, this._scene, { generateMipMaps : false, generateDepthTexture: true });
+            this._multiRenderTarget = new MultiRenderTarget("gBuffer",
+                { width: engine.getRenderWidth() * this._ratio, height: engine.getRenderHeight() * this._ratio }, count, this._scene,
+                { generateMipMaps: false, generateDepthTexture: true, defaultType: Engine.TEXTURETYPE_FLOAT });
             if (!this.isSupported) {
                 return;
             }
@@ -126,7 +136,7 @@ module BABYLON {
             this._multiRenderTarget.refreshRate = 1;
             this._multiRenderTarget.renderParticles = false;
             this._multiRenderTarget.renderList = null;
-            
+
             // set default depth value to 1.0 (far away)
             this._multiRenderTarget.onClearObservable.add((engine: Engine) => {
                 engine.clear(new Color4(0.0, 0.0, 0.0, 1.0), true, true, true);
@@ -158,7 +168,7 @@ module BABYLON {
                 if (this.isReady(subMesh, hardwareInstancedRendering)) {
                     engine.enableEffect(this._effect);
                     mesh._bind(subMesh, this._effect, Material.TriangleFillMode);
-                    
+
 
                     this._effect.setMatrix("viewProjection", scene.getTransformMatrix());
                     this._effect.setMatrix("view", scene.getViewMatrix());
@@ -188,12 +198,12 @@ module BABYLON {
                 var index;
 
                 if (depthOnlySubMeshes.length) {
-                    engine.setColorWrite(false);            
+                    engine.setColorWrite(false);
                     for (index = 0; index < depthOnlySubMeshes.length; index++) {
                         renderSubMesh(depthOnlySubMeshes.data[index]);
                     }
                     engine.setColorWrite(true);
-                }                  
+                }
 
                 for (index = 0; index < opaqueSubMeshes.length; index++) {
                     renderSubMesh(opaqueSubMeshes.data[index]);

+ 12 - 5
src/Rendering/babylon.renderingGroup.ts

@@ -315,12 +315,19 @@
         /**
          * Inserts the submesh in its correct queue depending on its material.
          * @param subMesh The submesh to dispatch
+         * @param [mesh] Optional reference to the submeshes's mesh. Provide if you have an exiting reference to improve performance.
+         * @param [material] Optional reference to the submeshes's material. Provide if you have an exiting reference to improve performance.
          */
-        public dispatch(subMesh: SubMesh): void {
-            var material = subMesh.getMaterial();
-            var mesh = subMesh.getMesh();
+        public dispatch(subMesh: SubMesh, mesh?: AbstractMesh, material?: Nullable<Material>): void {
+            // Get mesh and materials if not provided
+            if (mesh === undefined) {
+                mesh = subMesh.getMesh();
+            }
+            if (material === undefined) {
+                material = subMesh.getMaterial();
+            }
 
-            if (!material) {
+            if (material === null || material === undefined) {
                 return;
             }
 
@@ -340,7 +347,7 @@
                 this._opaqueSubMeshes.push(subMesh); // Opaque
             }
 
-            if (mesh._edgesRenderer) {
+            if (mesh._edgesRenderer !== null && mesh._edgesRenderer !== undefined) {
                 this._edgesRenderers.push(mesh._edgesRenderer);
             }
         }

+ 11 - 4
src/Rendering/babylon.renderingManager.ts

@@ -142,7 +142,7 @@
         }
 
         private _prepareRenderingGroup(renderingGroupId: number): void {
-            if (!this._renderingGroups[renderingGroupId]) {
+            if (this._renderingGroups[renderingGroupId] === undefined) {
                 this._renderingGroups[renderingGroupId] = new RenderingGroup(renderingGroupId, this._scene,
                     this._customOpaqueSortCompareFn[renderingGroupId],
                     this._customAlphaTestSortCompareFn[renderingGroupId],
@@ -167,13 +167,20 @@
             this._renderingGroups[renderingGroupId].dispatchParticles(particleSystem);
         }
 
-        public dispatch(subMesh: SubMesh): void {
-            var mesh = subMesh.getMesh();
+        /**
+         * @param subMesh The submesh to dispatch
+         * @param [mesh] Optional reference to the submeshes's mesh. Provide if you have an exiting reference to improve performance.
+         * @param [material] Optional reference to the submeshes's material. Provide if you have an exiting reference to improve performance.
+         */
+        public dispatch(subMesh: SubMesh, mesh?: AbstractMesh, material?: Nullable<Material>): void {
+            if (mesh === undefined) {
+                mesh = subMesh.getMesh();
+            }
             var renderingGroupId = mesh.renderingGroupId || 0;
 
             this._prepareRenderingGroup(renderingGroupId);
 
-            this._renderingGroups[renderingGroupId].dispatch(subMesh);
+            this._renderingGroups[renderingGroupId].dispatch(subMesh, mesh, material);
         }
 
         /**

+ 20 - 14
src/Tools/babylon.assetsManager.ts

@@ -7,7 +7,7 @@ module BABYLON {
         ERROR
     }
 
-    export abstract class AbstractAssetTask  {
+    export abstract class AbstractAssetTask {
         public onSuccess: (task: any) => void;
         public onError: (task: any, message?: string, exception?: any) => void;
 
@@ -112,17 +112,17 @@ module BABYLON {
         public text: string;
 
         public onSuccess: (task: TextFileAssetTask) => void;
-        public onError: (task: TextFileAssetTask, message?: string, exception?: any) => void;        
+        public onError: (task: TextFileAssetTask, message?: string, exception?: any) => void;
 
         constructor(public name: string, public url: string) {
             super(name);
         }
 
         public runTask(scene: Scene, onSuccess: () => void, onError: (message?: string, exception?: any) => void) {
-            Tools.LoadFile(this.url, (data) => {
+            scene._loadFile(this.url, (data) => {
                 this.text = data as string;
                 onSuccess();
-            }, undefined, scene.database, false, (request, exception) => {
+            }, undefined, false, true, (request, exception) => {
                 if (request) {
                     onError(request.status + " " + request.statusText, exception);
                 }
@@ -134,18 +134,17 @@ module BABYLON {
         public data: ArrayBuffer;
 
         public onSuccess: (task: BinaryFileAssetTask) => void;
-        public onError: (task: BinaryFileAssetTask, message?: string, exception?: any) => void;               
+        public onError: (task: BinaryFileAssetTask, message?: string, exception?: any) => void;
 
         constructor(public name: string, public url: string) {
             super(name);
         }
 
         public runTask(scene: Scene, onSuccess: () => void, onError: (message?: string, exception?: any) => void) {
-            Tools.LoadFile(this.url, (data) => {
-
+            scene._loadFile(this.url, (data) => {
                 this.data = data as ArrayBuffer;
                 onSuccess();
-            }, undefined, scene.database, true, (request, exception) => {
+            }, undefined, true, true, (request, exception) => {
                 if (request) {
                     onError(request.status + " " + request.statusText, exception);
                 }
@@ -157,7 +156,7 @@ module BABYLON {
         public image: HTMLImageElement;
 
         public onSuccess: (task: ImageAssetTask) => void;
-        public onError: (task: ImageAssetTask, message?: string, exception?: any) => void;            
+        public onError: (task: ImageAssetTask, message?: string, exception?: any) => void;
 
         constructor(public name: string, public url: string) {
             super(name);
@@ -187,9 +186,9 @@ module BABYLON {
 
     export class TextureAssetTask extends AbstractAssetTask implements ITextureAssetTask<Texture> {
         public texture: Texture;
-        
+
         public onSuccess: (task: TextureAssetTask) => void;
-        public onError: (task: TextureAssetTask, message?: string, exception?: any) => void;    
+        public onError: (task: TextureAssetTask, message?: string, exception?: any) => void;
 
         constructor(public name: string, public url: string, public noMipmap?: boolean, public invertY?: boolean, public samplingMode: number = Texture.TRILINEAR_SAMPLINGMODE) {
             super(name);
@@ -213,7 +212,7 @@ module BABYLON {
         public texture: CubeTexture;
 
         public onSuccess: (task: CubeTextureAssetTask) => void;
-        public onError: (task: CubeTextureAssetTask, message?: string, exception?: any) => void;            
+        public onError: (task: CubeTextureAssetTask, message?: string, exception?: any) => void;
 
         constructor(public name: string, public url: string, public extensions?: string[], public noMipmap?: boolean, public files?: string[]) {
             super(name);
@@ -237,7 +236,7 @@ module BABYLON {
         public texture: HDRCubeTexture;
 
         public onSuccess: (task: HDRCubeTextureAssetTask) => void;
-        public onError: (task: HDRCubeTextureAssetTask, message?: string, exception?: any) => void;               
+        public onError: (task: HDRCubeTextureAssetTask, message?: string, exception?: any) => void;
 
         constructor(public name: string, public url: string, public size?: number, public noMipmap = false, public generateHarmonics = true, public useInGammaSpace = false, public usePMREMGenerator = false) {
             super(name);
@@ -259,6 +258,7 @@ module BABYLON {
 
     export class AssetsManager {
         private _scene: Scene;
+        private _isLoading = false;
 
         protected tasks = new Array<AbstractAssetTask>();
         protected waitingTasksCount = 0;
@@ -366,7 +366,7 @@ module BABYLON {
                     Tools.Error("Error running tasks-done callbacks.");
                     console.log(e);
                 }
-
+                this._isLoading = false;
                 this._scene.getEngine().hideLoadingUI();
             }
         }
@@ -402,11 +402,16 @@ module BABYLON {
         }
 
         public reset(): AssetsManager {
+            this._isLoading = false;
             this.tasks = new Array<AbstractAssetTask>();
             return this;
         }
 
         public load(): AssetsManager {
+            if (this._isLoading) {
+                return this;
+            }
+            this._isLoading = true;
             this.waitingTasksCount = this.tasks.length;
 
             if (this.waitingTasksCount === 0) {
@@ -414,6 +419,7 @@ module BABYLON {
                     this.onFinish(this.tasks);
                 }
                 this.onTasksDoneObservable.notifyObservers(this.tasks);
+                this._isLoading = false;
                 return this;
             }
 

+ 2 - 2
src/Tools/babylon.filesInput.ts

@@ -7,7 +7,7 @@
         private _engine: Engine;
         private _currentScene: Scene;
         private _sceneLoadedCallback: (sceneFile: File, scene: Scene) => void;
-        private _progressCallback: (progress: ProgressEvent) => void;
+        private _progressCallback: (progress: SceneLoaderProgressEvent) => void;
         private _additionalRenderLoopLogicCallback: () => void;
         private _textureLoadingCallback: (remaining: number) => void;
         private _startingProcessingFilesCallback: () => void;
@@ -18,7 +18,7 @@
         private _sceneFileToLoad: File;
         private _filesToLoad: File[];
 
-        constructor(engine: Engine, scene: Scene, sceneLoadedCallback: (sceneFile: File, scene: Scene) => void, progressCallback: (progress: ProgressEvent) => void, additionalRenderLoopLogicCallback: () => void,
+        constructor(engine: Engine, scene: Scene, sceneLoadedCallback: (sceneFile: File, scene: Scene) => void, progressCallback: (progress: SceneLoaderProgressEvent) => void, additionalRenderLoopLogicCallback: () => void, 
             textureLoadingCallback: (remaining: number) => void, startingProcessingFilesCallback: () => void, onReloadCallback: (sceneFile: File) => void, errorCallback: (sceneFile: File, scene: Scene, message: string) => void) {
             this._engine = engine;
             this._currentScene = scene;

+ 5 - 0
src/Tools/babylon.sceneSerializer.ts

@@ -209,6 +209,11 @@
                 serializationObject.multiMaterials.push(multiMaterial.serialize());
             }
 
+            // Environment texture
+            if (scene.environmentTexture) {
+                serializationObject.environmentTexture = scene.environmentTexture.name;
+            }
+
             // Skeletons
             serializationObject.skeletons = [];
             for (index = 0; index < scene.skeletons.length; index++) {

+ 144 - 58
src/Tools/babylon.tools.ts

@@ -30,6 +30,18 @@
         }
     }
 
+    export interface IFileRequest {
+        /**
+         * Raised when the request is complete (success or error).
+         */
+        onCompleteObservable: Observable<IFileRequest>;
+
+        /**
+         * Aborts the request for a file.
+         */
+        abort: () => void;
+    }
+
     // Screenshots
     var screenshotCanvas: HTMLCanvasElement;
 
@@ -514,88 +526,141 @@
             return img;
         }
 
-        public static LoadFile(url: string, callback: (data: string | ArrayBuffer, responseURL?: string) => void, progressCallBack?: (data: any) => void, database?: Database, useArrayBuffer?: boolean, onError?: (request?: XMLHttpRequest, exception?: any) => void, onRetry?: (oldRequest: XMLHttpRequest, newRequest: XMLHttpRequest) => void, retryStrategy: Nullable<(url: string, request: XMLHttpRequest, retryIndex: number) => number> = null): Nullable<XMLHttpRequest> {
+        public static LoadFile(url: string, onSuccess: (data: string | ArrayBuffer, responseURL?: string) => void, onProgress?: (data: any) => void, database?: Database, useArrayBuffer?: boolean, onError?: (request?: XMLHttpRequest, exception?: any) => void): IFileRequest {
             url = Tools.CleanUrl(url);
 
             url = Tools.PreprocessUrl(url);
 
-            let request: Nullable<XMLHttpRequest> = null;
+            // If file and file input are set
+            if (url.indexOf("file:") !== -1) {
+                const fileName = decodeURIComponent(url.substring(5).toLowerCase());
+                if (FilesInput.FilesToLoad[fileName]) {
+                    return Tools.ReadFile(FilesInput.FilesToLoad[fileName], onSuccess, onProgress, useArrayBuffer);
+                }
+            }
 
-            let noIndexedDB = (retryIndex?: number) => {
-                let oldRequest = request;
-                request = new XMLHttpRequest();
+            const loadUrl = Tools.BaseUrl + url;
 
-                let loadUrl = Tools.BaseUrl + url;
-                request.open('GET', loadUrl, true);
+            let aborted = false;
+            const fileRequest: IFileRequest = {
+                onCompleteObservable: new Observable<IFileRequest>(),
+                abort: () => aborted = true,
+            };
 
-                if (useArrayBuffer) {
-                    request.responseType = "arraybuffer";
-                }
+            const requestFile = () => {
+                let request = new XMLHttpRequest();
+                let retryHandle: Nullable<number> = null;
 
-                if (progressCallBack) {
-                    request.onprogress = progressCallBack;
-                }
+                fileRequest.abort = () => {
+                    aborted = true;
 
-                request.onreadystatechange = () => {
-                    let req = <XMLHttpRequest>request;
-                    // In case of undefined state in some browsers.
-                    if (req.readyState === (XMLHttpRequest.DONE || 4)) {
-                        req.onreadystatechange = () => { };//some browsers have issues where onreadystatechange can be called multiple times with the same value
+                    if (request.readyState !== (XMLHttpRequest.DONE || 4)) {
+                        request.abort();
+                    }
+
+                    if (retryHandle !== null) {
+                        clearTimeout(retryHandle);
+                        retryHandle = null;
+                    }
+                };
 
-                        if (req.status >= 200 && req.status < 300 || (!Tools.IsWindowObjectExist() && (req.status === 0))) {
-                            callback(!useArrayBuffer ? req.responseText : <ArrayBuffer>req.response, req.responseURL);
+                const retryLoop = (retryIndex: number) => {
+                    request.open('GET', loadUrl, true);
+
+                    if (useArrayBuffer) {
+                        request.responseType = "arraybuffer";
+                    }
+
+                    if (onProgress) {
+                        request.addEventListener("progress", onProgress);
+                    }
+
+                    const onLoadEnd = () => {
+                        fileRequest.onCompleteObservable.notifyObservers(fileRequest);
+                    };
+
+                    request.addEventListener("loadend", onLoadEnd);
+
+                    const onReadyStateChange = () => {
+                        if (aborted) {
                             return;
                         }
 
-                        retryStrategy = retryStrategy || Tools.DefaultRetryStrategy;
-                        if (retryStrategy) {
-                            let waitTime = retryStrategy(loadUrl, req, retryIndex || 0);
-                            if (waitTime !== -1) {
-                                setTimeout(() => noIndexedDB((retryIndex || 0) + 1), waitTime);
+                        // In case of undefined state in some browsers.
+                        if (request.readyState === (XMLHttpRequest.DONE || 4)) {
+                            // Some browsers have issues where onreadystatechange can be called multiple times with the same value.
+                            request.removeEventListener("readystatechange", onReadyStateChange);
+
+                            if (request.status >= 200 && request.status < 300 || (!Tools.IsWindowObjectExist() && (request.status === 0))) {
+                                onSuccess(!useArrayBuffer ? request.responseText : <ArrayBuffer>request.response, request.responseURL);
                                 return;
                             }
-                        }
 
-                        let e = new Error("Error status: " + req.status + " - Unable to load " + loadUrl);
-                        if (onError) {
-                            onError(req, e);
-                        } else {
-                            throw e;
+                            let retryStrategy = Tools.DefaultRetryStrategy;
+                            if (retryStrategy) {
+                                let waitTime = retryStrategy(loadUrl, request, retryIndex);
+                                if (waitTime !== -1) {
+                                    // Prevent the request from completing for retry.
+                                    request.removeEventListener("loadend", onLoadEnd);
+                                    request = new XMLHttpRequest();
+                                    retryHandle = setTimeout(() => retryLoop(retryIndex + 1), waitTime);
+                                    return;
+                                }
+                            }
+
+                            let e = new LoadFileError("Error status: " + request.status + " " + request.statusText + " - Unable to load " + loadUrl, request);
+                            if (onError) {
+                                onError(request, e);
+                            } else {
+                                throw e;
+                            }
                         }
-                    }
-                };
+                    };
 
-                request.send();
+                    request.addEventListener("readystatechange", onReadyStateChange);
 
-                if (oldRequest && onRetry) {
-                    onRetry(oldRequest, request);
-                }
-            };
+                    request.send();
+                };
 
-            let loadFromIndexedDB = () => {
-                if (database) {
-                    database.loadFileFromDB(url, callback, progressCallBack, noIndexedDB, useArrayBuffer);
-                }
+                retryLoop(0);
             };
 
-            // If file and file input are set
-            if (url.indexOf("file:") !== -1) {
-                let fileName = decodeURIComponent(url.substring(5).toLowerCase());
-                if (FilesInput.FilesToLoad[fileName]) {
-                    Tools.ReadFile(FilesInput.FilesToLoad[fileName], callback, progressCallBack, useArrayBuffer);
-                    return request;
-                }
-            }
-
             // Caching all files
             if (database && database.enableSceneOffline) {
+                const noIndexedDB = () => {
+                    if (!aborted) {
+                        requestFile();
+                    }
+                };
+
+                const loadFromIndexedDB = () => {
+                    // TODO: database needs to support aborting and should return a IFileRequest
+                    if (aborted) {
+                        return;
+                    }
+
+                    if (database) {
+                        database.loadFileFromDB(url, data => {
+                            if (!aborted) {
+                                onSuccess(data);
+                            }
+
+                            fileRequest.onCompleteObservable.notifyObservers(fileRequest);
+                        }, onProgress ? event => {
+                            if (!aborted) {
+                                onProgress(event);
+                            }
+                        } : undefined, noIndexedDB, useArrayBuffer);
+                    }
+                };
+
                 database.openAsync(loadFromIndexedDB, noIndexedDB);
             }
             else {
-                noIndexedDB();
+                requestFile();
             }
 
-            return request;
+            return fileRequest;
         }
 
         /** 
@@ -623,18 +688,38 @@
             head.appendChild(script);
         }
 
-        public static ReadFileAsDataURL(fileToLoad: Blob, callback: (data: any) => void, progressCallback: (this: MSBaseReader, ev: ProgressEvent) => any): void {
-            var reader = new FileReader();
+        public static ReadFileAsDataURL(fileToLoad: Blob, callback: (data: any) => void, progressCallback: (this: MSBaseReader, ev: ProgressEvent) => any): IFileRequest {
+            let reader = new FileReader();
+
+            let request: IFileRequest = {
+                onCompleteObservable: new Observable<IFileRequest>(),
+                abort: () => reader.abort(),
+            };
+
+            reader.onloadend = e => {
+                request.onCompleteObservable.notifyObservers(request);
+            };
+
             reader.onload = e => {
                 //target doesn't have result from ts 1.3
                 callback((<any>e.target)['result']);
             };
+
             reader.onprogress = progressCallback;
+
             reader.readAsDataURL(fileToLoad);
+
+            return request;
         }
 
-        public static ReadFile(fileToLoad: File, callback: (data: any) => void, progressCallBack?: (this: MSBaseReader, ev: ProgressEvent) => any, useArrayBuffer?: boolean): void {
-            var reader = new FileReader();
+        public static ReadFile(fileToLoad: File, callback: (data: any) => void, progressCallBack?: (this: MSBaseReader, ev: ProgressEvent) => any, useArrayBuffer?: boolean): IFileRequest {
+            let reader = new FileReader();
+            let request: IFileRequest = {
+                onCompleteObservable: new Observable<IFileRequest>(),
+                abort: () => reader.abort(),
+            };
+
+            reader.onloadend = e => request.onCompleteObservable.notifyObservers(request);
             reader.onerror = e => {
                 Tools.Log("Error while reading file: " + fileToLoad.name);
                 callback(JSON.stringify({ autoClear: true, clearColor: [1, 0, 0], ambientColor: [0, 0, 0], gravity: [0, -9.807, 0], meshes: [], cameras: [], lights: [] }));
@@ -643,7 +728,6 @@
                 //target doesn't have result from ts 1.3
                 callback((<any>e.target)['result']);
             };
-
             if (progressCallBack) {
                 reader.onprogress = progressCallBack;
             }
@@ -654,6 +738,8 @@
             else {
                 reader.readAsArrayBuffer(fileToLoad);
             }
+
+            return request;
         }
 
         //returns a downloadable url to a file content.

+ 14 - 43
src/babylon.mixins.ts

@@ -1,18 +1,11 @@
 // Mixins
 interface Window {
-    mozIndexedDB(func: any): any;
-    webkitIndexedDB(func: any): any;
+    mozIndexedDB: IDBFactory;
+    webkitIndexedDB: IDBFactory;
     msIndexedDB: IDBFactory;
-    IDBTransaction(func: any): any;
-    webkitIDBTransaction(func: any): any;
-    msIDBTransaction(func: any): any;
-    IDBKeyRange(func: any): any;
-    webkitIDBKeyRange(func: any): any;
-    msIDBKeyRange(func: any): any;
-    webkitURL: HTMLURL;
-    webkitRequestAnimationFrame(func: any): any;
-    mozRequestAnimationFrame(func: any): any;
-    oRequestAnimationFrame(func: any): any;
+    webkitURL: typeof URL;
+    mozRequestAnimationFrame(callback: FrameRequestCallback): number;
+    oRequestAnimationFrame(callback: FrameRequestCallback): number;
     WebGLRenderingContext: WebGLRenderingContext;
     MSGesture: MSGesture;
     CANNON: any;
@@ -23,8 +16,8 @@ interface Window {
     Math: Math;
     Uint8Array: Uint8ArrayConstructor;
     Float32Array: Float32ArrayConstructor;
-    mozURL: any;
-    msURL: any;
+    mozURL: typeof URL;
+    msURL: typeof URL;
     VRFrameData: any; // WebVR, from specs 1.1
 }
 
@@ -81,13 +74,7 @@ interface WebGLRenderingContext {
     QUERY_RESULT: number;
 }
 
-interface HTMLURL {
-    createObjectURL(param1: any, param2?: any): string;
-}
-
 interface Document {
-    exitFullscreen(): void;
-    webkitCancelFullScreen(): void;
     mozCancelFullScreen(): void;
     msCancelFullScreen(): void;
     mozFullScreen: boolean;
@@ -99,17 +86,12 @@ interface Document {
 }
 
 interface HTMLCanvasElement {
-    requestPointerLock(): void;
     msRequestPointerLock?(): void;
     mozRequestPointerLock?(): void;
     webkitRequestPointerLock?(): void;
 }
 
 interface CanvasRenderingContext2D {
-    imageSmoothingEnabled: boolean;
-    mozImageSmoothingEnabled: boolean;
-    oImageSmoothingEnabled: boolean;
-    webkitImageSmoothingEnabled: boolean;
     msImageSmoothingEnabled: boolean;
 }
 
@@ -133,23 +115,16 @@ interface MouseEvent {
     msMovementY: number;
 }
 
-interface MSStyleCSSProperties {
-    webkitTransform: string;
-    webkitTransition: string;
-}
-
 interface Navigator {
     getVRDisplays: () => any;
     mozGetVRDevices: (any: any) => any;
-    getUserMedia: any;
-    webkitGetUserMedia: any;
-    mozGetUserMedia: any;
-    msGetUserMedia: any;
-
-    getGamepads(func?: any): any;
-    webkitGetGamepads(func?: any): any
-    msGetGamepads(func?: any): any;
-    webkitGamepads(func?: any): any;
+    webkitGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
+    mozGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
+    msGetUserMedia(constraints: MediaStreamConstraints, successCallback: NavigatorUserMediaSuccessCallback, errorCallback: NavigatorUserMediaErrorCallback): void;
+
+    webkitGetGamepads(): Gamepad[];
+    msGetGamepads(): Gamepad[];
+    webkitGamepads(): Gamepad[];
 }
 
 interface HTMLVideoElement {
@@ -161,10 +136,6 @@ interface Screen {
     mozOrientation: string;
 }
 
-interface HTMLMediaElement {
-    crossOrigin: string | null;
-}
-
 interface Math {
     fround(x: number): number;
     imul(a: number, b: number): number;

+ 15 - 7
src/babylon.node.ts

@@ -44,17 +44,20 @@
                 return;
             }
 
-            if (this._parentNode) {
+            // Remove self from list of children of parent
+            if (this._parentNode && this._parentNode._children !== undefined && this._parentNode._children !== null) {
                 var index = this._parentNode._children.indexOf(this);
                 if (index !== -1) {
                     this._parentNode._children.splice(index, 1);
                 }
             }
 
+            // Store new parent
             this._parentNode = parent;
 
+            // Add as child to new parent
             if (this._parentNode) {
-                if (!this._parentNode._children) {
+                if (this._parentNode._children === undefined || this._parentNode._children === null) {
                     this._parentNode._children = new Array<Node>();
                 }
                 this._parentNode._children.push(this);
@@ -241,17 +244,22 @@
 
         /**
          * Is this node enabled. 
-         * If the node has a parent and is enabled, the parent will be inspected as well.
+         * If the node has a parent, all ancestors will be checked and false will be returned if any are false (not enabled), otherwise will return true.
+         * @param {boolean} [checkAncestors=true] - Indicates if this method should check the ancestors. The default is to check the ancestors. If set to false, the method will return the value of this node without checking ancestors.
          * @return {boolean} whether this node (and its parent) is enabled.
          * @see setEnabled
          */
-        public isEnabled(): boolean {
-            if (!this._isEnabled) {
+        public isEnabled(checkAncestors: boolean = true): boolean {
+            if (checkAncestors === false) {
+                return this._isEnabled;
+            }
+
+            if (this._isEnabled === false) {
                 return false;
             }
 
-            if (this.parent) {
-                return this.parent.isEnabled();
+            if (this.parent !== undefined && this.parent !== null) {
+                return this.parent.isEnabled(checkAncestors);
             }
 
             return true;

+ 103 - 24
src/babylon.scene.ts

@@ -3,6 +3,11 @@
         dispose(): void;
     }
 
+    export interface IActiveMeshCandidateProvider {
+        getMeshes(scene: Scene): AbstractMesh[];
+        readonly checksIsEnabled: boolean; // Indicates if the meshes have been checked to make sure they are isEnabled().
+    }
+
     class ClickInfo {
         private _singleClick = false;
         private _doubleClick = false;
@@ -870,9 +875,11 @@
         private _alternateProjectionUpdateFlag = -1;
 
         public _toBeDisposed = new SmartArray<Nullable<IDisposable>>(256);
+        private _activeRequests = new Array<IFileRequest>();
         private _pendingData = new Array();
         private _isDisposed = false;
 
+        public dispatchAllSubMeshesOfActiveMeshes: boolean = false;
         private _activeMeshes = new SmartArray<AbstractMesh>(256);
         private _processedMaterials = new SmartArray<Material>(256);
         private _renderTargets = new SmartArrayNoDuplicate<RenderTargetTexture>(256);
@@ -2015,6 +2022,17 @@
             return animatable;
         }
 
+        /**
+         * Begin a new animation on a given node
+         * @param {BABYLON.Node} node defines the root node where the animation will take place
+         * @param {BABYLON.Animation[]} defines the list of animations to start
+         * @param {number} from defines the initial value
+         * @param {number} to defines the final value
+         * @param {boolean} loop defines if you want animation to loop (off by default)
+         * @param {number} speedRatio defines the speed ratio to apply to all animations
+         * @param onAnimationEnd defines the callback to call when an animation ends (will be called once per node)
+         * @returns the list of created animatables
+         */
         public beginDirectAnimation(target: any, animations: Animation[], from: number, to: number, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void): Animatable {
             if (speedRatio === undefined) {
                 speedRatio = 1.0;
@@ -2025,6 +2043,28 @@
             return animatable;
         }
 
+        /**
+         * Begin a new animation on a given node and its hierarchy
+         * @param {BABYLON.Node} node defines the root node where the animation will take place
+         * @param {boolean} directDescendantsOnly if true only direct descendants will be used, if false direct and also indirect (children of children, an so on in a recursive manner) descendants will be used.
+         * @param {BABYLON.Animation[]} defines the list of animations to start
+         * @param {number} from defines the initial value
+         * @param {number} to defines the final value
+         * @param {boolean} loop defines if you want animation to loop (off by default)
+         * @param {number} speedRatio defines the speed ratio to apply to all animations
+         * @param onAnimationEnd defines the callback to call when an animation ends (will be called once per node)
+         * @returns the list of animatables created for all nodes
+         */
+        public beginDirectHierarchyAnimation(target: Node, directDescendantsOnly: boolean, animations: Animation[], from: number, to: number, loop?: boolean, speedRatio?: number, onAnimationEnd?: () => void): Animatable[] {
+            let children = target.getDescendants(directDescendantsOnly);
+            let result = [];
+            for (var child of children) {
+                result.push(this.beginDirectAnimation(child, animations, from, to, loop, speedRatio, onAnimationEnd));
+            }
+
+            return result;
+        }
+
         public getAnimatableByTarget(target: any): Nullable<Animatable> {
             for (var index = 0; index < this._activeAnimatables.length; index++) {
                 if (this._activeAnimatables[index].target === target) {
@@ -2035,7 +2075,7 @@
             return null;
         }
 
-        public get Animatables(): Animatable[] {
+        public get animatables(): Animatable[] {
             return this._activeAnimatables;
         }
 
@@ -2918,18 +2958,18 @@
         }
 
         private _evaluateSubMesh(subMesh: SubMesh, mesh: AbstractMesh): void {
-            if (mesh.alwaysSelectAsActiveMesh || mesh.subMeshes.length === 1 || subMesh.isInFrustum(this._frustumPlanes)) {
-                var material = subMesh.getMaterial();
-
+            if (this.dispatchAllSubMeshesOfActiveMeshes || mesh.alwaysSelectAsActiveMesh || mesh.subMeshes.length === 1 || subMesh.isInFrustum(this._frustumPlanes)) {
                 if (mesh.showSubMeshesBoundingBox) {
-                    let boundingInfo = subMesh.getBoundingInfo();
-
-                    this.getBoundingBoxRenderer().renderList.push(boundingInfo.boundingBox);
+                    const boundingInfo = subMesh.getBoundingInfo();
+                    if (boundingInfo !== null && boundingInfo !== undefined) {
+                        this.getBoundingBoxRenderer().renderList.push(boundingInfo.boundingBox);
+                    }
                 }
 
-                if (material) {
+                const material = subMesh.getMaterial();
+                if (material !== null && material !== undefined) {
                     // Render targets
-                    if (material.getRenderTargetTextures) {
+                    if (material.getRenderTargetTextures !== undefined) {
                         if (this._processedMaterials.indexOf(material) === -1) {
                             this._processedMaterials.push(material);
 
@@ -2939,7 +2979,7 @@
 
                     // Dispatch
                     this._activeIndices.addCount(subMesh.indexCount, false);
-                    this._renderingManager.dispatch(subMesh);
+                    this._renderingManager.dispatch(subMesh, mesh, material);
                 }
             }
         }
@@ -2948,6 +2988,14 @@
             return this._intermediateRendering
         }
 
+        private _activeMeshCandidateProvider: IActiveMeshCandidateProvider;
+        public setActiveMeshCandidateProvider(provider: IActiveMeshCandidateProvider): void {
+            this._activeMeshCandidateProvider = provider;
+        }
+        public getActiveMeshCandidateProvider(): IActiveMeshCandidateProvider {
+            return this._activeMeshCandidateProvider;
+        }
+
         private _activeMeshesFrozen = false;
 
         /**
@@ -2992,18 +3040,32 @@
             // Meshes
             var meshes: AbstractMesh[];
             var len: number;
-
-            if (this._selectionOctree) { // Octree
+            var checkIsEnabled = true;
+
+            // Determine mesh candidates
+            if (this._activeMeshCandidateProvider !== undefined) {
+                // Use _activeMeshCandidateProvider
+                meshes = this._activeMeshCandidateProvider.getMeshes(this);
+                checkIsEnabled = this._activeMeshCandidateProvider.checksIsEnabled === false;
+                if (meshes !== undefined) {
+                    len = meshes.length;
+                } else {
+                    len = 0;
+                }
+            } else if (this._selectionOctree !== undefined) {
+                // Octree
                 var selection = this._selectionOctree.select(this._frustumPlanes);
                 meshes = selection.data;
                 len = selection.length;
-            } else { // Full scene traversal
+            } else {
+                // Full scene traversal
                 len = this.meshes.length;
                 meshes = this.meshes;
             }
 
-            for (var meshIndex = 0; meshIndex < len; meshIndex++) {
-                var mesh = meshes[meshIndex];
+            // Check each mesh
+            for (var meshIndex = 0, mesh, meshLOD; meshIndex < len; meshIndex++) {
+                mesh = meshes[meshIndex];
 
                 if (mesh.isBlocked) {
                     continue;
@@ -3011,7 +3073,7 @@
 
                 this._totalVertices.addCount(mesh.getTotalVertices(), false);
 
-                if (!mesh.isReady() || !mesh.isEnabled()) {
+                if (!mesh.isReady() || (checkIsEnabled && !mesh.isEnabled())) {
                     continue;
                 }
 
@@ -3023,9 +3085,9 @@
                 }
 
                 // Switch to current LOD
-                var meshLOD = mesh.getLOD(this.activeCamera);
+                meshLOD = mesh.getLOD(this.activeCamera);
 
-                if (!meshLOD) {
+                if (meshLOD === undefined || meshLOD === null) {
                     continue;
                 }
 
@@ -3068,7 +3130,7 @@
         }
 
         private _activeMesh(sourceMesh: AbstractMesh, mesh: AbstractMesh): void {
-            if (mesh.skeleton && this.skeletonsEnabled) {
+            if (this.skeletonsEnabled && mesh.skeleton !== null && mesh.skeleton !== undefined) {
                 if (this._activeSkeletons.pushNoDuplicate(mesh.skeleton)) {
                     mesh.skeleton.prepare();
                 }
@@ -3084,12 +3146,15 @@
                 this.getBoundingBoxRenderer().renderList.push(boundingInfo.boundingBox);
             }
 
-            if (mesh && mesh.subMeshes) {
+            if (
+                mesh !== undefined && mesh !== null
+                && mesh.subMeshes !== undefined && mesh.subMeshes !== null && mesh.subMeshes.length > 0
+            ) {
                 // Submeshes Octrees
                 var len: number;
                 var subMeshes: SubMesh[];
 
-                if (mesh._submeshesOctree && mesh.useOctreeForRenderingSelection) {
+                if (mesh.useOctreeForRenderingSelection && mesh._submeshesOctree !== undefined && mesh._submeshesOctree !== null) {
                     var intersections = mesh._submeshesOctree.select(this._frustumPlanes);
 
                     len = intersections.length;
@@ -3099,8 +3164,8 @@
                     len = subMeshes.length;
                 }
 
-                for (var subIndex = 0; subIndex < len; subIndex++) {
-                    var subMesh = subMeshes[subIndex];
+                for (var subIndex = 0, subMesh; subIndex < len; subIndex++) {
+                    subMesh = subMeshes[subIndex];
 
                     this._evaluateSubMesh(subMesh, mesh);
                 }
@@ -3812,6 +3877,11 @@
             this._meshesForIntersections.dispose();
             this._toBeDisposed.dispose();
 
+            // Abort active requests
+            for (let request of this._activeRequests) {
+                request.abort();
+            }
+
             // Debug layer
             if (this._debugLayer) {
                 this._debugLayer.hide();
@@ -4441,7 +4511,7 @@
                     camera = freeCamera;
                 }
                 camera.minZ = radius * 0.01;
-                camera.maxZ = radius * 100;
+                camera.maxZ = radius * 1000;
                 camera.speed = radius * 0.2;
                 this.activeCamera = camera;
 
@@ -4587,5 +4657,14 @@
                 material.markAsDirty(flag);
             }
         }
+
+        public _loadFile(url: string, onSuccess: (data: string | ArrayBuffer, responseURL?: string) => void, onProgress?: (data: any) => void, useDatabase?: boolean, useArrayBuffer?: boolean, onError?: (request?: XMLHttpRequest, exception?: any) => void): IFileRequest {
+            let request = Tools.LoadFile(url, onSuccess, onProgress, useDatabase ? this.database : undefined, useArrayBuffer, onError);
+            this._activeRequests.push(request);
+            request.onCompleteObservable.add(request => {
+                this._activeRequests.splice(this._activeRequests.indexOf(request), 1);
+            });
+            return request;
+        }
     }
 }

BIN=BIN
tests/validation/ReferenceImages/Flat2009.png


BIN=BIN
tests/validation/ReferenceImages/GLTF LOD.png


BIN=BIN
tests/validation/ReferenceImages/SpaceDeK.png


+ 0 - 0
tests/validation/ReferenceImages/instancedBones.png


Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio