Bladeren bron

Added animations to inspector

David Catuhe 6 jaren geleden
bovenliggende
commit
977048a5b7
26 gewijzigde bestanden met toevoegingen van 378744 en 376523 verwijderingen
  1. 5278 5254
      Playground/babylon.d.txt
  2. 10737 10723
      dist/preview release/babylon.d.ts
  3. 1 1
      dist/preview release/babylon.js
  4. 120783 120024
      dist/preview release/babylon.max.js
  5. 120783 120024
      dist/preview release/babylon.no-module.max.js
  6. 1 1
      dist/preview release/babylon.worker.js
  7. 120783 120024
      dist/preview release/es6.js
  8. 7 7
      dist/preview release/inspector/babylon.inspector.bundle.js
  9. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.js.map
  10. 19 197
      dist/preview release/viewer/babylon.viewer.d.ts
  11. 1 1
      dist/preview release/viewer/babylon.viewer.js
  12. 1 1
      dist/preview release/viewer/babylon.viewer.max.js
  13. 24 202
      dist/preview release/viewer/babylon.viewer.module.d.ts
  14. 1 1
      inspector/src/components/actionTabs/actionTabs.scss
  15. 41 13
      inspector/src/components/actionTabs/lines/sliderLineComponent.tsx
  16. 5 2
      inspector/src/components/actionTabs/lines/vector3LineComponent.tsx
  17. 20 1
      inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx
  18. 138 0
      inspector/src/components/actionTabs/tabs/propertyGrids/animationGroupPropertyGridComponent.tsx
  19. 0 10
      inspector/src/components/actionTabs/tabs/propertyGrids/cameras/arcRotateCameraPropertyGridComponent.tsx
  20. 0 10
      inspector/src/components/actionTabs/tabs/propertyGrids/cameras/freeCameraPropertyGridComponent.tsx
  21. 30 0
      inspector/src/components/sceneExplorer/entities/animationGroupTreeItemComponent.tsx
  22. 14 0
      inspector/src/components/sceneExplorer/sceneExplorer.scss
  23. 4 0
      inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx
  24. 6 1
      inspector/src/components/sceneExplorer/treeItemSpecializedComponent.tsx
  25. 38 25
      sandbox/index.js
  26. 28 0
      src/Animations/babylon.animationGroup.ts

File diff suppressed because it is too large
+ 5278 - 5254
Playground/babylon.d.txt


File diff suppressed because it is too large
+ 10737 - 10723
dist/preview release/babylon.d.ts


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/babylon.js


File diff suppressed because it is too large
+ 120783 - 120024
dist/preview release/babylon.max.js


File diff suppressed because it is too large
+ 120783 - 120024
dist/preview release/babylon.no-module.max.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/babylon.worker.js


File diff suppressed because it is too large
+ 120783 - 120024
dist/preview release/es6.js


File diff suppressed because it is too large
+ 7 - 7
dist/preview release/inspector/babylon.inspector.bundle.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/inspector/babylon.inspector.bundle.js.map


+ 19 - 197
dist/preview release/viewer/babylon.viewer.d.ts

@@ -2,6 +2,10 @@
 /// <reference path="./babylon.glTF2Interface.d.ts"/>
 /// <reference path="./babylonjs.loaders.d.ts"/>
 declare module "babylonjs-loaders"{ export=BABYLON;}
+/// <reference path="./babylon.d.ts"/>
+/// <reference path="./babylon.glTF2Interface.d.ts"/>
+/// <reference path="./babylonjs.loaders.d.ts"/>
+declare module "babylonjs-loaders"{ export=BABYLON;}
 // Generated by dts-bundle v0.7.3
 // Dependencies for this module:
 //   ../../../../../Tools/gulp/babylonjs
@@ -924,7 +928,7 @@ declare module BabylonViewer {
       * @param name the name of the custom optimizer configuration
       * @param upgrade set to true if you want to upgrade optimizer and false if you want to degrade
       */
-    export function getCustomOptimizerByName(name: string, upgrade?: boolean): (sceneManager: SceneManager) => boolean;
+    export function getCustomOptimizerByName(name: string, upgrade?: boolean): typeof extendedUpgrade;
     export function registerCustomOptimizer(name: string, optimizer: (sceneManager: SceneManager) => boolean): void;
 }
 declare module BabylonViewer {
@@ -1038,169 +1042,6 @@ declare module BabylonViewer {
     }
 }
 declare module BabylonViewer {
-    /**
-        * The object sent when an event is triggered
-        */
-    export interface EventCallback {
-            event: Event;
-            template: Template;
-            selector: string;
-            payload?: any;
-    }
-    /**
-        * The template manager, a member of the viewer class, will manage the viewer's templates and generate the HTML.
-        * The template manager managers a single viewer and can be seen as the collection of all sub-templates of the viewer.
-        */
-    export class TemplateManager {
-            containerElement: Element;
-            /**
-                * Will be triggered when any template is initialized
-                */
-            onTemplateInit: BABYLON.Observable<Template>;
-            /**
-                * Will be triggered when any template is fully loaded
-                */
-            onTemplateLoaded: BABYLON.Observable<Template>;
-            /**
-                * Will be triggered when a template state changes
-                */
-            onTemplateStateChange: BABYLON.Observable<Template>;
-            /**
-                * Will be triggered when all templates finished loading
-                */
-            onAllLoaded: BABYLON.Observable<TemplateManager>;
-            /**
-                * Will be triggered when any event on any template is triggered.
-                */
-            onEventTriggered: BABYLON.Observable<EventCallback>;
-            /**
-                * This template manager's event manager. In charge of callback registrations to native event types
-                */
-            eventManager: EventManager;
-            constructor(containerElement: Element);
-            /**
-                * Initialize the template(s) for the viewer. Called bay the Viewer class
-                * @param templates the templates to be used to initialize the main template
-                */
-            initTemplate(templates: {
-                    [key: string]: ITemplateConfiguration;
-            }): Promise<void>;
-            /**
-                * Get the canvas in the template tree.
-                * There must be one and only one canvas inthe template.
-                */
-            getCanvas(): HTMLCanvasElement | null;
-            /**
-                * Get a specific template from the template tree
-                * @param name the name of the template to load
-                */
-            getTemplate(name: string): Template | undefined;
-            /**
-                * Dispose the template manager
-                */
-            dispose(): void;
-    }
-    /**
-        * This class represents a single template in the viewer's template tree.
-        * An example for a template is a single canvas, an overlay (containing sub-templates) or the navigation bar.
-        * A template is injected using the template manager in the correct position.
-        * The template is rendered using Handlebars and can use Handlebars' features (such as parameter injection)
-        *
-        * For further information please refer to the documentation page, https://doc.babylonjs.com
-        */
-    export class Template {
-            name: string;
-            /**
-                * Will be triggered when the template is loaded
-                */
-            onLoaded: BABYLON.Observable<Template>;
-            /**
-                * will be triggered when the template is appended to the tree
-                */
-            onAppended: BABYLON.Observable<Template>;
-            /**
-                * Will be triggered when the template's state changed (shown, hidden)
-                */
-            onStateChange: BABYLON.Observable<Template>;
-            /**
-                * Will be triggered when an event is triggered on ths template.
-                * The event is a native browser event (like mouse or pointer events)
-                */
-            onEventTriggered: BABYLON.Observable<EventCallback>;
-            onParamsUpdated: BABYLON.Observable<Template>;
-            onHTMLRendered: BABYLON.Observable<Template>;
-            /**
-                * is the template loaded?
-                */
-            isLoaded: boolean;
-            /**
-                * This is meant to be used to track the show and hide functions.
-                * This is NOT (!!) a flag to check if the element is actually visible to the user.
-                */
-            isShown: boolean;
-            /**
-                * Is this template a part of the HTML tree (the template manager injected it)
-                */
-            isInHtmlTree: boolean;
-            /**
-                * The HTML element containing this template
-                */
-            parent: HTMLElement;
-            /**
-                * A promise that is fulfilled when the template finished loading.
-                */
-            initPromise: Promise<Template>;
-            constructor(name: string, _configuration: ITemplateConfiguration);
-            /**
-                * Some templates have parameters (like background color for example).
-                * The parameters are provided to Handlebars which in turn generates the template.
-                * This function will update the template with the new parameters
-                *
-                * Note that when updating parameters the events will be registered again (after being cleared).
-                *
-                * @param params the new template parameters
-                */
-            updateParams(params: {
-                    [key: string]: string | number | boolean | object;
-            }, append?: boolean): void;
-            redraw(): void;
-            /**
-                * Get the template'S configuration
-                */
-            readonly configuration: ITemplateConfiguration;
-            /**
-                * A template can be a parent element for other templates or HTML elements.
-                * This function will deliver all child HTML elements of this template.
-                */
-            getChildElements(): Array<string>;
-            /**
-                * Appending the template to a parent HTML element.
-                * If a parent is already set and you wish to replace the old HTML with new one, forceRemove should be true.
-                * @param parent the parent to which the template is added
-                * @param forceRemove if the parent already exists, shoud the template be removed from it?
-                */
-            appendTo(parent: HTMLElement, forceRemove?: boolean): void;
-            /**
-                * Show the template using the provided visibilityFunction, or natively using display: flex.
-                * The provided function returns a promise that should be fullfilled when the element is shown.
-                * Since it is a promise async operations are more than possible.
-                * See the default viewer for an opacity example.
-                * @param visibilityFunction The function to execute to show the template.
-                */
-            show(visibilityFunction?: (template: Template) => Promise<Template>): Promise<Template>;
-            /**
-                * Hide the template using the provided visibilityFunction, or natively using display: none.
-                * The provided function returns a promise that should be fullfilled when the element is hidden.
-                * Since it is a promise async operations are more than possible.
-                * See the default viewer for an opacity example.
-                * @param visibilityFunction The function to execute to show the template.
-                */
-            hide(visibilityFunction?: (template: Template) => Promise<Template>): Promise<Template>;
-            /**
-                * Dispose this template
-                */
-            dispose(): void;
-    }
 }
 declare module BabylonViewer {
     export class ConfigurationContainer {
@@ -1558,6 +1399,20 @@ declare module BabylonViewer {
     export function addLoaderPlugin(name: string, plugin: ILoaderPlugin): void;
 }
 declare module BabylonViewer {
+    /**
+        * A custom upgrade-oriented function configuration for the scene optimizer.
+        *
+        * @param viewer the viewer to optimize
+        */
+    export function extendedUpgrade(sceneManager: SceneManager): boolean;
+    /**
+        * A custom degrade-oriented function configuration for the scene optimizer.
+        *
+        * @param viewer the viewer to optimize
+        */
+    export function extendedDegrade(sceneManager: SceneManager): boolean;
+}
+declare module BabylonViewer {
 }
 declare module BabylonViewer {
     export interface IEnvironmentMapConfiguration {
@@ -1585,39 +1440,6 @@ declare module BabylonViewer {
 }
 declare module BabylonViewer {
     /**
-        * The EventManager is in charge of registering user interctions with the viewer.
-        * It is used in the TemplateManager
-        */
-    export class EventManager {
-            constructor(_templateManager: TemplateManager);
-            /**
-                * Register a new callback to a specific template.
-                * The best example for the usage can be found in the DefaultViewer
-                *
-                * @param templateName the templateName to register the event to
-                * @param callback The callback to be executed
-                * @param eventType the type of event to register
-                * @param selector an optional selector. if not defined the parent object in the template will be selected
-                */
-            registerCallback(templateName: string, callback: (eventData: EventCallback) => void, eventType?: string, selector?: string): void;
-            /**
-                * This will remove a registered event from the defined template.
-                * Each one of the variables apart from the template name are optional, but one must be provided.
-                *
-                * @param templateName the templateName
-                * @param callback the callback to remove (optional)
-                * @param eventType the event type to remove (optional)
-                * @param selector the selector from which to remove the event (optional)
-                */
-            unregisterCallback(templateName: string, callback: (eventData: EventCallback) => void, eventType?: string, selector?: string): void;
-            /**
-                * Dispose the event manager
-                */
-            dispose(): void;
-    }
-}
-declare module BabylonViewer {
-    /**
         * The ViewerLabs class will hold functions that are not (!) backwards compatible.
         * The APIs in all labs-related classes and configuration  might change.
         * Once stable, lab features will be moved to the publis API and configuration object.

File diff suppressed because it is too large
+ 1 - 1
dist/preview release/viewer/babylon.viewer.js


File diff suppressed because it is too large
+ 1 - 1
dist/preview release/viewer/babylon.viewer.max.js


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

@@ -3,6 +3,11 @@
 /// <reference path="./babylonjs.loaders.d.ts"/>
 declare module "babylonjs-loaders"{ export=BABYLON;}
 
+/// <reference path="./babylon.d.ts"/>
+/// <reference path="./babylon.glTF2Interface.d.ts"/>
+/// <reference path="./babylonjs.loaders.d.ts"/>
+declare module "babylonjs-loaders"{ export=BABYLON;}
+
 // Generated by dts-bundle v0.7.3
 // Dependencies for this module:
 //   ../../../../../Tools/gulp/babylonjs
@@ -985,13 +990,14 @@ declare module 'babylonjs-viewer/templating/viewerTemplatePlugin' {
 }
 
 declare module 'babylonjs-viewer/optimizer/custom' {
+    import { extendedUpgrade } from "babylonjs-viewer/optimizer/custom/extended";
     import { SceneManager } from "babylonjs-viewer/managers/sceneManager";
     /**
       *
       * @param name the name of the custom optimizer configuration
       * @param upgrade set to true if you want to upgrade optimizer and false if you want to degrade
       */
-    export function getCustomOptimizerByName(name: string, upgrade?: boolean): (sceneManager: SceneManager) => boolean;
+    export function getCustomOptimizerByName(name: string, upgrade?: boolean): typeof extendedUpgrade;
     export function registerCustomOptimizer(name: string, optimizer: (sceneManager: SceneManager) => boolean): void;
 }
 
@@ -1114,172 +1120,7 @@ declare module 'babylonjs-viewer/configuration/configuration' {
 }
 
 declare module 'babylonjs-viewer/templating/templateManager' {
-    import { Observable } from 'babylonjs';
-    import { EventManager } from 'babylonjs-viewer/templating/eventManager';
-    import { ITemplateConfiguration } from 'babylonjs-viewer/configuration/interfaces';
-    /**
-        * The object sent when an event is triggered
-        */
-    export interface EventCallback {
-            event: Event;
-            template: Template;
-            selector: string;
-            payload?: any;
-    }
-    /**
-        * The template manager, a member of the viewer class, will manage the viewer's templates and generate the HTML.
-        * The template manager managers a single viewer and can be seen as the collection of all sub-templates of the viewer.
-        */
-    export class TemplateManager {
-            containerElement: Element;
-            /**
-                * Will be triggered when any template is initialized
-                */
-            onTemplateInit: Observable<Template>;
-            /**
-                * Will be triggered when any template is fully loaded
-                */
-            onTemplateLoaded: Observable<Template>;
-            /**
-                * Will be triggered when a template state changes
-                */
-            onTemplateStateChange: Observable<Template>;
-            /**
-                * Will be triggered when all templates finished loading
-                */
-            onAllLoaded: Observable<TemplateManager>;
-            /**
-                * Will be triggered when any event on any template is triggered.
-                */
-            onEventTriggered: Observable<EventCallback>;
-            /**
-                * This template manager's event manager. In charge of callback registrations to native event types
-                */
-            eventManager: EventManager;
-            constructor(containerElement: Element);
-            /**
-                * Initialize the template(s) for the viewer. Called bay the Viewer class
-                * @param templates the templates to be used to initialize the main template
-                */
-            initTemplate(templates: {
-                    [key: string]: ITemplateConfiguration;
-            }): Promise<void>;
-            /**
-                * Get the canvas in the template tree.
-                * There must be one and only one canvas inthe template.
-                */
-            getCanvas(): HTMLCanvasElement | null;
-            /**
-                * Get a specific template from the template tree
-                * @param name the name of the template to load
-                */
-            getTemplate(name: string): Template | undefined;
-            /**
-                * Dispose the template manager
-                */
-            dispose(): void;
-    }
-    /**
-        * This class represents a single template in the viewer's template tree.
-        * An example for a template is a single canvas, an overlay (containing sub-templates) or the navigation bar.
-        * A template is injected using the template manager in the correct position.
-        * The template is rendered using Handlebars and can use Handlebars' features (such as parameter injection)
-        *
-        * For further information please refer to the documentation page, https://doc.babylonjs.com
-        */
-    export class Template {
-            name: string;
-            /**
-                * Will be triggered when the template is loaded
-                */
-            onLoaded: Observable<Template>;
-            /**
-                * will be triggered when the template is appended to the tree
-                */
-            onAppended: Observable<Template>;
-            /**
-                * Will be triggered when the template's state changed (shown, hidden)
-                */
-            onStateChange: Observable<Template>;
-            /**
-                * Will be triggered when an event is triggered on ths template.
-                * The event is a native browser event (like mouse or pointer events)
-                */
-            onEventTriggered: Observable<EventCallback>;
-            onParamsUpdated: Observable<Template>;
-            onHTMLRendered: Observable<Template>;
-            /**
-                * is the template loaded?
-                */
-            isLoaded: boolean;
-            /**
-                * This is meant to be used to track the show and hide functions.
-                * This is NOT (!!) a flag to check if the element is actually visible to the user.
-                */
-            isShown: boolean;
-            /**
-                * Is this template a part of the HTML tree (the template manager injected it)
-                */
-            isInHtmlTree: boolean;
-            /**
-                * The HTML element containing this template
-                */
-            parent: HTMLElement;
-            /**
-                * A promise that is fulfilled when the template finished loading.
-                */
-            initPromise: Promise<Template>;
-            constructor(name: string, _configuration: ITemplateConfiguration);
-            /**
-                * Some templates have parameters (like background color for example).
-                * The parameters are provided to Handlebars which in turn generates the template.
-                * This function will update the template with the new parameters
-                *
-                * Note that when updating parameters the events will be registered again (after being cleared).
-                *
-                * @param params the new template parameters
-                */
-            updateParams(params: {
-                    [key: string]: string | number | boolean | object;
-            }, append?: boolean): void;
-            redraw(): void;
-            /**
-                * Get the template'S configuration
-                */
-            readonly configuration: ITemplateConfiguration;
-            /**
-                * A template can be a parent element for other templates or HTML elements.
-                * This function will deliver all child HTML elements of this template.
-                */
-            getChildElements(): Array<string>;
-            /**
-                * Appending the template to a parent HTML element.
-                * If a parent is already set and you wish to replace the old HTML with new one, forceRemove should be true.
-                * @param parent the parent to which the template is added
-                * @param forceRemove if the parent already exists, shoud the template be removed from it?
-                */
-            appendTo(parent: HTMLElement, forceRemove?: boolean): void;
-            /**
-                * Show the template using the provided visibilityFunction, or natively using display: flex.
-                * The provided function returns a promise that should be fullfilled when the element is shown.
-                * Since it is a promise async operations are more than possible.
-                * See the default viewer for an opacity example.
-                * @param visibilityFunction The function to execute to show the template.
-                */
-            show(visibilityFunction?: (template: Template) => Promise<Template>): Promise<Template>;
-            /**
-                * Hide the template using the provided visibilityFunction, or natively using display: none.
-                * The provided function returns a promise that should be fullfilled when the element is hidden.
-                * Since it is a promise async operations are more than possible.
-                * See the default viewer for an opacity example.
-                * @param visibilityFunction The function to execute to show the template.
-                */
-            hide(visibilityFunction?: (template: Template) => Promise<Template>): Promise<Template>;
-            /**
-                * Dispose this template
-                */
-            dispose(): void;
-    }
+    
 }
 
 declare module 'babylonjs-viewer/configuration/configurationContainer' {
@@ -1662,6 +1503,22 @@ declare module 'babylonjs-viewer/loader/plugins' {
     export function addLoaderPlugin(name: string, plugin: ILoaderPlugin): void;
 }
 
+declare module 'babylonjs-viewer/optimizer/custom/extended' {
+    import { SceneManager } from 'babylonjs-viewer/managers/sceneManager';
+    /**
+        * A custom upgrade-oriented function configuration for the scene optimizer.
+        *
+        * @param viewer the viewer to optimize
+        */
+    export function extendedUpgrade(sceneManager: SceneManager): boolean;
+    /**
+        * A custom degrade-oriented function configuration for the scene optimizer.
+        *
+        * @param viewer the viewer to optimize
+        */
+    export function extendedDegrade(sceneManager: SceneManager): boolean;
+}
+
 declare module 'babylonjs-viewer/configuration/interfaces' {
     export * from 'babylonjs-viewer/configuration/interfaces/cameraConfiguration';
     export * from 'babylonjs-viewer/configuration/interfaces/colorGradingConfiguration';
@@ -1705,41 +1562,6 @@ declare module 'babylonjs-viewer/configuration/interfaces/environmentMapConfigur
     }
 }
 
-declare module 'babylonjs-viewer/templating/eventManager' {
-    import { EventCallback, TemplateManager } from "babylonjs-viewer/templating/templateManager";
-    /**
-        * The EventManager is in charge of registering user interctions with the viewer.
-        * It is used in the TemplateManager
-        */
-    export class EventManager {
-            constructor(_templateManager: TemplateManager);
-            /**
-                * Register a new callback to a specific template.
-                * The best example for the usage can be found in the DefaultViewer
-                *
-                * @param templateName the templateName to register the event to
-                * @param callback The callback to be executed
-                * @param eventType the type of event to register
-                * @param selector an optional selector. if not defined the parent object in the template will be selected
-                */
-            registerCallback(templateName: string, callback: (eventData: EventCallback) => void, eventType?: string, selector?: string): void;
-            /**
-                * This will remove a registered event from the defined template.
-                * Each one of the variables apart from the template name are optional, but one must be provided.
-                *
-                * @param templateName the templateName
-                * @param callback the callback to remove (optional)
-                * @param eventType the event type to remove (optional)
-                * @param selector the selector from which to remove the event (optional)
-                */
-            unregisterCallback(templateName: string, callback: (eventData: EventCallback) => void, eventType?: string, selector?: string): void;
-            /**
-                * Dispose the event manager
-                */
-            dispose(): void;
-    }
-}
-
 declare module 'babylonjs-viewer/labs/viewerLabs' {
     import { PBREnvironment } from "babylonjs-viewer/labs/environmentSerializer";
     import { ShadowLight, Vector3, Scene } from 'babylonjs';

+ 1 - 1
inspector/src/components/actionTabs/actionTabs.scss

@@ -309,7 +309,7 @@
                     }   
                     
                     button:focus {
-                        border: 0px;
+                        border: 1px solid rgb(51, 122, 183);
                         outline: 0px;
                     }  
                 }

+ 41 - 13
inspector/src/components/actionTabs/lines/sliderLineComponent.tsx

@@ -4,11 +4,14 @@ import { PropertyChangedEvent } from "../../propertyChangedEvent";
 
 interface ISliderLineComponentProps {
     label: string,
-    target: any,
-    propertyName: string,
+    target?: any,
+    propertyName?: string,
     minimum: number,
     maximum: number,
     step: number,
+    directValue?: number,
+    onChange?: (value: number) => void,
+    onInput?: (value: number) => void,
     onPropertyChangedObservable?: Observable<PropertyChangedEvent>
 }
 
@@ -17,11 +20,21 @@ export class SliderLineComponent extends React.Component<ISliderLineComponentPro
     constructor(props: ISliderLineComponentProps) {
         super(props);
 
-        this.state = { value: this.props.target[this.props.propertyName] };
+        if (this.props.directValue !== undefined) {
+            this.state = {
+                value: this.props.directValue
+            }
+        } else {
+            this.state = { value: this.props.target![this.props.propertyName!] };
+        }
     }
 
     shouldComponentUpdate(nextProps: ISliderLineComponentProps, nextState: { value: number }) {
-        const currentState = nextProps.target[nextProps.propertyName];
+        if (nextProps.directValue !== undefined) {
+            nextState.value = nextProps.directValue;
+            return true;
+        }
+        const currentState = nextProps.target![nextProps.propertyName!];
 
         if (currentState !== nextState.value || this._localChange) {
             nextState.value = currentState;
@@ -35,20 +48,33 @@ export class SliderLineComponent extends React.Component<ISliderLineComponentPro
         this._localChange = true;
         const newValue = parseFloat(newValueString);
 
-        if (this.props.onPropertyChangedObservable) {
-            this.props.onPropertyChangedObservable.notifyObservers({
-                object: this.props.target,
-                property: this.props.propertyName,
-                value: newValue,
-                initialValue: this.state.value
-            });
+        if (this.props.target) {
+            if (this.props.onPropertyChangedObservable) {
+                this.props.onPropertyChangedObservable.notifyObservers({
+                    object: this.props.target,
+                    property: this.props.propertyName!,
+                    value: newValue,
+                    initialValue: this.state.value
+                });
+            }
+
+            this.props.target[this.props.propertyName!] = newValue;
         }
 
-        this.props.target[this.props.propertyName] = newValue;
+        if (this.props.onChange) {
+            this.props.onChange(newValue);
+        }
 
         this.setState({ value: newValue });
     }
 
+    onInput(newValueString: any) {
+        const newValue = parseFloat(newValueString);
+        if (this.props.onInput) {
+            this.props.onInput(newValue);
+        }
+    }
+
     render() {
         return (
             <div className="sliderLine">
@@ -56,7 +82,9 @@ export class SliderLineComponent extends React.Component<ISliderLineComponentPro
                     {this.props.label}
                 </div>
                 <div className="slider">
-                    {this.state.value ? this.state.value.toFixed(2) : "0"}&nbsp;<input className="range" type="range" step={this.props.step} min={this.props.minimum} max={this.props.maximum} value={this.state.value} onChange={evt => this.onChange(evt.target.value)} />
+                    {this.state.value ? this.state.value.toFixed(2) : "0"}&nbsp;<input className="range" type="range" step={this.props.step} min={this.props.minimum} max={this.props.maximum} value={this.state.value}
+                        onInput={evt => this.onInput((evt.target as HTMLInputElement).value)}
+                        onChange={evt => this.onChange(evt.target.value)} />
                 </div>
             </div>
         );

+ 5 - 2
inspector/src/components/actionTabs/lines/vector3LineComponent.tsx

@@ -19,14 +19,14 @@ export class Vector3LineComponent extends React.Component<IVector3LineComponentP
     constructor(props: IVector3LineComponentProps) {
         super(props);
 
-        this.state = { isExpanded: false, value: this.props.target[this.props.propertyName] }
+        this.state = { isExpanded: false, value: this.props.target[this.props.propertyName].clone() }
     }
 
     shouldComponentUpdate(nextProps: IVector3LineComponentProps, nextState: { isExpanded: boolean, value: Vector3 }) {
         const nextPropsValue = nextProps.target[nextProps.propertyName];
 
         if (!nextPropsValue.equals(nextState.value) || this._localChange) {
-            nextState.value = nextPropsValue;
+            nextState.value = nextPropsValue.clone();
             this._localChange = false;
             return true;
         }
@@ -58,6 +58,7 @@ export class Vector3LineComponent extends React.Component<IVector3LineComponentP
         this._localChange = true;
 
         const store = this.state.value.clone();
+        this.props.target[this.props.propertyName].x = value;
         this.state.value.x = value;
         this.setState({ value: this.state.value });
 
@@ -68,6 +69,7 @@ export class Vector3LineComponent extends React.Component<IVector3LineComponentP
         this._localChange = true;
 
         const store = this.state.value.clone();
+        this.props.target[this.props.propertyName].y = value;
         this.state.value.y = value;
         this.setState({ value: this.state.value });
 
@@ -78,6 +80,7 @@ export class Vector3LineComponent extends React.Component<IVector3LineComponentP
         this._localChange = true;
 
         const store = this.state.value.clone();
+        this.props.target[this.props.propertyName].z = value;
         this.state.value.z = value;
         this.setState({ value: this.state.value });
 

+ 20 - 1
inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx

@@ -1,6 +1,6 @@
 import * as React from "react";
 import { PaneComponent, IPaneComponentProps } from "../paneComponent";
-import { Mesh, TransformNode, Material, StandardMaterial, Texture, PBRMaterial, Scene, FreeCamera, ArcRotateCamera, HemisphericLight, PointLight, BackgroundMaterial } from "babylonjs";
+import { Mesh, TransformNode, Material, StandardMaterial, Texture, PBRMaterial, Scene, FreeCamera, ArcRotateCamera, HemisphericLight, PointLight, BackgroundMaterial, AnimationGroup } from "babylonjs";
 import { MaterialPropertyGridComponent } from "./propertyGrids/materials/materialPropertyGridComponent";
 import { StandardMaterialPropertyGridComponent } from "./propertyGrids/materials/standardMaterialPropertyGridComponent";
 import { TexturePropertyGridComponent } from "./propertyGrids/materials/texturePropertyGridComponent";
@@ -21,12 +21,23 @@ import { InputText } from "babylonjs-gui/2D/controls/inputText";
 import { InputTextPropertyGridComponent } from "./propertyGrids/gui/inputTextPropertyGridComponent";
 import { ColorPicker } from "babylonjs-gui";
 import { ColorPickerPropertyGridComponent } from "./propertyGrids/gui/colorPickerPropertyGridComponent";
+import { AnimationGroupGridComponent } from "./propertyGrids/animationGroupPropertyGridComponent";
 
 export class PropertyGridTabComponent extends PaneComponent {
+    private _timerIntervalId: number;
+
     constructor(props: IPaneComponentProps) {
         super(props);
     }
 
+    componentWillMount() {
+        this._timerIntervalId = window.setInterval(() => this.forceUpdate(), 500);
+    }
+
+    componentWillUnmount() {
+        window.clearInterval(this._timerIntervalId);
+    }
+
     render() {
         const entity = this.props.selectedEntity;
 
@@ -103,6 +114,14 @@ export class PropertyGridTabComponent extends PaneComponent {
                     onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
             }
 
+            if (className === "AnimationGroup") {
+                const animationGroup = entity as AnimationGroup;
+                return (<AnimationGroupGridComponent
+                    animationGroup={animationGroup}
+                    scene={this.props.scene}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }
+
             if (className.indexOf("Material") !== -1) {
                 const material = entity as Material;
                 return (<MaterialPropertyGridComponent material={material} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);

+ 138 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animationGroupPropertyGridComponent.tsx

@@ -0,0 +1,138 @@
+import * as React from "react";
+import { Observable, AnimationGroup, Scene, Nullable, Observer } from "babylonjs";
+import { PropertyChangedEvent } from "../../../propertyChangedEvent";
+import { ButtonLineComponent } from "../../lines/buttonLineComponent";
+import { LineContainerComponent } from "../../lineContainerComponent";
+import { TextLineComponent } from "../../lines/textLineComponent";
+import { SliderLineComponent } from "../../lines/sliderLineComponent";
+
+interface IAnimationGroupGridComponentProps {
+    animationGroup: AnimationGroup,
+    scene: Scene,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class AnimationGroupGridComponent extends React.Component<IAnimationGroupGridComponentProps, { playButtonText: string, currentFrame: number }> {
+    private _onAnimationGroupPlayObserver: Nullable<Observer<AnimationGroup>>;
+    private _onAnimationGroupPauseObserver: Nullable<Observer<AnimationGroup>>;
+    private _onBeforeRenderObserver: Nullable<Observer<Scene>>;
+
+    constructor(props: IAnimationGroupGridComponentProps) {
+        super(props);
+
+        const animationGroup = this.props.animationGroup;
+        this.state = { playButtonText: animationGroup.isPlaying ? "Pause" : "Play", currentFrame: 0 };
+    }
+
+    disconnect(animationGroup: AnimationGroup) {
+
+        if (this._onAnimationGroupPlayObserver) {
+            animationGroup.onAnimationGroupPlayObservable.remove(this._onAnimationGroupPlayObserver);
+            this._onAnimationGroupPlayObserver = null;
+        }
+
+        if (this._onAnimationGroupPauseObserver) {
+            animationGroup.onAnimationGroupPauseObservable.remove(this._onAnimationGroupPauseObserver);
+            this._onAnimationGroupPauseObserver = null;
+        }
+    }
+
+    connect(animationGroup: AnimationGroup) {
+        this._onAnimationGroupPlayObserver = animationGroup.onAnimationGroupPlayObservable.add(() => {
+            this.forceUpdate();
+        });
+
+        this._onAnimationGroupPauseObserver = animationGroup.onAnimationGroupPauseObservable.add(() => {
+            this.forceUpdate();
+        });
+
+        this.updateCurrentFrame(animationGroup);
+    }
+
+    updateCurrentFrame(animationGroup: AnimationGroup) {
+        var targetedAnimations = animationGroup.targetedAnimations;
+        if (targetedAnimations.length > 0) {
+            var runtimeAnimations = animationGroup.targetedAnimations[0].animation.runtimeAnimations;
+            if (runtimeAnimations.length > 0) {
+                this.setState({ currentFrame: runtimeAnimations[0].currentFrame });
+            } else {
+                this.setState({ currentFrame: 0 });
+            }
+        }
+    }
+
+    shouldComponentUpdate(nextProps: IAnimationGroupGridComponentProps): boolean {
+        if (this.props.animationGroup !== nextProps.animationGroup) {
+            this.disconnect(this.props.animationGroup);
+            this.connect(nextProps.animationGroup);
+        }
+        return true;
+    }
+
+    componentWillMount() {
+        this.connect(this.props.animationGroup);
+
+        this._onBeforeRenderObserver = this.props.scene.onBeforeRenderObservable.add(() => {
+            if (this.props.animationGroup.isPlaying) {
+                this.updateCurrentFrame(this.props.animationGroup);
+            }
+        });
+    }
+
+    componentWillUnmount() {
+        this.disconnect(this.props.animationGroup);
+
+        if (this._onBeforeRenderObserver) {
+            this.props.scene.onBeforeRenderObservable.remove(this._onBeforeRenderObserver);
+            this._onBeforeRenderObserver = null;
+        }
+    }
+
+    playOrPause() {
+        const animationGroup = this.props.animationGroup;
+
+        if (animationGroup.isPlaying) {
+            this.setState({ playButtonText: "Play" });
+            animationGroup.pause();
+        } else {
+            this.setState({ playButtonText: "Pause" });
+            this.props.scene.animationGroups.forEach(grp => grp.pause());
+            animationGroup.play(true);
+        }
+    }
+
+    onCurrentFrameChange(value: number) {
+        const animationGroup = this.props.animationGroup;
+
+        if (!animationGroup.isPlaying) {
+            animationGroup.play(true);
+            animationGroup.goToFrame(value);
+            animationGroup.pause();
+        } else {
+            animationGroup.goToFrame(value);
+        }
+
+        this.setState({ currentFrame: value });
+    }
+
+    render() {
+        const animationGroup = this.props.animationGroup;
+
+        const playButtonText = animationGroup.isPlaying ? "Pause" : "Play"
+
+        return (
+            <div className="pane">
+                <LineContainerComponent title="CONTROLS">
+                    <ButtonLineComponent label={playButtonText} onClick={() => this.playOrPause()} />
+                    <SliderLineComponent label="Speed ratio" minimum={0} maximum={10} step={0.1} target={animationGroup} propertyName="speedRatio" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
+                    <SliderLineComponent ref="timeline" label="Current frame" minimum={animationGroup.from} maximum={animationGroup.to} step={(animationGroup.to - animationGroup.from) / 100.0} directValue={this.state.currentFrame} onInput={value => this.onCurrentFrameChange(value)} />
+                </LineContainerComponent>
+                <LineContainerComponent title="INFOS">
+                    <TextLineComponent label="Animation count" value={animationGroup.targetedAnimations.length.toString()} />
+                    <TextLineComponent label="From" value={animationGroup.from.toFixed(2)} />
+                    <TextLineComponent label="To" value={animationGroup.to.toFixed(2)} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 0 - 10
inspector/src/components/actionTabs/tabs/propertyGrids/cameras/arcRotateCameraPropertyGridComponent.tsx

@@ -14,20 +14,10 @@ interface IArcRotateCameraPropertyGridComponentProps {
 }
 
 export class ArcRotateCameraPropertyGridComponent extends React.Component<IArcRotateCameraPropertyGridComponentProps> {
-    private _timerIntervalId: number;
-
     constructor(props: IArcRotateCameraPropertyGridComponentProps) {
         super(props);
     }
 
-    componentWillMount() {
-        this._timerIntervalId = window.setInterval(() => this.forceUpdate(), 500);
-    }
-
-    componentWillUnmount() {
-        window.clearInterval(this._timerIntervalId);
-    }
-
     render() {
         const camera = this.props.camera;
 

+ 0 - 10
inspector/src/components/actionTabs/tabs/propertyGrids/cameras/freeCameraPropertyGridComponent.tsx

@@ -13,20 +13,10 @@ interface IFreeCameraPropertyGridComponentProps {
 }
 
 export class FreeCameraPropertyGridComponent extends React.Component<IFreeCameraPropertyGridComponentProps> {
-    private _timerIntervalId: number;
-
     constructor(props: IFreeCameraPropertyGridComponentProps) {
         super(props);
     }
 
-    componentWillMount() {
-        this._timerIntervalId = window.setInterval(() => this.forceUpdate(), 500);
-    }
-
-    componentWillUnmount() {
-        window.clearInterval(this._timerIntervalId);
-    }
-
     render() {
         const camera = this.props.camera;
 

+ 30 - 0
inspector/src/components/sceneExplorer/entities/animationGroupTreeItemComponent.tsx

@@ -0,0 +1,30 @@
+import { IExplorerExtensibilityGroup, AnimationGroup } from "babylonjs";
+import { faFilm } from '@fortawesome/free-solid-svg-icons';
+import { TreeItemLabelComponent } from "../treeItemLabelComponent";
+import { ExtensionsComponent } from "../extensionsComponent";
+import * as React from "react";
+
+interface IAnimationGroupItemComponentProps {
+    animationGroup: AnimationGroup,
+    extensibilityGroups?: IExplorerExtensibilityGroup[],
+    onClick: () => void
+}
+
+export class AnimationGroupItemComponent extends React.Component<IAnimationGroupItemComponentProps> {
+    constructor(props: IAnimationGroupItemComponentProps) {
+        super(props);
+    }
+
+
+    render() {
+        const animationGroup = this.props.animationGroup;
+        return (
+            <div className="animationGroupTools">
+                <TreeItemLabelComponent label={animationGroup.name} onClick={() => this.props.onClick()} icon={faFilm} color="cornflowerblue" />
+                {
+                    <ExtensionsComponent target={animationGroup} extensibilityGroups={this.props.extensibilityGroups} />
+                }
+            </div>
+        )
+    }
+}

+ 14 - 0
inspector/src/components/sceneExplorer/sceneExplorer.scss

@@ -309,6 +309,20 @@
             }
         }
 
+        .animationGroupTools {
+            grid-column: 2;
+            width: 100%;
+            display: grid;
+            grid-template-columns: 1fr auto 5px;
+            align-items: center;
+            min-width: 0;
+           
+            .extensions {
+                width: 20px;
+                grid-column: 2;
+            }
+        }
+
         .meshTools {
             grid-column: 2;
             width: 100%;

+ 4 - 0
inspector/src/components/sceneExplorer/sceneExplorerComponent.tsx

@@ -179,6 +179,10 @@ export class SceneExplorerComponent extends React.Component<ISceneExplorerCompon
                     guiElements && guiElements.length > 0 &&
                     <TreeItemComponent extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={guiElements} label="GUI" offset={1} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} filter={this.state.filter} />
                 }
+                {
+                    scene.animationGroups.length > 0 &&
+                    <TreeItemComponent extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.state.selectedEntity} items={scene.animationGroups} label="Animation groups" offset={1} onSelectionChangedObservable={this.props.globalState.onSelectionChangedObservable} filter={this.state.filter} />
+                }
             </div>
         )
     }

+ 6 - 1
inspector/src/components/sceneExplorer/treeItemSpecializedComponent.tsx

@@ -1,4 +1,4 @@
-import { AbstractMesh, Camera, Light, Material, Texture, TransformNode, IExplorerExtensibilityGroup, Observable } from "babylonjs";
+import { AbstractMesh, Camera, Light, Material, Texture, TransformNode, IExplorerExtensibilityGroup, Observable, AnimationGroup } from "babylonjs";
 import { MeshTreeItemComponent } from "./entities/meshTreeItemComponent";
 import { CameraTreeItemComponent } from "./entities/cameraTreeItemComponent";
 import { LightTreeItemComponent } from "./entities/lightTreeItemComponent";
@@ -11,6 +11,7 @@ import * as React from "react";
 import { ControlTreeItemComponent } from "./entities/gui/controlTreeItemComponent";
 import { Control, AdvancedDynamicTexture } from "babylonjs-gui";
 import { AdvancedDynamicTextureTreeItemComponent } from "./entities/gui/advancedDynamicTextureTreeItemComponent";
+import { AnimationGroupItemComponent } from "./entities/animationGroupTreeItemComponent";
 
 interface ITreeItemSpecializedComponentProps {
     label: string,
@@ -68,6 +69,10 @@ export class TreeItemSpecializedComponent extends React.Component<ITreeItemSpeci
                 return (<AdvancedDynamicTextureTreeItemComponent onSelectionChangedObservable={this.props.onSelectionChangedObservable} extensibilityGroups={this.props.extensibilityGroups} texture={entity as AdvancedDynamicTexture} onClick={() => this.onClick()} />);
             }
 
+            if (className === "AnimationGroup") {
+                return (<AnimationGroupItemComponent extensibilityGroups={this.props.extensibilityGroups} animationGroup={entity as AnimationGroup} onClick={() => this.onClick()} />);
+            }
+
             if (className.indexOf("Texture") !== -1) {
                 return (<TextureTreeItemComponent extensibilityGroups={this.props.extensibilityGroups} texture={entity as Texture} onClick={() => this.onClick()} />);
             }

+ 38 - 25
sandbox/index.js

@@ -18,6 +18,7 @@ var playBtn = document.getElementById("playBtn");
 var slider = document.getElementById("slider");
 var footer = document.getElementById("footer");
 var canvas = document.getElementById("renderCanvas");
+var canvasZone = document.getElementById("canvasZone");
 
 var indexOf = location.href.indexOf("?");
 if (indexOf !== -1) {
@@ -45,7 +46,7 @@ if (indexOf !== -1) {
 
 if (kiosk) {
     footer.style.display = "none";
-    canvas.style.height = "100%";
+    canvasZone.style.height = "100%";
 }
 
 if (BABYLON.Engine.isSupported()) {
@@ -374,6 +375,27 @@ dropdownBtn.addEventListener("click", function() {
     }
 });
 
+function selectCurrentGroup(group, index, animation) {
+    if (currentGroupIndex !== undefined) {
+        document.getElementById(formatId(currentGroup.name + "-" + currentGroupIndex)).classList.remove("active");
+    }
+    playBtn.classList.remove("play");
+    playBtn.classList.add("pause");
+
+    // start the new animation group
+    currentGroup = group;
+    currentGroupIndex = index;
+    animation.classList.add("active");
+    dropdownLabel.innerHTML = currentGroup.name;
+    dropdownLabel.title = currentGroup.name;
+
+    // set the slider
+    slider.setAttribute("min", currentGroup.from);
+    slider.setAttribute("max", currentGroup.to);
+    currentSliderValue = currentGroup.from;
+    slider.value = currentGroup.from;
+}
+
 function createDropdownLink(group, index) {
     var animation = document.createElement("a");
     animation.innerHTML = group.name;
@@ -383,55 +405,46 @@ function createDropdownLink(group, index) {
         // stop the current animation group
         currentGroup.reset();
         currentGroup.stop();
-        document.getElementById(formatId(currentGroup.name + "-" + currentGroupIndex)).classList.remove("active");
-        playBtn.classList.remove("play");
-        playBtn.classList.add("pause");
-
-        // start the new animation group
-        currentGroup = group;
-        currentGroupIndex = index;
-        currentGroup.start(true);
-        this.classList.add("active");
-        dropdownLabel.innerHTML = currentGroup.name;
-        dropdownLabel.title = currentGroup.name;
-
-        // set the slider
-        slider.setAttribute("min", currentGroup.from);
-        slider.setAttribute("max", currentGroup.to);
-        currentSliderValue = currentGroup.from;
-        slider.value = currentGroup.from;
+
+        group.play(true);
 
         // hide the content of the dropdown
         displayDropdownContent(false);
     });
     dropdownContent.appendChild(animation);
+
+    group.onAnimationGroupPlayObservable.add(function(grp) {
+        selectCurrentGroup(grp, index, animation);
+    });
+
+    group.onAnimationGroupPauseObservable.add(function(grp) {
+        playBtn.classList.add("play");
+        playBtn.classList.remove("pause");
+    });
 }
 
 // event on the play/pause button
 playBtn.addEventListener("click", function() {
     // click on the button to run the animation
     if (this.classList.contains("play")) {
-        this.classList.remove("play");
-        this.classList.add("pause");
-        var currentFrame = slider.value;
         currentGroup.play(true);
     }
     // click on the button to pause the animation
     else {
-        this.classList.add("play");
-        this.classList.remove("pause");
         currentGroup.pause();
     }
 });
 
 // event on the slider
 slider.addEventListener("input", function() {
+    var value = parseFloat(this.value);
+
     if (playBtn.classList.contains("play")) {
         currentGroup.play(true);
-        currentGroup.goToFrame(this.value);
+        currentGroup.goToFrame(value);
         currentGroup.pause();
     } else {
-        currentGroup.goToFrame(this.value);
+        currentGroup.goToFrame(value);
     }
 });
 

+ 28 - 0
src/Animations/babylon.animationGroup.ts

@@ -24,9 +24,15 @@ module BABYLON {
         private _from = Number.MAX_VALUE;
         private _to = -Number.MAX_VALUE;
         private _isStarted: boolean;
+        private _isPaused: boolean;
         private _speedRatio = 1;
 
         /**
+         * Gets or sets the unique id of the node
+         */
+        public uniqueId: number;
+
+        /**
          * This observable will notify when one animation have ended.
          */
         public onAnimationEndObservable = new Observable<TargetedAnimation>();
@@ -42,6 +48,11 @@ module BABYLON {
         public onAnimationGroupPauseObservable = new Observable<AnimationGroup>();
 
         /**
+         * This observable will notify when all animations are playing.
+         */
+        public onAnimationGroupPlayObservable = new Observable<AnimationGroup>();
+
+        /**
          * Gets the first frame
          */
         public get from(): number {
@@ -63,6 +74,13 @@ module BABYLON {
         }
 
         /**
+         * Gets a value indicating that the current group is playing
+         */
+        public get isPlaying(): boolean {
+            return this._isStarted && !this._isPaused;
+        }
+
+        /**
          * Gets or sets the speed ratio to use for all animations
          */
         public get speedRatio(): number {
@@ -111,6 +129,7 @@ module BABYLON {
             public name: string,
             scene: Nullable<Scene> = null) {
             this._scene = scene || Engine.LastCreatedScene!;
+            this.uniqueId = this._scene.getUniqueId();
 
             this._scene.animationGroups.push(this);
         }
@@ -216,6 +235,9 @@ module BABYLON {
             }
 
             this._isStarted = true;
+            this._isPaused = false;
+
+            this.onAnimationGroupPlayObservable.notifyObservers(this);
 
             return this;
         }
@@ -229,6 +251,8 @@ module BABYLON {
                 return this;
             }
 
+            this._isPaused = true;
+
             for (var index = 0; index < this._animatables.length; index++) {
                 let animatable = this._animatables[index];
                 animatable.pause();
@@ -260,6 +284,8 @@ module BABYLON {
                 this.start(loop, this._speedRatio);
             }
 
+            this._isPaused = false;
+
             return this;
         }
 
@@ -294,6 +320,8 @@ module BABYLON {
                 animatable.restart();
             }
 
+            this.onAnimationGroupPlayObservable.notifyObservers(this);
+
             return this;
         }