瀏覽代碼

Merge branch 'master' into draco-fix

David Catuhe 5 年之前
父節點
當前提交
738bf1715a
共有 99 個文件被更改,包括 7661 次插入1629 次删除
  1. 341 16
      dist/preview release/babylon.d.ts
  2. 1 1
      dist/preview release/babylon.js
  3. 988 181
      dist/preview release/babylon.max.js
  4. 1 1
      dist/preview release/babylon.max.js.map
  5. 709 33
      dist/preview release/babylon.module.d.ts
  6. 394 20
      dist/preview release/documentation.d.ts
  7. 48 48
      dist/preview release/gui/babylon.gui.js
  8. 1 1
      dist/preview release/gui/babylon.gui.js.map
  9. 5 5
      dist/preview release/inspector/babylon.inspector.bundle.js
  10. 973 390
      dist/preview release/inspector/babylon.inspector.bundle.max.js
  11. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.max.js.map
  12. 138 28
      dist/preview release/inspector/babylon.inspector.d.ts
  13. 287 56
      dist/preview release/inspector/babylon.inspector.module.d.ts
  14. 151 20
      dist/preview release/loaders/babylon.glTF2FileLoader.js
  15. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.js.map
  16. 1 1
      dist/preview release/loaders/babylon.glTF2FileLoader.min.js
  17. 151 20
      dist/preview release/loaders/babylon.glTFFileLoader.js
  18. 1 1
      dist/preview release/loaders/babylon.glTFFileLoader.js.map
  19. 2 2
      dist/preview release/loaders/babylon.glTFFileLoader.min.js
  20. 48 1
      dist/preview release/loaders/babylonjs.loaders.d.ts
  21. 151 20
      dist/preview release/loaders/babylonjs.loaders.js
  22. 1 1
      dist/preview release/loaders/babylonjs.loaders.js.map
  23. 1 1
      dist/preview release/loaders/babylonjs.loaders.min.js
  24. 102 2
      dist/preview release/loaders/babylonjs.loaders.module.d.ts
  25. 1 1
      dist/preview release/nodeEditor/babylon.nodeEditor.d.ts
  26. 1 1
      dist/preview release/nodeEditor/babylon.nodeEditor.js
  27. 18 2
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js
  28. 1 1
      dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map
  29. 4 2
      dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts
  30. 1 1
      dist/preview release/packagesSizeBaseLine.json
  31. 709 33
      dist/preview release/viewer/babylon.module.d.ts
  32. 150 134
      dist/preview release/viewer/babylon.viewer.js
  33. 2 2
      dist/preview release/viewer/babylon.viewer.max.js
  34. 102 2
      dist/preview release/viewer/babylonjs.loaders.module.d.ts
  35. 7 5
      dist/preview release/what's new.md
  36. 3 3
      inspector/src/components/actionTabs/lineContainerComponent.tsx
  37. 13 1
      inspector/src/components/actionTabs/tabs/propertyGridTabComponent.tsx
  38. 8 3
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorSvgPoint.tsx
  39. 769 383
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx
  40. 6 1
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationGroupPropertyGridComponent.tsx
  41. 7 6
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationPropertyGridComponent.tsx
  42. 4 3
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/curveEditor.scss
  43. 7 1
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/graphActionsBar.tsx
  44. 16 4
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint.tsx
  45. 16 5
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/svgDraggableArea.tsx
  46. 113 0
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/targetedAnimationPropertyGridComponent.tsx
  47. 115 25
      inspector/src/components/actionTabs/tabs/propertyGrids/animations/timeline.tsx
  48. 2 0
      inspector/src/components/actionTabs/tabs/propertyGrids/scenePropertyGridComponent.tsx
  49. 1 1
      inspector/src/components/globalState.ts
  50. 2 2
      inspector/src/components/sceneExplorer/entities/animationGroupTreeItemComponent.tsx
  51. 32 0
      inspector/src/components/sceneExplorer/entities/targetedAnimationTreeItemComponent.tsx
  52. 14 0
      inspector/src/components/sceneExplorer/sceneExplorer.scss
  53. 3 2
      inspector/src/components/sceneExplorer/treeItemSelectableComponent.tsx
  54. 6 1
      inspector/src/components/sceneExplorer/treeItemSpecializedComponent.tsx
  55. 7 1
      inspector/src/inspector.ts
  56. 7 5
      loaders/src/glTF/2.0/Extensions/KHR_materials_variants.ts
  57. 11 0
      nodeEditor/src/blockTools.ts
  58. 5 2
      nodeEditor/src/components/nodeList/nodeListComponent.tsx
  59. 2 0
      nodeEditor/src/diagram/display/inputDisplayManager.ts
  60. 1 1
      readme.md
  61. 68 0
      src/Animations/animation.ts
  62. 15 0
      src/Animations/animationGroup.ts
  63. 7 2
      src/Behaviors/Meshes/pointerDragBehavior.ts
  64. 5 0
      src/Cameras/targetCamera.ts
  65. 10 4
      src/Debug/debugLayer.ts
  66. 1 1
      src/DeviceInput/deviceInputSystem.ts
  67. 8 0
      src/Engines/thinEngine.ts
  68. 9 7
      src/Materials/Node/Blocks/Dual/textureBlock.ts
  69. 110 0
      src/Materials/Node/Blocks/Fragment/fragCoordBlock.ts
  70. 2 0
      src/Materials/Node/Blocks/Fragment/index.ts
  71. 98 0
      src/Materials/Node/Blocks/Fragment/screenSizeBlock.ts
  72. 3 0
      src/Materials/Node/Blocks/Input/inputBlock.ts
  73. 3 12
      src/Materials/Node/Blocks/PBR/clearCoatBlock.ts
  74. 58 27
      src/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.ts
  75. 11 1
      src/Materials/Node/Blocks/PBR/reflectionBlock.ts
  76. 46 4
      src/Materials/Node/Blocks/PBR/reflectivityBlock.ts
  77. 1 1
      src/Materials/Node/Blocks/PBR/refractionBlock.ts
  78. 1 10
      src/Materials/Node/Blocks/PBR/sheenBlock.ts
  79. 73 0
      src/Materials/shaderMaterial.ts
  80. 13 1
      src/Meshes/instancedMesh.ts
  81. 8 0
      src/Meshes/mesh.ts
  82. 264 0
      src/Misc/timer.ts
  83. 4 2
      src/Misc/tools.ts
  84. 9 2
      src/Particles/gpuParticleSystem.ts
  85. 9 2
      src/Particles/particleSystem.ts
  86. 3 1
      src/Physics/Plugins/cannonJSPlugin.ts
  87. 79 13
      src/Rendering/edgesRenderer.ts
  88. 1 0
      src/Rendering/geometryBufferRenderer.ts
  89. 5 0
      src/Shaders/ShadersInclude/helperFunctions.fx
  90. 14 11
      src/Shaders/gpuRenderParticles.vertex.fx
  91. 8 2
      src/Shaders/line.vertex.fx
  92. 11 8
      src/Shaders/particles.vertex.fx
  93. 10 4
      src/XR/features/WebXRControllerPointerSelection.ts
  94. 16 23
      src/XR/features/WebXRControllerTeleportation.ts
  95. 2 0
      src/XR/webXREnterExitUI.ts
  96. 1 6
      src/XR/webXRExperienceHelper.ts
  97. 7 0
      src/scene.ts
  98. 二進制
      tests/validation/ReferenceImages/node-material-pbr-1.png
  99. 5 0
      tests/validation/config.json

+ 341 - 16
dist/preview release/babylon.d.ts

@@ -16301,6 +16301,10 @@ declare module BABYLON {
              * @param stride defines the stride in floats
              */
             registerInstancedBuffer(kind: string, stride: number): void;
+            /**
+             * true to use the edge renderer for all instances of this mesh
+             */
+            edgesShareWithInstances: boolean;
             /** @hidden */
             _userInstancedBuffersStorage: {
                 data: {
@@ -16395,6 +16399,10 @@ declare module BABYLON {
         private _renderId;
         private _multiview;
         private _cachedDefines;
+        /** Define the Url to load snippets */
+        static SnippetUrl: string;
+        /** Snippet ID if the material was created from the snippet server */
+        snippetId: string;
         /**
          * Instantiate a new shader material.
          * The ShaderMaterial object has the necessary methods to pass data from your scene to the Vertex and Fragment Shaders and returns a material that can be applied to any mesh.
@@ -16649,6 +16657,23 @@ declare module BABYLON {
          * @returns a new material
          */
         static Parse(source: any, scene: Scene, rootUrl: string): ShaderMaterial;
+        /**
+         * Creates a new ShaderMaterial from a snippet saved in a remote file
+         * @param name defines the name of the ShaderMaterial to create (can be null or empty to use the one from the json data)
+         * @param url defines the url to load from
+         * @param scene defines the hosting scene
+         * @param rootUrl defines the root URL to use to load textures and relative dependencies
+         * @returns a promise that will resolve to the new ShaderMaterial
+         */
+        static ParseFromFileAsync(name: Nullable<string>, url: string, scene: Scene, rootUrl?: string): Promise<ShaderMaterial>;
+        /**
+         * Creates a ShaderMaterial from a snippet saved by the Inspector
+         * @param snippetId defines the snippet to load
+         * @param scene defines the hosting scene
+         * @param rootUrl defines the root URL to use to load textures and relative dependencies
+         * @returns a promise that will resolve to the new ShaderMaterial
+         */
+        static CreateFromSnippetAsync(snippetId: string, scene: Scene, rootUrl?: string): Promise<ShaderMaterial>;
     }
 }
 declare module BABYLON {
@@ -16788,6 +16813,10 @@ declare module BABYLON {
     };
 }
 declare module BABYLON {
+        interface Scene {
+            /** @hidden */
+            _edgeRenderLineShader: Nullable<ShaderMaterial>;
+        }
         interface AbstractMesh {
             /**
              * Gets the edgesRenderer associated with the mesh
@@ -16833,6 +16862,10 @@ declare module BABYLON {
          * @return true if ready, otherwise false.
          */
         isReady(): boolean;
+        /**
+         * List of instances to render in case the source mesh has instances
+         */
+        customInstances: SmartArray<Matrix>;
     }
     /**
      * This class is used to generate edges of the mesh that could then easily be rendered in a scene.
@@ -16857,12 +16890,20 @@ declare module BABYLON {
         protected _buffers: {
             [key: string]: Nullable<VertexBuffer>;
         };
+        protected _buffersForInstances: {
+            [key: string]: Nullable<VertexBuffer>;
+        };
         protected _checkVerticesInsteadOfIndices: boolean;
         private _meshRebuildObserver;
         private _meshDisposeObserver;
         /** Gets or sets a boolean indicating if the edgesRenderer is active */
         isEnabled: boolean;
         /**
+         * List of instances to render in case the source mesh has instances
+         */
+        customInstances: SmartArray<Matrix>;
+        private static GetShader;
+        /**
          * Creates an instance of the EdgesRenderer. It is primarily use to display edges of a mesh.
          * Beware when you use this class with complex objects as the adjacencies computation can be really long
          * @param  source Mesh used to create edges
@@ -21616,6 +21657,15 @@ declare module BABYLON {
          */
         get transform(): NodeMaterialConnectionPoint;
         protected _buildBlock(state: NodeMaterialBuildState): this;
+        /**
+         * Update defines for shader compilation
+         * @param mesh defines the mesh to be rendered
+         * @param nodeMaterial defines the node material requesting the update
+         * @param defines defines the material defines to update
+         * @param useInstances specifies that instances should be used
+         * @param subMesh defines which submesh to render
+         */
+        prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances?: boolean, subMesh?: SubMesh): void;
         serialize(): any;
         _deserialize(serializationObject: any, scene: Scene, rootUrl: string): void;
         protected _dumpPropertiesCode(): string;
@@ -27577,6 +27627,8 @@ declare module BABYLON {
         /** @hidden */
         _registerInstanceForRenderId(instance: InstancedMesh, renderId: number): Mesh;
         protected _afterComputeWorldMatrix(): void;
+        /** @hidden */
+        _postActivate(): void;
         /**
          * This method recomputes and sets a new BoundingInfo to the mesh unless it is locked.
          * This means the mesh underlying bounding box and sphere are recomputed.
@@ -32203,6 +32255,10 @@ declare module BABYLON {
          * When matrix interpolation is enabled, this boolean forces the system to use Matrix.DecomposeLerp instead of Matrix.Lerp. Interpolation is more precise but slower
          */
         static AllowMatrixDecomposeForInterpolation: boolean;
+        /** Define the Url to load snippets */
+        static SnippetUrl: string;
+        /** Snippet ID if the animation was created from the snippet server */
+        snippetId: string;
         /**
          * Stores the key frames of the animation
          */
@@ -32590,6 +32646,19 @@ declare module BABYLON {
          * @param destination Target to store the animations
          */
         static AppendSerializedAnimations(source: IAnimatable, destination: any): void;
+        /**
+         * Creates a new animation from a snippet saved in a remote file
+         * @param name defines the name of the animation to create (can be null or empty to use the one from the json data)
+         * @param url defines the url to load from
+         * @returns a promise that will resolve to the new animation
+         */
+        static ParseFromFileAsync(name: Nullable<string>, url: string): Promise<Animation>;
+        /**
+         * Creates an animation from a snippet saved by the Inspector
+         * @param snippetId defines the snippet to load
+         * @returns a promise that will resolve to the new animation
+         */
+        static CreateFromSnippetAsync(snippetId: string): Promise<Animation>;
     }
 }
 declare module BABYLON {
@@ -34047,6 +34116,10 @@ declare module BABYLON {
          * Defines that engine should compile shaders with high precision floats (if supported). True by default
          */
         useHighPrecisionFloats?: boolean;
+        /**
+         * Make the canvas XR Compatible for XR sessions
+         */
+        xrCompatible?: boolean;
     }
     /**
      * The base engine class (root of all engines)
@@ -37880,6 +37953,11 @@ declare module BABYLON {
          */
         target: any;
         /**
+         * Returns the string "TargetedAnimation"
+         * @returns "TargetedAnimation"
+         */
+        getClassName(): string;
+        /**
          * Serialize the object
          * @returns the JSON object representing the current entity
          */
@@ -37972,6 +38050,10 @@ declare module BABYLON {
          */
         get animatables(): Array<Animatable>;
         /**
+         * Gets the list of target animations
+         */
+        get children(): TargetedAnimation[];
+        /**
          * Instantiates a new Animation Group.
          * This helps managing several animations at once.
          * @see http://doc.babylonjs.com/how_to/group
@@ -47621,7 +47703,7 @@ declare module BABYLON {
         /**
          * Default color of the laser pointer
          */
-        lasterPointerDefaultColor: Color3;
+        laserPointerDefaultColor: Color3;
         /**
          * default color of the selection ring
          */
@@ -47678,6 +47760,8 @@ declare module BABYLON {
         private _generateNewMeshPair;
         private _pickingMoved;
         private _updatePointerDistance;
+        /** @hidden */
+        get lasterPointerDefaultColor(): Color3;
     }
 }
 declare module BABYLON {
@@ -47859,6 +47943,157 @@ declare module BABYLON {
 }
 declare module BABYLON {
     /**
+     * Construction options for a timer
+     */
+    export interface ITimerOptions<T> {
+        /**
+         * Time-to-end
+         */
+        timeout: number;
+        /**
+         * The context observable is used to calculate time deltas and provides the context of the timer's callbacks. Will usually be OnBeforeRenderObservable.
+         * Countdown calculation is done ONLY when the observable is notifying its observers, meaning that if
+         * you choose an observable that doesn't trigger too often, the wait time might extend further than the requested max time
+         */
+        contextObservable: Observable<T>;
+        /**
+         * Optional parameters when adding an observer to the observable
+         */
+        observableParameters?: {
+            mask?: number;
+            insertFirst?: boolean;
+            scope?: any;
+        };
+        /**
+         * An optional break condition that will stop the times prematurely. In this case onEnded will not be triggered!
+         */
+        breakCondition?: (data?: ITimerData<T>) => boolean;
+        /**
+         * Will be triggered when the time condition has met
+         */
+        onEnded?: (data: ITimerData<any>) => void;
+        /**
+         * Will be triggered when the break condition has met (prematurely ended)
+         */
+        onAborted?: (data: ITimerData<any>) => void;
+        /**
+         * Optional function to execute on each tick (or count)
+         */
+        onTick?: (data: ITimerData<any>) => void;
+    }
+    /**
+     * An interface defining the data sent by the timer
+     */
+    export interface ITimerData<T> {
+        /**
+         * When did it start
+         */
+        startTime: number;
+        /**
+         * Time now
+         */
+        currentTime: number;
+        /**
+         * Time passed since started
+         */
+        deltaTime: number;
+        /**
+         * How much is completed, in [0.0...1.0].
+         * Note that this CAN be higher than 1 due to the fact that we don't actually measure time but delta between observable calls
+         */
+        completeRate: number;
+        /**
+         * What the registered observable sent in the last count
+         */
+        payload: T;
+    }
+    /**
+     * The current state of the timer
+     */
+    export enum TimerState {
+        /**
+         * Timer initialized, not yet started
+         */
+        INIT = 0,
+        /**
+         * Timer started and counting
+         */
+        STARTED = 1,
+        /**
+         * Timer ended (whether aborted or time reached)
+         */
+        ENDED = 2
+    }
+    /**
+     * A simple version of the timer. Will take options and start the timer immediately after calling it
+     *
+     * @param options options with which to initialize this timer
+     */
+    export function setAndStartTimer(options: ITimerOptions<any>): Nullable<Observer<any>>;
+    /**
+     * An advanced implementation of a timer class
+     */
+    export class AdvancedTimer<T = any> implements IDisposable {
+        /**
+         * Will notify each time the timer calculates the remaining time
+         */
+        onEachCountObservable: Observable<ITimerData<T>>;
+        /**
+         * Will trigger when the timer was aborted due to the break condition
+         */
+        onTimerAbortedObservable: Observable<ITimerData<T>>;
+        /**
+         * Will trigger when the timer ended successfully
+         */
+        onTimerEndedObservable: Observable<ITimerData<T>>;
+        /**
+         * Will trigger when the timer state has changed
+         */
+        onStateChangedObservable: Observable<TimerState>;
+        private _observer;
+        private _contextObservable;
+        private _observableParameters;
+        private _startTime;
+        private _timer;
+        private _state;
+        private _breakCondition;
+        private _timeToEnd;
+        private _breakOnNextTick;
+        /**
+         * Will construct a new advanced timer based on the options provided. Timer will not start until start() is called.
+         * @param options construction options for this advanced timer
+         */
+        constructor(options: ITimerOptions<T>);
+        /**
+         * set a breaking condition for this timer. Default is to never break during count
+         * @param predicate the new break condition. Returns true to break, false otherwise
+         */
+        set breakCondition(predicate: (data: ITimerData<T>) => boolean);
+        /**
+         * Reset ALL associated observables in this advanced timer
+         */
+        clearObservables(): void;
+        /**
+         * Will start a new iteration of this timer. Only one instance of this timer can run at a time.
+         *
+         * @param timeToEnd how much time to measure until timer ended
+         */
+        start(timeToEnd?: number): void;
+        /**
+         * Will force a stop on the next tick.
+         */
+        stop(): void;
+        /**
+         * Dispose this timer, clearing all resources
+         */
+        dispose(): void;
+        private _setState;
+        private _tick;
+        private _stop;
+    }
+}
+declare module BABYLON {
+    /**
      * The options container for the teleportation module
      */
     export interface IWebXRTeleportationOptions {
@@ -49328,9 +49563,9 @@ declare module BABYLON {
         /**
          * Select a specific entity in the scene explorer and highlight a specific block in that entity property grid
          * @param entity defines the entity to select
-         * @param lineContainerTitle defines the specific block to highlight
+         * @param lineContainerTitles defines the specific blocks to highlight (could be a string or an array of strings)
          */
-        select(entity: any, lineContainerTitle?: string): void;
+        select(entity: any, lineContainerTitles?: string | string[]): void;
         /** Get the inspector from bundle or global */
         private _getGlobalInspector;
         /**
@@ -60998,6 +61233,87 @@ declare module BABYLON {
 }
 declare module BABYLON {
     /**
+     * Block used to make gl_FragCoord available
+     */
+    export class FragCoordBlock extends NodeMaterialBlock {
+        /**
+         * Creates a new FragCoordBlock
+         * @param name defines the block name
+         */
+        constructor(name: string);
+        /**
+         * Gets the current class name
+         * @returns the class name
+         */
+        getClassName(): string;
+        /**
+         * Gets the xy component
+         */
+        get xy(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the xyz component
+         */
+        get xyz(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the xyzw component
+         */
+        get xyzw(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the x component
+         */
+        get x(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the y component
+         */
+        get y(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the z component
+         */
+        get z(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the w component
+         */
+        get output(): NodeMaterialConnectionPoint;
+        protected writeOutputs(state: NodeMaterialBuildState): string;
+        protected _buildBlock(state: NodeMaterialBuildState): this;
+    }
+}
+declare module BABYLON {
+    /**
+     * Block used to get the screen sizes
+     */
+    export class ScreenSizeBlock extends NodeMaterialBlock {
+        private _varName;
+        private _scene;
+        /**
+         * Creates a new ScreenSizeBlock
+         * @param name defines the block name
+         */
+        constructor(name: string);
+        /**
+         * Gets the current class name
+         * @returns the class name
+         */
+        getClassName(): string;
+        /**
+         * Gets the xy component
+         */
+        get xy(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the x component
+         */
+        get x(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the y component
+         */
+        get y(): NodeMaterialConnectionPoint;
+        bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh): void;
+        protected writeOutputs(state: NodeMaterialBuildState, varName: string): string;
+        protected _buildBlock(state: NodeMaterialBuildState): this;
+    }
+}
+declare module BABYLON {
+    /**
      * Block used to add support for scene fog
      */
     export class FogBlock extends NodeMaterialBlock {
@@ -62535,6 +62851,8 @@ declare module BABYLON {
         _vReflectionMicrosurfaceInfosName: string;
         /** @hidden */
         _vReflectionInfosName: string;
+        /** @hidden */
+        _vReflectionFilteringInfoName: string;
         private _scene;
         /**
          * The three properties below are set by the main PBR block prior to calling methods of this class.
@@ -62671,10 +62989,6 @@ declare module BABYLON {
          */
         get roughness(): NodeMaterialConnectionPoint;
         /**
-         * Gets the texture input component
-         */
-        get texture(): NodeMaterialConnectionPoint;
-        /**
          * Gets the sheen object output component
          */
         get sheen(): NodeMaterialConnectionPoint;
@@ -62696,6 +63010,15 @@ declare module BABYLON {
      * Block used to implement the reflectivity module of the PBR material
      */
     export class ReflectivityBlock extends NodeMaterialBlock {
+        private _metallicReflectanceColor;
+        private _metallicF0Factor;
+        /** @hidden */
+        _vMetallicReflectanceFactorsName: string;
+        /**
+         * The property below is set by the main PBR block prior to calling methods of this class.
+        */
+        /** @hidden */
+        indexOfRefractionConnectionPoint: Nullable<NodeMaterialConnectionPoint>;
         /**
          * Specifies if the metallic texture contains the ambient occlusion information in its red channel.
          */
@@ -62743,12 +63066,14 @@ declare module BABYLON {
          * Gets the reflectivity object output component
          */
         get reflectivity(): NodeMaterialConnectionPoint;
+        bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh, subMesh?: SubMesh): void;
         /**
          * Gets the main code of the block (fragment side)
+         * @param state current state of the node material building
          * @param aoIntensityVarName name of the variable with the ambient occlusion intensity
          * @returns the shader code
          */
-        getCode(aoIntensityVarName: string): string;
+        getCode(state: NodeMaterialBuildState, aoIntensityVarName: string): string;
         prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines): void;
         protected _buildBlock(state: NodeMaterialBuildState): this;
         protected _dumpPropertiesCode(): string;
@@ -62879,10 +63204,6 @@ declare module BABYLON {
          */
         get tintThickness(): NodeMaterialConnectionPoint;
         /**
-         * Gets the tint texture input component
-         */
-        get tintTexture(): NodeMaterialConnectionPoint;
-        /**
          * Gets the world tangent input component
          */
         get worldTangent(): NodeMaterialConnectionPoint;
@@ -63060,6 +63381,14 @@ declare module BABYLON {
          */
         enableSpecularAntiAliasing: boolean;
         /**
+         * Enables realtime filtering on the texture.
+         */
+        realTimeFiltering: boolean;
+        /**
+         * Quality switch for realtime filtering
+         */
+        realTimeFilteringQuality: number;
+        /**
          * Defines if the material uses energy conservation.
          */
         useEnergyConservation: boolean;
@@ -63129,10 +63458,6 @@ declare module BABYLON {
          */
         get baseColor(): NodeMaterialConnectionPoint;
         /**
-         * Gets the base texture input component
-         */
-        get baseTexture(): NodeMaterialConnectionPoint;
-        /**
          * Gets the opacity texture input component
          */
         get opacityTexture(): NodeMaterialConnectionPoint;

文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/babylon.js


文件差異過大導致無法顯示
+ 988 - 181
dist/preview release/babylon.max.js


文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/babylon.max.js.map


文件差異過大導致無法顯示
+ 709 - 33
dist/preview release/babylon.module.d.ts


+ 394 - 20
dist/preview release/documentation.d.ts

@@ -16301,6 +16301,10 @@ declare module BABYLON {
              * @param stride defines the stride in floats
              */
             registerInstancedBuffer(kind: string, stride: number): void;
+            /**
+             * true to use the edge renderer for all instances of this mesh
+             */
+            edgesShareWithInstances: boolean;
             /** @hidden */
             _userInstancedBuffersStorage: {
                 data: {
@@ -16395,6 +16399,10 @@ declare module BABYLON {
         private _renderId;
         private _multiview;
         private _cachedDefines;
+        /** Define the Url to load snippets */
+        static SnippetUrl: string;
+        /** Snippet ID if the material was created from the snippet server */
+        snippetId: string;
         /**
          * Instantiate a new shader material.
          * The ShaderMaterial object has the necessary methods to pass data from your scene to the Vertex and Fragment Shaders and returns a material that can be applied to any mesh.
@@ -16649,6 +16657,23 @@ declare module BABYLON {
          * @returns a new material
          */
         static Parse(source: any, scene: Scene, rootUrl: string): ShaderMaterial;
+        /**
+         * Creates a new ShaderMaterial from a snippet saved in a remote file
+         * @param name defines the name of the ShaderMaterial to create (can be null or empty to use the one from the json data)
+         * @param url defines the url to load from
+         * @param scene defines the hosting scene
+         * @param rootUrl defines the root URL to use to load textures and relative dependencies
+         * @returns a promise that will resolve to the new ShaderMaterial
+         */
+        static ParseFromFileAsync(name: Nullable<string>, url: string, scene: Scene, rootUrl?: string): Promise<ShaderMaterial>;
+        /**
+         * Creates a ShaderMaterial from a snippet saved by the Inspector
+         * @param snippetId defines the snippet to load
+         * @param scene defines the hosting scene
+         * @param rootUrl defines the root URL to use to load textures and relative dependencies
+         * @returns a promise that will resolve to the new ShaderMaterial
+         */
+        static CreateFromSnippetAsync(snippetId: string, scene: Scene, rootUrl?: string): Promise<ShaderMaterial>;
     }
 }
 declare module BABYLON {
@@ -16788,6 +16813,10 @@ declare module BABYLON {
     };
 }
 declare module BABYLON {
+        interface Scene {
+            /** @hidden */
+            _edgeRenderLineShader: Nullable<ShaderMaterial>;
+        }
         interface AbstractMesh {
             /**
              * Gets the edgesRenderer associated with the mesh
@@ -16833,6 +16862,10 @@ declare module BABYLON {
          * @return true if ready, otherwise false.
          */
         isReady(): boolean;
+        /**
+         * List of instances to render in case the source mesh has instances
+         */
+        customInstances: SmartArray<Matrix>;
     }
     /**
      * This class is used to generate edges of the mesh that could then easily be rendered in a scene.
@@ -16857,12 +16890,20 @@ declare module BABYLON {
         protected _buffers: {
             [key: string]: Nullable<VertexBuffer>;
         };
+        protected _buffersForInstances: {
+            [key: string]: Nullable<VertexBuffer>;
+        };
         protected _checkVerticesInsteadOfIndices: boolean;
         private _meshRebuildObserver;
         private _meshDisposeObserver;
         /** Gets or sets a boolean indicating if the edgesRenderer is active */
         isEnabled: boolean;
         /**
+         * List of instances to render in case the source mesh has instances
+         */
+        customInstances: SmartArray<Matrix>;
+        private static GetShader;
+        /**
          * Creates an instance of the EdgesRenderer. It is primarily use to display edges of a mesh.
          * Beware when you use this class with complex objects as the adjacencies computation can be really long
          * @param  source Mesh used to create edges
@@ -21616,6 +21657,15 @@ declare module BABYLON {
          */
         get transform(): NodeMaterialConnectionPoint;
         protected _buildBlock(state: NodeMaterialBuildState): this;
+        /**
+         * Update defines for shader compilation
+         * @param mesh defines the mesh to be rendered
+         * @param nodeMaterial defines the node material requesting the update
+         * @param defines defines the material defines to update
+         * @param useInstances specifies that instances should be used
+         * @param subMesh defines which submesh to render
+         */
+        prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines, useInstances?: boolean, subMesh?: SubMesh): void;
         serialize(): any;
         _deserialize(serializationObject: any, scene: Scene, rootUrl: string): void;
         protected _dumpPropertiesCode(): string;
@@ -27577,6 +27627,8 @@ declare module BABYLON {
         /** @hidden */
         _registerInstanceForRenderId(instance: InstancedMesh, renderId: number): Mesh;
         protected _afterComputeWorldMatrix(): void;
+        /** @hidden */
+        _postActivate(): void;
         /**
          * This method recomputes and sets a new BoundingInfo to the mesh unless it is locked.
          * This means the mesh underlying bounding box and sphere are recomputed.
@@ -32203,6 +32255,10 @@ declare module BABYLON {
          * When matrix interpolation is enabled, this boolean forces the system to use Matrix.DecomposeLerp instead of Matrix.Lerp. Interpolation is more precise but slower
          */
         static AllowMatrixDecomposeForInterpolation: boolean;
+        /** Define the Url to load snippets */
+        static SnippetUrl: string;
+        /** Snippet ID if the animation was created from the snippet server */
+        snippetId: string;
         /**
          * Stores the key frames of the animation
          */
@@ -32590,6 +32646,19 @@ declare module BABYLON {
          * @param destination Target to store the animations
          */
         static AppendSerializedAnimations(source: IAnimatable, destination: any): void;
+        /**
+         * Creates a new animation from a snippet saved in a remote file
+         * @param name defines the name of the animation to create (can be null or empty to use the one from the json data)
+         * @param url defines the url to load from
+         * @returns a promise that will resolve to the new animation
+         */
+        static ParseFromFileAsync(name: Nullable<string>, url: string): Promise<Animation>;
+        /**
+         * Creates an animation from a snippet saved by the Inspector
+         * @param snippetId defines the snippet to load
+         * @returns a promise that will resolve to the new animation
+         */
+        static CreateFromSnippetAsync(snippetId: string): Promise<Animation>;
     }
 }
 declare module BABYLON {
@@ -34047,6 +34116,10 @@ declare module BABYLON {
          * Defines that engine should compile shaders with high precision floats (if supported). True by default
          */
         useHighPrecisionFloats?: boolean;
+        /**
+         * Make the canvas XR Compatible for XR sessions
+         */
+        xrCompatible?: boolean;
     }
     /**
      * The base engine class (root of all engines)
@@ -37880,6 +37953,11 @@ declare module BABYLON {
          */
         target: any;
         /**
+         * Returns the string "TargetedAnimation"
+         * @returns "TargetedAnimation"
+         */
+        getClassName(): string;
+        /**
          * Serialize the object
          * @returns the JSON object representing the current entity
          */
@@ -37972,6 +38050,10 @@ declare module BABYLON {
          */
         get animatables(): Array<Animatable>;
         /**
+         * Gets the list of target animations
+         */
+        get children(): TargetedAnimation[];
+        /**
          * Instantiates a new Animation Group.
          * This helps managing several animations at once.
          * @see http://doc.babylonjs.com/how_to/group
@@ -47621,7 +47703,7 @@ declare module BABYLON {
         /**
          * Default color of the laser pointer
          */
-        lasterPointerDefaultColor: Color3;
+        laserPointerDefaultColor: Color3;
         /**
          * default color of the selection ring
          */
@@ -47678,6 +47760,8 @@ declare module BABYLON {
         private _generateNewMeshPair;
         private _pickingMoved;
         private _updatePointerDistance;
+        /** @hidden */
+        get lasterPointerDefaultColor(): Color3;
     }
 }
 declare module BABYLON {
@@ -47859,6 +47943,157 @@ declare module BABYLON {
 }
 declare module BABYLON {
     /**
+     * Construction options for a timer
+     */
+    export interface ITimerOptions<T> {
+        /**
+         * Time-to-end
+         */
+        timeout: number;
+        /**
+         * The context observable is used to calculate time deltas and provides the context of the timer's callbacks. Will usually be OnBeforeRenderObservable.
+         * Countdown calculation is done ONLY when the observable is notifying its observers, meaning that if
+         * you choose an observable that doesn't trigger too often, the wait time might extend further than the requested max time
+         */
+        contextObservable: Observable<T>;
+        /**
+         * Optional parameters when adding an observer to the observable
+         */
+        observableParameters?: {
+            mask?: number;
+            insertFirst?: boolean;
+            scope?: any;
+        };
+        /**
+         * An optional break condition that will stop the times prematurely. In this case onEnded will not be triggered!
+         */
+        breakCondition?: (data?: ITimerData<T>) => boolean;
+        /**
+         * Will be triggered when the time condition has met
+         */
+        onEnded?: (data: ITimerData<any>) => void;
+        /**
+         * Will be triggered when the break condition has met (prematurely ended)
+         */
+        onAborted?: (data: ITimerData<any>) => void;
+        /**
+         * Optional function to execute on each tick (or count)
+         */
+        onTick?: (data: ITimerData<any>) => void;
+    }
+    /**
+     * An interface defining the data sent by the timer
+     */
+    export interface ITimerData<T> {
+        /**
+         * When did it start
+         */
+        startTime: number;
+        /**
+         * Time now
+         */
+        currentTime: number;
+        /**
+         * Time passed since started
+         */
+        deltaTime: number;
+        /**
+         * How much is completed, in [0.0...1.0].
+         * Note that this CAN be higher than 1 due to the fact that we don't actually measure time but delta between observable calls
+         */
+        completeRate: number;
+        /**
+         * What the registered observable sent in the last count
+         */
+        payload: T;
+    }
+    /**
+     * The current state of the timer
+     */
+    export enum TimerState {
+        /**
+         * Timer initialized, not yet started
+         */
+        INIT = 0,
+        /**
+         * Timer started and counting
+         */
+        STARTED = 1,
+        /**
+         * Timer ended (whether aborted or time reached)
+         */
+        ENDED = 2
+    }
+    /**
+     * A simple version of the timer. Will take options and start the timer immediately after calling it
+     *
+     * @param options options with which to initialize this timer
+     */
+    export function setAndStartTimer(options: ITimerOptions<any>): Nullable<Observer<any>>;
+    /**
+     * An advanced implementation of a timer class
+     */
+    export class AdvancedTimer<T = any> implements IDisposable {
+        /**
+         * Will notify each time the timer calculates the remaining time
+         */
+        onEachCountObservable: Observable<ITimerData<T>>;
+        /**
+         * Will trigger when the timer was aborted due to the break condition
+         */
+        onTimerAbortedObservable: Observable<ITimerData<T>>;
+        /**
+         * Will trigger when the timer ended successfully
+         */
+        onTimerEndedObservable: Observable<ITimerData<T>>;
+        /**
+         * Will trigger when the timer state has changed
+         */
+        onStateChangedObservable: Observable<TimerState>;
+        private _observer;
+        private _contextObservable;
+        private _observableParameters;
+        private _startTime;
+        private _timer;
+        private _state;
+        private _breakCondition;
+        private _timeToEnd;
+        private _breakOnNextTick;
+        /**
+         * Will construct a new advanced timer based on the options provided. Timer will not start until start() is called.
+         * @param options construction options for this advanced timer
+         */
+        constructor(options: ITimerOptions<T>);
+        /**
+         * set a breaking condition for this timer. Default is to never break during count
+         * @param predicate the new break condition. Returns true to break, false otherwise
+         */
+        set breakCondition(predicate: (data: ITimerData<T>) => boolean);
+        /**
+         * Reset ALL associated observables in this advanced timer
+         */
+        clearObservables(): void;
+        /**
+         * Will start a new iteration of this timer. Only one instance of this timer can run at a time.
+         *
+         * @param timeToEnd how much time to measure until timer ended
+         */
+        start(timeToEnd?: number): void;
+        /**
+         * Will force a stop on the next tick.
+         */
+        stop(): void;
+        /**
+         * Dispose this timer, clearing all resources
+         */
+        dispose(): void;
+        private _setState;
+        private _tick;
+        private _stop;
+    }
+}
+declare module BABYLON {
+    /**
      * The options container for the teleportation module
      */
     export interface IWebXRTeleportationOptions {
@@ -49328,9 +49563,9 @@ declare module BABYLON {
         /**
          * Select a specific entity in the scene explorer and highlight a specific block in that entity property grid
          * @param entity defines the entity to select
-         * @param lineContainerTitle defines the specific block to highlight
+         * @param lineContainerTitles defines the specific blocks to highlight (could be a string or an array of strings)
          */
-        select(entity: any, lineContainerTitle?: string): void;
+        select(entity: any, lineContainerTitles?: string | string[]): void;
         /** Get the inspector from bundle or global */
         private _getGlobalInspector;
         /**
@@ -49781,11 +50016,13 @@ declare module BABYLON {
         private _gamepadDisconnectedEvent;
         private static _MAX_KEYCODES;
         private static _MAX_POINTER_INPUTS;
+        private constructor();
         /**
-         * Default Constructor
-         * @param engine - engine to pull input element from
+         * Creates a new DeviceInputSystem instance
+         * @param engine Engine to pull input element from
+         * @returns The new instance
          */
-        constructor(engine: Engine);
+        static Create(engine: Engine): DeviceInputSystem;
         /**
          * Checks for current device input value, given an id and input index
          * @param deviceName Id of connected device
@@ -60996,6 +61233,87 @@ declare module BABYLON {
 }
 declare module BABYLON {
     /**
+     * Block used to make gl_FragCoord available
+     */
+    export class FragCoordBlock extends NodeMaterialBlock {
+        /**
+         * Creates a new FragCoordBlock
+         * @param name defines the block name
+         */
+        constructor(name: string);
+        /**
+         * Gets the current class name
+         * @returns the class name
+         */
+        getClassName(): string;
+        /**
+         * Gets the xy component
+         */
+        get xy(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the xyz component
+         */
+        get xyz(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the xyzw component
+         */
+        get xyzw(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the x component
+         */
+        get x(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the y component
+         */
+        get y(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the z component
+         */
+        get z(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the w component
+         */
+        get output(): NodeMaterialConnectionPoint;
+        protected writeOutputs(state: NodeMaterialBuildState): string;
+        protected _buildBlock(state: NodeMaterialBuildState): this;
+    }
+}
+declare module BABYLON {
+    /**
+     * Block used to get the screen sizes
+     */
+    export class ScreenSizeBlock extends NodeMaterialBlock {
+        private _varName;
+        private _scene;
+        /**
+         * Creates a new ScreenSizeBlock
+         * @param name defines the block name
+         */
+        constructor(name: string);
+        /**
+         * Gets the current class name
+         * @returns the class name
+         */
+        getClassName(): string;
+        /**
+         * Gets the xy component
+         */
+        get xy(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the x component
+         */
+        get x(): NodeMaterialConnectionPoint;
+        /**
+         * Gets the y component
+         */
+        get y(): NodeMaterialConnectionPoint;
+        bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh): void;
+        protected writeOutputs(state: NodeMaterialBuildState, varName: string): string;
+        protected _buildBlock(state: NodeMaterialBuildState): this;
+    }
+}
+declare module BABYLON {
+    /**
      * Block used to add support for scene fog
      */
     export class FogBlock extends NodeMaterialBlock {
@@ -62533,6 +62851,8 @@ declare module BABYLON {
         _vReflectionMicrosurfaceInfosName: string;
         /** @hidden */
         _vReflectionInfosName: string;
+        /** @hidden */
+        _vReflectionFilteringInfoName: string;
         private _scene;
         /**
          * The three properties below are set by the main PBR block prior to calling methods of this class.
@@ -62669,10 +62989,6 @@ declare module BABYLON {
          */
         get roughness(): NodeMaterialConnectionPoint;
         /**
-         * Gets the texture input component
-         */
-        get texture(): NodeMaterialConnectionPoint;
-        /**
          * Gets the sheen object output component
          */
         get sheen(): NodeMaterialConnectionPoint;
@@ -62694,6 +63010,15 @@ declare module BABYLON {
      * Block used to implement the reflectivity module of the PBR material
      */
     export class ReflectivityBlock extends NodeMaterialBlock {
+        private _metallicReflectanceColor;
+        private _metallicF0Factor;
+        /** @hidden */
+        _vMetallicReflectanceFactorsName: string;
+        /**
+         * The property below is set by the main PBR block prior to calling methods of this class.
+        */
+        /** @hidden */
+        indexOfRefractionConnectionPoint: Nullable<NodeMaterialConnectionPoint>;
         /**
          * Specifies if the metallic texture contains the ambient occlusion information in its red channel.
          */
@@ -62741,12 +63066,14 @@ declare module BABYLON {
          * Gets the reflectivity object output component
          */
         get reflectivity(): NodeMaterialConnectionPoint;
+        bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh, subMesh?: SubMesh): void;
         /**
          * Gets the main code of the block (fragment side)
+         * @param state current state of the node material building
          * @param aoIntensityVarName name of the variable with the ambient occlusion intensity
          * @returns the shader code
          */
-        getCode(aoIntensityVarName: string): string;
+        getCode(state: NodeMaterialBuildState, aoIntensityVarName: string): string;
         prepareDefines(mesh: AbstractMesh, nodeMaterial: NodeMaterial, defines: NodeMaterialDefines): void;
         protected _buildBlock(state: NodeMaterialBuildState): this;
         protected _dumpPropertiesCode(): string;
@@ -62877,10 +63204,6 @@ declare module BABYLON {
          */
         get tintThickness(): NodeMaterialConnectionPoint;
         /**
-         * Gets the tint texture input component
-         */
-        get tintTexture(): NodeMaterialConnectionPoint;
-        /**
          * Gets the world tangent input component
          */
         get worldTangent(): NodeMaterialConnectionPoint;
@@ -63058,6 +63381,14 @@ declare module BABYLON {
          */
         enableSpecularAntiAliasing: boolean;
         /**
+         * Enables realtime filtering on the texture.
+         */
+        realTimeFiltering: boolean;
+        /**
+         * Quality switch for realtime filtering
+         */
+        realTimeFilteringQuality: number;
+        /**
          * Defines if the material uses energy conservation.
          */
         useEnergyConservation: boolean;
@@ -63127,10 +63458,6 @@ declare module BABYLON {
          */
         get baseColor(): NodeMaterialConnectionPoint;
         /**
-         * Gets the base texture input component
-         */
-        get baseTexture(): NodeMaterialConnectionPoint;
-        /**
          * Gets the opacity texture input component
          */
         get opacityTexture(): NodeMaterialConnectionPoint;
@@ -79075,7 +79402,7 @@ declare module BABYLON.GLTF2 {
          * @param assign A function called synchronously after parsing the glTF properties
          * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
          */
-        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
         /**
          * @hidden
          * Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
@@ -79774,6 +80101,53 @@ declare module BABYLON.GLTF2.Loader.Extensions {
 }
 declare module BABYLON.GLTF2.Loader.Extensions {
     /**
+     * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1681)
+     * !!! Experimental Extension Subject to Changes !!!
+     */
+    export class KHR_materials_variants implements IGLTFLoaderExtension {
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Defines whether this extension is enabled.
+         */
+        enabled: boolean;
+        private _loader;
+        /**
+         * The default variant name.
+         */
+        defaultVariant: string | undefined;
+        private _tagsToMap;
+        /** @hidden */
+        constructor(loader: GLTFLoader);
+        /** @hidden */
+        dispose(): void;
+        /**
+         * Return a list of available variants for this asset.
+         * @returns {string[]}
+         */
+        getVariants(): string[];
+        /**
+         * Select a variant by providing a list of variant tag names.
+         *
+         * @param {(string | string[])} variantName
+         */
+        selectVariant(variantName: string | string[]): void;
+        /**
+         * Select a variant by providing a single variant tag.
+         *
+         * @param {string} variantName
+         */
+        selectVariantTag(variantName: string): void;
+        /** @hidden */
+        onLoading(): void;
+        /** @hidden */
+        _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
+    }
+}
+declare module BABYLON.GLTF2.Loader.Extensions {
+    /**
      * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization)
      */
     export class KHR_mesh_quantization implements IGLTFLoaderExtension {

+ 48 - 48
dist/preview release/gui/babylon.gui.js

@@ -7,7 +7,7 @@
 		exports["babylonjs-gui"] = factory(require("babylonjs"));
 	else
 		root["BABYLON"] = root["BABYLON"] || {}, root["BABYLON"]["GUI"] = factory(root["BABYLON"]);
-})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Maths_math_vector__) {
+})((typeof self !== "undefined" ? self : typeof global !== "undefined" ? global : this), function(__WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_perfCounter__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
@@ -366,7 +366,7 @@ module.exports = g;
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AdvancedDynamicTextureInstrumentation", function() { return AdvancedDynamicTextureInstrumentation; });
-/* harmony import */ var babylonjs_Misc_perfCounter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/perfCounter */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_perfCounter__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/perfCounter */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_perfCounter__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_perfCounter__WEBPACK_IMPORTED_MODULE_0__);
 
 /**
@@ -509,7 +509,7 @@ var AdvancedDynamicTextureInstrumentation = /** @class */ (function () {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AdvancedDynamicTexture", function() { return AdvancedDynamicTexture; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _controls_container__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./controls/container */ "./2D/controls/container.ts");
 /* harmony import */ var _style__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./style */ "./2D/style.ts");
@@ -1481,7 +1481,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _textBlock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./textBlock */ "./2D/controls/textBlock.ts");
 /* harmony import */ var _image__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./image */ "./2D/controls/image.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5__);
 
 
@@ -1713,7 +1713,7 @@ babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_5__["_TypeStore"].RegisteredTy
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Checkbox", function() { return Checkbox; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _stackPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./stackPanel */ "./2D/controls/stackPanel.ts");
@@ -1896,7 +1896,7 @@ babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ColorPicker", function() { return ColorPicker; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _inputText__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./inputText */ "./2D/controls/inputText.ts");
@@ -3285,7 +3285,7 @@ babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Container", function() { return Container; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/logger */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/logger */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _measure__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../measure */ "./2D/measure.ts");
@@ -3700,7 +3700,7 @@ babylonjs_Misc_logger__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredTypes
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Control", function() { return Control; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
 /* harmony import */ var _measure__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../measure */ "./2D/measure.ts");
@@ -5626,7 +5626,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DisplayGrid", function() { return DisplayGrid; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -5859,7 +5859,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__);
 
 
@@ -5956,7 +5956,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__);
 
 
@@ -6414,7 +6414,7 @@ babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_4__["_TypeStore"].RegisteredTypes[
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Image", function() { return Image; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 
@@ -7341,7 +7341,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "InputPassword", function() { return InputPassword; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _inputText__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./inputText */ "./2D/controls/inputText.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -7380,7 +7380,7 @@ babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__["_TypeStore"].RegisteredTy
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "InputText", function() { return InputText; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
@@ -8393,7 +8393,7 @@ babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].RegisteredT
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Line", function() { return Line; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
@@ -8664,7 +8664,7 @@ babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].Registere
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MultiLine", function() { return MultiLine; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/abstractMesh */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/abstractMesh */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _multiLinePoint__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../multiLinePoint */ "./2D/multiLinePoint.ts");
@@ -8934,7 +8934,7 @@ babylonjs_Meshes_abstractMesh__WEBPACK_IMPORTED_MODULE_1__["_TypeStore"].Registe
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RadioButton", function() { return RadioButton; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
 /* harmony import */ var _stackPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./stackPanel */ "./2D/controls/stackPanel.ts");
@@ -9141,7 +9141,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Rectangle", function() { return Rectangle; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -9291,7 +9291,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _scrollViewerWindow__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./scrollViewerWindow */ "./2D/controls/scrollViewers/scrollViewerWindow.ts");
 /* harmony import */ var _sliders_scrollBar__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ../sliders/scrollBar */ "./2D/controls/sliders/scrollBar.ts");
 /* harmony import */ var _sliders_imageScrollBar__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ../sliders/imageScrollBar */ "./2D/controls/sliders/imageScrollBar.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_7___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_7__);
 
 
@@ -10914,7 +10914,7 @@ var SelectionPanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BaseSlider", function() { return BaseSlider; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../control */ "./2D/controls/control.ts");
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../valueAndUnit */ "./2D/valueAndUnit.ts");
@@ -11244,7 +11244,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _baseSlider__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./baseSlider */ "./2D/controls/sliders/baseSlider.ts");
 /* harmony import */ var _measure__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../../measure */ "./2D/measure.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_3__);
 
 
@@ -11837,7 +11837,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Slider", function() { return Slider; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _baseSlider__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./baseSlider */ "./2D/controls/sliders/baseSlider.ts");
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__);
 
 
@@ -12092,7 +12092,7 @@ babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_2__["_TypeStore"].RegisteredTy
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "StackPanel", function() { return StackPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container */ "./2D/controls/container.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
@@ -12360,7 +12360,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TextWrapping", function() { return TextWrapping; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TextBlock", function() { return TextBlock; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ../valueAndUnit */ "./2D/valueAndUnit.ts");
 /* harmony import */ var _control__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./control */ "./2D/controls/control.ts");
@@ -12823,7 +12823,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KeyPropertySet", function() { return KeyPropertySet; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VirtualKeyboard", function() { return VirtualKeyboard; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _stackPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./stackPanel */ "./2D/controls/stackPanel.ts");
 /* harmony import */ var _button__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./button */ "./2D/controls/button.ts");
@@ -13212,7 +13212,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Vector2WithInfo", function() { return Vector2WithInfo; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Matrix2D", function() { return Matrix2D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -13437,7 +13437,7 @@ var Matrix2D = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Measure", function() { return Measure; });
-/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0__);
 
 var tmpRect = [
@@ -13586,7 +13586,7 @@ var Measure = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MultiLinePoint", function() { return MultiLinePoint; });
-/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./valueAndUnit */ "./2D/valueAndUnit.ts");
 
@@ -13729,7 +13729,7 @@ var MultiLinePoint = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Style", function() { return Style; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _valueAndUnit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./valueAndUnit */ "./2D/valueAndUnit.ts");
 
@@ -14035,7 +14035,7 @@ var ValueAndUnit = /** @class */ (function () {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "XmlLoader", function() { return XmlLoader; });
-/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/typeStore */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_typeStore__WEBPACK_IMPORTED_MODULE_0__);
 
 /**
@@ -14354,7 +14354,7 @@ var XmlLoader = /** @class */ (function () {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AbstractButton3D", function() { return AbstractButton3D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control3D */ "./3D/controls/control3D.ts");
 
@@ -14397,7 +14397,7 @@ var AbstractButton3D = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Button3D", function() { return Button3D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _abstractButton3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./abstractButton3D */ "./3D/controls/abstractButton3D.ts");
 /* harmony import */ var _2D_advancedDynamicTexture__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../../2D/advancedDynamicTexture */ "./2D/advancedDynamicTexture.ts");
@@ -14578,7 +14578,7 @@ var Button3D = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Container3D", function() { return Container3D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/transformNode */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_transformNode__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _control3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./control3D */ "./3D/controls/control3D.ts");
 
@@ -14735,7 +14735,7 @@ var Container3D = /** @class */ (function (_super) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Control3D", function() { return Control3D; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _vector3WithInfo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ../vector3WithInfo */ "./3D/vector3WithInfo.ts");
 
@@ -15141,7 +15141,7 @@ var Control3D = /** @class */ (function () {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CylinderPanel", function() { return CylinderPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _volumeBasedPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./volumeBasedPanel */ "./3D/controls/volumeBasedPanel.ts");
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
@@ -15227,7 +15227,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "HolographicButton", function() { return HolographicButton; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
 /* harmony import */ var _button3D__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./button3D */ "./3D/controls/button3D.ts");
-/* harmony import */ var babylonjs_Materials_standardMaterial__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Materials/standardMaterial */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Materials_standardMaterial__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! babylonjs/Materials/standardMaterial */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Materials_standardMaterial__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_standardMaterial__WEBPACK_IMPORTED_MODULE_2__);
 /* harmony import */ var _materials_fluentMaterial__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ../materials/fluentMaterial */ "./3D/materials/fluentMaterial.ts");
 /* harmony import */ var _2D_controls_stackPanel__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ../../2D/controls/stackPanel */ "./2D/controls/stackPanel.ts");
@@ -15721,7 +15721,7 @@ var MeshButton3D = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "PlanePanel", function() { return PlanePanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
 /* harmony import */ var _volumeBasedPanel__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./volumeBasedPanel */ "./3D/controls/volumeBasedPanel.ts");
@@ -15776,7 +15776,7 @@ var PlanePanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ScatterPanel", function() { return ScatterPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _volumeBasedPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./volumeBasedPanel */ "./3D/controls/volumeBasedPanel.ts");
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
@@ -15903,7 +15903,7 @@ var ScatterPanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SpherePanel", function() { return SpherePanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _volumeBasedPanel__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./volumeBasedPanel */ "./3D/controls/volumeBasedPanel.ts");
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
@@ -15989,7 +15989,7 @@ var SpherePanel = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "StackPanel3D", function() { return StackPanel3D; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
 
@@ -16114,7 +16114,7 @@ var StackPanel3D = /** @class */ (function (_super) {
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VolumeBasedPanel", function() { return VolumeBasedPanel; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/tools */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_tools__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _container3D__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./container3D */ "./3D/controls/container3D.ts");
 
@@ -16305,7 +16305,7 @@ var VolumeBasedPanel = /** @class */ (function (_super) {
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GUI3DManager", function() { return GUI3DManager; });
-/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Misc/observable */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_observable__WEBPACK_IMPORTED_MODULE_0__);
 /* harmony import */ var _controls_container3D__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./controls/container3D */ "./3D/controls/container3D.ts");
 
@@ -16572,7 +16572,7 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FluentMaterialDefines", function() { return FluentMaterialDefines; });
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FluentMaterial", function() { return FluentMaterial; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Misc/decorators */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Misc_decorators__WEBPACK_IMPORTED_MODULE_1__);
 /* harmony import */ var _shaders_fluent_vertex__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./shaders/fluent.vertex */ "./3D/materials/shaders/fluent.vertex.ts");
 /* harmony import */ var _shaders_fluent_fragment__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./shaders/fluent.fragment */ "./3D/materials/shaders/fluent.fragment.ts");
@@ -16895,7 +16895,7 @@ __webpack_require__.r(__webpack_exports__);
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fluentPixelShader", function() { return fluentPixelShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 var name = 'fluentPixelShader';
@@ -16917,7 +16917,7 @@ var fluentPixelShader = { name: name, shader: shader };
 "use strict";
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fluentVertexShader", function() { return fluentVertexShader; });
-/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babylonjs/Materials/effect */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Materials_effect__WEBPACK_IMPORTED_MODULE_0__);
 
 var name = 'fluentVertexShader';
@@ -16940,7 +16940,7 @@ var fluentVertexShader = { name: name, shader: shader };
 __webpack_require__.r(__webpack_exports__);
 /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Vector3WithInfo", function() { return Vector3WithInfo; });
 /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! tslib */ "../../node_modules/tslib/tslib.es6.js");
-/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Maths/math.vector");
+/* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Maths/math.vector */ "babylonjs/Misc/perfCounter");
 /* harmony import */ var babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Maths_math_vector__WEBPACK_IMPORTED_MODULE_1__);
 
 
@@ -17242,14 +17242,14 @@ if (typeof globalObject !== "undefined") {
 
 /***/ }),
 
-/***/ "babylonjs/Maths/math.vector":
+/***/ "babylonjs/Misc/perfCounter":
 /*!****************************************************************************************************!*\
   !*** external {"root":"BABYLON","commonjs":"babylonjs","commonjs2":"babylonjs","amd":"babylonjs"} ***!
   \****************************************************************************************************/
 /*! no static exports found */
 /***/ (function(module, exports) {
 
-module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Maths_math_vector__;
+module.exports = __WEBPACK_EXTERNAL_MODULE_babylonjs_Misc_perfCounter__;
 
 /***/ })
 

文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/gui/babylon.gui.js.map


文件差異過大導致無法顯示
+ 5 - 5
dist/preview release/inspector/babylon.inspector.bundle.js


文件差異過大導致無法顯示
+ 973 - 390
dist/preview release/inspector/babylon.inspector.bundle.max.js


文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/inspector/babylon.inspector.bundle.max.js.map


+ 138 - 28
dist/preview release/inspector/babylon.inspector.d.ts

@@ -51,7 +51,7 @@ declare module INSPECTOR {
             [key: string]: any;
         };
         blockMutationUpdates: boolean;
-        selectedLineContainerTitle: string;
+        selectedLineContainerTitles: Array<string>;
         recorder: ReplayRecorder;
         private _onlyUseEulers;
         get onlyUseEulers(): boolean;
@@ -519,9 +519,11 @@ declare module INSPECTOR {
         type: string;
         index: string;
         selected: boolean;
+        selectControlPoint: (id: string) => void;
     }
     export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps> {
         constructor(props: IAnchorSvgPointProps);
+        select(): void;
         render(): JSX.Element;
     }
 }
@@ -531,15 +533,24 @@ declare module INSPECTOR {
         rightControlPoint: BABYLON.Vector2 | null;
         leftControlPoint: BABYLON.Vector2 | null;
         id: string;
+        selected: boolean;
+        isLeftActive: boolean;
+        isRightActive: boolean;
     }
     interface IKeyframeSvgPointProps {
         keyframePoint: BABYLON.Vector2;
         leftControlPoint: BABYLON.Vector2 | null;
         rightControlPoint: BABYLON.Vector2 | null;
         id: string;
+        selected: boolean;
+        selectKeyframe: (id: string) => void;
+        selectedControlPoint: (type: string, id: string) => void;
+        isLeftActive: boolean;
+        isRightActive: boolean;
     }
     export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps> {
         constructor(props: IKeyframeSvgPointProps);
+        select(): void;
         render(): JSX.Element;
     }
 }
@@ -549,6 +560,8 @@ declare module INSPECTOR {
         updatePosition: (updatedKeyframe: IKeyframeSvgPoint, index: number) => void;
         scale: number;
         viewBoxScale: number;
+        selectKeyframe: (id: string) => void;
+        selectedControlPoint: (type: string, id: string) => void;
     }
     export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps> {
         private _active;
@@ -578,22 +591,37 @@ declare module INSPECTOR {
 }
 declare module INSPECTOR {
     interface ITimelineProps {
-        keyframes: BABYLON.IAnimationKey[];
-        selected: BABYLON.IAnimationKey;
+        keyframes: BABYLON.IAnimationKey[] | null;
+        selected: BABYLON.IAnimationKey | null;
         currentFrame: number;
         onCurrentFrameChange: (frame: number) => void;
+        dragKeyframe: (frame: number, index: number) => void;
+        playPause: (direction: number) => void;
+        isPlaying: boolean;
     }
     export class Timeline extends React.Component<ITimelineProps, {
         selected: BABYLON.IAnimationKey;
+        activeKeyframe: number | null;
     }> {
         readonly _frames: object[];
         private _scrollable;
+        private _direction;
         constructor(props: ITimelineProps);
+        playBackwards(event: React.MouseEvent<HTMLDivElement>): void;
+        play(event: React.MouseEvent<HTMLDivElement>): void;
+        pause(event: React.MouseEvent<HTMLDivElement>): void;
         handleInputChange(event: React.ChangeEvent<HTMLInputElement>): void;
         nextFrame(event: React.MouseEvent<HTMLDivElement>): void;
         previousFrame(event: React.MouseEvent<HTMLDivElement>): void;
         nextKeyframe(event: React.MouseEvent<HTMLDivElement>): void;
         previousKeyframe(event: React.MouseEvent<HTMLDivElement>): void;
+        dragStart(e: React.TouchEvent<SVGSVGElement>): void;
+        dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+        drag(e: React.TouchEvent<SVGSVGElement>): void;
+        drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+        isFrameBeingUsed(frame: number, direction: number): number | false;
+        dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
+        dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
         render(): JSX.Element;
     }
 }
@@ -625,6 +653,10 @@ declare module INSPECTOR {
         handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
         handleFrameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
         flatTangent: () => void;
+        brokeTangents: () => void;
+        setLerpMode: () => void;
+        brokenMode: boolean;
+        lerpMode: boolean;
         currentValue: number;
         currentFrame: number;
     }
@@ -636,78 +668,108 @@ declare module INSPECTOR {
 declare module INSPECTOR {
     interface IAnimationCurveEditorComponentProps {
         close: (event: any) => void;
-        playOrPause: () => void;
+        playOrPause?: () => void;
         title: string;
-        animations: BABYLON.Animation[];
-        entityName: string;
         scene: BABYLON.Scene;
-        entity: BABYLON.IAnimatable;
+        entity: BABYLON.IAnimatable | BABYLON.TargetedAnimation;
     }
     interface ICanvasAxis {
         value: number;
         label: number;
     }
     export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, {
-        animations: BABYLON.Animation[];
         animationName: string;
         animationType: string;
         animationTargetProperty: string;
         isOpen: boolean;
-        selected: BABYLON.Animation;
+        selected: BABYLON.Animation | null;
         currentPathData: string | undefined;
         svgKeyframes: IKeyframeSvgPoint[] | undefined;
         currentFrame: number;
         currentValue: number;
         frameAxisLength: ICanvasAxis[];
         valueAxisLength: ICanvasAxis[];
-        flatTangent: boolean;
+        isFlatTangentMode: boolean;
+        isTangentMode: boolean;
+        isBrokenMode: boolean;
+        lerpMode: boolean;
         scale: number;
         playheadOffset: number;
         notification: string;
         currentPoint: SVGPoint | undefined;
         lastFrame: number;
         playheadPos: number;
+        isPlaying: boolean;
     }> {
         private _heightScale;
+        readonly _entityName: string;
         readonly _canvasLength: number;
-        private _newAnimations;
         private _svgKeyframes;
         private _frames;
         private _isPlaying;
         private _graphCanvas;
         private _selectedCurve;
         private _svgCanvas;
+        private _isTargetedAnimation;
         constructor(props: IAnimationCurveEditorComponentProps);
         componentDidMount(): void;
-        resetPlayheadOffset(): void;
+        /**
+        * Notifications
+        * To add notification we set the state and clear to make the notification bar hide.
+        */
+        clearNotification(): void;
+        /**
+        * Zoom and Scroll
+        * This section handles zoom and scroll
+        * of the graph area.
+        */
+        zoom(e: React.WheelEvent<HTMLDivElement>): void;
         setAxesLength(): void;
         getValueLabel(i: number): number;
+        resetPlayheadOffset(): void;
+        /**
+        * Add New BABYLON.Animation
+        * This section handles events from AnimationCreation.
+        */
         handleNameChange(event: React.ChangeEvent<HTMLInputElement>): void;
-        handleValueChange(event: React.ChangeEvent<HTMLInputElement>): void;
-        handleFrameChange(event: React.ChangeEvent<HTMLInputElement>): void;
         handleTypeChange(event: React.ChangeEvent<HTMLSelectElement>): void;
         handlePropertyChange(event: React.ChangeEvent<HTMLInputElement>): void;
+        setListItem(animation: BABYLON.Animation, i: number): JSX.Element | null;
+        getAnimationTypeofChange(selected: string): number;
+        deleteAnimation(): void;
         addAnimation(): void;
-        clearNotification(): void;
+        /**
+        * Keyframe Manipulation
+        * This section handles events from SvgDraggableArea.
+        */
+        selectKeyframe(id: string): void;
+        selectedControlPoint(type: string, id: string): void;
+        renderPoints(updatedSvgKeyFrame: IKeyframeSvgPoint, index: number): void;
+        /**
+        * Actions
+        * This section handles events from GraphActionsBar.
+        */
+        handleFrameChange(event: React.ChangeEvent<HTMLInputElement>): void;
+        handleValueChange(event: React.ChangeEvent<HTMLInputElement>): void;
+        setFlatTangent(): void;
+        setTangentMode(): void;
+        setBrokenMode(): void;
+        setLerpMode(): void;
         addKeyframeClick(): void;
         removeKeyframeClick(): void;
         addKeyFrame(event: React.MouseEvent<SVGSVGElement>): void;
         updateKeyframe(keyframe: BABYLON.Vector2, index: number): void;
+        /**
+        * Curve Rendering Functions
+        * This section handles how to render curves.
+        */
         getAnimationProperties(animation: BABYLON.Animation): {
             easingType: string | undefined;
             easingMode: number | undefined;
         };
-        getPathData(animation: BABYLON.Animation): string;
-        drawAllFrames(initialKey: BABYLON.IAnimationKey, endKey: BABYLON.IAnimationKey, easingFunction: BABYLON.EasingFunction): void;
-        curvePathFlat(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, dataType: number): string;
-        curvePathWithTangents(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, type: number): string;
-        curvePath(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, easingFunction: BABYLON.EasingFunction): string;
-        renderPoints(updatedSvgKeyFrame: IKeyframeSvgPoint, index: number): void;
         linearInterpolation(keyframes: BABYLON.IAnimationKey[], data: string, middle: number): string;
         setKeyframePointLinear(point: BABYLON.Vector2, index: number): void;
-        setKeyframePoint(controlPoints: BABYLON.Vector2[], index: number, keyframesCount: number): void;
-        isAnimationPlaying(): void;
-        selectAnimation(animation: BABYLON.Animation): void;
+        getPathData(animation: BABYLON.Animation): string;
         getAnimationData(animation: BABYLON.Animation): {
             loopMode: number | undefined;
             name: string;
@@ -717,13 +779,29 @@ declare module INSPECTOR {
             framesPerSecond: number;
             highestFrame: number;
             serialized: any;
-            usesTangents: BABYLON.IAnimationKey | undefined;
+            usesTangents: boolean;
         };
-        getAnimationTypeofChange(selected: string): number;
+        drawAllFrames(initialKey: BABYLON.IAnimationKey, endKey: BABYLON.IAnimationKey, easingFunction: BABYLON.EasingFunction): void;
+        curvePathFlat(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, dataType: number): string;
+        curvePathWithTangents(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, type: number): string;
+        curvePath(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, easingFunction: BABYLON.EasingFunction): string;
+        setKeyframePoint(controlPoints: BABYLON.Vector2[], index: number, keyframesCount: number): void;
         interpolateControlPoints(p0: BABYLON.Vector2, p1: BABYLON.Vector2, u: number, p2: BABYLON.Vector2, v: number, p3: BABYLON.Vector2): BABYLON.Vector2[] | undefined;
+        /**
+        * Core functions
+        * This section handles main Curve Editor Functions.
+        */
+        selectAnimation(animation: BABYLON.Animation): void;
+        isAnimationPlaying(): boolean;
+        playPause(direction: number): void;
+        playStopAnimation(): boolean;
+        analizeAnimation(animation: BABYLON.Animation | null): boolean;
+        /**
+        * Timeline
+        * This section controls the timeline.
+        */
         changeCurrentFrame(frame: number): void;
-        setFlatTangent(): void;
-        zoom(e: React.WheelEvent<HTMLDivElement>): void;
+        updateFrameInKeyFrame(frame: number, index: number): void;
         render(): JSX.Element;
     }
 }
@@ -1957,6 +2035,26 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    interface ITargetedAnimationGridComponentProps {
+        globalState: GlobalState;
+        targetedAnimation: BABYLON.TargetedAnimation;
+        scene: BABYLON.Scene;
+        lockObject: LockObject;
+        onSelectionChangedObservable?: BABYLON.Observable<any>;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class TargetedAnimationGridComponent extends React.Component<ITargetedAnimationGridComponentProps> {
+        private _isCurveEditorOpen;
+        private _animationGroup;
+        constructor(props: ITargetedAnimationGridComponentProps);
+        onOpenAnimationCurveEditor(): void;
+        onCloseAnimationCurveEditor(window: Window | null): void;
+        playOrPause(): void;
+        deleteAnimation(): void;
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     export class PropertyGridTabComponent extends PaneComponent {
         private _timerIntervalId;
         private _lockObject;
@@ -2332,6 +2430,17 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    interface ITargetedAnimationItemComponentProps {
+        targetedAnimation: BABYLON.TargetedAnimation;
+        extensibilityGroups?: BABYLON.IExplorerExtensibilityGroup[];
+        onClick: () => void;
+    }
+    export class TargetedAnimationItemComponent extends React.Component<ITargetedAnimationItemComponentProps> {
+        constructor(props: ITargetedAnimationItemComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     interface ITreeItemSpecializedComponentProps {
         label: string;
         entity?: any;
@@ -2534,6 +2643,7 @@ declare module INSPECTOR {
         static OnPropertyChangedObservable: BABYLON.Observable<PropertyChangedEvent>;
         private static _GlobalState;
         static MarkLineContainerTitleForHighlighting(title: string): void;
+        static MarkMultipleLineContainerTitlesForHighlighting(titles: string[]): void;
         private static _CopyStyles;
         private static _CreateSceneExplorer;
         private static _CreateActionTabs;

+ 287 - 56
dist/preview release/inspector/babylon.inspector.module.d.ts

@@ -64,7 +64,7 @@ declare module "babylonjs-inspector/components/globalState" {
             [key: string]: any;
         };
         blockMutationUpdates: boolean;
-        selectedLineContainerTitle: string;
+        selectedLineContainerTitles: Array<string>;
         recorder: ReplayRecorder;
         private _onlyUseEulers;
         get onlyUseEulers(): boolean;
@@ -590,9 +590,11 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/ani
         type: string;
         index: string;
         selected: boolean;
+        selectControlPoint: (id: string) => void;
     }
     export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps> {
         constructor(props: IAnchorSvgPointProps);
+        select(): void;
         render(): JSX.Element;
     }
 }
@@ -604,15 +606,24 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/ani
         rightControlPoint: Vector2 | null;
         leftControlPoint: Vector2 | null;
         id: string;
+        selected: boolean;
+        isLeftActive: boolean;
+        isRightActive: boolean;
     }
     interface IKeyframeSvgPointProps {
         keyframePoint: Vector2;
         leftControlPoint: Vector2 | null;
         rightControlPoint: Vector2 | null;
         id: string;
+        selected: boolean;
+        selectKeyframe: (id: string) => void;
+        selectedControlPoint: (type: string, id: string) => void;
+        isLeftActive: boolean;
+        isRightActive: boolean;
     }
     export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps> {
         constructor(props: IKeyframeSvgPointProps);
+        select(): void;
         render(): JSX.Element;
     }
 }
@@ -625,6 +636,8 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/ani
         updatePosition: (updatedKeyframe: IKeyframeSvgPoint, index: number) => void;
         scale: number;
         viewBoxScale: number;
+        selectKeyframe: (id: string) => void;
+        selectedControlPoint: (type: string, id: string) => void;
     }
     export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps> {
         private _active;
@@ -656,22 +669,37 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/ani
     import * as React from "react";
     import { IAnimationKey } from 'babylonjs/Animations/animationKey';
     interface ITimelineProps {
-        keyframes: IAnimationKey[];
-        selected: IAnimationKey;
+        keyframes: IAnimationKey[] | null;
+        selected: IAnimationKey | null;
         currentFrame: number;
         onCurrentFrameChange: (frame: number) => void;
+        dragKeyframe: (frame: number, index: number) => void;
+        playPause: (direction: number) => void;
+        isPlaying: boolean;
     }
     export class Timeline extends React.Component<ITimelineProps, {
         selected: IAnimationKey;
+        activeKeyframe: number | null;
     }> {
         readonly _frames: object[];
         private _scrollable;
+        private _direction;
         constructor(props: ITimelineProps);
+        playBackwards(event: React.MouseEvent<HTMLDivElement>): void;
+        play(event: React.MouseEvent<HTMLDivElement>): void;
+        pause(event: React.MouseEvent<HTMLDivElement>): void;
         handleInputChange(event: React.ChangeEvent<HTMLInputElement>): void;
         nextFrame(event: React.MouseEvent<HTMLDivElement>): void;
         previousFrame(event: React.MouseEvent<HTMLDivElement>): void;
         nextKeyframe(event: React.MouseEvent<HTMLDivElement>): void;
         previousKeyframe(event: React.MouseEvent<HTMLDivElement>): void;
+        dragStart(e: React.TouchEvent<SVGSVGElement>): void;
+        dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+        drag(e: React.TouchEvent<SVGSVGElement>): void;
+        drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+        isFrameBeingUsed(frame: number, direction: number): number | false;
+        dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
+        dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
         render(): JSX.Element;
     }
 }
@@ -706,6 +734,10 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/ani
         handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
         handleFrameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
         flatTangent: () => void;
+        brokeTangents: () => void;
+        setLerpMode: () => void;
+        brokenMode: boolean;
+        lerpMode: boolean;
         currentValue: number;
         currentFrame: number;
     }
@@ -723,80 +755,111 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/ani
     import { IKeyframeSvgPoint } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint";
     import { Scene } from "babylonjs/scene";
     import { IAnimatable } from 'babylonjs/Animations/animatable.interface';
+    import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
     interface IAnimationCurveEditorComponentProps {
         close: (event: any) => void;
-        playOrPause: () => void;
+        playOrPause?: () => void;
         title: string;
-        animations: Animation[];
-        entityName: string;
         scene: Scene;
-        entity: IAnimatable;
+        entity: IAnimatable | TargetedAnimation;
     }
     interface ICanvasAxis {
         value: number;
         label: number;
     }
     export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, {
-        animations: Animation[];
         animationName: string;
         animationType: string;
         animationTargetProperty: string;
         isOpen: boolean;
-        selected: Animation;
+        selected: Animation | null;
         currentPathData: string | undefined;
         svgKeyframes: IKeyframeSvgPoint[] | undefined;
         currentFrame: number;
         currentValue: number;
         frameAxisLength: ICanvasAxis[];
         valueAxisLength: ICanvasAxis[];
-        flatTangent: boolean;
+        isFlatTangentMode: boolean;
+        isTangentMode: boolean;
+        isBrokenMode: boolean;
+        lerpMode: boolean;
         scale: number;
         playheadOffset: number;
         notification: string;
         currentPoint: SVGPoint | undefined;
         lastFrame: number;
         playheadPos: number;
+        isPlaying: boolean;
     }> {
         private _heightScale;
+        readonly _entityName: string;
         readonly _canvasLength: number;
-        private _newAnimations;
         private _svgKeyframes;
         private _frames;
         private _isPlaying;
         private _graphCanvas;
         private _selectedCurve;
         private _svgCanvas;
+        private _isTargetedAnimation;
         constructor(props: IAnimationCurveEditorComponentProps);
         componentDidMount(): void;
-        resetPlayheadOffset(): void;
+        /**
+        * Notifications
+        * To add notification we set the state and clear to make the notification bar hide.
+        */
+        clearNotification(): void;
+        /**
+        * Zoom and Scroll
+        * This section handles zoom and scroll
+        * of the graph area.
+        */
+        zoom(e: React.WheelEvent<HTMLDivElement>): void;
         setAxesLength(): void;
         getValueLabel(i: number): number;
+        resetPlayheadOffset(): void;
+        /**
+        * Add New Animation
+        * This section handles events from AnimationCreation.
+        */
         handleNameChange(event: React.ChangeEvent<HTMLInputElement>): void;
-        handleValueChange(event: React.ChangeEvent<HTMLInputElement>): void;
-        handleFrameChange(event: React.ChangeEvent<HTMLInputElement>): void;
         handleTypeChange(event: React.ChangeEvent<HTMLSelectElement>): void;
         handlePropertyChange(event: React.ChangeEvent<HTMLInputElement>): void;
+        setListItem(animation: Animation, i: number): JSX.Element | null;
+        getAnimationTypeofChange(selected: string): number;
+        deleteAnimation(): void;
         addAnimation(): void;
-        clearNotification(): void;
+        /**
+        * Keyframe Manipulation
+        * This section handles events from SvgDraggableArea.
+        */
+        selectKeyframe(id: string): void;
+        selectedControlPoint(type: string, id: string): void;
+        renderPoints(updatedSvgKeyFrame: IKeyframeSvgPoint, index: number): void;
+        /**
+        * Actions
+        * This section handles events from GraphActionsBar.
+        */
+        handleFrameChange(event: React.ChangeEvent<HTMLInputElement>): void;
+        handleValueChange(event: React.ChangeEvent<HTMLInputElement>): void;
+        setFlatTangent(): void;
+        setTangentMode(): void;
+        setBrokenMode(): void;
+        setLerpMode(): void;
         addKeyframeClick(): void;
         removeKeyframeClick(): void;
         addKeyFrame(event: React.MouseEvent<SVGSVGElement>): void;
         updateKeyframe(keyframe: Vector2, index: number): void;
+        /**
+        * Curve Rendering Functions
+        * This section handles how to render curves.
+        */
         getAnimationProperties(animation: Animation): {
             easingType: string | undefined;
             easingMode: number | undefined;
         };
-        getPathData(animation: Animation): string;
-        drawAllFrames(initialKey: IAnimationKey, endKey: IAnimationKey, easingFunction: EasingFunction): void;
-        curvePathFlat(keyframes: IAnimationKey[], data: string, middle: number, dataType: number): string;
-        curvePathWithTangents(keyframes: IAnimationKey[], data: string, middle: number, type: number): string;
-        curvePath(keyframes: IAnimationKey[], data: string, middle: number, easingFunction: EasingFunction): string;
-        renderPoints(updatedSvgKeyFrame: IKeyframeSvgPoint, index: number): void;
         linearInterpolation(keyframes: IAnimationKey[], data: string, middle: number): string;
         setKeyframePointLinear(point: Vector2, index: number): void;
-        setKeyframePoint(controlPoints: Vector2[], index: number, keyframesCount: number): void;
-        isAnimationPlaying(): void;
-        selectAnimation(animation: Animation): void;
+        getPathData(animation: Animation): string;
         getAnimationData(animation: Animation): {
             loopMode: number | undefined;
             name: string;
@@ -806,13 +869,29 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/ani
             framesPerSecond: number;
             highestFrame: number;
             serialized: any;
-            usesTangents: IAnimationKey | undefined;
+            usesTangents: boolean;
         };
-        getAnimationTypeofChange(selected: string): number;
+        drawAllFrames(initialKey: IAnimationKey, endKey: IAnimationKey, easingFunction: EasingFunction): void;
+        curvePathFlat(keyframes: IAnimationKey[], data: string, middle: number, dataType: number): string;
+        curvePathWithTangents(keyframes: IAnimationKey[], data: string, middle: number, type: number): string;
+        curvePath(keyframes: IAnimationKey[], data: string, middle: number, easingFunction: EasingFunction): string;
+        setKeyframePoint(controlPoints: Vector2[], index: number, keyframesCount: number): void;
         interpolateControlPoints(p0: Vector2, p1: Vector2, u: number, p2: Vector2, v: number, p3: Vector2): Vector2[] | undefined;
+        /**
+        * Core functions
+        * This section handles main Curve Editor Functions.
+        */
+        selectAnimation(animation: Animation): void;
+        isAnimationPlaying(): boolean;
+        playPause(direction: number): void;
+        playStopAnimation(): boolean;
+        analizeAnimation(animation: Animation | null): boolean;
+        /**
+        * Timeline
+        * This section controls the timeline.
+        */
         changeCurrentFrame(frame: number): void;
-        setFlatTangent(): void;
-        zoom(e: React.WheelEvent<HTMLDivElement>): void;
+        updateFrameInKeyFrame(frame: number, index: number): void;
         render(): JSX.Element;
     }
 }
@@ -2471,6 +2550,33 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/spr
         render(): JSX.Element;
     }
 }
+declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/animations/targetedAnimationPropertyGridComponent" {
+    import * as React from "react";
+    import { Observable } from "babylonjs/Misc/observable";
+    import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
+    import { Scene } from "babylonjs/scene";
+    import { PropertyChangedEvent } from "babylonjs-inspector/components/propertyChangedEvent";
+    import { LockObject } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/lockObject";
+    import { GlobalState } from "babylonjs-inspector/components/globalState";
+    interface ITargetedAnimationGridComponentProps {
+        globalState: GlobalState;
+        targetedAnimation: TargetedAnimation;
+        scene: Scene;
+        lockObject: LockObject;
+        onSelectionChangedObservable?: Observable<any>;
+        onPropertyChangedObservable?: Observable<PropertyChangedEvent>;
+    }
+    export class TargetedAnimationGridComponent extends React.Component<ITargetedAnimationGridComponentProps> {
+        private _isCurveEditorOpen;
+        private _animationGroup;
+        constructor(props: ITargetedAnimationGridComponentProps);
+        onOpenAnimationCurveEditor(): void;
+        onCloseAnimationCurveEditor(window: Window | null): void;
+        playOrPause(): void;
+        deleteAnimation(): void;
+        render(): JSX.Element;
+    }
+}
 declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGridTabComponent" {
     import { PaneComponent, IPaneComponentProps } from "babylonjs-inspector/components/actionTabs/paneComponent";
     export class PropertyGridTabComponent extends PaneComponent {
@@ -2915,6 +3021,20 @@ declare module "babylonjs-inspector/components/sceneExplorer/entities/spriteTree
         render(): JSX.Element;
     }
 }
+declare module "babylonjs-inspector/components/sceneExplorer/entities/targetedAnimationTreeItemComponent" {
+    import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
+    import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
+    import * as React from "react";
+    interface ITargetedAnimationItemComponentProps {
+        targetedAnimation: TargetedAnimation;
+        extensibilityGroups?: IExplorerExtensibilityGroup[];
+        onClick: () => void;
+    }
+    export class TargetedAnimationItemComponent extends React.Component<ITargetedAnimationItemComponentProps> {
+        constructor(props: ITargetedAnimationItemComponentProps);
+        render(): JSX.Element;
+    }
+}
 declare module "babylonjs-inspector/components/sceneExplorer/treeItemSpecializedComponent" {
     import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
     import * as React from "react";
@@ -3147,6 +3267,7 @@ declare module "babylonjs-inspector/inspector" {
         static OnPropertyChangedObservable: Observable<PropertyChangedEvent>;
         private static _GlobalState;
         static MarkLineContainerTitleForHighlighting(title: string): void;
+        static MarkMultipleLineContainerTitlesForHighlighting(titles: string[]): void;
         private static _CopyStyles;
         private static _CreateSceneExplorer;
         private static _CreateActionTabs;
@@ -3225,7 +3346,7 @@ declare module INSPECTOR {
             [key: string]: any;
         };
         blockMutationUpdates: boolean;
-        selectedLineContainerTitle: string;
+        selectedLineContainerTitles: Array<string>;
         recorder: ReplayRecorder;
         private _onlyUseEulers;
         get onlyUseEulers(): boolean;
@@ -3693,9 +3814,11 @@ declare module INSPECTOR {
         type: string;
         index: string;
         selected: boolean;
+        selectControlPoint: (id: string) => void;
     }
     export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps> {
         constructor(props: IAnchorSvgPointProps);
+        select(): void;
         render(): JSX.Element;
     }
 }
@@ -3705,15 +3828,24 @@ declare module INSPECTOR {
         rightControlPoint: BABYLON.Vector2 | null;
         leftControlPoint: BABYLON.Vector2 | null;
         id: string;
+        selected: boolean;
+        isLeftActive: boolean;
+        isRightActive: boolean;
     }
     interface IKeyframeSvgPointProps {
         keyframePoint: BABYLON.Vector2;
         leftControlPoint: BABYLON.Vector2 | null;
         rightControlPoint: BABYLON.Vector2 | null;
         id: string;
+        selected: boolean;
+        selectKeyframe: (id: string) => void;
+        selectedControlPoint: (type: string, id: string) => void;
+        isLeftActive: boolean;
+        isRightActive: boolean;
     }
     export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps> {
         constructor(props: IKeyframeSvgPointProps);
+        select(): void;
         render(): JSX.Element;
     }
 }
@@ -3723,6 +3855,8 @@ declare module INSPECTOR {
         updatePosition: (updatedKeyframe: IKeyframeSvgPoint, index: number) => void;
         scale: number;
         viewBoxScale: number;
+        selectKeyframe: (id: string) => void;
+        selectedControlPoint: (type: string, id: string) => void;
     }
     export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps> {
         private _active;
@@ -3752,22 +3886,37 @@ declare module INSPECTOR {
 }
 declare module INSPECTOR {
     interface ITimelineProps {
-        keyframes: BABYLON.IAnimationKey[];
-        selected: BABYLON.IAnimationKey;
+        keyframes: BABYLON.IAnimationKey[] | null;
+        selected: BABYLON.IAnimationKey | null;
         currentFrame: number;
         onCurrentFrameChange: (frame: number) => void;
+        dragKeyframe: (frame: number, index: number) => void;
+        playPause: (direction: number) => void;
+        isPlaying: boolean;
     }
     export class Timeline extends React.Component<ITimelineProps, {
         selected: BABYLON.IAnimationKey;
+        activeKeyframe: number | null;
     }> {
         readonly _frames: object[];
         private _scrollable;
+        private _direction;
         constructor(props: ITimelineProps);
+        playBackwards(event: React.MouseEvent<HTMLDivElement>): void;
+        play(event: React.MouseEvent<HTMLDivElement>): void;
+        pause(event: React.MouseEvent<HTMLDivElement>): void;
         handleInputChange(event: React.ChangeEvent<HTMLInputElement>): void;
         nextFrame(event: React.MouseEvent<HTMLDivElement>): void;
         previousFrame(event: React.MouseEvent<HTMLDivElement>): void;
         nextKeyframe(event: React.MouseEvent<HTMLDivElement>): void;
         previousKeyframe(event: React.MouseEvent<HTMLDivElement>): void;
+        dragStart(e: React.TouchEvent<SVGSVGElement>): void;
+        dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+        drag(e: React.TouchEvent<SVGSVGElement>): void;
+        drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+        isFrameBeingUsed(frame: number, direction: number): number | false;
+        dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
+        dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
         render(): JSX.Element;
     }
 }
@@ -3799,6 +3948,10 @@ declare module INSPECTOR {
         handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
         handleFrameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
         flatTangent: () => void;
+        brokeTangents: () => void;
+        setLerpMode: () => void;
+        brokenMode: boolean;
+        lerpMode: boolean;
         currentValue: number;
         currentFrame: number;
     }
@@ -3810,78 +3963,108 @@ declare module INSPECTOR {
 declare module INSPECTOR {
     interface IAnimationCurveEditorComponentProps {
         close: (event: any) => void;
-        playOrPause: () => void;
+        playOrPause?: () => void;
         title: string;
-        animations: BABYLON.Animation[];
-        entityName: string;
         scene: BABYLON.Scene;
-        entity: BABYLON.IAnimatable;
+        entity: BABYLON.IAnimatable | BABYLON.TargetedAnimation;
     }
     interface ICanvasAxis {
         value: number;
         label: number;
     }
     export class AnimationCurveEditorComponent extends React.Component<IAnimationCurveEditorComponentProps, {
-        animations: BABYLON.Animation[];
         animationName: string;
         animationType: string;
         animationTargetProperty: string;
         isOpen: boolean;
-        selected: BABYLON.Animation;
+        selected: BABYLON.Animation | null;
         currentPathData: string | undefined;
         svgKeyframes: IKeyframeSvgPoint[] | undefined;
         currentFrame: number;
         currentValue: number;
         frameAxisLength: ICanvasAxis[];
         valueAxisLength: ICanvasAxis[];
-        flatTangent: boolean;
+        isFlatTangentMode: boolean;
+        isTangentMode: boolean;
+        isBrokenMode: boolean;
+        lerpMode: boolean;
         scale: number;
         playheadOffset: number;
         notification: string;
         currentPoint: SVGPoint | undefined;
         lastFrame: number;
         playheadPos: number;
+        isPlaying: boolean;
     }> {
         private _heightScale;
+        readonly _entityName: string;
         readonly _canvasLength: number;
-        private _newAnimations;
         private _svgKeyframes;
         private _frames;
         private _isPlaying;
         private _graphCanvas;
         private _selectedCurve;
         private _svgCanvas;
+        private _isTargetedAnimation;
         constructor(props: IAnimationCurveEditorComponentProps);
         componentDidMount(): void;
-        resetPlayheadOffset(): void;
+        /**
+        * Notifications
+        * To add notification we set the state and clear to make the notification bar hide.
+        */
+        clearNotification(): void;
+        /**
+        * Zoom and Scroll
+        * This section handles zoom and scroll
+        * of the graph area.
+        */
+        zoom(e: React.WheelEvent<HTMLDivElement>): void;
         setAxesLength(): void;
         getValueLabel(i: number): number;
+        resetPlayheadOffset(): void;
+        /**
+        * Add New BABYLON.Animation
+        * This section handles events from AnimationCreation.
+        */
         handleNameChange(event: React.ChangeEvent<HTMLInputElement>): void;
-        handleValueChange(event: React.ChangeEvent<HTMLInputElement>): void;
-        handleFrameChange(event: React.ChangeEvent<HTMLInputElement>): void;
         handleTypeChange(event: React.ChangeEvent<HTMLSelectElement>): void;
         handlePropertyChange(event: React.ChangeEvent<HTMLInputElement>): void;
+        setListItem(animation: BABYLON.Animation, i: number): JSX.Element | null;
+        getAnimationTypeofChange(selected: string): number;
+        deleteAnimation(): void;
         addAnimation(): void;
-        clearNotification(): void;
+        /**
+        * Keyframe Manipulation
+        * This section handles events from SvgDraggableArea.
+        */
+        selectKeyframe(id: string): void;
+        selectedControlPoint(type: string, id: string): void;
+        renderPoints(updatedSvgKeyFrame: IKeyframeSvgPoint, index: number): void;
+        /**
+        * Actions
+        * This section handles events from GraphActionsBar.
+        */
+        handleFrameChange(event: React.ChangeEvent<HTMLInputElement>): void;
+        handleValueChange(event: React.ChangeEvent<HTMLInputElement>): void;
+        setFlatTangent(): void;
+        setTangentMode(): void;
+        setBrokenMode(): void;
+        setLerpMode(): void;
         addKeyframeClick(): void;
         removeKeyframeClick(): void;
         addKeyFrame(event: React.MouseEvent<SVGSVGElement>): void;
         updateKeyframe(keyframe: BABYLON.Vector2, index: number): void;
+        /**
+        * Curve Rendering Functions
+        * This section handles how to render curves.
+        */
         getAnimationProperties(animation: BABYLON.Animation): {
             easingType: string | undefined;
             easingMode: number | undefined;
         };
-        getPathData(animation: BABYLON.Animation): string;
-        drawAllFrames(initialKey: BABYLON.IAnimationKey, endKey: BABYLON.IAnimationKey, easingFunction: BABYLON.EasingFunction): void;
-        curvePathFlat(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, dataType: number): string;
-        curvePathWithTangents(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, type: number): string;
-        curvePath(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, easingFunction: BABYLON.EasingFunction): string;
-        renderPoints(updatedSvgKeyFrame: IKeyframeSvgPoint, index: number): void;
         linearInterpolation(keyframes: BABYLON.IAnimationKey[], data: string, middle: number): string;
         setKeyframePointLinear(point: BABYLON.Vector2, index: number): void;
-        setKeyframePoint(controlPoints: BABYLON.Vector2[], index: number, keyframesCount: number): void;
-        isAnimationPlaying(): void;
-        selectAnimation(animation: BABYLON.Animation): void;
+        getPathData(animation: BABYLON.Animation): string;
         getAnimationData(animation: BABYLON.Animation): {
             loopMode: number | undefined;
             name: string;
@@ -3891,13 +4074,29 @@ declare module INSPECTOR {
             framesPerSecond: number;
             highestFrame: number;
             serialized: any;
-            usesTangents: BABYLON.IAnimationKey | undefined;
+            usesTangents: boolean;
         };
-        getAnimationTypeofChange(selected: string): number;
+        drawAllFrames(initialKey: BABYLON.IAnimationKey, endKey: BABYLON.IAnimationKey, easingFunction: BABYLON.EasingFunction): void;
+        curvePathFlat(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, dataType: number): string;
+        curvePathWithTangents(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, type: number): string;
+        curvePath(keyframes: BABYLON.IAnimationKey[], data: string, middle: number, easingFunction: BABYLON.EasingFunction): string;
+        setKeyframePoint(controlPoints: BABYLON.Vector2[], index: number, keyframesCount: number): void;
         interpolateControlPoints(p0: BABYLON.Vector2, p1: BABYLON.Vector2, u: number, p2: BABYLON.Vector2, v: number, p3: BABYLON.Vector2): BABYLON.Vector2[] | undefined;
+        /**
+        * Core functions
+        * This section handles main Curve Editor Functions.
+        */
+        selectAnimation(animation: BABYLON.Animation): void;
+        isAnimationPlaying(): boolean;
+        playPause(direction: number): void;
+        playStopAnimation(): boolean;
+        analizeAnimation(animation: BABYLON.Animation | null): boolean;
+        /**
+        * Timeline
+        * This section controls the timeline.
+        */
         changeCurrentFrame(frame: number): void;
-        setFlatTangent(): void;
-        zoom(e: React.WheelEvent<HTMLDivElement>): void;
+        updateFrameInKeyFrame(frame: number, index: number): void;
         render(): JSX.Element;
     }
 }
@@ -5131,6 +5330,26 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    interface ITargetedAnimationGridComponentProps {
+        globalState: GlobalState;
+        targetedAnimation: BABYLON.TargetedAnimation;
+        scene: BABYLON.Scene;
+        lockObject: LockObject;
+        onSelectionChangedObservable?: BABYLON.Observable<any>;
+        onPropertyChangedObservable?: BABYLON.Observable<PropertyChangedEvent>;
+    }
+    export class TargetedAnimationGridComponent extends React.Component<ITargetedAnimationGridComponentProps> {
+        private _isCurveEditorOpen;
+        private _animationGroup;
+        constructor(props: ITargetedAnimationGridComponentProps);
+        onOpenAnimationCurveEditor(): void;
+        onCloseAnimationCurveEditor(window: Window | null): void;
+        playOrPause(): void;
+        deleteAnimation(): void;
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     export class PropertyGridTabComponent extends PaneComponent {
         private _timerIntervalId;
         private _lockObject;
@@ -5506,6 +5725,17 @@ declare module INSPECTOR {
     }
 }
 declare module INSPECTOR {
+    interface ITargetedAnimationItemComponentProps {
+        targetedAnimation: BABYLON.TargetedAnimation;
+        extensibilityGroups?: BABYLON.IExplorerExtensibilityGroup[];
+        onClick: () => void;
+    }
+    export class TargetedAnimationItemComponent extends React.Component<ITargetedAnimationItemComponentProps> {
+        constructor(props: ITargetedAnimationItemComponentProps);
+        render(): JSX.Element;
+    }
+}
+declare module INSPECTOR {
     interface ITreeItemSpecializedComponentProps {
         label: string;
         entity?: any;
@@ -5708,6 +5938,7 @@ declare module INSPECTOR {
         static OnPropertyChangedObservable: BABYLON.Observable<PropertyChangedEvent>;
         private static _GlobalState;
         static MarkLineContainerTitleForHighlighting(title: string): void;
+        static MarkMultipleLineContainerTitlesForHighlighting(titles: string[]): void;
         private static _CopyStyles;
         private static _CreateSceneExplorer;
         private static _CreateActionTabs;

+ 151 - 20
dist/preview release/loaders/babylon.glTF2FileLoader.js

@@ -1120,6 +1120,131 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 
 /***/ }),
 
+/***/ "./glTF/2.0/Extensions/KHR_materials_variants.ts":
+/*!*******************************************************!*\
+  !*** ./glTF/2.0/Extensions/KHR_materials_variants.ts ***!
+  \*******************************************************/
+/*! exports provided: KHR_materials_variants */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_variants", function() { return KHR_materials_variants; });
+/* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
+/* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/mesh */ "babylonjs/Misc/tools");
+/* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__);
+
+
+var NAME = "KHR_materials_variants";
+/**
+ * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1681)
+ * !!! Experimental Extension Subject to Changes !!!
+ */
+var KHR_materials_variants = /** @class */ (function () {
+    /** @hidden */
+    function KHR_materials_variants(loader) {
+        /**
+         * The name of this extension.
+         */
+        this.name = NAME;
+        this._tagsToMap = {};
+        this._loader = loader;
+        this.enabled = this._loader.isExtensionUsed(NAME);
+    }
+    /** @hidden */
+    KHR_materials_variants.prototype.dispose = function () {
+        delete this._loader;
+    };
+    /**
+     * Return a list of available variants for this asset.
+     * @returns {string[]}
+     */
+    KHR_materials_variants.prototype.getVariants = function () {
+        return Object.keys(this._tagsToMap);
+    };
+    /**
+     * Select a variant by providing a list of variant tag names.
+     *
+     * @param {(string | string[])} variantName
+     */
+    KHR_materials_variants.prototype.selectVariant = function (variantName) {
+        var _this = this;
+        if (variantName instanceof Array) {
+            variantName.forEach(function (name) { return _this.selectVariantTag(name); });
+        }
+        else {
+            this.selectVariantTag(variantName);
+        }
+    };
+    /**
+     * Select a variant by providing a single variant tag.
+     *
+     * @param {string} variantName
+     */
+    KHR_materials_variants.prototype.selectVariantTag = function (variantName) {
+        // If the name is valid, switch all meshes to use materials defined by the tags
+        var variantMappings = this._tagsToMap[variantName];
+        if (variantMappings === undefined) {
+            return;
+        }
+        variantMappings.forEach(function (mapping) {
+            if (mapping.material) {
+                mapping.mesh.material = mapping.material;
+                return;
+            }
+            mapping.materialPromise.then(function (material) {
+                mapping.mesh.material = material;
+            });
+        });
+    };
+    /** @hidden */
+    KHR_materials_variants.prototype.onLoading = function () {
+        var extensions = this._loader.gltf.extensions;
+        if (extensions && extensions[this.name]) {
+            var extension = extensions[this.name];
+            this.defaultVariant = extension.default;
+        }
+    };
+    /** @hidden */
+    KHR_materials_variants.prototype._loadMeshPrimitiveAsync = function (context, name, node, mesh, primitive, assign) {
+        var _this = this;
+        return _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].LoadExtensionAsync(context, primitive, this.name, function (extensionContext, extension) {
+            var assignMesh = function (babylonMesh) {
+                assign(babylonMesh);
+                var babylonDrawMode = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"]._GetDrawMode(context, primitive.mode);
+                // For each mapping, look at the tags and make a new entry for them
+                extension.mapping.forEach(function (mapping) {
+                    mapping.tags.forEach(function (tag, index) {
+                        var tagMapping = _this._tagsToMap[tag] || [];
+                        var material = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get("#/materials/", _this._loader.gltf.materials, mapping.material);
+                        var meshEntry = {
+                            mesh: babylonMesh,
+                            materialPromise: Promise.resolve(null)
+                        };
+                        if (babylonMesh instanceof babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__["Mesh"]) {
+                            meshEntry.materialPromise = _this._loader._loadMaterialAsync("#/materials/" + mapping.material, material, babylonMesh, babylonDrawMode, function (material) {
+                                meshEntry.material = material;
+                            });
+                        }
+                        tagMapping.push(meshEntry);
+                        _this._tagsToMap[tag] = tagMapping;
+                    });
+                });
+            };
+            _this._loader._disableInstancedMesh++;
+            var promise = _this._loader._loadMeshPrimitiveAsync(context, name, node, mesh, primitive, assignMesh);
+            _this._loader._disableInstancedMesh--;
+            return promise;
+        });
+    };
+    return KHR_materials_variants;
+}());
+
+_glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].RegisterExtension(NAME, function (loader) { return new KHR_materials_variants(loader); });
+
+
+/***/ }),
+
 /***/ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts":
 /*!******************************************************!*\
   !*** ./glTF/2.0/Extensions/KHR_mesh_quantization.ts ***!
@@ -2017,7 +2142,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 /*!**************************************!*\
   !*** ./glTF/2.0/Extensions/index.ts ***!
   \**************************************/
-/*! exports provided: EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, KHR_xmp, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
+/*! exports provided: EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_materials_variants, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, KHR_xmp, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
@@ -2052,32 +2177,35 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _KHR_materials_ior__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./KHR_materials_ior */ "./glTF/2.0/Extensions/KHR_materials_ior.ts");
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return _KHR_materials_ior__WEBPACK_IMPORTED_MODULE_9__["KHR_materials_ior"]; });
 
-/* harmony import */ var _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./KHR_mesh_quantization */ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_10__["KHR_mesh_quantization"]; });
+/* harmony import */ var _KHR_materials_variants__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./KHR_materials_variants */ "./glTF/2.0/Extensions/KHR_materials_variants.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_variants", function() { return _KHR_materials_variants__WEBPACK_IMPORTED_MODULE_10__["KHR_materials_variants"]; });
+
+/* harmony import */ var _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./KHR_mesh_quantization */ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_11__["KHR_mesh_quantization"]; });
 
-/* harmony import */ var _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./KHR_texture_basisu */ "./glTF/2.0/Extensions/KHR_texture_basisu.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_11__["KHR_texture_basisu"]; });
+/* harmony import */ var _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./KHR_texture_basisu */ "./glTF/2.0/Extensions/KHR_texture_basisu.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_12__["KHR_texture_basisu"]; });
 
-/* harmony import */ var _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./KHR_texture_transform */ "./glTF/2.0/Extensions/KHR_texture_transform.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_12__["KHR_texture_transform"]; });
+/* harmony import */ var _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./KHR_texture_transform */ "./glTF/2.0/Extensions/KHR_texture_transform.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_13__["KHR_texture_transform"]; });
 
-/* harmony import */ var _KHR_xmp__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./KHR_xmp */ "./glTF/2.0/Extensions/KHR_xmp.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_xmp", function() { return _KHR_xmp__WEBPACK_IMPORTED_MODULE_13__["KHR_xmp"]; });
+/* harmony import */ var _KHR_xmp__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./KHR_xmp */ "./glTF/2.0/Extensions/KHR_xmp.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_xmp", function() { return _KHR_xmp__WEBPACK_IMPORTED_MODULE_14__["KHR_xmp"]; });
 
-/* harmony import */ var _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./MSFT_audio_emitter */ "./glTF/2.0/Extensions/MSFT_audio_emitter.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_14__["MSFT_audio_emitter"]; });
+/* harmony import */ var _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./MSFT_audio_emitter */ "./glTF/2.0/Extensions/MSFT_audio_emitter.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_15__["MSFT_audio_emitter"]; });
 
-/* harmony import */ var _MSFT_lod__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./MSFT_lod */ "./glTF/2.0/Extensions/MSFT_lod.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return _MSFT_lod__WEBPACK_IMPORTED_MODULE_15__["MSFT_lod"]; });
+/* harmony import */ var _MSFT_lod__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./MSFT_lod */ "./glTF/2.0/Extensions/MSFT_lod.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return _MSFT_lod__WEBPACK_IMPORTED_MODULE_16__["MSFT_lod"]; });
 
-/* harmony import */ var _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./MSFT_minecraftMesh */ "./glTF/2.0/Extensions/MSFT_minecraftMesh.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_16__["MSFT_minecraftMesh"]; });
+/* harmony import */ var _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./MSFT_minecraftMesh */ "./glTF/2.0/Extensions/MSFT_minecraftMesh.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_17__["MSFT_minecraftMesh"]; });
 
-/* harmony import */ var _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./MSFT_sRGBFactors */ "./glTF/2.0/Extensions/MSFT_sRGBFactors.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_17__["MSFT_sRGBFactors"]; });
+/* harmony import */ var _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./MSFT_sRGBFactors */ "./glTF/2.0/Extensions/MSFT_sRGBFactors.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_18__["MSFT_sRGBFactors"]; });
 
-/* harmony import */ var _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./ExtrasAsMetadata */ "./glTF/2.0/Extensions/ExtrasAsMetadata.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ExtrasAsMetadata", function() { return _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_18__["ExtrasAsMetadata"]; });
+/* harmony import */ var _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./ExtrasAsMetadata */ "./glTF/2.0/Extensions/ExtrasAsMetadata.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ExtrasAsMetadata", function() { return _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_19__["ExtrasAsMetadata"]; });
 
 
 
@@ -2098,6 +2226,7 @@ __webpack_require__.r(__webpack_exports__);
 
 
 
+
 
 
 /***/ }),
@@ -4263,7 +4392,7 @@ _glTFFileLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFFileLoader"]._CreateGLTF2Loade
 /*!***************************!*\
   !*** ./glTF/2.0/index.ts ***!
   \***************************/
-/*! exports provided: ArrayItem, GLTFLoader, EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, KHR_xmp, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
+/*! exports provided: ArrayItem, GLTFLoader, EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_materials_variants, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, KHR_xmp, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
@@ -4294,6 +4423,8 @@ __webpack_require__.r(__webpack_exports__);
 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_materials_ior"]; });
 
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_variants", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_materials_variants"]; });
+
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_mesh_quantization"]; });
 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_texture_basisu"]; });

文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.js.map


文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/loaders/babylon.glTF2FileLoader.min.js


+ 151 - 20
dist/preview release/loaders/babylon.glTFFileLoader.js

@@ -3700,6 +3700,131 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 
 /***/ }),
 
+/***/ "./glTF/2.0/Extensions/KHR_materials_variants.ts":
+/*!*******************************************************!*\
+  !*** ./glTF/2.0/Extensions/KHR_materials_variants.ts ***!
+  \*******************************************************/
+/*! exports provided: KHR_materials_variants */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_variants", function() { return KHR_materials_variants; });
+/* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
+/* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/mesh */ "babylonjs/Misc/tools");
+/* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__);
+
+
+var NAME = "KHR_materials_variants";
+/**
+ * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1681)
+ * !!! Experimental Extension Subject to Changes !!!
+ */
+var KHR_materials_variants = /** @class */ (function () {
+    /** @hidden */
+    function KHR_materials_variants(loader) {
+        /**
+         * The name of this extension.
+         */
+        this.name = NAME;
+        this._tagsToMap = {};
+        this._loader = loader;
+        this.enabled = this._loader.isExtensionUsed(NAME);
+    }
+    /** @hidden */
+    KHR_materials_variants.prototype.dispose = function () {
+        delete this._loader;
+    };
+    /**
+     * Return a list of available variants for this asset.
+     * @returns {string[]}
+     */
+    KHR_materials_variants.prototype.getVariants = function () {
+        return Object.keys(this._tagsToMap);
+    };
+    /**
+     * Select a variant by providing a list of variant tag names.
+     *
+     * @param {(string | string[])} variantName
+     */
+    KHR_materials_variants.prototype.selectVariant = function (variantName) {
+        var _this = this;
+        if (variantName instanceof Array) {
+            variantName.forEach(function (name) { return _this.selectVariantTag(name); });
+        }
+        else {
+            this.selectVariantTag(variantName);
+        }
+    };
+    /**
+     * Select a variant by providing a single variant tag.
+     *
+     * @param {string} variantName
+     */
+    KHR_materials_variants.prototype.selectVariantTag = function (variantName) {
+        // If the name is valid, switch all meshes to use materials defined by the tags
+        var variantMappings = this._tagsToMap[variantName];
+        if (variantMappings === undefined) {
+            return;
+        }
+        variantMappings.forEach(function (mapping) {
+            if (mapping.material) {
+                mapping.mesh.material = mapping.material;
+                return;
+            }
+            mapping.materialPromise.then(function (material) {
+                mapping.mesh.material = material;
+            });
+        });
+    };
+    /** @hidden */
+    KHR_materials_variants.prototype.onLoading = function () {
+        var extensions = this._loader.gltf.extensions;
+        if (extensions && extensions[this.name]) {
+            var extension = extensions[this.name];
+            this.defaultVariant = extension.default;
+        }
+    };
+    /** @hidden */
+    KHR_materials_variants.prototype._loadMeshPrimitiveAsync = function (context, name, node, mesh, primitive, assign) {
+        var _this = this;
+        return _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].LoadExtensionAsync(context, primitive, this.name, function (extensionContext, extension) {
+            var assignMesh = function (babylonMesh) {
+                assign(babylonMesh);
+                var babylonDrawMode = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"]._GetDrawMode(context, primitive.mode);
+                // For each mapping, look at the tags and make a new entry for them
+                extension.mapping.forEach(function (mapping) {
+                    mapping.tags.forEach(function (tag, index) {
+                        var tagMapping = _this._tagsToMap[tag] || [];
+                        var material = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get("#/materials/", _this._loader.gltf.materials, mapping.material);
+                        var meshEntry = {
+                            mesh: babylonMesh,
+                            materialPromise: Promise.resolve(null)
+                        };
+                        if (babylonMesh instanceof babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__["Mesh"]) {
+                            meshEntry.materialPromise = _this._loader._loadMaterialAsync("#/materials/" + mapping.material, material, babylonMesh, babylonDrawMode, function (material) {
+                                meshEntry.material = material;
+                            });
+                        }
+                        tagMapping.push(meshEntry);
+                        _this._tagsToMap[tag] = tagMapping;
+                    });
+                });
+            };
+            _this._loader._disableInstancedMesh++;
+            var promise = _this._loader._loadMeshPrimitiveAsync(context, name, node, mesh, primitive, assignMesh);
+            _this._loader._disableInstancedMesh--;
+            return promise;
+        });
+    };
+    return KHR_materials_variants;
+}());
+
+_glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].RegisterExtension(NAME, function (loader) { return new KHR_materials_variants(loader); });
+
+
+/***/ }),
+
 /***/ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts":
 /*!******************************************************!*\
   !*** ./glTF/2.0/Extensions/KHR_mesh_quantization.ts ***!
@@ -4597,7 +4722,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 /*!**************************************!*\
   !*** ./glTF/2.0/Extensions/index.ts ***!
   \**************************************/
-/*! exports provided: EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, KHR_xmp, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
+/*! exports provided: EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_materials_variants, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, KHR_xmp, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
@@ -4632,32 +4757,35 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _KHR_materials_ior__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./KHR_materials_ior */ "./glTF/2.0/Extensions/KHR_materials_ior.ts");
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return _KHR_materials_ior__WEBPACK_IMPORTED_MODULE_9__["KHR_materials_ior"]; });
 
-/* harmony import */ var _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./KHR_mesh_quantization */ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_10__["KHR_mesh_quantization"]; });
+/* harmony import */ var _KHR_materials_variants__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./KHR_materials_variants */ "./glTF/2.0/Extensions/KHR_materials_variants.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_variants", function() { return _KHR_materials_variants__WEBPACK_IMPORTED_MODULE_10__["KHR_materials_variants"]; });
+
+/* harmony import */ var _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./KHR_mesh_quantization */ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_11__["KHR_mesh_quantization"]; });
 
-/* harmony import */ var _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./KHR_texture_basisu */ "./glTF/2.0/Extensions/KHR_texture_basisu.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_11__["KHR_texture_basisu"]; });
+/* harmony import */ var _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./KHR_texture_basisu */ "./glTF/2.0/Extensions/KHR_texture_basisu.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_12__["KHR_texture_basisu"]; });
 
-/* harmony import */ var _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./KHR_texture_transform */ "./glTF/2.0/Extensions/KHR_texture_transform.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_12__["KHR_texture_transform"]; });
+/* harmony import */ var _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./KHR_texture_transform */ "./glTF/2.0/Extensions/KHR_texture_transform.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_13__["KHR_texture_transform"]; });
 
-/* harmony import */ var _KHR_xmp__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./KHR_xmp */ "./glTF/2.0/Extensions/KHR_xmp.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_xmp", function() { return _KHR_xmp__WEBPACK_IMPORTED_MODULE_13__["KHR_xmp"]; });
+/* harmony import */ var _KHR_xmp__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./KHR_xmp */ "./glTF/2.0/Extensions/KHR_xmp.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_xmp", function() { return _KHR_xmp__WEBPACK_IMPORTED_MODULE_14__["KHR_xmp"]; });
 
-/* harmony import */ var _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./MSFT_audio_emitter */ "./glTF/2.0/Extensions/MSFT_audio_emitter.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_14__["MSFT_audio_emitter"]; });
+/* harmony import */ var _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./MSFT_audio_emitter */ "./glTF/2.0/Extensions/MSFT_audio_emitter.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_15__["MSFT_audio_emitter"]; });
 
-/* harmony import */ var _MSFT_lod__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./MSFT_lod */ "./glTF/2.0/Extensions/MSFT_lod.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return _MSFT_lod__WEBPACK_IMPORTED_MODULE_15__["MSFT_lod"]; });
+/* harmony import */ var _MSFT_lod__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./MSFT_lod */ "./glTF/2.0/Extensions/MSFT_lod.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return _MSFT_lod__WEBPACK_IMPORTED_MODULE_16__["MSFT_lod"]; });
 
-/* harmony import */ var _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./MSFT_minecraftMesh */ "./glTF/2.0/Extensions/MSFT_minecraftMesh.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_16__["MSFT_minecraftMesh"]; });
+/* harmony import */ var _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./MSFT_minecraftMesh */ "./glTF/2.0/Extensions/MSFT_minecraftMesh.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_17__["MSFT_minecraftMesh"]; });
 
-/* harmony import */ var _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./MSFT_sRGBFactors */ "./glTF/2.0/Extensions/MSFT_sRGBFactors.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_17__["MSFT_sRGBFactors"]; });
+/* harmony import */ var _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./MSFT_sRGBFactors */ "./glTF/2.0/Extensions/MSFT_sRGBFactors.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_18__["MSFT_sRGBFactors"]; });
 
-/* harmony import */ var _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./ExtrasAsMetadata */ "./glTF/2.0/Extensions/ExtrasAsMetadata.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ExtrasAsMetadata", function() { return _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_18__["ExtrasAsMetadata"]; });
+/* harmony import */ var _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./ExtrasAsMetadata */ "./glTF/2.0/Extensions/ExtrasAsMetadata.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ExtrasAsMetadata", function() { return _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_19__["ExtrasAsMetadata"]; });
 
 
 
@@ -4678,6 +4806,7 @@ __webpack_require__.r(__webpack_exports__);
 
 
 
+
 
 
 /***/ }),
@@ -6843,7 +6972,7 @@ _glTFFileLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFFileLoader"]._CreateGLTF2Loade
 /*!***************************!*\
   !*** ./glTF/2.0/index.ts ***!
   \***************************/
-/*! exports provided: ArrayItem, GLTFLoader, EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, KHR_xmp, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
+/*! exports provided: ArrayItem, GLTFLoader, EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_materials_variants, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, KHR_xmp, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
@@ -6874,6 +7003,8 @@ __webpack_require__.r(__webpack_exports__);
 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_materials_ior"]; });
 
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_variants", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_materials_variants"]; });
+
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_mesh_quantization"]; });
 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_texture_basisu"]; });

文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/loaders/babylon.glTFFileLoader.js.map


文件差異過大導致無法顯示
+ 2 - 2
dist/preview release/loaders/babylon.glTFFileLoader.min.js


+ 48 - 1
dist/preview release/loaders/babylonjs.loaders.d.ts

@@ -1213,7 +1213,7 @@ declare module BABYLON.GLTF2 {
          * @param assign A function called synchronously after parsing the glTF properties
          * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
          */
-        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
         /**
          * @hidden
          * Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
@@ -1912,6 +1912,53 @@ declare module BABYLON.GLTF2.Loader.Extensions {
 }
 declare module BABYLON.GLTF2.Loader.Extensions {
     /**
+     * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1681)
+     * !!! Experimental Extension Subject to Changes !!!
+     */
+    export class KHR_materials_variants implements IGLTFLoaderExtension {
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Defines whether this extension is enabled.
+         */
+        enabled: boolean;
+        private _loader;
+        /**
+         * The default variant name.
+         */
+        defaultVariant: string | undefined;
+        private _tagsToMap;
+        /** @hidden */
+        constructor(loader: GLTFLoader);
+        /** @hidden */
+        dispose(): void;
+        /**
+         * Return a list of available variants for this asset.
+         * @returns {string[]}
+         */
+        getVariants(): string[];
+        /**
+         * Select a variant by providing a list of variant tag names.
+         *
+         * @param {(string | string[])} variantName
+         */
+        selectVariant(variantName: string | string[]): void;
+        /**
+         * Select a variant by providing a single variant tag.
+         *
+         * @param {string} variantName
+         */
+        selectVariantTag(variantName: string): void;
+        /** @hidden */
+        onLoading(): void;
+        /** @hidden */
+        _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
+    }
+}
+declare module BABYLON.GLTF2.Loader.Extensions {
+    /**
      * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization)
      */
     export class KHR_mesh_quantization implements IGLTFLoaderExtension {

+ 151 - 20
dist/preview release/loaders/babylonjs.loaders.js

@@ -5080,6 +5080,131 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 
 /***/ }),
 
+/***/ "./glTF/2.0/Extensions/KHR_materials_variants.ts":
+/*!*******************************************************!*\
+  !*** ./glTF/2.0/Extensions/KHR_materials_variants.ts ***!
+  \*******************************************************/
+/*! exports provided: KHR_materials_variants */
+/***/ (function(module, __webpack_exports__, __webpack_require__) {
+
+"use strict";
+__webpack_require__.r(__webpack_exports__);
+/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_variants", function() { return KHR_materials_variants; });
+/* harmony import */ var _glTFLoader__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../glTFLoader */ "./glTF/2.0/glTFLoader.ts");
+/* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! babylonjs/Meshes/mesh */ "babylonjs/Misc/tools");
+/* harmony import */ var babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__);
+
+
+var NAME = "KHR_materials_variants";
+/**
+ * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1681)
+ * !!! Experimental Extension Subject to Changes !!!
+ */
+var KHR_materials_variants = /** @class */ (function () {
+    /** @hidden */
+    function KHR_materials_variants(loader) {
+        /**
+         * The name of this extension.
+         */
+        this.name = NAME;
+        this._tagsToMap = {};
+        this._loader = loader;
+        this.enabled = this._loader.isExtensionUsed(NAME);
+    }
+    /** @hidden */
+    KHR_materials_variants.prototype.dispose = function () {
+        delete this._loader;
+    };
+    /**
+     * Return a list of available variants for this asset.
+     * @returns {string[]}
+     */
+    KHR_materials_variants.prototype.getVariants = function () {
+        return Object.keys(this._tagsToMap);
+    };
+    /**
+     * Select a variant by providing a list of variant tag names.
+     *
+     * @param {(string | string[])} variantName
+     */
+    KHR_materials_variants.prototype.selectVariant = function (variantName) {
+        var _this = this;
+        if (variantName instanceof Array) {
+            variantName.forEach(function (name) { return _this.selectVariantTag(name); });
+        }
+        else {
+            this.selectVariantTag(variantName);
+        }
+    };
+    /**
+     * Select a variant by providing a single variant tag.
+     *
+     * @param {string} variantName
+     */
+    KHR_materials_variants.prototype.selectVariantTag = function (variantName) {
+        // If the name is valid, switch all meshes to use materials defined by the tags
+        var variantMappings = this._tagsToMap[variantName];
+        if (variantMappings === undefined) {
+            return;
+        }
+        variantMappings.forEach(function (mapping) {
+            if (mapping.material) {
+                mapping.mesh.material = mapping.material;
+                return;
+            }
+            mapping.materialPromise.then(function (material) {
+                mapping.mesh.material = material;
+            });
+        });
+    };
+    /** @hidden */
+    KHR_materials_variants.prototype.onLoading = function () {
+        var extensions = this._loader.gltf.extensions;
+        if (extensions && extensions[this.name]) {
+            var extension = extensions[this.name];
+            this.defaultVariant = extension.default;
+        }
+    };
+    /** @hidden */
+    KHR_materials_variants.prototype._loadMeshPrimitiveAsync = function (context, name, node, mesh, primitive, assign) {
+        var _this = this;
+        return _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].LoadExtensionAsync(context, primitive, this.name, function (extensionContext, extension) {
+            var assignMesh = function (babylonMesh) {
+                assign(babylonMesh);
+                var babylonDrawMode = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"]._GetDrawMode(context, primitive.mode);
+                // For each mapping, look at the tags and make a new entry for them
+                extension.mapping.forEach(function (mapping) {
+                    mapping.tags.forEach(function (tag, index) {
+                        var tagMapping = _this._tagsToMap[tag] || [];
+                        var material = _glTFLoader__WEBPACK_IMPORTED_MODULE_0__["ArrayItem"].Get("#/materials/", _this._loader.gltf.materials, mapping.material);
+                        var meshEntry = {
+                            mesh: babylonMesh,
+                            materialPromise: Promise.resolve(null)
+                        };
+                        if (babylonMesh instanceof babylonjs_Meshes_mesh__WEBPACK_IMPORTED_MODULE_1__["Mesh"]) {
+                            meshEntry.materialPromise = _this._loader._loadMaterialAsync("#/materials/" + mapping.material, material, babylonMesh, babylonDrawMode, function (material) {
+                                meshEntry.material = material;
+                            });
+                        }
+                        tagMapping.push(meshEntry);
+                        _this._tagsToMap[tag] = tagMapping;
+                    });
+                });
+            };
+            _this._loader._disableInstancedMesh++;
+            var promise = _this._loader._loadMeshPrimitiveAsync(context, name, node, mesh, primitive, assignMesh);
+            _this._loader._disableInstancedMesh--;
+            return promise;
+        });
+    };
+    return KHR_materials_variants;
+}());
+
+_glTFLoader__WEBPACK_IMPORTED_MODULE_0__["GLTFLoader"].RegisterExtension(NAME, function (loader) { return new KHR_materials_variants(loader); });
+
+
+/***/ }),
+
 /***/ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts":
 /*!******************************************************!*\
   !*** ./glTF/2.0/Extensions/KHR_mesh_quantization.ts ***!
@@ -5977,7 +6102,7 @@ _glTFLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFLoader"].RegisterExtension(NAME, f
 /*!**************************************!*\
   !*** ./glTF/2.0/Extensions/index.ts ***!
   \**************************************/
-/*! exports provided: EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, KHR_xmp, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
+/*! exports provided: EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_materials_variants, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, KHR_xmp, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
@@ -6012,32 +6137,35 @@ __webpack_require__.r(__webpack_exports__);
 /* harmony import */ var _KHR_materials_ior__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./KHR_materials_ior */ "./glTF/2.0/Extensions/KHR_materials_ior.ts");
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return _KHR_materials_ior__WEBPACK_IMPORTED_MODULE_9__["KHR_materials_ior"]; });
 
-/* harmony import */ var _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./KHR_mesh_quantization */ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_10__["KHR_mesh_quantization"]; });
+/* harmony import */ var _KHR_materials_variants__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! ./KHR_materials_variants */ "./glTF/2.0/Extensions/KHR_materials_variants.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_variants", function() { return _KHR_materials_variants__WEBPACK_IMPORTED_MODULE_10__["KHR_materials_variants"]; });
+
+/* harmony import */ var _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./KHR_mesh_quantization */ "./glTF/2.0/Extensions/KHR_mesh_quantization.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _KHR_mesh_quantization__WEBPACK_IMPORTED_MODULE_11__["KHR_mesh_quantization"]; });
 
-/* harmony import */ var _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./KHR_texture_basisu */ "./glTF/2.0/Extensions/KHR_texture_basisu.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_11__["KHR_texture_basisu"]; });
+/* harmony import */ var _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./KHR_texture_basisu */ "./glTF/2.0/Extensions/KHR_texture_basisu.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _KHR_texture_basisu__WEBPACK_IMPORTED_MODULE_12__["KHR_texture_basisu"]; });
 
-/* harmony import */ var _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(/*! ./KHR_texture_transform */ "./glTF/2.0/Extensions/KHR_texture_transform.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_12__["KHR_texture_transform"]; });
+/* harmony import */ var _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./KHR_texture_transform */ "./glTF/2.0/Extensions/KHR_texture_transform.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_transform", function() { return _KHR_texture_transform__WEBPACK_IMPORTED_MODULE_13__["KHR_texture_transform"]; });
 
-/* harmony import */ var _KHR_xmp__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(/*! ./KHR_xmp */ "./glTF/2.0/Extensions/KHR_xmp.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_xmp", function() { return _KHR_xmp__WEBPACK_IMPORTED_MODULE_13__["KHR_xmp"]; });
+/* harmony import */ var _KHR_xmp__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./KHR_xmp */ "./glTF/2.0/Extensions/KHR_xmp.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_xmp", function() { return _KHR_xmp__WEBPACK_IMPORTED_MODULE_14__["KHR_xmp"]; });
 
-/* harmony import */ var _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(/*! ./MSFT_audio_emitter */ "./glTF/2.0/Extensions/MSFT_audio_emitter.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_14__["MSFT_audio_emitter"]; });
+/* harmony import */ var _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./MSFT_audio_emitter */ "./glTF/2.0/Extensions/MSFT_audio_emitter.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_audio_emitter", function() { return _MSFT_audio_emitter__WEBPACK_IMPORTED_MODULE_15__["MSFT_audio_emitter"]; });
 
-/* harmony import */ var _MSFT_lod__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(/*! ./MSFT_lod */ "./glTF/2.0/Extensions/MSFT_lod.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return _MSFT_lod__WEBPACK_IMPORTED_MODULE_15__["MSFT_lod"]; });
+/* harmony import */ var _MSFT_lod__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./MSFT_lod */ "./glTF/2.0/Extensions/MSFT_lod.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_lod", function() { return _MSFT_lod__WEBPACK_IMPORTED_MODULE_16__["MSFT_lod"]; });
 
-/* harmony import */ var _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(/*! ./MSFT_minecraftMesh */ "./glTF/2.0/Extensions/MSFT_minecraftMesh.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_16__["MSFT_minecraftMesh"]; });
+/* harmony import */ var _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./MSFT_minecraftMesh */ "./glTF/2.0/Extensions/MSFT_minecraftMesh.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_minecraftMesh", function() { return _MSFT_minecraftMesh__WEBPACK_IMPORTED_MODULE_17__["MSFT_minecraftMesh"]; });
 
-/* harmony import */ var _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(/*! ./MSFT_sRGBFactors */ "./glTF/2.0/Extensions/MSFT_sRGBFactors.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_17__["MSFT_sRGBFactors"]; });
+/* harmony import */ var _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./MSFT_sRGBFactors */ "./glTF/2.0/Extensions/MSFT_sRGBFactors.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "MSFT_sRGBFactors", function() { return _MSFT_sRGBFactors__WEBPACK_IMPORTED_MODULE_18__["MSFT_sRGBFactors"]; });
 
-/* harmony import */ var _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(/*! ./ExtrasAsMetadata */ "./glTF/2.0/Extensions/ExtrasAsMetadata.ts");
-/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ExtrasAsMetadata", function() { return _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_18__["ExtrasAsMetadata"]; });
+/* harmony import */ var _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(/*! ./ExtrasAsMetadata */ "./glTF/2.0/Extensions/ExtrasAsMetadata.ts");
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ExtrasAsMetadata", function() { return _ExtrasAsMetadata__WEBPACK_IMPORTED_MODULE_19__["ExtrasAsMetadata"]; });
 
 
 
@@ -6058,6 +6186,7 @@ __webpack_require__.r(__webpack_exports__);
 
 
 
+
 
 
 /***/ }),
@@ -8223,7 +8352,7 @@ _glTFFileLoader__WEBPACK_IMPORTED_MODULE_1__["GLTFFileLoader"]._CreateGLTF2Loade
 /*!***************************!*\
   !*** ./glTF/2.0/index.ts ***!
   \***************************/
-/*! exports provided: ArrayItem, GLTFLoader, EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, KHR_xmp, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
+/*! exports provided: ArrayItem, GLTFLoader, EXT_lights_image_based, EXT_mesh_gpu_instancing, KHR_draco_mesh_compression, KHR_lights, KHR_materials_pbrSpecularGlossiness, KHR_materials_unlit, KHR_materials_clearcoat, KHR_materials_sheen, KHR_materials_specular, KHR_materials_ior, KHR_materials_variants, KHR_mesh_quantization, KHR_texture_basisu, KHR_texture_transform, KHR_xmp, MSFT_audio_emitter, MSFT_lod, MSFT_minecraftMesh, MSFT_sRGBFactors, ExtrasAsMetadata */
 /***/ (function(module, __webpack_exports__, __webpack_require__) {
 
 "use strict";
@@ -8254,6 +8383,8 @@ __webpack_require__.r(__webpack_exports__);
 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_ior", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_materials_ior"]; });
 
+/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_materials_variants", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_materials_variants"]; });
+
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_mesh_quantization", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_mesh_quantization"]; });
 
 /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "KHR_texture_basisu", function() { return _Extensions__WEBPACK_IMPORTED_MODULE_1__["KHR_texture_basisu"]; });

文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.js.map


文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/loaders/babylonjs.loaders.min.js


+ 102 - 2
dist/preview release/loaders/babylonjs.loaders.module.d.ts

@@ -1285,7 +1285,7 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoaderExtension" {
          * @param assign A function called synchronously after parsing the glTF properties
          * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
          */
-        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
         /**
          * @hidden
          * Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
@@ -2050,6 +2050,58 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_ior" {
         private _loadIorPropertiesAsync;
     }
 }
+declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_variants" {
+    import { Nullable } from "babylonjs/types";
+    import { IGLTFLoaderExtension } from "babylonjs-loaders/glTF/2.0/glTFLoaderExtension";
+    import { GLTFLoader } from "babylonjs-loaders/glTF/2.0/glTFLoader";
+    import { AbstractMesh } from 'babylonjs/Meshes/abstractMesh';
+    import { INode, IMeshPrimitive, IMesh } from "babylonjs-loaders/glTF/2.0/glTFLoaderInterfaces";
+    /**
+     * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1681)
+     * !!! Experimental Extension Subject to Changes !!!
+     */
+    export class KHR_materials_variants implements IGLTFLoaderExtension {
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Defines whether this extension is enabled.
+         */
+        enabled: boolean;
+        private _loader;
+        /**
+         * The default variant name.
+         */
+        defaultVariant: string | undefined;
+        private _tagsToMap;
+        /** @hidden */
+        constructor(loader: GLTFLoader);
+        /** @hidden */
+        dispose(): void;
+        /**
+         * Return a list of available variants for this asset.
+         * @returns {string[]}
+         */
+        getVariants(): string[];
+        /**
+         * Select a variant by providing a list of variant tag names.
+         *
+         * @param {(string | string[])} variantName
+         */
+        selectVariant(variantName: string | string[]): void;
+        /**
+         * Select a variant by providing a single variant tag.
+         *
+         * @param {string} variantName
+         */
+        selectVariantTag(variantName: string): void;
+        /** @hidden */
+        onLoading(): void;
+        /** @hidden */
+        _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
+    }
+}
 declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_mesh_quantization" {
     import { IGLTFLoaderExtension } from "babylonjs-loaders/glTF/2.0/glTFLoaderExtension";
     import { GLTFLoader } from "babylonjs-loaders/glTF/2.0/glTFLoader";
@@ -2346,6 +2398,7 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/index" {
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_sheen";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_specular";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_ior";
+    export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_variants";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_mesh_quantization";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_texture_basisu";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_texture_transform";
@@ -3949,7 +4002,7 @@ declare module BABYLON.GLTF2 {
          * @param assign A function called synchronously after parsing the glTF properties
          * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
          */
-        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
         /**
          * @hidden
          * Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
@@ -4648,6 +4701,53 @@ declare module BABYLON.GLTF2.Loader.Extensions {
 }
 declare module BABYLON.GLTF2.Loader.Extensions {
     /**
+     * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1681)
+     * !!! Experimental Extension Subject to Changes !!!
+     */
+    export class KHR_materials_variants implements IGLTFLoaderExtension {
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Defines whether this extension is enabled.
+         */
+        enabled: boolean;
+        private _loader;
+        /**
+         * The default variant name.
+         */
+        defaultVariant: string | undefined;
+        private _tagsToMap;
+        /** @hidden */
+        constructor(loader: GLTFLoader);
+        /** @hidden */
+        dispose(): void;
+        /**
+         * Return a list of available variants for this asset.
+         * @returns {string[]}
+         */
+        getVariants(): string[];
+        /**
+         * Select a variant by providing a list of variant tag names.
+         *
+         * @param {(string | string[])} variantName
+         */
+        selectVariant(variantName: string | string[]): void;
+        /**
+         * Select a variant by providing a single variant tag.
+         *
+         * @param {string} variantName
+         */
+        selectVariantTag(variantName: string): void;
+        /** @hidden */
+        onLoading(): void;
+        /** @hidden */
+        _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
+    }
+}
+declare module BABYLON.GLTF2.Loader.Extensions {
+    /**
      * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization)
      */
     export class KHR_mesh_quantization implements IGLTFLoaderExtension {

文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.d.ts


文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.js


+ 18 - 2
dist/preview release/nodeEditor/babylon.nodeEditor.max.js

@@ -59444,6 +59444,8 @@ __webpack_require__.r(__webpack_exports__);
 
 
 
+
+
 var BlockTools = /** @class */ (function () {
     function BlockTools() {
     }
@@ -59847,10 +59849,19 @@ var BlockTools = /** @class */ (function () {
                 u.setAsAttribute("particle_texturemask");
                 return u;
             }
+            case "ParticlePositionWorldBlock": {
+                var pos = new babylonjs_Materials_Node_Blocks_Fragment_discardBlock__WEBPACK_IMPORTED_MODULE_0__["InputBlock"]("PositionWorld");
+                pos.setAsAttribute("particle_positionw");
+                return pos;
+            }
             case "ParticleRampGradientBlock":
                 return new babylonjs_Materials_Node_Blocks_Fragment_discardBlock__WEBPACK_IMPORTED_MODULE_0__["ParticleRampGradientBlock"]("ParticleRampGradient");
             case "ParticleBlendMultiplyBlock":
                 return new babylonjs_Materials_Node_Blocks_Fragment_discardBlock__WEBPACK_IMPORTED_MODULE_0__["ParticleBlendMultiplyBlock"]("ParticleBlendMultiply");
+            case "FragCoordBlock":
+                return new babylonjs_Materials_Node_Blocks_Fragment_discardBlock__WEBPACK_IMPORTED_MODULE_0__["FragCoordBlock"]("FragCoord");
+            case "ScreenSizeBlock":
+                return new babylonjs_Materials_Node_Blocks_Fragment_discardBlock__WEBPACK_IMPORTED_MODULE_0__["ScreenSizeBlock"]("ScreenSize");
         }
         return null;
     };
@@ -60094,7 +60105,7 @@ var NodeListComponent = /** @class */ (function (_super) {
             Animation: ["BonesBlock", "MorphTargetsBlock"],
             Color_Management: ["ReplaceColorBlock", "PosterizeBlock", "GradientBlock", "DesaturateBlock"],
             Conversion_Blocks: ["ColorMergerBlock", "ColorSplitterBlock", "VectorMergerBlock", "VectorSplitterBlock"],
-            Inputs: ["Float", "Vector2", "Vector3", "Vector4", "Color3", "Color4", "TextureBlock", "ReflectionTextureBlock", "TimeBlock", "DeltaTimeBlock"],
+            Inputs: ["Float", "Vector2", "Vector3", "Vector4", "Color3", "Color4", "TextureBlock", "ReflectionTextureBlock", "TimeBlock", "DeltaTimeBlock", "FragCoordBlock", "ScreenSizeBlock"],
             Interpolation: ["LerpBlock", "StepBlock", "SmoothStepBlock", "NLerpBlock"],
             Math__Standard: ["AddBlock", "DivideBlock", "MaxBlock", "MinBlock", "ModBlock", "MultiplyBlock", "NegateBlock", "OneMinusBlock", "ReciprocalBlock", "ScaleBlock", "SignBlock", "SqrtBlock", "SubtractBlock"],
             Math__Scientific: ["AbsBlock", "ArcCosBlock", "ArcSinBlock", "ArcTanBlock", "ArcTan2Block", "CosBlock", "DegreesToRadiansBlock", "ExpBlock", "Exp2Block", "FractBlock", "LogBlock", "PowBlock", "RadiansToDegreesBlock", "SawToothWaveBlock", "SinBlock", "SquareWaveBlock", "TanBlock", "TriangleWaveBlock"],
@@ -60103,7 +60114,7 @@ var NodeListComponent = /** @class */ (function (_super) {
             Mesh: ["InstancesBlock", "PositionBlock", "UVBlock", "ColorBlock", "NormalBlock", "PerturbNormalBlock", "NormalBlendBlock", "TangentBlock", "MatrixIndicesBlock", "MatrixWeightsBlock", "WorldPositionBlock", "WorldNormalBlock", "WorldTangentBlock", "FrontFacingBlock"],
             Noises: ["RandomNumberBlock", "SimplexPerlin3DBlock", "WorleyNoise3DBlock"],
             Output_Nodes: ["VertexOutputBlock", "FragmentOutputBlock", "DiscardBlock"],
-            Particle: ["ParticleBlendMultiplyBlock", "ParticleColorBlock", "ParticleRampGradientBlock", "ParticleTextureBlock", "ParticleTextureMaskBlock", "ParticleUVBlock"],
+            Particle: ["ParticleBlendMultiplyBlock", "ParticleColorBlock", "ParticlePositionWorldBlock", "ParticleRampGradientBlock", "ParticleTextureBlock", "ParticleTextureMaskBlock", "ParticleUVBlock"],
             PBR: ["PBRMetallicRoughnessBlock", "AmbientOcclusionBlock", "AnisotropyBlock", "ClearCoatBlock", "ReflectionBlock", "ReflectivityBlock", "RefractionBlock", "SheenBlock", "SubSurfaceBlock"],
             PostProcess: ["Position2DBlock", "CurrentScreenBlock"],
             Range: ["ClampBlock", "RemapBlock", "NormalizeBlock"],
@@ -60272,6 +60283,9 @@ var NodeListComponent = /** @class */ (function (_super) {
         "ParticleTextureMaskBlock": "The particle texture mask",
         "ParticleRampGradientBlock": "The particle ramp gradient block",
         "ParticleBlendMultiplyBlock": "The particle blend multiply block",
+        "ParticlePositionWorldBlock": "The world position of the particle",
+        "FragCoordBlock": "The gl_FragCoord predefined variable that contains the window relative coordinate (x, y, z, 1/w)",
+        "ScreenSizeBlock": "The size (in pixels) of the screen window",
     };
     return NodeListComponent;
 }(react__WEBPACK_IMPORTED_MODULE_1__["Component"]));
@@ -61881,12 +61895,14 @@ var inputNameToAttributeValue = {
     "particle_uv": "uv",
     "particle_color": "color",
     "particle_texturemask": "textureMask",
+    "particle_positionw": "positionW",
 };
 var inputNameToAttributeName = {
     "position2d": "postprocess",
     "particle_uv": "particle",
     "particle_color": "particle",
     "particle_texturemask": "particle",
+    "particle_positionw": "particle",
 };
 var InputDisplayManager = /** @class */ (function () {
     function InputDisplayManager() {

文件差異過大導致無法顯示
+ 1 - 1
dist/preview release/nodeEditor/babylon.nodeEditor.max.js.map


文件差異過大導致無法顯示
+ 4 - 2
dist/preview release/nodeEditor/babylon.nodeEditor.module.d.ts


+ 1 - 1
dist/preview release/packagesSizeBaseLine.json

@@ -1 +1 @@
-{"thinEngineOnly":115966,"engineOnly":152369,"sceneOnly":505311,"minGridMaterial":647903,"minStandardMaterial":792016}
+{"thinEngineOnly":116011,"engineOnly":152414,"sceneOnly":505526,"minGridMaterial":648479,"minStandardMaterial":791757}

文件差異過大導致無法顯示
+ 709 - 33
dist/preview release/viewer/babylon.module.d.ts


文件差異過大導致無法顯示
+ 150 - 134
dist/preview release/viewer/babylon.viewer.js


文件差異過大導致無法顯示
+ 2 - 2
dist/preview release/viewer/babylon.viewer.max.js


+ 102 - 2
dist/preview release/viewer/babylonjs.loaders.module.d.ts

@@ -1285,7 +1285,7 @@ declare module "babylonjs-loaders/glTF/2.0/glTFLoaderExtension" {
          * @param assign A function called synchronously after parsing the glTF properties
          * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
          */
-        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
         /**
          * @hidden
          * Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
@@ -2050,6 +2050,58 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_ior" {
         private _loadIorPropertiesAsync;
     }
 }
+declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_variants" {
+    import { Nullable } from "babylonjs/types";
+    import { IGLTFLoaderExtension } from "babylonjs-loaders/glTF/2.0/glTFLoaderExtension";
+    import { GLTFLoader } from "babylonjs-loaders/glTF/2.0/glTFLoader";
+    import { AbstractMesh } from 'babylonjs/Meshes/abstractMesh';
+    import { INode, IMeshPrimitive, IMesh } from "babylonjs-loaders/glTF/2.0/glTFLoaderInterfaces";
+    /**
+     * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1681)
+     * !!! Experimental Extension Subject to Changes !!!
+     */
+    export class KHR_materials_variants implements IGLTFLoaderExtension {
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Defines whether this extension is enabled.
+         */
+        enabled: boolean;
+        private _loader;
+        /**
+         * The default variant name.
+         */
+        defaultVariant: string | undefined;
+        private _tagsToMap;
+        /** @hidden */
+        constructor(loader: GLTFLoader);
+        /** @hidden */
+        dispose(): void;
+        /**
+         * Return a list of available variants for this asset.
+         * @returns {string[]}
+         */
+        getVariants(): string[];
+        /**
+         * Select a variant by providing a list of variant tag names.
+         *
+         * @param {(string | string[])} variantName
+         */
+        selectVariant(variantName: string | string[]): void;
+        /**
+         * Select a variant by providing a single variant tag.
+         *
+         * @param {string} variantName
+         */
+        selectVariantTag(variantName: string): void;
+        /** @hidden */
+        onLoading(): void;
+        /** @hidden */
+        _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
+    }
+}
 declare module "babylonjs-loaders/glTF/2.0/Extensions/KHR_mesh_quantization" {
     import { IGLTFLoaderExtension } from "babylonjs-loaders/glTF/2.0/glTFLoaderExtension";
     import { GLTFLoader } from "babylonjs-loaders/glTF/2.0/glTFLoader";
@@ -2346,6 +2398,7 @@ declare module "babylonjs-loaders/glTF/2.0/Extensions/index" {
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_sheen";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_specular";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_ior";
+    export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_materials_variants";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_mesh_quantization";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_texture_basisu";
     export * from "babylonjs-loaders/glTF/2.0/Extensions/KHR_texture_transform";
@@ -3949,7 +4002,7 @@ declare module BABYLON.GLTF2 {
          * @param assign A function called synchronously after parsing the glTF properties
          * @returns A promise that resolves with the loaded mesh when the load is complete or null if not handled
          */
-        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Promise<AbstractMesh>;
+        _loadMeshPrimitiveAsync?(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
         /**
          * @hidden
          * Define this method to modify the default behavior when loading materials. Load material creates the material and then loads material properties.
@@ -4648,6 +4701,53 @@ declare module BABYLON.GLTF2.Loader.Extensions {
 }
 declare module BABYLON.GLTF2.Loader.Extensions {
     /**
+     * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1681)
+     * !!! Experimental Extension Subject to Changes !!!
+     */
+    export class KHR_materials_variants implements IGLTFLoaderExtension {
+        /**
+         * The name of this extension.
+         */
+        readonly name: string;
+        /**
+         * Defines whether this extension is enabled.
+         */
+        enabled: boolean;
+        private _loader;
+        /**
+         * The default variant name.
+         */
+        defaultVariant: string | undefined;
+        private _tagsToMap;
+        /** @hidden */
+        constructor(loader: GLTFLoader);
+        /** @hidden */
+        dispose(): void;
+        /**
+         * Return a list of available variants for this asset.
+         * @returns {string[]}
+         */
+        getVariants(): string[];
+        /**
+         * Select a variant by providing a list of variant tag names.
+         *
+         * @param {(string | string[])} variantName
+         */
+        selectVariant(variantName: string | string[]): void;
+        /**
+         * Select a variant by providing a single variant tag.
+         *
+         * @param {string} variantName
+         */
+        selectVariantTag(variantName: string): void;
+        /** @hidden */
+        onLoading(): void;
+        /** @hidden */
+        _loadMeshPrimitiveAsync(context: string, name: string, node: INode, mesh: IMesh, primitive: IMeshPrimitive, assign: (babylonMesh: AbstractMesh) => void): Nullable<Promise<AbstractMesh>>;
+    }
+}
+declare module BABYLON.GLTF2.Loader.Extensions {
+    /**
      * [Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_mesh_quantization)
      */
     export class KHR_mesh_quantization implements IGLTFLoaderExtension {

+ 7 - 5
dist/preview release/what's new.md

@@ -8,7 +8,7 @@
 - Added HDR texture filtering tools to the sandbox ([Sebavan](https://github.com/sebavan/))
 - Reflection probes can now be used to give accurate shading with PBR ([CraigFeldpsar](https://github.com/craigfeldspar) and ([Sebavan](https://github.com/sebavan/)))
 - Added editing of PBR materials, Post processes and Particle fragment shaders in the node material editor ([Popov72](https://github.com/Popov72))
-- Added Curve editor to create and view selected entity's animations in the Inspector ([pixelspace](https://github.com/devpixelspace))
+- Added Curve editor to manage entity's animations and edit animation within animation groups in Inspector ([pixelspace](https://github.com/devpixelspace))
 - Added support in `ShadowGenerator` for fast fake soft transparent shadows ([Popov72](https://github.com/Popov72))
 - Added support for **thin instances** for faster mesh instances. [Doc](https://doc.babylonjs.com/how_to/how_to_use_thininstances) ([Popov72](https://github.com/Popov72))
 
@@ -24,9 +24,9 @@
 - Added support for `material.disableColorWrite` ([Deltakosh](https://github.com/deltakosh))
 - The Mesh Asset Task also accepts File as sceneInput ([RaananW](https://github.com/RaananW))
 - Added support preserving vert colors for CSG objects ([PirateJC](https://github.com/PirateJC))
-- Added support in `ShadowGenerator` for fast fake soft transparent shadows ([Popov72](https://github.com/Popov72))
 - Added `boundingBoxRenderer.onBeforeBoxRenderingObservable` and `boundingBoxRenderer.onAfterBoxRenderingObservable` ([Deltakosh](https://github.com/deltakosh))
 - Added initial code for user facing DeviceSourceManager ([PolygonalSun](https://github.com/PolygonalSun))
+- Added a Simple and advanced timer, based on observables ([RaananW](https://github.com/RaananW))
 
 ### Engine
 
@@ -45,6 +45,8 @@
 - Added a modulo block ([ageneau](https://github.com/ageneau))
 - Fix bug where frame port labels would be the names of incorrect nodes ([belfortk](https://github.com/belfortk))
 - Fix bug where long comments on collapsed frames broke port alignment ([belfortk](https://github.com/belfortk))
+- Add the `FragCoord` and `ScreenSize` blocks ([Popov72](https://github.com/Popov72))
+- Particle systems: add the `ParticlePositionWorld` block ([Popov72](https://github.com/Popov72))
 
 ### Inspector
 
@@ -59,6 +61,7 @@
 ### Cameras
 
 - Fixed up vector not correctly handled with stereoscopic rig ([cedricguillemet](https://github.com/cedricguillemet))
+- handle reattachment of panning button for `ArcRotateCamera` ([cedricguillemet](https://github.com/cedricguillemet))
 - Added flag to TargetCamera to invert rotation direction and multiplier to adjust speed ([Exolun](https://github.com/Exolun))
 - Added upwards and downwards keyboard input to `FreeCamera` ([Pheater](https://github.com/pheater))
 
@@ -141,7 +144,6 @@
 
 - .HDR environment files will now give accurate PBR reflections ([CraigFeldpsar](https://github.com/craigfeldspar))
 
-
 ### Audio
 
 - Added support of `metadata` in `Sound` class. ([julien-moreau](https://github.com/julien-moreau))
@@ -201,13 +203,13 @@
 - Fixed an issue with stereoscopic rendering ([#8000](https://github.com/BabylonJS/Babylon.js/issues/8000)) ([RaananW](https://github.com/RaananW))
 - Fix bug with multiple scenes when resizing the screen and there's a glow or highlight layer active ([Popov72](https://github.com/Popov72))
 - Fix an error when compiling with the closure compiler ([ageneau](https://github.com/ageneau/))
-- Fix an error in applying texture to sides of `extrudePolygon` using faceUV[1] ([JohnK](https://github.com/BabylonJSGuide/))
+- Fix an error in applying texture to sides of `extrudePolygon` using faceUV\[1\] ([JohnK](https://github.com/BabylonJSGuide/))
 - Playground didn't work if query params were added to the URL ([RaananW](https://github.com/RaananW))
 - Fixed Path3D `_distances` / length computation ([Poolminer](https://github.com/Poolminer))
 - Make sure bone matrices are up to date when calling `TransformNode.attachToBone` ([Popov72](https://github.com/Popov72))
 - Fix display problem with transparent objects and SSAO2 pipeline (bug in the `GeometryBufferRenderer`) ([Popov72](https://github.com/Popov72))
 - Fixed `Sound` not accepting a `TransformNode` as a source for spatial sound ([Poolminer](https://github.com/Poolminer))
-- Fixed an issue with transformation set after physics body was created using cannon.js ([#7928](https://github.com/BabylonJS/Babylon.js/issues/7928)) ([RaananW](https://github.com/RaananW))
+- Fixed an issue with transformation set after physics body was created using cannon.js (excluding height and plane) ([#7928](https://github.com/BabylonJS/Babylon.js/issues/7928)) ([RaananW](https://github.com/RaananW))
 - Fix bug when using `ShadowOnlyMaterial` with Cascaded Shadow Map and `autoCalcDepthBounds` is `true` ([Popov72](https://github.com/Popov72))
 - Fix OBJ serializer default scene scene handedness causing [OBJ Mirror export](https://forum.babylonjs.com/t/obj-export-mirrored/10835/10)
 - Fix bug when using shadows + instances + transparent meshes + `transparencyShadow = false` ([Popov72](https://github.com/Popov72))

+ 3 - 3
inspector/src/components/actionTabs/lineContainerComponent.tsx

@@ -26,13 +26,13 @@ export class LineContainerComponent extends React.Component<ILineContainerCompon
     }
 
     componentDidMount() {
-        if (this.props.globalState && !this.props.globalState.selectedLineContainerTitle) {
+        if (this.props.globalState && this.props.globalState.selectedLineContainerTitles.length === 0) {
             return;
         }
 
-        if (this.props.globalState && this.props.globalState.selectedLineContainerTitle === this.props.title) {
+        if (this.props.globalState && this.props.globalState.selectedLineContainerTitles.indexOf(this.props.title) > -1) {
             setTimeout(() => {
-                this.props.globalState!.selectedLineContainerTitle = "";
+                this.props.globalState!.selectedLineContainerTitles = [];
             });
 
             this.setState({ isExpanded: true, isHighlighted: true });

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

@@ -3,7 +3,7 @@ import { PaneComponent, IPaneComponentProps } from "../paneComponent";
 
 import { ArcRotateCamera } from "babylonjs/Cameras/arcRotateCamera";
 import { FreeCamera } from "babylonjs/Cameras/freeCamera";
-import { AnimationGroup } from "babylonjs/Animations/animationGroup";
+import { AnimationGroup, TargetedAnimation } from "babylonjs/Animations/animationGroup";
 import { Material } from "babylonjs/Materials/material";
 import { BackgroundMaterial } from "babylonjs/Materials/Background/backgroundMaterial";
 import { StandardMaterial } from "babylonjs/Materials/standardMaterial";
@@ -95,6 +95,7 @@ import { SpriteManagerPropertyGridComponent } from './propertyGrids/sprites/spri
 import { SpriteManager } from 'babylonjs/Sprites/spriteManager';
 import { SpritePropertyGridComponent } from './propertyGrids/sprites/spritePropertyGridComponent';
 import { Sprite } from 'babylonjs/Sprites/sprite';
+import { TargetedAnimationGridComponent } from './propertyGrids/animations/targetedAnimationPropertyGridComponent';
 
 export class PropertyGridTabComponent extends PaneComponent {
     private _timerIntervalId: number;
@@ -320,6 +321,17 @@ export class PropertyGridTabComponent extends PaneComponent {
                     onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
             }
 
+            if (className === "TargetedAnimation") {
+                const targetedAnimation = entity as TargetedAnimation;
+                return (<TargetedAnimationGridComponent
+                    globalState={this.props.globalState}
+                    targetedAnimation={targetedAnimation}
+                    scene={this.props.scene}
+                    lockObject={this._lockObject}
+                    onSelectionChangedObservable={this.props.onSelectionChangedObservable}
+                    onPropertyChangedObservable={this.props.onPropertyChangedObservable} />);
+            }            
+
             if (className.indexOf("Material") !== -1) {
                 const material = entity as Material;
                 return (<MaterialPropertyGridComponent material={material}

+ 8 - 3
inspector/src/components/actionTabs/tabs/propertyGrids/animations/anchorSvgPoint.tsx

@@ -9,6 +9,7 @@ interface IAnchorSvgPointProps {
    type: string;
    index: string;
    selected: boolean;
+   selectControlPoint: (id: string) => void;
 }
 
 
@@ -16,14 +17,18 @@ export class AnchorSvgPoint extends React.Component<IAnchorSvgPointProps>{
     constructor(props: IAnchorSvgPointProps) {
         super(props);
     }
+
+    select(){
+        this.props.selectControlPoint(this.props.type);
+    }
     
     render() {
         return (
         <>
-            <svg x={this.props.control.x} y={this.props.control.y} style={{overflow:'visible'}}>
-                <circle type={this.props.type} data-id={this.props.index} className={`draggable control-point ${this.props.active ? 'active' : ''}`} cx="0" cy="0"  r="2" stroke="none" strokeWidth="0" fill={this.props.active ? "blue" : "black"}   />
+            <svg x={this.props.control.x} y={this.props.control.y} style={{overflow:'visible'}} onClick={() => this.select()}>
+                <circle type={this.props.type} data-id={this.props.index} className={`draggable control-point ${this.props.active ? 'active' : ''}`} cx="0" cy="0"  r="2" stroke="white" strokeWidth={this.props.selected ? 1 : 0}  fill={this.props.active ? "blue" : "black"}   />
             </svg>
-            <line className={`control-point ${this.props.active ? 'active' : ''}`} x1={this.props.anchor.x} y1={this.props.anchor.y} x2={this.props.control.x} y2={this.props.control.y} stroke="green" strokeWidth="0.75" />
+            <line className={`control-point ${this.props.active ? 'active' : ''}`} x1={this.props.anchor.x} y1={this.props.anchor.y} x2={this.props.control.x} y2={this.props.control.y} strokeWidth="1" />
         </>
         )
     }

文件差異過大導致無法顯示
+ 769 - 383
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationCurveEditorComponent.tsx


+ 6 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationGroupPropertyGridComponent.tsx

@@ -12,6 +12,7 @@ import { TextLineComponent } from "../../../lines/textLineComponent";
 import { SliderLineComponent } from "../../../lines/sliderLineComponent";
 import { LockObject } from "../lockObject";
 import { GlobalState } from '../../../../globalState';
+import { TextInputLineComponent } from '../../../lines/textInputLineComponent';
 
 interface IAnimationGroupGridComponentProps {
     globalState: GlobalState;
@@ -129,7 +130,11 @@ export class AnimationGroupGridComponent extends React.Component<IAnimationGroup
         const playButtonText = animationGroup.isPlaying ? "Pause" : "Play"
 
         return (
-            <div className="pane">
+            <div className="pane">                
+                <LineContainerComponent globalState={this.props.globalState} title="GENERAL">
+                    <TextLineComponent label="Class" value={animationGroup.getClassName()} />
+                    <TextInputLineComponent lockObject={this.props.lockObject} label="Name" target={animationGroup} propertyName="name" onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>
+                </LineContainerComponent>
                 <LineContainerComponent globalState={this.props.globalState} 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} />

+ 7 - 6
inspector/src/components/actionTabs/tabs/propertyGrids/animations/animationPropertyGridComponent.tsx

@@ -181,7 +181,7 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                     </LineContainerComponent>
                 }
                 {
-                    animations && animations.length > 0 &&
+                    animations && 
                     <>
                         <LineContainerComponent globalState={this.props.globalState} title="ANIMATIONS">
                             <TextLineComponent label="Count" value={animations.length.toString()} />
@@ -207,14 +207,14 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                                         title="Animations Curve Editor" 
                                         scene={this.props.scene} 
                                         entity={animatableAsAny} 
-                                        entityName={animatableAsAny.id} 
                                         close={(event) => this.onCloseAnimationCurveEditor(event.view)} 
-                                        playOrPause={() => this.playOrPause()}
-                                        animations={animations} />
+                                        playOrPause={() => this.playOrPause()} />
                                 </PopupComponent>
                             }
                         </LineContainerComponent>
-                        <LineContainerComponent globalState={this.props.globalState} title="ANIMATION GENERAL CONTROL">
+                        {
+                            animations.length > 0 &&
+                            <LineContainerComponent globalState={this.props.globalState} title="ANIMATION GENERAL CONTROL">
                             <FloatLineComponent lockObject={this.props.lockObject} isInteger={true} label="From" target={this._animationControl} propertyName="from" onChange={() => this.onChangeFromOrTo()} />
                             <FloatLineComponent lockObject={this.props.lockObject} isInteger={true} label="To" target={this._animationControl} propertyName="to" onChange={() => this.onChangeFromOrTo()} />
                             <CheckBoxLineComponent label="Loop" onSelect={value => this._animationControl.loop = value} isSelected={() => this._animationControl.loop} />
@@ -250,7 +250,8 @@ export class AnimationGridComponent extends React.Component<IAnimationGridCompon
                                 </>
                             }
                         </LineContainerComponent>
-                    </>
+                        }
+                        </>
                 }
             </div>
         );

+ 4 - 3
inspector/src/components/actionTabs/tabs/propertyGrids/animations/curveEditor.scss

@@ -121,7 +121,7 @@
                     display: flex;
                     justify-content: center;
                     align-items: center;
-                    width: 8em;
+                    width: 15em;
 
                     .input-frame input {
                         width: 3em;
@@ -152,13 +152,14 @@
                     p {
                         font-weight: bolder;
                         font-variant: all-small-caps;
+                        display: inline;
                     }
                     cursor: pointer;
                     &:before {
                         content: '';
                         display: inline-block;
-                        height: 1em;
-                        width: 1em;
+                        height: 0.7em;
+                        width: 0.7em;
                         background-size: contain;
                         background-repeat: no-repeat;
                         margin-right:0.5em; 

+ 7 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/animations/graphActionsBar.tsx

@@ -8,6 +8,10 @@ interface IGraphActionsBarProps {
    handleValueChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
    handleFrameChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
    flatTangent: () => void;
+   brokeTangents: () => void;
+   setLerpMode: () => void;
+   brokenMode: boolean;
+   lerpMode: boolean;
    currentValue: number;
    currentFrame: number;
 }
@@ -30,7 +34,9 @@ export class GraphActionsBar extends React.Component<IGraphActionsBarProps>{
                </div>
               <ButtonLineComponent label={"Add Keyframe"} onClick={this.props.addKeyframe} />
               <ButtonLineComponent label={"Remove Keyframe"} onClick={this.props.removeKeyframe} />
-              <ButtonLineComponent label={"Flat Tangent"} onClick={this.props.flatTangent} />
+              <ButtonLineComponent label={"Flat Tangents"} onClick={this.props.flatTangent} />
+              <ButtonLineComponent label={this.props.brokenMode ? "Broken Mode On" : "Broken Mode Off" } onClick={this.props.brokeTangents} />
+              <ButtonLineComponent label={this.props.lerpMode ? "Lerp On" : "lerp Off" } onClick={this.props.setLerpMode} />
            </div>
         )
     }

+ 16 - 4
inspector/src/components/actionTabs/tabs/propertyGrids/animations/keyframeSvgPoint.tsx

@@ -7,6 +7,9 @@ export interface IKeyframeSvgPoint {
     rightControlPoint: Vector2 | null;
     leftControlPoint: Vector2 | null;
     id: string;
+    selected: boolean;
+    isLeftActive: boolean;
+    isRightActive: boolean;
 }
 
 interface IKeyframeSvgPointProps {
@@ -14,6 +17,11 @@ interface IKeyframeSvgPointProps {
     leftControlPoint: Vector2 | null;
     rightControlPoint: Vector2 | null;
     id: string;
+    selected: boolean;
+    selectKeyframe: (id: string) => void;
+    selectedControlPoint: (type: string, id: string) => void;
+    isLeftActive: boolean;
+    isRightActive: boolean;
 }
 
 export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps>{ 
@@ -22,14 +30,18 @@ export class KeyframeSvgPoint extends React.Component<IKeyframeSvgPointProps>{
         super(props);
     }
 
+    select(){
+        this.props.selectKeyframe(this.props.id);
+    }
+
     render() {
         return (
             <>
-                <svg className="draggable" x={this.props.keyframePoint.x} y={this.props.keyframePoint.y} style={{overflow:'visible'}}>
-                    <circle data-id={this.props.id} className="draggable" cx="0" cy="0"  r="2" stroke="none" strokeWidth="0" fill="red" />
+                <svg className="draggable" x={this.props.keyframePoint.x} y={this.props.keyframePoint.y} style={{overflow:'visible', cursor: 'pointer'}} >
+                    <circle data-id={this.props.id} className="draggable" cx="0" cy="0"  r="2" stroke="none" strokeWidth="0" fill={this.props.selected ? "red" : "black"} onClick={() => this.select()}/>
                 </svg>
-               { this.props.leftControlPoint && <AnchorSvgPoint type="left" index={this.props.id} control={this.props.leftControlPoint} anchor={this.props.keyframePoint} active={false} selected={false}/>} 
-               { this.props.rightControlPoint &&  <AnchorSvgPoint type="right" index={this.props.id} control={this.props.rightControlPoint} anchor={this.props.keyframePoint} active={false} selected={false}/>}
+               { this.props.leftControlPoint && <AnchorSvgPoint type="left" index={this.props.id} control={this.props.leftControlPoint} anchor={this.props.keyframePoint} active={this.props.selected} selected={this.props.isLeftActive} selectControlPoint={(type: string) => this.props.selectedControlPoint(type, this.props.id)}/>} 
+               { this.props.rightControlPoint &&  <AnchorSvgPoint type="right" index={this.props.id} control={this.props.rightControlPoint} anchor={this.props.keyframePoint} active={this.props.selected} selected={this.props.isRightActive} selectControlPoint={(type: string) => this.props.selectedControlPoint(type, this.props.id)}/>}
             </>
         )
     }

+ 16 - 5
inspector/src/components/actionTabs/tabs/propertyGrids/animations/svgDraggableArea.tsx

@@ -7,6 +7,8 @@ interface ISvgDraggableAreaProps {
     updatePosition: (updatedKeyframe: IKeyframeSvgPoint, index: number) => void;
     scale: number;
     viewBoxScale: number;
+    selectKeyframe: (id: string) => void;
+    selectedControlPoint: (type: string, id: string) => void;
 }
 
 export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps>{
@@ -35,8 +37,7 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps>{
             this._width = this._draggableArea.current?.clientWidth !== undefined ? this._draggableArea.current?.clientWidth : 0;
             console.log(this._width);
         }, 500);
-        
-    }  
+    }
 
     dragStart(e: React.TouchEvent<SVGSVGElement>): void;
     dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
@@ -70,7 +71,7 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps>{
             if (coord !== undefined) {
 
                 var newPoints = [...this.props.keyframeSvgPoints];
-
+                // Check for NaN values here. 
                 if (this._isCurrentPointControl === "left") {
                     newPoints[this._currentPointIndex].leftControlPoint = coord;
                 } else if (this._isCurrentPointControl === "right") {
@@ -187,7 +188,7 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps>{
     render() {
         return (
             <>
-                <svg className="linear pannable" ref={this._draggableArea}  tabIndex={0} 
+                <svg className="linear pannable" ref={this._draggableArea} tabIndex={0}
 
                     onMouseMove={(e) => this.drag(e)}
                     onTouchMove={(e) => this.drag(e)}
@@ -204,7 +205,17 @@ export class SvgDraggableArea extends React.Component<ISvgDraggableAreaProps>{
 
                     {this.props.children}
                     {this.props.keyframeSvgPoints.map((keyframe, i) =>
-                        <KeyframeSvgPoint key={i} id={i.toString()} keyframePoint={keyframe.keyframePoint} leftControlPoint={keyframe.leftControlPoint} rightControlPoint={keyframe.rightControlPoint} />
+                        <KeyframeSvgPoint
+                            key={i}
+                            id={i.toString()}
+                            keyframePoint={keyframe.keyframePoint} 
+                            leftControlPoint={keyframe.leftControlPoint} 
+                            rightControlPoint={keyframe.rightControlPoint}
+                            isLeftActive={keyframe.isLeftActive}
+                            isRightActive={keyframe.isRightActive}
+                            selected={keyframe.selected} 
+                            selectedControlPoint={(type: string, id: string) => this.props.selectedControlPoint(type, id)}
+                            selectKeyframe={(id: string) => this.props.selectKeyframe(id)} />
                     )}
                 </svg>
             </>)

+ 113 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/animations/targetedAnimationPropertyGridComponent.tsx

@@ -0,0 +1,113 @@
+import * as React from "react";
+
+import { Observable } from "babylonjs/Misc/observable";
+import { TargetedAnimation } from "babylonjs/Animations/animationGroup";
+import { Scene } from "babylonjs/scene";
+
+import { PropertyChangedEvent } from "../../../../propertyChangedEvent";
+import { ButtonLineComponent } from "../../../lines/buttonLineComponent";
+import { LineContainerComponent } from "../../../lineContainerComponent";
+import { TextLineComponent } from "../../../lines/textLineComponent";
+import { LockObject } from "../lockObject";
+import { GlobalState } from '../../../../globalState';
+import { TextInputLineComponent } from '../../../lines/textInputLineComponent';
+import { PopupComponent } from '../animations/popupComponent';
+import { AnimationCurveEditorComponent } from '../animations/animationCurveEditorComponent';
+import { AnimationGroup } from "babylonjs/Animations/animationGroup";
+
+interface ITargetedAnimationGridComponentProps {
+    globalState: GlobalState;
+    targetedAnimation: TargetedAnimation,
+    scene: Scene,
+    lockObject: LockObject,    
+    onSelectionChangedObservable?: Observable<any>,
+    onPropertyChangedObservable?: Observable<PropertyChangedEvent>
+}
+
+export class TargetedAnimationGridComponent extends React.Component<ITargetedAnimationGridComponentProps> {
+
+    private _isCurveEditorOpen: boolean;
+    private _animationGroup: AnimationGroup | undefined;
+    constructor(props: ITargetedAnimationGridComponentProps) {
+        super(props);
+        this._animationGroup = this.props.scene.animationGroups.find(ag => {
+            let ta = ag.targetedAnimations.find(ta => ta === this.props.targetedAnimation);  
+            return ta !== undefined;
+        });
+    }
+
+    onOpenAnimationCurveEditor() {
+        this._isCurveEditorOpen = true;
+    }
+
+    onCloseAnimationCurveEditor(window: Window | null) {
+        this._isCurveEditorOpen = false;
+        if (window === null) {
+            console.log("Window already closed");
+        } else {
+            window.close();
+        }
+    }
+
+    playOrPause() {
+        if (this._animationGroup){
+            if (this._animationGroup.isPlaying) {
+                this._animationGroup.stop();
+            } else {
+                this._animationGroup.start();
+            }
+            this.forceUpdate();
+        } 
+    }
+
+    deleteAnimation() {        
+        if (this._animationGroup) {
+            let index = this._animationGroup.targetedAnimations.indexOf(this.props.targetedAnimation);
+
+            if (index > -1) {
+                this._animationGroup.targetedAnimations.splice(index, 1);
+                this.props.onSelectionChangedObservable?.notifyObservers(null);
+
+                if (this._animationGroup.isPlaying) {
+                    this._animationGroup.stop();
+                    this._animationGroup.start();
+                }
+            }
+        }
+    }
+
+    render() {
+        const targetedAnimation = this.props.targetedAnimation;
+
+        return (
+            <div className="pane">
+                <LineContainerComponent globalState={this.props.globalState} title="GENERAL">
+                    <TextLineComponent label="Class" value={targetedAnimation.getClassName()} />
+                    <TextInputLineComponent lockObject={this.props.lockObject} label="Name" target={targetedAnimation.animation} propertyName="name" onPropertyChangedObservable={this.props.onPropertyChangedObservable}/>
+                    {
+                        targetedAnimation.target.name &&
+                        <TextLineComponent label="Target" value={targetedAnimation.target.name} onLink={() => this.props.globalState.onSelectionChangedObservable.notifyObservers(targetedAnimation)}/>
+                    }
+                    <ButtonLineComponent label="Edit animation" onClick={() => this.onOpenAnimationCurveEditor()} />
+                    {
+                        this._isCurveEditorOpen && <PopupComponent
+                            id="curve-editor"
+                            title="Curve Animation Editor"
+                            size={{ width: 950, height: 540 }}
+                            onOpen={(window: Window) => { window.console.log("Window opened!!") }}
+                            onClose={(window: Window) => this.onCloseAnimationCurveEditor(window)}>
+
+                            <AnimationCurveEditorComponent 
+                                title="Animations Curve Editor" 
+                                scene={this.props.scene} 
+                                entity={targetedAnimation as any} 
+                                playOrPause={() => this.playOrPause()}
+                                close={(event) => this.onCloseAnimationCurveEditor(event.view)} />
+                        </PopupComponent>
+                    }                    
+                    <ButtonLineComponent label="Dispose" onClick={() => this.deleteAnimation()} />
+                </LineContainerComponent>
+            </div>
+        );
+    }
+}

+ 115 - 25
inspector/src/components/actionTabs/tabs/propertyGrids/animations/timeline.tsx

@@ -2,23 +2,44 @@
 import * as React from "react";
 import { IAnimationKey } from 'babylonjs/Animations/animationKey';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faCaretRight, faCaretLeft, faStepBackward, faStepForward } from "@fortawesome/free-solid-svg-icons";
+import { faCaretRight, faCaretLeft, faStepBackward, faStepForward, faPlay, faPause } from "@fortawesome/free-solid-svg-icons";
 
 interface ITimelineProps {
-    keyframes: IAnimationKey[];
-    selected: IAnimationKey;
+    keyframes: IAnimationKey[] | null;
+    selected: IAnimationKey | null;
     currentFrame: number;
     onCurrentFrameChange: (frame: number) => void;
+    dragKeyframe: (frame: number, index: number) => void;
+    playPause: (direction: number) => void;
+    isPlaying: boolean;
 }
 
 
-export class Timeline extends React.Component<ITimelineProps, { selected: IAnimationKey }>{
+export class Timeline extends React.Component<ITimelineProps, { selected: IAnimationKey, activeKeyframe: number | null }>{
     readonly _frames: object[] = Array(300).fill({});
     private _scrollable: React.RefObject<HTMLDivElement>;
+    private _direction: number;
     constructor(props: ITimelineProps) {
         super(props);
-        this.state = { selected: this.props.selected };
+        if (this.props.selected !== null) {
+            this.state = { selected: this.props.selected, activeKeyframe: null };
+        }
         this._scrollable = React.createRef();
+        this._direction = 0;
+    }
+
+    playBackwards(event: React.MouseEvent<HTMLDivElement>) {
+        this.props.playPause(-1);
+    }
+
+    play(event: React.MouseEvent<HTMLDivElement>) {
+        this.props.playPause(1);
+    }
+
+    pause(event: React.MouseEvent<HTMLDivElement>) {
+        if (this.props.isPlaying) {
+            this.props.playPause(1);
+        }
     }
 
     handleInputChange(event: React.ChangeEvent<HTMLInputElement>) {
@@ -42,43 +63,105 @@ export class Timeline extends React.Component<ITimelineProps, { selected: IAnima
 
     nextKeyframe(event: React.MouseEvent<HTMLDivElement>) {
         event.preventDefault();
-        let first = this.props.keyframes.find(kf => kf.frame > this.props.currentFrame);
-        if (first) {
-            this.props.onCurrentFrameChange(first.frame);
-            this.setState({ selected: first });
-            (this._scrollable.current as HTMLDivElement).scrollLeft = first.frame * 5;
+        if (this.props.keyframes !== null) {
+            let first = this.props.keyframes.find(kf => kf.frame > this.props.currentFrame);
+            if (first) {
+                this.props.onCurrentFrameChange(first.frame);
+                this.setState({ selected: first });
+                (this._scrollable.current as HTMLDivElement).scrollLeft = first.frame * 5;
+            }
         }
     }
 
     previousKeyframe(event: React.MouseEvent<HTMLDivElement>) {
         event.preventDefault();
-        let first = this.props.keyframes.find(kf => kf.frame < this.props.currentFrame);
-        if (first) {
-            this.props.onCurrentFrameChange(first.frame);
-            this.setState({ selected: first });
-            (this._scrollable.current as HTMLDivElement).scrollLeft = -(first.frame * 5);
+        if (this.props.keyframes !== null) {
+            let keyframes = [...this.props.keyframes]
+            let first = keyframes.reverse().find(kf => kf.frame < this.props.currentFrame);
+            if (first) {
+                this.props.onCurrentFrameChange(first.frame);
+                this.setState({ selected: first });
+                (this._scrollable.current as HTMLDivElement).scrollLeft = -(first.frame * 5);
+            }
+        }
+    }
+
+    dragStart(e: React.TouchEvent<SVGSVGElement>): void;
+    dragStart(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+    dragStart(e: any): void {
+        e.preventDefault();
+        this.setState({ activeKeyframe: parseInt(e.target.id.replace('kf_', '')) });
+        this._direction = e.clientX;
+
+    }
+
+    drag(e: React.TouchEvent<SVGSVGElement>): void;
+    drag(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+    drag(e: any): void {
+        e.preventDefault();
+        if (this.props.keyframes) {
+            if (this.state.activeKeyframe === parseInt(e.target.id.replace('kf_', ''))) {
+                let updatedKeyframe = this.props.keyframes[this.state.activeKeyframe];
+                if (this._direction > e.clientX) {
+                    console.log(`Dragging left ${this.state.activeKeyframe}`);
+                    let used = this.isFrameBeingUsed(updatedKeyframe.frame - 1, -1);
+                    if (used) {
+                        updatedKeyframe.frame = used
+                    }
+                } else {
+                    console.log(`Dragging Right ${this.state.activeKeyframe}`)
+                    let used = this.isFrameBeingUsed(updatedKeyframe.frame + 1, 1);
+                    if (used) {
+                        updatedKeyframe.frame = used
+                    }
+                }
+
+                this.props.dragKeyframe(updatedKeyframe.frame, this.state.activeKeyframe);
+
+            }
         }
     }
 
+    isFrameBeingUsed(frame: number, direction: number) {
+        let used = this.props.keyframes?.find(kf => kf.frame === frame);
+        if (used) {
+            this.isFrameBeingUsed(used.frame + direction, direction);
+            return false;
+        } else {
+            return frame;
+        }
+    }
+
+    dragEnd(e: React.TouchEvent<SVGSVGElement>): void;
+    dragEnd(e: React.MouseEvent<SVGSVGElement, MouseEvent>): void;
+    dragEnd(e: any): void {
+        e.preventDefault();
+        this._direction = 0;
+        this.setState({ activeKeyframe: null })
+    }
+
     render() {
         return (
             <>
                 <div className="timeline">
-                    <div ref={this._scrollable} className="display-line">
-                        <svg viewBox="0 0 2010 100" style={{ width: 2000 }}>
+                    <div ref={this._scrollable} className="display-line" >
+                        <svg viewBox="0 0 2010 100" style={{ width: 2000 }} onMouseMove={(e) => this.drag(e)}
+                            onTouchMove={(e) => this.drag(e)}
+                            onTouchStart={(e) => this.dragStart(e)}
+                            onTouchEnd={(e) => this.dragEnd(e)}
+                            onMouseDown={(e) => this.dragStart(e)}
+                            onMouseUp={(e) => this.dragEnd(e)}
+                            onMouseLeave={(e) => this.dragEnd(e)}>
 
                             <line x1={this.props.currentFrame * 10} y1="10" x2={this.props.currentFrame * 10} y2="20" style={{ stroke: '#12506b', strokeWidth: 6 }} />
-
                             {
-                                this.props.keyframes.map((kf, i) => {
+                                this.props.keyframes && this.props.keyframes.map((kf, i) => {
 
-                                    return <svg key={`kf_${i}`}>
-                                        <line x1={kf.frame * 10} y1="10" x2={kf.frame * 10} y2="20" style={{ stroke: 'red', strokeWidth: 6 }} />
+                                    return <svg key={`kf_${i}`} style={{ cursor: 'pointer' }} tabIndex={i + 40} >
+                                        <line id={`kf_${i.toString()}`} x1={kf.frame * 10} y1="10" x2={kf.frame * 10} y2="20" style={{ stroke: 'red', strokeWidth: 6 }} />
                                     </svg>
-
                                 })
                             }
-
                             {
                                 this._frames.map((frame, i) => {
 
@@ -86,10 +169,8 @@ export class Timeline extends React.Component<ITimelineProps, { selected: IAnima
                                         {i % 10 === 0 ? <text x={(i * 10) - 3} y="8" style={{ fontSize: 10 }}>{i}</text> : null}
                                         <line x1={i * 10} y1="10" x2={i * 10} y2="20" style={{ stroke: 'black', strokeWidth: 0.5 }} />
                                     </svg>
-
                                 })
                             }
-
                         </svg>
                     </div>
                     <div className="controls">
@@ -102,6 +183,15 @@ export class Timeline extends React.Component<ITimelineProps, { selected: IAnima
                         <div className="previous-key-frame button" onClick={(e) => this.previousKeyframe(e)}>
                             <FontAwesomeIcon icon={faStepBackward} />
                         </div>
+                        <div className="previous-key-frame button" onClick={(e) => this.playBackwards(e)}>
+                            <FontAwesomeIcon icon={faPlay} style={{ transform: 'rotate(180deg)' }} />
+                        </div>
+                        <div className="previous-key-frame button" onClick={(e) => this.pause(e)}>
+                            <FontAwesomeIcon icon={faPause} />
+                        </div>
+                        <div className="previous-key-frame button" onClick={(e) => this.play(e)}>
+                            <FontAwesomeIcon icon={faPlay} />
+                        </div>
                         <div className="next-key-frame button" onClick={(e) => this.nextKeyframe(e)}>
                             <FontAwesomeIcon icon={faStepForward} />
                         </div>

+ 2 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/scenePropertyGridComponent.tsx

@@ -24,6 +24,7 @@ import { OptionsLineComponent } from "../../lines/optionsLineComponent";
 import { LockObject } from "./lockObject";
 import { GlobalState } from '../../../globalState';
 import { ButtonLineComponent } from '../../lines/buttonLineComponent';
+import { AnimationGridComponent } from './animations/animationPropertyGridComponent';
 
 interface IScenePropertyGridComponentProps {
     globalState: GlobalState;
@@ -158,6 +159,7 @@ export class ScenePropertyGridComponent extends React.Component<IScenePropertyGr
                     <SliderLineComponent minimum={0} maximum={2} step={0.01} label="IBL Intensity" target={scene} propertyName="environmentIntensity" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <FogPropertyGridComponent globalState={this.props.globalState} lockObject={this.props.lockObject} scene={scene} onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                 </LineContainerComponent>
+                <AnimationGridComponent globalState={this.props.globalState} animatable={scene} scene={scene} lockObject={this.props.lockObject} />
                 <LineContainerComponent globalState={this.props.globalState} title="MATERIAL IMAGE PROCESSING">
                     <SliderLineComponent minimum={0} maximum={4} step={0.1} label="Contrast" target={imageProcessing} propertyName="contrast" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />
                     <SliderLineComponent minimum={0} maximum={4} step={0.1} label="Exposure" target={imageProcessing} propertyName="exposure" onPropertyChangedObservable={this.props.onPropertyChangedObservable} />

+ 1 - 1
inspector/src/components/globalState.ts

@@ -31,7 +31,7 @@ export class GlobalState {
     public glTFLoaderDefaults: { [key: string]: any } = { "validate": true };
 
     public blockMutationUpdates = false;
-    public selectedLineContainerTitle = "";
+    public selectedLineContainerTitles:Array<string> = [];
 
     public recorder = new ReplayRecorder();
 

+ 2 - 2
inspector/src/components/sceneExplorer/entities/animationGroupTreeItemComponent.tsx

@@ -1,7 +1,7 @@
 import { AnimationGroup } from "babylonjs/Animations/animationGroup";
 import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
 
-import { faFilm } from '@fortawesome/free-solid-svg-icons';
+import { faLayerGroup } from '@fortawesome/free-solid-svg-icons';
 import { TreeItemLabelComponent } from "../treeItemLabelComponent";
 import { ExtensionsComponent } from "../extensionsComponent";
 import * as React from "react";
@@ -22,7 +22,7 @@ export class AnimationGroupItemComponent extends React.Component<IAnimationGroup
         const animationGroup = this.props.animationGroup;
         return (
             <div className="animationGroupTools">
-                <TreeItemLabelComponent label={animationGroup.name} onClick={() => this.props.onClick()} icon={faFilm} color="cornflowerblue" />
+                <TreeItemLabelComponent label={animationGroup.name} onClick={() => this.props.onClick()} icon={faLayerGroup} color="cornflowerblue" />
                 {
                     <ExtensionsComponent target={animationGroup} extensibilityGroups={this.props.extensibilityGroups} />
                 }

+ 32 - 0
inspector/src/components/sceneExplorer/entities/targetedAnimationTreeItemComponent.tsx

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

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

@@ -343,6 +343,20 @@
                 grid-column: 9;
             }
         }
+        
+        .targetedAnimationTools {
+            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;
+            }
+        }
 
         .animationGroupTools {
             grid-column: 2;

+ 3 - 2
inspector/src/components/sceneExplorer/treeItemSelectableComponent.tsx

@@ -92,10 +92,11 @@ export class TreeItemSelectableComponent extends React.Component<ITreeItemSelect
 
         const children = Tools.SortAndFilter(entity, entity.getChildren ? entity.getChildren() : entity.children);
         return (
-            children.map(item => {
+            children.map((item, i) => {
 
                 return (
-                    <TreeItemSelectableComponent globalState={this.props.globalState} mustExpand={this.props.mustExpand} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.props.selectedEntity} key={item.uniqueId} offset={this.props.offset + 2} entity={item} filter={this.props.filter} />
+                    <TreeItemSelectableComponent globalState={this.props.globalState} mustExpand={this.props.mustExpand} extensibilityGroups={this.props.extensibilityGroups} selectedEntity={this.props.selectedEntity} 
+                    key={i} offset={this.props.offset + 2} entity={item} filter={this.props.filter} />
                 );
             })
         )

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

@@ -1,6 +1,6 @@
 import { Camera } from "babylonjs/Cameras/camera";
 import { IExplorerExtensibilityGroup } from "babylonjs/Debug/debugLayer";
-import { AnimationGroup } from "babylonjs/Animations/animationGroup";
+import { AnimationGroup, TargetedAnimation } from "babylonjs/Animations/animationGroup";
 import { Material } from "babylonjs/Materials/material";
 import { Texture } from "babylonjs/Materials/Textures/texture";
 import { TransformNode } from "babylonjs/Meshes/transformNode";
@@ -36,6 +36,7 @@ import { SpriteManagerTreeItemComponent } from './entities/spriteManagerTreeItem
 import { SpriteManager } from 'babylonjs/Sprites/spriteManager';
 import { SpriteTreeItemComponent } from './entities/spriteTreeItemComponent';
 import { Sprite } from 'babylonjs/Sprites/sprite';
+import { TargetedAnimationItemComponent } from './entities/targetedAnimationTreeItemComponent';
 
 
 interface ITreeItemSpecializedComponentProps {
@@ -118,6 +119,10 @@ export class TreeItemSpecializedComponent extends React.Component<ITreeItemSpeci
                 return (<AnimationGroupItemComponent extensibilityGroups={this.props.extensibilityGroups} animationGroup={entity as AnimationGroup} onClick={() => this.onClick()} />);
             }
 
+            if (className === "TargetedAnimation") {
+                return (<TargetedAnimationItemComponent extensibilityGroups={this.props.extensibilityGroups} targetedAnimation={entity as TargetedAnimation} onClick={() => this.onClick()} />);
+            }
+
             if (className.indexOf("Texture") !== -1) {
                 return (<TextureTreeItemComponent extensibilityGroups={this.props.extensibilityGroups} texture={entity as Texture} onClick={() => this.onClick()} />);
             }

+ 7 - 1
inspector/src/inspector.ts

@@ -42,7 +42,13 @@ export class Inspector {
     private static _GlobalState = new GlobalState();
 
     public static MarkLineContainerTitleForHighlighting(title: string) {
-        this._GlobalState.selectedLineContainerTitle = title;
+        this._GlobalState.selectedLineContainerTitles = [];
+        this._GlobalState.selectedLineContainerTitles.push(title);
+    }
+
+    public static MarkMultipleLineContainerTitlesForHighlighting(titles: string[]) {
+        this._GlobalState.selectedLineContainerTitles = [];
+        this._GlobalState.selectedLineContainerTitles.push(...titles);
     }
 
     private static _CopyStyles(sourceDoc: HTMLDocument, targetDoc: HTMLDocument) {

+ 7 - 5
loaders/src/glTF/2.0/Extensions/KHR_materials_variants.ts

@@ -22,6 +22,9 @@ interface IKHRMaterialVariantsTop {
     default?: string;
 }
 
+/**
+ * Interface for the mapping from variant tag name to a mesh and material.
+ */
 interface VariantMapping {
     mesh: AbstractMesh;
     materialPromise: Promise<Nullable<Material>>;
@@ -50,11 +53,6 @@ export class KHR_materials_variants implements IGLTFLoaderExtension {
      */
     public defaultVariant: string | undefined;
 
-    /**
-     * A list of the available variant names in this asset.
-     */
-    public availableVariants: string[];
-
     private _tagsToMap: { [key: string]: VariantMapping[]; } = {};
 
     /** @hidden */
@@ -78,6 +76,8 @@ export class KHR_materials_variants implements IGLTFLoaderExtension {
 
     /**
      * Select a variant by providing a list of variant tag names.
+     *
+     * @param {(string | string[])} variantName
      */
     public selectVariant(variantName: string | string[]) {
         if (variantName instanceof Array) {
@@ -89,6 +89,8 @@ export class KHR_materials_variants implements IGLTFLoaderExtension {
 
     /**
      * Select a variant by providing a single variant tag.
+     *
+     * @param {string} variantName
      */
     public selectVariantTag(variantName: string) {
         // If the name is valid, switch all meshes to use materials defined by the tags

+ 11 - 0
nodeEditor/src/blockTools.ts

@@ -77,6 +77,8 @@ import { ParticleTextureBlock } from 'babylonjs/Materials/Node/Blocks/Particle/p
 import { ParticleRampGradientBlock } from 'babylonjs/Materials/Node/Blocks/Particle/particleRampGradientBlock';
 import { ParticleBlendMultiplyBlock } from 'babylonjs/Materials/Node/Blocks/Particle/particleBlendMultiplyBlock';
 import { NodeMaterialModes } from 'babylonjs/Materials/Node/Enums/nodeMaterialModes';
+import { FragCoordBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/fragCoordBlock';
+import { ScreenSizeBlock } from 'babylonjs/Materials/Node/Blocks/Fragment/screenSizeBlock';
 
 export class BlockTools {
     public static GetBlockFromString(data: string, scene: Scene, nodeMaterial: NodeMaterial) {
@@ -491,10 +493,19 @@ export class BlockTools {
                 u.setAsAttribute("particle_texturemask");
                 return u;
             }
+            case "ParticlePositionWorldBlock": {
+                let pos = new InputBlock("PositionWorld");
+                pos.setAsAttribute("particle_positionw");
+                return pos;
+            }
             case "ParticleRampGradientBlock":
                 return new ParticleRampGradientBlock("ParticleRampGradient");
             case "ParticleBlendMultiplyBlock":
                 return new ParticleBlendMultiplyBlock("ParticleBlendMultiply");
+            case "FragCoordBlock":
+                return new FragCoordBlock("FragCoord");
+            case "ScreenSizeBlock":
+                return new ScreenSizeBlock("ScreenSize");
         }
 
         return null;

+ 5 - 2
nodeEditor/src/components/nodeList/nodeListComponent.tsx

@@ -140,6 +140,9 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
         "ParticleTextureMaskBlock": "The particle texture mask",
         "ParticleRampGradientBlock": "The particle ramp gradient block",
         "ParticleBlendMultiplyBlock": "The particle blend multiply block",
+        "ParticlePositionWorldBlock": "The world position of the particle",
+        "FragCoordBlock": "The gl_FragCoord predefined variable that contains the window relative coordinate (x, y, z, 1/w)",
+        "ScreenSizeBlock": "The size (in pixels) of the screen window",
     };
 
     constructor(props: INodeListComponentProps) {
@@ -167,7 +170,7 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
             Animation: ["BonesBlock", "MorphTargetsBlock"],
             Color_Management: ["ReplaceColorBlock", "PosterizeBlock", "GradientBlock", "DesaturateBlock"],
             Conversion_Blocks: ["ColorMergerBlock", "ColorSplitterBlock", "VectorMergerBlock", "VectorSplitterBlock"],
-            Inputs: ["Float", "Vector2", "Vector3", "Vector4", "Color3", "Color4", "TextureBlock", "ReflectionTextureBlock", "TimeBlock", "DeltaTimeBlock"],
+            Inputs: ["Float", "Vector2", "Vector3", "Vector4", "Color3", "Color4", "TextureBlock", "ReflectionTextureBlock", "TimeBlock", "DeltaTimeBlock", "FragCoordBlock", "ScreenSizeBlock"],
             Interpolation: ["LerpBlock", "StepBlock", "SmoothStepBlock", "NLerpBlock"],
             Math__Standard: ["AddBlock", "DivideBlock", "MaxBlock", "MinBlock", "ModBlock", "MultiplyBlock", "NegateBlock", "OneMinusBlock", "ReciprocalBlock", "ScaleBlock", "SignBlock", "SqrtBlock", "SubtractBlock"],
             Math__Scientific: ["AbsBlock", "ArcCosBlock", "ArcSinBlock", "ArcTanBlock", "ArcTan2Block", "CosBlock", "DegreesToRadiansBlock", "ExpBlock", "Exp2Block", "FractBlock", "LogBlock", "PowBlock", "RadiansToDegreesBlock", "SawToothWaveBlock", "SinBlock", "SquareWaveBlock", "TanBlock", "TriangleWaveBlock"],
@@ -176,7 +179,7 @@ export class NodeListComponent extends React.Component<INodeListComponentProps,
             Mesh: ["InstancesBlock", "PositionBlock", "UVBlock", "ColorBlock", "NormalBlock", "PerturbNormalBlock", "NormalBlendBlock" , "TangentBlock", "MatrixIndicesBlock", "MatrixWeightsBlock", "WorldPositionBlock", "WorldNormalBlock", "WorldTangentBlock", "FrontFacingBlock"],
             Noises: ["RandomNumberBlock", "SimplexPerlin3DBlock", "WorleyNoise3DBlock"],
             Output_Nodes: ["VertexOutputBlock", "FragmentOutputBlock", "DiscardBlock"],
-            Particle: ["ParticleBlendMultiplyBlock", "ParticleColorBlock", "ParticleRampGradientBlock", "ParticleTextureBlock", "ParticleTextureMaskBlock", "ParticleUVBlock"],
+            Particle: ["ParticleBlendMultiplyBlock", "ParticleColorBlock", "ParticlePositionWorldBlock", "ParticleRampGradientBlock", "ParticleTextureBlock", "ParticleTextureMaskBlock", "ParticleUVBlock"],
             PBR: ["PBRMetallicRoughnessBlock", "AmbientOcclusionBlock", "AnisotropyBlock", "ClearCoatBlock", "ReflectionBlock", "ReflectivityBlock", "RefractionBlock", "SheenBlock", "SubSurfaceBlock"],
             PostProcess: ["Position2DBlock", "CurrentScreenBlock"],
             Range: ["ClampBlock", "RemapBlock", "NormalizeBlock"],

+ 2 - 0
nodeEditor/src/diagram/display/inputDisplayManager.ts

@@ -14,6 +14,7 @@ const inputNameToAttributeValue: { [name: string] : string } = {
     "particle_uv" : "uv",
     "particle_color" : "color",
     "particle_texturemask": "textureMask",
+    "particle_positionw" : "positionW",
 };
 
 const inputNameToAttributeName: { [name: string] : string } = {
@@ -21,6 +22,7 @@ const inputNameToAttributeName: { [name: string] : string } = {
     "particle_uv" : "particle",
     "particle_color" : "particle",
     "particle_texturemask": "particle",
+    "particle_positionw": "particle",
 };
 
 export class InputDisplayManager implements IDisplayManager {

+ 1 - 1
readme.md

@@ -111,7 +111,7 @@ If you want to contribute, please read our [contribution guidelines](https://git
 - [Examples](https://doc.babylonjs.com/examples)
 
 ## Contributing
-Please see the [Contributing Guidelines](./contributing.md)
+Please see the [Contributing Guidelines](./contributing.md).
 
 ## Useful links
 

+ 68 - 0
src/Animations/animation.ts

@@ -13,6 +13,7 @@ import { AnimationEvent } from './animationEvent';
 import { Node } from "../node";
 import { IAnimatable } from './animatable.interface';
 import { Size } from '../Maths/math.size';
+import { WebRequest } from '../Misc/webRequest';
 
 declare type Animatable = import("./animatable").Animatable;
 declare type RuntimeAnimation = import("./runtimeAnimation").RuntimeAnimation;
@@ -43,6 +44,12 @@ export class Animation {
      */
     public static AllowMatrixDecomposeForInterpolation = true;
 
+    /** Define the Url to load snippets */
+    public static SnippetUrl = "https://snippet.babylonjs.com";
+
+    /** Snippet ID if the animation was created from the snippet server */
+    public snippetId: string;
+
     /**
      * Stores the key frames of the animation
      */
@@ -1210,6 +1217,67 @@ export class Animation {
     public static AppendSerializedAnimations(source: IAnimatable, destination: any): void {
         SerializationHelper.AppendSerializedAnimations(source, destination);
     }
+
+    /**
+     * Creates a new animation from a snippet saved in a remote file
+     * @param name defines the name of the animation to create (can be null or empty to use the one from the json data)
+     * @param url defines the url to load from
+     * @returns a promise that will resolve to the new animation
+     */
+    public static ParseFromFileAsync(name: Nullable<string>, url: string): Promise<Animation> {
+
+        return new Promise((resolve, reject) => {
+            var request = new WebRequest();
+            request.addEventListener("readystatechange", () => {
+                if (request.readyState == 4) {
+                    if (request.status == 200) {
+                        let serializationObject = JSON.parse(request.responseText);
+                        let output = this.Parse(serializationObject);
+
+                        if (name) {
+                            output.name = name;
+                        }
+
+                        resolve(output);
+                    } else {
+                        reject("Unable to load the animation");
+                    }
+                }
+            });
+
+            request.open("GET", url);
+            request.send();
+        });
+    }
+
+    /**
+     * Creates an animation from a snippet saved by the Inspector
+     * @param snippetId defines the snippet to load
+     * @returns a promise that will resolve to the new animation
+     */
+    public static CreateFromSnippetAsync(snippetId: string): Promise<Animation> {
+        return new Promise((resolve, reject) => {
+            var request = new WebRequest();
+            request.addEventListener("readystatechange", () => {
+                if (request.readyState == 4) {
+                    if (request.status == 200) {
+                        var snippet = JSON.parse(JSON.parse(request.responseText).jsonPayload);
+                        let serializationObject = JSON.parse(snippet.animation);
+                        let output = this.Parse(serializationObject);
+
+                        output.snippetId = snippetId;
+
+                        resolve(output);
+                    } else {
+                        reject("Unable to load the snippet " + snippetId);
+                    }
+                }
+            });
+
+            request.open("GET", this.SnippetUrl + "/" + snippetId.replace(/#/g, "/"));
+            request.send();
+        });
+    }
 }
 
 _TypeStore.RegisteredTypes["BABYLON.Animation"] = Animation;

+ 15 - 0
src/Animations/animationGroup.ts

@@ -23,6 +23,14 @@ export class TargetedAnimation {
     public target: any;
 
     /**
+     * Returns the string "TargetedAnimation"
+     * @returns "TargetedAnimation"
+     */
+    public getClassName(): string {
+        return "TargetedAnimation";
+    }
+
+    /**
      * Serialize the object
      * @returns the JSON object representing the current entity
      */
@@ -192,6 +200,13 @@ export class AnimationGroup implements IDisposable {
     }
 
     /**
+     * Gets the list of target animations
+     */
+    public get children() {
+        return this._targetedAnimations;
+    }
+
+    /**
      * Instantiates a new Animation Group.
      * This helps managing several animations at once.
      * @see http://doc.babylonjs.com/how_to/group

+ 7 - 2
src/Behaviors/Meshes/pointerDragBehavior.ts

@@ -8,7 +8,7 @@ import { Vector3 } from "../../Maths/math.vector";
 import { PointerInfo, PointerEventTypes } from "../../Events/pointerEvents";
 import { Ray } from "../../Culling/ray";
 import { PivotTools } from '../../Misc/pivotTools';
-
+import { ArcRotateCamera } from '../../Cameras/arcRotateCamera';
 import "../../Meshes/Builders/planeBuilder";
 
 /**
@@ -255,7 +255,12 @@ export class PointerDragBehavior implements Behavior<AbstractMesh> {
 
         // Reattach camera controls
         if (this.detachCameraControls && this._attachedElement && this._scene.activeCamera && !this._scene.activeCamera.leftCamera) {
-            this._scene.activeCamera.attachControl(this._attachedElement, this._scene.activeCamera.inputs ? this._scene.activeCamera.inputs.noPreventDefault : true);
+            if (this._scene.activeCamera.getClassName() === "ArcRotateCamera") {
+                const arcRotateCamera = this._scene.activeCamera as ArcRotateCamera;
+                arcRotateCamera.attachControl(this._attachedElement, arcRotateCamera.inputs ? arcRotateCamera.inputs.noPreventDefault : true, arcRotateCamera._useCtrlForPanning, arcRotateCamera._panningMouseButton);
+            } else {
+                this._scene.activeCamera.attachControl(this._attachedElement, this._scene.activeCamera.inputs ? this._scene.activeCamera.inputs.noPreventDefault : true);
+            }
         }
     }
 

+ 5 - 0
src/Cameras/targetCamera.ts

@@ -312,6 +312,11 @@ export class TargetCamera extends Camera {
 
         // Rotate
         if (needToRotate) {
+            //rotate, if quaternion is set and rotation was used
+            if (this.rotationQuaternion) {
+                this.rotationQuaternion.toEulerAnglesToRef(this.rotation);
+            }
+
             this.rotation.x += this.cameraRotation.x * directionMultiplier;
             this.rotation.y += this.cameraRotation.y * directionMultiplier;
 

+ 10 - 4
src/Debug/debugLayer.ts

@@ -220,14 +220,20 @@ export class DebugLayer {
     /**
      * Select a specific entity in the scene explorer and highlight a specific block in that entity property grid
      * @param entity defines the entity to select
-     * @param lineContainerTitle defines the specific block to highlight
+     * @param lineContainerTitles defines the specific blocks to highlight (could be a string or an array of strings)
      */
-    public select(entity: any, lineContainerTitle?: string) {
+    public select(entity: any, lineContainerTitles?: string | string[]) {
         if (this.BJSINSPECTOR) {
-            this.BJSINSPECTOR.Inspector.MarkLineContainerTitleForHighlighting(lineContainerTitle);
+
+            if (lineContainerTitles) {
+                if (Object.prototype.toString.call(lineContainerTitles) == '[object String]') {
+                    this.BJSINSPECTOR.Inspector.MarkLineContainerTitleForHighlighting(lineContainerTitles);
+                } else {
+                    this.BJSINSPECTOR.Inspector.MarkMultipleLineContainerTitlesForHighlighting(lineContainerTitles);
+                }
+            }
             this.BJSINSPECTOR.Inspector.OnSelectionChangeObservable.notifyObservers(entity);
         }
-
     }
 
     /** Get the inspector from bundle or global */

+ 1 - 1
src/DeviceInput/deviceInputSystem.ts

@@ -65,7 +65,7 @@ export class DeviceInputSystem implements IDisposable {
      */
     public static Create(engine: Engine): DeviceInputSystem {
         // If running in Babylon Native, then defer to the native input system, which has the same public contract
-        if (typeof _native.DeviceInputSystem !== 'undefined') {
+        if (typeof _native !== 'undefined' && _native.DeviceInputSystem) {
             return new _native.DeviceInputSystem(engine);
         }
 

+ 8 - 0
src/Engines/thinEngine.ts

@@ -106,6 +106,10 @@ export interface EngineOptions extends WebGLContextAttributes {
      * Defines that engine should compile shaders with high precision floats (if supported). True by default
      */
     useHighPrecisionFloats?: boolean;
+    /**
+     * Make the canvas XR Compatible for XR sessions
+     */
+    xrCompatible?: boolean;
 }
 
 /**
@@ -516,6 +520,10 @@ export class ThinEngine {
                 this.premultipliedAlpha = false;
             }
 
+            if (options.xrCompatible === undefined) {
+                options.xrCompatible = true;
+            }
+
             this._doNotHandleContextLost = options.doNotHandleContextLost ? true : false;
 
             // Exceptions

+ 9 - 7
src/Materials/Node/Blocks/Dual/textureBlock.ts

@@ -332,13 +332,15 @@ export class TextureBlock extends NodeMaterialBlock {
 
         state.compilationString += `${this._declareOutput(output, state)} = ${this._tempTextureRead}.${swizzle}${complement};\r\n`;
 
-        state.compilationString += `#ifdef ${this._linearDefineName}\r\n`;
-        state.compilationString += `${output.associatedVariableName} = toGammaSpace(${output.associatedVariableName});\r\n`;
-        state.compilationString += `#endif\r\n`;
-
-        state.compilationString += `#ifdef ${this._gammaDefineName}\r\n`;
-        state.compilationString += `${output.associatedVariableName} = toLinearSpace(${output.associatedVariableName});\r\n`;
-        state.compilationString += `#endif\r\n`;
+        if (swizzle !== 'a') { // no conversion if the output is "a" (alpha)
+            state.compilationString += `#ifdef ${this._linearDefineName}\r\n`;
+            state.compilationString += `${output.associatedVariableName} = toGammaSpace(${output.associatedVariableName});\r\n`;
+            state.compilationString += `#endif\r\n`;
+
+            state.compilationString += `#ifdef ${this._gammaDefineName}\r\n`;
+            state.compilationString += `${output.associatedVariableName} = toLinearSpace(${output.associatedVariableName});\r\n`;
+            state.compilationString += `#endif\r\n`;
+        }
     }
 
     protected _buildBlock(state: NodeMaterialBuildState) {

+ 110 - 0
src/Materials/Node/Blocks/Fragment/fragCoordBlock.ts

@@ -0,0 +1,110 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
+import { _TypeStore } from '../../../../Misc/typeStore';
+
+/**
+ * Block used to make gl_FragCoord available
+ */
+export class FragCoordBlock extends NodeMaterialBlock {
+    /**
+     * Creates a new FragCoordBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this.registerOutput("xy", NodeMaterialBlockConnectionPointTypes.Vector2, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("xyz", NodeMaterialBlockConnectionPointTypes.Vector3, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("xyzw", NodeMaterialBlockConnectionPointTypes.Vector4, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("x", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("y", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("z", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("w", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Fragment);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "FragCoordBlock";
+    }
+
+    /**
+     * Gets the xy component
+     */
+    public get xy(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    /**
+     * Gets the xyz component
+     */
+    public get xyz(): NodeMaterialConnectionPoint {
+        return this._outputs[1];
+    }
+
+    /**
+     * Gets the xyzw component
+     */
+    public get xyzw(): NodeMaterialConnectionPoint {
+        return this._outputs[2];
+    }
+
+    /**
+     * Gets the x component
+     */
+    public get x(): NodeMaterialConnectionPoint {
+        return this._outputs[3];
+    }
+
+    /**
+     * Gets the y component
+     */
+    public get y(): NodeMaterialConnectionPoint {
+        return this._outputs[4];
+    }
+
+    /**
+     * Gets the z component
+     */
+    public get z(): NodeMaterialConnectionPoint {
+        return this._outputs[5];
+    }
+
+    /**
+     * Gets the w component
+     */
+    public get output(): NodeMaterialConnectionPoint {
+        return this._outputs[6];
+    }
+
+    protected writeOutputs(state: NodeMaterialBuildState): string {
+        let code = "";
+
+        for (var output of this._outputs) {
+            if (output.hasEndpoints) {
+                code += `${this._declareOutput(output, state)} = gl_FragCoord.${output.name};\r\n`;
+            }
+        }
+
+        return code;
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        if (state.target === NodeMaterialBlockTargets.Vertex) {
+            throw "FragCoordBlock must only be used in a fragment shader";
+        }
+
+        state.compilationString += this.writeOutputs(state);
+
+        return this;
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.FragCoordBlock"] = FragCoordBlock;

+ 2 - 0
src/Materials/Node/Blocks/Fragment/index.ts

@@ -5,3 +5,5 @@ export * from "./perturbNormalBlock";
 export * from "./discardBlock";
 export * from "./frontFacingBlock";
 export * from "./derivativeBlock";
+export * from "./fragCoordBlock";
+export * from "./screenSizeBlock";

+ 98 - 0
src/Materials/Node/Blocks/Fragment/screenSizeBlock.ts

@@ -0,0 +1,98 @@
+import { NodeMaterialBlock } from '../../nodeMaterialBlock';
+import { NodeMaterialBlockConnectionPointTypes } from '../../Enums/nodeMaterialBlockConnectionPointTypes';
+import { NodeMaterialBuildState } from '../../nodeMaterialBuildState';
+import { NodeMaterialConnectionPoint } from '../../nodeMaterialBlockConnectionPoint';
+import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
+import { _TypeStore } from '../../../../Misc/typeStore';
+import { Effect } from '../../../effect';
+import { NodeMaterial } from '../../nodeMaterial';
+import { Mesh } from '../../../../Meshes/mesh';
+import { Scene } from '../../../../scene';
+
+/**
+ * Block used to get the screen sizes
+ */
+export class ScreenSizeBlock extends NodeMaterialBlock {
+    private _varName: string;
+    private _scene: Scene;
+
+    /**
+     * Creates a new ScreenSizeBlock
+     * @param name defines the block name
+     */
+    public constructor(name: string) {
+        super(name, NodeMaterialBlockTargets.Fragment);
+
+        this.registerOutput("xy", NodeMaterialBlockConnectionPointTypes.Vector2, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("x", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Fragment);
+        this.registerOutput("y", NodeMaterialBlockConnectionPointTypes.Float, NodeMaterialBlockTargets.Fragment);
+    }
+
+    /**
+     * Gets the current class name
+     * @returns the class name
+     */
+    public getClassName() {
+        return "ScreenSizeBlock";
+    }
+
+    /**
+     * Gets the xy component
+     */
+    public get xy(): NodeMaterialConnectionPoint {
+        return this._outputs[0];
+    }
+
+    /**
+     * Gets the x component
+     */
+    public get x(): NodeMaterialConnectionPoint {
+        return this._outputs[1];
+    }
+
+    /**
+     * Gets the y component
+     */
+    public get y(): NodeMaterialConnectionPoint {
+        return this._outputs[2];
+    }
+
+    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh) {
+        const engine = this._scene.getEngine();
+
+        effect.setFloat2(this._varName, engine.getRenderWidth(), engine.getRenderWidth());
+    }
+
+    protected writeOutputs(state: NodeMaterialBuildState, varName: string): string {
+        let code = "";
+
+        for (var output of this._outputs) {
+            if (output.hasEndpoints) {
+                code += `${this._declareOutput(output, state)} = ${varName}.${output.name};\r\n`;
+            }
+        }
+
+        return code;
+    }
+
+    protected _buildBlock(state: NodeMaterialBuildState) {
+        super._buildBlock(state);
+
+        this._scene = state.sharedData.scene;
+
+        if (state.target === NodeMaterialBlockTargets.Vertex) {
+            throw "ScreenSizeBlock must only be used in a fragment shader";
+        }
+
+        state.sharedData.bindableBlocks.push(this);
+
+        this._varName = state._getFreeVariableName("screenSize");
+        state._emitUniformFromString(this._varName, "vec2");
+
+        state.compilationString += this.writeOutputs(state, this._varName);
+
+        return this;
+    }
+}
+
+_TypeStore.RegisteredTypes["BABYLON.ScreenSizeBlock"] = ScreenSizeBlock;

+ 3 - 0
src/Materials/Node/Blocks/Input/inputBlock.ts

@@ -20,12 +20,14 @@ const remapAttributeName: { [name: string]: string }  = {
     "particle_uv": "vUV",
     "particle_color": "vColor",
     "particle_texturemask": "textureMask",
+    "particle_positionw": "vPositionW",
 };
 
 const attributeInFragmentOnly: { [name: string]: boolean }  = {
     "particle_uv": true,
     "particle_color": true,
     "particle_texturemask": true,
+    "particle_positionw": true,
 };
 
 const attributeAsUniform: { [name: string]: boolean }  = {
@@ -108,6 +110,7 @@ export class InputBlock extends NodeMaterialBlock {
                     case "position":
                     case "normal":
                     case "tangent":
+                    case "particle_positionw":
                         this._type = NodeMaterialBlockConnectionPointTypes.Vector3;
                         return this._type;
                     case "uv":

+ 3 - 12
src/Materials/Node/Blocks/PBR/clearCoatBlock.ts

@@ -42,7 +42,6 @@ export class ClearCoatBlock extends NodeMaterialBlock {
         this.registerInput("tintColor", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("tintAtDistance", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("tintThickness", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("tintTexture", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("worldTangent", NodeMaterialBlockConnectionPointTypes.Vector4, true);
 
         this.registerOutput("clearcoat", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
@@ -133,17 +132,10 @@ export class ClearCoatBlock extends NodeMaterialBlock {
     }
 
     /**
-     * Gets the tint texture input component
-     */
-    public get tintTexture(): NodeMaterialConnectionPoint {
-        return this._inputs[9];
-    }
-
-    /**
      * Gets the world tangent input component
      */
     public get worldTangent(): NodeMaterialConnectionPoint {
-        return this._inputs[10];
+        return this._inputs[9];
     }
 
     /**
@@ -166,8 +158,7 @@ export class ClearCoatBlock extends NodeMaterialBlock {
 
         defines.setValue("CLEARCOAT", true);
         defines.setValue("CLEARCOAT_TEXTURE", this.texture.isConnected, true);
-        defines.setValue("CLEARCOAT_TINT", this.tintColor.isConnected || this.tintThickness.isConnected || this.tintAtDistance.isConnected || this.tintTexture.isConnected, true);
-        defines.setValue("CLEARCOAT_TINT_TEXTURE", this.tintTexture.isConnected, true);
+        defines.setValue("CLEARCOAT_TINT", this.tintColor.isConnected || this.tintThickness.isConnected || this.tintAtDistance.isConnected, true);
         defines.setValue("CLEARCOAT_BUMP", this.bumpTexture.isConnected, true);
         defines.setValue("CLEARCOAT_DEFAULTIOR", this.ior.isConnected ? this.ior.connectInputBlock!.value === 1.5 : false, true);
     }
@@ -245,7 +236,7 @@ export class ClearCoatBlock extends NodeMaterialBlock {
         const tintColor = ccBlock?.tintColor.isConnected ? ccBlock.tintColor.associatedVariableName : "vec3(1.)";
         const tintThickness = ccBlock?.tintThickness.isConnected ? ccBlock.tintThickness.associatedVariableName : "1.";
         const tintAtDistance = ccBlock?.tintAtDistance.isConnected ? ccBlock.tintAtDistance.associatedVariableName : "1.";
-        const tintTexture = ccBlock?.tintTexture.isConnected ? ccBlock.tintTexture.associatedVariableName : "vec4(0.)";
+        const tintTexture = "vec4(0.)";
 
         if (ccBlock) {
             state._emitUniformFromString("vClearCoatRefractionParams", "vec4");

+ 58 - 27
src/Materials/Node/Blocks/PBR/pbrMetallicRoughnessBlock.ts

@@ -29,6 +29,7 @@ import { ClearCoatBlock } from './clearCoatBlock';
 import { SubSurfaceBlock } from './subSurfaceBlock';
 import { RefractionBlock } from './refractionBlock';
 import { PerturbNormalBlock } from '../Fragment/perturbNormalBlock';
+import { Constants } from '../../../../Engines/constants';
 
 const mapOutputToVariable: { [name: string] : [string, string] } = {
     "ambient":      ["finalAmbient", ""],
@@ -76,7 +77,6 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         this.registerInput("perturbedNormal", NodeMaterialBlockConnectionPointTypes.Vector4, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("cameraPosition", NodeMaterialBlockConnectionPointTypes.Vector3, false, NodeMaterialBlockTargets.Fragment);
         this.registerInput("baseColor", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("baseTexture", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("opacityTexture", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("ambientColor", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("reflectivity", NodeMaterialBlockConnectionPointTypes.Object, false, NodeMaterialBlockTargets.Fragment,
@@ -195,6 +195,22 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
     public enableSpecularAntiAliasing: boolean = false;
 
     /**
+     * Enables realtime filtering on the texture.
+     */
+    @editableInPropertyPage("Realtime filtering", PropertyTypeForEdition.Boolean, "RENDERING", { "notifiers": { "update": true }})
+    public realTimeFiltering: boolean = false;
+
+    /**
+     * Quality switch for realtime filtering
+     */
+    @editableInPropertyPage("Realtime filtering quality", PropertyTypeForEdition.List, "RENDERING", { "notifiers": { "update": true }, "options": [
+        { label: "Low", value: Constants.TEXTURE_FILTERING_QUALITY_LOW },
+        { label: "Medium", value: Constants.TEXTURE_FILTERING_QUALITY_MEDIUM },
+        { label: "High", value: Constants.TEXTURE_FILTERING_QUALITY_HIGH },
+    ]})
+    public realTimeFilteringQuality = Constants.TEXTURE_FILTERING_QUALITY_LOW;
+
+    /**
      * Defines if the material uses energy conservation.
      */
     @editableInPropertyPage("Energy Conservation", PropertyTypeForEdition.Boolean, "ADVANCED", { "notifiers": { "update": true }})
@@ -398,73 +414,66 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
     }
 
     /**
-     * Gets the base texture input component
-     */
-    public get baseTexture(): NodeMaterialConnectionPoint {
-        return this._inputs[5];
-    }
-
-    /**
      * Gets the opacity texture input component
      */
     public get opacityTexture(): NodeMaterialConnectionPoint {
-        return this._inputs[6];
+        return this._inputs[5];
     }
 
     /**
      * Gets the ambient color input component
      */
     public get ambientColor(): NodeMaterialConnectionPoint {
-        return this._inputs[7];
+        return this._inputs[6];
     }
 
     /**
      * Gets the reflectivity object parameters
      */
     public get reflectivity(): NodeMaterialConnectionPoint {
-        return this._inputs[8];
+        return this._inputs[7];
     }
 
     /**
      * Gets the ambient occlusion object parameters
      */
     public get ambientOcclusion(): NodeMaterialConnectionPoint {
-        return this._inputs[9];
+        return this._inputs[8];
     }
 
     /**
      * Gets the reflection object parameters
      */
     public get reflection(): NodeMaterialConnectionPoint {
-        return this._inputs[10];
+        return this._inputs[9];
     }
 
     /**
      * Gets the sheen object parameters
      */
     public get sheen(): NodeMaterialConnectionPoint {
-        return this._inputs[11];
+        return this._inputs[10];
     }
 
     /**
      * Gets the clear coat object parameters
      */
     public get clearcoat(): NodeMaterialConnectionPoint {
-        return this._inputs[12];
+        return this._inputs[11];
     }
 
     /**
      * Gets the sub surface object parameters
      */
     public get subsurface(): NodeMaterialConnectionPoint {
-        return this._inputs[13];
+        return this._inputs[12];
     }
 
     /**
      * Gets the anisotropy object parameters
      */
     public get anisotropy(): NodeMaterialConnectionPoint {
-        return this._inputs[14];
+        return this._inputs[13];
     }
 
     /**
@@ -580,7 +589,6 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         defines.setValue("LODBASEDMICROSFURACE", this._scene.getEngine().getCaps().textureLOD);
 
         // Albedo & Opacity
-        defines.setValue("ALBEDO", this.baseTexture.isConnected, true);
         defines.setValue("OPACITY", this.opacityTexture.isConnected, true);
 
         // Lighting & colors
@@ -606,6 +614,12 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         defines.setValue("RADIANCEOVERALPHA", this.useRadianceOverAlpha, true);
         defines.setValue("SPECULAROVERALPHA", this.useSpecularOverAlpha, true);
         defines.setValue("SPECULARAA", this._scene.getEngine().getCaps().standardDerivatives && this.enableSpecularAntiAliasing, true);
+        defines.setValue("REALTIME_FILTERING", this.realTimeFiltering, true);
+        defines.setValue("NUM_SAMPLES", "" + this.realTimeFilteringQuality, true);
+
+        if (this._scene.getEngine().webGLVersion > 1) {
+            defines.setValue("NUM_SAMPLES", this.realTimeFilteringQuality + "u", true);
+        }
 
         // Advanced
         defines.setValue("BRDF_V_HEIGHT_CORRELATED", true);
@@ -756,13 +770,12 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         let code = `albedoOpacityOutParams albedoOpacityOut;\r\n`;
 
         const albedoColor = this.baseColor.isConnected ? this.baseColor.associatedVariableName : "vec4(1., 1., 1., 1.)";
-        const albedoTexture = this.baseTexture.isConnected ? this.baseTexture.associatedVariableName : "";
         const opacityTexture = this.opacityTexture.isConnected ? this.opacityTexture.associatedVariableName : "";
 
         code += `albedoOpacityBlock(
                 ${albedoColor},
             #ifdef ALBEDO
-                ${albedoTexture},
+                vec4(1.),
                 vec2(1., 1.),
             #endif
             #ifdef OPACITY
@@ -834,6 +847,7 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         }
 
         state._emitFunctionFromInclude("helperFunctions", comments);
+        state._emitFunctionFromInclude("importanceSampling", comments);
         state._emitFunctionFromInclude("pbrHelperFunctions", comments);
         state._emitFunctionFromInclude("imageProcessingFunctions", comments);
 
@@ -851,6 +865,7 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
 
         state._emitFunctionFromInclude("pbrDirectLightingFalloffFunctions", comments);
         state._emitFunctionFromInclude("pbrBRDFFunctions", comments);
+        state._emitFunctionFromInclude("hdrFilteringFunctions", comments);
 
         state._emitFunctionFromInclude("pbrDirectLightingFunctions", comments, {
             replaceStrings: [
@@ -913,9 +928,18 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
             #else\r\n`;
 
         // _____________________________ Reflectivity _______________________________
+        const subsurfaceBlock = this.subsurface.isConnected ? this.subsurface.connectedPoint?.ownerBlock as SubSurfaceBlock : null;
+        const refractionBlock = this.subsurface.isConnected ? (this.subsurface.connectedPoint?.ownerBlock as SubSurfaceBlock).refraction.connectedPoint?.ownerBlock as RefractionBlock : null;
+
+        const reflectivityBlock = this.reflectivity.connectedPoint?.ownerBlock as Nullable<ReflectivityBlock> ?? null;
+
+        if (reflectivityBlock) {
+            reflectivityBlock.indexOfRefractionConnectionPoint = refractionBlock?.indexOfRefraction ?? null;
+        }
+
         const aoIntensity = aoBlock?.intensity.isConnected ? aoBlock.intensity.associatedVariableName : "1.";
 
-        state.compilationString += (this.reflectivity.connectedPoint?.ownerBlock as Nullable<ReflectivityBlock>)?.getCode(aoIntensity) ?? "";
+        state.compilationString += reflectivityBlock?.getCode(state, aoIntensity) ?? "";
 
         // _____________________________ Geometry info _________________________________
         state.compilationString += state._emitCodeFromInclude("pbrBlockGeometryInfo", comments, {
@@ -949,12 +973,16 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
                 { search: /REFLECTIONMAP_SKYBOX/g, replace: reflectionBlock?._defineSkyboxName ?? "REFLECTIONMAP_SKYBOX" },
                 { search: /LODINREFLECTIONALPHA/g, replace: reflectionBlock?._defineLODReflectionAlpha ?? "LODINREFLECTIONALPHA" },
                 { search: /LINEARSPECULARREFLECTION/g, replace: reflectionBlock?._defineLinearSpecularReflection ?? "LINEARSPECULARREFLECTION" },
+                { search: /vReflectionFilteringInfo/g, replace: reflectionBlock?._vReflectionFilteringInfoName ?? "vReflectionFilteringInfo" },
             ]
         });
 
         // ___________________ Compute Reflectance aka R0 F0 info _________________________
-        state.compilationString += state._emitCodeFromInclude("pbrBlockReflectance0", comments);
-
+        state.compilationString += state._emitCodeFromInclude("pbrBlockReflectance0", comments, {
+            replaceStrings: [
+                { search: /metallicReflectanceFactors/g, replace: reflectivityBlock?._vMetallicReflectanceFactorsName ?? "metallicReflectanceFactors" },
+            ]
+        });
         // ________________________________ Sheen ______________________________
         const sheenBlock = this.sheen.isConnected ? this.sheen.connectedPoint?.ownerBlock as SheenBlock : null;
 
@@ -1006,9 +1034,6 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         });
 
         // ___________________________________ SubSurface ______________________________________
-        const subsurfaceBlock = this.subsurface.isConnected ? this.subsurface.connectedPoint?.ownerBlock as SubSurfaceBlock : null;
-        const refractionBlock = this.subsurface.isConnected ? (this.subsurface.connectedPoint?.ownerBlock as SubSurfaceBlock).refraction.connectedPoint?.ownerBlock as RefractionBlock : null;
-
         state.compilationString += SubSurfaceBlock.GetCode(state, subsurfaceBlock, reflectionBlock, worldPosVarName);
 
         state._emitFunctionFromInclude("pbrBlockSubSurface", comments, {
@@ -1080,7 +1105,7 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
             replaceStrings: [
                 { search: /vNormalW/g, replace: this._vNormalWName },
                 { search: /vPositionW/g, replace: worldPosVarName },
-                { search: /albedoTexture\.rgb;/g, replace: this.baseTexture.associatedVariableName + ".rgb;\r\ngl_FragColor.rgb = toGammaSpace(gl_FragColor.rgb);\r\n" },
+                { search: /albedoTexture\.rgb;/g, replace: "vec3(1.);\r\ngl_FragColor.rgb = toGammaSpace(gl_FragColor.rgb);\r\n" },
                 { search: /opacityMap/g, replace: this.opacityTexture.associatedVariableName },
             ]
         });
@@ -1121,6 +1146,8 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         codeString += `${this._codeVariableName}.useRadianceOverAlpha = ${this.useRadianceOverAlpha};\r\n`;
         codeString += `${this._codeVariableName}.useSpecularOverAlpha = ${this.useSpecularOverAlpha};\r\n`;
         codeString += `${this._codeVariableName}.enableSpecularAntiAliasing = ${this.enableSpecularAntiAliasing};\r\n`;
+        codeString += `${this._codeVariableName}.realTimeFiltering = ${this.realTimeFiltering};\r\n`;
+        codeString += `${this._codeVariableName}.realTimeFilteringQuality = ${this.realTimeFilteringQuality};\r\n`;
         codeString += `${this._codeVariableName}.useEnergyConservation = ${this.useEnergyConservation};\r\n`;
         codeString += `${this._codeVariableName}.useRadianceOcclusion = ${this.useRadianceOcclusion};\r\n`;
         codeString += `${this._codeVariableName}.useHorizonOcclusion = ${this.useHorizonOcclusion};\r\n`;
@@ -1149,6 +1176,8 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         serializationObject.useRadianceOverAlpha = this.useRadianceOverAlpha;
         serializationObject.useSpecularOverAlpha = this.useSpecularOverAlpha;
         serializationObject.enableSpecularAntiAliasing = this.enableSpecularAntiAliasing;
+        serializationObject.realTimeFiltering = this.realTimeFiltering;
+        serializationObject.realTimeFilteringQuality = this.realTimeFilteringQuality;
         serializationObject.useEnergyConservation = this.useEnergyConservation;
         serializationObject.useRadianceOcclusion = this.useRadianceOcclusion;
         serializationObject.useHorizonOcclusion = this.useHorizonOcclusion;
@@ -1177,6 +1206,8 @@ export class PBRMetallicRoughnessBlock extends NodeMaterialBlock {
         this.useRadianceOverAlpha = serializationObject.useRadianceOverAlpha;
         this.useSpecularOverAlpha = serializationObject.useSpecularOverAlpha;
         this.enableSpecularAntiAliasing = serializationObject.enableSpecularAntiAliasing;
+        this.realTimeFiltering = !!serializationObject.realTimeFiltering;
+        this.realTimeFilteringQuality = serializationObject.realTimeFilteringQuality ?? Constants.TEXTURE_FILTERING_QUALITY_LOW;
         this.useEnergyConservation = serializationObject.useEnergyConservation;
         this.useRadianceOcclusion = serializationObject.useRadianceOcclusion;
         this.useHorizonOcclusion = serializationObject.useHorizonOcclusion;

+ 11 - 1
src/Materials/Node/Blocks/PBR/reflectionBlock.ts

@@ -15,6 +15,7 @@ import { SubMesh } from '../../../../Meshes/subMesh';
 import { Effect } from '../../../effect';
 import { editableInPropertyPage, PropertyTypeForEdition } from "../../nodeMaterialDecorator";
 import { Scene } from '../../../../scene';
+import { Scalar } from '../../../../Maths/math.scalar';
 
 /**
  * Block used to implement the reflection module of the PBR material
@@ -30,6 +31,8 @@ export class ReflectionBlock extends ReflectionTextureBaseBlock {
     public _vReflectionMicrosurfaceInfosName: string;
     /** @hidden */
     public _vReflectionInfosName: string;
+    /** @hidden */
+    public _vReflectionFilteringInfoName: string;
     private _scene: Scene;
 
     /**
@@ -211,7 +214,10 @@ export class ReflectionBlock extends ReflectionTextureBaseBlock {
             effect.setTexture(this._2DSamplerName, reflectionTexture);
         }
 
-        effect.setFloat3(this._vReflectionMicrosurfaceInfosName, reflectionTexture.getSize().width, reflectionTexture.lodGenerationScale, reflectionTexture.lodGenerationOffset);
+        const width = reflectionTexture.getSize().width;
+
+        effect.setFloat3(this._vReflectionMicrosurfaceInfosName, width, reflectionTexture.lodGenerationScale, reflectionTexture.lodGenerationOffset);
+        effect.setFloat2(this._vReflectionFilteringInfoName, width, Scalar.Log2(width));
 
         const defines = subMesh._materialDefines as  NodeMaterialDefines;
 
@@ -346,6 +352,10 @@ export class ReflectionBlock extends ReflectionTextureBaseBlock {
 
         this._vReflectionInfosName = state._getFreeVariableName("vReflectionInfos");
 
+        this._vReflectionFilteringInfoName = state._getFreeVariableName("vReflectionFilteringInfo");
+
+        state._emitUniformFromString(this._vReflectionFilteringInfoName, "vec2");
+
         code += `#ifdef REFLECTION
             vec2 ${this._vReflectionInfosName} = vec2(1., 0.);
 

+ 46 - 4
src/Materials/Node/Blocks/PBR/reflectivityBlock.ts

@@ -9,12 +9,31 @@ import { NodeMaterialBlockTargets } from '../../Enums/nodeMaterialBlockTargets';
 import { NodeMaterialConnectionPointCustomObject } from "../../nodeMaterialConnectionPointCustomObject";
 import { NodeMaterialConnectionPoint, NodeMaterialConnectionPointDirection } from '../../nodeMaterialBlockConnectionPoint';
 import { Scene } from '../../../../scene';
+import { Nullable } from '../../../../types';
+import { Color3 } from '../../../../Maths/math.color';
+import { TmpColors } from '../../../../Maths/math.color';
+import { Mesh } from '../../../../Meshes/mesh';
+import { SubMesh } from '../../../../Meshes/subMesh';
+import { Effect } from '../../../effect';
 
 /**
  * Block used to implement the reflectivity module of the PBR material
  */
 export class ReflectivityBlock extends NodeMaterialBlock {
 
+    private _metallicReflectanceColor: Color3 = Color3.White();
+    private _metallicF0Factor = 1;
+
+    /** @hidden */
+    public _vMetallicReflectanceFactorsName: string;
+
+    /**
+     * The property below is set by the main PBR block prior to calling methods of this class.
+    */
+
+    /** @hidden */
+    public indexOfRefractionConnectionPoint: Nullable<NodeMaterialConnectionPoint>;
+
     /**
      * Specifies if the metallic texture contains the ambient occlusion information in its red channel.
      */
@@ -103,24 +122,46 @@ export class ReflectivityBlock extends NodeMaterialBlock {
         return this._outputs[0];
     }
 
+    public bind(effect: Effect, nodeMaterial: NodeMaterial, mesh?: Mesh, subMesh?: SubMesh) {
+        super.bind(effect, nodeMaterial, mesh);
+
+        const outside_ior = 1; // consider air as clear coat and other layers would remap in the shader.
+        const ior = this.indexOfRefractionConnectionPoint?.connectInputBlock?.value ?? 1.5;
+
+        // We are here deriving our default reflectance from a common value for none metallic surface.
+        // Based of the schlick fresnel approximation model
+        // for dielectrics.
+        const f0 = Math.pow((ior - outside_ior) / (ior + outside_ior), 2);
+
+        // Tweak the default F0 and F90 based on our given setup
+        this._metallicReflectanceColor.scaleToRef(f0 * this._metallicF0Factor, TmpColors.Color3[0]);
+        const metallicF90 = this._metallicF0Factor;
+
+        effect.setColor4(this._vMetallicReflectanceFactorsName, TmpColors.Color3[0], metallicF90);
+    }
+
     /**
      * Gets the main code of the block (fragment side)
+     * @param state current state of the node material building
      * @param aoIntensityVarName name of the variable with the ambient occlusion intensity
      * @returns the shader code
      */
-    public getCode(aoIntensityVarName: string): string {
+    public getCode(state: NodeMaterialBuildState, aoIntensityVarName: string): string {
         const metalRoughTexture = this.texture.isConnected ? this.texture.connectedPoint?.associatedVariableName : null;
 
-        // note: metallic F0 factor = 0.04
+        this._vMetallicReflectanceFactorsName = state._getFreeVariableName("vMetallicReflectanceFactors");
+
+        state._emitUniformFromString(this._vMetallicReflectanceFactorsName, "vec4");
+
+        // note: metallic F0 factor = 1
         let code = `vec3 baseColor = surfaceAlbedo;
-            vec4 metallicReflectanceFactors = vec4(1.);
             reflectivityOutParams reflectivityOut;
 
             reflectivityBlock(
                 vec4(${this.metallic.associatedVariableName}, ${this.roughness.associatedVariableName}, 0., 0.),
             #ifdef METALLICWORKFLOW
                 surfaceAlbedo,
-                metallicReflectanceFactors,
+                ${this._vMetallicReflectanceFactorsName},
             #endif
             #ifdef REFLECTIVITY
                 vec3(0., 0., ${aoIntensityVarName}),
@@ -161,6 +202,7 @@ export class ReflectivityBlock extends NodeMaterialBlock {
     protected _buildBlock(state: NodeMaterialBuildState) {
         if (state.target === NodeMaterialBlockTargets.Fragment) {
             state.sharedData.blocksWithDefines.push(this);
+            state.sharedData.bindableBlocks.push(this);
         }
 
         return this;

+ 1 - 1
src/Materials/Node/Blocks/PBR/refractionBlock.ts

@@ -213,7 +213,7 @@ export class RefractionBlock extends NodeMaterialBlock {
             }
         }
 
-        const indexOfRefraction = this.indexOfRefraction.connectInputBlock?.value ?? 1.0;
+        const indexOfRefraction = this.indexOfRefraction.connectInputBlock?.value ?? 1.5;
 
         effect.setFloat4(this._vRefractionInfosName, refractionTexture.level, 1 / indexOfRefraction, depth, this.invertRefractionY ? -1 : 1);
 

+ 1 - 10
src/Materials/Node/Blocks/PBR/sheenBlock.ts

@@ -29,7 +29,6 @@ export class SheenBlock extends NodeMaterialBlock {
         this.registerInput("intensity", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("color", NodeMaterialBlockConnectionPointTypes.Color3, true, NodeMaterialBlockTargets.Fragment);
         this.registerInput("roughness", NodeMaterialBlockConnectionPointTypes.Float, true, NodeMaterialBlockTargets.Fragment);
-        this.registerInput("texture", NodeMaterialBlockConnectionPointTypes.Color4, true, NodeMaterialBlockTargets.Fragment);
 
         this.registerOutput("sheen", NodeMaterialBlockConnectionPointTypes.Object, NodeMaterialBlockTargets.Fragment,
             new NodeMaterialConnectionPointCustomObject("sheen", this, NodeMaterialConnectionPointDirection.Output, SheenBlock, "SheenBlock"));
@@ -90,13 +89,6 @@ export class SheenBlock extends NodeMaterialBlock {
     }
 
     /**
-     * Gets the texture input component
-     */
-    public get texture(): NodeMaterialConnectionPoint {
-        return this._inputs[3];
-    }
-
-    /**
      * Gets the sheen object output component
      */
     public get sheen(): NodeMaterialConnectionPoint {
@@ -110,7 +102,6 @@ export class SheenBlock extends NodeMaterialBlock {
         defines.setValue("SHEEN_LINKWITHALBEDO", this.linkSheenWithAlbedo, true);
         defines.setValue("SHEEN_ROUGHNESS", this.roughness.isConnected, true);
         defines.setValue("SHEEN_ALBEDOSCALING", this.albedoScaling, true);
-        defines.setValue("SHEEN_TEXTURE", this.texture.isConnected, true);
     }
 
     /**
@@ -124,7 +115,7 @@ export class SheenBlock extends NodeMaterialBlock {
         const color = this.color.isConnected ? this.color.associatedVariableName : "vec3(1.)";
         const intensity = this.intensity.isConnected ? this.intensity.associatedVariableName : "1.";
         const roughness = this.roughness.isConnected ? this.roughness.associatedVariableName : "0.";
-        const texture = this.texture.isConnected ? this.texture.associatedVariableName : "vec4(0.)";
+        const texture = "vec4(0.)";
 
         code = `#ifdef SHEEN
             sheenOutParams sheenOut;

+ 73 - 0
src/Materials/shaderMaterial.ts

@@ -14,6 +14,8 @@ import { Material } from "./material";
 import { _TypeStore } from '../Misc/typeStore';
 import { Color3, Color4 } from '../Maths/math.color';
 import { EffectFallbacks } from './effectFallbacks';
+import { WebRequest } from '../Misc/webRequest';
+import { Engine } from '../Engines/engine';
 
 const onCreatedEffectParameters = { effect: null as unknown as Effect, subMesh: null as unknown as Nullable<SubMesh> };
 
@@ -92,6 +94,12 @@ export class ShaderMaterial extends Material {
     private _multiview: boolean = false;
     private _cachedDefines: string;
 
+    /** Define the Url to load snippets */
+    public static SnippetUrl = "https://snippet.babylonjs.com";
+
+    /** Snippet ID if the material was created from the snippet server */
+    public snippetId: string;
+
     /**
      * Instantiate a new shader material.
      * The ShaderMaterial object has the necessary methods to pass data from your scene to the Vertex and Fragment Shaders and returns a material that can be applied to any mesh.
@@ -1230,6 +1238,71 @@ export class ShaderMaterial extends Material {
 
         return material;
     }
+
+    /**
+     * Creates a new ShaderMaterial from a snippet saved in a remote file
+     * @param name defines the name of the ShaderMaterial to create (can be null or empty to use the one from the json data)
+     * @param url defines the url to load from
+     * @param scene defines the hosting scene
+     * @param rootUrl defines the root URL to use to load textures and relative dependencies
+     * @returns a promise that will resolve to the new ShaderMaterial
+     */
+    public static ParseFromFileAsync(name: Nullable<string>, url: string, scene: Scene, rootUrl: string = ""): Promise<ShaderMaterial> {
+
+        return new Promise((resolve, reject) => {
+            var request = new WebRequest();
+            request.addEventListener("readystatechange", () => {
+                if (request.readyState == 4) {
+                    if (request.status == 200) {
+                        let serializationObject = JSON.parse(request.responseText);
+                        let output = this.Parse(serializationObject, scene || Engine.LastCreatedScene, rootUrl);
+
+                        if (name) {
+                            output.name = name;
+                        }
+
+                        resolve(output);
+                    } else {
+                        reject("Unable to load the ShaderMaterial");
+                    }
+                }
+            });
+
+            request.open("GET", url);
+            request.send();
+        });
+    }
+
+    /**
+     * Creates a ShaderMaterial from a snippet saved by the Inspector
+     * @param snippetId defines the snippet to load
+     * @param scene defines the hosting scene
+     * @param rootUrl defines the root URL to use to load textures and relative dependencies
+     * @returns a promise that will resolve to the new ShaderMaterial
+     */
+    public static CreateFromSnippetAsync(snippetId: string, scene: Scene, rootUrl: string = ""): Promise<ShaderMaterial> {
+        return new Promise((resolve, reject) => {
+            var request = new WebRequest();
+            request.addEventListener("readystatechange", () => {
+                if (request.readyState == 4) {
+                    if (request.status == 200) {
+                        var snippet = JSON.parse(JSON.parse(request.responseText).jsonPayload);
+                        let serializationObject = JSON.parse(snippet.shaderMaterial);
+                        let output = this.Parse(serializationObject, scene || Engine.LastCreatedScene, rootUrl);
+
+                        output.snippetId = snippetId;
+
+                        resolve(output);
+                    } else {
+                        reject("Unable to load the snippet " + snippetId);
+                    }
+                }
+            });
+
+            request.open("GET", this.SnippetUrl + "/" + snippetId.replace(/#/g, "/"));
+            request.send();
+        });
+    }
 }
 
 _TypeStore.RegisteredTypes["BABYLON.ShaderMaterial"] = ShaderMaterial;

+ 13 - 1
src/Meshes/instancedMesh.ts

@@ -340,7 +340,12 @@ export class InstancedMesh extends AbstractMesh {
 
     /** @hidden */
     public _postActivate(): void {
-        if (this._edgesRenderer && this._edgesRenderer.isEnabled && this._sourceMesh._renderingGroup) {
+        if (this._sourceMesh.edgesShareWithInstances && this._sourceMesh._edgesRenderer && this._sourceMesh._edgesRenderer.isEnabled && this._sourceMesh._renderingGroup) {
+            // we are using the edge renderer of the source mesh
+            this._sourceMesh._renderingGroup._edgesRenderers.pushNoDuplicate(this._sourceMesh._edgesRenderer);
+            this._sourceMesh._edgesRenderer.customInstances.push(this.getWorldMatrix());
+        } else if (this._edgesRenderer && this._edgesRenderer.isEnabled && this._sourceMesh._renderingGroup) {
+            // we are using the edge renderer defined for this instance
             this._sourceMesh._renderingGroup._edgesRenderers.push(this._edgesRenderer);
         }
     }
@@ -470,6 +475,11 @@ declare module "./mesh" {
          */
         registerInstancedBuffer(kind: string, stride: number): void;
 
+        /**
+         * true to use the edge renderer for all instances of this mesh
+         */
+        edgesShareWithInstances: boolean;
+
         /** @hidden */
         _userInstancedBuffersStorage: {
             data: {[key: string]: Float32Array},
@@ -490,6 +500,8 @@ declare module "./abstractMesh" {
     }
 }
 
+Mesh.prototype.edgesShareWithInstances = false;
+
 Mesh.prototype.registerInstancedBuffer = function(kind: string, stride: number): void {
     // Remove existing one
     this.removeVerticesData(kind);

+ 8 - 0
src/Meshes/mesh.ts

@@ -1108,6 +1108,14 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
         }
     }
 
+    /** @hidden */
+    public _postActivate(): void {
+        if (this.edgesShareWithInstances && this.edgesRenderer && this.edgesRenderer.isEnabled && this._renderingGroup) {
+            this._renderingGroup._edgesRenderers.pushNoDuplicate(this.edgesRenderer);
+            this.edgesRenderer.customInstances.push(this.getWorldMatrix());
+        }
+    }
+
     /**
      * This method recomputes and sets a new BoundingInfo to the mesh unless it is locked.
      * This means the mesh underlying bounding box and sphere are recomputed.

+ 264 - 0
src/Misc/timer.ts

@@ -0,0 +1,264 @@
+import { Observable, Observer } from '../Misc/observable';
+import { Nullable } from '../types';
+import { IDisposable } from '../scene';
+
+/**
+ * Construction options for a timer
+ */
+export interface ITimerOptions<T> {
+    /**
+     * Time-to-end
+     */
+    timeout: number;
+    /**
+     * The context observable is used to calculate time deltas and provides the context of the timer's callbacks. Will usually be OnBeforeRenderObservable.
+     * Countdown calculation is done ONLY when the observable is notifying its observers, meaning that if
+     * you choose an observable that doesn't trigger too often, the wait time might extend further than the requested max time
+     */
+    contextObservable: Observable<T>;
+    /**
+     * Optional parameters when adding an observer to the observable
+     */
+    observableParameters?: {
+        mask?: number;
+        insertFirst?: boolean;
+        scope?: any;
+    };
+    /**
+     * An optional break condition that will stop the times prematurely. In this case onEnded will not be triggered!
+     */
+    breakCondition?: (data?: ITimerData<T>) => boolean;
+    /**
+     * Will be triggered when the time condition has met
+     */
+    onEnded?: (data: ITimerData<any>) => void;
+    /**
+     * Will be triggered when the break condition has met (prematurely ended)
+     */
+    onAborted?: (data: ITimerData<any>) => void;
+    /**
+     * Optional function to execute on each tick (or count)
+     */
+    onTick?: (data: ITimerData<any>) => void;
+}
+
+/**
+ * An interface defining the data sent by the timer
+ */
+export interface ITimerData<T> {
+    /**
+     * When did it start
+     */
+    startTime: number;
+    /**
+     * Time now
+     */
+    currentTime: number;
+    /**
+     * Time passed since started
+     */
+    deltaTime: number;
+    /**
+     * How much is completed, in [0.0...1.0].
+     * Note that this CAN be higher than 1 due to the fact that we don't actually measure time but delta between observable calls
+     */
+    completeRate: number;
+    /**
+     * What the registered observable sent in the last count
+     */
+    payload: T;
+}
+
+/**
+ * The current state of the timer
+ */
+export enum TimerState {
+    /**
+     * Timer initialized, not yet started
+     */
+    INIT,
+    /**
+     * Timer started and counting
+     */
+    STARTED,
+    /**
+     * Timer ended (whether aborted or time reached)
+     */
+    ENDED
+}
+
+/**
+ * A simple version of the timer. Will take options and start the timer immediately after calling it
+ *
+ * @param options options with which to initialize this timer
+ */
+export function setAndStartTimer(options: ITimerOptions<any>): Nullable<Observer<any>> {
+    let timer = 0;
+    const startTime = Date.now();
+    options.observableParameters = options.observableParameters ?? {};
+    const observer = options.contextObservable.add((payload: any) => {
+        const now = Date.now();
+        timer = now - startTime;
+        const data: ITimerData<any> = {
+            startTime,
+            currentTime: now,
+            deltaTime: timer,
+            completeRate: timer / options.timeout,
+            payload
+        };
+        options.onTick && options.onTick(data);
+        if (options.breakCondition && options.breakCondition()) {
+            options.contextObservable.remove(observer);
+            options.onAborted && options.onAborted(data);
+        }
+        if (timer >= options.timeout) {
+            options.contextObservable.remove(observer);
+            options.onEnded && options.onEnded(data);
+        }
+    }, options.observableParameters.mask, options.observableParameters.insertFirst, options.observableParameters.scope);
+    return observer;
+}
+
+/**
+ * An advanced implementation of a timer class
+ */
+export class AdvancedTimer<T = any> implements IDisposable {
+
+    /**
+     * Will notify each time the timer calculates the remaining time
+     */
+    public onEachCountObservable: Observable<ITimerData<T>> = new Observable();
+    /**
+     * Will trigger when the timer was aborted due to the break condition
+     */
+    public onTimerAbortedObservable: Observable<ITimerData<T>> = new Observable();
+    /**
+     * Will trigger when the timer ended successfully
+     */
+    public onTimerEndedObservable: Observable<ITimerData<T>> = new Observable();
+    /**
+     * Will trigger when the timer state has changed
+     */
+    public onStateChangedObservable: Observable<TimerState> = new Observable();
+
+    private _observer: Nullable<Observer<T>> = null;
+    private _contextObservable: Observable<T>;
+    private _observableParameters: {
+        mask?: number;
+        insertFirst?: boolean;
+        scope?: any;
+    };
+    private _startTime: number;
+    private _timer: number;
+    private _state: TimerState;
+    private _breakCondition: (data: ITimerData<T>) => boolean;
+    private _timeToEnd: number;
+    private _breakOnNextTick: boolean = false;
+
+    /**
+     * Will construct a new advanced timer based on the options provided. Timer will not start until start() is called.
+     * @param options construction options for this advanced timer
+     */
+    constructor(options: ITimerOptions<T>) {
+        this._setState(TimerState.INIT);
+        this._contextObservable = options.contextObservable;
+        this._observableParameters = options.observableParameters ?? {};
+        this._breakCondition = options.breakCondition ?? (() => false);
+        if (options.onEnded) {
+            this.onTimerEndedObservable.add(options.onEnded);
+        }
+        if (options.onTick) {
+            this.onEachCountObservable.add(options.onTick);
+        }
+        if (options.onAborted) {
+            this.onTimerAbortedObservable.add(options.onAborted);
+        }
+    }
+
+    /**
+     * set a breaking condition for this timer. Default is to never break during count
+     * @param predicate the new break condition. Returns true to break, false otherwise
+     */
+    public set breakCondition(predicate: (data: ITimerData<T>) => boolean) {
+        this._breakCondition = predicate;
+    }
+
+    /**
+     * Reset ALL associated observables in this advanced timer
+     */
+    public clearObservables() {
+        this.onEachCountObservable.clear();
+        this.onTimerAbortedObservable.clear();
+        this.onTimerEndedObservable.clear();
+        this.onStateChangedObservable.clear();
+    }
+
+    /**
+     * Will start a new iteration of this timer. Only one instance of this timer can run at a time.
+     *
+     * @param timeToEnd how much time to measure until timer ended
+     */
+    public start(timeToEnd: number = this._timeToEnd) {
+        if (this._state === TimerState.STARTED) {
+            throw new Error('Timer already started. Please stop it before starting again');
+        }
+        this._timeToEnd = timeToEnd;
+        this._startTime = Date.now();
+        this._timer = 0;
+        this._observer = this._contextObservable.add(this._tick, this._observableParameters.mask, this._observableParameters.insertFirst, this._observableParameters.scope);
+        this._setState(TimerState.STARTED);
+    }
+
+    /**
+     * Will force a stop on the next tick.
+     */
+    public stop() {
+        if (this._state !== TimerState.STARTED) {
+            return;
+        }
+        this._breakOnNextTick = true;
+    }
+
+    /**
+     * Dispose this timer, clearing all resources
+     */
+    public dispose() {
+        if (this._observer) {
+            this._contextObservable.remove(this._observer);
+        }
+        this.clearObservables();
+    }
+
+    private _setState(newState: TimerState) {
+        this._state = newState;
+        this.onStateChangedObservable.notifyObservers(this._state);
+    }
+
+    private _tick = (payload: T) => {
+        const now = Date.now();
+        this._timer = now - this._startTime;
+        const data: ITimerData<T> = {
+            startTime: this._startTime,
+            currentTime: now,
+            deltaTime: this._timer,
+            completeRate: this._timer / this._timeToEnd,
+            payload
+        };
+        const shouldBreak = this._breakOnNextTick || this._breakCondition(data);
+        if (shouldBreak || this._timer >= this._timeToEnd) {
+            this._stop(data, shouldBreak);
+        } else {
+            this.onEachCountObservable.notifyObservers(data);
+        }
+    }
+
+    private _stop(data: ITimerData<T>, aborted: boolean = false) {
+        this._contextObservable.remove(this._observer);
+        this._setState(TimerState.ENDED);
+        if (aborted) {
+            this.onTimerAbortedObservable.notifyObservers(data);
+        } else {
+            this.onTimerEndedObservable.notifyObservers(data);
+        }
+    }
+}

+ 4 - 2
src/Misc/tools.ts

@@ -292,8 +292,10 @@ export class Tools {
             eventPrefix = "mouse";
         }
 
-        // Special Fallback...
-        if (engine._badDesktopOS && !engine._badOS) {
+        // Special Fallback MacOS Safari...
+        if (engine._badDesktopOS && !engine._badOS &&
+            // And not ipad pros who claim to be macs...
+            !(document && 'ontouchend' in document)) {
             eventPrefix = "mouse";
         }
 

+ 9 - 2
src/Particles/gpuParticleSystem.ts

@@ -1196,6 +1196,8 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
                     defines.push("#define BILLBOARDSTRETCHED");
                     break;
                 case ParticleSystem.BILLBOARDMODE_ALL:
+                    defines.push("#define BILLBOARDMODE_ALL");
+                    break;
                 default:
                     break;
             }
@@ -1509,11 +1511,16 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
                 effect.setVector3("eyePosition", camera.globalPosition);
             }
 
+            const defines = effect.defines;
+
             if (this._scene.clipPlane || this._scene.clipPlane2 || this._scene.clipPlane3 || this._scene.clipPlane4 || this._scene.clipPlane5 || this._scene.clipPlane6) {
+                MaterialHelper.BindClipPlane(effect, this._scene);
+            }
+
+            if (defines.indexOf("#define BILLBOARDMODE_ALL") >= 0) {
                 var invView = viewMatrix.clone();
                 invView.invert();
                 effect.setMatrix("invView", invView);
-                MaterialHelper.BindClipPlane(effect, this._scene);
             }
 
             // image processing
@@ -1682,7 +1689,7 @@ export class GPUParticleSystem extends BaseParticleSystem implements IDisposable
         var result = new GPUParticleSystem(name, { capacity: this._capacity, randomTextureSize: this._randomTextureSize }, this._scene);
         result._customEffect = custom;
 
-        DeepCopier.DeepCopy(this, result, ["particles", "customShader", "noiseTexture", "particleTexture", "onDisposeObservable"]);
+        DeepCopier.DeepCopy(this, result, ["particles", "customShader", "noiseTexture", "particleTexture", "onDisposeObservable", "vertexShaderName"]);
 
         if (newEmitter === undefined) {
             newEmitter = this.emitter;

+ 9 - 2
src/Particles/particleSystem.ts

@@ -1656,6 +1656,8 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
                     defines.push("#define BILLBOARDSTRETCHED");
                     break;
                 case ParticleSystem.BILLBOARDMODE_ALL:
+                    defines.push("#define BILLBOARDMODE_ALL");
+                    break;
                 default:
                     break;
             }
@@ -1902,11 +1904,16 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
             effect.setTexture("rampSampler", this._rampGradientsTexture);
         }
 
+        const defines = effect.defines;
+
         if (this._scene.clipPlane || this._scene.clipPlane2 || this._scene.clipPlane3 || this._scene.clipPlane4 || this._scene.clipPlane5 || this._scene.clipPlane6) {
+            MaterialHelper.BindClipPlane(effect, this._scene);
+        }
+
+        if (defines.indexOf("#define BILLBOARDMODE_ALL") >= 0) {
             var invView = viewMatrix.clone();
             invView.invert();
             effect.setMatrix("invView", invView);
-            MaterialHelper.BindClipPlane(effect, this._scene);
         }
 
         engine.bindBuffers(this._vertexBuffers, this._indexBuffer, effect);
@@ -2065,7 +2072,7 @@ export class ParticleSystem extends BaseParticleSystem implements IDisposable, I
         result.customShader = program;
         result._customEffect = custom;
 
-        DeepCopier.DeepCopy(this, result, ["particles", "customShader", "noiseTexture", "particleTexture", "onDisposeObservable"]);
+        DeepCopier.DeepCopy(this, result, ["particles", "customShader", "noiseTexture", "particleTexture", "onDisposeObservable", "vertexShaderName"]);
 
         if (newEmitter === undefined) {
             newEmitter = this.emitter;

+ 3 - 1
src/Physics/Plugins/cannonJSPlugin.ts

@@ -59,7 +59,9 @@ export class CannonJSPlugin implements IPhysicsEnginePlugin {
         if (this._firstFrame) {
             this._firstFrame = false;
             for (const impostor of impostors) {
-                impostor.beforeStep();
+                if (!(impostor.type == PhysicsImpostor.HeightmapImpostor || impostor.type === PhysicsImpostor.PlaneImpostor)) {
+                    impostor.beforeStep();
+                }
             }
         }
         this.world.step(this._useDeltaForWorldStep ? delta : this._fixedTimeStep);

+ 79 - 13
src/Rendering/edgesRenderer.ts

@@ -1,9 +1,10 @@
 import { Nullable } from "../types";
 import { VertexBuffer } from "../Meshes/buffer";
 import { AbstractMesh } from "../Meshes/abstractMesh";
+import { Mesh } from "../Meshes/mesh";
 import { LinesMesh, InstancedLinesMesh } from "../Meshes/linesMesh";
-import { Vector3, TmpVectors } from "../Maths/math.vector";
-import { IDisposable } from "../scene";
+import { Vector3, TmpVectors, Matrix } from "../Maths/math.vector";
+import { IDisposable, Scene } from "../scene";
 import { Observer } from "../Misc/observable";
 import { Effect } from "../Materials/effect";
 import { Material } from "../Materials/material";
@@ -15,6 +16,14 @@ import { Node } from "../node";
 import "../Shaders/line.fragment";
 import "../Shaders/line.vertex";
 import { DataBuffer } from '../Meshes/dataBuffer';
+import { SmartArray } from '../Misc/smartArray';
+
+declare module "../scene" {
+    export interface Scene {
+        /** @hidden */
+        _edgeRenderLineShader: Nullable<ShaderMaterial>;
+    }
+}
 
 declare module "../Meshes/abstractMesh" {
     export interface AbstractMesh {
@@ -114,6 +123,11 @@ export interface IEdgesRenderer extends IDisposable {
      * @return true if ready, otherwise false.
      */
     isReady(): boolean;
+
+    /**
+     * List of instances to render in case the source mesh has instances
+     */
+    customInstances: SmartArray<Matrix>;
 }
 
 /**
@@ -141,6 +155,7 @@ export class EdgesRenderer implements IEdgesRenderer {
     protected _lineShader: ShaderMaterial;
     protected _ib: DataBuffer;
     protected _buffers: { [key: string]: Nullable<VertexBuffer> } = {};
+    protected _buffersForInstances: { [key: string]: Nullable<VertexBuffer> } = {};
     protected _checkVerticesInsteadOfIndices = false;
 
     private _meshRebuildObserver: Nullable<Observer<AbstractMesh>>;
@@ -150,6 +165,28 @@ export class EdgesRenderer implements IEdgesRenderer {
     public isEnabled = true;
 
     /**
+     * List of instances to render in case the source mesh has instances
+     */
+    public customInstances = new SmartArray<Matrix>(32);
+
+    private static GetShader(scene: Scene): ShaderMaterial {
+        if (!scene._edgeRenderLineShader) {
+            const shader = new ShaderMaterial("lineShader", scene, "line",
+                {
+                    attributes: ["position", "normal"],
+                    uniforms: ["world", "viewProjection", "color", "width", "aspectRatio"]
+                });
+
+            shader.disableDepthWrite = true;
+            shader.backFaceCulling = false;
+
+            scene._edgeRenderLineShader = shader;
+        }
+
+        return scene._edgeRenderLineShader;
+    }
+
+    /**
      * Creates an instance of the EdgesRenderer. It is primarily use to display edges of a mesh.
      * Beware when you use this class with complex objects as the adjacencies computation can be really long
      * @param  source Mesh used to create edges
@@ -182,14 +219,7 @@ export class EdgesRenderer implements IEdgesRenderer {
             return;
         }
 
-        this._lineShader = new ShaderMaterial("lineShader", this._source.getScene(), "line",
-            {
-                attributes: ["position", "normal"],
-                uniforms: ["worldViewProjection", "color", "width", "aspectRatio"]
-            });
-
-        this._lineShader.disableDepthWrite = true;
-        this._lineShader.backFaceCulling = false;
+        this._lineShader = EdgesRenderer.GetShader(this._source.getScene());
     }
 
     /** @hidden */
@@ -437,6 +467,9 @@ export class EdgesRenderer implements IEdgesRenderer {
         this._buffers[VertexBuffer.PositionKind] = new VertexBuffer(engine, this._linesPositions, VertexBuffer.PositionKind, false);
         this._buffers[VertexBuffer.NormalKind] = new VertexBuffer(engine, this._linesNormals, VertexBuffer.NormalKind, false, false, 4);
 
+        this._buffersForInstances[VertexBuffer.PositionKind] = this._buffers[VertexBuffer.PositionKind];
+        this._buffersForInstances[VertexBuffer.NormalKind] = this._buffers[VertexBuffer.NormalKind];
+
         this._ib = engine.createIndexBuffer(this._linesIndices);
 
         this._indicesCount = this._linesIndices.length;
@@ -447,7 +480,7 @@ export class EdgesRenderer implements IEdgesRenderer {
      * @return true if ready, otherwise false.
      */
     public isReady(): boolean {
-        return this._lineShader.isReady();
+        return this._lineShader.isReady(this._source, (this._source.hasInstances && this.customInstances.length > 0) || this._source.hasThinInstances);
     }
 
     /**
@@ -469,8 +502,39 @@ export class EdgesRenderer implements IEdgesRenderer {
             engine.setAlphaMode(Constants.ALPHA_DISABLE);
         }
 
+        const hasInstances = this._source.hasInstances && this.customInstances.length > 0;
+        const useBuffersWithInstances = hasInstances || this._source.hasThinInstances;
+
+        let instanceCount = 0;
+
+        if (useBuffersWithInstances) {
+            this._buffersForInstances["world0"] = (this._source as Mesh).getVertexBuffer("world0");
+            this._buffersForInstances["world1"] = (this._source as Mesh).getVertexBuffer("world1");
+            this._buffersForInstances["world2"] = (this._source as Mesh).getVertexBuffer("world2");
+            this._buffersForInstances["world3"] = (this._source as Mesh).getVertexBuffer("world3");
+
+            if (hasInstances) {
+                let instanceStorage = (this._source as Mesh)._instanceDataStorage;
+
+                instanceCount = this.customInstances.length;
+
+                if (!instanceStorage.isFrozen) {
+                    let offset = 0;
+
+                    for (let i = 0; i < instanceCount; ++i) {
+                        this.customInstances.data[i].copyToArray(instanceStorage.instancesData, offset);
+                        offset += 16;
+                    }
+
+                    instanceStorage.instancesBuffer!.updateDirectly(instanceStorage.instancesData, 0, instanceCount);
+                }
+            } else {
+                instanceCount = (this._source as Mesh).thinInstanceCount;
+            }
+        }
+
         // VBOs
-        engine.bindBuffers(this._buffers, this._ib, <Effect>this._lineShader.getEffect());
+        engine.bindBuffers(useBuffersWithInstances ? this._buffersForInstances : this._buffers, this._ib, <Effect>this._lineShader.getEffect());
 
         scene.resetCachedMaterial();
         this._lineShader.setColor4("color", this._source.edgesColor);
@@ -485,8 +549,10 @@ export class EdgesRenderer implements IEdgesRenderer {
         this._lineShader.bind(this._source.getWorldMatrix());
 
         // Draw order
-        engine.drawElementsType(Material.TriangleFillMode, 0, this._indicesCount);
+        engine.drawElementsType(Material.TriangleFillMode, 0, this._indicesCount, instanceCount);
         this._lineShader.unbind();
+
+        this.customInstances.reset();
     }
 }
 

+ 1 - 0
src/Rendering/geometryBufferRenderer.ts

@@ -223,6 +223,7 @@ export class GeometryBufferRenderer {
 
             if (material.bumpTexture && StandardMaterial.BumpTextureEnabled) {
                 defines.push("#define BUMP");
+                defines.push("#define BUMPDIRECTUV 0");
                 needUv = true;
             }
 

+ 5 - 0
src/Shaders/ShadersInclude/helperFunctions.fx

@@ -43,6 +43,11 @@ mat3 inverseMat3(mat3 inMatrix) {
               b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det;
 }
 
+float toLinearSpace(float color)
+{
+    return pow(color, LinearEncodePowerApprox);
+}
+
 vec3 toLinearSpace(vec3 color)
 {
     return pow(color, vec3(LinearEncodePowerApprox));

+ 14 - 11
src/Shaders/gpuRenderParticles.vertex.fx

@@ -28,8 +28,9 @@ in vec2 uv;
 
 out vec2 vUV;
 out vec4 vColor;
+out vec3 vPositionW;
 
-#if defined(CLIPPLANE) || defined(CLIPPLANE2) || defined(CLIPPLANE3) || defined(CLIPPLANE4) || defined(CLIPPLANE5) || defined(CLIPPLANE6)
+#if defined(BILLBOARD) && !defined(BILLBOARDY) && !defined(BILLBOARDSTRETCHED)
 uniform mat4 invView;
 #endif
 
@@ -99,7 +100,7 @@ void main() {
 		vec2 uvOffset = vec2(uv.x , 1.0 - uv.y);
 		vUV = (uvOffset + vec2(columnOffset, rowOffset)) * uvScale;
 	#else
-   	vUV = uv;
+   	    vUV = uv;
 	#endif
   float ratio = age / life;
 #ifdef COLORGRADIENTS
@@ -121,18 +122,18 @@ void main() {
 
 		vec3 yaxis = (position + worldOffset) - eyePosition;
 		yaxis.y = 0.;
-		vec3 worldPos = rotate(normalize(yaxis), rotatedCorner.xyz);
+		vPositionW = rotate(normalize(yaxis), rotatedCorner.xyz);
 
-		vec4 viewPosition = (view * vec4(worldPos, 1.0));
+		vec4 viewPosition = (view * vec4(vPositionW, 1.0));
 	#elif defined(BILLBOARDSTRETCHED)
 		rotatedCorner.x = cornerPos.x * cos(angle) - cornerPos.y * sin(angle);
 		rotatedCorner.y = cornerPos.x * sin(angle) + cornerPos.y * cos(angle);
 		rotatedCorner.z = 0.;
 
 		vec3 toCamera = (position + worldOffset) - eyePosition;
-		vec3 worldPos = rotateAlign(toCamera, rotatedCorner.xyz);
+		vPositionW = rotateAlign(toCamera, rotatedCorner.xyz);
 
-		vec4 viewPosition = (view * vec4(worldPos, 1.0));
+		vec4 viewPosition = (view * vec4(vPositionW, 1.0));
 	#else
 		// Rotate
 		rotatedCorner.x = cornerPos.x * cos(angle) - cornerPos.y * sin(angle);
@@ -145,26 +146,28 @@ void main() {
 		#else
 			vec4 viewPosition = view * vec4((position + worldOffset), 1.0) + rotatedCorner;
 		#endif
+
+        vPositionW = (invView * viewPosition).xyz;
 	#endif
 
 #else
-  // Rotate
+    // Rotate
 	vec3 rotatedCorner;
 	rotatedCorner.x = cornerPos.x * cos(angle) - cornerPos.y * sin(angle);
 	rotatedCorner.y = 0.;
 	rotatedCorner.z = cornerPos.x * sin(angle) + cornerPos.y * cos(angle);
 
 	vec3 yaxis = normalize(initialDirection);
-	vec3 worldPos = rotate(yaxis, rotatedCorner);
+	vPositionW = rotate(yaxis, rotatedCorner);
 
-  // Expand position
-  vec4 viewPosition = view * vec4(worldPos, 1.0);
+    // Expand position
+    vec4 viewPosition = view * vec4(vPositionW, 1.0);
 #endif
 	gl_Position = projection * viewPosition;
 
 	// Clip plane
 #if defined(CLIPPLANE) || defined(CLIPPLANE2) || defined(CLIPPLANE3) || defined(CLIPPLANE4) || defined(CLIPPLANE5) || defined(CLIPPLANE6)
-	vec4 worldPos = invView * viewPosition;
+    vec4 worldPos = vec4(vPositionW, 1.0);
 #endif
 	#include<clipPlaneVertex>
 }

+ 8 - 2
src/Shaders/line.vertex.fx

@@ -1,14 +1,20 @@
-// Attributes
+#include<instancesDeclaration>
+
+// Attributes
 attribute vec3 position;
 attribute vec4 normal;
 
 // Uniforms
-uniform mat4 worldViewProjection;
+uniform mat4 viewProjection;
 
 uniform float width;
 uniform float aspectRatio;
 
 void main(void) {
+    #include<instancesVertex>
+
+    mat4 worldViewProjection = viewProjection * finalWorld;
+
 	vec4 viewPosition = worldViewProjection * vec4(position, 1.0);
 	vec4 viewPositionNext = worldViewProjection * vec4(normal.xyz, 1.0);
 

+ 11 - 8
src/Shaders/particles.vertex.fx

@@ -29,12 +29,13 @@ uniform vec3 particlesInfos; // x (number of rows) y(number of columns) z(rowSiz
 // Output
 varying vec2 vUV;
 varying vec4 vColor;
+varying vec3 vPositionW;
 
 #ifdef RAMPGRADIENT
 varying vec4 remapRanges;
 #endif
 
-#if defined(CLIPPLANE) || defined(CLIPPLANE2) || defined(CLIPPLANE3) || defined(CLIPPLANE4) || defined(CLIPPLANE5) || defined(CLIPPLANE6)
+#if defined(BILLBOARD) && !defined(BILLBOARDY) && !defined(BILLBOARDSTRETCHED)
 uniform mat4 invView;
 #endif
 #include<clipPlaneVertexDeclaration>
@@ -91,24 +92,26 @@ void main(void) {
 	vec3 yaxis = position - eyePosition;
 	yaxis.y = 0.;
 
-	vec3 worldPos = rotate(normalize(yaxis), rotatedCorner);
+	vPositionW = rotate(normalize(yaxis), rotatedCorner);
 
-	vec3 viewPos = (view * vec4(worldPos, 1.0)).xyz;
+	vec3 viewPos = (view * vec4(vPositionW, 1.0)).xyz;
 #elif defined(BILLBOARDSTRETCHED)
 	rotatedCorner.x = cornerPos.x * cos(angle) - cornerPos.y * sin(angle);
 	rotatedCorner.y = cornerPos.x * sin(angle) + cornerPos.y * cos(angle);
 	rotatedCorner.z = 0.;
 
 	vec3 toCamera = position - eyePosition;
-	vec3 worldPos = rotateAlign(toCamera, rotatedCorner);
+	vPositionW = rotateAlign(toCamera, rotatedCorner);
 
-	vec3 viewPos = (view * vec4(worldPos, 1.0)).xyz;
+	vec3 viewPos = (view * vec4(vPositionW, 1.0)).xyz;
 #else
 	rotatedCorner.x = cornerPos.x * cos(angle) - cornerPos.y * sin(angle);
 	rotatedCorner.y = cornerPos.x * sin(angle) + cornerPos.y * cos(angle);
 	rotatedCorner.z = 0.;
 
 	vec3 viewPos = (view * vec4(position, 1.0)).xyz + rotatedCorner;
+
+    vPositionW = (invView * vec4(viewPos, 1)).xyz;
 #endif
 
 #ifdef RAMPGRADIENT
@@ -125,9 +128,9 @@ void main(void) {
 	rotatedCorner.y = 0.;
 
 	vec3 yaxis = normalize(direction);
-	vec3 worldPos = rotate(yaxis, rotatedCorner);
+	vPositionW = rotate(yaxis, rotatedCorner);
 
-	gl_Position = projection * view * vec4(worldPos, 1.0);
+	gl_Position = projection * view * vec4(vPositionW, 1.0);
 #endif
 	vColor = color;
 
@@ -144,7 +147,7 @@ void main(void) {
 
 	// Clip plane
 #if defined(CLIPPLANE) || defined(CLIPPLANE2) || defined(CLIPPLANE3) || defined(CLIPPLANE4) || defined(CLIPPLANE5) || defined(CLIPPLANE6)
-	vec4 worldPos = invView * vec4(viewPos, 1.0);
+    vec4 worldPos = vec4(vPositionW, 1.0);
 #endif
 	#include<clipPlaneVertex>
 

+ 10 - 4
src/XR/features/WebXRControllerPointerSelection.ts

@@ -155,7 +155,7 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
     /**
      * Default color of the laser pointer
      */
-    public lasterPointerDefaultColor: Color3 = new Color3(0.7, 0.7, 0.7);
+    public laserPointerDefaultColor: Color3 = new Color3(0.7, 0.7, 0.7);
     /**
      * default color of the selection ring
      */
@@ -418,7 +418,7 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
                             } else {
                                 this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
                                 (<StandardMaterial>controllerData.selectionMesh.material).emissiveColor = this.selectionMeshDefaultColor;
-                        (<StandardMaterial>controllerData.laserPointer.material).emissiveColor = this.lasterPointerDefaultColor;
+                        (<StandardMaterial>controllerData.laserPointer.material).emissiveColor = this.laserPointerDefaultColor;
                             }
                         }
                     }
@@ -443,7 +443,7 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
                 if (event.inputSource === controllerData.xrController.inputSource && controllerData.pick) {
                     this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
                     (<StandardMaterial>controllerData.selectionMesh.material).emissiveColor = this.selectionMeshDefaultColor;
-                    (<StandardMaterial>controllerData.laserPointer.material).emissiveColor = this.lasterPointerDefaultColor;
+                    (<StandardMaterial>controllerData.laserPointer.material).emissiveColor = this.laserPointerDefaultColor;
                 }
             };
 
@@ -503,7 +503,7 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
         }, sceneToRenderTo);
         laserPointer.parent = xrController.pointer;
         let laserPointerMaterial = new StandardMaterial("laserPointerMat", sceneToRenderTo);
-        laserPointerMaterial.emissiveColor = this.lasterPointerDefaultColor;
+        laserPointerMaterial.emissiveColor = this.laserPointerDefaultColor;
         laserPointerMaterial.alpha = 0.7;
         laserPointer.material = laserPointerMaterial;
         laserPointer.rotation.x = Math.PI / 2;
@@ -557,6 +557,12 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
         }
         _laserPointer.position.z = (distance / 2) + 0.05;
     }
+
+    /** @hidden */
+    public get lasterPointerDefaultColor(): Color3 {
+        // here due to a typo
+        return this.laserPointerDefaultColor;
+    }
 }
 
 //register the plugin

+ 16 - 23
src/XR/features/WebXRControllerTeleportation.ts

@@ -25,6 +25,7 @@ import { Color3 } from '../../Maths/math.color';
 import { Scene } from '../../scene';
 import { UtilityLayerRenderer } from '../../Rendering/utilityLayerRenderer';
 import { PointerEventTypes } from '../../Events/pointerEvents';
+import { setAndStartTimer } from '../../Misc/timer';
 
 /**
  * The options container for the teleportation module
@@ -429,20 +430,14 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
                                     controllerData.teleportationState.baseRotation = this._options.xrInput.xrCamera.rotationQuaternion.toEulerAngles().y;
                                     controllerData.teleportationState.currentRotation = 0;
                                     const timeToSelect = this._options.timeToTeleport || 3000;
-                                    let timer = 0;
-                                    const observer = this._xrSessionManager.onXRFrameObservable.add(() => {
-                                        if (!mainComponent.pressed) {
-                                            this._xrSessionManager.onXRFrameObservable.remove(observer);
-                                            return;
-                                        }
-                                        timer += this._xrSessionManager.scene.getEngine().getDeltaTime();
-                                        if (timer >= timeToSelect && this._currentTeleportationControllerId === controllerData.xrController.uniqueId && controllerData.teleportationState.forward) {
-                                            this._teleportForward(xrController.uniqueId);
-                                        }
-
-                                        // failsafe
-                                        if (timer >= timeToSelect) {
-                                            this._xrSessionManager.onXRFrameObservable.remove(observer);
+                                    setAndStartTimer({
+                                        timeout: timeToSelect,
+                                        contextObservable: this._xrSessionManager.onXRFrameObservable,
+                                        breakCondition: () => !mainComponent.pressed,
+                                        onEnded: () => {
+                                            if (this._currentTeleportationControllerId === controllerData.xrController.uniqueId && controllerData.teleportationState.forward) {
+                                                this._teleportForward(xrController.uniqueId);
+                                            }
                                         }
                                     });
                                 } else {
@@ -526,15 +521,13 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
                     controllerData.teleportationState.baseRotation = this._options.xrInput.xrCamera.rotationQuaternion.toEulerAngles().y;
                     controllerData.teleportationState.currentRotation = 0;
                     const timeToSelect = this._options.timeToTeleport || 3000;
-                    let timer = 0;
-                    const observer = this._xrSessionManager.onXRFrameObservable.add(() => {
-                        timer += this._xrSessionManager.scene.getEngine().getDeltaTime();
-                        if (timer >= timeToSelect && this._currentTeleportationControllerId === controllerData.xrController.uniqueId && controllerData.teleportationState.forward) {
-                            this._teleportForward(xrController.uniqueId);
-                        }
-
-                        if (timer >= timeToSelect) {
-                            this._xrSessionManager.onXRFrameObservable.remove(observer);
+                    setAndStartTimer({
+                        timeout: timeToSelect,
+                        contextObservable: this._xrSessionManager.onXRFrameObservable,
+                        onEnded: () => {
+                            if (this._currentTeleportationControllerId === controllerData.xrController.uniqueId && controllerData.teleportationState.forward) {
+                                this._teleportForward(xrController.uniqueId);
+                            }
                         }
                     });
                 } else if (pointerInfo.type === PointerEventTypes.POINTERUP) {

+ 2 - 0
src/XR/webXREnterExitUI.ts

@@ -170,6 +170,8 @@ export class WebXREnterExitUI implements IDisposable {
                             }
                         }
                     };
+                } else {
+                    Tools.Warn(`Session mode "${ui._buttons[i].sessionMode}" not supported in browser`);
                 }
             });
             return ui;

+ 1 - 6
src/XR/webXRExperienceHelper.ts

@@ -108,12 +108,7 @@ export class WebXRExperienceHelper implements IDisposable {
             Logger.Warn("We recommend using 'unbounded' reference space type when using 'immersive-ar' session mode");
         }
         // make sure that the session mode is supported
-        return this.sessionManager.isSessionSupportedAsync(sessionMode).then((supported) => {
-            if (!supported) {
-                throw new Error(`Session mode "${sessionMode}" not supported in browser`);
-            }
-            return this.sessionManager.initializeSessionAsync(sessionMode, sessionCreationOptions);
-        }).then(() => {
+        return this.sessionManager.initializeSessionAsync(sessionMode, sessionCreationOptions).then(() => {
             return this.sessionManager.setReferenceSpaceTypeAsync(referenceSpaceType);
         }).then(() => {
             return renderTarget.initializeXRLayerAsync(this.sessionManager.session);

+ 7 - 0
src/scene.ts

@@ -3478,6 +3478,13 @@ export class Scene extends AbstractScene implements IAnimatable {
                 }
             }
 
+            if (this._activeParticleSystems) {
+                const psLength = this._activeParticleSystems.length;
+                for (let i = 0; i < psLength; i++) {
+                    this._activeParticleSystems.data[i].animate();
+                }
+            }
+
             return;
         }
 

二進制
tests/validation/ReferenceImages/node-material-pbr-1.png


+ 5 - 0
tests/validation/config.json

@@ -67,6 +67,11 @@
             "referenceImage": "node-material6.png"
         },    
         {
+            "title": "Node material PBR 1",
+            "playgroundId": "#D8AK3Z#7",
+            "referenceImage": "node-material-pbr-1.png"
+        },    
+        {
             "title": "Basis loader",
             "playgroundId": "#4RN0VF#0",
             "referenceImage": "basis.png"