Explorar o código

Merge remote-tracking branch 'refs/remotes/BabylonJS/master' into AddingCellIdToSpriteGui

DESKTOP-QJU4N0L\mityh %!s(int64=7) %!d(string=hai) anos
pai
achega
49aa16fe4c
Modificáronse 50 ficheiros con 29733 adicións e 29063 borrados
  1. 5 5
      Playground/js/frame.js
  2. 1 1
      Playground/js/index.js
  3. 15 0
      Tools/Gulp/config.json
  4. 0 40
      Viewer/assets/templates/default/defaultTemplate.html
  5. 12 25
      Viewer/assets/templates/default/defaultViewer.html
  6. 15 0
      Viewer/assets/templates/default/loadingScreen.html
  7. 26 0
      Viewer/assets/templates/default/navbar.html
  8. 17 5
      Viewer/assets/templates/default/overlay.html
  9. 7 4
      Viewer/dist/basicExample.html
  10. 6 6
      Viewer/dist/domExample.html
  11. 944 838
      Viewer/dist/viewer.js
  12. 37 157
      Viewer/src/configuration/configuration.ts
  13. 9 6
      Viewer/src/configuration/loader.ts
  14. 1 1
      Viewer/src/configuration/mappers.ts
  15. 126 0
      Viewer/src/configuration/types/default.ts
  16. 16 0
      Viewer/src/configuration/types/index.ts
  17. 34 0
      Viewer/src/configuration/types/minimal.ts
  18. 9 4
      Viewer/src/index.ts
  19. 33 21
      Viewer/src/templateManager.ts
  20. 77 49
      Viewer/src/viewer/defaultViewer.ts
  21. 1 1
      Viewer/src/viewer/viewer.ts
  22. 1 1
      Viewer/webpack.config.js
  23. 5750 5712
      dist/preview release/babylon.d.ts
  24. 28 28
      dist/preview release/babylon.js
  25. 59 1
      dist/preview release/babylon.max.js
  26. 5750 5712
      dist/preview release/babylon.module.d.ts
  27. 23 23
      dist/preview release/babylon.worker.js
  28. 8198 8179
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.d.ts
  29. 29 29
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.js
  30. 59 1
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js
  31. 8198 8179
      dist/preview release/customConfigurations/minimalGLTFViewer/babylon.module.d.ts
  32. 4 4
      dist/preview release/inspector/babylon.inspector.bundle.js
  33. 11 3
      dist/preview release/inspector/babylon.inspector.js
  34. 4 4
      dist/preview release/inspector/babylon.inspector.min.js
  35. 5 3
      inspector/src/adapters/MeshAdapter.ts
  36. 7 0
      inspector/src/tabs/StatsTab.ts
  37. 46 4
      src/Engine/babylon.engine.ts
  38. 2 1
      src/Engine/babylon.nullEngine.ts
  39. 9 0
      src/Engine/babylon.webgl2.ts
  40. 50 3
      src/Instrumentation/babylon.sceneInstrumentation.ts
  41. 6 3
      src/Materials/babylon.effect.ts
  42. 22 1
      src/Particles/babylon.gpuParticleSystem.ts
  43. 4 0
      src/Shaders/gpuRenderParticles.fragment.fx
  44. 12 0
      src/Shaders/gpuRenderParticles.vertex.fx
  45. 5 0
      src/Shaders/gpuUpdateParticles.fragment.fx
  46. 34 0
      src/Shaders/gpuUpdateParticles.vertex.fx
  47. 4 0
      src/Tools/babylon.observable.ts
  48. 1 0
      src/babylon.mixins.ts
  49. 13 1
      src/babylon.scene.ts
  50. 8 8
      tests/validation/validation.js

+ 5 - 5
Playground/js/frame.js

@@ -1,5 +1,5 @@
 (function () {
-    var snippetUrl = "https://babylonjs-api2.azurewebsites.net/snippets";
+    var snippetUrl = "//babylonjs-api2.azurewebsites.net/snippets";
     var currentSnippetToken;
     var engine;
     var fpsLabel = document.getElementById("fpsLabel");
@@ -43,7 +43,7 @@
         xhr.send(null);
     };
 
-    var showError = function(error) {
+    var showError = function (error) {
         console.warn(error);
     };
 
@@ -61,7 +61,7 @@
             }
 
             var canvas = document.getElementById("renderCanvas");
-            engine = new BABYLON.Engine(canvas, true, {stencil: true});
+            engine = new BABYLON.Engine(canvas, true, { stencil: true });
             BABYLON.Camera.ForceAttachControlToAlwaysPreventDefault = true;
 
             engine.runRenderLoop(function () {
@@ -158,7 +158,7 @@
 
                             if (refresh) {
                                 refresh.addEventListener("click", function () {
-                                compileAndRun(snippetCode);
+                                    compileAndRun(snippetCode);
                                 });
                             }
                         }
@@ -167,7 +167,7 @@
 
                 var hash = location.hash.substr(1);
                 currentSnippetToken = hash.split("#")[0];
-                if(!hash.split("#")[1]) hash += "#0";
+                if (!hash.split("#")[1]) hash += "#0";
 
                 xmlHttp.open("GET", snippetUrl + "/" + hash.replace("#", "/"));
                 xmlHttp.send();

+ 1 - 1
Playground/js/index.js

@@ -75,7 +75,7 @@
             markDirty();
         });
 
-        var snippetUrl = "https://babylonjs-api2.azurewebsites.net/snippets";
+        var snippetUrl = "//babylonjs-api2.azurewebsites.net/snippets";
         var currentSnippetToken;
         var currentSnippetTitle = null;
         var currentSnippetDescription = null;

+ 15 - 0
Tools/Gulp/config.json

@@ -26,6 +26,7 @@
             "picking",
             "collisions",
             "particles",
+            "gpuParticles",
             "solidParticles",
             "additionalMeshes",
             "meshBuilder",
@@ -220,6 +221,20 @@
                 "particles.fragment"
             ]
         },
+        "gpuParticles": {
+            "files": [
+                "../../src/Particles/babylon.gpuParticleSystem.js"
+            ],
+            "dependUpon": [
+                "core"
+            ],
+            "shaders": [
+                "gpuRenderParticles.vertex",
+                "gpuRenderParticles.fragment",
+                "gpuUpdateParticles.vertex",
+                "gpuUpdateParticles.fragment"
+            ]
+        },
         "nullEngine": {
             "files": [
                 "../../src/Engine/babylon.nullEngine.js"

+ 0 - 40
Viewer/assets/templates/default/defaultTemplate.html

@@ -1,43 +1,3 @@
-<style>
-    loading-screen {
-        position: absolute;
-        z-index: 100;
-        opacity: 1;
-        pointer-events: none;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        -webkit-transition: opacity 2s ease;
-        -moz-transition: opacity 2s ease;
-        transition: opacity 2s ease;
-    }
-
-    viewer {
-        position: relative;
-        overflow: hidden;
-        /* Start stage */
-        flex: 1;
-        z-index: 1;
-        justify-content: center;
-        align-items: center;
-
-        width: 100%;
-        height: 100%;
-    }
-
-    overlay {
-        position: absolute;
-        z-index: 99;
-        opacity: 0;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        -webkit-transition: opacity 1s ease;
-        -moz-transition: opacity 1s ease;
-        transition: opacity 1s ease;
-    }
-</style>
-
 <viewer></viewer>
 <loading-screen></loading-screen>
 <overlay></overlay>

+ 12 - 25
Viewer/assets/templates/default/defaultViewer.html

@@ -1,35 +1,22 @@
 <style>
-    .babylonjs-canvas {
+    viewer {
+        position: relative;
+        overflow: hidden;
+        /* Start stage */
         flex: 1;
+        z-index: 1;
+        justify-content: center;
+        align-items: center;
+
         width: 100%;
         height: 100%;
-        touch-action: none;
     }
 
-    nav-bar {
-        position: absolute;
-        height: 160px;
+    .babylonjs-canvas {
+        flex: 1;
         width: 100%;
-        bottom: 0;
-        background-color: rgba(0, 0, 0, 0.3);
-        color: white;
-        transition: 1s;
-        align-items: flex-start;
-        justify-content: space-around;
-        display: flex;
-
-        flex-direction: column;
-    }
-
-    /* Big screens have room for the entire navbar */
-
-    @media screen and (min-width: 768px) {
-        nav-bar {
-            align-items: center;
-            flex-direction: row;
-            justify-content: space-between;
-            height: 80px;
-        }
+        height: 100%;
+        touch-action: none;
     }
 </style>
 

+ 15 - 0
Viewer/assets/templates/default/loadingScreen.html

@@ -1,4 +1,19 @@
 <style>
+    /* Loading Screen element */
+
+    loading-screen {
+        position: absolute;
+        z-index: 100;
+        opacity: 1;
+        pointer-events: none;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        -webkit-transition: opacity 2s ease;
+        -moz-transition: opacity 2s ease;
+        transition: opacity 2s ease;
+    }
+
     img.loading-image {
         -webkit-animation: spin 2s linear infinite;
         animation: spin 2s linear infinite;

+ 26 - 0
Viewer/assets/templates/default/navbar.html

@@ -1,4 +1,30 @@
 <style>
+    nav-bar {
+        position: absolute;
+        height: 160px;
+        width: 100%;
+        bottom: 0;
+        background-color: rgba(0, 0, 0, 0.3);
+        color: white;
+        transition: 1s;
+        align-items: flex-start;
+        justify-content: space-around;
+        display: flex;
+
+        flex-direction: column;
+    }
+
+    /* Big screens have room for the entire navbar */
+
+    @media screen and (min-width: 768px) {
+        nav-bar {
+            align-items: center;
+            flex-direction: row;
+            justify-content: space-between;
+            height: 80px;
+        }
+    }
+
     div.flex-container {
         display: flex;
         width: 100%;

+ 17 - 5
Viewer/assets/templates/default/overlay.html

@@ -1,5 +1,17 @@
 <style>
-    .overlay {
+    overlay {
+        position: absolute;
+        z-index: 99;
+        opacity: 0;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        -webkit-transition: opacity 1s ease;
+        -moz-transition: opacity 1s ease;
+        transition: opacity 1s ease;
+    }
+
+    .overlay-item {
         width: 100%;
         height: 100%;
         display: none;
@@ -8,7 +20,7 @@
         background-color: rgba(121, 121, 121, 0.3);
     }
 
-    error.overlay {
+    error.overlay-item {
         background-color: rgba(121, 121, 121, 1);
     }
 
@@ -29,6 +41,6 @@
 <div id="close-button">
     <img src="{{closeImage}}" alt="{{closeText}}">
 </div>
-<help class="overlay"></help>
-<error class="overlay"></error>
-<share class="overlay"></share>
+<help class="overlay-item"></help>
+<error class="overlay-item"></error>
+<share class="overlay-item"></share>

+ 7 - 4
Viewer/dist/basicExample.html

@@ -18,11 +18,14 @@
 
     <body>
         <babylon model.title="Amazing Rabbit" model.subtitle="BabylonJS" model.thumbnail="https://www.babylonjs.com/img/favicon/apple-icon-144x144.png"
-            model.url="https://playground.babylonjs.com/scenes/Rabbit.babylon" default-viewer="true" templates.nav-bar.params.disable-on-fullscreen="true"></babylon>
+            model.url="https://playground.babylonjs.com/scenes/Rabbit.babylon" camera.behaviors.auto-rotate="0" templates.nav-bar.params.disable-on-fullscreen="true"></babylon>
         <script src="viewer.js"></script>
+        <script>
+            // a simple way of disabling auto init 
+            BabylonViewer.disableInit = true;
+            // Initializing the viewer on specific HTML tags.
+            BabylonViewer.InitTags('babylon');
+        </script>
     </body>
 
-</html>
-<html>
-
 </html>

+ 6 - 6
Viewer/dist/domExample.html

@@ -15,21 +15,21 @@
     </head>
 
     <body>
-        <babylon default-viewer="true">
+        <babylon extends="minimal">
             <model url="https://ugcorigin.s-microsoft.com/12/2e77b8e3-0000-0000-7a48-6505db2f0ef9/952/1508427934473.gltf" title="The Bus!"
                 subtitle="Remix3D" thumbnail="http://d33wubrfki0l68.cloudfront.net/7e08139ddee0ec38005f4232346c7f7386831300/fd934/githubuniverse/remix3d.png">
             </model>
             <camera>
-                <behaviors array="true">
-                    <behavior type="0"></behavior>
+                <behaviors>
+                    <auto-rotate type="0"></auto-rotate>
                 </behaviors>
             </camera>
-            <lights array="true">
-                <light type="1" shadow-enabled="true" position.y="0.5" direction.y="-1" intensity="4.5">
+            <lights>
+                <light1 type="1" shadow-enabled="true" position.y="0.5" direction.y="-1" intensity="4.5">
                     <shadow-config use-blur-exponential-shadow-map="true" use-kernel-blur="true" blur-kernel="64" blur-scale="4">
 
                     </shadow-config>
-                </light>
+                </light1>
             </lights>
         </babylon>
         <script src="viewer.js"></script>

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 944 - 838
Viewer/dist/viewer.js


A diferenza do arquivo foi suprimida porque é demasiado grande
+ 37 - 157
Viewer/src/configuration/configuration.ts


+ 9 - 6
Viewer/src/configuration/loader.ts

@@ -1,5 +1,6 @@
 import { mapperManager } from './mappers';
-import { ViewerConfiguration, defaultConfiguration } from './configuration';
+import { ViewerConfiguration } from './configuration';
+import { getConfigurationType } from './types';
 
 import * as merge from 'lodash.merge';
 
@@ -11,11 +12,9 @@ export class ConfigurationLoader {
 
         let loadedConfig = merge({}, initConfig);
 
-        if (loadedConfig.defaultViewer) {
-            loadedConfig = merge(loadedConfig, defaultConfiguration);
-        } else {
-            loadedConfig = merge(defaultConfiguration, loadedConfig);
-        }
+        let extendedConfiguration = getConfigurationType(loadedConfig && loadedConfig.extends);
+
+        loadedConfig = merge(extendedConfiguration, loadedConfig);
 
         if (loadedConfig.configuration) {
 
@@ -42,6 +41,10 @@ export class ConfigurationLoader {
         }
     }
 
+    public getConfigurationType(type: string) {
+
+    }
+
     private loadFile(url: string): Promise<any> {
         let cacheReference = this.configurationCache;
         if (cacheReference[url]) {

+ 1 - 1
Viewer/src/configuration/mappers.ts

@@ -22,7 +22,7 @@ class HTMLMapper implements IMapper {
                 //convert html-style to json-style
                 let camelKey = kebabToCamel(key);
                 if (idx === split.length - 1) {
-                    let val: any = attr.nodeValue;
+                    let val: any = attr.nodeValue; // firefox warns nodeValue is deprecated, but I found no sign of it anywhere.
                     if (val === "true") {
                         val = true;
                     } else if (val === "false") {

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 126 - 0
Viewer/src/configuration/types/default.ts


+ 16 - 0
Viewer/src/configuration/types/index.ts

@@ -0,0 +1,16 @@
+import { minimalConfiguration } from './minimal';
+import { defaultConfiguration } from './default';
+
+let getConfigurationType = function (type: string) {
+    switch (type) {
+        case 'default':
+            return defaultConfiguration;
+        case 'minimal':
+            return minimalConfiguration;
+        default:
+            return defaultConfiguration;
+    }
+
+}
+
+export { getConfigurationType, defaultConfiguration, minimalConfiguration }

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 34 - 0
Viewer/src/configuration/types/minimal.ts


+ 9 - 4
Viewer/src/index.ts

@@ -14,10 +14,15 @@ import 'babylonjs-materials';
 
 import { InitTags } from './initializer';
 
-// promise polyfill
-global.Promise = require('es6-promise').Promise;
+// promise polyfill, if needed!
+global.Promise = Promise || require('es6-promise').Promise;
 
-InitTags();
+export let disableInit: boolean = false;
+
+setTimeout(() => {
+    if (disableInit) return;
+    InitTags();
+});
 
 // public API for initialization
-export { AbstractViewer, InitTags };
+export { InitTags };

+ 33 - 21
Viewer/src/templateManager.ts

@@ -9,21 +9,21 @@ export interface ITemplateConfiguration {
     params?: { [key: string]: string | number | boolean | object };
     events?: {
         // pointer events
-        pointerdown?: boolean | Array<string>;
-        pointerup?: boolean | Array<string>;
-        pointermove?: boolean | Array<string>;
-        pointerover?: boolean | Array<string>;
-        pointerout?: boolean | Array<string>;
-        pointerenter?: boolean | Array<string>;
-        pointerleave?: boolean | Array<string>;
-        pointercancel?: boolean | Array<string>;
+        pointerdown?: boolean | { [id: string]: boolean; };
+        pointerup?: boolean | { [id: string]: boolean; };
+        pointermove?: boolean | { [id: string]: boolean; };
+        pointerover?: boolean | { [id: string]: boolean; };
+        pointerout?: boolean | { [id: string]: boolean; };
+        pointerenter?: boolean | { [id: string]: boolean; };
+        pointerleave?: boolean | { [id: string]: boolean; };
+        pointercancel?: boolean | { [id: string]: boolean; };
         //click, just in case
-        click?: boolean | Array<string>;
+        click?: boolean | { [id: string]: boolean; };
         // drag and drop
-        dragstart?: boolean | Array<string>;
-        drop?: boolean | Array<string>;
+        dragstart?: boolean | { [id: string]: boolean; };
+        drop?: boolean | { [id: string]: boolean; };
 
-        [key: string]: boolean | Array<string> | undefined;
+        [key: string]: boolean | { [id: string]: boolean; } | undefined;
     }
 }
 
@@ -83,7 +83,7 @@ export class TemplateManager {
         }
 
         //build the html tree
-        let htmlTree = this.buildHTMLTree(templates).then(htmlTree => {
+        this.buildHTMLTree(templates).then(htmlTree => {
             internalInit(htmlTree, 'main');
         });
     }
@@ -102,6 +102,7 @@ export class TemplateManager {
         let promises = Object.keys(templates).map(name => {
             let template = new Template(name, templates[name]);
             this.templates[name] = template;
+            return template.initPromise;
         });
 
         return Promise.all(promises).then(() => {
@@ -125,7 +126,7 @@ export class TemplateManager {
         return this.containerElement.querySelector('canvas');
     }
 
-    public getTemplate(name: string) {
+    public getTemplate(name: string): Template | undefined {
         return this.templates[name];
     }
 
@@ -196,8 +197,14 @@ export class Template {
 
     public getChildElements(): Array<string> {
         let childrenArray: string[] = [];
-        for (let i = 0; i < this.fragment.children.length; ++i) {
-            childrenArray.push(kebabToCamel(this.fragment.children.item(i).nodeName.toLowerCase()));
+        //Edge and IE don't support frage,ent.children
+        let children = this.fragment.children;
+        if (!children) {
+            // casting to HTMLCollection, as both NodeListOf and HTMLCollection have 'item()' and 'length'.
+            children = <HTMLCollection>this.fragment.querySelectorAll('*');
+        }
+        for (let i = 0; i < children.length; ++i) {
+            childrenArray.push(kebabToCamel(children.item(i).nodeName.toLowerCase()));
         }
         return childrenArray;
     }
@@ -251,7 +258,7 @@ export class Template {
     // TODO - Should events be removed as well? when are templates disposed?
     private registerEvents() {
         if (this._configuration.events) {
-            Object.keys(this._configuration.events).forEach(eventName => {
+            for (let eventName in this._configuration.events) {
                 if (this._configuration.events && this._configuration.events[eventName]) {
                     let functionToFire = (selector, event) => {
                         this.onEventTriggered.notifyObservers({ event: event, template: this, selector: selector });
@@ -260,15 +267,20 @@ export class Template {
                     // if boolean, set the parent as the event listener
                     if (typeof this._configuration.events[eventName] === 'boolean') {
                         this.parent.addEventListener(eventName, functionToFire.bind(this, '#' + this.parent.id), false);
-                    } else {
-                        let selectorsArray: Array<string> = <Array<string>>this._configuration.events[eventName];
-                        selectorsArray.forEach(selector => {
+                    } else if (typeof this._configuration.events[eventName] === 'object') {
+                        let selectorsArray: Array<string> = Object.keys(this._configuration.events[eventName] || {});
+                        // strict null checl is working incorrectly, must override:
+                        let event = this._configuration.events[eventName] || {};
+                        selectorsArray.filter(selector => event[selector]).forEach(selector => {
+                            if (selector.indexOf('#') !== 0) {
+                                selector = '#' + selector;
+                            }
                             let htmlElement = <HTMLElement>this.parent.querySelector(selector);
                             htmlElement && htmlElement.addEventListener(eventName, functionToFire.bind(this, selector), false)
                         });
                     }
                 }
-            });
+            }
         }
     }
 

+ 77 - 49
Viewer/src/viewer/defaultViewer.ts

@@ -25,42 +25,49 @@ export class DefaultViewer extends AbstractViewer {
         // navbar
         let viewerElement = this.templateManager.getTemplate('viewer');
         let navbar = this.templateManager.getTemplate('navBar');
-        let navbarHeight = navbar.parent.clientHeight + 'px';
-
-        let navbarShown: boolean = true;
-        let timeoutCancel /*: number*/;
-
-        let triggerNavbar = function (show: boolean = false, evt: PointerEvent) {
-            // only left-click on no-button.
-            if (evt.button > 0) return;
-            // clear timeout
-            timeoutCancel && clearTimeout(timeoutCancel);
-            // if state is the same, do nothing
-            if (show === navbarShown) return;
-            //showing? simply show it!
-            if (show) {
-                navbar.parent.style.bottom = show ? '0px' : '-' + navbarHeight;
-                navbarShown = show;
-            } else {
-                let visibilityTimeout = 2000;
-                if (navbar.configuration.params && navbar.configuration.params.visibilityTimeout !== undefined) {
-                    visibilityTimeout = <number>navbar.configuration.params.visibilityTimeout;
-                }
-                // not showing? set timeout until it is removed.
-                timeoutCancel = setTimeout(function () {
-                    navbar.parent.style.bottom = '-' + navbarHeight;
+        if (viewerElement && navbar) {
+            let navbarHeight = navbar.parent.clientHeight + 'px';
+
+            let navbarShown: boolean = true;
+            let timeoutCancel /*: number*/;
+
+            let triggerNavbar = function (show: boolean = false, evt: PointerEvent) {
+                // only left-click on no-button.
+                if (!navbar || evt.button > 0) return;
+                // clear timeout
+                timeoutCancel && clearTimeout(timeoutCancel);
+                // if state is the same, do nothing
+                if (show === navbarShown) return;
+                //showing? simply show it!
+                if (show) {
+                    navbar.parent.style.bottom = show ? '0px' : '-' + navbarHeight;
                     navbarShown = show;
-                }, visibilityTimeout);
+                } else {
+                    let visibilityTimeout = 2000;
+                    if (navbar.configuration.params && navbar.configuration.params.visibilityTimeout !== undefined) {
+                        visibilityTimeout = <number>navbar.configuration.params.visibilityTimeout;
+                    }
+                    // not showing? set timeout until it is removed.
+                    timeoutCancel = setTimeout(function () {
+                        if (navbar) {
+                            navbar.parent.style.bottom = '-' + navbarHeight;
+                        }
+                        navbarShown = show;
+                    }, visibilityTimeout);
+                }
             }
-        }
 
 
 
-        viewerElement.parent.addEventListener('pointerout', triggerNavbar.bind(this, false));
-        viewerElement.parent.addEventListener('pointerdown', triggerNavbar.bind(this, true));
-        viewerElement.parent.addEventListener('pointerup', triggerNavbar.bind(this, false));
-        navbar.parent.addEventListener('pointerover', triggerNavbar.bind(this, true))
-        // triggerNavbar(false);
+            viewerElement.parent.addEventListener('pointerout', triggerNavbar.bind(this, false));
+            viewerElement.parent.addEventListener('pointerdown', triggerNavbar.bind(this, true));
+            viewerElement.parent.addEventListener('pointerup', triggerNavbar.bind(this, false));
+            navbar.parent.addEventListener('pointerover', triggerNavbar.bind(this, true))
+            // triggerNavbar(false);
+
+            // events registration
+            this.registerNavbarButtons();
+        }
 
         // close overlay button
         let closeButton = document.getElementById('close-button');
@@ -70,9 +77,6 @@ export class DefaultViewer extends AbstractViewer {
             })
         }
 
-        // events registration
-        this.registerNavbarButtons();
-
         return super.onTemplatesLoaded();
     }
 
@@ -80,7 +84,11 @@ export class DefaultViewer extends AbstractViewer {
         let isFullscreen = false;
 
         let navbar = this.templateManager.getTemplate('navBar');
-        let viewerElement = this.templateManager.getTemplate('viewer').parent;
+        let viewerTemplate = this.templateManager.getTemplate('viewer');
+        if (!navbar || !viewerTemplate) return;
+
+        let viewerElement = viewerTemplate.parent;
+
 
         navbar.onEventTriggered.add((data) => {
             switch (data.event.type) {
@@ -90,7 +98,7 @@ export class DefaultViewer extends AbstractViewer {
                         switch (data.selector) {
                             case '#fullscreen-button':
                                 if (!isFullscreen) {
-                                    let requestFullScreen = viewerElement.requestFullscreen || viewerElement.webkitRequestFullscreen; // || viewerElement.parent.msRequestFullscreen || viewerElement.parent.mozRequestFullScreen 
+                                    let requestFullScreen = viewerElement.requestFullscreen || viewerElement.webkitRequestFullscreen || (<any>viewerElement).msRequestFullscreen || (<any>viewerElement).mozRequestFullScreen;
                                     requestFullScreen.call(viewerElement);
                                 } else {
                                     let exitFullscreen = document.exitFullscreen || document.webkitExitFullscreen
@@ -107,7 +115,6 @@ export class DefaultViewer extends AbstractViewer {
                     break;
             }
         });
-
     }
 
     protected prepareContainerElement() {
@@ -117,7 +124,9 @@ export class DefaultViewer extends AbstractViewer {
 
     public loadModel(model: any = this.configuration.model): Promise<Scene> {
         this.showLoadingScreen();
-        return super.loadModel(model, true).catch(() => {
+        return super.loadModel(model, true).catch((error) => {
+            console.log(error);
+            this.hideLoadingScreen();
             this.showOverlayScreen('error');
             return this.scene;
         });
@@ -148,6 +157,7 @@ export class DefaultViewer extends AbstractViewer {
 
     private setModelMetaData() {
         let navbar = this.templateManager.getTemplate('navBar');
+        if (!navbar) return;
 
         let metadataContainer = navbar.parent.querySelector('#model-metadata');
 
@@ -225,7 +235,10 @@ export class DefaultViewer extends AbstractViewer {
     }
 
     public showOverlayScreen(subScreen: string) {
-        return this.templateManager.getTemplate('overlay').show((template => {
+        let template = this.templateManager.getTemplate('overlay');
+        if (!template) return Promise.reject('Overlay template not found');
+
+        return template.show((template => {
 
             var canvasRect = this.containerElement.getBoundingClientRect();
             var canvasPositioning = window.getComputedStyle(this.containerElement).position;
@@ -235,7 +248,11 @@ export class DefaultViewer extends AbstractViewer {
             template.parent.style.height = canvasRect.height + "px";
             template.parent.style.opacity = "1";
 
-            return this.templateManager.getTemplate(subScreen).show((template => {
+            let subTemplate = this.templateManager.getTemplate(subScreen);
+            if (!subTemplate) {
+                return Promise.reject(subScreen + ' template not found');
+            }
+            return subTemplate.show((template => {
                 template.parent.style.display = 'flex';
                 return Promise.resolve(template);
             }));
@@ -243,7 +260,10 @@ export class DefaultViewer extends AbstractViewer {
     }
 
     public hideOverlayScreen() {
-        return this.templateManager.getTemplate('overlay').hide((template => {
+        let template = this.templateManager.getTemplate('overlay');
+        if (!template) return Promise.reject('Overlay template not found');
+
+        return template.hide((template => {
             template.parent.style.opacity = "0";
             let onTransitionEnd = () => {
                 template.parent.removeEventListener("transitionend", onTransitionEnd);
@@ -268,7 +288,10 @@ export class DefaultViewer extends AbstractViewer {
     }
 
     public showLoadingScreen() {
-        return this.templateManager.getTemplate('loadingScreen').show((template => {
+        let template = this.templateManager.getTemplate('loadingScreen');
+        if (!template) return Promise.reject('oading Screen template not found');
+
+        return template.show((template => {
 
             var canvasRect = this.containerElement.getBoundingClientRect();
             var canvasPositioning = window.getComputedStyle(this.containerElement).position;
@@ -284,7 +307,10 @@ export class DefaultViewer extends AbstractViewer {
     }
 
     public hideLoadingScreen() {
-        return this.templateManager.getTemplate('loadingScreen').hide((template => {
+        let template = this.templateManager.getTemplate('loadingScreen');
+        if (!template) return Promise.reject('oading Screen template not found');
+
+        return template.hide((template => {
             template.parent.style.opacity = "0";
             let onTransitionEnd = () => {
                 template.parent.removeEventListener("transitionend", onTransitionEnd);
@@ -299,14 +325,15 @@ export class DefaultViewer extends AbstractViewer {
 
         let sceneConfig = this.configuration.scene || { defaultLight: true };
 
-        if (!sceneConfig.defaultLight && (this.configuration.lights && this.configuration.lights.length)) {
+        if (!sceneConfig.defaultLight && (this.configuration.lights && Object.keys(this.configuration.lights).length)) {
             // remove old lights
             this.scene.lights.forEach(l => {
                 l.dispose();
             });
 
-            this.configuration.lights.forEach((lightConfig, idx) => {
-                lightConfig.name = lightConfig.name || 'light-' + idx;
+            Object.keys(this.configuration.lights).forEach((name, idx) => {
+                let lightConfig = this.configuration.lights && this.configuration.lights[name] || { name: name, type: 0 };
+                lightConfig.name = name;
                 let constructor = Light.GetConstructorFromName(lightConfig.type, lightConfig.name, this.scene);
                 let light = constructor();
 
@@ -354,9 +381,9 @@ export class DefaultViewer extends AbstractViewer {
         this.camera.maxZ = cameraConfig.maxZ || this.camera.maxZ;
 
         if (cameraConfig.behaviors) {
-            cameraConfig.behaviors.forEach((behaviorConfig) => {
-                this.setCameraBehavior(behaviorConfig, focusMeshes);
-            });
+            for (let name in cameraConfig.behaviors) {
+                this.setCameraBehavior(cameraConfig.behaviors[name], focusMeshes);
+            }
         };
 
         if (sceneConfig.autoRotate) {
@@ -415,6 +442,7 @@ export class DefaultViewer extends AbstractViewer {
     }
 
     private extendClassWithConfig(object: any, config: any) {
+        if (!config) return;
         Object.keys(config).forEach(key => {
             if (key in object && typeof object[key] !== 'function') {
                 if (typeof object[key] === 'function') return;

+ 1 - 1
Viewer/src/viewer/viewer.ts

@@ -13,7 +13,7 @@ export abstract class AbstractViewer {
 
     protected configuration: ViewerConfiguration;
 
-    constructor(public containerElement: HTMLElement, initialConfiguration: ViewerConfiguration = { defaultViewer: true }) {
+    constructor(public containerElement: HTMLElement, initialConfiguration: ViewerConfiguration = {}) {
         // if exists, use the container id. otherwise, generate a random string.
         if (containerElement.id) {
             this.baseId = containerElement.id;

+ 1 - 1
Viewer/webpack.config.js

@@ -11,7 +11,7 @@ module.exports = {
         path: path.resolve(__dirname, 'dist'),
         filename: '[name].js',
         libraryTarget: 'umd',
-        library: 'Viewer3D',
+        library: 'BabylonViewer',
         umdNamedDefine: true,
         devtoolModuleFilenameTemplate: '[absolute-resource-path]'
     },

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


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


+ 59 - 1
dist/preview release/babylon.max.js

@@ -5549,6 +5549,9 @@ var BABYLON;
          */
         Observable.prototype.notifyObservers = function (eventData, mask, target, currentTarget) {
             if (mask === void 0) { mask = -1; }
+            if (!this._observers.length) {
+                return false;
+            }
             var state = this._eventState;
             state.mask = mask;
             state.target = target;
@@ -9188,7 +9191,6 @@ var BABYLON;
                 return _this._vrDisplay = devices[0];
             };
             if (navigator.getVRDisplays) {
-                // TODO: Backwards compatible for 1.0?
                 navigator.getVRDisplays().then(getWebVRDevices).then(callback).catch(function (error) {
                     // TODO: System CANNOT support WebVR, despite API presence.
                     _this._vrSupported = false;
@@ -17347,6 +17349,11 @@ var BABYLON;
             */
             this.onBeforeAnimationsObservable = new BABYLON.Observable();
             /**
+            * An event triggered after animations processing
+            * @type {BABYLON.Observable}
+            */
+            this.onAfterAnimationsObservable = new BABYLON.Observable();
+            /**
             * An event triggered before draw calls are ready to be sent
             * @type {BABYLON.Observable}
             */
@@ -20017,6 +20024,7 @@ var BABYLON;
                     // Animations
                     this._animationRatio = defaultTimeStep * (60.0 / 1000.0);
                     this._animate();
+                    this.onAfterAnimationsObservable.notifyObservers(this);
                     // Physics
                     if (this._physicsEngine) {
                         this.onBeforePhysicsObservable.notifyObservers(this);
@@ -20036,6 +20044,7 @@ var BABYLON;
                 var deltaTime = Math.max(Scene.MinDeltaTime, Math.min(this._engine.getDeltaTime(), Scene.MaxDeltaTime));
                 this._animationRatio = deltaTime * (60.0 / 1000.0);
                 this._animate();
+                this.onAfterAnimationsObservable.notifyObservers(this);
                 // Physics
                 if (this._physicsEngine) {
                     this.onBeforePhysicsObservable.notifyObservers(this);
@@ -20371,6 +20380,8 @@ var BABYLON;
             this.onAfterDrawPhaseObservable.clear();
             this.onBeforePhysicsObservable.clear();
             this.onAfterPhysicsObservable.clear();
+            this.onBeforeAnimationsObservable.clear();
+            this.onAfterAnimationsObservable.clear();
             this.detachControl();
             // Release sounds & sounds tracks
             if (BABYLON.AudioEngine) {
@@ -75322,6 +75333,8 @@ var BABYLON;
             this._spritesRenderTime = new BABYLON.PerfCounter();
             this._capturePhysicsTime = false;
             this._physicsTime = new BABYLON.PerfCounter();
+            this._captureAnimationsTime = false;
+            this._animationsTime = new BABYLON.PerfCounter();
             // Observers
             this._onBeforeActiveMeshesEvaluationObserver = null;
             this._onAfterActiveMeshesEvaluationObserver = null;
@@ -75337,6 +75350,7 @@ var BABYLON;
             this._onAfterSpritesRenderingObserver = null;
             this._onBeforePhysicsObserver = null;
             this._onAfterPhysicsObserver = null;
+            this._onAfterAnimationsObserver = null;
             // Before render
             this._onBeforeAnimationsObserver = scene.onBeforeAnimationsObservable.add(function () {
                 if (_this._captureActiveMeshesEvaluationTime) {
@@ -75358,6 +75372,9 @@ var BABYLON;
                 if (_this._captureSpritesRenderTime) {
                     _this._spritesRenderTime.fetchNewFrame();
                 }
+                if (_this._captureAnimationsTime) {
+                    _this._animationsTime.beginMonitoring();
+                }
                 _this.scene.getEngine()._drawCalls.fetchNewFrame();
             });
             // After render
@@ -75605,6 +75622,45 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(SceneInstrumentation.prototype, "animationsTimeCounter", {
+            /**
+             * Gets the perf counter used for animations time
+             */
+            get: function () {
+                return this._animationsTime;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(SceneInstrumentation.prototype, "captureAnimationsTime", {
+            /**
+             * Gets the animations time capture status
+             */
+            get: function () {
+                return this._captureAnimationsTime;
+            },
+            /**
+             * Enable or disable the animations time capture
+             */
+            set: function (value) {
+                var _this = this;
+                if (value === this._captureAnimationsTime) {
+                    return;
+                }
+                this._captureAnimationsTime = value;
+                if (value) {
+                    this._onAfterAnimationsObserver = this.scene.onAfterAnimationsObservable.add(function () {
+                        _this._animationsTime.endMonitoring();
+                    });
+                }
+                else {
+                    this.scene.onAfterAnimationsObservable.remove(this._onAfterAnimationsObserver);
+                    this._onAfterAnimationsObserver = null;
+                }
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(SceneInstrumentation.prototype, "frameTimeCounter", {
             /**
              * Gets the perf counter used for frame time capture
@@ -75742,6 +75798,8 @@ var BABYLON;
             this._onBeforePhysicsObserver = null;
             this.scene.onAfterPhysicsObservable.remove(this._onAfterPhysicsObserver);
             this._onAfterPhysicsObserver = null;
+            this.scene.onAfterAnimationsObservable.remove(this._onAfterAnimationsObserver);
+            this._onAfterAnimationsObserver = null;
             this.scene = null;
         };
         return SceneInstrumentation;

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


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


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


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


+ 59 - 1
dist/preview release/customConfigurations/minimalGLTFViewer/babylon.max.js

@@ -5549,6 +5549,9 @@ var BABYLON;
          */
         Observable.prototype.notifyObservers = function (eventData, mask, target, currentTarget) {
             if (mask === void 0) { mask = -1; }
+            if (!this._observers.length) {
+                return false;
+            }
             var state = this._eventState;
             state.mask = mask;
             state.target = target;
@@ -9188,7 +9191,6 @@ var BABYLON;
                 return _this._vrDisplay = devices[0];
             };
             if (navigator.getVRDisplays) {
-                // TODO: Backwards compatible for 1.0?
                 navigator.getVRDisplays().then(getWebVRDevices).then(callback).catch(function (error) {
                     // TODO: System CANNOT support WebVR, despite API presence.
                     _this._vrSupported = false;
@@ -17347,6 +17349,11 @@ var BABYLON;
             */
             this.onBeforeAnimationsObservable = new BABYLON.Observable();
             /**
+            * An event triggered after animations processing
+            * @type {BABYLON.Observable}
+            */
+            this.onAfterAnimationsObservable = new BABYLON.Observable();
+            /**
             * An event triggered before draw calls are ready to be sent
             * @type {BABYLON.Observable}
             */
@@ -20017,6 +20024,7 @@ var BABYLON;
                     // Animations
                     this._animationRatio = defaultTimeStep * (60.0 / 1000.0);
                     this._animate();
+                    this.onAfterAnimationsObservable.notifyObservers(this);
                     // Physics
                     if (this._physicsEngine) {
                         this.onBeforePhysicsObservable.notifyObservers(this);
@@ -20036,6 +20044,7 @@ var BABYLON;
                 var deltaTime = Math.max(Scene.MinDeltaTime, Math.min(this._engine.getDeltaTime(), Scene.MaxDeltaTime));
                 this._animationRatio = deltaTime * (60.0 / 1000.0);
                 this._animate();
+                this.onAfterAnimationsObservable.notifyObservers(this);
                 // Physics
                 if (this._physicsEngine) {
                     this.onBeforePhysicsObservable.notifyObservers(this);
@@ -20371,6 +20380,8 @@ var BABYLON;
             this.onAfterDrawPhaseObservable.clear();
             this.onBeforePhysicsObservable.clear();
             this.onAfterPhysicsObservable.clear();
+            this.onBeforeAnimationsObservable.clear();
+            this.onAfterAnimationsObservable.clear();
             this.detachControl();
             // Release sounds & sounds tracks
             if (BABYLON.AudioEngine) {
@@ -74998,6 +75009,8 @@ var BABYLON;
             this._spritesRenderTime = new BABYLON.PerfCounter();
             this._capturePhysicsTime = false;
             this._physicsTime = new BABYLON.PerfCounter();
+            this._captureAnimationsTime = false;
+            this._animationsTime = new BABYLON.PerfCounter();
             // Observers
             this._onBeforeActiveMeshesEvaluationObserver = null;
             this._onAfterActiveMeshesEvaluationObserver = null;
@@ -75013,6 +75026,7 @@ var BABYLON;
             this._onAfterSpritesRenderingObserver = null;
             this._onBeforePhysicsObserver = null;
             this._onAfterPhysicsObserver = null;
+            this._onAfterAnimationsObserver = null;
             // Before render
             this._onBeforeAnimationsObserver = scene.onBeforeAnimationsObservable.add(function () {
                 if (_this._captureActiveMeshesEvaluationTime) {
@@ -75034,6 +75048,9 @@ var BABYLON;
                 if (_this._captureSpritesRenderTime) {
                     _this._spritesRenderTime.fetchNewFrame();
                 }
+                if (_this._captureAnimationsTime) {
+                    _this._animationsTime.beginMonitoring();
+                }
                 _this.scene.getEngine()._drawCalls.fetchNewFrame();
             });
             // After render
@@ -75281,6 +75298,45 @@ var BABYLON;
             enumerable: true,
             configurable: true
         });
+        Object.defineProperty(SceneInstrumentation.prototype, "animationsTimeCounter", {
+            /**
+             * Gets the perf counter used for animations time
+             */
+            get: function () {
+                return this._animationsTime;
+            },
+            enumerable: true,
+            configurable: true
+        });
+        Object.defineProperty(SceneInstrumentation.prototype, "captureAnimationsTime", {
+            /**
+             * Gets the animations time capture status
+             */
+            get: function () {
+                return this._captureAnimationsTime;
+            },
+            /**
+             * Enable or disable the animations time capture
+             */
+            set: function (value) {
+                var _this = this;
+                if (value === this._captureAnimationsTime) {
+                    return;
+                }
+                this._captureAnimationsTime = value;
+                if (value) {
+                    this._onAfterAnimationsObserver = this.scene.onAfterAnimationsObservable.add(function () {
+                        _this._animationsTime.endMonitoring();
+                    });
+                }
+                else {
+                    this.scene.onAfterAnimationsObservable.remove(this._onAfterAnimationsObserver);
+                    this._onAfterAnimationsObserver = null;
+                }
+            },
+            enumerable: true,
+            configurable: true
+        });
         Object.defineProperty(SceneInstrumentation.prototype, "frameTimeCounter", {
             /**
              * Gets the perf counter used for frame time capture
@@ -75418,6 +75474,8 @@ var BABYLON;
             this._onBeforePhysicsObserver = null;
             this.scene.onAfterPhysicsObservable.remove(this._onAfterPhysicsObserver);
             this._onAfterPhysicsObserver = null;
+            this.scene.onAfterAnimationsObservable.remove(this._onAfterAnimationsObserver);
+            this._onAfterAnimationsObserver = null;
             this.scene = null;
         };
         return SceneInstrumentation;

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


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


+ 11 - 3
dist/preview release/inspector/babylon.inspector.js

@@ -1304,10 +1304,11 @@ var INSPECTOR;
          */
         MeshAdapter.prototype._drawAxis = function () {
             this._obj.computeWorldMatrix();
+            var mesh = this._obj;
             // Axis
-            var x = new BABYLON.Vector3(8 / this._obj.scaling.x, 0, 0);
-            var y = new BABYLON.Vector3(0, 8 / this._obj.scaling.y, 0);
-            var z = new BABYLON.Vector3(0, 0, 8 / this._obj.scaling.z);
+            var x = new BABYLON.Vector3(8 / Math.abs(mesh.scaling.x), 0, 0);
+            var y = new BABYLON.Vector3(0, 8 / Math.abs(mesh.scaling.y), 0);
+            var z = new BABYLON.Vector3(0, 0, 8 / Math.abs(mesh.scaling.z));
             this._axesViewer = new BABYLON.Debug.AxesViewer(this._obj.getScene());
             this._axesViewer.update(this._obj.position, x, y, z);
         };
@@ -3677,6 +3678,7 @@ var INSPECTOR;
             _this._sceneInstrumentation.captureParticlesRenderTime = true;
             _this._sceneInstrumentation.captureSpritesRenderTime = true;
             _this._sceneInstrumentation.capturePhysicsTime = true;
+            _this._sceneInstrumentation.captureAnimationsTime = true;
             _this._engineInstrumentation = new BABYLON.EngineInstrumentation(_this._engine);
             _this._engineInstrumentation.captureGPUFrameTime = true;
             // Build the stats panel: a div that will contains all stats
@@ -3785,6 +3787,12 @@ var INSPECTOR;
                     elem: elemValue,
                     updateFct: function () { return BABYLON.Tools.Format(_this._sceneInstrumentation.spritesRenderTimeCounter.current); }
                 });
+                elemLabel = _this._createStatLabel("Animations", _this._panel);
+                elemValue = INSPECTOR.Helpers.CreateDiv('stat-value', _this._panel);
+                _this._updatableProperties.push({
+                    elem: elemValue,
+                    updateFct: function () { return BABYLON.Tools.Format(_this._sceneInstrumentation.animationsTimeCounter.current); }
+                });
                 elemLabel = _this._createStatLabel("Physics", _this._panel);
                 elemValue = INSPECTOR.Helpers.CreateDiv('stat-value', _this._panel);
                 _this._updatableProperties.push({

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


+ 5 - 3
inspector/src/adapters/MeshAdapter.ts

@@ -79,10 +79,12 @@ module INSPECTOR {
         private _drawAxis() {
             this._obj.computeWorldMatrix();
 
+            let mesh = this._obj as BABYLON.AbstractMesh;
+
             // Axis
-            var x = new BABYLON.Vector3(8 / (this._obj as BABYLON.AbstractMesh).scaling.x, 0, 0);
-            var y = new BABYLON.Vector3(0, 8 / (this._obj as BABYLON.AbstractMesh).scaling.y, 0);
-            var z = new BABYLON.Vector3(0, 0, 8 / (this._obj as BABYLON.AbstractMesh).scaling.z);
+            var x = new BABYLON.Vector3(8 / Math.abs(mesh.scaling.x), 0, 0);
+            var y = new BABYLON.Vector3(0, 8 / Math.abs(mesh.scaling.y), 0);
+            var z = new BABYLON.Vector3(0, 0, 8 / Math.abs(mesh.scaling.z));
 
             this._axesViewer = new BABYLON.Debug.AxesViewer(this._obj.getScene());
             this._axesViewer.update(this._obj.position, x, y, z);

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

@@ -37,6 +37,7 @@ module INSPECTOR {
             this._sceneInstrumentation.captureParticlesRenderTime = true;
             this._sceneInstrumentation.captureSpritesRenderTime = true;
             this._sceneInstrumentation.capturePhysicsTime = true;
+            this._sceneInstrumentation.captureAnimationsTime = true;
 
             this._engineInstrumentation = new BABYLON.EngineInstrumentation(this._engine);
             this._engineInstrumentation.captureGPUFrameTime = true;
@@ -161,6 +162,12 @@ module INSPECTOR {
                     elem:elemValue, 
                     updateFct:() => { return BABYLON.Tools.Format(this._sceneInstrumentation.spritesRenderTimeCounter.current)}
                 });
+                elemLabel = this._createStatLabel("Animations", this._panel);
+                elemValue = Helpers.CreateDiv('stat-value', this._panel);
+                this._updatableProperties.push({ 
+                    elem:elemValue, 
+                    updateFct:() => { return BABYLON.Tools.Format(this._sceneInstrumentation.animationsTimeCounter.current)}
+                });                       
                 elemLabel = this._createStatLabel("Physics", this._panel);
                 elemValue = Helpers.CreateDiv('stat-value', this._panel);
                 this._updatableProperties.push({ 

+ 46 - 4
src/Engine/babylon.engine.ts

@@ -1766,7 +1766,6 @@
             }
 
             if (navigator.getVRDisplays) {
-                // TODO: Backwards compatible for 1.0?
                 navigator.getVRDisplays().then(getWebVRDevices).then(callback).catch((error: () => void) => {
                     // TODO: System CANNOT support WebVR, despite API presence.
                     this._vrSupported = false;
@@ -2422,6 +2421,12 @@
         public _deleteProgram(program: WebGLProgram): void {
             if (program) {
                 program.__SPECTOR_rebuildProgram = null;
+
+                if (program.transformFeedback) {
+                    this.deleteTransformFeedback(program.transformFeedback);
+                    program.transformFeedback = null;
+                }
+
                 this._gl.deleteProgram(program);
             }
         }
@@ -2474,7 +2479,7 @@
             return this._createShaderProgram(vertexShader, fragmentShader, context);
         }
 
-        public createShaderProgram(vertexCode: string, fragmentCode: string, defines: Nullable<string>, context?: WebGLRenderingContext): WebGLProgram {
+        public createShaderProgram(vertexCode: string, fragmentCode: string, defines: Nullable<string>, context?: WebGLRenderingContext, transformFeedbackVaryings: Nullable<string[]> = null): WebGLProgram {
             context = context || this._gl;
 
             this.onBeforeShaderCompilationObservable.notifyObservers(this);
@@ -2483,14 +2488,14 @@
             var vertexShader = compileShader(context, vertexCode, "vertex", defines, shaderVersion);
             var fragmentShader = compileShader(context, fragmentCode, "fragment", defines, shaderVersion);
 
-            let program = this._createShaderProgram(vertexShader, fragmentShader, context);
+            let program = this._createShaderProgram(vertexShader, fragmentShader, context, transformFeedbackVaryings);
 
             this.onAfterShaderCompilationObservable.notifyObservers(this);
 
             return program;
         }
 
-        private _createShaderProgram(vertexShader: WebGLShader, fragmentShader: WebGLShader, context: WebGLRenderingContext): WebGLProgram {
+        private _createShaderProgram(vertexShader: WebGLShader, fragmentShader: WebGLShader, context: WebGLRenderingContext, transformFeedbackVaryings: Nullable<string[]> = null): WebGLProgram {
             var shaderProgram = context.createProgram();
 
             if (!shaderProgram) {
@@ -2500,8 +2505,20 @@
             context.attachShader(shaderProgram, vertexShader);
             context.attachShader(shaderProgram, fragmentShader);
 
+            if (this.webGLVersion > 1 && transformFeedbackVaryings) {
+                let transformFeedback = this.createTransformFeedback();
+
+                this.bindTransformFeedback(transformFeedback);
+                this.setTranformFeedbackVaryings(shaderProgram, transformFeedbackVaryings);
+                shaderProgram.transformFeedback = transformFeedback;
+            }
+
             context.linkProgram(shaderProgram);
 
+            if (this.webGLVersion > 1 && transformFeedbackVaryings) {
+                this.bindTransformFeedback(null);
+            }
+
             var linked = context.getProgramParameter(shaderProgram, context.LINK_STATUS);
 
             if (!linked) {
@@ -5251,6 +5268,31 @@
             return algorithmType === AbstractMesh.OCCLUSION_ALGORITHM_TYPE_CONSERVATIVE ? this._gl.ANY_SAMPLES_PASSED_CONSERVATIVE : this._gl.ANY_SAMPLES_PASSED;
         }
 
+        // Transform feedback
+        public createTransformFeedback(): WebGLTransformFeedback {
+            return this._gl.createTransformFeedback();
+        }
+
+        public deleteTransformFeedback(value: WebGLTransformFeedback): void {
+            this._gl.deleteTransformFeedback(value);
+        }
+
+        public bindTransformFeedback(value: Nullable<WebGLTransformFeedback>): void {
+            this._gl.bindTransformFeedback(this._gl.TRANSFORM_FEEDBACK, value);
+        }
+
+        public beginTransformFeedback(): void {
+            this._gl.beginTransformFeedback(this._gl.TRIANGLES);
+        }
+
+        public endTransformFeedback(): void {
+            this._gl.endTransformFeedback();
+        }
+
+        public setTranformFeedbackVaryings(program: WebGLProgram, value: string[]): void {
+            this._gl.transformFeedbackVaryings(program, value, this._gl.INTERLEAVED_ATTRIBS);
+        }
+
         // Statics
         public static isSupported(): boolean {
             try {

+ 2 - 1
src/Engine/babylon.nullEngine.ts

@@ -120,7 +120,8 @@
 
         public createShaderProgram(vertexCode: string, fragmentCode: string, defines: string, context?: WebGLRenderingContext): WebGLProgram {
             return {
-                __SPECTOR_rebuildProgram: null,
+                transformFeedback: null,
+                __SPECTOR_rebuildProgram: null
             };
         }
 

+ 9 - 0
src/Engine/babylon.webgl2.ts

@@ -13,6 +13,15 @@ interface WebGLRenderingContext {
     texImage3D(target: number, level: number, internalformat: number, width: number, height: number, depth: number, border: number, format: number, type: number, pixels: ImageBitmap | ImageData | HTMLVideoElement | HTMLImageElement | HTMLCanvasElement): void;
     
     compressedTexImage3D(target: number, level: number, internalformat: number, width: number, height: number, depth: number, border: number, data: ArrayBufferView, offset?: number, length?: number): void;
+
+    readonly TRANSFORM_FEEDBACK: number;
+    readonly INTERLEAVED_ATTRIBS: number;
+    createTransformFeedback(): WebGLTransformFeedback;
+    deleteTransformFeedback(transformFeedbac: WebGLTransformFeedback): void;
+    bindTransformFeedback(target: number, transformFeedback: Nullable<WebGLTransformFeedback>): void;
+    beginTransformFeedback(primitiveMode: number): void;
+    endTransformFeedback(): void;
+    transformFeedbackVaryings(program: WebGLProgram, varyings: string[], bufferMode: number): void;
 }
 
 interface ImageBitmap {

+ 50 - 3
src/Instrumentation/babylon.sceneInstrumentation.ts

@@ -25,7 +25,10 @@ module BABYLON {
         private _spritesRenderTime = new PerfCounter();   
 
         private _capturePhysicsTime = false;
-        private _physicsTime = new PerfCounter();         
+        private _physicsTime = new PerfCounter();     
+        
+        private _captureAnimationsTime = false;
+        private _animationsTime = new PerfCounter();            
 
         // Observers
         private _onBeforeActiveMeshesEvaluationObserver: Nullable<Observer<Scene>> = null;
@@ -47,7 +50,9 @@ module BABYLON {
         private _onAfterSpritesRenderingObserver: Nullable<Observer<Scene>> = null;      
         
         private _onBeforePhysicsObserver: Nullable<Observer<Scene>> = null;
-        private _onAfterPhysicsObserver: Nullable<Observer<Scene>> = null;             
+        private _onAfterPhysicsObserver: Nullable<Observer<Scene>> = null;     
+        
+        private _onAfterAnimationsObserver: Nullable<Observer<Scene>> = null;    
                 
         // Properties
         /**
@@ -264,6 +269,41 @@ module BABYLON {
                 this._onAfterPhysicsObserver = null;
             }
         }      
+
+
+        /**
+         * Gets the perf counter used for animations time
+         */
+        public get animationsTimeCounter(): PerfCounter {
+            return this._animationsTime;
+        }
+
+        /**
+         * Gets the animations time capture status
+         */
+        public get captureAnimationsTime(): boolean {
+            return this._captureAnimationsTime;
+        }        
+
+        /**
+         * Enable or disable the animations time capture
+         */        
+        public set captureAnimationsTime(value: boolean) {
+            if (value === this._captureAnimationsTime) {
+                return;
+            }
+
+            this._captureAnimationsTime = value;
+
+            if (value) {
+                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
@@ -384,6 +424,10 @@ module BABYLON {
                     this._spritesRenderTime.fetchNewFrame();
                 }
 
+                if (this._captureAnimationsTime) {
+                    this._animationsTime.beginMonitoring();
+                }
+
                 this.scene.getEngine()._drawCalls.fetchNewFrame();
             });
 
@@ -445,7 +489,10 @@ module BABYLON {
             this._onBeforePhysicsObserver = null;
 
             this.scene.onAfterPhysicsObservable.remove(this._onAfterPhysicsObserver);
-            this._onAfterPhysicsObserver = null;            
+            this._onAfterPhysicsObserver = null;    
+            
+            this.scene.onAfterAnimationsObservable.remove(this._onAfterAnimationsObserver);
+            this._onAfterAnimationsObserver = null;            
                 
             (<any>this.scene) = null;
         }

+ 6 - 3
src/Materials/babylon.effect.ts

@@ -81,11 +81,12 @@
         public uniformBuffersNames: string[];
         public samplers: string[];
         public defines: any;
-        public fallbacks: EffectFallbacks;
-        public onCompiled: (effect: Effect) => void;
-        public onError: (effect: Effect, errors: string) => void;
+        public fallbacks: Nullable<EffectFallbacks>;
+        public onCompiled: Nullable<(effect: Effect) => void>;
+        public onError: Nullable<(effect: Effect, errors: string) => void>;
         public indexParameters: any;
         public maxSimultaneousLights: number;
+        public transformFeedbackVaryings: Nullable<string[]>;
     }
 
     export class Effect {
@@ -116,6 +117,7 @@
         private _fragmentSourceCode: string;
         private _vertexSourceCodeOverride: string;
         private _fragmentSourceCodeOverride: string;
+        private _transformFeedbackVaryings: Nullable<string[]>;
 
         public _program: WebGLProgram;
         private _valueCache: { [key: string]: any };
@@ -137,6 +139,7 @@
                 this.onCompiled = options.onCompiled;
                 this._fallbacks = options.fallbacks;
                 this._indexParameters = options.indexParameters;  
+                this._transformFeedbackVaryings = options.transformFeedbackVaryings;
 
                 if (options.uniformBuffersNames) {
                     for (var i = 0; i < options.uniformBuffersNames.length; i++) {

+ 22 - 1
src/Particles/babylon.gpuParticleSystem.ts

@@ -5,6 +5,8 @@
         public emitter: Nullable<AbstractMesh | Vector3> = null;       
         public renderingGroupId = 0;        
         public layerMask: number = 0x0FFFFFFF;
+        private _renderingEffect: Effect;
+        private _updateEffect: Effect;
 
         private _scene: Scene;
 
@@ -23,7 +25,26 @@
             this.id = name;
             this._scene = scene || Engine.LastCreatedScene;
 
-            scene.particleSystems.push(this);
+            this._scene.particleSystems.push(this);
+
+            this._renderingEffect = new Effect("gpuRenderParticles", ["position", "age", "life", "velocity"], [], [], this._scene.getEngine());
+
+            let updateEffectOptions: EffectCreationOptions = {
+                attributes: ["position", "age", "life", "velocity"],
+                uniformsNames: [],
+                uniformBuffersNames: [],
+                samplers:[],
+                defines: "",
+                fallbacks: null,  
+                onCompiled: null,
+                onError: null,
+                indexParameters: null,
+                maxSimultaneousLights: 0,                                                      
+                transformFeedbackVaryings: ["outPosition", "outAge", "outLife", "outVelocity"]
+            };
+
+            this._updateEffect = new Effect("gpuUpdateParticles", updateEffectOptions, this._scene.getEngine());
+                                            
         }
 
         public animate(): void {

+ 4 - 0
src/Shaders/gpuRenderParticles.fragment.fx

@@ -0,0 +1,4 @@
+
+void main() {
+  gl_FragColor = vec4(1.0);
+}

+ 12 - 0
src/Shaders/gpuRenderParticles.vertex.fx

@@ -0,0 +1,12 @@
+#version 300 es
+
+// Particles state
+in vec3 position;
+in float age;
+in float life;
+in vec3 velocity;
+
+void main() {
+  gl_PointSize = 1.0;
+  gl_Position = vec4(position, 1.0);
+}

+ 5 - 0
src/Shaders/gpuUpdateParticles.fragment.fx

@@ -0,0 +1,5 @@
+#version 300 es
+
+void main() {
+  discard;
+}

+ 34 - 0
src/Shaders/gpuUpdateParticles.vertex.fx

@@ -0,0 +1,34 @@
+#version 300 es
+
+uniform float timeDelta;
+
+// Particles state
+in vec3 position;
+in float age;
+in float life;
+in vec3 velocity;
+
+// Output
+out vec3 outPosition;
+out float outAge;
+out float outLife;
+out vec3 outVelocity;
+
+void main() {
+  if (age >= life) {
+    // Create the particle at origin
+    outPosition = vec3(0, 0, 0);
+
+    // Age and life
+    outAge = 0.0;
+    outLife = life;
+
+    // Initial velocity
+    outVelocity = vec3(0, 1, 0);
+  } else {
+    outPosition = position + velocity * timeDelta;
+    outAge = age + timeDelta;
+    outLife = life;
+    outVelocity = velocity;
+  }
+}

+ 4 - 0
src/Tools/babylon.observable.ts

@@ -177,6 +177,10 @@
          * @param mask
          */
         public notifyObservers(eventData: Nullable<T>, mask: number = -1, target?: any, currentTarget?: any): boolean {
+            if (!this._observers.length) {
+                return false;
+            }
+
             let state = this._eventState;
             state.mask = mask;
             state.target = target;

+ 1 - 0
src/babylon.mixins.ts

@@ -120,6 +120,7 @@ interface WebGLBuffer {
 }
 
 interface WebGLProgram {
+    transformFeedback: Nullable<WebGLTransformFeedback>;
     __SPECTOR_rebuildProgram: Nullable<(vertexSourceCode: string, fragmentSourceCode: string, onCompiled: (program: WebGLProgram) => void, onError: (message: string) => void) => void>;
 }
 

+ 13 - 1
src/babylon.scene.ts

@@ -253,7 +253,13 @@
         * An event triggered before animating the scene
         * @type {BABYLON.Observable}
         */
-        public onBeforeAnimationsObservable = new Observable<Scene>();        
+        public onBeforeAnimationsObservable = new Observable<Scene>();       
+        
+        /**
+        * An event triggered after animations processing
+        * @type {BABYLON.Observable}
+        */
+        public onAfterAnimationsObservable = new Observable<Scene>();               
 
         /**
         * An event triggered before draw calls are ready to be sent
@@ -354,6 +360,8 @@
         */
         public onAfterSpritesRenderingObservable = new Observable<Scene>();          
 
+         
+
         /**
         * An event triggered when a camera is created
         * @type {BABYLON.Observable}
@@ -3296,6 +3304,7 @@
                 // Animations
                 this._animationRatio = defaultTimeStep * (60.0 / 1000.0);
                 this._animate();
+                this.onAfterAnimationsObservable.notifyObservers(this);
 
                 // Physics
                 if (this._physicsEngine) {
@@ -3318,6 +3327,7 @@
               var deltaTime = Math.max(Scene.MinDeltaTime, Math.min(this._engine.getDeltaTime(), Scene.MaxDeltaTime));
               this._animationRatio = deltaTime * (60.0 / 1000.0);
               this._animate();
+              this.onAfterAnimationsObservable.notifyObservers(this);
 
               // Physics
               if (this._physicsEngine) {
@@ -3707,6 +3717,8 @@
             this.onAfterDrawPhaseObservable.clear();
             this.onBeforePhysicsObservable.clear();
             this.onAfterPhysicsObservable.clear();
+            this.onBeforeAnimationsObservable.clear();
+            this.onAfterAnimationsObservable.clear();
 
             this.detachControl();
 

+ 8 - 8
tests/validation/validation.js

@@ -80,7 +80,7 @@ function evaluate(test, resultCanvas, result, renderImage, index, waitRing) {
     var renderData = getRenderData(canvas, engine);
     if (!test.onlyVisual) {
 
-        if (compare(renderData, resultCanvas)) { 
+        if (compare(renderData, resultCanvas)) {
             result.classList.add("failed");
             result.innerHTML = "×";
             console.log("failed");
@@ -104,7 +104,7 @@ function processCurrentScene(test, resultCanvas, result, renderImage, index, wai
     currentScene.executeWhenReady(function () {
         var renderCount = test.renderCount || 1;
 
-        engine.runRenderLoop(function() {
+        engine.runRenderLoop(function () {
             currentScene.render();
             renderCount--;
 
@@ -117,7 +117,7 @@ function processCurrentScene(test, resultCanvas, result, renderImage, index, wai
     });
 }
 
-function 
+function
 
 
 runTest(index) {
@@ -179,7 +179,7 @@ runTest(index) {
         });
     }
     else if (test.playgroundId) {
-        var snippetUrl = "https://babylonjs-api2.azurewebsites.net/snippets";
+        var snippetUrl = "//babylonjs-api2.azurewebsites.net/snippets";
         var pgRoot = "/playground"
         var xmlHttp = new XMLHttpRequest();
         xmlHttp.onreadystatechange = function () {
@@ -211,7 +211,7 @@ runTest(index) {
 
         request.onreadystatechange = () => {
             if (request.readyState === 4) {
-                request.onreadystatechange = null; 
+                request.onreadystatechange = null;
 
                 var scriptToRun = request.responseText.replace(/..\/..\/assets\//g, config.root + "/Assets/");
                 scriptToRun = scriptToRun.replace(/..\/..\/Assets\//g, config.root + "/Assets/");
@@ -220,7 +220,7 @@ runTest(index) {
 
                 if (test.replace) {
                     var split = test.replace.split(",");
-                    for (var i = 0; i < split.length; i+= 2) {
+                    for (var i = 0; i < split.length; i += 2) {
                         var source = split[i].trim();
                         var destination = split[i + 1].trim();
                         scriptToRun = scriptToRun.replace(source, destination);
@@ -242,7 +242,7 @@ runTest(index) {
         };
 
         request.send(null);
-        
+
     }
 }
 
@@ -261,7 +261,7 @@ var xhr = new XMLHttpRequest();
 
 xhr.open("GET", "config.json", true);
 
-xhr.addEventListener("load",function() {
+xhr.addEventListener("load", function () {
     if (xhr.status === 200) {
 
         config = JSON.parse(xhr.responseText);