Browse Source

Merge branch 'master' into resize-editor

Alejandro Toledo 5 years ago
parent
commit
83b3bb6b50
57 changed files with 4045 additions and 926 deletions
  1. 48 49
      Playground/src/components/examplesComponent.tsx
  2. 24 18
      Playground/src/tools/loadManager.ts
  3. 22 0
      dist/ktx2Transcoders/msc_basis_transcoder.js
  4. BIN
      dist/ktx2Transcoders/msc_basis_transcoder.wasm
  5. BIN
      dist/ktx2Transcoders/uastc_astc.wasm
  6. BIN
      dist/ktx2Transcoders/uastc_bc7.wasm
  7. 280 57
      dist/preview release/babylon.d.ts
  8. 2 2
      dist/preview release/babylon.js
  9. 354 27
      dist/preview release/babylon.max.js
  10. 1 1
      dist/preview release/babylon.max.js.map
  11. 512 62
      dist/preview release/babylon.module.d.ts
  12. 280 57
      dist/preview release/documentation.d.ts
  13. 5 5
      dist/preview release/inspector/babylon.inspector.bundle.js
  14. 559 178
      dist/preview release/inspector/babylon.inspector.bundle.max.js
  15. 1 1
      dist/preview release/inspector/babylon.inspector.bundle.max.js.map
  16. 66 11
      dist/preview release/inspector/babylon.inspector.d.ts
  17. 137 24
      dist/preview release/inspector/babylon.inspector.module.d.ts
  18. 22 0
      dist/preview release/ktx2Transcoders/msc_basis_transcoder.js
  19. BIN
      dist/preview release/ktx2Transcoders/msc_basis_transcoder.wasm
  20. BIN
      dist/preview release/ktx2Transcoders/uastc_astc.wasm
  21. BIN
      dist/preview release/ktx2Transcoders/uastc_bc7.wasm
  22. 1 1
      dist/preview release/packagesSizeBaseLine.json
  23. 512 62
      dist/preview release/viewer/babylon.module.d.ts
  24. 20 16
      dist/preview release/viewer/babylon.viewer.js
  25. 1 1
      dist/preview release/viewer/babylon.viewer.max.js
  26. 4 1
      dist/preview release/what's new.md
  27. 4 1
      inspector/src/components/actionTabs/actionTabs.scss
  28. 1 0
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx
  29. 3 0
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/bottomBar.tsx
  30. 10 2
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/contrast.ts
  31. 2 1
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/defaultTools.ts
  32. 13 13
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/eyedropper.ts
  33. 15 13
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/floodfill.ts
  34. 102 50
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/paintbrush.ts
  35. 63 3
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/rectangleSelect.ts
  36. 65 61
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/propertiesBar.tsx
  37. 233 41
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/textureCanvasManager.ts
  38. 53 47
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditor.scss
  39. 57 14
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent.tsx
  40. 7 5
      inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/toolBar.tsx
  41. 15 0
      inspector/src/lod.ts
  42. 33 0
      inspector/src/lodCube.ts
  43. 28 18
      inspector/src/textureHelper.ts
  44. 4 4
      src/Animations/animation.ts
  45. 1 1
      src/Behaviors/Meshes/pointerDragBehavior.ts
  46. 65 59
      src/LibDeclarations/webxr.d.ts
  47. 4 0
      src/XR/features/WebXRControllerPointerSelection.ts
  48. 1 1
      src/XR/features/WebXRControllerTeleportation.ts
  49. 366 0
      src/XR/features/WebXRHandTracking.ts
  50. 1 0
      src/XR/features/index.ts
  51. 6 6
      src/XR/motionController/webXRAbstractMotionController.ts
  52. 6 3
      src/XR/motionController/webXRProfiledMotionController.ts
  53. 11 7
      src/XR/webXREnterExitUI.ts
  54. 15 0
      src/XR/webXRFeaturesManager.ts
  55. 6 0
      src/XR/webXRInputSource.ts
  56. 3 2
      src/XR/webXRManagedOutputCanvas.ts
  57. 1 1
      src/XR/webXRSessionManager.ts

+ 48 - 49
Playground/src/components/examplesComponent.tsx

@@ -1,5 +1,5 @@
 import * as React from "react";
 import * as React from "react";
-import { GlobalState } from '../globalState';
+import { GlobalState } from "../globalState";
 
 
 require("../scss/examples.scss");
 require("../scss/examples.scss");
 
 
@@ -7,7 +7,7 @@ interface IExamplesComponentProps {
     globalState: GlobalState;
     globalState: GlobalState;
 }
 }
 
 
-export class ExamplesComponent extends React.Component<IExamplesComponentProps, {filter: string}> {  
+export class ExamplesComponent extends React.Component<IExamplesComponentProps, { filter: string }> {
     private _state = "removed";
     private _state = "removed";
     private _rootRef: React.RefObject<HTMLDivElement>;
     private _rootRef: React.RefObject<HTMLDivElement>;
     private _scripts: {
     private _scripts: {
@@ -19,13 +19,13 @@ export class ExamplesComponent extends React.Component<IExamplesComponentProps,
             PGID: string;
             PGID: string;
             description: string;
             description: string;
         }[];
         }[];
-    }[];  
-  
+    }[];
+
     public constructor(props: IExamplesComponentProps) {
     public constructor(props: IExamplesComponentProps) {
         super(props);
         super(props);
         this._loadScripts();
         this._loadScripts();
 
 
-        this.state = {filter: ""};
+        this.state = { filter: "" };
         this._rootRef = React.createRef();
         this._rootRef = React.createRef();
 
 
         this.props.globalState.onExamplesDisplayChangedObservable.add(() => {
         this.props.globalState.onExamplesDisplayChangedObservable.add(() => {
@@ -40,18 +40,18 @@ export class ExamplesComponent extends React.Component<IExamplesComponentProps,
                 this._state = "";
                 this._state = "";
                 setTimeout(() => {
                 setTimeout(() => {
                     this._rootRef.current!.classList.add("removed");
                     this._rootRef.current!.classList.add("removed");
-                }, 200)
+                }, 200);
             }
             }
         });
         });
-    }  
+    }
 
 
     private _loadScripts() {
     private _loadScripts() {
         var xhr = new XMLHttpRequest();
         var xhr = new XMLHttpRequest();
 
 
         if (this.props.globalState.language === "JS") {
         if (this.props.globalState.language === "JS") {
-            xhr.open('GET', 'https://raw.githubusercontent.com/BabylonJS/Documentation/master/examples/list.json', true);
+            xhr.open("GET", "https://raw.githubusercontent.com/BabylonJS/Documentation/master/examples/list.json", true);
         } else {
         } else {
-            xhr.open('GET', 'https://raw.githubusercontent.com/BabylonJS/Documentation/master/examples/list_ts.json', true);
+            xhr.open("GET", "https://raw.githubusercontent.com/BabylonJS/Documentation/master/examples/list_ts.json", true);
         }
         }
 
 
         xhr.onreadystatechange = () => {
         xhr.onreadystatechange = () => {
@@ -66,7 +66,7 @@ export class ExamplesComponent extends React.Component<IExamplesComponentProps,
                         return 1;
                         return 1;
                     });
                     });
 
 
-                    this._scripts.forEach(s => {
+                    this._scripts.forEach((s) => {
                         s.samples.sort((a, b) => {
                         s.samples.sort((a, b) => {
                             if (a.title < b.title) {
                             if (a.title < b.title) {
                                 return -1;
                                 return -1;
@@ -78,12 +78,11 @@ export class ExamplesComponent extends React.Component<IExamplesComponentProps,
                     this.forceUpdate();
                     this.forceUpdate();
                 }
                 }
             }
             }
-        }
+        };
 
 
         xhr.send(null);
         xhr.send(null);
     }
     }
 
 
-
     private _onLoadPG(id: string) {
     private _onLoadPG(id: string) {
         this.props.globalState.onLoadRequiredObservable.notifyObservers(id);
         this.props.globalState.onLoadRequiredObservable.notifyObservers(id);
 
 
@@ -101,46 +100,46 @@ export class ExamplesComponent extends React.Component<IExamplesComponentProps,
             <div id="examples" className={this._state} ref={this._rootRef}>
             <div id="examples" className={this._state} ref={this._rootRef}>
                 <div id="examples-header">Examples</div>
                 <div id="examples-header">Examples</div>
                 <div id="examples-filter">
                 <div id="examples-filter">
-                    <input id="examples-filter-text" type="text" placeholder="Filter examples" value={this.state.filter} onChange={evt => {
-                        this.setState({filter: evt.target.value});
-                    }}/>
+                    <input
+                        id="examples-filter-text"
+                        type="text"
+                        placeholder="Filter examples"
+                        value={this.state.filter}
+                        onChange={(evt) => {
+                            this.setState({ filter: evt.target.value });
+                        }}
+                    />
                 </div>
                 </div>
                 <div id="examples-list">
                 <div id="examples-list">
-                    {
-                        this._scripts.map(s => {
-                            let active = s.samples.filter(ss => {
-                                return !this.state.filter 
-                                    || ss.title.toLowerCase().indexOf(this.state.filter.toLowerCase()) !== -1
-                                    || ss.description.toLowerCase().indexOf(this.state.filter.toLowerCase()) !== -1
-                            });
-
-                            if (active.length === 0) {
-                                return null;
-                            }
+                    {this._scripts.map((s) => {
+                        let active = s.samples.filter((ss) => {
+                            return !this.state.filter || ss.title.toLowerCase().indexOf(this.state.filter.toLowerCase()) !== -1 || ss.description.toLowerCase().indexOf(this.state.filter.toLowerCase()) !== -1;
+                        });
+
+                        if (active.length === 0) {
+                            return null;
+                        }
 
 
-                            return(
-                                <div key={s.title} className="example-category">
-                                    <div className="example-category-title">
-                                        {s.title}
-                                    </div>
-                                    {
-                                        active.map(ss => {
-                                            return (
-                                                <div className="example" key={ss.title} onClick={() => this._onLoadPG(ss.PGID)}>
-                                                    <img src={ss.icon.replace("icons", "https://doc.babylonjs.com/examples/icons")}/>
-                                                    <div className="example-title">{ss.title}</div>
-                                                    <div className="example-description">{ss.description}</div>
-                                                    <a className="example-link" href={ss.doc} target="_blank">Documentation</a>
-                                                </div>
-                                            )
-                                        })
-                                    }
-                                </div>
-                            )
-                        })
-                    }
+                        return (
+                            <div key={s.title} className="example-category">
+                                <div className="example-category-title">{s.title}</div>
+                                {active.map((ss) => {
+                                    return (
+                                        <div className="example" key={ss.title} onClick={() => this._onLoadPG(ss.PGID)}>
+                                            <img src={ss.icon.replace("icons", "https://doc.babylonjs.com/examples/icons")} />
+                                            <div className="example-title">{ss.title}</div>
+                                            <div className="example-description">{ss.description}</div>
+                                            <a className="example-link" href={ss.doc} target="_blank">
+                                                Documentation
+                                            </a>
+                                        </div>
+                                    );
+                                })}
+                            </div>
+                        );
+                    })}
                 </div>
                 </div>
             </div>
             </div>
-        )
+        );
     }
     }
-}
+}

+ 24 - 18
Playground/src/tools/loadManager.ts

@@ -1,22 +1,22 @@
-import { GlobalState } from '../globalState';
-import { Utilities } from './utilities';
+import { GlobalState } from "../globalState";
+import { Utilities } from "./utilities";
 
 
 export class LoadManager {
 export class LoadManager {
     private _previousHash = "";
     private _previousHash = "";
 
 
-    public constructor(public globalState: GlobalState) {  
-        // Check the url to prepopulate data        
+    public constructor(public globalState: GlobalState) {
+        // Check the url to prepopulate data
         this._checkHash();
         this._checkHash();
         window.addEventListener("hashchange", () => this._checkHash());
         window.addEventListener("hashchange", () => this._checkHash());
 
 
-        globalState.onLoadRequiredObservable.add(id => {
+        globalState.onLoadRequiredObservable.add((id) => {
             globalState.onDisplayWaitRingObservable.notifyObservers(true);
             globalState.onDisplayWaitRingObservable.notifyObservers(true);
             this._loadPlayground(id);
             this._loadPlayground(id);
         });
         });
     }
     }
 
 
     private _cleanHash() {
     private _cleanHash() {
-        var substr = location.hash[1]==='#' ? 2 : 1
+        var substr = location.hash[1] === "#" ? 2 : 1;
         var splits = decodeURIComponent(location.hash.substr(substr)).split("#");
         var splits = decodeURIComponent(location.hash.substr(substr)).split("#");
 
 
         if (splits.length > 2) {
         if (splits.length > 2) {
@@ -24,14 +24,14 @@ export class LoadManager {
         }
         }
 
 
         location.hash = splits.join("#");
         location.hash = splits.join("#");
-    };
+    }
 
 
     private _checkHash() {
     private _checkHash() {
         let pgHash = "";
         let pgHash = "";
-        if (location.search && (!location.pathname  || location.pathname === '/') && !location.hash) {
+        if (location.search && (!location.pathname || location.pathname === "/") && !location.hash) {
             var query = Utilities.ParseQuery();
             var query = Utilities.ParseQuery();
             if (query.pg) {
             if (query.pg) {
-                pgHash = "#" + query.pg + "#" + (query.revision || "0")
+                pgHash = "#" + query.pg + "#" + (query.revision || "0");
             }
             }
         } else if (location.hash) {
         } else if (location.hash) {
             if (this._previousHash !== location.hash) {
             if (this._previousHash !== location.hash) {
@@ -52,27 +52,27 @@ export class LoadManager {
         if (pgHash) {
         if (pgHash) {
             var match = pgHash.match(/^(#[A-Za-z\d]*)(%23)([\d]+)$/);
             var match = pgHash.match(/^(#[A-Za-z\d]*)(%23)([\d]+)$/);
             if (match) {
             if (match) {
-                pgHash = match[1] + '#' + match[3];
+                pgHash = match[1] + "#" + match[3];
                 parent.location.hash = pgHash;
                 parent.location.hash = pgHash;
             }
             }
             this._previousHash = pgHash;
             this._previousHash = pgHash;
             this._loadPlayground(pgHash.substr(1));
             this._loadPlayground(pgHash.substr(1));
-        }        
+        }
     }
     }
 
 
-    private _loadPlayground(id: string) {        
+    private _loadPlayground(id: string) {
         this.globalState.loadingCodeInProgress = true;
         this.globalState.loadingCodeInProgress = true;
         try {
         try {
             var xmlHttp = new XMLHttpRequest();
             var xmlHttp = new XMLHttpRequest();
             xmlHttp.onreadystatechange = () => {
             xmlHttp.onreadystatechange = () => {
                 if (xmlHttp.readyState === 4) {
                 if (xmlHttp.readyState === 4) {
                     if (xmlHttp.status === 200) {
                     if (xmlHttp.status === 200) {
-
                         if (xmlHttp.responseText.indexOf("class Playground") !== -1) {
                         if (xmlHttp.responseText.indexOf("class Playground") !== -1) {
                             if (this.globalState.language === "JS") {
                             if (this.globalState.language === "JS") {
                                 Utilities.SwitchLanguage("TS", this.globalState);
                                 Utilities.SwitchLanguage("TS", this.globalState);
                             }
                             }
-                        } else { // If we're loading JS content and it's TS page
+                        } else {
+                            // If we're loading JS content and it's TS page
                             if (this.globalState.language === "TS") {
                             if (this.globalState.language === "TS") {
                                 Utilities.SwitchLanguage("JS", this.globalState);
                                 Utilities.SwitchLanguage("JS", this.globalState);
                             }
                             }
@@ -100,20 +100,26 @@ export class LoadManager {
                         }
                         }
 
 
                         this.globalState.onCodeLoaded.notifyObservers(JSON.parse(snippet.jsonPayload).code.toString());
                         this.globalState.onCodeLoaded.notifyObservers(JSON.parse(snippet.jsonPayload).code.toString());
-                         
+
                         this.globalState.onMetadataUpdatedObservable.notifyObservers();
                         this.globalState.onMetadataUpdatedObservable.notifyObservers();
                     }
                     }
                 }
                 }
+            };
+
+            if (id[0] === "#") {
+                id = id.substr(1);
             }
             }
 
 
             this.globalState.currentSnippetToken = id.split("#")[0];
             this.globalState.currentSnippetToken = id.split("#")[0];
-            if (!id.split("#")[1]) id += "#0";
+            if (!id.split("#")[1]) {
+                id += "#0";
+            }
 
 
-            xmlHttp.open("GET", this.globalState.SnippetServerUrl + "/" + id.replace("#", "/"));
+            xmlHttp.open("GET", this.globalState.SnippetServerUrl + "/" + id.replace(/#/g, "/"));
             xmlHttp.send();
             xmlHttp.send();
         } catch (e) {
         } catch (e) {
             this.globalState.loadingCodeInProgress = false;
             this.globalState.loadingCodeInProgress = false;
             this.globalState.onCodeLoaded.notifyObservers("");
             this.globalState.onCodeLoaded.notifyObservers("");
         }
         }
     }
     }
-}
+}

File diff suppressed because it is too large
+ 22 - 0
dist/ktx2Transcoders/msc_basis_transcoder.js


BIN
dist/ktx2Transcoders/msc_basis_transcoder.wasm


BIN
dist/ktx2Transcoders/uastc_astc.wasm


BIN
dist/ktx2Transcoders/uastc_bc7.wasm


+ 280 - 57
dist/preview release/babylon.d.ts

@@ -46800,9 +46800,10 @@ declare module BABYLON {
         newCanvasCssStyle?: string;
         newCanvasCssStyle?: string;
         /**
         /**
          * Get the default values of the configuration object
          * Get the default values of the configuration object
+         * @param engine defines the engine to use (can be null)
          * @returns default values of this configuration object
          * @returns default values of this configuration object
          */
          */
-        static GetDefaults(): WebXRManagedOutputCanvasOptions;
+        static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions;
     }
     }
     /**
     /**
      * Creates a canvas that is added/removed from the webpage when entering/exiting XR
      * Creates a canvas that is added/removed from the webpage when entering/exiting XR
@@ -47082,6 +47083,10 @@ declare module BABYLON {
          * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
          * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
          */
          */
         xrNativeFeatureName?: string;
         xrNativeFeatureName?: string;
+        /**
+         * A list of (Babylon WebXR) features this feature depends on
+         */
+        dependsOn?: string[];
     }
     }
     /**
     /**
      * A list of the currently available features without referencing them
      * A list of the currently available features without referencing them
@@ -47119,6 +47124,10 @@ declare module BABYLON {
          * The name of the feature points feature.
          * The name of the feature points feature.
          */
          */
         static readonly FEATURE_POINTS: string;
         static readonly FEATURE_POINTS: string;
+        /**
+         * The name of the hand tracking feature.
+         */
+        static readonly HAND_TRACKING: string;
     }
     }
     /**
     /**
      * Defining the constructor of a feature. Used to register the modules.
      * Defining the constructor of a feature. Used to register the modules.
@@ -48065,7 +48074,7 @@ declare module BABYLON {
         /**
         /**
          * The mesh that will be changed when axis value changes
          * The mesh that will be changed when axis value changes
          */
          */
-        valueMesh: AbstractMesh;
+        valueMesh?: AbstractMesh;
     }
     }
     /**
     /**
      * The elements needed for change-detection of the gamepad objects in motion controllers
      * The elements needed for change-detection of the gamepad objects in motion controllers
@@ -48215,8 +48224,8 @@ declare module BABYLON {
          * @returns a promise that will send true when the pulse has ended and false if the device doesn't support pulse or an error accrued
          * @returns a promise that will send true when the pulse has ended and false if the device doesn't support pulse or an error accrued
          */
          */
         pulse(value: number, duration: number, hapticActuatorIndex?: number): Promise<boolean>;
         pulse(value: number, duration: number, hapticActuatorIndex?: number): Promise<boolean>;
-        protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh;
-        protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh;
+        protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined;
+        protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined;
         /**
         /**
          * Moves the axis on the controller mesh based on its current state
          * Moves the axis on the controller mesh based on its current state
          * @param axis the index of the axis
          * @param axis the index of the axis
@@ -48467,6 +48476,7 @@ declare module BABYLON {
         private _options;
         private _options;
         private _tmpVector;
         private _tmpVector;
         private _uniqueId;
         private _uniqueId;
+        private _disposed;
         /**
         /**
          * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
          * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
          */
          */
@@ -48992,7 +49002,10 @@ declare module BABYLON {
         options: WebXREnterExitUIOptions;
         options: WebXREnterExitUIOptions;
         private _activeButton;
         private _activeButton;
         private _buttons;
         private _buttons;
-        private _overlay;
+        /**
+         * The HTML Div Element to which buttons are added.
+         */
+        readonly overlay: HTMLDivElement;
         /**
         /**
          * Fired every time the active button is changed.
          * Fired every time the active button is changed.
          *
          *
@@ -74861,6 +74874,209 @@ declare module BABYLON {
 }
 }
 declare module BABYLON {
 declare module BABYLON {
     /**
     /**
+     * Configuration interface for the hand tracking feature
+     */
+    export interface IWebXRHandTrackingOptions {
+        /**
+         * The xrInput that will be used as source for new hands
+         */
+        xrInput: WebXRInput;
+        /**
+         * Configuration object for the joint meshes
+         */
+        jointMeshes?: {
+            /**
+             * Should the meshes created be invisible (defaults to false)
+             */
+            invisible?: boolean;
+            /**
+             * A source mesh to be used to create instances. Defaults to a sphere.
+             * This mesh will be the source for all other (25) meshes.
+             * It should have the general size of a single unit, as the instances will be scaled according to the provided radius
+             */
+            sourceMesh?: Mesh;
+            /**
+             * Should the source mesh stay visible. Defaults to false
+             */
+            keepOriginalVisible?: boolean;
+            /**
+             * Scale factor for all instances (defaults to 2)
+             */
+            scaleFactor?: number;
+            /**
+             * Should each instance have its own physics impostor
+             */
+            enablePhysics?: boolean;
+            /**
+             * If enabled, override default physics properties
+             */
+            physicsProps?: {
+                friction?: number;
+                restitution?: number;
+                impostorType?: number;
+            };
+            /**
+             * For future use - a single hand-mesh that will be updated according to the XRHand data provided
+             */
+            handMesh?: AbstractMesh;
+        };
+    }
+    /**
+     * Parts of the hands divided to writs and finger names
+     */
+    export const enum HandPart {
+        /**
+         * HandPart - Wrist
+         */
+        WRIST = "wrist",
+        /**
+         * HandPart - The THumb
+         */
+        THUMB = "thumb",
+        /**
+         * HandPart - Index finger
+         */
+        INDEX = "index",
+        /**
+         * HandPart - Middle finger
+         */
+        MIDDLE = "middle",
+        /**
+         * HandPart - Ring finger
+         */
+        RING = "ring",
+        /**
+         * HandPart - Little finger
+         */
+        LITTLE = "little"
+    }
+    /**
+     * Representing a single hand (with its corresponding native XRHand object)
+     */
+    export class WebXRHand implements IDisposable {
+        /** the controller to which the hand correlates */
+        readonly xrController: WebXRInputSource;
+        /** the meshes to be used to track the hand joints */
+        readonly trackedMeshes: AbstractMesh[];
+        /**
+         * Hand-parts definition (key is HandPart)
+         */
+        static HandPartsDefinition: {
+            [key: string]: number[];
+        };
+        /**
+         * Populate the HandPartsDefinition object.
+         * This is called as a side effect since certain browsers don't have XRHand defined.
+         */
+        static _PopulateHandPartsDefinition(): void;
+        /**
+         * Construct a new hand object
+         * @param xrController the controller to which the hand correlates
+         * @param trackedMeshes the meshes to be used to track the hand joints
+         */
+        constructor(
+        /** the controller to which the hand correlates */
+        xrController: WebXRInputSource, 
+        /** the meshes to be used to track the hand joints */
+        trackedMeshes: AbstractMesh[]);
+        /**
+         * Update this hand from the latest xr frame
+         * @param xrFrame xrFrame to update from
+         * @param referenceSpace The current viewer reference space
+         * @param scaleFactor optional scale factor for the meshes
+         */
+        updateFromXRFrame(xrFrame: XRFrame, referenceSpace: XRReferenceSpace, scaleFactor?: number): void;
+        /**
+         * Get meshes of part of the hand
+         * @param part the part of hand to get
+         * @returns An array of meshes that correlate to the hand part requested
+         */
+        getHandPartMeshes(part: HandPart): AbstractMesh[];
+        /**
+         * Dispose this Hand object
+         */
+        dispose(): void;
+    }
+    /**
+     * WebXR Hand Joint tracking feature, available for selected browsers and devices
+     */
+    export class WebXRHandTracking extends WebXRAbstractFeature {
+        /**
+         * options to use when constructing this feature
+         */
+        readonly options: IWebXRHandTrackingOptions;
+        private static _idCounter;
+        /**
+         * The module's name
+         */
+        static readonly Name: string;
+        /**
+         * The (Babylon) version of this module.
+         * This is an integer representing the implementation version.
+         * This number does not correspond to the WebXR specs version
+         */
+        static readonly Version: number;
+        /**
+         * This observable will notify registered observers when a new hand object was added and initialized
+         */
+        onHandAddedObservable: Observable<WebXRHand>;
+        /**
+         * This observable will notify its observers right before the hand object is disposed
+         */
+        onHandRemovedObservable: Observable<WebXRHand>;
+        private _hands;
+        /**
+         * Creates a new instance of the hit test feature
+         * @param _xrSessionManager an instance of WebXRSessionManager
+         * @param options options to use when constructing this feature
+         */
+        constructor(_xrSessionManager: WebXRSessionManager, 
+        /**
+         * options to use when constructing this feature
+         */
+        options: IWebXRHandTrackingOptions);
+        /**
+         * Check if the needed objects are defined.
+         * This does not mean that the feature is enabled, but that the objects needed are well defined.
+         */
+        isCompatible(): boolean;
+        /**
+         * attach this feature
+         * Will usually be called by the features manager
+         *
+         * @returns true if successful.
+         */
+        attach(): boolean;
+        /**
+         * detach this feature.
+         * Will usually be called by the features manager
+         *
+         * @returns true if successful.
+         */
+        detach(): boolean;
+        /**
+         * Dispose this feature and all of the resources attached
+         */
+        dispose(): void;
+        /**
+         * Get the hand object according to the controller id
+         * @param controllerId the controller id to which we want to get the hand
+         * @returns null if not found or the WebXRHand object if found
+         */
+        getHandByControllerId(controllerId: string): Nullable<WebXRHand>;
+        /**
+         * Get a hand object according to the requested handedness
+         * @param handedness the handedness to request
+         * @returns null if not found or the WebXRHand object if found
+         */
+        getHandByHandedness(handedness: XRHandedness): Nullable<WebXRHand>;
+        protected _onXRFrame(_xrFrame: XRFrame): void;
+        private _attachHand;
+        private _detachHand;
+    }
+}
+declare module BABYLON {
+    /**
      * The motion controller class for all microsoft mixed reality controllers
      * The motion controller class for all microsoft mixed reality controllers
      */
      */
     export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
     export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
@@ -75589,59 +75805,23 @@ interface Window {
 interface Gamepad {
 interface Gamepad {
     readonly displayId: number;
     readonly displayId: number;
 }
 }
-type XRSessionMode =
-    | "inline"
-    | "immersive-vr"
-    | "immersive-ar";
-
-type XRReferenceSpaceType =
-    | "viewer"
-    | "local"
-    | "local-floor"
-    | "bounded-floor"
-    | "unbounded";
+type XRSessionMode = "inline" | "immersive-vr" | "immersive-ar";
 
 
-type XREnvironmentBlendMode =
-    | "opaque"
-    | "additive"
-    | "alpha-blend";
+type XRReferenceSpaceType = "viewer" | "local" | "local-floor" | "bounded-floor" | "unbounded";
 
 
-type XRVisibilityState =
-    | "visible"
-    | "visible-blurred"
-    | "hidden";
+type XREnvironmentBlendMode = "opaque" | "additive" | "alpha-blend";
 
 
-type XRHandedness =
-    | "none"
-    | "left"
-    | "right";
+type XRVisibilityState = "visible" | "visible-blurred" | "hidden";
 
 
-type XRTargetRayMode =
-    | "gaze"
-    | "tracked-pointer"
-    | "screen";
+type XRHandedness = "none" | "left" | "right";
 
 
-type XREye =
-    | "none"
-    | "left"
-    | "right";
+type XRTargetRayMode = "gaze" | "tracked-pointer" | "screen";
 
 
-type XREventType =
-    | "devicechange"
-    | "visibilitychange"
-    | "end"
-    | "inputsourceschange"
-    | "select"
-    | "selectstart"
-    | "selectend"
-    | "squeeze"
-    | "squeezestart"
-    | "squeezeend"
-    | "reset";
+type XREye = "none" | "left" | "right";
 
 
-interface XRSpace extends EventTarget {
+type XREventType = "devicechange" | "visibilitychange" | "end" | "inputsourceschange" | "select" | "selectstart" | "selectend" | "squeeze" | "squeezestart" | "squeezeend" | "reset";
 
 
-}
+interface XRSpace extends EventTarget {}
 
 
 interface XRRenderState {
 interface XRRenderState {
     depthNear?: number;
     depthNear?: number;
@@ -75657,6 +75837,7 @@ interface XRInputSource {
     gripSpace: XRSpace | undefined;
     gripSpace: XRSpace | undefined;
     gamepad: Gamepad | undefined;
     gamepad: Gamepad | undefined;
     profiles: Array<string>;
     profiles: Array<string>;
+    hand: XRHand | undefined;
 }
 }
 
 
 interface XRSessionInit {
 interface XRSessionInit {
@@ -75682,9 +75863,7 @@ interface XRSession {
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
 
 
     // legacy plane detection
     // legacy plane detection
-    updateWorldTrackingState(options: {
-        planeDetectionState?: { enabled: boolean; }
-    }): void;
+    updateWorldTrackingState(options: { planeDetectionState?: { enabled: boolean } }): void;
 }
 }
 
 
 interface XRReferenceSpace extends XRSpace {
 interface XRReferenceSpace extends XRSpace {
@@ -75701,7 +75880,7 @@ interface XRFrame {
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
 
 
     // AR
     // AR
-    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult> ;
+    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     // Anchors
     // Anchors
     trackedAnchors?: XRAnchorSet;
     trackedAnchors?: XRAnchorSet;
@@ -75710,6 +75889,8 @@ interface XRFrame {
     worldInformation: {
     worldInformation: {
         detectedPlanes?: XRPlaneSet;
         detectedPlanes?: XRPlaneSet;
     };
     };
+    // Hand tracking
+    getJointPose(joint: XRJointSpace, baseSpace: XRSpace): XRJointPose;
 }
 }
 
 
 interface XRViewerPose extends XRPose {
 interface XRViewerPose extends XRPose {
@@ -75732,7 +75913,7 @@ interface XRWebGLLayerOptions {
 
 
 declare var XRWebGLLayer: {
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
     prototype: XRWebGLLayer;
-    new(session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
+    new (session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
 };
 };
 interface XRWebGLLayer {
 interface XRWebGLLayer {
     framebuffer: WebGLFramebuffer;
     framebuffer: WebGLFramebuffer;
@@ -75777,7 +75958,7 @@ declare class XRRay {
 declare enum XRHitTestTrackableType {
 declare enum XRHitTestTrackableType {
     "point",
     "point",
     "plane",
     "plane",
-    "mesh"
+    "mesh",
 }
 }
 
 
 interface XRHitResult {
 interface XRHitResult {
@@ -75825,7 +76006,49 @@ interface XRPlane {
     planeSpace: XRSpace;
     planeSpace: XRSpace;
     polygon: Array<DOMPointReadOnly>;
     polygon: Array<DOMPointReadOnly>;
     lastChangedTime: number;
     lastChangedTime: number;
-}
+}
+
+interface XRJointSpace extends XRSpace {}
+
+interface XRJointPose extends XRPose {
+    radius: number | undefined;
+}
+
+declare class XRHand extends Array<XRJointSpace> {
+    readonly length: number;
+
+    static readonly WRIST = 0;
+
+    static readonly THUMB_METACARPAL = 1;
+    static readonly THUMB_PHALANX_PROXIMAL = 2;
+    static readonly THUMB_PHALANX_DISTAL = 3;
+    static readonly THUMB_PHALANX_TIP = 4;
+
+    static readonly INDEX_METACARPAL = 5;
+    static readonly INDEX_PHALANX_PROXIMAL = 6;
+    static readonly INDEX_PHALANX_INTERMEDIATE = 7;
+    static readonly INDEX_PHALANX_DISTAL = 8;
+    static readonly INDEX_PHALANX_TIP = 9;
+
+    static readonly MIDDLE_METACARPAL = 10;
+    static readonly MIDDLE_PHALANX_PROXIMAL = 11;
+    static readonly MIDDLE_PHALANX_INTERMEDIATE = 12;
+    static readonly MIDDLE_PHALANX_DISTAL = 13;
+    static readonly MIDDLE_PHALANX_TIP = 14;
+
+    static readonly RING_METACARPAL = 15;
+    static readonly RING_PHALANX_PROXIMAL = 16;
+    static readonly RING_PHALANX_INTERMEDIATE = 17;
+    static readonly RING_PHALANX_DISTAL = 18;
+    static readonly RING_PHALANX_TIP = 19;
+
+    static readonly LITTLE_METACARPAL = 20;
+    static readonly LITTLE_PHALANX_PROXIMAL = 21;
+    static readonly LITTLE_PHALANX_INTERMEDIATE = 22;
+    static readonly LITTLE_PHALANX_DISTAL = 23;
+    static readonly LITTLE_PHALANX_TIP = 24;
+}
+
 // This file contains native only extensions for WebXR  These APIs are not supported in the browser yet.
 // This file contains native only extensions for WebXR  These APIs are not supported in the browser yet.
 // They are intended for use with either Babylon Native https://github.com/BabylonJS/BabylonNative or
 // They are intended for use with either Babylon Native https://github.com/BabylonJS/BabylonNative or
 // Babylon React Native: https://github.com/BabylonJS/BabylonReactNative
 // Babylon React Native: https://github.com/BabylonJS/BabylonReactNative

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


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


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


+ 512 - 62
dist/preview release/babylon.module.d.ts

@@ -48542,6 +48542,7 @@ declare module "babylonjs/XR/webXRTypes" {
 }
 }
 declare module "babylonjs/XR/webXRManagedOutputCanvas" {
 declare module "babylonjs/XR/webXRManagedOutputCanvas" {
     import { Nullable } from "babylonjs/types";
     import { Nullable } from "babylonjs/types";
+    import { ThinEngine } from "babylonjs/Engines/thinEngine";
     import { WebXRRenderTarget } from "babylonjs/XR/webXRTypes";
     import { WebXRRenderTarget } from "babylonjs/XR/webXRTypes";
     import { WebXRSessionManager } from "babylonjs/XR/webXRSessionManager";
     import { WebXRSessionManager } from "babylonjs/XR/webXRSessionManager";
     import { Observable } from "babylonjs/Misc/observable";
     import { Observable } from "babylonjs/Misc/observable";
@@ -48564,9 +48565,10 @@ declare module "babylonjs/XR/webXRManagedOutputCanvas" {
         newCanvasCssStyle?: string;
         newCanvasCssStyle?: string;
         /**
         /**
          * Get the default values of the configuration object
          * Get the default values of the configuration object
+         * @param engine defines the engine to use (can be null)
          * @returns default values of this configuration object
          * @returns default values of this configuration object
          */
          */
-        static GetDefaults(): WebXRManagedOutputCanvasOptions;
+        static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions;
     }
     }
     /**
     /**
      * Creates a canvas that is added/removed from the webpage when entering/exiting XR
      * Creates a canvas that is added/removed from the webpage when entering/exiting XR
@@ -48860,6 +48862,10 @@ declare module "babylonjs/XR/webXRFeaturesManager" {
          * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
          * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
          */
          */
         xrNativeFeatureName?: string;
         xrNativeFeatureName?: string;
+        /**
+         * A list of (Babylon WebXR) features this feature depends on
+         */
+        dependsOn?: string[];
     }
     }
     /**
     /**
      * A list of the currently available features without referencing them
      * A list of the currently available features without referencing them
@@ -48897,6 +48903,10 @@ declare module "babylonjs/XR/webXRFeaturesManager" {
          * The name of the feature points feature.
          * The name of the feature points feature.
          */
          */
         static readonly FEATURE_POINTS: string;
         static readonly FEATURE_POINTS: string;
+        /**
+         * The name of the hand tracking feature.
+         */
+        static readonly HAND_TRACKING: string;
     }
     }
     /**
     /**
      * Defining the constructor of a feature. Used to register the modules.
      * Defining the constructor of a feature. Used to register the modules.
@@ -49868,7 +49878,7 @@ declare module "babylonjs/XR/motionController/webXRAbstractMotionController" {
         /**
         /**
          * The mesh that will be changed when axis value changes
          * The mesh that will be changed when axis value changes
          */
          */
-        valueMesh: AbstractMesh;
+        valueMesh?: AbstractMesh;
     }
     }
     /**
     /**
      * The elements needed for change-detection of the gamepad objects in motion controllers
      * The elements needed for change-detection of the gamepad objects in motion controllers
@@ -50018,8 +50028,8 @@ declare module "babylonjs/XR/motionController/webXRAbstractMotionController" {
          * @returns a promise that will send true when the pulse has ended and false if the device doesn't support pulse or an error accrued
          * @returns a promise that will send true when the pulse has ended and false if the device doesn't support pulse or an error accrued
          */
          */
         pulse(value: number, duration: number, hapticActuatorIndex?: number): Promise<boolean>;
         pulse(value: number, duration: number, hapticActuatorIndex?: number): Promise<boolean>;
-        protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh;
-        protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh;
+        protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined;
+        protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined;
         /**
         /**
          * Moves the axis on the controller mesh based on its current state
          * Moves the axis on the controller mesh based on its current state
          * @param axis the index of the axis
          * @param axis the index of the axis
@@ -50287,6 +50297,7 @@ declare module "babylonjs/XR/webXRInputSource" {
         private _options;
         private _options;
         private _tmpVector;
         private _tmpVector;
         private _uniqueId;
         private _uniqueId;
+        private _disposed;
         /**
         /**
          * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
          * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
          */
          */
@@ -50840,7 +50851,10 @@ declare module "babylonjs/XR/webXREnterExitUI" {
         options: WebXREnterExitUIOptions;
         options: WebXREnterExitUIOptions;
         private _activeButton;
         private _activeButton;
         private _buttons;
         private _buttons;
-        private _overlay;
+        /**
+         * The HTML Div Element to which buttons are added.
+         */
+        readonly overlay: HTMLDivElement;
         /**
         /**
          * Fired every time the active button is changed.
          * Fired every time the active button is changed.
          *
          *
@@ -79029,6 +79043,218 @@ declare module "babylonjs/XR/features/WebXRFeaturePointSystem" {
         private _init;
         private _init;
     }
     }
 }
 }
+declare module "babylonjs/XR/features/WebXRHandTracking" {
+    import { WebXRAbstractFeature } from "babylonjs/XR/features/WebXRAbstractFeature";
+    import { WebXRSessionManager } from "babylonjs/XR/webXRSessionManager";
+    import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
+    import { Mesh } from "babylonjs/Meshes/mesh";
+    import { WebXRInput } from "babylonjs/XR/webXRInput";
+    import { WebXRInputSource } from "babylonjs/XR/webXRInputSource";
+    import { Nullable } from "babylonjs/types";
+    import { IDisposable } from "babylonjs/scene";
+    import { Observable } from "babylonjs/Misc/observable";
+    /**
+     * Configuration interface for the hand tracking feature
+     */
+    export interface IWebXRHandTrackingOptions {
+        /**
+         * The xrInput that will be used as source for new hands
+         */
+        xrInput: WebXRInput;
+        /**
+         * Configuration object for the joint meshes
+         */
+        jointMeshes?: {
+            /**
+             * Should the meshes created be invisible (defaults to false)
+             */
+            invisible?: boolean;
+            /**
+             * A source mesh to be used to create instances. Defaults to a sphere.
+             * This mesh will be the source for all other (25) meshes.
+             * It should have the general size of a single unit, as the instances will be scaled according to the provided radius
+             */
+            sourceMesh?: Mesh;
+            /**
+             * Should the source mesh stay visible. Defaults to false
+             */
+            keepOriginalVisible?: boolean;
+            /**
+             * Scale factor for all instances (defaults to 2)
+             */
+            scaleFactor?: number;
+            /**
+             * Should each instance have its own physics impostor
+             */
+            enablePhysics?: boolean;
+            /**
+             * If enabled, override default physics properties
+             */
+            physicsProps?: {
+                friction?: number;
+                restitution?: number;
+                impostorType?: number;
+            };
+            /**
+             * For future use - a single hand-mesh that will be updated according to the XRHand data provided
+             */
+            handMesh?: AbstractMesh;
+        };
+    }
+    /**
+     * Parts of the hands divided to writs and finger names
+     */
+    export const enum HandPart {
+        /**
+         * HandPart - Wrist
+         */
+        WRIST = "wrist",
+        /**
+         * HandPart - The THumb
+         */
+        THUMB = "thumb",
+        /**
+         * HandPart - Index finger
+         */
+        INDEX = "index",
+        /**
+         * HandPart - Middle finger
+         */
+        MIDDLE = "middle",
+        /**
+         * HandPart - Ring finger
+         */
+        RING = "ring",
+        /**
+         * HandPart - Little finger
+         */
+        LITTLE = "little"
+    }
+    /**
+     * Representing a single hand (with its corresponding native XRHand object)
+     */
+    export class WebXRHand implements IDisposable {
+        /** the controller to which the hand correlates */
+        readonly xrController: WebXRInputSource;
+        /** the meshes to be used to track the hand joints */
+        readonly trackedMeshes: AbstractMesh[];
+        /**
+         * Hand-parts definition (key is HandPart)
+         */
+        static HandPartsDefinition: {
+            [key: string]: number[];
+        };
+        /**
+         * Populate the HandPartsDefinition object.
+         * This is called as a side effect since certain browsers don't have XRHand defined.
+         */
+        static _PopulateHandPartsDefinition(): void;
+        /**
+         * Construct a new hand object
+         * @param xrController the controller to which the hand correlates
+         * @param trackedMeshes the meshes to be used to track the hand joints
+         */
+        constructor(
+        /** the controller to which the hand correlates */
+        xrController: WebXRInputSource, 
+        /** the meshes to be used to track the hand joints */
+        trackedMeshes: AbstractMesh[]);
+        /**
+         * Update this hand from the latest xr frame
+         * @param xrFrame xrFrame to update from
+         * @param referenceSpace The current viewer reference space
+         * @param scaleFactor optional scale factor for the meshes
+         */
+        updateFromXRFrame(xrFrame: XRFrame, referenceSpace: XRReferenceSpace, scaleFactor?: number): void;
+        /**
+         * Get meshes of part of the hand
+         * @param part the part of hand to get
+         * @returns An array of meshes that correlate to the hand part requested
+         */
+        getHandPartMeshes(part: HandPart): AbstractMesh[];
+        /**
+         * Dispose this Hand object
+         */
+        dispose(): void;
+    }
+    /**
+     * WebXR Hand Joint tracking feature, available for selected browsers and devices
+     */
+    export class WebXRHandTracking extends WebXRAbstractFeature {
+        /**
+         * options to use when constructing this feature
+         */
+        readonly options: IWebXRHandTrackingOptions;
+        private static _idCounter;
+        /**
+         * The module's name
+         */
+        static readonly Name: string;
+        /**
+         * The (Babylon) version of this module.
+         * This is an integer representing the implementation version.
+         * This number does not correspond to the WebXR specs version
+         */
+        static readonly Version: number;
+        /**
+         * This observable will notify registered observers when a new hand object was added and initialized
+         */
+        onHandAddedObservable: Observable<WebXRHand>;
+        /**
+         * This observable will notify its observers right before the hand object is disposed
+         */
+        onHandRemovedObservable: Observable<WebXRHand>;
+        private _hands;
+        /**
+         * Creates a new instance of the hit test feature
+         * @param _xrSessionManager an instance of WebXRSessionManager
+         * @param options options to use when constructing this feature
+         */
+        constructor(_xrSessionManager: WebXRSessionManager, 
+        /**
+         * options to use when constructing this feature
+         */
+        options: IWebXRHandTrackingOptions);
+        /**
+         * Check if the needed objects are defined.
+         * This does not mean that the feature is enabled, but that the objects needed are well defined.
+         */
+        isCompatible(): boolean;
+        /**
+         * attach this feature
+         * Will usually be called by the features manager
+         *
+         * @returns true if successful.
+         */
+        attach(): boolean;
+        /**
+         * detach this feature.
+         * Will usually be called by the features manager
+         *
+         * @returns true if successful.
+         */
+        detach(): boolean;
+        /**
+         * Dispose this feature and all of the resources attached
+         */
+        dispose(): void;
+        /**
+         * Get the hand object according to the controller id
+         * @param controllerId the controller id to which we want to get the hand
+         * @returns null if not found or the WebXRHand object if found
+         */
+        getHandByControllerId(controllerId: string): Nullable<WebXRHand>;
+        /**
+         * Get a hand object according to the requested handedness
+         * @param handedness the handedness to request
+         * @returns null if not found or the WebXRHand object if found
+         */
+        getHandByHandedness(handedness: XRHandedness): Nullable<WebXRHand>;
+        protected _onXRFrame(_xrFrame: XRFrame): void;
+        private _attachHand;
+        private _detachHand;
+    }
+}
 declare module "babylonjs/XR/features/index" {
 declare module "babylonjs/XR/features/index" {
     export * from "babylonjs/XR/features/WebXRHitTestLegacy";
     export * from "babylonjs/XR/features/WebXRHitTestLegacy";
     export * from "babylonjs/XR/features/WebXRAnchorSystem";
     export * from "babylonjs/XR/features/WebXRAnchorSystem";
@@ -79039,6 +79265,7 @@ declare module "babylonjs/XR/features/index" {
     export * from "babylonjs/XR/features/WebXRControllerPhysics";
     export * from "babylonjs/XR/features/WebXRControllerPhysics";
     export * from "babylonjs/XR/features/WebXRHitTest";
     export * from "babylonjs/XR/features/WebXRHitTest";
     export * from "babylonjs/XR/features/WebXRFeaturePointSystem";
     export * from "babylonjs/XR/features/WebXRFeaturePointSystem";
+    export * from "babylonjs/XR/features/WebXRHandTracking";
 }
 }
 declare module "babylonjs/XR/motionController/webXRMicrosoftMixedRealityController" {
 declare module "babylonjs/XR/motionController/webXRMicrosoftMixedRealityController" {
     import { WebXRAbstractMotionController, IMinimalMotionControllerObject, MotionControllerHandedness } from "babylonjs/XR/motionController/webXRAbstractMotionController";
     import { WebXRAbstractMotionController, IMinimalMotionControllerObject, MotionControllerHandedness } from "babylonjs/XR/motionController/webXRAbstractMotionController";
@@ -126183,9 +126410,10 @@ declare module BABYLON {
         newCanvasCssStyle?: string;
         newCanvasCssStyle?: string;
         /**
         /**
          * Get the default values of the configuration object
          * Get the default values of the configuration object
+         * @param engine defines the engine to use (can be null)
          * @returns default values of this configuration object
          * @returns default values of this configuration object
          */
          */
-        static GetDefaults(): WebXRManagedOutputCanvasOptions;
+        static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions;
     }
     }
     /**
     /**
      * Creates a canvas that is added/removed from the webpage when entering/exiting XR
      * Creates a canvas that is added/removed from the webpage when entering/exiting XR
@@ -126465,6 +126693,10 @@ declare module BABYLON {
          * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
          * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
          */
          */
         xrNativeFeatureName?: string;
         xrNativeFeatureName?: string;
+        /**
+         * A list of (Babylon WebXR) features this feature depends on
+         */
+        dependsOn?: string[];
     }
     }
     /**
     /**
      * A list of the currently available features without referencing them
      * A list of the currently available features without referencing them
@@ -126502,6 +126734,10 @@ declare module BABYLON {
          * The name of the feature points feature.
          * The name of the feature points feature.
          */
          */
         static readonly FEATURE_POINTS: string;
         static readonly FEATURE_POINTS: string;
+        /**
+         * The name of the hand tracking feature.
+         */
+        static readonly HAND_TRACKING: string;
     }
     }
     /**
     /**
      * Defining the constructor of a feature. Used to register the modules.
      * Defining the constructor of a feature. Used to register the modules.
@@ -127448,7 +127684,7 @@ declare module BABYLON {
         /**
         /**
          * The mesh that will be changed when axis value changes
          * The mesh that will be changed when axis value changes
          */
          */
-        valueMesh: AbstractMesh;
+        valueMesh?: AbstractMesh;
     }
     }
     /**
     /**
      * The elements needed for change-detection of the gamepad objects in motion controllers
      * The elements needed for change-detection of the gamepad objects in motion controllers
@@ -127598,8 +127834,8 @@ declare module BABYLON {
          * @returns a promise that will send true when the pulse has ended and false if the device doesn't support pulse or an error accrued
          * @returns a promise that will send true when the pulse has ended and false if the device doesn't support pulse or an error accrued
          */
          */
         pulse(value: number, duration: number, hapticActuatorIndex?: number): Promise<boolean>;
         pulse(value: number, duration: number, hapticActuatorIndex?: number): Promise<boolean>;
-        protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh;
-        protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh;
+        protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined;
+        protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined;
         /**
         /**
          * Moves the axis on the controller mesh based on its current state
          * Moves the axis on the controller mesh based on its current state
          * @param axis the index of the axis
          * @param axis the index of the axis
@@ -127850,6 +128086,7 @@ declare module BABYLON {
         private _options;
         private _options;
         private _tmpVector;
         private _tmpVector;
         private _uniqueId;
         private _uniqueId;
+        private _disposed;
         /**
         /**
          * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
          * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
          */
          */
@@ -128375,7 +128612,10 @@ declare module BABYLON {
         options: WebXREnterExitUIOptions;
         options: WebXREnterExitUIOptions;
         private _activeButton;
         private _activeButton;
         private _buttons;
         private _buttons;
-        private _overlay;
+        /**
+         * The HTML Div Element to which buttons are added.
+         */
+        readonly overlay: HTMLDivElement;
         /**
         /**
          * Fired every time the active button is changed.
          * Fired every time the active button is changed.
          *
          *
@@ -154244,6 +154484,209 @@ declare module BABYLON {
 }
 }
 declare module BABYLON {
 declare module BABYLON {
     /**
     /**
+     * Configuration interface for the hand tracking feature
+     */
+    export interface IWebXRHandTrackingOptions {
+        /**
+         * The xrInput that will be used as source for new hands
+         */
+        xrInput: WebXRInput;
+        /**
+         * Configuration object for the joint meshes
+         */
+        jointMeshes?: {
+            /**
+             * Should the meshes created be invisible (defaults to false)
+             */
+            invisible?: boolean;
+            /**
+             * A source mesh to be used to create instances. Defaults to a sphere.
+             * This mesh will be the source for all other (25) meshes.
+             * It should have the general size of a single unit, as the instances will be scaled according to the provided radius
+             */
+            sourceMesh?: Mesh;
+            /**
+             * Should the source mesh stay visible. Defaults to false
+             */
+            keepOriginalVisible?: boolean;
+            /**
+             * Scale factor for all instances (defaults to 2)
+             */
+            scaleFactor?: number;
+            /**
+             * Should each instance have its own physics impostor
+             */
+            enablePhysics?: boolean;
+            /**
+             * If enabled, override default physics properties
+             */
+            physicsProps?: {
+                friction?: number;
+                restitution?: number;
+                impostorType?: number;
+            };
+            /**
+             * For future use - a single hand-mesh that will be updated according to the XRHand data provided
+             */
+            handMesh?: AbstractMesh;
+        };
+    }
+    /**
+     * Parts of the hands divided to writs and finger names
+     */
+    export const enum HandPart {
+        /**
+         * HandPart - Wrist
+         */
+        WRIST = "wrist",
+        /**
+         * HandPart - The THumb
+         */
+        THUMB = "thumb",
+        /**
+         * HandPart - Index finger
+         */
+        INDEX = "index",
+        /**
+         * HandPart - Middle finger
+         */
+        MIDDLE = "middle",
+        /**
+         * HandPart - Ring finger
+         */
+        RING = "ring",
+        /**
+         * HandPart - Little finger
+         */
+        LITTLE = "little"
+    }
+    /**
+     * Representing a single hand (with its corresponding native XRHand object)
+     */
+    export class WebXRHand implements IDisposable {
+        /** the controller to which the hand correlates */
+        readonly xrController: WebXRInputSource;
+        /** the meshes to be used to track the hand joints */
+        readonly trackedMeshes: AbstractMesh[];
+        /**
+         * Hand-parts definition (key is HandPart)
+         */
+        static HandPartsDefinition: {
+            [key: string]: number[];
+        };
+        /**
+         * Populate the HandPartsDefinition object.
+         * This is called as a side effect since certain browsers don't have XRHand defined.
+         */
+        static _PopulateHandPartsDefinition(): void;
+        /**
+         * Construct a new hand object
+         * @param xrController the controller to which the hand correlates
+         * @param trackedMeshes the meshes to be used to track the hand joints
+         */
+        constructor(
+        /** the controller to which the hand correlates */
+        xrController: WebXRInputSource, 
+        /** the meshes to be used to track the hand joints */
+        trackedMeshes: AbstractMesh[]);
+        /**
+         * Update this hand from the latest xr frame
+         * @param xrFrame xrFrame to update from
+         * @param referenceSpace The current viewer reference space
+         * @param scaleFactor optional scale factor for the meshes
+         */
+        updateFromXRFrame(xrFrame: XRFrame, referenceSpace: XRReferenceSpace, scaleFactor?: number): void;
+        /**
+         * Get meshes of part of the hand
+         * @param part the part of hand to get
+         * @returns An array of meshes that correlate to the hand part requested
+         */
+        getHandPartMeshes(part: HandPart): AbstractMesh[];
+        /**
+         * Dispose this Hand object
+         */
+        dispose(): void;
+    }
+    /**
+     * WebXR Hand Joint tracking feature, available for selected browsers and devices
+     */
+    export class WebXRHandTracking extends WebXRAbstractFeature {
+        /**
+         * options to use when constructing this feature
+         */
+        readonly options: IWebXRHandTrackingOptions;
+        private static _idCounter;
+        /**
+         * The module's name
+         */
+        static readonly Name: string;
+        /**
+         * The (Babylon) version of this module.
+         * This is an integer representing the implementation version.
+         * This number does not correspond to the WebXR specs version
+         */
+        static readonly Version: number;
+        /**
+         * This observable will notify registered observers when a new hand object was added and initialized
+         */
+        onHandAddedObservable: Observable<WebXRHand>;
+        /**
+         * This observable will notify its observers right before the hand object is disposed
+         */
+        onHandRemovedObservable: Observable<WebXRHand>;
+        private _hands;
+        /**
+         * Creates a new instance of the hit test feature
+         * @param _xrSessionManager an instance of WebXRSessionManager
+         * @param options options to use when constructing this feature
+         */
+        constructor(_xrSessionManager: WebXRSessionManager, 
+        /**
+         * options to use when constructing this feature
+         */
+        options: IWebXRHandTrackingOptions);
+        /**
+         * Check if the needed objects are defined.
+         * This does not mean that the feature is enabled, but that the objects needed are well defined.
+         */
+        isCompatible(): boolean;
+        /**
+         * attach this feature
+         * Will usually be called by the features manager
+         *
+         * @returns true if successful.
+         */
+        attach(): boolean;
+        /**
+         * detach this feature.
+         * Will usually be called by the features manager
+         *
+         * @returns true if successful.
+         */
+        detach(): boolean;
+        /**
+         * Dispose this feature and all of the resources attached
+         */
+        dispose(): void;
+        /**
+         * Get the hand object according to the controller id
+         * @param controllerId the controller id to which we want to get the hand
+         * @returns null if not found or the WebXRHand object if found
+         */
+        getHandByControllerId(controllerId: string): Nullable<WebXRHand>;
+        /**
+         * Get a hand object according to the requested handedness
+         * @param handedness the handedness to request
+         * @returns null if not found or the WebXRHand object if found
+         */
+        getHandByHandedness(handedness: XRHandedness): Nullable<WebXRHand>;
+        protected _onXRFrame(_xrFrame: XRFrame): void;
+        private _attachHand;
+        private _detachHand;
+    }
+}
+declare module BABYLON {
+    /**
      * The motion controller class for all microsoft mixed reality controllers
      * The motion controller class for all microsoft mixed reality controllers
      */
      */
     export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
     export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
@@ -154972,59 +155415,23 @@ interface Window {
 interface Gamepad {
 interface Gamepad {
     readonly displayId: number;
     readonly displayId: number;
 }
 }
-type XRSessionMode =
-    | "inline"
-    | "immersive-vr"
-    | "immersive-ar";
+type XRSessionMode = "inline" | "immersive-vr" | "immersive-ar";
 
 
-type XRReferenceSpaceType =
-    | "viewer"
-    | "local"
-    | "local-floor"
-    | "bounded-floor"
-    | "unbounded";
+type XRReferenceSpaceType = "viewer" | "local" | "local-floor" | "bounded-floor" | "unbounded";
 
 
-type XREnvironmentBlendMode =
-    | "opaque"
-    | "additive"
-    | "alpha-blend";
+type XREnvironmentBlendMode = "opaque" | "additive" | "alpha-blend";
 
 
-type XRVisibilityState =
-    | "visible"
-    | "visible-blurred"
-    | "hidden";
+type XRVisibilityState = "visible" | "visible-blurred" | "hidden";
 
 
-type XRHandedness =
-    | "none"
-    | "left"
-    | "right";
+type XRHandedness = "none" | "left" | "right";
 
 
-type XRTargetRayMode =
-    | "gaze"
-    | "tracked-pointer"
-    | "screen";
+type XRTargetRayMode = "gaze" | "tracked-pointer" | "screen";
 
 
-type XREye =
-    | "none"
-    | "left"
-    | "right";
+type XREye = "none" | "left" | "right";
 
 
-type XREventType =
-    | "devicechange"
-    | "visibilitychange"
-    | "end"
-    | "inputsourceschange"
-    | "select"
-    | "selectstart"
-    | "selectend"
-    | "squeeze"
-    | "squeezestart"
-    | "squeezeend"
-    | "reset";
+type XREventType = "devicechange" | "visibilitychange" | "end" | "inputsourceschange" | "select" | "selectstart" | "selectend" | "squeeze" | "squeezestart" | "squeezeend" | "reset";
 
 
-interface XRSpace extends EventTarget {
-
-}
+interface XRSpace extends EventTarget {}
 
 
 interface XRRenderState {
 interface XRRenderState {
     depthNear?: number;
     depthNear?: number;
@@ -155040,6 +155447,7 @@ interface XRInputSource {
     gripSpace: XRSpace | undefined;
     gripSpace: XRSpace | undefined;
     gamepad: Gamepad | undefined;
     gamepad: Gamepad | undefined;
     profiles: Array<string>;
     profiles: Array<string>;
+    hand: XRHand | undefined;
 }
 }
 
 
 interface XRSessionInit {
 interface XRSessionInit {
@@ -155065,9 +155473,7 @@ interface XRSession {
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
 
 
     // legacy plane detection
     // legacy plane detection
-    updateWorldTrackingState(options: {
-        planeDetectionState?: { enabled: boolean; }
-    }): void;
+    updateWorldTrackingState(options: { planeDetectionState?: { enabled: boolean } }): void;
 }
 }
 
 
 interface XRReferenceSpace extends XRSpace {
 interface XRReferenceSpace extends XRSpace {
@@ -155084,7 +155490,7 @@ interface XRFrame {
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
 
 
     // AR
     // AR
-    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult> ;
+    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     // Anchors
     // Anchors
     trackedAnchors?: XRAnchorSet;
     trackedAnchors?: XRAnchorSet;
@@ -155093,6 +155499,8 @@ interface XRFrame {
     worldInformation: {
     worldInformation: {
         detectedPlanes?: XRPlaneSet;
         detectedPlanes?: XRPlaneSet;
     };
     };
+    // Hand tracking
+    getJointPose(joint: XRJointSpace, baseSpace: XRSpace): XRJointPose;
 }
 }
 
 
 interface XRViewerPose extends XRPose {
 interface XRViewerPose extends XRPose {
@@ -155115,7 +155523,7 @@ interface XRWebGLLayerOptions {
 
 
 declare var XRWebGLLayer: {
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
     prototype: XRWebGLLayer;
-    new(session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
+    new (session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
 };
 };
 interface XRWebGLLayer {
 interface XRWebGLLayer {
     framebuffer: WebGLFramebuffer;
     framebuffer: WebGLFramebuffer;
@@ -155160,7 +155568,7 @@ declare class XRRay {
 declare enum XRHitTestTrackableType {
 declare enum XRHitTestTrackableType {
     "point",
     "point",
     "plane",
     "plane",
-    "mesh"
+    "mesh",
 }
 }
 
 
 interface XRHitResult {
 interface XRHitResult {
@@ -155208,7 +155616,49 @@ interface XRPlane {
     planeSpace: XRSpace;
     planeSpace: XRSpace;
     polygon: Array<DOMPointReadOnly>;
     polygon: Array<DOMPointReadOnly>;
     lastChangedTime: number;
     lastChangedTime: number;
-}
+}
+
+interface XRJointSpace extends XRSpace {}
+
+interface XRJointPose extends XRPose {
+    radius: number | undefined;
+}
+
+declare class XRHand extends Array<XRJointSpace> {
+    readonly length: number;
+
+    static readonly WRIST = 0;
+
+    static readonly THUMB_METACARPAL = 1;
+    static readonly THUMB_PHALANX_PROXIMAL = 2;
+    static readonly THUMB_PHALANX_DISTAL = 3;
+    static readonly THUMB_PHALANX_TIP = 4;
+
+    static readonly INDEX_METACARPAL = 5;
+    static readonly INDEX_PHALANX_PROXIMAL = 6;
+    static readonly INDEX_PHALANX_INTERMEDIATE = 7;
+    static readonly INDEX_PHALANX_DISTAL = 8;
+    static readonly INDEX_PHALANX_TIP = 9;
+
+    static readonly MIDDLE_METACARPAL = 10;
+    static readonly MIDDLE_PHALANX_PROXIMAL = 11;
+    static readonly MIDDLE_PHALANX_INTERMEDIATE = 12;
+    static readonly MIDDLE_PHALANX_DISTAL = 13;
+    static readonly MIDDLE_PHALANX_TIP = 14;
+
+    static readonly RING_METACARPAL = 15;
+    static readonly RING_PHALANX_PROXIMAL = 16;
+    static readonly RING_PHALANX_INTERMEDIATE = 17;
+    static readonly RING_PHALANX_DISTAL = 18;
+    static readonly RING_PHALANX_TIP = 19;
+
+    static readonly LITTLE_METACARPAL = 20;
+    static readonly LITTLE_PHALANX_PROXIMAL = 21;
+    static readonly LITTLE_PHALANX_INTERMEDIATE = 22;
+    static readonly LITTLE_PHALANX_DISTAL = 23;
+    static readonly LITTLE_PHALANX_TIP = 24;
+}
+
 // This file contains native only extensions for WebXR  These APIs are not supported in the browser yet.
 // This file contains native only extensions for WebXR  These APIs are not supported in the browser yet.
 // They are intended for use with either Babylon Native https://github.com/BabylonJS/BabylonNative or
 // They are intended for use with either Babylon Native https://github.com/BabylonJS/BabylonNative or
 // Babylon React Native: https://github.com/BabylonJS/BabylonReactNative
 // Babylon React Native: https://github.com/BabylonJS/BabylonReactNative

+ 280 - 57
dist/preview release/documentation.d.ts

@@ -46800,9 +46800,10 @@ declare module BABYLON {
         newCanvasCssStyle?: string;
         newCanvasCssStyle?: string;
         /**
         /**
          * Get the default values of the configuration object
          * Get the default values of the configuration object
+         * @param engine defines the engine to use (can be null)
          * @returns default values of this configuration object
          * @returns default values of this configuration object
          */
          */
-        static GetDefaults(): WebXRManagedOutputCanvasOptions;
+        static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions;
     }
     }
     /**
     /**
      * Creates a canvas that is added/removed from the webpage when entering/exiting XR
      * Creates a canvas that is added/removed from the webpage when entering/exiting XR
@@ -47082,6 +47083,10 @@ declare module BABYLON {
          * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
          * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
          */
          */
         xrNativeFeatureName?: string;
         xrNativeFeatureName?: string;
+        /**
+         * A list of (Babylon WebXR) features this feature depends on
+         */
+        dependsOn?: string[];
     }
     }
     /**
     /**
      * A list of the currently available features without referencing them
      * A list of the currently available features without referencing them
@@ -47119,6 +47124,10 @@ declare module BABYLON {
          * The name of the feature points feature.
          * The name of the feature points feature.
          */
          */
         static readonly FEATURE_POINTS: string;
         static readonly FEATURE_POINTS: string;
+        /**
+         * The name of the hand tracking feature.
+         */
+        static readonly HAND_TRACKING: string;
     }
     }
     /**
     /**
      * Defining the constructor of a feature. Used to register the modules.
      * Defining the constructor of a feature. Used to register the modules.
@@ -48065,7 +48074,7 @@ declare module BABYLON {
         /**
         /**
          * The mesh that will be changed when axis value changes
          * The mesh that will be changed when axis value changes
          */
          */
-        valueMesh: AbstractMesh;
+        valueMesh?: AbstractMesh;
     }
     }
     /**
     /**
      * The elements needed for change-detection of the gamepad objects in motion controllers
      * The elements needed for change-detection of the gamepad objects in motion controllers
@@ -48215,8 +48224,8 @@ declare module BABYLON {
          * @returns a promise that will send true when the pulse has ended and false if the device doesn't support pulse or an error accrued
          * @returns a promise that will send true when the pulse has ended and false if the device doesn't support pulse or an error accrued
          */
          */
         pulse(value: number, duration: number, hapticActuatorIndex?: number): Promise<boolean>;
         pulse(value: number, duration: number, hapticActuatorIndex?: number): Promise<boolean>;
-        protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh;
-        protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh;
+        protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined;
+        protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined;
         /**
         /**
          * Moves the axis on the controller mesh based on its current state
          * Moves the axis on the controller mesh based on its current state
          * @param axis the index of the axis
          * @param axis the index of the axis
@@ -48467,6 +48476,7 @@ declare module BABYLON {
         private _options;
         private _options;
         private _tmpVector;
         private _tmpVector;
         private _uniqueId;
         private _uniqueId;
+        private _disposed;
         /**
         /**
          * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
          * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
          */
          */
@@ -48992,7 +49002,10 @@ declare module BABYLON {
         options: WebXREnterExitUIOptions;
         options: WebXREnterExitUIOptions;
         private _activeButton;
         private _activeButton;
         private _buttons;
         private _buttons;
-        private _overlay;
+        /**
+         * The HTML Div Element to which buttons are added.
+         */
+        readonly overlay: HTMLDivElement;
         /**
         /**
          * Fired every time the active button is changed.
          * Fired every time the active button is changed.
          *
          *
@@ -74861,6 +74874,209 @@ declare module BABYLON {
 }
 }
 declare module BABYLON {
 declare module BABYLON {
     /**
     /**
+     * Configuration interface for the hand tracking feature
+     */
+    export interface IWebXRHandTrackingOptions {
+        /**
+         * The xrInput that will be used as source for new hands
+         */
+        xrInput: WebXRInput;
+        /**
+         * Configuration object for the joint meshes
+         */
+        jointMeshes?: {
+            /**
+             * Should the meshes created be invisible (defaults to false)
+             */
+            invisible?: boolean;
+            /**
+             * A source mesh to be used to create instances. Defaults to a sphere.
+             * This mesh will be the source for all other (25) meshes.
+             * It should have the general size of a single unit, as the instances will be scaled according to the provided radius
+             */
+            sourceMesh?: Mesh;
+            /**
+             * Should the source mesh stay visible. Defaults to false
+             */
+            keepOriginalVisible?: boolean;
+            /**
+             * Scale factor for all instances (defaults to 2)
+             */
+            scaleFactor?: number;
+            /**
+             * Should each instance have its own physics impostor
+             */
+            enablePhysics?: boolean;
+            /**
+             * If enabled, override default physics properties
+             */
+            physicsProps?: {
+                friction?: number;
+                restitution?: number;
+                impostorType?: number;
+            };
+            /**
+             * For future use - a single hand-mesh that will be updated according to the XRHand data provided
+             */
+            handMesh?: AbstractMesh;
+        };
+    }
+    /**
+     * Parts of the hands divided to writs and finger names
+     */
+    export const enum HandPart {
+        /**
+         * HandPart - Wrist
+         */
+        WRIST = "wrist",
+        /**
+         * HandPart - The THumb
+         */
+        THUMB = "thumb",
+        /**
+         * HandPart - Index finger
+         */
+        INDEX = "index",
+        /**
+         * HandPart - Middle finger
+         */
+        MIDDLE = "middle",
+        /**
+         * HandPart - Ring finger
+         */
+        RING = "ring",
+        /**
+         * HandPart - Little finger
+         */
+        LITTLE = "little"
+    }
+    /**
+     * Representing a single hand (with its corresponding native XRHand object)
+     */
+    export class WebXRHand implements IDisposable {
+        /** the controller to which the hand correlates */
+        readonly xrController: WebXRInputSource;
+        /** the meshes to be used to track the hand joints */
+        readonly trackedMeshes: AbstractMesh[];
+        /**
+         * Hand-parts definition (key is HandPart)
+         */
+        static HandPartsDefinition: {
+            [key: string]: number[];
+        };
+        /**
+         * Populate the HandPartsDefinition object.
+         * This is called as a side effect since certain browsers don't have XRHand defined.
+         */
+        static _PopulateHandPartsDefinition(): void;
+        /**
+         * Construct a new hand object
+         * @param xrController the controller to which the hand correlates
+         * @param trackedMeshes the meshes to be used to track the hand joints
+         */
+        constructor(
+        /** the controller to which the hand correlates */
+        xrController: WebXRInputSource, 
+        /** the meshes to be used to track the hand joints */
+        trackedMeshes: AbstractMesh[]);
+        /**
+         * Update this hand from the latest xr frame
+         * @param xrFrame xrFrame to update from
+         * @param referenceSpace The current viewer reference space
+         * @param scaleFactor optional scale factor for the meshes
+         */
+        updateFromXRFrame(xrFrame: XRFrame, referenceSpace: XRReferenceSpace, scaleFactor?: number): void;
+        /**
+         * Get meshes of part of the hand
+         * @param part the part of hand to get
+         * @returns An array of meshes that correlate to the hand part requested
+         */
+        getHandPartMeshes(part: HandPart): AbstractMesh[];
+        /**
+         * Dispose this Hand object
+         */
+        dispose(): void;
+    }
+    /**
+     * WebXR Hand Joint tracking feature, available for selected browsers and devices
+     */
+    export class WebXRHandTracking extends WebXRAbstractFeature {
+        /**
+         * options to use when constructing this feature
+         */
+        readonly options: IWebXRHandTrackingOptions;
+        private static _idCounter;
+        /**
+         * The module's name
+         */
+        static readonly Name: string;
+        /**
+         * The (Babylon) version of this module.
+         * This is an integer representing the implementation version.
+         * This number does not correspond to the WebXR specs version
+         */
+        static readonly Version: number;
+        /**
+         * This observable will notify registered observers when a new hand object was added and initialized
+         */
+        onHandAddedObservable: Observable<WebXRHand>;
+        /**
+         * This observable will notify its observers right before the hand object is disposed
+         */
+        onHandRemovedObservable: Observable<WebXRHand>;
+        private _hands;
+        /**
+         * Creates a new instance of the hit test feature
+         * @param _xrSessionManager an instance of WebXRSessionManager
+         * @param options options to use when constructing this feature
+         */
+        constructor(_xrSessionManager: WebXRSessionManager, 
+        /**
+         * options to use when constructing this feature
+         */
+        options: IWebXRHandTrackingOptions);
+        /**
+         * Check if the needed objects are defined.
+         * This does not mean that the feature is enabled, but that the objects needed are well defined.
+         */
+        isCompatible(): boolean;
+        /**
+         * attach this feature
+         * Will usually be called by the features manager
+         *
+         * @returns true if successful.
+         */
+        attach(): boolean;
+        /**
+         * detach this feature.
+         * Will usually be called by the features manager
+         *
+         * @returns true if successful.
+         */
+        detach(): boolean;
+        /**
+         * Dispose this feature and all of the resources attached
+         */
+        dispose(): void;
+        /**
+         * Get the hand object according to the controller id
+         * @param controllerId the controller id to which we want to get the hand
+         * @returns null if not found or the WebXRHand object if found
+         */
+        getHandByControllerId(controllerId: string): Nullable<WebXRHand>;
+        /**
+         * Get a hand object according to the requested handedness
+         * @param handedness the handedness to request
+         * @returns null if not found or the WebXRHand object if found
+         */
+        getHandByHandedness(handedness: XRHandedness): Nullable<WebXRHand>;
+        protected _onXRFrame(_xrFrame: XRFrame): void;
+        private _attachHand;
+        private _detachHand;
+    }
+}
+declare module BABYLON {
+    /**
      * The motion controller class for all microsoft mixed reality controllers
      * The motion controller class for all microsoft mixed reality controllers
      */
      */
     export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
     export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
@@ -75589,59 +75805,23 @@ interface Window {
 interface Gamepad {
 interface Gamepad {
     readonly displayId: number;
     readonly displayId: number;
 }
 }
-type XRSessionMode =
-    | "inline"
-    | "immersive-vr"
-    | "immersive-ar";
-
-type XRReferenceSpaceType =
-    | "viewer"
-    | "local"
-    | "local-floor"
-    | "bounded-floor"
-    | "unbounded";
+type XRSessionMode = "inline" | "immersive-vr" | "immersive-ar";
 
 
-type XREnvironmentBlendMode =
-    | "opaque"
-    | "additive"
-    | "alpha-blend";
+type XRReferenceSpaceType = "viewer" | "local" | "local-floor" | "bounded-floor" | "unbounded";
 
 
-type XRVisibilityState =
-    | "visible"
-    | "visible-blurred"
-    | "hidden";
+type XREnvironmentBlendMode = "opaque" | "additive" | "alpha-blend";
 
 
-type XRHandedness =
-    | "none"
-    | "left"
-    | "right";
+type XRVisibilityState = "visible" | "visible-blurred" | "hidden";
 
 
-type XRTargetRayMode =
-    | "gaze"
-    | "tracked-pointer"
-    | "screen";
+type XRHandedness = "none" | "left" | "right";
 
 
-type XREye =
-    | "none"
-    | "left"
-    | "right";
+type XRTargetRayMode = "gaze" | "tracked-pointer" | "screen";
 
 
-type XREventType =
-    | "devicechange"
-    | "visibilitychange"
-    | "end"
-    | "inputsourceschange"
-    | "select"
-    | "selectstart"
-    | "selectend"
-    | "squeeze"
-    | "squeezestart"
-    | "squeezeend"
-    | "reset";
+type XREye = "none" | "left" | "right";
 
 
-interface XRSpace extends EventTarget {
+type XREventType = "devicechange" | "visibilitychange" | "end" | "inputsourceschange" | "select" | "selectstart" | "selectend" | "squeeze" | "squeezestart" | "squeezeend" | "reset";
 
 
-}
+interface XRSpace extends EventTarget {}
 
 
 interface XRRenderState {
 interface XRRenderState {
     depthNear?: number;
     depthNear?: number;
@@ -75657,6 +75837,7 @@ interface XRInputSource {
     gripSpace: XRSpace | undefined;
     gripSpace: XRSpace | undefined;
     gamepad: Gamepad | undefined;
     gamepad: Gamepad | undefined;
     profiles: Array<string>;
     profiles: Array<string>;
+    hand: XRHand | undefined;
 }
 }
 
 
 interface XRSessionInit {
 interface XRSessionInit {
@@ -75682,9 +75863,7 @@ interface XRSession {
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
 
 
     // legacy plane detection
     // legacy plane detection
-    updateWorldTrackingState(options: {
-        planeDetectionState?: { enabled: boolean; }
-    }): void;
+    updateWorldTrackingState(options: { planeDetectionState?: { enabled: boolean } }): void;
 }
 }
 
 
 interface XRReferenceSpace extends XRSpace {
 interface XRReferenceSpace extends XRSpace {
@@ -75701,7 +75880,7 @@ interface XRFrame {
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
 
 
     // AR
     // AR
-    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult> ;
+    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     // Anchors
     // Anchors
     trackedAnchors?: XRAnchorSet;
     trackedAnchors?: XRAnchorSet;
@@ -75710,6 +75889,8 @@ interface XRFrame {
     worldInformation: {
     worldInformation: {
         detectedPlanes?: XRPlaneSet;
         detectedPlanes?: XRPlaneSet;
     };
     };
+    // Hand tracking
+    getJointPose(joint: XRJointSpace, baseSpace: XRSpace): XRJointPose;
 }
 }
 
 
 interface XRViewerPose extends XRPose {
 interface XRViewerPose extends XRPose {
@@ -75732,7 +75913,7 @@ interface XRWebGLLayerOptions {
 
 
 declare var XRWebGLLayer: {
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
     prototype: XRWebGLLayer;
-    new(session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
+    new (session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
 };
 };
 interface XRWebGLLayer {
 interface XRWebGLLayer {
     framebuffer: WebGLFramebuffer;
     framebuffer: WebGLFramebuffer;
@@ -75777,7 +75958,7 @@ declare class XRRay {
 declare enum XRHitTestTrackableType {
 declare enum XRHitTestTrackableType {
     "point",
     "point",
     "plane",
     "plane",
-    "mesh"
+    "mesh",
 }
 }
 
 
 interface XRHitResult {
 interface XRHitResult {
@@ -75825,7 +76006,49 @@ interface XRPlane {
     planeSpace: XRSpace;
     planeSpace: XRSpace;
     polygon: Array<DOMPointReadOnly>;
     polygon: Array<DOMPointReadOnly>;
     lastChangedTime: number;
     lastChangedTime: number;
-}
+}
+
+interface XRJointSpace extends XRSpace {}
+
+interface XRJointPose extends XRPose {
+    radius: number | undefined;
+}
+
+declare class XRHand extends Array<XRJointSpace> {
+    readonly length: number;
+
+    static readonly WRIST = 0;
+
+    static readonly THUMB_METACARPAL = 1;
+    static readonly THUMB_PHALANX_PROXIMAL = 2;
+    static readonly THUMB_PHALANX_DISTAL = 3;
+    static readonly THUMB_PHALANX_TIP = 4;
+
+    static readonly INDEX_METACARPAL = 5;
+    static readonly INDEX_PHALANX_PROXIMAL = 6;
+    static readonly INDEX_PHALANX_INTERMEDIATE = 7;
+    static readonly INDEX_PHALANX_DISTAL = 8;
+    static readonly INDEX_PHALANX_TIP = 9;
+
+    static readonly MIDDLE_METACARPAL = 10;
+    static readonly MIDDLE_PHALANX_PROXIMAL = 11;
+    static readonly MIDDLE_PHALANX_INTERMEDIATE = 12;
+    static readonly MIDDLE_PHALANX_DISTAL = 13;
+    static readonly MIDDLE_PHALANX_TIP = 14;
+
+    static readonly RING_METACARPAL = 15;
+    static readonly RING_PHALANX_PROXIMAL = 16;
+    static readonly RING_PHALANX_INTERMEDIATE = 17;
+    static readonly RING_PHALANX_DISTAL = 18;
+    static readonly RING_PHALANX_TIP = 19;
+
+    static readonly LITTLE_METACARPAL = 20;
+    static readonly LITTLE_PHALANX_PROXIMAL = 21;
+    static readonly LITTLE_PHALANX_INTERMEDIATE = 22;
+    static readonly LITTLE_PHALANX_DISTAL = 23;
+    static readonly LITTLE_PHALANX_TIP = 24;
+}
+
 // This file contains native only extensions for WebXR  These APIs are not supported in the browser yet.
 // This file contains native only extensions for WebXR  These APIs are not supported in the browser yet.
 // They are intended for use with either Babylon Native https://github.com/BabylonJS/BabylonNative or
 // They are intended for use with either Babylon Native https://github.com/BabylonJS/BabylonNative or
 // Babylon React Native: https://github.com/BabylonJS/BabylonReactNative
 // Babylon React Native: https://github.com/BabylonJS/BabylonReactNative

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


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


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


+ 66 - 11
dist/preview release/inspector/babylon.inspector.d.ts

@@ -1282,6 +1282,20 @@ declare module INSPECTOR {
     }
     }
 }
 }
 declare module INSPECTOR {
 declare module INSPECTOR {
+    /** @hidden */
+    export var lodPixelShader: {
+        name: string;
+        shader: string;
+    };
+}
+declare module INSPECTOR {
+    /** @hidden */
+    export var lodCubePixelShader: {
+        name: string;
+        shader: string;
+    };
+}
+declare module INSPECTOR {
     export interface TextureChannelsToDisplay {
     export interface TextureChannelsToDisplay {
         R: boolean;
         R: boolean;
         G: boolean;
         G: boolean;
@@ -1290,7 +1304,7 @@ declare module INSPECTOR {
     }
     }
     export class TextureHelper {
     export class TextureHelper {
         private static _ProcessAsync;
         private static _ProcessAsync;
-        static GetTextureDataAsync(texture: BABYLON.BaseTexture, width: number, height: number, face: number, channels: TextureChannelsToDisplay, globalState?: GlobalState): Promise<Uint8Array>;
+        static GetTextureDataAsync(texture: BABYLON.BaseTexture, width: number, height: number, face: number, channels: TextureChannelsToDisplay, globalState?: GlobalState, lod?: number): Promise<Uint8Array>;
     }
     }
 }
 }
 declare module INSPECTOR {
 declare module INSPECTOR {
@@ -1327,7 +1341,7 @@ declare module INSPECTOR {
         addTool(url: string): void;
         addTool(url: string): void;
         changeTool(toolIndex: number): void;
         changeTool(toolIndex: number): void;
         activeToolIndex: number;
         activeToolIndex: number;
-        metadata: any;
+        metadata: IMetadata;
         setMetadata(data: any): void;
         setMetadata(data: any): void;
     }
     }
     interface IToolBarState {
     interface IToolBarState {
@@ -1379,6 +1393,7 @@ declare module INSPECTOR {
         private _engine;
         private _engine;
         private _scene;
         private _scene;
         private _camera;
         private _camera;
+        private _cameraPos;
         private _scale;
         private _scale;
         private _isPanning;
         private _isPanning;
         private _mouseX;
         private _mouseX;
@@ -1394,6 +1409,7 @@ declare module INSPECTOR {
         private _3DScene;
         private _3DScene;
         private _channels;
         private _channels;
         private _face;
         private _face;
+        private _mipLevel;
         private _originalTexture;
         private _originalTexture;
         private _target;
         private _target;
         private _originalInternalTexture;
         private _originalInternalTexture;
@@ -1409,14 +1425,26 @@ declare module INSPECTOR {
         private static PAN_MOUSE_BUTTON;
         private static PAN_MOUSE_BUTTON;
         private static MIN_SCALE;
         private static MIN_SCALE;
         private static MAX_SCALE;
         private static MAX_SCALE;
+        private static SELECT_ALL_KEY;
+        private static DESELECT_KEY;
         private _tool;
         private _tool;
         private _setPixelData;
         private _setPixelData;
         private _GUI;
         private _GUI;
         private _window;
         private _window;
-        metadata: any;
+        private _metadata;
         private _editing3D;
         private _editing3D;
-        constructor(texture: BABYLON.BaseTexture, window: Window, canvasUI: HTMLCanvasElement, canvas2D: HTMLCanvasElement, canvas3D: HTMLCanvasElement, setPixelData: (pixelData: IPixelData) => void);
+        private _onUpdate;
+        private _setMetadata;
+        private _imageData;
+        private _canUpdate;
+        private _shouldUpdate;
+        private _paintCanvas;
+        constructor(texture: BABYLON.BaseTexture, window: Window, canvasUI: HTMLCanvasElement, canvas2D: HTMLCanvasElement, canvas3D: HTMLCanvasElement, setPixelData: (pixelData: IPixelData) => void, metadata: IMetadata, onUpdate: () => void, setMetadata: (metadata: any) => void);
         updateTexture(): Promise<void>;
         updateTexture(): Promise<void>;
+        private queueTextureUpdate;
+        startPainting(): CanvasRenderingContext2D;
+        updatePainting(): void;
+        stopPainting(): void;
         private updateDisplay;
         private updateDisplay;
         set channels(channels: IChannel[]);
         set channels(channels: IChannel[]);
         static paintPixelsOnCanvas(pixelData: Uint8Array, canvas: HTMLCanvasElement): void;
         static paintPixelsOnCanvas(pixelData: Uint8Array, canvas: HTMLCanvasElement): void;
@@ -1428,10 +1456,12 @@ declare module INSPECTOR {
         set tool(tool: BABYLON.Nullable<ITool>);
         set tool(tool: BABYLON.Nullable<ITool>);
         get tool(): BABYLON.Nullable<ITool>;
         get tool(): BABYLON.Nullable<ITool>;
         set face(face: number);
         set face(face: number);
+        set mipLevel(mipLevel: number);
         /** Returns the tool GUI object, allowing tools to access the GUI */
         /** Returns the tool GUI object, allowing tools to access the GUI */
         get GUI(): IToolGUI;
         get GUI(): IToolGUI;
         /** Returns the 3D scene used for postprocesses */
         /** Returns the 3D scene used for postprocesses */
         get scene3D(): BABYLON.Scene;
         get scene3D(): BABYLON.Scene;
+        set metadata(metadata: IMetadata);
         private makePlane;
         private makePlane;
         reset(): void;
         reset(): void;
         resize(newSize: BABYLON.ISize): Promise<void>;
         resize(newSize: BABYLON.ISize): Promise<void>;
@@ -1450,6 +1480,8 @@ declare module INSPECTOR {
         resetTexture(): void;
         resetTexture(): void;
         resizeTexture(width: number, height: number): void;
         resizeTexture(width: number, height: number): void;
         uploadTexture(file: File): void;
         uploadTexture(file: File): void;
+        mipLevel: number;
+        setMipLevel: (mipLevel: number) => void;
     }
     }
     interface IPropertiesBarState {
     interface IPropertiesBarState {
         width: number;
         width: number;
@@ -1473,6 +1505,8 @@ declare module INSPECTOR {
 declare module INSPECTOR {
 declare module INSPECTOR {
     interface BottomBarProps {
     interface BottomBarProps {
         name: string;
         name: string;
+        mipLevel: number;
+        hasMips: boolean;
     }
     }
     export class BottomBar extends React.Component<BottomBarProps> {
     export class BottomBar extends React.Component<BottomBarProps> {
         render(): JSX.Element;
         render(): JSX.Element;
@@ -1503,6 +1537,9 @@ declare module INSPECTOR {
     export const Contrast: IToolData;
     export const Contrast: IToolData;
 }
 }
 declare module INSPECTOR {
 declare module INSPECTOR {
+    export const RectangleSelect: IToolData;
+}
+declare module INSPECTOR {
     const _default: import("babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent").IToolData[];
     const _default: import("babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent").IToolData[];
     export default _default;
     export default _default;
 }
 }
@@ -1512,19 +1549,21 @@ declare module INSPECTOR {
         texture: BABYLON.BaseTexture;
         texture: BABYLON.BaseTexture;
         url: string;
         url: string;
         window: React.RefObject<PopupComponent>;
         window: React.RefObject<PopupComponent>;
+        onUpdate: () => void;
     }
     }
     interface ITextureEditorComponentState {
     interface ITextureEditorComponentState {
         tools: ITool[];
         tools: ITool[];
         activeToolIndex: number;
         activeToolIndex: number;
-        metadata: any;
+        metadata: IMetadata;
         channels: IChannel[];
         channels: IChannel[];
         pixelData: IPixelData;
         pixelData: IPixelData;
         face: number;
         face: number;
+        mipLevel: number;
     }
     }
     export interface IToolParameters {
     export interface IToolParameters {
         /** The visible scene in the editor. Useful for adding pointer and keyboard events. */
         /** The visible scene in the editor. Useful for adding pointer and keyboard events. */
         scene: BABYLON.Scene;
         scene: BABYLON.Scene;
-        /** The 2D canvas which tools can paint on using the canvas API. */
+        /** The 2D canvas which you can sample pixel data from. Tools should not paint directly on this canvas. */
         canvas2D: HTMLCanvasElement;
         canvas2D: HTMLCanvasElement;
         /** The 3D scene which tools can add post processes to. */
         /** The 3D scene which tools can add post processes to. */
         scene3D: BABYLON.Scene;
         scene3D: BABYLON.Scene;
@@ -1533,7 +1572,7 @@ declare module INSPECTOR {
         /** Pushes the editor texture back to the original scene. This should be called every time a tool makes any modification to a texture. */
         /** Pushes the editor texture back to the original scene. This should be called every time a tool makes any modification to a texture. */
         updateTexture: () => void;
         updateTexture: () => void;
         /** The metadata object which is shared between all tools. Feel free to store any information here. Do not set this directly: instead call setMetadata. */
         /** The metadata object which is shared between all tools. Feel free to store any information here. Do not set this directly: instead call setMetadata. */
-        metadata: any;
+        metadata: IMetadata;
         /** Call this when you want to mutate the metadata. */
         /** Call this when you want to mutate the metadata. */
         setMetadata: (data: any) => void;
         setMetadata: (data: any) => void;
         /** Returns the texture coordinates under the cursor */
         /** Returns the texture coordinates under the cursor */
@@ -1542,6 +1581,12 @@ declare module INSPECTOR {
         GUI: IToolGUI;
         GUI: IToolGUI;
         /** Provides access to the BABYLON namespace */
         /** Provides access to the BABYLON namespace */
         BABYLON: any;
         BABYLON: any;
+        /** Provides a canvas that you can use the canvas API to paint on. */
+        startPainting: () => CanvasRenderingContext2D;
+        /** After you have painted on your canvas, call this method to push the updates back to the texture. */
+        updatePainting: () => void;
+        /** Call this when you are finished painting. */
+        stopPainting: () => void;
     }
     }
     /** An interface representing the definition of a tool */
     /** An interface representing the definition of a tool */
     export interface IToolData {
     export interface IToolData {
@@ -1568,6 +1613,17 @@ declare module INSPECTOR {
     interface IToolConstructable {
     interface IToolConstructable {
         new (getParameters: () => IToolParameters): IToolType;
         new (getParameters: () => IToolParameters): IToolType;
     }
     }
+    export interface IMetadata {
+        color: string;
+        alpha: number;
+        select: {
+            x1: number;
+            y1: number;
+            x2: number;
+            y2: number;
+        };
+        [key: string]: any;
+    }
     global {
     global {
         var _TOOL_DATA_: IToolData;
         var _TOOL_DATA_: IToolData;
     }
     }
@@ -1576,16 +1632,18 @@ declare module INSPECTOR {
         private _UICanvas;
         private _UICanvas;
         private _2DCanvas;
         private _2DCanvas;
         private _3DCanvas;
         private _3DCanvas;
+        private _timer;
+        private static PREVIEW_UPDATE_DELAY_MS;
         constructor(props: ITextureEditorComponentProps);
         constructor(props: ITextureEditorComponentProps);
         componentDidMount(): void;
         componentDidMount(): void;
         componentDidUpdate(): void;
         componentDidUpdate(): void;
         componentWillUnmount(): void;
         componentWillUnmount(): void;
+        textureDidUpdate(): void;
         loadToolFromURL(url: string): void;
         loadToolFromURL(url: string): void;
         addTools(tools: IToolData[]): void;
         addTools(tools: IToolData[]): void;
         getToolParameters(): IToolParameters;
         getToolParameters(): IToolParameters;
         changeTool(index: number): void;
         changeTool(index: number): void;
         setMetadata(newMetadata: any): void;
         setMetadata(newMetadata: any): void;
-        setFace(face: number): void;
         saveTexture(): void;
         saveTexture(): void;
         resetTexture(): void;
         resetTexture(): void;
         resizeTexture(width: number, height: number): void;
         resizeTexture(width: number, height: number): void;
@@ -3365,7 +3423,4 @@ declare module INSPECTOR {
         calculateMove(): string;
         calculateMove(): string;
         render(): JSX.Element;
         render(): JSX.Element;
     }
     }
-}
-declare module INSPECTOR {
-    export const RectangleSelect: IToolData;
 }
 }

+ 137 - 24
dist/preview release/inspector/babylon.inspector.module.d.ts

@@ -1448,9 +1448,25 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         render(): JSX.Element;
         render(): JSX.Element;
     }
     }
 }
 }
+declare module "babylonjs-inspector/lod" {
+    /** @hidden */
+    export var lodPixelShader: {
+        name: string;
+        shader: string;
+    };
+}
+declare module "babylonjs-inspector/lodCube" {
+    /** @hidden */
+    export var lodCubePixelShader: {
+        name: string;
+        shader: string;
+    };
+}
 declare module "babylonjs-inspector/textureHelper" {
 declare module "babylonjs-inspector/textureHelper" {
     import { GlobalState } from "babylonjs-inspector/components/globalState";
     import { GlobalState } from "babylonjs-inspector/components/globalState";
     import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
     import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
+    import "babylonjs-inspector/lod";
+    import "babylonjs-inspector/lodCube";
     export interface TextureChannelsToDisplay {
     export interface TextureChannelsToDisplay {
         R: boolean;
         R: boolean;
         G: boolean;
         G: boolean;
@@ -1459,7 +1475,7 @@ declare module "babylonjs-inspector/textureHelper" {
     }
     }
     export class TextureHelper {
     export class TextureHelper {
         private static _ProcessAsync;
         private static _ProcessAsync;
-        static GetTextureDataAsync(texture: BaseTexture, width: number, height: number, face: number, channels: TextureChannelsToDisplay, globalState?: GlobalState): Promise<Uint8Array>;
+        static GetTextureDataAsync(texture: BaseTexture, width: number, height: number, face: number, channels: TextureChannelsToDisplay, globalState?: GlobalState, lod?: number): Promise<Uint8Array>;
     }
     }
 }
 }
 declare module "babylonjs-inspector/components/actionTabs/lines/textureLineComponent" {
 declare module "babylonjs-inspector/components/actionTabs/lines/textureLineComponent" {
@@ -1493,7 +1509,7 @@ declare module "babylonjs-inspector/components/actionTabs/lines/textureLineCompo
 }
 }
 declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/toolBar" {
 declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/toolBar" {
     import * as React from 'react';
     import * as React from 'react';
-    import { IToolData, IToolType } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent";
+    import { IToolData, IToolType, IMetadata } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent";
     export interface ITool extends IToolData {
     export interface ITool extends IToolData {
         instance: IToolType;
         instance: IToolType;
     }
     }
@@ -1502,7 +1518,7 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         addTool(url: string): void;
         addTool(url: string): void;
         changeTool(toolIndex: number): void;
         changeTool(toolIndex: number): void;
         activeToolIndex: number;
         activeToolIndex: number;
-        metadata: any;
+        metadata: IMetadata;
         setMetadata(data: any): void;
         setMetadata(data: any): void;
     }
     }
     interface IToolBarState {
     interface IToolBarState {
@@ -1547,6 +1563,7 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
     import { StackPanel } from 'babylonjs-gui/2D/controls/stackPanel';
     import { StackPanel } from 'babylonjs-gui/2D/controls/stackPanel';
     import { Style } from 'babylonjs-gui/2D/style';
     import { Style } from 'babylonjs-gui/2D/style';
     import { AdvancedDynamicTexture } from 'babylonjs-gui/2D/advancedDynamicTexture';
     import { AdvancedDynamicTexture } from 'babylonjs-gui/2D/advancedDynamicTexture';
+    import { IMetadata } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent";
     export interface IPixelData {
     export interface IPixelData {
         x?: number;
         x?: number;
         y?: number;
         y?: number;
@@ -1566,6 +1583,7 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         private _engine;
         private _engine;
         private _scene;
         private _scene;
         private _camera;
         private _camera;
+        private _cameraPos;
         private _scale;
         private _scale;
         private _isPanning;
         private _isPanning;
         private _mouseX;
         private _mouseX;
@@ -1581,6 +1599,7 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         private _3DScene;
         private _3DScene;
         private _channels;
         private _channels;
         private _face;
         private _face;
+        private _mipLevel;
         private _originalTexture;
         private _originalTexture;
         private _target;
         private _target;
         private _originalInternalTexture;
         private _originalInternalTexture;
@@ -1596,14 +1615,26 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         private static PAN_MOUSE_BUTTON;
         private static PAN_MOUSE_BUTTON;
         private static MIN_SCALE;
         private static MIN_SCALE;
         private static MAX_SCALE;
         private static MAX_SCALE;
+        private static SELECT_ALL_KEY;
+        private static DESELECT_KEY;
         private _tool;
         private _tool;
         private _setPixelData;
         private _setPixelData;
         private _GUI;
         private _GUI;
         private _window;
         private _window;
-        metadata: any;
+        private _metadata;
         private _editing3D;
         private _editing3D;
-        constructor(texture: BaseTexture, window: Window, canvasUI: HTMLCanvasElement, canvas2D: HTMLCanvasElement, canvas3D: HTMLCanvasElement, setPixelData: (pixelData: IPixelData) => void);
+        private _onUpdate;
+        private _setMetadata;
+        private _imageData;
+        private _canUpdate;
+        private _shouldUpdate;
+        private _paintCanvas;
+        constructor(texture: BaseTexture, window: Window, canvasUI: HTMLCanvasElement, canvas2D: HTMLCanvasElement, canvas3D: HTMLCanvasElement, setPixelData: (pixelData: IPixelData) => void, metadata: IMetadata, onUpdate: () => void, setMetadata: (metadata: any) => void);
         updateTexture(): Promise<void>;
         updateTexture(): Promise<void>;
+        private queueTextureUpdate;
+        startPainting(): CanvasRenderingContext2D;
+        updatePainting(): void;
+        stopPainting(): void;
         private updateDisplay;
         private updateDisplay;
         set channels(channels: IChannel[]);
         set channels(channels: IChannel[]);
         static paintPixelsOnCanvas(pixelData: Uint8Array, canvas: HTMLCanvasElement): void;
         static paintPixelsOnCanvas(pixelData: Uint8Array, canvas: HTMLCanvasElement): void;
@@ -1615,10 +1646,12 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         set tool(tool: Nullable<ITool>);
         set tool(tool: Nullable<ITool>);
         get tool(): Nullable<ITool>;
         get tool(): Nullable<ITool>;
         set face(face: number);
         set face(face: number);
+        set mipLevel(mipLevel: number);
         /** Returns the tool GUI object, allowing tools to access the GUI */
         /** Returns the tool GUI object, allowing tools to access the GUI */
         get GUI(): IToolGUI;
         get GUI(): IToolGUI;
         /** Returns the 3D scene used for postprocesses */
         /** Returns the 3D scene used for postprocesses */
         get scene3D(): Scene;
         get scene3D(): Scene;
+        set metadata(metadata: IMetadata);
         private makePlane;
         private makePlane;
         reset(): void;
         reset(): void;
         resize(newSize: ISize): Promise<void>;
         resize(newSize: ISize): Promise<void>;
@@ -1640,6 +1673,8 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         resetTexture(): void;
         resetTexture(): void;
         resizeTexture(width: number, height: number): void;
         resizeTexture(width: number, height: number): void;
         uploadTexture(file: File): void;
         uploadTexture(file: File): void;
+        mipLevel: number;
+        setMipLevel: (mipLevel: number) => void;
     }
     }
     interface IPropertiesBarState {
     interface IPropertiesBarState {
         width: number;
         width: number;
@@ -1664,6 +1699,8 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
     import * as React from 'react';
     import * as React from 'react';
     interface BottomBarProps {
     interface BottomBarProps {
         name: string;
         name: string;
+        mipLevel: number;
+        hasMips: boolean;
     }
     }
     export class BottomBar extends React.Component<BottomBarProps> {
     export class BottomBar extends React.Component<BottomBarProps> {
         render(): JSX.Element;
         render(): JSX.Element;
@@ -1699,6 +1736,10 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
     import { IToolData } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent";
     import { IToolData } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent";
     export const Contrast: IToolData;
     export const Contrast: IToolData;
 }
 }
+declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/rectangleSelect" {
+    import { IToolData } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent";
+    export const RectangleSelect: IToolData;
+}
 declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/defaultTools" {
 declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/defaultTools" {
     const _default: import("babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent").IToolData[];
     const _default: import("babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent").IToolData[];
     export default _default;
     export default _default;
@@ -1720,19 +1761,21 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         texture: BaseTexture;
         texture: BaseTexture;
         url: string;
         url: string;
         window: React.RefObject<PopupComponent>;
         window: React.RefObject<PopupComponent>;
+        onUpdate: () => void;
     }
     }
     interface ITextureEditorComponentState {
     interface ITextureEditorComponentState {
         tools: ITool[];
         tools: ITool[];
         activeToolIndex: number;
         activeToolIndex: number;
-        metadata: any;
+        metadata: IMetadata;
         channels: IChannel[];
         channels: IChannel[];
         pixelData: IPixelData;
         pixelData: IPixelData;
         face: number;
         face: number;
+        mipLevel: number;
     }
     }
     export interface IToolParameters {
     export interface IToolParameters {
         /** The visible scene in the editor. Useful for adding pointer and keyboard events. */
         /** The visible scene in the editor. Useful for adding pointer and keyboard events. */
         scene: Scene;
         scene: Scene;
-        /** The 2D canvas which tools can paint on using the canvas API. */
+        /** The 2D canvas which you can sample pixel data from. Tools should not paint directly on this canvas. */
         canvas2D: HTMLCanvasElement;
         canvas2D: HTMLCanvasElement;
         /** The 3D scene which tools can add post processes to. */
         /** The 3D scene which tools can add post processes to. */
         scene3D: Scene;
         scene3D: Scene;
@@ -1741,7 +1784,7 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         /** Pushes the editor texture back to the original scene. This should be called every time a tool makes any modification to a texture. */
         /** Pushes the editor texture back to the original scene. This should be called every time a tool makes any modification to a texture. */
         updateTexture: () => void;
         updateTexture: () => void;
         /** The metadata object which is shared between all tools. Feel free to store any information here. Do not set this directly: instead call setMetadata. */
         /** The metadata object which is shared between all tools. Feel free to store any information here. Do not set this directly: instead call setMetadata. */
-        metadata: any;
+        metadata: IMetadata;
         /** Call this when you want to mutate the metadata. */
         /** Call this when you want to mutate the metadata. */
         setMetadata: (data: any) => void;
         setMetadata: (data: any) => void;
         /** Returns the texture coordinates under the cursor */
         /** Returns the texture coordinates under the cursor */
@@ -1750,6 +1793,12 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         GUI: IToolGUI;
         GUI: IToolGUI;
         /** Provides access to the BABYLON namespace */
         /** Provides access to the BABYLON namespace */
         BABYLON: any;
         BABYLON: any;
+        /** Provides a canvas that you can use the canvas API to paint on. */
+        startPainting: () => CanvasRenderingContext2D;
+        /** After you have painted on your canvas, call this method to push the updates back to the texture. */
+        updatePainting: () => void;
+        /** Call this when you are finished painting. */
+        stopPainting: () => void;
     }
     }
     /** An interface representing the definition of a tool */
     /** An interface representing the definition of a tool */
     export interface IToolData {
     export interface IToolData {
@@ -1776,6 +1825,17 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
     interface IToolConstructable {
     interface IToolConstructable {
         new (getParameters: () => IToolParameters): IToolType;
         new (getParameters: () => IToolParameters): IToolType;
     }
     }
+    export interface IMetadata {
+        color: string;
+        alpha: number;
+        select: {
+            x1: number;
+            y1: number;
+            x2: number;
+            y2: number;
+        };
+        [key: string]: any;
+    }
     global {
     global {
         var _TOOL_DATA_: IToolData;
         var _TOOL_DATA_: IToolData;
     }
     }
@@ -1784,16 +1844,18 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         private _UICanvas;
         private _UICanvas;
         private _2DCanvas;
         private _2DCanvas;
         private _3DCanvas;
         private _3DCanvas;
+        private _timer;
+        private static PREVIEW_UPDATE_DELAY_MS;
         constructor(props: ITextureEditorComponentProps);
         constructor(props: ITextureEditorComponentProps);
         componentDidMount(): void;
         componentDidMount(): void;
         componentDidUpdate(): void;
         componentDidUpdate(): void;
         componentWillUnmount(): void;
         componentWillUnmount(): void;
+        textureDidUpdate(): void;
         loadToolFromURL(url: string): void;
         loadToolFromURL(url: string): void;
         addTools(tools: IToolData[]): void;
         addTools(tools: IToolData[]): void;
         getToolParameters(): IToolParameters;
         getToolParameters(): IToolParameters;
         changeTool(index: number): void;
         changeTool(index: number): void;
         setMetadata(newMetadata: any): void;
         setMetadata(newMetadata: any): void;
-        setFace(face: number): void;
         saveTexture(): void;
         saveTexture(): void;
         resetTexture(): void;
         resetTexture(): void;
         resizeTexture(width: number, height: number): void;
         resizeTexture(width: number, height: number): void;
@@ -4091,10 +4153,6 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/ani
         render(): JSX.Element;
         render(): JSX.Element;
     }
     }
 }
 }
-declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/rectangleSelect" {
-    import { IToolData } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent";
-    export const RectangleSelect: IToolData;
-}
 declare module "babylonjs-inspector/legacy/legacy" {
 declare module "babylonjs-inspector/legacy/legacy" {
     export * from "babylonjs-inspector/index";
     export * from "babylonjs-inspector/index";
 }
 }
@@ -5385,6 +5443,20 @@ declare module INSPECTOR {
     }
     }
 }
 }
 declare module INSPECTOR {
 declare module INSPECTOR {
+    /** @hidden */
+    export var lodPixelShader: {
+        name: string;
+        shader: string;
+    };
+}
+declare module INSPECTOR {
+    /** @hidden */
+    export var lodCubePixelShader: {
+        name: string;
+        shader: string;
+    };
+}
+declare module INSPECTOR {
     export interface TextureChannelsToDisplay {
     export interface TextureChannelsToDisplay {
         R: boolean;
         R: boolean;
         G: boolean;
         G: boolean;
@@ -5393,7 +5465,7 @@ declare module INSPECTOR {
     }
     }
     export class TextureHelper {
     export class TextureHelper {
         private static _ProcessAsync;
         private static _ProcessAsync;
-        static GetTextureDataAsync(texture: BABYLON.BaseTexture, width: number, height: number, face: number, channels: TextureChannelsToDisplay, globalState?: GlobalState): Promise<Uint8Array>;
+        static GetTextureDataAsync(texture: BABYLON.BaseTexture, width: number, height: number, face: number, channels: TextureChannelsToDisplay, globalState?: GlobalState, lod?: number): Promise<Uint8Array>;
     }
     }
 }
 }
 declare module INSPECTOR {
 declare module INSPECTOR {
@@ -5430,7 +5502,7 @@ declare module INSPECTOR {
         addTool(url: string): void;
         addTool(url: string): void;
         changeTool(toolIndex: number): void;
         changeTool(toolIndex: number): void;
         activeToolIndex: number;
         activeToolIndex: number;
-        metadata: any;
+        metadata: IMetadata;
         setMetadata(data: any): void;
         setMetadata(data: any): void;
     }
     }
     interface IToolBarState {
     interface IToolBarState {
@@ -5482,6 +5554,7 @@ declare module INSPECTOR {
         private _engine;
         private _engine;
         private _scene;
         private _scene;
         private _camera;
         private _camera;
+        private _cameraPos;
         private _scale;
         private _scale;
         private _isPanning;
         private _isPanning;
         private _mouseX;
         private _mouseX;
@@ -5497,6 +5570,7 @@ declare module INSPECTOR {
         private _3DScene;
         private _3DScene;
         private _channels;
         private _channels;
         private _face;
         private _face;
+        private _mipLevel;
         private _originalTexture;
         private _originalTexture;
         private _target;
         private _target;
         private _originalInternalTexture;
         private _originalInternalTexture;
@@ -5512,14 +5586,26 @@ declare module INSPECTOR {
         private static PAN_MOUSE_BUTTON;
         private static PAN_MOUSE_BUTTON;
         private static MIN_SCALE;
         private static MIN_SCALE;
         private static MAX_SCALE;
         private static MAX_SCALE;
+        private static SELECT_ALL_KEY;
+        private static DESELECT_KEY;
         private _tool;
         private _tool;
         private _setPixelData;
         private _setPixelData;
         private _GUI;
         private _GUI;
         private _window;
         private _window;
-        metadata: any;
+        private _metadata;
         private _editing3D;
         private _editing3D;
-        constructor(texture: BABYLON.BaseTexture, window: Window, canvasUI: HTMLCanvasElement, canvas2D: HTMLCanvasElement, canvas3D: HTMLCanvasElement, setPixelData: (pixelData: IPixelData) => void);
+        private _onUpdate;
+        private _setMetadata;
+        private _imageData;
+        private _canUpdate;
+        private _shouldUpdate;
+        private _paintCanvas;
+        constructor(texture: BABYLON.BaseTexture, window: Window, canvasUI: HTMLCanvasElement, canvas2D: HTMLCanvasElement, canvas3D: HTMLCanvasElement, setPixelData: (pixelData: IPixelData) => void, metadata: IMetadata, onUpdate: () => void, setMetadata: (metadata: any) => void);
         updateTexture(): Promise<void>;
         updateTexture(): Promise<void>;
+        private queueTextureUpdate;
+        startPainting(): CanvasRenderingContext2D;
+        updatePainting(): void;
+        stopPainting(): void;
         private updateDisplay;
         private updateDisplay;
         set channels(channels: IChannel[]);
         set channels(channels: IChannel[]);
         static paintPixelsOnCanvas(pixelData: Uint8Array, canvas: HTMLCanvasElement): void;
         static paintPixelsOnCanvas(pixelData: Uint8Array, canvas: HTMLCanvasElement): void;
@@ -5531,10 +5617,12 @@ declare module INSPECTOR {
         set tool(tool: BABYLON.Nullable<ITool>);
         set tool(tool: BABYLON.Nullable<ITool>);
         get tool(): BABYLON.Nullable<ITool>;
         get tool(): BABYLON.Nullable<ITool>;
         set face(face: number);
         set face(face: number);
+        set mipLevel(mipLevel: number);
         /** Returns the tool GUI object, allowing tools to access the GUI */
         /** Returns the tool GUI object, allowing tools to access the GUI */
         get GUI(): IToolGUI;
         get GUI(): IToolGUI;
         /** Returns the 3D scene used for postprocesses */
         /** Returns the 3D scene used for postprocesses */
         get scene3D(): BABYLON.Scene;
         get scene3D(): BABYLON.Scene;
+        set metadata(metadata: IMetadata);
         private makePlane;
         private makePlane;
         reset(): void;
         reset(): void;
         resize(newSize: BABYLON.ISize): Promise<void>;
         resize(newSize: BABYLON.ISize): Promise<void>;
@@ -5553,6 +5641,8 @@ declare module INSPECTOR {
         resetTexture(): void;
         resetTexture(): void;
         resizeTexture(width: number, height: number): void;
         resizeTexture(width: number, height: number): void;
         uploadTexture(file: File): void;
         uploadTexture(file: File): void;
+        mipLevel: number;
+        setMipLevel: (mipLevel: number) => void;
     }
     }
     interface IPropertiesBarState {
     interface IPropertiesBarState {
         width: number;
         width: number;
@@ -5576,6 +5666,8 @@ declare module INSPECTOR {
 declare module INSPECTOR {
 declare module INSPECTOR {
     interface BottomBarProps {
     interface BottomBarProps {
         name: string;
         name: string;
+        mipLevel: number;
+        hasMips: boolean;
     }
     }
     export class BottomBar extends React.Component<BottomBarProps> {
     export class BottomBar extends React.Component<BottomBarProps> {
         render(): JSX.Element;
         render(): JSX.Element;
@@ -5606,6 +5698,9 @@ declare module INSPECTOR {
     export const Contrast: IToolData;
     export const Contrast: IToolData;
 }
 }
 declare module INSPECTOR {
 declare module INSPECTOR {
+    export const RectangleSelect: IToolData;
+}
+declare module INSPECTOR {
     const _default: import("babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent").IToolData[];
     const _default: import("babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent").IToolData[];
     export default _default;
     export default _default;
 }
 }
@@ -5615,19 +5710,21 @@ declare module INSPECTOR {
         texture: BABYLON.BaseTexture;
         texture: BABYLON.BaseTexture;
         url: string;
         url: string;
         window: React.RefObject<PopupComponent>;
         window: React.RefObject<PopupComponent>;
+        onUpdate: () => void;
     }
     }
     interface ITextureEditorComponentState {
     interface ITextureEditorComponentState {
         tools: ITool[];
         tools: ITool[];
         activeToolIndex: number;
         activeToolIndex: number;
-        metadata: any;
+        metadata: IMetadata;
         channels: IChannel[];
         channels: IChannel[];
         pixelData: IPixelData;
         pixelData: IPixelData;
         face: number;
         face: number;
+        mipLevel: number;
     }
     }
     export interface IToolParameters {
     export interface IToolParameters {
         /** The visible scene in the editor. Useful for adding pointer and keyboard events. */
         /** The visible scene in the editor. Useful for adding pointer and keyboard events. */
         scene: BABYLON.Scene;
         scene: BABYLON.Scene;
-        /** The 2D canvas which tools can paint on using the canvas API. */
+        /** The 2D canvas which you can sample pixel data from. Tools should not paint directly on this canvas. */
         canvas2D: HTMLCanvasElement;
         canvas2D: HTMLCanvasElement;
         /** The 3D scene which tools can add post processes to. */
         /** The 3D scene which tools can add post processes to. */
         scene3D: BABYLON.Scene;
         scene3D: BABYLON.Scene;
@@ -5636,7 +5733,7 @@ declare module INSPECTOR {
         /** Pushes the editor texture back to the original scene. This should be called every time a tool makes any modification to a texture. */
         /** Pushes the editor texture back to the original scene. This should be called every time a tool makes any modification to a texture. */
         updateTexture: () => void;
         updateTexture: () => void;
         /** The metadata object which is shared between all tools. Feel free to store any information here. Do not set this directly: instead call setMetadata. */
         /** The metadata object which is shared between all tools. Feel free to store any information here. Do not set this directly: instead call setMetadata. */
-        metadata: any;
+        metadata: IMetadata;
         /** Call this when you want to mutate the metadata. */
         /** Call this when you want to mutate the metadata. */
         setMetadata: (data: any) => void;
         setMetadata: (data: any) => void;
         /** Returns the texture coordinates under the cursor */
         /** Returns the texture coordinates under the cursor */
@@ -5645,6 +5742,12 @@ declare module INSPECTOR {
         GUI: IToolGUI;
         GUI: IToolGUI;
         /** Provides access to the BABYLON namespace */
         /** Provides access to the BABYLON namespace */
         BABYLON: any;
         BABYLON: any;
+        /** Provides a canvas that you can use the canvas API to paint on. */
+        startPainting: () => CanvasRenderingContext2D;
+        /** After you have painted on your canvas, call this method to push the updates back to the texture. */
+        updatePainting: () => void;
+        /** Call this when you are finished painting. */
+        stopPainting: () => void;
     }
     }
     /** An interface representing the definition of a tool */
     /** An interface representing the definition of a tool */
     export interface IToolData {
     export interface IToolData {
@@ -5671,6 +5774,17 @@ declare module INSPECTOR {
     interface IToolConstructable {
     interface IToolConstructable {
         new (getParameters: () => IToolParameters): IToolType;
         new (getParameters: () => IToolParameters): IToolType;
     }
     }
+    export interface IMetadata {
+        color: string;
+        alpha: number;
+        select: {
+            x1: number;
+            y1: number;
+            x2: number;
+            y2: number;
+        };
+        [key: string]: any;
+    }
     global {
     global {
         var _TOOL_DATA_: IToolData;
         var _TOOL_DATA_: IToolData;
     }
     }
@@ -5679,16 +5793,18 @@ declare module INSPECTOR {
         private _UICanvas;
         private _UICanvas;
         private _2DCanvas;
         private _2DCanvas;
         private _3DCanvas;
         private _3DCanvas;
+        private _timer;
+        private static PREVIEW_UPDATE_DELAY_MS;
         constructor(props: ITextureEditorComponentProps);
         constructor(props: ITextureEditorComponentProps);
         componentDidMount(): void;
         componentDidMount(): void;
         componentDidUpdate(): void;
         componentDidUpdate(): void;
         componentWillUnmount(): void;
         componentWillUnmount(): void;
+        textureDidUpdate(): void;
         loadToolFromURL(url: string): void;
         loadToolFromURL(url: string): void;
         addTools(tools: IToolData[]): void;
         addTools(tools: IToolData[]): void;
         getToolParameters(): IToolParameters;
         getToolParameters(): IToolParameters;
         changeTool(index: number): void;
         changeTool(index: number): void;
         setMetadata(newMetadata: any): void;
         setMetadata(newMetadata: any): void;
-        setFace(face: number): void;
         saveTexture(): void;
         saveTexture(): void;
         resetTexture(): void;
         resetTexture(): void;
         resizeTexture(width: number, height: number): void;
         resizeTexture(width: number, height: number): void;
@@ -7468,7 +7584,4 @@ declare module INSPECTOR {
         calculateMove(): string;
         calculateMove(): string;
         render(): JSX.Element;
         render(): JSX.Element;
     }
     }
-}
-declare module INSPECTOR {
-    export const RectangleSelect: IToolData;
 }
 }

File diff suppressed because it is too large
+ 22 - 0
dist/preview release/ktx2Transcoders/msc_basis_transcoder.js


BIN
dist/preview release/ktx2Transcoders/msc_basis_transcoder.wasm


BIN
dist/preview release/ktx2Transcoders/uastc_astc.wasm


BIN
dist/preview release/ktx2Transcoders/uastc_bc7.wasm


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

@@ -1 +1 @@
-{"thinEngineOnly":117231,"engineOnly":153671,"sceneOnly":517938,"minGridMaterial":656445,"minStandardMaterial":805870}
+{"thinEngineOnly":117231,"engineOnly":153671,"sceneOnly":517938,"minGridMaterial":656459,"minStandardMaterial":805884}

+ 512 - 62
dist/preview release/viewer/babylon.module.d.ts

@@ -48542,6 +48542,7 @@ declare module "babylonjs/XR/webXRTypes" {
 }
 }
 declare module "babylonjs/XR/webXRManagedOutputCanvas" {
 declare module "babylonjs/XR/webXRManagedOutputCanvas" {
     import { Nullable } from "babylonjs/types";
     import { Nullable } from "babylonjs/types";
+    import { ThinEngine } from "babylonjs/Engines/thinEngine";
     import { WebXRRenderTarget } from "babylonjs/XR/webXRTypes";
     import { WebXRRenderTarget } from "babylonjs/XR/webXRTypes";
     import { WebXRSessionManager } from "babylonjs/XR/webXRSessionManager";
     import { WebXRSessionManager } from "babylonjs/XR/webXRSessionManager";
     import { Observable } from "babylonjs/Misc/observable";
     import { Observable } from "babylonjs/Misc/observable";
@@ -48564,9 +48565,10 @@ declare module "babylonjs/XR/webXRManagedOutputCanvas" {
         newCanvasCssStyle?: string;
         newCanvasCssStyle?: string;
         /**
         /**
          * Get the default values of the configuration object
          * Get the default values of the configuration object
+         * @param engine defines the engine to use (can be null)
          * @returns default values of this configuration object
          * @returns default values of this configuration object
          */
          */
-        static GetDefaults(): WebXRManagedOutputCanvasOptions;
+        static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions;
     }
     }
     /**
     /**
      * Creates a canvas that is added/removed from the webpage when entering/exiting XR
      * Creates a canvas that is added/removed from the webpage when entering/exiting XR
@@ -48860,6 +48862,10 @@ declare module "babylonjs/XR/webXRFeaturesManager" {
          * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
          * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
          */
          */
         xrNativeFeatureName?: string;
         xrNativeFeatureName?: string;
+        /**
+         * A list of (Babylon WebXR) features this feature depends on
+         */
+        dependsOn?: string[];
     }
     }
     /**
     /**
      * A list of the currently available features without referencing them
      * A list of the currently available features without referencing them
@@ -48897,6 +48903,10 @@ declare module "babylonjs/XR/webXRFeaturesManager" {
          * The name of the feature points feature.
          * The name of the feature points feature.
          */
          */
         static readonly FEATURE_POINTS: string;
         static readonly FEATURE_POINTS: string;
+        /**
+         * The name of the hand tracking feature.
+         */
+        static readonly HAND_TRACKING: string;
     }
     }
     /**
     /**
      * Defining the constructor of a feature. Used to register the modules.
      * Defining the constructor of a feature. Used to register the modules.
@@ -49868,7 +49878,7 @@ declare module "babylonjs/XR/motionController/webXRAbstractMotionController" {
         /**
         /**
          * The mesh that will be changed when axis value changes
          * The mesh that will be changed when axis value changes
          */
          */
-        valueMesh: AbstractMesh;
+        valueMesh?: AbstractMesh;
     }
     }
     /**
     /**
      * The elements needed for change-detection of the gamepad objects in motion controllers
      * The elements needed for change-detection of the gamepad objects in motion controllers
@@ -50018,8 +50028,8 @@ declare module "babylonjs/XR/motionController/webXRAbstractMotionController" {
          * @returns a promise that will send true when the pulse has ended and false if the device doesn't support pulse or an error accrued
          * @returns a promise that will send true when the pulse has ended and false if the device doesn't support pulse or an error accrued
          */
          */
         pulse(value: number, duration: number, hapticActuatorIndex?: number): Promise<boolean>;
         pulse(value: number, duration: number, hapticActuatorIndex?: number): Promise<boolean>;
-        protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh;
-        protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh;
+        protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined;
+        protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined;
         /**
         /**
          * Moves the axis on the controller mesh based on its current state
          * Moves the axis on the controller mesh based on its current state
          * @param axis the index of the axis
          * @param axis the index of the axis
@@ -50287,6 +50297,7 @@ declare module "babylonjs/XR/webXRInputSource" {
         private _options;
         private _options;
         private _tmpVector;
         private _tmpVector;
         private _uniqueId;
         private _uniqueId;
+        private _disposed;
         /**
         /**
          * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
          * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
          */
          */
@@ -50840,7 +50851,10 @@ declare module "babylonjs/XR/webXREnterExitUI" {
         options: WebXREnterExitUIOptions;
         options: WebXREnterExitUIOptions;
         private _activeButton;
         private _activeButton;
         private _buttons;
         private _buttons;
-        private _overlay;
+        /**
+         * The HTML Div Element to which buttons are added.
+         */
+        readonly overlay: HTMLDivElement;
         /**
         /**
          * Fired every time the active button is changed.
          * Fired every time the active button is changed.
          *
          *
@@ -79029,6 +79043,218 @@ declare module "babylonjs/XR/features/WebXRFeaturePointSystem" {
         private _init;
         private _init;
     }
     }
 }
 }
+declare module "babylonjs/XR/features/WebXRHandTracking" {
+    import { WebXRAbstractFeature } from "babylonjs/XR/features/WebXRAbstractFeature";
+    import { WebXRSessionManager } from "babylonjs/XR/webXRSessionManager";
+    import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
+    import { Mesh } from "babylonjs/Meshes/mesh";
+    import { WebXRInput } from "babylonjs/XR/webXRInput";
+    import { WebXRInputSource } from "babylonjs/XR/webXRInputSource";
+    import { Nullable } from "babylonjs/types";
+    import { IDisposable } from "babylonjs/scene";
+    import { Observable } from "babylonjs/Misc/observable";
+    /**
+     * Configuration interface for the hand tracking feature
+     */
+    export interface IWebXRHandTrackingOptions {
+        /**
+         * The xrInput that will be used as source for new hands
+         */
+        xrInput: WebXRInput;
+        /**
+         * Configuration object for the joint meshes
+         */
+        jointMeshes?: {
+            /**
+             * Should the meshes created be invisible (defaults to false)
+             */
+            invisible?: boolean;
+            /**
+             * A source mesh to be used to create instances. Defaults to a sphere.
+             * This mesh will be the source for all other (25) meshes.
+             * It should have the general size of a single unit, as the instances will be scaled according to the provided radius
+             */
+            sourceMesh?: Mesh;
+            /**
+             * Should the source mesh stay visible. Defaults to false
+             */
+            keepOriginalVisible?: boolean;
+            /**
+             * Scale factor for all instances (defaults to 2)
+             */
+            scaleFactor?: number;
+            /**
+             * Should each instance have its own physics impostor
+             */
+            enablePhysics?: boolean;
+            /**
+             * If enabled, override default physics properties
+             */
+            physicsProps?: {
+                friction?: number;
+                restitution?: number;
+                impostorType?: number;
+            };
+            /**
+             * For future use - a single hand-mesh that will be updated according to the XRHand data provided
+             */
+            handMesh?: AbstractMesh;
+        };
+    }
+    /**
+     * Parts of the hands divided to writs and finger names
+     */
+    export const enum HandPart {
+        /**
+         * HandPart - Wrist
+         */
+        WRIST = "wrist",
+        /**
+         * HandPart - The THumb
+         */
+        THUMB = "thumb",
+        /**
+         * HandPart - Index finger
+         */
+        INDEX = "index",
+        /**
+         * HandPart - Middle finger
+         */
+        MIDDLE = "middle",
+        /**
+         * HandPart - Ring finger
+         */
+        RING = "ring",
+        /**
+         * HandPart - Little finger
+         */
+        LITTLE = "little"
+    }
+    /**
+     * Representing a single hand (with its corresponding native XRHand object)
+     */
+    export class WebXRHand implements IDisposable {
+        /** the controller to which the hand correlates */
+        readonly xrController: WebXRInputSource;
+        /** the meshes to be used to track the hand joints */
+        readonly trackedMeshes: AbstractMesh[];
+        /**
+         * Hand-parts definition (key is HandPart)
+         */
+        static HandPartsDefinition: {
+            [key: string]: number[];
+        };
+        /**
+         * Populate the HandPartsDefinition object.
+         * This is called as a side effect since certain browsers don't have XRHand defined.
+         */
+        static _PopulateHandPartsDefinition(): void;
+        /**
+         * Construct a new hand object
+         * @param xrController the controller to which the hand correlates
+         * @param trackedMeshes the meshes to be used to track the hand joints
+         */
+        constructor(
+        /** the controller to which the hand correlates */
+        xrController: WebXRInputSource, 
+        /** the meshes to be used to track the hand joints */
+        trackedMeshes: AbstractMesh[]);
+        /**
+         * Update this hand from the latest xr frame
+         * @param xrFrame xrFrame to update from
+         * @param referenceSpace The current viewer reference space
+         * @param scaleFactor optional scale factor for the meshes
+         */
+        updateFromXRFrame(xrFrame: XRFrame, referenceSpace: XRReferenceSpace, scaleFactor?: number): void;
+        /**
+         * Get meshes of part of the hand
+         * @param part the part of hand to get
+         * @returns An array of meshes that correlate to the hand part requested
+         */
+        getHandPartMeshes(part: HandPart): AbstractMesh[];
+        /**
+         * Dispose this Hand object
+         */
+        dispose(): void;
+    }
+    /**
+     * WebXR Hand Joint tracking feature, available for selected browsers and devices
+     */
+    export class WebXRHandTracking extends WebXRAbstractFeature {
+        /**
+         * options to use when constructing this feature
+         */
+        readonly options: IWebXRHandTrackingOptions;
+        private static _idCounter;
+        /**
+         * The module's name
+         */
+        static readonly Name: string;
+        /**
+         * The (Babylon) version of this module.
+         * This is an integer representing the implementation version.
+         * This number does not correspond to the WebXR specs version
+         */
+        static readonly Version: number;
+        /**
+         * This observable will notify registered observers when a new hand object was added and initialized
+         */
+        onHandAddedObservable: Observable<WebXRHand>;
+        /**
+         * This observable will notify its observers right before the hand object is disposed
+         */
+        onHandRemovedObservable: Observable<WebXRHand>;
+        private _hands;
+        /**
+         * Creates a new instance of the hit test feature
+         * @param _xrSessionManager an instance of WebXRSessionManager
+         * @param options options to use when constructing this feature
+         */
+        constructor(_xrSessionManager: WebXRSessionManager, 
+        /**
+         * options to use when constructing this feature
+         */
+        options: IWebXRHandTrackingOptions);
+        /**
+         * Check if the needed objects are defined.
+         * This does not mean that the feature is enabled, but that the objects needed are well defined.
+         */
+        isCompatible(): boolean;
+        /**
+         * attach this feature
+         * Will usually be called by the features manager
+         *
+         * @returns true if successful.
+         */
+        attach(): boolean;
+        /**
+         * detach this feature.
+         * Will usually be called by the features manager
+         *
+         * @returns true if successful.
+         */
+        detach(): boolean;
+        /**
+         * Dispose this feature and all of the resources attached
+         */
+        dispose(): void;
+        /**
+         * Get the hand object according to the controller id
+         * @param controllerId the controller id to which we want to get the hand
+         * @returns null if not found or the WebXRHand object if found
+         */
+        getHandByControllerId(controllerId: string): Nullable<WebXRHand>;
+        /**
+         * Get a hand object according to the requested handedness
+         * @param handedness the handedness to request
+         * @returns null if not found or the WebXRHand object if found
+         */
+        getHandByHandedness(handedness: XRHandedness): Nullable<WebXRHand>;
+        protected _onXRFrame(_xrFrame: XRFrame): void;
+        private _attachHand;
+        private _detachHand;
+    }
+}
 declare module "babylonjs/XR/features/index" {
 declare module "babylonjs/XR/features/index" {
     export * from "babylonjs/XR/features/WebXRHitTestLegacy";
     export * from "babylonjs/XR/features/WebXRHitTestLegacy";
     export * from "babylonjs/XR/features/WebXRAnchorSystem";
     export * from "babylonjs/XR/features/WebXRAnchorSystem";
@@ -79039,6 +79265,7 @@ declare module "babylonjs/XR/features/index" {
     export * from "babylonjs/XR/features/WebXRControllerPhysics";
     export * from "babylonjs/XR/features/WebXRControllerPhysics";
     export * from "babylonjs/XR/features/WebXRHitTest";
     export * from "babylonjs/XR/features/WebXRHitTest";
     export * from "babylonjs/XR/features/WebXRFeaturePointSystem";
     export * from "babylonjs/XR/features/WebXRFeaturePointSystem";
+    export * from "babylonjs/XR/features/WebXRHandTracking";
 }
 }
 declare module "babylonjs/XR/motionController/webXRMicrosoftMixedRealityController" {
 declare module "babylonjs/XR/motionController/webXRMicrosoftMixedRealityController" {
     import { WebXRAbstractMotionController, IMinimalMotionControllerObject, MotionControllerHandedness } from "babylonjs/XR/motionController/webXRAbstractMotionController";
     import { WebXRAbstractMotionController, IMinimalMotionControllerObject, MotionControllerHandedness } from "babylonjs/XR/motionController/webXRAbstractMotionController";
@@ -126183,9 +126410,10 @@ declare module BABYLON {
         newCanvasCssStyle?: string;
         newCanvasCssStyle?: string;
         /**
         /**
          * Get the default values of the configuration object
          * Get the default values of the configuration object
+         * @param engine defines the engine to use (can be null)
          * @returns default values of this configuration object
          * @returns default values of this configuration object
          */
          */
-        static GetDefaults(): WebXRManagedOutputCanvasOptions;
+        static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions;
     }
     }
     /**
     /**
      * Creates a canvas that is added/removed from the webpage when entering/exiting XR
      * Creates a canvas that is added/removed from the webpage when entering/exiting XR
@@ -126465,6 +126693,10 @@ declare module BABYLON {
          * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
          * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
          */
          */
         xrNativeFeatureName?: string;
         xrNativeFeatureName?: string;
+        /**
+         * A list of (Babylon WebXR) features this feature depends on
+         */
+        dependsOn?: string[];
     }
     }
     /**
     /**
      * A list of the currently available features without referencing them
      * A list of the currently available features without referencing them
@@ -126502,6 +126734,10 @@ declare module BABYLON {
          * The name of the feature points feature.
          * The name of the feature points feature.
          */
          */
         static readonly FEATURE_POINTS: string;
         static readonly FEATURE_POINTS: string;
+        /**
+         * The name of the hand tracking feature.
+         */
+        static readonly HAND_TRACKING: string;
     }
     }
     /**
     /**
      * Defining the constructor of a feature. Used to register the modules.
      * Defining the constructor of a feature. Used to register the modules.
@@ -127448,7 +127684,7 @@ declare module BABYLON {
         /**
         /**
          * The mesh that will be changed when axis value changes
          * The mesh that will be changed when axis value changes
          */
          */
-        valueMesh: AbstractMesh;
+        valueMesh?: AbstractMesh;
     }
     }
     /**
     /**
      * The elements needed for change-detection of the gamepad objects in motion controllers
      * The elements needed for change-detection of the gamepad objects in motion controllers
@@ -127598,8 +127834,8 @@ declare module BABYLON {
          * @returns a promise that will send true when the pulse has ended and false if the device doesn't support pulse or an error accrued
          * @returns a promise that will send true when the pulse has ended and false if the device doesn't support pulse or an error accrued
          */
          */
         pulse(value: number, duration: number, hapticActuatorIndex?: number): Promise<boolean>;
         pulse(value: number, duration: number, hapticActuatorIndex?: number): Promise<boolean>;
-        protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh;
-        protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh;
+        protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined;
+        protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined;
         /**
         /**
          * Moves the axis on the controller mesh based on its current state
          * Moves the axis on the controller mesh based on its current state
          * @param axis the index of the axis
          * @param axis the index of the axis
@@ -127850,6 +128086,7 @@ declare module BABYLON {
         private _options;
         private _options;
         private _tmpVector;
         private _tmpVector;
         private _uniqueId;
         private _uniqueId;
+        private _disposed;
         /**
         /**
          * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
          * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
          */
          */
@@ -128375,7 +128612,10 @@ declare module BABYLON {
         options: WebXREnterExitUIOptions;
         options: WebXREnterExitUIOptions;
         private _activeButton;
         private _activeButton;
         private _buttons;
         private _buttons;
-        private _overlay;
+        /**
+         * The HTML Div Element to which buttons are added.
+         */
+        readonly overlay: HTMLDivElement;
         /**
         /**
          * Fired every time the active button is changed.
          * Fired every time the active button is changed.
          *
          *
@@ -154244,6 +154484,209 @@ declare module BABYLON {
 }
 }
 declare module BABYLON {
 declare module BABYLON {
     /**
     /**
+     * Configuration interface for the hand tracking feature
+     */
+    export interface IWebXRHandTrackingOptions {
+        /**
+         * The xrInput that will be used as source for new hands
+         */
+        xrInput: WebXRInput;
+        /**
+         * Configuration object for the joint meshes
+         */
+        jointMeshes?: {
+            /**
+             * Should the meshes created be invisible (defaults to false)
+             */
+            invisible?: boolean;
+            /**
+             * A source mesh to be used to create instances. Defaults to a sphere.
+             * This mesh will be the source for all other (25) meshes.
+             * It should have the general size of a single unit, as the instances will be scaled according to the provided radius
+             */
+            sourceMesh?: Mesh;
+            /**
+             * Should the source mesh stay visible. Defaults to false
+             */
+            keepOriginalVisible?: boolean;
+            /**
+             * Scale factor for all instances (defaults to 2)
+             */
+            scaleFactor?: number;
+            /**
+             * Should each instance have its own physics impostor
+             */
+            enablePhysics?: boolean;
+            /**
+             * If enabled, override default physics properties
+             */
+            physicsProps?: {
+                friction?: number;
+                restitution?: number;
+                impostorType?: number;
+            };
+            /**
+             * For future use - a single hand-mesh that will be updated according to the XRHand data provided
+             */
+            handMesh?: AbstractMesh;
+        };
+    }
+    /**
+     * Parts of the hands divided to writs and finger names
+     */
+    export const enum HandPart {
+        /**
+         * HandPart - Wrist
+         */
+        WRIST = "wrist",
+        /**
+         * HandPart - The THumb
+         */
+        THUMB = "thumb",
+        /**
+         * HandPart - Index finger
+         */
+        INDEX = "index",
+        /**
+         * HandPart - Middle finger
+         */
+        MIDDLE = "middle",
+        /**
+         * HandPart - Ring finger
+         */
+        RING = "ring",
+        /**
+         * HandPart - Little finger
+         */
+        LITTLE = "little"
+    }
+    /**
+     * Representing a single hand (with its corresponding native XRHand object)
+     */
+    export class WebXRHand implements IDisposable {
+        /** the controller to which the hand correlates */
+        readonly xrController: WebXRInputSource;
+        /** the meshes to be used to track the hand joints */
+        readonly trackedMeshes: AbstractMesh[];
+        /**
+         * Hand-parts definition (key is HandPart)
+         */
+        static HandPartsDefinition: {
+            [key: string]: number[];
+        };
+        /**
+         * Populate the HandPartsDefinition object.
+         * This is called as a side effect since certain browsers don't have XRHand defined.
+         */
+        static _PopulateHandPartsDefinition(): void;
+        /**
+         * Construct a new hand object
+         * @param xrController the controller to which the hand correlates
+         * @param trackedMeshes the meshes to be used to track the hand joints
+         */
+        constructor(
+        /** the controller to which the hand correlates */
+        xrController: WebXRInputSource, 
+        /** the meshes to be used to track the hand joints */
+        trackedMeshes: AbstractMesh[]);
+        /**
+         * Update this hand from the latest xr frame
+         * @param xrFrame xrFrame to update from
+         * @param referenceSpace The current viewer reference space
+         * @param scaleFactor optional scale factor for the meshes
+         */
+        updateFromXRFrame(xrFrame: XRFrame, referenceSpace: XRReferenceSpace, scaleFactor?: number): void;
+        /**
+         * Get meshes of part of the hand
+         * @param part the part of hand to get
+         * @returns An array of meshes that correlate to the hand part requested
+         */
+        getHandPartMeshes(part: HandPart): AbstractMesh[];
+        /**
+         * Dispose this Hand object
+         */
+        dispose(): void;
+    }
+    /**
+     * WebXR Hand Joint tracking feature, available for selected browsers and devices
+     */
+    export class WebXRHandTracking extends WebXRAbstractFeature {
+        /**
+         * options to use when constructing this feature
+         */
+        readonly options: IWebXRHandTrackingOptions;
+        private static _idCounter;
+        /**
+         * The module's name
+         */
+        static readonly Name: string;
+        /**
+         * The (Babylon) version of this module.
+         * This is an integer representing the implementation version.
+         * This number does not correspond to the WebXR specs version
+         */
+        static readonly Version: number;
+        /**
+         * This observable will notify registered observers when a new hand object was added and initialized
+         */
+        onHandAddedObservable: Observable<WebXRHand>;
+        /**
+         * This observable will notify its observers right before the hand object is disposed
+         */
+        onHandRemovedObservable: Observable<WebXRHand>;
+        private _hands;
+        /**
+         * Creates a new instance of the hit test feature
+         * @param _xrSessionManager an instance of WebXRSessionManager
+         * @param options options to use when constructing this feature
+         */
+        constructor(_xrSessionManager: WebXRSessionManager, 
+        /**
+         * options to use when constructing this feature
+         */
+        options: IWebXRHandTrackingOptions);
+        /**
+         * Check if the needed objects are defined.
+         * This does not mean that the feature is enabled, but that the objects needed are well defined.
+         */
+        isCompatible(): boolean;
+        /**
+         * attach this feature
+         * Will usually be called by the features manager
+         *
+         * @returns true if successful.
+         */
+        attach(): boolean;
+        /**
+         * detach this feature.
+         * Will usually be called by the features manager
+         *
+         * @returns true if successful.
+         */
+        detach(): boolean;
+        /**
+         * Dispose this feature and all of the resources attached
+         */
+        dispose(): void;
+        /**
+         * Get the hand object according to the controller id
+         * @param controllerId the controller id to which we want to get the hand
+         * @returns null if not found or the WebXRHand object if found
+         */
+        getHandByControllerId(controllerId: string): Nullable<WebXRHand>;
+        /**
+         * Get a hand object according to the requested handedness
+         * @param handedness the handedness to request
+         * @returns null if not found or the WebXRHand object if found
+         */
+        getHandByHandedness(handedness: XRHandedness): Nullable<WebXRHand>;
+        protected _onXRFrame(_xrFrame: XRFrame): void;
+        private _attachHand;
+        private _detachHand;
+    }
+}
+declare module BABYLON {
+    /**
      * The motion controller class for all microsoft mixed reality controllers
      * The motion controller class for all microsoft mixed reality controllers
      */
      */
     export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
     export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
@@ -154972,59 +155415,23 @@ interface Window {
 interface Gamepad {
 interface Gamepad {
     readonly displayId: number;
     readonly displayId: number;
 }
 }
-type XRSessionMode =
-    | "inline"
-    | "immersive-vr"
-    | "immersive-ar";
+type XRSessionMode = "inline" | "immersive-vr" | "immersive-ar";
 
 
-type XRReferenceSpaceType =
-    | "viewer"
-    | "local"
-    | "local-floor"
-    | "bounded-floor"
-    | "unbounded";
+type XRReferenceSpaceType = "viewer" | "local" | "local-floor" | "bounded-floor" | "unbounded";
 
 
-type XREnvironmentBlendMode =
-    | "opaque"
-    | "additive"
-    | "alpha-blend";
+type XREnvironmentBlendMode = "opaque" | "additive" | "alpha-blend";
 
 
-type XRVisibilityState =
-    | "visible"
-    | "visible-blurred"
-    | "hidden";
+type XRVisibilityState = "visible" | "visible-blurred" | "hidden";
 
 
-type XRHandedness =
-    | "none"
-    | "left"
-    | "right";
+type XRHandedness = "none" | "left" | "right";
 
 
-type XRTargetRayMode =
-    | "gaze"
-    | "tracked-pointer"
-    | "screen";
+type XRTargetRayMode = "gaze" | "tracked-pointer" | "screen";
 
 
-type XREye =
-    | "none"
-    | "left"
-    | "right";
+type XREye = "none" | "left" | "right";
 
 
-type XREventType =
-    | "devicechange"
-    | "visibilitychange"
-    | "end"
-    | "inputsourceschange"
-    | "select"
-    | "selectstart"
-    | "selectend"
-    | "squeeze"
-    | "squeezestart"
-    | "squeezeend"
-    | "reset";
+type XREventType = "devicechange" | "visibilitychange" | "end" | "inputsourceschange" | "select" | "selectstart" | "selectend" | "squeeze" | "squeezestart" | "squeezeend" | "reset";
 
 
-interface XRSpace extends EventTarget {
-
-}
+interface XRSpace extends EventTarget {}
 
 
 interface XRRenderState {
 interface XRRenderState {
     depthNear?: number;
     depthNear?: number;
@@ -155040,6 +155447,7 @@ interface XRInputSource {
     gripSpace: XRSpace | undefined;
     gripSpace: XRSpace | undefined;
     gamepad: Gamepad | undefined;
     gamepad: Gamepad | undefined;
     profiles: Array<string>;
     profiles: Array<string>;
+    hand: XRHand | undefined;
 }
 }
 
 
 interface XRSessionInit {
 interface XRSessionInit {
@@ -155065,9 +155473,7 @@ interface XRSession {
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
 
 
     // legacy plane detection
     // legacy plane detection
-    updateWorldTrackingState(options: {
-        planeDetectionState?: { enabled: boolean; }
-    }): void;
+    updateWorldTrackingState(options: { planeDetectionState?: { enabled: boolean } }): void;
 }
 }
 
 
 interface XRReferenceSpace extends XRSpace {
 interface XRReferenceSpace extends XRSpace {
@@ -155084,7 +155490,7 @@ interface XRFrame {
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
 
 
     // AR
     // AR
-    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult> ;
+    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     // Anchors
     // Anchors
     trackedAnchors?: XRAnchorSet;
     trackedAnchors?: XRAnchorSet;
@@ -155093,6 +155499,8 @@ interface XRFrame {
     worldInformation: {
     worldInformation: {
         detectedPlanes?: XRPlaneSet;
         detectedPlanes?: XRPlaneSet;
     };
     };
+    // Hand tracking
+    getJointPose(joint: XRJointSpace, baseSpace: XRSpace): XRJointPose;
 }
 }
 
 
 interface XRViewerPose extends XRPose {
 interface XRViewerPose extends XRPose {
@@ -155115,7 +155523,7 @@ interface XRWebGLLayerOptions {
 
 
 declare var XRWebGLLayer: {
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
     prototype: XRWebGLLayer;
-    new(session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
+    new (session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
 };
 };
 interface XRWebGLLayer {
 interface XRWebGLLayer {
     framebuffer: WebGLFramebuffer;
     framebuffer: WebGLFramebuffer;
@@ -155160,7 +155568,7 @@ declare class XRRay {
 declare enum XRHitTestTrackableType {
 declare enum XRHitTestTrackableType {
     "point",
     "point",
     "plane",
     "plane",
-    "mesh"
+    "mesh",
 }
 }
 
 
 interface XRHitResult {
 interface XRHitResult {
@@ -155208,7 +155616,49 @@ interface XRPlane {
     planeSpace: XRSpace;
     planeSpace: XRSpace;
     polygon: Array<DOMPointReadOnly>;
     polygon: Array<DOMPointReadOnly>;
     lastChangedTime: number;
     lastChangedTime: number;
-}
+}
+
+interface XRJointSpace extends XRSpace {}
+
+interface XRJointPose extends XRPose {
+    radius: number | undefined;
+}
+
+declare class XRHand extends Array<XRJointSpace> {
+    readonly length: number;
+
+    static readonly WRIST = 0;
+
+    static readonly THUMB_METACARPAL = 1;
+    static readonly THUMB_PHALANX_PROXIMAL = 2;
+    static readonly THUMB_PHALANX_DISTAL = 3;
+    static readonly THUMB_PHALANX_TIP = 4;
+
+    static readonly INDEX_METACARPAL = 5;
+    static readonly INDEX_PHALANX_PROXIMAL = 6;
+    static readonly INDEX_PHALANX_INTERMEDIATE = 7;
+    static readonly INDEX_PHALANX_DISTAL = 8;
+    static readonly INDEX_PHALANX_TIP = 9;
+
+    static readonly MIDDLE_METACARPAL = 10;
+    static readonly MIDDLE_PHALANX_PROXIMAL = 11;
+    static readonly MIDDLE_PHALANX_INTERMEDIATE = 12;
+    static readonly MIDDLE_PHALANX_DISTAL = 13;
+    static readonly MIDDLE_PHALANX_TIP = 14;
+
+    static readonly RING_METACARPAL = 15;
+    static readonly RING_PHALANX_PROXIMAL = 16;
+    static readonly RING_PHALANX_INTERMEDIATE = 17;
+    static readonly RING_PHALANX_DISTAL = 18;
+    static readonly RING_PHALANX_TIP = 19;
+
+    static readonly LITTLE_METACARPAL = 20;
+    static readonly LITTLE_PHALANX_PROXIMAL = 21;
+    static readonly LITTLE_PHALANX_INTERMEDIATE = 22;
+    static readonly LITTLE_PHALANX_DISTAL = 23;
+    static readonly LITTLE_PHALANX_TIP = 24;
+}
+
 // This file contains native only extensions for WebXR  These APIs are not supported in the browser yet.
 // This file contains native only extensions for WebXR  These APIs are not supported in the browser yet.
 // They are intended for use with either Babylon Native https://github.com/BabylonJS/BabylonNative or
 // They are intended for use with either Babylon Native https://github.com/BabylonJS/BabylonNative or
 // Babylon React Native: https://github.com/BabylonJS/BabylonReactNative
 // Babylon React Native: https://github.com/BabylonJS/BabylonReactNative

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


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


+ 4 - 1
dist/preview release/what's new.md

@@ -83,7 +83,7 @@
 - Popup Window available (To be used in Curve Editor) ([pixelspace](https://github.com/devpixelspace))
 - Popup Window available (To be used in Curve Editor) ([pixelspace](https://github.com/devpixelspace))
 - Add support to update inspector when switching to a new scene ([belfortk](https://github.com/belfortk))
 - Add support to update inspector when switching to a new scene ([belfortk](https://github.com/belfortk))
 - Hex Component for Hex inputs on layer masks. ([msDestiny14](https://github.com/msDestiny14))
 - Hex Component for Hex inputs on layer masks. ([msDestiny14](https://github.com/msDestiny14))
-- View & edit textures in pop out inspector using canvas and postprocesses. ([DarraghBurkeMS](https://github.com/DarraghBurkeMS))
+- View & edit textures in pop out inspector using canvas and postprocesses. Supports region selection and individual channel editing. ([DarraghBurkeMS](https://github.com/DarraghBurkeMS))
 
 
 ### Cameras
 ### Cameras
 
 
@@ -172,6 +172,9 @@
 - Optional camera gaze mode added to the pointer selection feature ([RaananW](https://github.com/RaananW))
 - Optional camera gaze mode added to the pointer selection feature ([RaananW](https://github.com/RaananW))
 - Exposing feature points when running on top of BabylonNative ([Alex-MSFT](https://github.com/Alex-MSFT))
 - Exposing feature points when running on top of BabylonNative ([Alex-MSFT](https://github.com/Alex-MSFT))
 - WebXR hit test can now define different entity type for the results ([#8687](https://github.com/BabylonJS/Babylon.js/issues/8687)) ([RaananW](https://github.com/RaananW))
 - WebXR hit test can now define different entity type for the results ([#8687](https://github.com/BabylonJS/Babylon.js/issues/8687)) ([RaananW](https://github.com/RaananW))
+- Fixed an issue with stencil not enabled per default ([#8720](https://github.com/BabylonJS/Babylon.js/issues/8720)) ([RaananW](https://github.com/RaananW))
+- Expose the overlay to which the XR Enter/Exit buttons are added to ([#8754](https://github.com/BabylonJS/Babylon.js/issues/8754)) ([RaananW](https://github.com/RaananW))
+- WebXR hand-tracking module is available, able to track hand-joints on selected devices including physics interactions ([RaananW](https://github.com/RaananW))
 
 
 ### Collisions
 ### Collisions
 
 

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

@@ -801,7 +801,10 @@ $line-padding-left: 2px;
                         width: 256px;
                         width: 256px;
                         margin-top: 5px;
                         margin-top: 5px;
                         margin-bottom: 5px;
                         margin-bottom: 5px;
-                        border: 2px solid rgba(255, 255, 255, 0.4);
+                        border: 1px solid white;
+                        background-size: 32px 32px;
+                        background-color: white;
+                        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 2 2'%3E%3Cpath fill='rgba(1.0,1.0,1.0,0.3)' fill-rule='evenodd' d='M0 0h1v1H0V0zm1 1h1v1H1V1z'/%3E%3C/svg%3E");
                     }
                     }
                 }
                 }
 
 

+ 1 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/texturePropertyGridComponent.tsx

@@ -191,6 +191,7 @@ export class TexturePropertyGridComponent extends React.Component<ITextureProper
                         texture={this.props.texture}
                         texture={this.props.texture}
                         url={textureUrl}
                         url={textureUrl}
                         window={this.popoutWindowRef}
                         window={this.popoutWindowRef}
+                        onUpdate={() => this.forceRefresh()}
                     />
                     />
                 </PopupComponent>)}
                 </PopupComponent>)}
                 <CustomPropertyGridComponent globalState={this.props.globalState} target={texture}
                 <CustomPropertyGridComponent globalState={this.props.globalState} target={texture}

+ 3 - 0
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/bottomBar.tsx

@@ -2,12 +2,15 @@ import * as React from 'react';
 
 
 interface BottomBarProps {
 interface BottomBarProps {
     name: string;
     name: string;
+    mipLevel: number;
+    hasMips: boolean;
 }
 }
 
 
 export class BottomBar extends React.Component<BottomBarProps> {
 export class BottomBar extends React.Component<BottomBarProps> {
     render() {
     render() {
         return <div id='bottom-bar'>
         return <div id='bottom-bar'>
             <span id='file-url'>{this.props.name}</span>
             <span id='file-url'>{this.props.name}</span>
+            {this.props.hasMips && <span id='mip-level'>MIP Preview: {this.props.mipLevel}</span>}
         </div>;
         </div>;
     }
     }
 }
 }

+ 10 - 2
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/contrast.ts

@@ -35,11 +35,19 @@ export const Contrast : IToolData = {
         }
         }
         /** Maps slider values to post processing values using an exponential regression */
         /** Maps slider values to post processing values using an exponential regression */
         computeExposure(sliderValue : number) {
         computeExposure(sliderValue : number) {
-            return Math.pow(1.05698, sliderValue) + 0.0000392163 * sliderValue;
+            if (sliderValue <= 0) {
+                return 1 - (-sliderValue / 100);
+            } else {
+                return Math.pow(1.05698, sliderValue) + 0.0000392163 * sliderValue;
+            }
         }
         }
         /** Maps slider values to post processing values using an exponential regression */
         /** Maps slider values to post processing values using an exponential regression */
         computeContrast(sliderValue : number) {
         computeContrast(sliderValue : number) {
-            return Math.pow(1.05698, sliderValue) + 0.0000392163 * sliderValue;
+            if (sliderValue <= 0) {
+                return 1 - (-sliderValue / 100);
+            } else {
+                return Math.pow(1.05698, sliderValue) + 0.0000392163 * sliderValue;
+            }
         }
         }
         setup() {
         setup() {
             this.contrast = 0;
             this.contrast = 0;

+ 2 - 1
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/defaultTools.ts

@@ -2,5 +2,6 @@ import { Paintbrush } from './paintbrush';
 import { Eyedropper } from './eyedropper';
 import { Eyedropper } from './eyedropper';
 import { Floodfill } from './floodfill';
 import { Floodfill } from './floodfill';
 import { Contrast } from './contrast';
 import { Contrast } from './contrast';
+import { RectangleSelect } from './rectangleSelect';
 
 
-export default [Paintbrush, Eyedropper, Floodfill, Contrast];
+export default [RectangleSelect, Paintbrush, Eyedropper, Floodfill, Contrast];

+ 13 - 13
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/eyedropper.ts

@@ -1,11 +1,14 @@
 import { IToolParameters, IToolData } from '../textureEditorComponent';
 import { IToolParameters, IToolData } from '../textureEditorComponent';
 import { PointerEventTypes, PointerInfo } from 'babylonjs/Events/pointerEvents';
 import { PointerEventTypes, PointerInfo } from 'babylonjs/Events/pointerEvents';
+import { Nullable } from 'babylonjs/types'
+import { Observer } from 'babylonjs/Misc/observable';
+import { Color3 } from 'babylonjs/Maths/math.color';
 
 
 export const Eyedropper : IToolData = {
 export const Eyedropper : IToolData = {
     name: 'Eyedropper',
     name: 'Eyedropper',
     type: class {
     type: class {
         getParameters: () => IToolParameters;
         getParameters: () => IToolParameters;
-        pointerObservable: any;
+        pointerObserver: Nullable<Observer<PointerInfo>>;
         isPicking: boolean;
         isPicking: boolean;
 
 
         constructor(getParameters: () => IToolParameters) {
         constructor(getParameters: () => IToolParameters) {
@@ -13,18 +16,18 @@ export const Eyedropper : IToolData = {
         }
         }
 
 
         pick(pointerInfo : PointerInfo) {
         pick(pointerInfo : PointerInfo) {
-            const p = this.getParameters();
-            const ctx = p.canvas2D.getContext('2d');
-            const {x, y} = p.getMouseCoordinates(pointerInfo);
+            const {canvas2D, setMetadata, getMouseCoordinates} = this.getParameters();
+            const ctx = canvas2D.getContext('2d');
+            const {x, y} = getMouseCoordinates(pointerInfo);
             const pixel = ctx!.getImageData(x, y, 1, 1).data;
             const pixel = ctx!.getImageData(x, y, 1, 1).data;
-            p.setMetadata({
-                color: '#' + ('000000' + this.rgbToHex(pixel[0], pixel[1], pixel[2])).slice(-6),
-                opacity: pixel[3] / 255
+            setMetadata({
+                color: Color3.FromInts(pixel[0], pixel[1], pixel[2]).toHexString(),
+                alpha: pixel[3] / 255
             });
             });
         }
         }
         
         
         setup () {
         setup () {
-            this.pointerObservable = this.getParameters().scene.onPointerObservable.add((pointerInfo) => {
+            this.pointerObserver = this.getParameters().scene.onPointerObservable.add((pointerInfo) => {
                 if (pointerInfo.pickInfo?.hit) {
                 if (pointerInfo.pickInfo?.hit) {
                     if (pointerInfo.type === PointerEventTypes.POINTERDOWN) {
                     if (pointerInfo.type === PointerEventTypes.POINTERDOWN) {
                         this.isPicking = true;
                         this.isPicking = true;
@@ -41,13 +44,10 @@ export const Eyedropper : IToolData = {
             this.isPicking = false;
             this.isPicking = false;
         }
         }
         cleanup () {
         cleanup () {
-            if (this.pointerObservable) {
-                this.getParameters().scene.onPointerObservable.remove(this.pointerObservable);
+            if (this.pointerObserver) {
+                this.getParameters().scene.onPointerObservable.remove(this.pointerObserver);
             }
             }
         }
         }
-        rgbToHex(r: number, g:number, b: number) {
-            return ((r << 16) | (g << 8) | b).toString(16);
-        }
     },
     },
     icon: `PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDQwIDQwIj48cmVjdCB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHN0eWxlPSJmaWxsOm5vbmUiLz48cGF0aCBkPSJNMjkuMzIsMTAu
     icon: `PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0MCIgaGVpZ2h0PSI0MCIgdmlld0JveD0iMCAwIDQwIDQwIj48cmVjdCB3aWR0aD0iNDAiIGhlaWdodD0iNDAiIHN0eWxlPSJmaWxsOm5vbmUiLz48cGF0aCBkPSJNMjkuMzIsMTAu
     NjhjLTEuNjYtMS42Ni00LjA2LTEtNS41Ni41YTExLjg5LDExLjg5LDAsMCwwLTEuNjYsMi4zMUwyMiwxMy40MWExLjg5LDEuODksMCwwLDAtMi42NiwwbC0uOS45YTEuODksMS44OSwwLDAsMC0uMjIsMi4zOWwtNi4wOSw2LjA5YTIuNzUsMi43NSwwLDAsMC0uNzMs
     NjhjLTEuNjYtMS42Ni00LjA2LTEtNS41Ni41YTExLjg5LDExLjg5LDAsMCwwLTEuNjYsMi4zMUwyMiwxMy40MWExLjg5LDEuODksMCwwLDAtMi42NiwwbC0uOS45YTEuODksMS44OSwwLDAsMC0uMjIsMi4zOWwtNi4wOSw2LjA5YTIuNzUsMi43NSwwLDAsMC0uNzMs

+ 15 - 13
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/floodfill.ts

@@ -1,28 +1,30 @@
 import { IToolParameters, IToolData } from '../textureEditorComponent';
 import { IToolParameters, IToolData } from '../textureEditorComponent';
-import { PointerEventTypes } from 'babylonjs/Events/pointerEvents';
+import { PointerEventTypes, PointerInfo } from 'babylonjs/Events/pointerEvents';
+import { Nullable } from 'babylonjs/types'
+import { Observer } from 'babylonjs/Misc/observable';
 
 
 export const Floodfill : IToolData = {
 export const Floodfill : IToolData = {
     name: 'Floodfill',
     name: 'Floodfill',
     type: class {
     type: class {
         getParameters: () => IToolParameters;
         getParameters: () => IToolParameters;
-        pointerObservable: any;
-
+        pointerObserver: Nullable<Observer<PointerInfo>>;
         constructor(getParameters: () => IToolParameters) {
         constructor(getParameters: () => IToolParameters) {
             this.getParameters = getParameters;
             this.getParameters = getParameters;
         }
         }
 
 
         fill() {
         fill() {
-            const p = this.getParameters();
-            const ctx = p.canvas2D.getContext('2d')!;
-            ctx.fillStyle = p.metadata.color;
-            ctx.globalAlpha = p.metadata.opacity;
-            ctx.globalCompositeOperation = 'source-over';
-            ctx.fillRect(0,0, p.size.width, p.size.height);
-            p.updateTexture();
+            const {metadata, startPainting, updatePainting, stopPainting} = this.getParameters();
+            const ctx = startPainting();
+            ctx.fillStyle = metadata.color;
+            ctx.globalAlpha = metadata.alpha;
+            ctx.globalCompositeOperation = 'copy';
+            ctx.fillRect(0,0, ctx.canvas.width, ctx.canvas.height);
+            updatePainting();
+            stopPainting();
         }
         }
         
         
         setup () {
         setup () {
-            this.pointerObservable = this.getParameters().scene.onPointerObservable.add((pointerInfo) => {
+            this.pointerObserver = this.getParameters().scene.onPointerObservable.add((pointerInfo) => {
                 if (pointerInfo.pickInfo?.hit) {
                 if (pointerInfo.pickInfo?.hit) {
                     if (pointerInfo.type === PointerEventTypes.POINTERDOWN) {
                     if (pointerInfo.type === PointerEventTypes.POINTERDOWN) {
                         this.fill();
                         this.fill();
@@ -31,8 +33,8 @@ export const Floodfill : IToolData = {
             });
             });
         }
         }
         cleanup () {
         cleanup () {
-            if (this.pointerObservable) {
-                this.getParameters().scene.onPointerObservable.remove(this.pointerObservable);
+            if (this.pointerObserver) {
+                this.getParameters().scene.onPointerObservable.remove(this.pointerObserver);
             }
             }
         }
         }
     },
     },

File diff suppressed because it is too large
+ 102 - 50
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/paintbrush.ts


File diff suppressed because it is too large
+ 63 - 3
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/rectangleSelect.ts


+ 65 - 61
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/propertiesBar.tsx

@@ -11,6 +11,8 @@ interface IPropertiesBarProps {
     resetTexture() : void;
     resetTexture() : void;
     resizeTexture(width: number, height: number) : void;
     resizeTexture(width: number, height: number) : void;
     uploadTexture(file : File) : void;
     uploadTexture(file : File) : void;
+    mipLevel: number;
+    setMipLevel: (mipLevel : number) => void;
 }
 }
 
 
 interface IPropertiesBarState {
 interface IPropertiesBarState {
@@ -20,7 +22,7 @@ interface IPropertiesBarState {
 
 
 interface IPixelDataProps {
 interface IPixelDataProps {
     name : string;
     name : string;
-    data?: number;
+    data: number | undefined;
 }
 }
 
 
 export class PropertiesBar extends React.Component<IPropertiesBarProps,IPropertiesBarState> {
 export class PropertiesBar extends React.Component<IPropertiesBarProps,IPropertiesBarState> {
@@ -54,7 +56,7 @@ export class PropertiesBar extends React.Component<IPropertiesBarProps,IProperti
     }
     }
 
 
     private pixelData(props: IPixelDataProps) {
     private pixelData(props: IPixelDataProps) {
-        return <span className='pixel-data'>{props.name}: <span className='value'>{props.data || '-'}</span></span>;
+        return <span className='pixel-data'>{props.name}: <span className='value'>{props.data !== undefined ? props.data : '-'}</span></span>;
     }
     }
 
 
     private getNewDimension(oldDim : number, newDim : any) {
     private getNewDimension(oldDim : number, newDim : any) {
@@ -68,76 +70,78 @@ export class PropertiesBar extends React.Component<IPropertiesBarProps,IProperti
     }
     }
 
 
     render() {
     render() {
+        const {mipLevel, setMipLevel, pixelData, resizeTexture, texture, face, setFace, saveTexture, resetTexture, uploadTexture} = this.props;
         return <div id='properties'>
         return <div id='properties'>
                 <div className='tab' id='logo-tab'>
                 <div className='tab' id='logo-tab'>
                     <img className='icon' src={this._babylonLogo}/>
                     <img className='icon' src={this._babylonLogo}/>
                 </div>
                 </div>
-                <div className='tab' id='dimensions-tab'>
-                    <form onSubmit={evt => {
-                        this.props.resizeTexture(this.state.width, this.state.height);
-                        evt.preventDefault();
-                    }}>
-                        <label className='dimensions'>
-                            W: <input type='text' value={this.state.width} onChange={(evt) => this.setState({width: this.getNewDimension(this.state.width, evt.target.value)})}/>
+                <div id='left'>
+                    <div className='tab' id='dimensions-tab'>
+                        <form onSubmit={evt => {
+                            this.props.resizeTexture(this.state.width, this.state.height);
+                            evt.preventDefault();
+                        }}>
+                            <label className='dimensions'>
+                                W: <input type='text' value={this.state.width} readOnly={texture.isCube} onChange={(evt) => this.setState({width: this.getNewDimension(this.state.width, evt.target.value)})}/>
                             </label>
                             </label>
-                        <label className='dimensions'>
-                            H: <input type='text' value={this.state.height} onChange={(evt) => this.setState({height: this.getNewDimension(this.state.height, evt.target.value)})}/>
+                            <label className='dimensions'>
+                                H: <input type='text' value={this.state.height} readOnly={texture.isCube} onChange={(evt) => this.setState({height: this.getNewDimension(this.state.height, evt.target.value)})}/>
                             </label>
                             </label>
-                        <img id='resize' className='icon button' title='Resize' alt='Resize' src={this._resizeButton} onClick={() => this.props.resizeTexture(this.state.width, this.state.height)}/> 
-                    </form>
-                </div>
-                <div className='tab' id='pixel-coords-tab'>
-                    <this.pixelData name='X' data={this.props.pixelData.x}/>
-                    <this.pixelData name='Y' data={this.props.pixelData.y}/>
-                </div>
-                <div className='tab' id='pixel-color-tab'>
-                    <this.pixelData name='R' data={this.props.pixelData.r}/>
-                    <this.pixelData name='G' data={this.props.pixelData.g}/>
-                    <this.pixelData name='B' data={this.props.pixelData.b}/>
-                    <this.pixelData name='A' data={this.props.pixelData.a}/>
-                </div>
-                {this.props.texture.isCube &&
-                <>
-                    <div className='tab' id='face-tab'>
-                        {this._faces.map((face, index) =>
-                        <img
-                            key={index}
-                            className={this.props.face == index ? 'icon face button active' : 'icon face button'}
-                            src={face}
-                            onClick={() => this.props.setFace(index)}
-                        />)}
+                        {!texture.isCube && <img id='resize' className='icon button' title='Resize' alt='Resize' src={this._resizeButton} onClick={() => resizeTexture(this.state.width, this.state.height)}/>} 
+                        </form>
+                    </div>
+                    <div className='tab' id='pixel-coords-tab'>
+                        <this.pixelData name='X' data={pixelData.x}/>
+                        <this.pixelData name='Y' data={pixelData.y}/>
                     </div>
                     </div>
-                    <div className='tab' id='mip-tab'>
-                        <img title='Mip Preview Up' className='icon button' src={this._mipUp} />
-                        <img title='Mip Preview Down' className='icon button' src={this._mipDown} />
+                    <div className='tab' id='pixel-color-tab'>
+                        <this.pixelData name='R' data={pixelData.r}/>
+                        <this.pixelData name='G' data={pixelData.g}/>
+                        <this.pixelData name='B' data={pixelData.b}/>
+                        <this.pixelData name='A' data={pixelData.a}/>
                     </div>
                     </div>
-                </>}
+                    {texture.isCube &&
+                        <div className='tab' id='face-tab'>
+                            {this._faces.map((value, index) =>
+                            <img
+                                key={index}
+                                className={face == index ? 'icon face button active' : 'icon face button'}
+                                src={value}
+                                onClick={() => setFace(index)}
+                            />)}
+                        </div>
+                    }
+                    {!texture.noMipmap &&
+                        <div className='tab' id='mip-tab'>
+                            <img title='Mip Preview Up' className='icon button' src={this._mipUp} onClick={() => mipLevel > 0 && setMipLevel(mipLevel - 1)} />
+                            <img title='Mip Preview Down' className='icon button' src={this._mipDown} onClick={() => mipLevel < 12 && setMipLevel(mipLevel + 1)} />
+                        </div>
+                    }
+                </div>
                 <div className='tab' id='right-tab'>
                 <div className='tab' id='right-tab'>
-                    <div className='content'>
-                        <img title='Reset' className='icon button' src={this._resetButton} onClick={() => this.props.resetTexture()}/>
-                        <label>
-                            <input
-                                accept='.jpg, .png, .tga, .dds, .env'
-                                type='file'
-                                onChange={
-                                    (evt : React.ChangeEvent<HTMLInputElement>) => {
-                                        const files = evt.target.files;
-                                        if (files && files.length) {
-                                            this.props.uploadTexture(files[0]);
-                                        }
-                                
-                                        evt.target.value = "";
+                    <img title='Reset' className='icon button' src={this._resetButton} onClick={() => resetTexture()}/>
+                    <label>
+                        <input
+                            accept='.jpg, .png, .tga, .dds, .env'
+                            type='file'
+                            onChange={
+                                (evt : React.ChangeEvent<HTMLInputElement>) => {
+                                    const files = evt.target.files;
+                                    if (files && files.length) {
+                                        uploadTexture(files[0]);
                                     }
                                     }
+                            
+                                    evt.target.value = "";
                                 }
                                 }
-                            />
-                            <img
-                                title='Upload'
-                                className='icon button'
-                                src={this._uploadButton}
-                            />
-                        </label>
-                        <img title='Save' className='icon button' src={this._saveButton} onClick={() => this.props.saveTexture()}/>
-                    </div>
+                            }
+                        />
+                        <img
+                            title='Upload'
+                            className='icon button'
+                            src={this._uploadButton}
+                        />
+                    </label>
+                    <img title='Save' className='icon button' src={this._saveButton} onClick={() => saveTexture()}/>
                 </div>
                 </div>
         </div>;
         </div>;
     }
     }

+ 233 - 41
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/textureCanvasManager.ts

@@ -34,6 +34,7 @@ import { StackPanel } from 'babylonjs-gui/2D/controls/stackPanel';
 import { Control } from 'babylonjs-gui/2D/controls/control';
 import { Control } from 'babylonjs-gui/2D/controls/control';
 import { Style } from 'babylonjs-gui/2D/style';
 import { Style } from 'babylonjs-gui/2D/style';
 import { AdvancedDynamicTexture } from 'babylonjs-gui/2D/advancedDynamicTexture';
 import { AdvancedDynamicTexture } from 'babylonjs-gui/2D/advancedDynamicTexture';
+import { IMetadata } from './textureEditorComponent';
 
 
 
 
 export interface IPixelData {
 export interface IPixelData {
@@ -57,6 +58,7 @@ export class TextureCanvasManager {
     private _engine: Engine;
     private _engine: Engine;
     private _scene: Scene;
     private _scene: Scene;
     private _camera: FreeCamera;
     private _camera: FreeCamera;
+    private _cameraPos: Vector2;
 
 
     private _scale : number;
     private _scale : number;
     private _isPanning : boolean = false;
     private _isPanning : boolean = false;
@@ -81,6 +83,7 @@ export class TextureCanvasManager {
 
 
     private _channels : IChannel[] = [];
     private _channels : IChannel[] = [];
     private _face : number = 0;
     private _face : number = 0;
+    private _mipLevel : number = 0;
 
 
     /* The texture from the original engine that we invoked the editor on */
     /* The texture from the original engine that we invoked the editor on */
     private _originalTexture: BaseTexture;
     private _originalTexture: BaseTexture;
@@ -108,6 +111,9 @@ export class TextureCanvasManager {
     private static MIN_SCALE : number = 0.01;
     private static MIN_SCALE : number = 0.01;
     private static MAX_SCALE : number = 10;
     private static MAX_SCALE : number = 10;
 
 
+    private static SELECT_ALL_KEY = 'KeyA';
+    private static DESELECT_KEY = 'Escape'
+
     private _tool : Nullable<ITool>;
     private _tool : Nullable<ITool>;
 
 
     private _setPixelData : (pixelData : IPixelData) => void;
     private _setPixelData : (pixelData : IPixelData) => void;
@@ -116,24 +122,39 @@ export class TextureCanvasManager {
 
 
     private _window : Window;
     private _window : Window;
 
 
-    public metadata : any = {};
+    private _metadata : IMetadata;
 
 
     private _editing3D : boolean = false;
     private _editing3D : boolean = false;
 
 
+    private _onUpdate : () => void;
+    private _setMetadata : (metadata: any) => void;
+
+    private _imageData : Uint8Array | Uint8ClampedArray;
+    private _canUpdate : boolean = true;
+    private _shouldUpdate : boolean = false;
+    private _paintCanvas: HTMLCanvasElement;
+
     public constructor(
     public constructor(
         texture: BaseTexture,
         texture: BaseTexture,
         window: Window,
         window: Window,
         canvasUI: HTMLCanvasElement,
         canvasUI: HTMLCanvasElement,
         canvas2D: HTMLCanvasElement,
         canvas2D: HTMLCanvasElement,
         canvas3D: HTMLCanvasElement,
         canvas3D: HTMLCanvasElement,
-        setPixelData: (pixelData : IPixelData) => void
+        setPixelData: (pixelData : IPixelData) => void,
+        metadata: IMetadata,
+        onUpdate: () => void,
+        setMetadata: (metadata: any) => void
     ) {
     ) {
         this._window = window;
         this._window = window;
 
 
         this._UICanvas = canvasUI;
         this._UICanvas = canvasUI;
         this._2DCanvas = canvas2D;
         this._2DCanvas = canvas2D;
         this._3DCanvas = canvas3D;
         this._3DCanvas = canvas3D;
+        this._paintCanvas = document.createElement('canvas');
         this._setPixelData = setPixelData;
         this._setPixelData = setPixelData;
+        this._metadata = metadata;
+        this._onUpdate = onUpdate;
+        this._setMetadata = setMetadata;
 
 
         this._size = texture.getSize();
         this._size = texture.getSize();
         this._originalTexture = texture;
         this._originalTexture = texture;
@@ -144,8 +165,9 @@ export class TextureCanvasManager {
 
 
         this._camera = new FreeCamera('camera', new Vector3(0, 0, -1), this._scene);
         this._camera = new FreeCamera('camera', new Vector3(0, 0, -1), this._scene);
         this._camera.mode = Camera.ORTHOGRAPHIC_CAMERA;
         this._camera.mode = Camera.ORTHOGRAPHIC_CAMERA;
+        this._cameraPos = new Vector2();
 
 
-        this._channelsTexture = new HtmlElementTexture('ct', this._2DCanvas, {engine: this._engine, scene: null, samplingMode: Engine.TEXTURE_NEAREST_LINEAR});
+        this._channelsTexture = new HtmlElementTexture('ct', this._2DCanvas, {engine: this._engine, scene: null, samplingMode: Texture.NEAREST_SAMPLINGMODE, generateMipMaps: true});
 
 
         this._3DEngine = new Engine(this._3DCanvas);
         this._3DEngine = new Engine(this._3DCanvas);
         this._3DScene = new Scene(this._3DEngine);
         this._3DScene = new Scene(this._3DEngine);
@@ -156,15 +178,13 @@ export class TextureCanvasManager {
         cam.mode = Camera.ORTHOGRAPHIC_CAMERA;
         cam.mode = Camera.ORTHOGRAPHIC_CAMERA;
         [cam.orthoBottom, cam.orthoLeft, cam.orthoTop, cam.orthoRight] = [-0.5, -0.5, 0.5, 0.5];
         [cam.orthoBottom, cam.orthoLeft, cam.orthoTop, cam.orthoRight] = [-0.5, -0.5, 0.5, 0.5];
         this._3DPlane = PlaneBuilder.CreatePlane('texture', {width: 1, height: 1}, this._3DScene);
         this._3DPlane = PlaneBuilder.CreatePlane('texture', {width: 1, height: 1}, this._3DScene);
+        this._3DPlane.hasVertexAlpha = true;
         const mat = new StandardMaterial('material', this._3DScene);
         const mat = new StandardMaterial('material', this._3DScene);
         mat.diffuseTexture = this._3DCanvasTexture;
         mat.diffuseTexture = this._3DCanvasTexture;
+        mat.useAlphaFromDiffuseTexture = true;
         mat.disableLighting = true;
         mat.disableLighting = true;
         mat.emissiveColor = Color3.White();
         mat.emissiveColor = Color3.White();
         this._3DPlane.material = mat;
         this._3DPlane.material = mat;
-        
-
-        this.grabOriginalTexture();
-
 
 
         this._planeMaterial = new ShaderMaterial(
         this._planeMaterial = new ShaderMaterial(
             'shader',
             'shader',
@@ -194,11 +214,23 @@ export class TextureCanvasManager {
                     uniform bool g;
                     uniform bool g;
                     uniform bool b;
                     uniform bool b;
                     uniform bool a;
                     uniform bool a;
+
+                    uniform int x1;
+                    uniform int y1;
+                    uniform int x2;
+                    uniform int y2;
+                    uniform int w;
+                    uniform int h;
+
+                    uniform int time;
             
             
                     varying vec2 vUV;
                     varying vec2 vUV;
+
+                    float scl = 200.0;
+                    float speed = 10.0 / 1000.0;
+                    float smoothing = 0.2;
             
             
                     void main(void) {
                     void main(void) {
-                        float size = 20.0;
                         vec2 pos2 = vec2(gl_FragCoord.x, gl_FragCoord.y);
                         vec2 pos2 = vec2(gl_FragCoord.x, gl_FragCoord.y);
                         vec2 pos = floor(pos2 * 0.05);
                         vec2 pos = floor(pos2 * 0.05);
                         float pattern = mod(pos.x + pos.y, 2.0); 
                         float pattern = mod(pos.x + pos.y, 2.0); 
@@ -244,20 +276,42 @@ export class TextureCanvasManager {
                                 col.a = 1.0;
                                 col.a = 1.0;
                             }
                             }
                         }
                         }
-                        gl_FragColor = col;
                         gl_FragColor = col * (col.a) + bg * (1.0 - col.a);
                         gl_FragColor = col * (col.a) + bg * (1.0 - col.a);
+                        float wF = float(w);
+                        float hF = float(h);
+                        int xPixel = int(floor(vUV.x * wF));
+                        int yPixel = int(floor((1.0 - vUV.y) * hF));
+                        int xDis = min(abs(xPixel - x1), abs(xPixel - x2));
+                        int yDis = min(abs(yPixel - y1), abs(yPixel - y2));
+                        if (xPixel >= x1 && yPixel >= y1 && xPixel <= x2 && yPixel <= y2) {
+                            if (xDis <= 4 || yDis <= 4) {
+                                float c = sin(vUV.x * scl + vUV.y * scl + float(time) * speed);
+                                c = smoothstep(-smoothing,smoothing,c);
+                                float val = 1.0 - c;
+                                gl_FragColor = vec4(val, val, val, 1.0) * 0.7 + gl_FragColor * 0.3;
+                            }
+                        }
                     }`
                     }`
             },
             },
         {
         {
             attributes: ['position', 'uv'],
             attributes: ['position', 'uv'],
-            uniforms: ['worldViewProjection', 'textureSampler', 'r', 'g', 'b', 'a']
+            uniforms: ['worldViewProjection', 'textureSampler', 'r', 'g', 'b', 'a', 'x1', 'y1', 'x2', 'y2', 'w', 'h', 'time']
         });
         });
+        
+        this.grabOriginalTexture();
 
 
         this._planeMaterial.setTexture('textureSampler', this._channelsTexture);
         this._planeMaterial.setTexture('textureSampler', this._channelsTexture);
         this._planeMaterial.setFloat('r', 1.0);
         this._planeMaterial.setFloat('r', 1.0);
         this._planeMaterial.setFloat('g', 1.0);
         this._planeMaterial.setFloat('g', 1.0);
         this._planeMaterial.setFloat('b', 1.0);
         this._planeMaterial.setFloat('b', 1.0);
         this._planeMaterial.setFloat('a', 1.0);
         this._planeMaterial.setFloat('a', 1.0);
+        this._planeMaterial.setInt('x1', -1);
+        this._planeMaterial.setInt('y1', -1);
+        this._planeMaterial.setInt('x2', -1);
+        this._planeMaterial.setInt('y2', -1);
+        this._planeMaterial.setInt('w', this._size.width);
+        this._planeMaterial.setInt('h', this._size.height);
+        this._planeMaterial.setInt('time', 0);
         this._plane.material = this._planeMaterial;
         this._plane.material = this._planeMaterial;
         
         
         const adt = AdvancedDynamicTexture.CreateFullscreenUI('gui', true, this._scene);
         const adt = AdvancedDynamicTexture.CreateFullscreenUI('gui', true, this._scene);
@@ -284,7 +338,7 @@ export class TextureCanvasManager {
         topBar.background = '#666666';
         topBar.background = '#666666';
         topBar.thickness = 0;
         topBar.thickness = 0;
         topBar.hoverCursor = 'grab';
         topBar.hoverCursor = 'grab';
-        topBar.onPointerDownObservable.add(evt => {this._GUI.isDragging = true; topBar.hoverCursor = 'grabbing';});
+        topBar.onPointerDownObservable.add(() => {this._GUI.isDragging = true; topBar.hoverCursor = 'grabbing';});
         topBar.onPointerUpObservable.add(() => {this._GUI.isDragging = false; this._GUI.dragCoords = null; topBar.hoverCursor = 'grab';});
         topBar.onPointerUpObservable.add(() => {this._GUI.isDragging = false; this._GUI.dragCoords = null; topBar.hoverCursor = 'grab';});
 
 
         const title = new TextBlock();
         const title = new TextBlock();
@@ -295,25 +349,53 @@ export class TextureCanvasManager {
         topBar.addControl(title);
         topBar.addControl(title);
         this._GUI.toolWindow.addControl(topBar);
         this._GUI.toolWindow.addControl(topBar);
 
 
-        this._window.addEventListener('pointermove',  (evt : PointerEvent) => {
+        this._window.addEventListener('pointermove', evt => {
             if (!this._GUI.isDragging) return;
             if (!this._GUI.isDragging) return;
             if (!this._GUI.dragCoords) {
             if (!this._GUI.dragCoords) {
                 this._GUI.dragCoords = new Vector2(evt.x, evt.y);
                 this._GUI.dragCoords = new Vector2(evt.x, evt.y);
                 return;
                 return;
             }
             }
-            let x = parseInt(this._GUI.toolWindow.left.toString().replace('px', ''));
-            let y = parseInt(this._GUI.toolWindow.top.toString().replace('px', ''));
-            x += evt.x - this._GUI.dragCoords.x;
-            y += evt.y - this._GUI.dragCoords.y;
-            this._GUI.toolWindow.left = `${x}px`;
-            this._GUI.toolWindow.top = `${y}px`;
+            this._GUI.toolWindow.leftInPixels += evt.x - this._GUI.dragCoords.x;
+            this._GUI.toolWindow.topInPixels += evt.y - this._GUI.dragCoords.y;
             this._GUI.dragCoords.x = evt.x;
             this._GUI.dragCoords.x = evt.x;
             this._GUI.dragCoords.y = evt.y;
             this._GUI.dragCoords.y = evt.y;
         });
         });
 
 
+        this._window.addEventListener('keydown', evt => {
+            this._keyMap[evt.code] = true;
+            if (evt.code === TextureCanvasManager.SELECT_ALL_KEY && evt.ctrlKey) {
+                this._setMetadata({
+                    select: {
+                        x1: 0,
+                        y1: 0,
+                        x2: this._size.width,
+                        y2: this._size.height
+                    }
+                });
+                evt.preventDefault();
+            }
+            if (evt.code === TextureCanvasManager.DESELECT_KEY) {
+                this._setMetadata({
+                    select: {
+                        x1: -1,
+                        y1: -1,
+                        x2: -1,
+                        y2: -1
+                    }
+                })
+            }
+        });
+        
+        this._window.addEventListener('keyup', evt => {
+            this._keyMap[evt.code] = false;
+        });
+
         this._engine.runRenderLoop(() => {
         this._engine.runRenderLoop(() => {
             this._engine.resize();
             this._engine.resize();
+            this.GUI.toolWindow.left = Math.min(Math.max(this._GUI.toolWindow.leftInPixels, -this._UICanvas.width + this._GUI.toolWindow.widthInPixels), 0);
+            this.GUI.toolWindow.top = Math.min(Math.max(this._GUI.toolWindow.topInPixels, -this._UICanvas.height + this._GUI.toolWindow.heightInPixels), 0);
             this._scene.render();
             this._scene.render();
+            this._planeMaterial.setInt('time', new Date().getTime());
         });
         });
 
 
         this._scale = 1.5;
         this._scale = 1.5;
@@ -322,10 +404,11 @@ export class TextureCanvasManager {
         this._scene.onBeforeRenderObservable.add(() => {
         this._scene.onBeforeRenderObservable.add(() => {
             this._scale = Math.min(Math.max(this._scale, TextureCanvasManager.MIN_SCALE), TextureCanvasManager.MAX_SCALE);
             this._scale = Math.min(Math.max(this._scale, TextureCanvasManager.MIN_SCALE), TextureCanvasManager.MAX_SCALE);
             const ratio = this._UICanvas?.width / this._UICanvas?.height;
             const ratio = this._UICanvas?.width / this._UICanvas?.height;
-            this._camera.orthoBottom = -1 / this._scale;
-            this._camera.orthoTop = 1 / this._scale;
-            this._camera.orthoLeft =  ratio / -this._scale;
-            this._camera.orthoRight = ratio / this._scale;
+            const {x,y} = this._cameraPos;
+            this._camera.orthoBottom = y - 1 / this._scale;
+            this._camera.orthoTop = y + 1 / this._scale;
+            this._camera.orthoLeft =  x - ratio / this._scale;
+            this._camera.orthoRight = x + ratio / this._scale;
         })
         })
 
 
         this._scene.onPointerObservable.add((pointerInfo) => {
         this._scene.onPointerObservable.add((pointerInfo) => {
@@ -349,16 +432,15 @@ export class TextureCanvasManager {
                     break;
                     break;
                 case PointerEventTypes.POINTERMOVE:
                 case PointerEventTypes.POINTERMOVE:
                     if (this._isPanning) {
                     if (this._isPanning) {
-                        this._camera.position.x -= (pointerInfo.event.x - this._mouseX) / this._scale * TextureCanvasManager.PAN_SPEED;
-                        this._camera.position.y += (pointerInfo.event.y - this._mouseY) / this._scale * TextureCanvasManager.PAN_SPEED;
+                        this._cameraPos.x -= (pointerInfo.event.x - this._mouseX) / this._scale * TextureCanvasManager.PAN_SPEED;
+                        this._cameraPos.y += (pointerInfo.event.y - this._mouseY) / this._scale * TextureCanvasManager.PAN_SPEED;
                         this._mouseX = pointerInfo.event.x;
                         this._mouseX = pointerInfo.event.x;
                         this._mouseY = pointerInfo.event.y;
                         this._mouseY = pointerInfo.event.y;
                     }
                     }
                     if (pointerInfo.pickInfo?.hit) {
                     if (pointerInfo.pickInfo?.hit) {
                         const pos = this.getMouseCoordinates(pointerInfo);
                         const pos = this.getMouseCoordinates(pointerInfo);
-                        const ctx = this._2DCanvas.getContext('2d');
-                        const pixel = ctx?.getImageData(pos.x, pos.y, 1, 1).data!;
-                        this._setPixelData({x: pos.x, y: pos.y, r:pixel[0], g:pixel[1], b:pixel[2], a:pixel[3]});
+                        const idx = (pos.x + pos.y * this._size.width) * 4;
+                        this._setPixelData({x: pos.x, y: pos.y, r:this._imageData[idx], g:this._imageData[idx + 1], b:this._imageData[idx + 2], a:this._imageData[idx + 3]});
                     } else {
                     } else {
                         this._setPixelData({});
                         this._setPixelData({});
                     }
                     }
@@ -370,11 +452,13 @@ export class TextureCanvasManager {
             switch(kbInfo.type) {
             switch(kbInfo.type) {
                 case KeyboardEventTypes.KEYDOWN:
                 case KeyboardEventTypes.KEYDOWN:
                     this._keyMap[kbInfo.event.key] = true;
                     this._keyMap[kbInfo.event.key] = true;
-                    if (kbInfo.event.key === TextureCanvasManager.ZOOM_IN_KEY) {
-                        this._scale += TextureCanvasManager.ZOOM_KEYBOARD_SPEED * this._scale;
-                    }
-                    if (kbInfo.event.key === TextureCanvasManager.ZOOM_OUT_KEY) {
-                        this._scale -= TextureCanvasManager.ZOOM_KEYBOARD_SPEED * this._scale;
+                    switch (kbInfo.event.key) {
+                        case TextureCanvasManager.ZOOM_IN_KEY:
+                            this._scale += TextureCanvasManager.ZOOM_KEYBOARD_SPEED * this._scale;
+                            break;
+                        case TextureCanvasManager.ZOOM_OUT_KEY:
+                            this._scale -= TextureCanvasManager.ZOOM_KEYBOARD_SPEED * this._scale;
+                            break;
                     }
                     }
                     break;
                     break;
                 case KeyboardEventTypes.KEYUP:
                 case KeyboardEventTypes.KEYUP:
@@ -384,6 +468,7 @@ export class TextureCanvasManager {
         });
         });
     }
     }
 
 
+
     public async updateTexture() {
     public async updateTexture() {
         this._didEdit = true;
         this._didEdit = true;
         const element = this._editing3D ? this._3DCanvas : this._2DCanvas;
         const element = this._editing3D ? this._3DCanvas : this._2DCanvas;
@@ -407,16 +492,102 @@ export class TextureCanvasManager {
             } else {
             } else {
                 (this._target as HtmlElementTexture).element = element;
                 (this._target as HtmlElementTexture).element = element;
             }
             }
-            (this._target as HtmlElementTexture).update((this._originalTexture as Texture).invertY);
+            this.queueTextureUpdate();
         }
         }
         this._originalTexture._texture = this._target._texture;
         this._originalTexture._texture = this._target._texture;
         this._channelsTexture.element = element;
         this._channelsTexture.element = element;
         this.updateDisplay();
         this.updateDisplay();
+        this._onUpdate();
+    }
+
+    private queueTextureUpdate() {
+        if (this._canUpdate) {
+            (this._target as HtmlElementTexture).update((this._originalTexture as Texture).invertY);
+            if (this._editing3D) {
+                this._imageData = this._3DEngine.readPixels(0, 0, this._size.width, this._size.height);
+            } else {
+                this._imageData = this._2DCanvas.getContext('2d')!.getImageData(0, 0, this._size.width, this._size.height).data;
+            }
+            this._canUpdate = false;
+            this._shouldUpdate = false;
+            setTimeout(() => {
+                this._canUpdate = true;
+                if (this._shouldUpdate) {
+                    this.queueTextureUpdate();
+                }
+            }, 32);
+        } else {
+            this._shouldUpdate = true;
+        }
+    }
+
+    public startPainting() : CanvasRenderingContext2D {
+        let x = 0, y = 0, w = this._size.width, h = this._size.height;
+        if (this._metadata.select.x1 != -1) {
+            x = this._metadata.select.x1;
+            y = this._metadata.select.y1;
+            w = this._metadata.select.x2 - this._metadata.select.x1;
+            h = this._metadata.select.y2 - this._metadata.select.y1;
+        }
+        this._paintCanvas.width = w;
+        this._paintCanvas.height = h;
+        const ctx = this._paintCanvas.getContext('2d')!;
+        ctx.imageSmoothingEnabled = false;
+        ctx.drawImage(this._2DCanvas, x, y, w, h, 0, 0, w, h);
+        return ctx;
+    }
+
+    public updatePainting() {
+        let x = 0, y = 0, w = this._size.width, h = this._size.height;
+        if (this._metadata.select.x1 != -1) {
+            x = this._metadata.select.x1;
+            y = this._metadata.select.y1;
+            w = this._metadata.select.x2 - this._metadata.select.x1;
+            h = this._metadata.select.y2 - this._metadata.select.y1;
+        }
+        let editingAllChannels = true;
+        this._channels.forEach(channel => {
+            if (!channel.editable) editingAllChannels = false;
+        })
+        let oldData : Uint8ClampedArray;
+        if (!editingAllChannels) {
+            oldData = this._2DCanvas.getContext('2d')!.getImageData(x, y, w, h).data;
+        }
+        const ctx = this._paintCanvas.getContext('2d')!;
+        const ctx2D = this.canvas2D.getContext('2d')!;
+        ctx2D.globalAlpha = 1.0;
+        ctx2D.globalCompositeOperation = 'destination-out';
+        ctx2D.fillStyle = 'white';
+        ctx2D.fillRect(x,y,w,h);
+        ctx2D.imageSmoothingEnabled = false;
+        if (!editingAllChannels) {
+            const newData = ctx.getImageData(0, 0, w, h);
+            const nd = newData.data;
+            this._channels.forEach((channel, index) => {
+                if (!channel.editable) {
+                    for(let i = index; i < w * h * 4; i += 4) {
+                        nd[i] = oldData[i];
+                    }
+                }
+            });
+            ctx2D.globalCompositeOperation = 'source-over';
+            ctx2D.globalAlpha = 1.0;
+            ctx2D.putImageData(newData, x, y);
+        } else {
+            ctx2D.globalCompositeOperation = 'source-over';
+            ctx2D.globalAlpha = 1.0;
+            ctx2D.drawImage(ctx.canvas, x, y);
+        }
+        this.updateTexture();
+    }
+
+    public stopPainting() : void {
+        this._paintCanvas.getContext('2d')!.clearRect(0, 0, this._paintCanvas.width, this._paintCanvas.height);
     }
     }
 
 
     private updateDisplay() {
     private updateDisplay() {
-        this._3DScene.render()
-        this._channelsTexture.update();
+        this._3DScene.render();
+        this._channelsTexture.update(true);
     }
     }
 
 
     public set channels(channels: IChannel[]) {
     public set channels(channels: IChannel[]) {
@@ -456,8 +627,11 @@ export class TextureCanvasManager {
             this._size.width,
             this._size.width,
             this._size.height,
             this._size.height,
             this._face,
             this._face,
-            {R:true ,G:true ,B:true ,A:true}
+            {R:true, G:true, B:true, A:true},
+            undefined,
+            this._mipLevel
         ).then(data => {
         ).then(data => {
+            this._imageData = data;
             TextureCanvasManager.paintPixelsOnCanvas(data, this._2DCanvas);
             TextureCanvasManager.paintPixelsOnCanvas(data, this._2DCanvas);
             this._3DCanvasTexture.update();
             this._3DCanvasTexture.update();
             this.updateDisplay();
             this.updateDisplay();
@@ -513,7 +687,6 @@ export class TextureCanvasManager {
         return this._tool;
         return this._tool;
     }
     }
 
 
-    // BROKEN : FIX THIS
     public set face(face: number) {
     public set face(face: number) {
         if (this._face !== face) {
         if (this._face !== face) {
             this._face = face;
             this._face = face;
@@ -522,6 +695,12 @@ export class TextureCanvasManager {
         }
         }
     }
     }
 
 
+    public set mipLevel(mipLevel : number) {
+        if (this._mipLevel === mipLevel) return;
+        this._mipLevel = mipLevel;
+        this.grabOriginalTexture(false);
+    }
+
     /** Returns the tool GUI object, allowing tools to access the GUI */
     /** Returns the tool GUI object, allowing tools to access the GUI */
     public get GUI() {
     public get GUI() {
         return this._GUI;
         return this._GUI;
@@ -532,6 +711,15 @@ export class TextureCanvasManager {
         return this._3DScene;
         return this._3DScene;
     }
     }
 
 
+    public set metadata(metadata: IMetadata) {
+        this._metadata = metadata;
+        const {x1,y1,x2,y2} = metadata.select;
+        this._planeMaterial.setInt('x1', x1);
+        this._planeMaterial.setInt('y1', y1);
+        this._planeMaterial.setInt('x2', x2);
+        this._planeMaterial.setInt('y2', y2);
+    }
+
     private makePlane() {
     private makePlane() {
         const textureRatio = this._size.width / this._size.height;
         const textureRatio = this._size.width / this._size.height;
         if (this._plane) this._plane.dispose();
         if (this._plane) this._plane.dispose();
@@ -551,6 +739,7 @@ export class TextureCanvasManager {
         this.grabOriginalTexture();
         this.grabOriginalTexture();
         this.makePlane();
         this.makePlane();
         this._didEdit = false;
         this._didEdit = false;
+        this._onUpdate();
     }
     }
 
 
     public async resize(newSize : ISize) {
     public async resize(newSize : ISize) {
@@ -567,9 +756,11 @@ export class TextureCanvasManager {
         this._2DCanvas.height = this._size.height;
         this._2DCanvas.height = this._size.height;
         this._3DCanvas.width = this._size.width;
         this._3DCanvas.width = this._size.width;
         this._3DCanvas.height = this._size.height;
         this._3DCanvas.height = this._size.height;
+        this._planeMaterial.setInt('w', this._size.width);
+        this._planeMaterial.setInt('h', this._size.height);
         if (adjustZoom) {
         if (adjustZoom) {
-            this._camera.position.x = 0;
-            this._camera.position.y = 0;
+            this._cameraPos.x = 0;
+            this._cameraPos.y = 0;
             this._scale = 1.5 / (this._size.width/this._size.height);
             this._scale = 1.5 / (this._size.width/this._size.height);
         }
         }
         this.makePlane();
         this.makePlane();
@@ -607,7 +798,7 @@ export class TextureCanvasManager {
                         this._scene,
                         this._scene,
                         this._originalTexture.noMipmap,
                         this._originalTexture.noMipmap,
                         false,
                         false,
-                        Engine.TEXTURE_NEAREST_SAMPLINGMODE,
+                        Texture.NEAREST_SAMPLINGMODE,
                         () => {
                         () => {
                             TextureHelper.GetTextureDataAsync(texture, texture.getSize().width, texture.getSize().height, 0, {R: true, G: true, B: true, A: true})
                             TextureHelper.GetTextureDataAsync(texture, texture.getSize().width, texture.getSize().height, 0, {R: true, G: true, B: true, A: true})
                                 .then((pixels) => {
                                 .then((pixels) => {
@@ -633,7 +824,8 @@ export class TextureCanvasManager {
         }
         }
         if (this._tool) {
         if (this._tool) {
             this._tool.instance.cleanup();
             this._tool.instance.cleanup();
-        }        
+        }
+        this._paintCanvas.parentNode?.removeChild(this._paintCanvas);
         this._3DPlane.dispose();
         this._3DPlane.dispose();
         this._3DCanvasTexture.dispose();
         this._3DCanvasTexture.dispose();
         this._3DScene.dispose();
         this._3DScene.dispose();

+ 53 - 47
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditor.scss

@@ -47,70 +47,59 @@
     }
     }
     
     
     #properties {
     #properties {
-        position: absolute;
         width: 100%;
         width: 100%;
         height: 40px;
         height: 40px;
         
         
         display: flex;
         display: flex;
-        flex-flow: row nowrap;
         align-items: center;
         align-items: center;
         font-size: 12px;
         font-size: 12px;
         color: white;
         color: white;
         user-select: none;
         user-select: none;
-        background-color: #1e1e1e;
+        background-color: #333333;
     
     
         .tab {
         .tab {
-            display: flex;
+            display: inline-flex;
             line-height: 40px;
             line-height: 40px;
             height: 40px;
             height: 40px;
             flex-shrink: 0;
             flex-shrink: 0;
             flex-grow: 0;
             flex-grow: 0;
-            margin-right: 2px;
+            border-right: 2px solid #1e1e1e;
             background-color: #333333;
             background-color: #333333;
         }
         }
-        #dimensions-tab {
-            form {
-                display: flex;
-            }
-            label {
-                margin-left: 15px;
-                color: #afafaf;
-                input {
-                    width: 40px;
-                    height: 24px;
-                    background-color: #000000;
-                    color: #ffffff;
-                    border: 0;
-                    padding-left: 4px;
-                    font-size: 12px;
+        #left {
+            overflow: hidden;
+            height: 40px;
+            flex-grow: 1;
+            flex-shrink: 1;
+            display: flex;
+            flex-wrap: wrap;
+            #dimensions-tab {
+                form {
+                    display: flex;
                 }
                 }
-    
-                &:last-of-type {
-                    margin-right: 8px;
+                label {
+                    margin-left: 15px;
+                    color: #afafaf;
+                    input {
+                        width: 40px;
+                        height: 24px;
+                        background-color: #000000;
+                        color: #ffffff;
+                        border: 0;
+                        padding-left: 4px;
+                        font-size: 12px;
+                    }
+        
+                    &:last-of-type {
+                        margin-right: 8px;
+                    }
                 }
                 }
             }
             }
-            @media only screen and (max-width: 334px) {
-                display: none;
-            }
-        }
-        #pixel-coords-tab {
-            @media only screen and (max-width: 440px) {
-                display: none;
-            }
-        }
-        #pixel-color-tab {
-            @media only screen and (max-width: 640px) {
-                display: none;
-            }
         }
         }
         #right-tab {
         #right-tab {
-            flex-grow: 1;
-            flex-shrink: 1;
             margin-right: 0;
             margin-right: 0;
-            .content {
-                position: absolute;
-                right: 0px;
-            }
+            flex-grow: 0;
+            flex-shrink: 0;
             
             
             input[type="file"] {
             input[type="file"] {
                 display: none;
                 display: none;
@@ -179,10 +168,22 @@
 
 
         #color {
         #color {
             margin-top: 8px;
             margin-top: 8px;
-            #activeColor {
+            #active-color-bg {
+                border-radius: 50%;
+                width: 20px;
+                height: 20px;
                 margin: 10px;
                 margin: 10px;
+                position: relative;
+                background-image: linear-gradient(45deg, #808080 25%, transparent 25%), linear-gradient(-45deg, #808080 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #808080 75%), linear-gradient(-45deg, transparent 75%, #808080 75%);
+                background-size: 20px 20px;
+                background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
+            }
+            #active-color {
                 width: 20px;
                 width: 20px;
                 height: 20px;
                 height: 20px;
+                position: absolute;
+                top: 0;
+                left: 0;
                 border-radius: 50%;
                 border-radius: 50%;
             }
             }
         }
         }
@@ -227,20 +228,25 @@
     
     
     #canvas-ui {
     #canvas-ui {
         width: 100%;
         width: 100%;
-        height: 100%;
+        height: calc(100% - 70px);
+        outline: none;
     }
     }
     
     
     #bottom-bar {
     #bottom-bar {
-        position: absolute;
-        bottom: 0;
         height: 30px;
         height: 30px;
         width: 100%;
         width: 100%;
         background-color: #333333;
         background-color: #333333;
         font-size: 14px;
         font-size: 14px;
         user-select: none;
         user-select: none;
         line-height: 30px;
         line-height: 30px;
+        position: relative;
         #file-url {
         #file-url {
-            margin-left: 30px;
+            left: 30px;
+            position: absolute;
+        }
+        #mip-level {
+            right: 30px;
+            position: absolute;
         }
         }
     }
     }
 }
 }

+ 57 - 14
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent.tsx

@@ -24,21 +24,23 @@ interface ITextureEditorComponentProps {
     texture: BaseTexture;
     texture: BaseTexture;
     url: string;
     url: string;
     window: React.RefObject<PopupComponent>;
     window: React.RefObject<PopupComponent>;
+    onUpdate: () => void;
 }
 }
 
 
 interface ITextureEditorComponentState {
 interface ITextureEditorComponentState {
     tools: ITool[];
     tools: ITool[];
     activeToolIndex: number;
     activeToolIndex: number;
-    metadata: any;
+    metadata: IMetadata;
     channels: IChannel[];
     channels: IChannel[];
     pixelData : IPixelData;
     pixelData : IPixelData;
     face: number;
     face: number;
+    mipLevel: number;
 }
 }
 
 
 export interface IToolParameters {
 export interface IToolParameters {
     /** The visible scene in the editor. Useful for adding pointer and keyboard events. */
     /** The visible scene in the editor. Useful for adding pointer and keyboard events. */
     scene: Scene;
     scene: Scene;
-    /** The 2D canvas which tools can paint on using the canvas API. */
+    /** The 2D canvas which you can sample pixel data from. Tools should not paint directly on this canvas. */
     canvas2D: HTMLCanvasElement;
     canvas2D: HTMLCanvasElement;
     /** The 3D scene which tools can add post processes to. */
     /** The 3D scene which tools can add post processes to. */
     scene3D: Scene;
     scene3D: Scene;
@@ -47,7 +49,7 @@ export interface IToolParameters {
     /** Pushes the editor texture back to the original scene. This should be called every time a tool makes any modification to a texture. */
     /** Pushes the editor texture back to the original scene. This should be called every time a tool makes any modification to a texture. */
     updateTexture: () => void;
     updateTexture: () => void;
     /** The metadata object which is shared between all tools. Feel free to store any information here. Do not set this directly: instead call setMetadata. */
     /** The metadata object which is shared between all tools. Feel free to store any information here. Do not set this directly: instead call setMetadata. */
-    metadata: any;
+    metadata: IMetadata;
     /** Call this when you want to mutate the metadata. */
     /** Call this when you want to mutate the metadata. */
     setMetadata: (data : any) => void;
     setMetadata: (data : any) => void;
     /** Returns the texture coordinates under the cursor */
     /** Returns the texture coordinates under the cursor */
@@ -56,6 +58,12 @@ export interface IToolParameters {
     GUI: IToolGUI;
     GUI: IToolGUI;
     /** Provides access to the BABYLON namespace */
     /** Provides access to the BABYLON namespace */
     BABYLON: any;
     BABYLON: any;
+    /** Provides a canvas that you can use the canvas API to paint on. */
+    startPainting: () => CanvasRenderingContext2D;
+    /** After you have painted on your canvas, call this method to push the updates back to the texture. */
+    updatePainting: () => void;
+    /** Call this when you are finished painting. */
+    stopPainting: () => void;
 }
 }
 
 
 
 
@@ -87,6 +95,18 @@ interface IToolConstructable {
     new (getParameters: () => IToolParameters) : IToolType;
     new (getParameters: () => IToolParameters) : IToolType;
 }
 }
 
 
+export interface IMetadata {
+    color: string;
+    alpha: number;
+    select: {
+        x1: number,
+        y1: number,
+        x2: number,
+        y2: number
+    }
+    [key: string] : any;
+}
+
 declare global {
 declare global {
     var _TOOL_DATA_ : IToolData;
     var _TOOL_DATA_ : IToolData;
 }
 }
@@ -96,6 +116,8 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
     private _UICanvas = React.createRef<HTMLCanvasElement>();
     private _UICanvas = React.createRef<HTMLCanvasElement>();
     private _2DCanvas = React.createRef<HTMLCanvasElement>();
     private _2DCanvas = React.createRef<HTMLCanvasElement>();
     private _3DCanvas = React.createRef<HTMLCanvasElement>();
     private _3DCanvas = React.createRef<HTMLCanvasElement>();
+    private _timer : number | null;
+    private static PREVIEW_UPDATE_DELAY_MS = 160;
 
 
     constructor(props : ITextureEditorComponentProps) {
     constructor(props : ITextureEditorComponentProps) {
         super(props);
         super(props);
@@ -114,17 +136,23 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
             activeToolIndex: -1,
             activeToolIndex: -1,
             metadata: {
             metadata: {
                 color: '#ffffff',
                 color: '#ffffff',
-                opacity: 1
+                alpha: 1,
+                select: {
+                    x1: -1,
+                    y1: -1,
+                    x2: -1,
+                    y2: -1
+                }
             },
             },
             channels,
             channels,
             pixelData: {},
             pixelData: {},
-            face: 0
+            face: 0,
+            mipLevel: 0
         }
         }
         this.loadToolFromURL = this.loadToolFromURL.bind(this);
         this.loadToolFromURL = this.loadToolFromURL.bind(this);
         this.changeTool = this.changeTool.bind(this);
         this.changeTool = this.changeTool.bind(this);
         this.setMetadata = this.setMetadata.bind(this);
         this.setMetadata = this.setMetadata.bind(this);
         this.saveTexture = this.saveTexture.bind(this);
         this.saveTexture = this.saveTexture.bind(this);
-        this.setFace = this.setFace.bind(this);
         this.resetTexture = this.resetTexture.bind(this);
         this.resetTexture = this.resetTexture.bind(this);
         this.resizeTexture = this.resizeTexture.bind(this);
         this.resizeTexture = this.resizeTexture.bind(this);
         this.uploadTexture = this.uploadTexture.bind(this);
         this.uploadTexture = this.uploadTexture.bind(this);
@@ -138,7 +166,10 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
             this._UICanvas.current!,
             this._UICanvas.current!,
             this._2DCanvas.current!,
             this._2DCanvas.current!,
             this._3DCanvas.current!,
             this._3DCanvas.current!,
-            (data : IPixelData) => {this.setState({pixelData: data})}
+            (data : IPixelData) => {this.setState({pixelData: data})},
+            this.state.metadata,
+            () => this.textureDidUpdate(),
+            data => this.setMetadata(data)
         );
         );
         this.addTools(defaultTools);
         this.addTools(defaultTools);
     }
     }
@@ -147,14 +178,24 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
         let channelsClone : IChannel[] = [];
         let channelsClone : IChannel[] = [];
         this.state.channels.forEach(channel => channelsClone.push({...channel}));
         this.state.channels.forEach(channel => channelsClone.push({...channel}));
         this._textureCanvasManager.channels = channelsClone;
         this._textureCanvasManager.channels = channelsClone;
-        this._textureCanvasManager.metadata = {...this.state.metadata};
         this._textureCanvasManager.face = this.state.face;
         this._textureCanvasManager.face = this.state.face;
+        this._textureCanvasManager.mipLevel = this.state.mipLevel;
     }
     }
 
 
     componentWillUnmount() {
     componentWillUnmount() {
         this._textureCanvasManager.dispose();
         this._textureCanvasManager.dispose();
     }
     }
 
 
+    textureDidUpdate() {
+        if (this._timer != null) {
+            clearTimeout(this._timer);
+        }
+        this._timer = window.setTimeout(() => {
+            this.props.onUpdate();
+            this._timer = null;
+        }, TextureEditorComponent.PREVIEW_UPDATE_DELAY_MS);
+    }
+
     loadToolFromURL(url : string) {
     loadToolFromURL(url : string) {
         Tools.LoadScript(url, () => {
         Tools.LoadScript(url, () => {
             this.addTools([_TOOL_DATA_]);
             this.addTools([_TOOL_DATA_]);
@@ -182,6 +223,9 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
             scene3D: this._textureCanvasManager.scene3D,
             scene3D: this._textureCanvasManager.scene3D,
             size: this._textureCanvasManager.size,
             size: this._textureCanvasManager.size,
             updateTexture: () => this._textureCanvasManager.updateTexture(),
             updateTexture: () => this._textureCanvasManager.updateTexture(),
+            startPainting: () => this._textureCanvasManager.startPainting(),
+            stopPainting: () => this._textureCanvasManager.stopPainting(),
+            updatePainting: () => this._textureCanvasManager.updatePainting(),
             metadata: this.state.metadata,
             metadata: this.state.metadata,
             setMetadata: (data : any) => this.setMetadata(data),
             setMetadata: (data : any) => this.setMetadata(data),
             getMouseCoordinates: (pointerInfo : PointerInfo) => this._textureCanvasManager.getMouseCoordinates(pointerInfo),
             getMouseCoordinates: (pointerInfo : PointerInfo) => this._textureCanvasManager.getMouseCoordinates(pointerInfo),
@@ -205,10 +249,7 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
             ...newMetadata
             ...newMetadata
         }
         }
         this.setState({metadata: data});
         this.setState({metadata: data});
-    }
-
-    setFace(face: number) {
-        this.setState({face});
+        this._textureCanvasManager.metadata = data;
     }
     }
 
 
     saveTexture() {
     saveTexture() {
@@ -236,10 +277,12 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
                 saveTexture={this.saveTexture}
                 saveTexture={this.saveTexture}
                 pixelData={this.state.pixelData}
                 pixelData={this.state.pixelData}
                 face={this.state.face}
                 face={this.state.face}
-                setFace={this.setFace}
+                setFace={face => this.setState({face})}
                 resetTexture={this.resetTexture}
                 resetTexture={this.resetTexture}
                 resizeTexture={this.resizeTexture}
                 resizeTexture={this.resizeTexture}
                 uploadTexture={this.uploadTexture}
                 uploadTexture={this.uploadTexture}
+                mipLevel={this.state.mipLevel}
+                setMipLevel={mipLevel => this.setState({mipLevel})}
             />
             />
             {!this.props.texture.isCube && <ToolBar
             {!this.props.texture.isCube && <ToolBar
                 tools={this.state.tools}
                 tools={this.state.tools}
@@ -251,7 +294,7 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
             />}
             />}
             <ChannelsBar channels={this.state.channels} setChannels={(channels) => {this.setState({channels})}}/>
             <ChannelsBar channels={this.state.channels} setChannels={(channels) => {this.setState({channels})}}/>
             <TextureCanvasComponent canvas2D={this._2DCanvas} canvas3D={this._3DCanvas} canvasUI={this._UICanvas} texture={this.props.texture}/>
             <TextureCanvasComponent canvas2D={this._2DCanvas} canvas3D={this._3DCanvas} canvasUI={this._UICanvas} texture={this.props.texture}/>
-            <BottomBar name={this.props.url}/>
+            <BottomBar name={this.props.url} mipLevel={this.state.mipLevel} hasMips={!this.props.texture.noMipmap}/>
         </div>
         </div>
     }
     }
 }
 }

+ 7 - 5
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/toolBar.tsx

@@ -1,6 +1,6 @@
 import * as React from 'react';
 import * as React from 'react';
 import { SketchPicker } from 'react-color';
 import { SketchPicker } from 'react-color';
-import { IToolData, IToolType } from './textureEditorComponent';
+import { IToolData, IToolType, IMetadata } from './textureEditorComponent';
 
 
 export interface ITool extends IToolData {
 export interface ITool extends IToolData {
     instance: IToolType;
     instance: IToolType;
@@ -11,7 +11,7 @@ interface IToolBarProps {
     addTool(url: string): void;
     addTool(url: string): void;
     changeTool(toolIndex : number): void;
     changeTool(toolIndex : number): void;
     activeToolIndex : number;
     activeToolIndex : number;
-    metadata: any;
+    metadata: IMetadata;
     setMetadata(data : any): void;
     setMetadata(data : any): void;
 }
 }
 
 
@@ -37,7 +37,7 @@ export class ToolBar extends React.Component<IToolBarProps, IToolBarState> {
     }
     }
 
 
     computeRGBAColor() {
     computeRGBAColor() {
-        const opacityInt = Math.floor(this.props.metadata.opacity * 255);
+        const opacityInt = Math.floor(this.props.metadata.alpha * 255);
         const opacityHex = opacityInt.toString(16).padStart(2, '0');
         const opacityHex = opacityInt.toString(16).padStart(2, '0');
         return `${this.props.metadata.color}${opacityHex}`;
         return `${this.props.metadata.color}${opacityHex}`;
     }
     }
@@ -79,7 +79,9 @@ export class ToolBar extends React.Component<IToolBarProps, IToolBarState> {
                 </div>
                 </div>
             </div>
             </div>
             <div id='color' onClick={() => this.setState({pickerOpen: !this.state.pickerOpen})} title='Color' className={`icon button${this.state.pickerOpen ? ` active` : ``}`}>
             <div id='color' onClick={() => this.setState({pickerOpen: !this.state.pickerOpen})} title='Color' className={`icon button${this.state.pickerOpen ? ` active` : ``}`}>
-                <div id='activeColor' style={{backgroundColor: this.props.metadata.color}}></div>
+                <div id='active-color-bg'>
+                    <div id='active-color' style={{backgroundColor: this.props.metadata.color, opacity: this.props.metadata.alpha}}></div>
+                </div>
             </div>
             </div>
             {
             {
                 this.state.pickerOpen &&
                 this.state.pickerOpen &&
@@ -92,7 +94,7 @@ export class ToolBar extends React.Component<IToolBarProps, IToolBarState> {
                     }}>
                     }}>
                     </div>
                     </div>
                     <div className='color-picker' ref={this._pickerRef}>
                     <div className='color-picker' ref={this._pickerRef}>
-                            <SketchPicker color={this.computeRGBAColor()}  onChange={color => this.props.setMetadata({color: color.hex, opacity: color.rgb.a})}/>
+                            <SketchPicker color={this.computeRGBAColor()}  onChange={color => this.props.setMetadata({color: color.hex, alpha: color.rgb.a})}/>
                     </div>
                     </div>
                 </>
                 </>
             }
             }

+ 15 - 0
inspector/src/lod.ts

@@ -0,0 +1,15 @@
+import { Effect } from "babylonjs/Materials/effect";
+
+let name = 'lodPixelShader';
+let shader = `
+varying vec2 vUV;
+uniform sampler2D textureSampler;
+uniform float lod;
+void main(void)
+{
+gl_FragColor=textureLod(textureSampler,vUV,lod);
+}`;
+
+Effect.ShadersStore[name] = shader;
+/** @hidden */
+export var lodPixelShader = { name, shader };

+ 33 - 0
inspector/src/lodCube.ts

@@ -0,0 +1,33 @@
+import { Effect } from "babylonjs/Materials/effect";
+
+let name = 'lodCubePixelShader';
+let shader = `
+varying vec2 vUV;
+uniform samplerCube textureSampler;
+uniform float lod;
+void main(void)
+{
+vec2 uv=vUV*2.0-1.0;
+#ifdef POSITIVEX
+gl_FragColor=textureCube(textureSampler,vec3(1.001,uv.y,uv.x),lod);
+#endif
+#ifdef NEGATIVEX
+gl_FragColor=textureCube(textureSampler,vec3(-1.001,uv.y,uv.x),lod);
+#endif
+#ifdef POSITIVEY
+gl_FragColor=textureCube(textureSampler,vec3(uv.y,1.001,uv.x),lod);
+#endif
+#ifdef NEGATIVEY
+gl_FragColor=textureCube(textureSampler,vec3(uv.y,-1.001,uv.x),lod);
+#endif
+#ifdef POSITIVEZ
+gl_FragColor=textureCube(textureSampler,vec3(uv,1.001),lod);
+#endif
+#ifdef NEGATIVEZ
+gl_FragColor=textureCube(textureSampler,vec3(uv,-1.001),lod);
+#endif
+}`;
+
+Effect.ShadersStore[name] = shader;
+/** @hidden */
+export var lodCubePixelShader = { name, shader };

+ 28 - 18
inspector/src/textureHelper.ts

@@ -1,12 +1,14 @@
 import { PostProcess } from 'babylonjs/PostProcesses/postProcess';
 import { PostProcess } from 'babylonjs/PostProcesses/postProcess';
 import { Texture } from 'babylonjs/Materials/Textures/texture';
 import { Texture } from 'babylonjs/Materials/Textures/texture';
-import { PassPostProcess, PassCubePostProcess } from 'babylonjs/PostProcesses/passPostProcess';
-import { Constants } from 'babylonjs/Engines/constants';
 import { GlobalState } from './components/globalState';
 import { GlobalState } from './components/globalState';
 import { RenderTargetTexture } from 'babylonjs/Materials/Textures/renderTargetTexture';
 import { RenderTargetTexture } from 'babylonjs/Materials/Textures/renderTargetTexture';
 import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
 import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
 import { Nullable } from 'babylonjs/types';
 import { Nullable } from 'babylonjs/types';
 
 
+import "./lod";
+import "./lodCube";
+
+
 export interface TextureChannelsToDisplay {
 export interface TextureChannelsToDisplay {
     R: boolean;
     R: boolean;
     G: boolean;
     G: boolean;
@@ -16,27 +18,34 @@ export interface TextureChannelsToDisplay {
 
 
 export class TextureHelper {
 export class TextureHelper {
 
 
-    private static _ProcessAsync(texture: BaseTexture, width: number, height: number, face: number, channels: TextureChannelsToDisplay, globalState: Nullable<GlobalState>, resolve: (result: Uint8Array) => void, reject: () => void) {
+    private static _ProcessAsync(texture: BaseTexture, width: number, height: number, face: number, channels: TextureChannelsToDisplay, lod: number, globalState: Nullable<GlobalState>, resolve: (result: Uint8Array) => void, reject: () => void) {
         var scene = texture.getScene()!;
         var scene = texture.getScene()!;
         var engine = scene.getEngine();
         var engine = scene.getEngine();
 
 
-        let passPostProcess: PostProcess;
+        let lodPostProcess: PostProcess;
 
 
         if (!texture.isCube) {
         if (!texture.isCube) {
-            passPostProcess = new PassPostProcess("pass", 1, null, Texture.NEAREST_SAMPLINGMODE, engine, false, Constants.TEXTURETYPE_UNSIGNED_INT);
+            lodPostProcess = new PostProcess("lod", "lod", ["lod"], null, 1.0, null, Texture.NEAREST_SAMPLINGMODE, engine);
         } else {
         } else {
-            var passCubePostProcess = new PassCubePostProcess("pass", 1, null, Texture.NEAREST_SAMPLINGMODE, engine, false, Constants.TEXTURETYPE_UNSIGNED_INT);
-            passCubePostProcess.face = face;
-
-            passPostProcess = passCubePostProcess;
+            const faceDefines = [
+                "#define POSITIVEX",
+                "#define NEGATIVEX",
+                "#define POSITIVEY",
+                "#define NEGATIVEY",
+                "#define POSITIVEZ",
+                "#define NEGATIVEZ",
+            ];
+            lodPostProcess = new PostProcess("lodCube", "lodCube", ["lod"], null, 1.0, null, Texture.NEAREST_SAMPLINGMODE, engine, false, faceDefines[face]);
         }
         }
 
 
-        if (!passPostProcess.getEffect().isReady()) {
+        
+
+        if (!lodPostProcess.getEffect().isReady()) {
             // Try again later
             // Try again later
-            passPostProcess.dispose();
+            lodPostProcess.dispose();
 
 
             setTimeout(() => {
             setTimeout(() => {
-                this._ProcessAsync(texture, width, height, face, channels, globalState, resolve, reject);
+                this._ProcessAsync(texture, width, height, face, channels, lod, globalState, resolve, reject);
             }, 250);
             }, 250);
 
 
             return;
             return;
@@ -51,14 +60,15 @@ export class TextureHelper {
             { width: width, height: height },
             { width: width, height: height },
             scene, false);
             scene, false);
 
 
-        passPostProcess.onApply = function(effect) {
+        lodPostProcess.onApply = function(effect) {
             effect.setTexture("textureSampler", texture);
             effect.setTexture("textureSampler", texture);
+            effect.setFloat("lod", lod);
         };
         };
 
 
         let internalTexture = rtt.getInternalTexture();
         let internalTexture = rtt.getInternalTexture();
 
 
         if (internalTexture) {
         if (internalTexture) {
-            scene.postProcessManager.directRender([passPostProcess], internalTexture);
+            scene.postProcessManager.directRender([lodPostProcess], internalTexture);
 
 
             // Read the contents of the framebuffer
             // Read the contents of the framebuffer
             var numberOfChannelsByLine = width * 4;
             var numberOfChannelsByLine = width * 4;
@@ -141,23 +151,23 @@ export class TextureHelper {
         }
         }
 
 
         rtt.dispose();
         rtt.dispose();
-        passPostProcess.dispose();
+        lodPostProcess.dispose();
         
         
         if (globalState) {
         if (globalState) {
             globalState.blockMutationUpdates = false;
             globalState.blockMutationUpdates = false;
         }
         }
     }
     }
 
 
-    public static GetTextureDataAsync(texture: BaseTexture, width: number, height: number, face: number, channels: TextureChannelsToDisplay, globalState?: GlobalState): Promise<Uint8Array> {
+    public static GetTextureDataAsync(texture: BaseTexture, width: number, height: number, face: number, channels: TextureChannelsToDisplay, globalState?: GlobalState, lod: number = 0): Promise<Uint8Array> {
         return new Promise((resolve, reject) => {
         return new Promise((resolve, reject) => {
             if (!texture.isReady() && texture._texture) {
             if (!texture.isReady() && texture._texture) {
                 texture._texture.onLoadedObservable.addOnce(() => {
                 texture._texture.onLoadedObservable.addOnce(() => {
-                    this._ProcessAsync(texture, width, height, face, channels, globalState || null, resolve, reject);
+                    this._ProcessAsync(texture, width, height, face, channels, lod, globalState || null, resolve, reject);
                 });
                 });
                 return;
                 return;
             }        
             }        
 
 
-            this._ProcessAsync(texture, width, height, face, channels, globalState || null, resolve, reject);
+            this._ProcessAsync(texture, width, height, face, channels, lod, globalState || null, resolve, reject);
         });
         });
     }
     }
 }
 }

+ 4 - 4
src/Animations/animation.ts

@@ -1029,10 +1029,10 @@ export class Animation {
             switch (dataType) {
             switch (dataType) {
                 case Animation.ANIMATIONTYPE_FLOAT:
                 case Animation.ANIMATIONTYPE_FLOAT:
                     key.values = [animationKey.value];
                     key.values = [animationKey.value];
-                    if (animationKey.inTangent) {
+                    if (animationKey.inTangent !== undefined) {
                         key.values.push(animationKey.inTangent);
                         key.values.push(animationKey.inTangent);
                     }
                     }
-                    if (animationKey.outTangent) {
+                    if (animationKey.outTangent !== undefined) {
                         if (animationKey.inTangent === undefined) {
                         if (animationKey.inTangent === undefined) {
                             key.values.push(undefined);
                             key.values.push(undefined);
                         }
                         }
@@ -1045,10 +1045,10 @@ export class Animation {
                 case Animation.ANIMATIONTYPE_COLOR3:
                 case Animation.ANIMATIONTYPE_COLOR3:
                 case Animation.ANIMATIONTYPE_COLOR4:
                 case Animation.ANIMATIONTYPE_COLOR4:
                     key.values = animationKey.value.asArray();
                     key.values = animationKey.value.asArray();
-                    if (animationKey.inTangent) {
+                    if (animationKey.inTangent != undefined) {
                         key.values.push(animationKey.inTangent.asArray());
                         key.values.push(animationKey.inTangent.asArray());
                     }
                     }
-                    if (animationKey.outTangent) {
+                    if (animationKey.outTangent != undefined) {
                         if (animationKey.inTangent === undefined) {
                         if (animationKey.inTangent === undefined) {
                             key.values.push(undefined);
                             key.values.push(undefined);
                         }
                         }

+ 1 - 1
src/Behaviors/Meshes/pointerDragBehavior.ts

@@ -246,8 +246,8 @@ export class PointerDragBehavior implements Behavior<AbstractMesh> {
      */
      */
     public releaseDrag() {
     public releaseDrag() {
         if (this.dragging) {
         if (this.dragging) {
-            this.onDragEndObservable.notifyObservers({ dragPlanePoint: this.lastDragPosition, pointerId: this.currentDraggingPointerID });
             this.dragging = false;
             this.dragging = false;
+            this.onDragEndObservable.notifyObservers({ dragPlanePoint: this.lastDragPosition, pointerId: this.currentDraggingPointerID });
         }
         }
 
 
         this.currentDraggingPointerID = -1;
         this.currentDraggingPointerID = -1;

+ 65 - 59
src/LibDeclarations/webxr.d.ts

@@ -1,56 +1,20 @@
-type XRSessionMode =
-    | "inline"
-    | "immersive-vr"
-    | "immersive-ar";
-
-type XRReferenceSpaceType =
-    | "viewer"
-    | "local"
-    | "local-floor"
-    | "bounded-floor"
-    | "unbounded";
-
-type XREnvironmentBlendMode =
-    | "opaque"
-    | "additive"
-    | "alpha-blend";
-
-type XRVisibilityState =
-    | "visible"
-    | "visible-blurred"
-    | "hidden";
-
-type XRHandedness =
-    | "none"
-    | "left"
-    | "right";
-
-type XRTargetRayMode =
-    | "gaze"
-    | "tracked-pointer"
-    | "screen";
-
-type XREye =
-    | "none"
-    | "left"
-    | "right";
-
-type XREventType =
-    | "devicechange"
-    | "visibilitychange"
-    | "end"
-    | "inputsourceschange"
-    | "select"
-    | "selectstart"
-    | "selectend"
-    | "squeeze"
-    | "squeezestart"
-    | "squeezeend"
-    | "reset";
-
-interface XRSpace extends EventTarget {
+type XRSessionMode = "inline" | "immersive-vr" | "immersive-ar";
 
 
-}
+type XRReferenceSpaceType = "viewer" | "local" | "local-floor" | "bounded-floor" | "unbounded";
+
+type XREnvironmentBlendMode = "opaque" | "additive" | "alpha-blend";
+
+type XRVisibilityState = "visible" | "visible-blurred" | "hidden";
+
+type XRHandedness = "none" | "left" | "right";
+
+type XRTargetRayMode = "gaze" | "tracked-pointer" | "screen";
+
+type XREye = "none" | "left" | "right";
+
+type XREventType = "devicechange" | "visibilitychange" | "end" | "inputsourceschange" | "select" | "selectstart" | "selectend" | "squeeze" | "squeezestart" | "squeezeend" | "reset";
+
+interface XRSpace extends EventTarget {}
 
 
 interface XRRenderState {
 interface XRRenderState {
     depthNear?: number;
     depthNear?: number;
@@ -66,6 +30,7 @@ interface XRInputSource {
     gripSpace: XRSpace | undefined;
     gripSpace: XRSpace | undefined;
     gamepad: Gamepad | undefined;
     gamepad: Gamepad | undefined;
     profiles: Array<string>;
     profiles: Array<string>;
+    hand: XRHand | undefined;
 }
 }
 
 
 interface XRSessionInit {
 interface XRSessionInit {
@@ -91,9 +56,7 @@ interface XRSession {
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
 
 
     // legacy plane detection
     // legacy plane detection
-    updateWorldTrackingState(options: {
-        planeDetectionState?: { enabled: boolean; }
-    }): void;
+    updateWorldTrackingState(options: { planeDetectionState?: { enabled: boolean } }): void;
 }
 }
 
 
 interface XRReferenceSpace extends XRSpace {
 interface XRReferenceSpace extends XRSpace {
@@ -110,7 +73,7 @@ interface XRFrame {
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
 
 
     // AR
     // AR
-    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult> ;
+    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     // Anchors
     // Anchors
     trackedAnchors?: XRAnchorSet;
     trackedAnchors?: XRAnchorSet;
@@ -119,6 +82,8 @@ interface XRFrame {
     worldInformation: {
     worldInformation: {
         detectedPlanes?: XRPlaneSet;
         detectedPlanes?: XRPlaneSet;
     };
     };
+    // Hand tracking
+    getJointPose(joint: XRJointSpace, baseSpace: XRSpace): XRJointPose;
 }
 }
 
 
 interface XRViewerPose extends XRPose {
 interface XRViewerPose extends XRPose {
@@ -141,7 +106,7 @@ interface XRWebGLLayerOptions {
 
 
 declare var XRWebGLLayer: {
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
     prototype: XRWebGLLayer;
-    new(session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
+    new (session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
 };
 };
 interface XRWebGLLayer {
 interface XRWebGLLayer {
     framebuffer: WebGLFramebuffer;
     framebuffer: WebGLFramebuffer;
@@ -186,7 +151,7 @@ declare class XRRay {
 declare enum XRHitTestTrackableType {
 declare enum XRHitTestTrackableType {
     "point",
     "point",
     "plane",
     "plane",
-    "mesh"
+    "mesh",
 }
 }
 
 
 interface XRHitResult {
 interface XRHitResult {
@@ -234,4 +199,45 @@ interface XRPlane {
     planeSpace: XRSpace;
     planeSpace: XRSpace;
     polygon: Array<DOMPointReadOnly>;
     polygon: Array<DOMPointReadOnly>;
     lastChangedTime: number;
     lastChangedTime: number;
-}
+}
+
+interface XRJointSpace extends XRSpace {}
+
+interface XRJointPose extends XRPose {
+    radius: number | undefined;
+}
+
+declare class XRHand extends Array<XRJointSpace> {
+    readonly length: number;
+
+    static readonly WRIST = 0;
+
+    static readonly THUMB_METACARPAL = 1;
+    static readonly THUMB_PHALANX_PROXIMAL = 2;
+    static readonly THUMB_PHALANX_DISTAL = 3;
+    static readonly THUMB_PHALANX_TIP = 4;
+
+    static readonly INDEX_METACARPAL = 5;
+    static readonly INDEX_PHALANX_PROXIMAL = 6;
+    static readonly INDEX_PHALANX_INTERMEDIATE = 7;
+    static readonly INDEX_PHALANX_DISTAL = 8;
+    static readonly INDEX_PHALANX_TIP = 9;
+
+    static readonly MIDDLE_METACARPAL = 10;
+    static readonly MIDDLE_PHALANX_PROXIMAL = 11;
+    static readonly MIDDLE_PHALANX_INTERMEDIATE = 12;
+    static readonly MIDDLE_PHALANX_DISTAL = 13;
+    static readonly MIDDLE_PHALANX_TIP = 14;
+
+    static readonly RING_METACARPAL = 15;
+    static readonly RING_PHALANX_PROXIMAL = 16;
+    static readonly RING_PHALANX_INTERMEDIATE = 17;
+    static readonly RING_PHALANX_DISTAL = 18;
+    static readonly RING_PHALANX_TIP = 19;
+
+    static readonly LITTLE_METACARPAL = 20;
+    static readonly LITTLE_PHALANX_PROXIMAL = 21;
+    static readonly LITTLE_PHALANX_INTERMEDIATE = 22;
+    static readonly LITTLE_PHALANX_DISTAL = 23;
+    static readonly LITTLE_PHALANX_TIP = 24;
+}

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

@@ -85,6 +85,10 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature {
             // already attached
             // already attached
             return;
             return;
         }
         }
+        // For now no support for hand-tracked input sources!
+        if (xrController.inputSource.hand && !xrController.inputSource.gamepad) {
+            return;
+        }
         // only support tracker pointer
         // only support tracker pointer
         const { laserPointer, selectionMesh } = this._generateNewMeshPair(xrController.pointer);
         const { laserPointer, selectionMesh } = this._generateNewMeshPair(xrController.pointer);
 
 

+ 1 - 1
src/XR/features/WebXRControllerTeleportation.ts

@@ -343,7 +343,7 @@ export class WebXRMotionControllerTeleportation extends WebXRAbstractFeature {
             }
             }
             targetMesh.rotationQuaternion = targetMesh.rotationQuaternion || new Quaternion();
             targetMesh.rotationQuaternion = targetMesh.rotationQuaternion || new Quaternion();
             const controllerData = this._controllers[this._currentTeleportationControllerId];
             const controllerData = this._controllers[this._currentTeleportationControllerId];
-            if (controllerData.teleportationState.forward) {
+            if (controllerData && controllerData.teleportationState.forward) {
                 // set the rotation
                 // set the rotation
                 Quaternion.RotationYawPitchRollToRef(controllerData.teleportationState.currentRotation + controllerData.teleportationState.baseRotation, 0, 0, targetMesh.rotationQuaternion);
                 Quaternion.RotationYawPitchRollToRef(controllerData.teleportationState.currentRotation + controllerData.teleportationState.baseRotation, 0, 0, targetMesh.rotationQuaternion);
                 // set the ray and position
                 // set the ray and position

+ 366 - 0
src/XR/features/WebXRHandTracking.ts

@@ -0,0 +1,366 @@
+import { WebXRAbstractFeature } from "./WebXRAbstractFeature";
+import { WebXRSessionManager } from "../webXRSessionManager";
+import { WebXRFeatureName } from "../webXRFeaturesManager";
+import { AbstractMesh } from "../../Meshes/abstractMesh";
+import { Mesh } from "../../Meshes/mesh";
+import { SphereBuilder } from "../../Meshes/Builders/sphereBuilder";
+import { WebXRInput } from "../webXRInput";
+import { WebXRInputSource } from "../webXRInputSource";
+import { Quaternion } from "../../Maths/math.vector";
+import { Nullable } from "../../types";
+import { PhysicsImpostor } from "../../Physics/physicsImpostor";
+import { WebXRFeaturesManager } from "../webXRFeaturesManager";
+import { IDisposable } from "../../scene";
+import { Observable } from "../../Misc/observable";
+
+/**
+ * Configuration interface for the hand tracking feature
+ */
+export interface IWebXRHandTrackingOptions {
+    /**
+     * The xrInput that will be used as source for new hands
+     */
+    xrInput: WebXRInput;
+
+    /**
+     * Configuration object for the joint meshes
+     */
+    jointMeshes?: {
+        /**
+         * Should the meshes created be invisible (defaults to false)
+         */
+        invisible?: boolean;
+        /**
+         * A source mesh to be used to create instances. Defaults to a sphere.
+         * This mesh will be the source for all other (25) meshes.
+         * It should have the general size of a single unit, as the instances will be scaled according to the provided radius
+         */
+        sourceMesh?: Mesh;
+        /**
+         * Should the source mesh stay visible. Defaults to false
+         */
+        keepOriginalVisible?: boolean;
+        /**
+         * Scale factor for all instances (defaults to 2)
+         */
+        scaleFactor?: number;
+        /**
+         * Should each instance have its own physics impostor
+         */
+        enablePhysics?: boolean;
+        /**
+         * If enabled, override default physics properties
+         */
+        physicsProps?: { friction?: number; restitution?: number; impostorType?: number };
+        /**
+         * For future use - a single hand-mesh that will be updated according to the XRHand data provided
+         */
+        handMesh?: AbstractMesh;
+    };
+}
+
+/**
+ * Parts of the hands divided to writs and finger names
+ */
+export const enum HandPart {
+    /**
+     * HandPart - Wrist
+     */
+    WRIST = "wrist",
+    /**
+     * HandPart - The THumb
+     */
+    THUMB = "thumb",
+    /**
+     * HandPart - Index finger
+     */
+    INDEX = "index",
+    /**
+     * HandPart - Middle finger
+     */
+    MIDDLE = "middle",
+    /**
+     * HandPart - Ring finger
+     */
+    RING = "ring",
+    /**
+     * HandPart - Little finger
+     */
+    LITTLE = "little",
+}
+
+/**
+ * Representing a single hand (with its corresponding native XRHand object)
+ */
+export class WebXRHand implements IDisposable {
+    /**
+     * Hand-parts definition (key is HandPart)
+     */
+    public static HandPartsDefinition: { [key: string]: number[] };
+
+    /**
+     * Populate the HandPartsDefinition object.
+     * This is called as a side effect since certain browsers don't have XRHand defined.
+     */
+    public static _PopulateHandPartsDefinition() {
+        if (typeof XRHand !== "undefined") {
+            WebXRHand.HandPartsDefinition = {
+                [HandPart.WRIST]: [XRHand.WRIST],
+                [HandPart.THUMB]: [XRHand.THUMB_METACARPAL, XRHand.THUMB_PHALANX_PROXIMAL, XRHand.THUMB_PHALANX_DISTAL, XRHand.THUMB_PHALANX_TIP],
+                [HandPart.INDEX]: [XRHand.INDEX_METACARPAL, XRHand.INDEX_PHALANX_PROXIMAL, XRHand.INDEX_PHALANX_INTERMEDIATE, XRHand.INDEX_PHALANX_DISTAL, XRHand.INDEX_PHALANX_TIP],
+                [HandPart.MIDDLE]: [XRHand.MIDDLE_METACARPAL, XRHand.MIDDLE_PHALANX_PROXIMAL, XRHand.MIDDLE_PHALANX_INTERMEDIATE, XRHand.MIDDLE_PHALANX_DISTAL, XRHand.MIDDLE_PHALANX_TIP],
+                [HandPart.RING]: [XRHand.RING_METACARPAL, XRHand.RING_PHALANX_PROXIMAL, XRHand.RING_PHALANX_INTERMEDIATE, XRHand.RING_PHALANX_DISTAL, XRHand.RING_PHALANX_TIP],
+                [HandPart.LITTLE]: [XRHand.LITTLE_METACARPAL, XRHand.LITTLE_PHALANX_PROXIMAL, XRHand.LITTLE_PHALANX_INTERMEDIATE, XRHand.LITTLE_PHALANX_DISTAL, XRHand.LITTLE_PHALANX_TIP],
+            };
+        }
+    }
+
+    /**
+     * Construct a new hand object
+     * @param xrController the controller to which the hand correlates
+     * @param trackedMeshes the meshes to be used to track the hand joints
+     */
+    constructor(
+        /** the controller to which the hand correlates */
+        public readonly xrController: WebXRInputSource,
+        /** the meshes to be used to track the hand joints */
+        public readonly trackedMeshes: AbstractMesh[]) {}
+
+    /**
+     * Update this hand from the latest xr frame
+     * @param xrFrame xrFrame to update from
+     * @param referenceSpace The current viewer reference space
+     * @param scaleFactor optional scale factor for the meshes
+     */
+    public updateFromXRFrame(xrFrame: XRFrame, referenceSpace: XRReferenceSpace, scaleFactor: number = 2) {
+        const hand = this.xrController.inputSource.hand as XRJointSpace[];
+        if (!hand) {
+            return;
+        }
+        this.trackedMeshes.forEach((mesh, idx) => {
+            const xrJoint = hand[idx];
+            if (xrJoint) {
+                let pose = xrFrame.getJointPose(xrJoint, referenceSpace);
+                if (!pose || !pose.transform) {
+                    return;
+                }
+                // get the transformation. can be done with matrix decomposition as well
+                const pos = pose.transform.position;
+                const orientation = pose.transform.orientation;
+                mesh.position.set(pos.x, pos.y, pos.z);
+                mesh.rotationQuaternion!.set(orientation.x, orientation.y, orientation.z, orientation.w);
+                // left handed system conversion
+                if (!mesh.getScene().useRightHandedSystem) {
+                    mesh.position.z *= -1;
+                    mesh.rotationQuaternion!.z *= -1;
+                    mesh.rotationQuaternion!.w *= -1;
+                }
+                // get the radius of the joint. In general it is static, but just in case it does change we update it on each frame.
+                const radius = (pose.radius || 0.008) * scaleFactor;
+                mesh.scaling.set(radius, radius, radius);
+            }
+        });
+    }
+
+    /**
+     * Get meshes of part of the hand
+     * @param part the part of hand to get
+     * @returns An array of meshes that correlate to the hand part requested
+     */
+    public getHandPartMeshes(part: HandPart): AbstractMesh[] {
+        return WebXRHand.HandPartsDefinition[part].map((idx) => this.trackedMeshes[idx]);
+    }
+
+    /**
+     * Dispose this Hand object
+     */
+    public dispose() {
+        this.trackedMeshes.forEach((mesh) => mesh.dispose());
+    }
+}
+
+// Populate the hand parts definition
+WebXRHand._PopulateHandPartsDefinition();
+
+/**
+ * WebXR Hand Joint tracking feature, available for selected browsers and devices
+ */
+export class WebXRHandTracking extends WebXRAbstractFeature {
+    private static _idCounter = 0;
+    /**
+     * The module's name
+     */
+    public static readonly Name = WebXRFeatureName.HAND_TRACKING;
+    /**
+     * The (Babylon) version of this module.
+     * This is an integer representing the implementation version.
+     * This number does not correspond to the WebXR specs version
+     */
+    public static readonly Version = 1;
+
+    /**
+     * This observable will notify registered observers when a new hand object was added and initialized
+     */
+    public onHandAddedObservable: Observable<WebXRHand> = new Observable();
+    /**
+     * This observable will notify its observers right before the hand object is disposed
+     */
+    public onHandRemovedObservable: Observable<WebXRHand> = new Observable();
+
+    private _hands: {
+        [controllerId: string]: {
+            id: number;
+            handObject: WebXRHand;
+        };
+    } = {};
+
+    /**
+     * Creates a new instance of the hit test feature
+     * @param _xrSessionManager an instance of WebXRSessionManager
+     * @param options options to use when constructing this feature
+     */
+    constructor(
+        _xrSessionManager: WebXRSessionManager,
+        /**
+         * options to use when constructing this feature
+         */
+        public readonly options: IWebXRHandTrackingOptions
+    ) {
+        super(_xrSessionManager);
+        this.xrNativeFeatureName = "hand-tracking";
+    }
+
+    /**
+     * Check if the needed objects are defined.
+     * This does not mean that the feature is enabled, but that the objects needed are well defined.
+     */
+    public isCompatible(): boolean {
+        return typeof XRHand !== "undefined";
+    }
+
+    /**
+     * attach this feature
+     * Will usually be called by the features manager
+     *
+     * @returns true if successful.
+     */
+    public attach(): boolean {
+        if (!super.attach()) {
+            return false;
+        }
+        this.options.xrInput.controllers.forEach(this._attachHand);
+        this._addNewAttachObserver(this.options.xrInput.onControllerAddedObservable, this._attachHand);
+        this._addNewAttachObserver(this.options.xrInput.onControllerRemovedObservable, (controller) => {
+            // REMOVE the controller
+            this._detachHand(controller.uniqueId);
+        });
+
+        return true;
+    }
+
+    /**
+     * detach this feature.
+     * Will usually be called by the features manager
+     *
+     * @returns true if successful.
+     */
+    public detach(): boolean {
+        if (!super.detach()) {
+            return false;
+        }
+
+        Object.keys(this._hands).forEach((controllerId) => {
+            this._detachHand(controllerId);
+        });
+
+        return true;
+    }
+
+    /**
+     * Dispose this feature and all of the resources attached
+     */
+    public dispose(): void {
+        super.dispose();
+        this.onHandAddedObservable.clear();
+    }
+
+    /**
+     * Get the hand object according to the controller id
+     * @param controllerId the controller id to which we want to get the hand
+     * @returns null if not found or the WebXRHand object if found
+     */
+    public getHandByControllerId(controllerId: string): Nullable<WebXRHand> {
+        return this._hands[controllerId]?.handObject || null;
+    }
+
+    /**
+     * Get a hand object according to the requested handedness
+     * @param handedness the handedness to request
+     * @returns null if not found or the WebXRHand object if found
+     */
+    public getHandByHandedness(handedness: XRHandedness): Nullable<WebXRHand> {
+        const handednesses = Object.keys(this._hands).map((key) => this._hands[key].handObject.xrController.inputSource.handedness);
+        const found = handednesses.indexOf(handedness);
+        if (found !== -1) {
+            return this._hands[found].handObject;
+        }
+        return null;
+    }
+
+    protected _onXRFrame(_xrFrame: XRFrame): void {
+        // iterate over the hands object
+        Object.keys(this._hands).forEach((id) => {
+            this._hands[id].handObject.updateFromXRFrame(_xrFrame, this._xrSessionManager.referenceSpace, this.options.jointMeshes?.scaleFactor);
+        });
+    }
+
+    private _attachHand = (xrController: WebXRInputSource) => {
+        if (!xrController.inputSource.hand || this._hands[xrController.uniqueId]) {
+            // already attached
+            return;
+        }
+
+        const hand = xrController.inputSource.hand;
+        const trackedMeshes: AbstractMesh[] = [];
+        const originalMesh = this.options.jointMeshes?.sourceMesh || SphereBuilder.CreateSphere("jointParent", { diameter: 1 });
+        originalMesh.isVisible = !!this.options.jointMeshes?.keepOriginalVisible;
+        for (let i = 0; i < hand.length; ++i) {
+            const newInstance = originalMesh.createInstance(`${xrController.uniqueId}-handJoint-${i}`);
+            newInstance.isPickable = false;
+            if (this.options.jointMeshes?.enablePhysics) {
+                const props = this.options.jointMeshes.physicsProps || {};
+                const type = props.impostorType !== undefined ? props.impostorType : PhysicsImpostor.SphereImpostor;
+                newInstance.physicsImpostor = new PhysicsImpostor(newInstance, type, { mass: 0, ...props });
+            }
+            newInstance.rotationQuaternion = new Quaternion();
+            trackedMeshes.push(newInstance);
+        }
+
+        const webxrHand = new WebXRHand(xrController, trackedMeshes);
+
+        // get two new meshes
+        this._hands[xrController.uniqueId] = {
+            handObject: webxrHand,
+            id: WebXRHandTracking._idCounter++,
+        };
+
+        this.onHandAddedObservable.notifyObservers(webxrHand);
+    };
+
+    private _detachHand(controllerId: string) {
+        if (this._hands[controllerId]) {
+            this.onHandRemovedObservable.notifyObservers(this._hands[controllerId].handObject);
+            this._hands[controllerId].handObject.dispose();
+        }
+    }
+}
+
+//register the plugin
+WebXRFeaturesManager.AddWebXRFeature(
+    WebXRHandTracking.Name,
+    (xrSessionManager, options) => {
+        return () => new WebXRHandTracking(xrSessionManager, options);
+    },
+    WebXRHandTracking.Version,
+    false
+);

+ 1 - 0
src/XR/features/index.ts

@@ -7,3 +7,4 @@ export * from "./WebXRControllerPointerSelection";
 export * from "./WebXRControllerPhysics";
 export * from "./WebXRControllerPhysics";
 export * from "./WebXRHitTest";
 export * from "./WebXRHitTest";
 export * from "./WebXRFeaturePointSystem";
 export * from "./WebXRFeaturePointSystem";
+export * from "./WebXRHandTracking";

+ 6 - 6
src/XR/motionController/webXRAbstractMotionController.ts

@@ -186,7 +186,7 @@ export interface IMotionControllerMeshMap {
     /**
     /**
      * The mesh that will be changed when axis value changes
      * The mesh that will be changed when axis value changes
      */
      */
-    valueMesh: AbstractMesh;
+    valueMesh?: AbstractMesh;
 }
 }
 
 
 /**
 /**
@@ -431,13 +431,13 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
     }
     }
 
 
     // Look through all children recursively. This will return null if no mesh exists with the given name.
     // Look through all children recursively. This will return null if no mesh exists with the given name.
-    protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh {
-        return <AbstractMesh>node.getChildren((n) => n.name === name, false)[0];
+    protected _getChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined {
+        return <AbstractMesh | undefined>node.getChildren((n) => n.name === name, false)[0];
     }
     }
 
 
     // Look through only immediate children. This will return null if no mesh exists with the given name.
     // Look through only immediate children. This will return null if no mesh exists with the given name.
-    protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh {
-        return <AbstractMesh>node.getChildren((n) => n.name == name, true)[0];
+    protected _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh | undefined {
+        return <AbstractMesh | undefined>node.getChildren((n) => n.name == name, true)[0];
     }
     }
 
 
     /**
     /**
@@ -447,7 +447,7 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
      * @hidden
      * @hidden
      */
      */
     protected _lerpTransform(axisMap: IMotionControllerMeshMap, axisValue: number, fixValueCoordinates?: boolean): void {
     protected _lerpTransform(axisMap: IMotionControllerMeshMap, axisValue: number, fixValueCoordinates?: boolean): void {
-        if (!axisMap.minMesh || !axisMap.maxMesh) {
+        if (!axisMap.minMesh || !axisMap.maxMesh || !axisMap.valueMesh) {
             return;
             return;
         }
         }
 
 

+ 6 - 3
src/XR/motionController/webXRProfiledMotionController.ts

@@ -17,7 +17,7 @@ import { Logger } from "../../Misc/logger";
 export class WebXRProfiledMotionController extends WebXRAbstractMotionController {
 export class WebXRProfiledMotionController extends WebXRAbstractMotionController {
     private _buttonMeshMapping: {
     private _buttonMeshMapping: {
         [buttonName: string]: {
         [buttonName: string]: {
-            mainMesh: AbstractMesh;
+            mainMesh?: AbstractMesh;
             states: {
             states: {
                 [state: string]: IMotionControllerMeshMap;
                 [state: string]: IMotionControllerMeshMap;
             };
             };
@@ -89,7 +89,7 @@ export class WebXRProfiledMotionController extends WebXRAbstractMotionController
                         );
                         );
                         dot.material = new StandardMaterial(visualResponseKey + "mat", this.scene);
                         dot.material = new StandardMaterial(visualResponseKey + "mat", this.scene);
                         (<StandardMaterial>dot.material).diffuseColor = Color3.Red();
                         (<StandardMaterial>dot.material).diffuseColor = Color3.Red();
-                        dot.parent = this._buttonMeshMapping[type].states[visualResponseKey].valueMesh;
+                        dot.parent = this._buttonMeshMapping[type].states[visualResponseKey].valueMesh || null;
                         dot.isVisible = false;
                         dot.isVisible = false;
                         this._touchDots[visualResponseKey] = dot;
                         this._touchDots[visualResponseKey] = dot;
                     }
                     }
@@ -145,7 +145,10 @@ export class WebXRProfiledMotionController extends WebXRAbstractMotionController
                     this._lerpTransform(meshes.states[visualResponseKey], value, visResponse.componentProperty !== "button");
                     this._lerpTransform(meshes.states[visualResponseKey], value, visResponse.componentProperty !== "button");
                 } else {
                 } else {
                     // visibility
                     // visibility
-                    meshes.states[visualResponseKey].valueMesh.isVisible = component.touched || component.pressed;
+                    const valueMesh = meshes.states[visualResponseKey].valueMesh;
+                    if (valueMesh) {
+                        valueMesh.isVisible = component.touched || component.pressed;
+                    }
                     if (this._touchDots[visualResponseKey]) {
                     if (this._touchDots[visualResponseKey]) {
                         this._touchDots[visualResponseKey].isVisible = component.touched || component.pressed;
                         this._touchDots[visualResponseKey].isVisible = component.touched || component.pressed;
                     }
                     }

+ 11 - 7
src/XR/webXREnterExitUI.ts

@@ -69,7 +69,10 @@ export class WebXREnterExitUIOptions {
 export class WebXREnterExitUI implements IDisposable {
 export class WebXREnterExitUI implements IDisposable {
     private _activeButton: Nullable<WebXREnterExitUIButton> = null;
     private _activeButton: Nullable<WebXREnterExitUIButton> = null;
     private _buttons: Array<WebXREnterExitUIButton> = [];
     private _buttons: Array<WebXREnterExitUIButton> = [];
-    private _overlay: HTMLDivElement;
+    /**
+     * The HTML Div Element to which buttons are added.
+     */
+    public readonly overlay: HTMLDivElement;
 
 
     /**
     /**
      * Fired every time the active button is changed.
      * Fired every time the active button is changed.
@@ -90,8 +93,9 @@ export class WebXREnterExitUI implements IDisposable {
         /** version of the options passed to this UI */
         /** version of the options passed to this UI */
         public options: WebXREnterExitUIOptions
         public options: WebXREnterExitUIOptions
     ) {
     ) {
-        this._overlay = document.createElement("div");
-        this._overlay.style.cssText = "z-index:11;position: absolute; right: 20px;bottom: 50px;";
+        this.overlay = document.createElement("div");
+        this.overlay.classList.add('xr-button-overlay');
+        this.overlay.style.cssText = "z-index:11;position: absolute; right: 20px;bottom: 50px;";
 
 
         // if served over HTTP, warn people.
         // if served over HTTP, warn people.
         // Hopefully the browsers will catch up
         // Hopefully the browsers will catch up
@@ -131,7 +135,7 @@ export class WebXREnterExitUI implements IDisposable {
 
 
         var renderCanvas = scene.getEngine().getInputElement();
         var renderCanvas = scene.getEngine().getInputElement();
         if (renderCanvas && renderCanvas.parentNode) {
         if (renderCanvas && renderCanvas.parentNode) {
-            renderCanvas.parentNode.appendChild(this._overlay);
+            renderCanvas.parentNode.appendChild(this.overlay);
             scene.onDisposeObservable.addOnce(() => {
             scene.onDisposeObservable.addOnce(() => {
                 this.dispose();
                 this.dispose();
             });
             });
@@ -158,7 +162,7 @@ export class WebXREnterExitUI implements IDisposable {
         return Promise.all(supportedPromises).then((results) => {
         return Promise.all(supportedPromises).then((results) => {
             results.forEach((supported, i) => {
             results.forEach((supported, i) => {
                 if (supported) {
                 if (supported) {
-                    ui._overlay.appendChild(ui._buttons[i].element);
+                    ui.overlay.appendChild(ui._buttons[i].element);
                     ui._buttons[i].element.onclick = async () => {
                     ui._buttons[i].element.onclick = async () => {
                         if (helper.state == WebXRState.IN_XR) {
                         if (helper.state == WebXRState.IN_XR) {
                             await helper.exitXRAsync();
                             await helper.exitXRAsync();
@@ -192,8 +196,8 @@ export class WebXREnterExitUI implements IDisposable {
      */
      */
     public dispose() {
     public dispose() {
         var renderCanvas = this.scene.getEngine().getInputElement();
         var renderCanvas = this.scene.getEngine().getInputElement();
-        if (renderCanvas && renderCanvas.parentNode && renderCanvas.parentNode.contains(this._overlay)) {
-            renderCanvas.parentNode.removeChild(this._overlay);
+        if (renderCanvas && renderCanvas.parentNode && renderCanvas.parentNode.contains(this.overlay)) {
+            renderCanvas.parentNode.removeChild(this.overlay);
         }
         }
         this.activeButtonChangedObservable.clear();
         this.activeButtonChangedObservable.clear();
     }
     }

+ 15 - 0
src/XR/webXRFeaturesManager.ts

@@ -43,6 +43,11 @@ export interface IWebXRFeature extends IDisposable {
      * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
      * The name of the native xr feature name, if applicable (like anchor, hit-test, or hand-tracking)
      */
      */
     xrNativeFeatureName?: string;
     xrNativeFeatureName?: string;
+
+    /**
+     * A list of (Babylon WebXR) features this feature depends on
+     */
+    dependsOn?: string[];
 }
 }
 
 
 /**
 /**
@@ -81,6 +86,10 @@ export class WebXRFeatureName {
      * The name of the feature points feature.
      * The name of the feature points feature.
      */
      */
     public static readonly FEATURE_POINTS = "xr-feature-points";
     public static readonly FEATURE_POINTS = "xr-feature-points";
+    /**
+     * The name of the hand tracking feature.
+     */
+    public static readonly HAND_TRACKING = "xr-hand-tracking";
 }
 }
 
 
 /**
 /**
@@ -312,6 +321,12 @@ export class WebXRFeaturesManager implements IDisposable {
         }
         }
 
 
         const constructed = constructFunction();
         const constructed = constructFunction();
+        if (constructed.dependsOn) {
+            const dependentsFound = constructed.dependsOn.every((featureName) => !!this._features[featureName]);
+            if (!dependentsFound) {
+                throw new Error(`Dependant features missing. Make sure the following features are enabled - ${constructed.dependsOn.join(", ")}`);
+            }
+        }
         if (constructed.isCompatible()) {
         if (constructed.isCompatible()) {
             this._features[name] = {
             this._features[name] = {
                 featureImplementation: constructed,
                 featureImplementation: constructed,

+ 6 - 0
src/XR/webXRInputSource.ts

@@ -40,6 +40,7 @@ export interface IWebXRControllerOptions {
 export class WebXRInputSource {
 export class WebXRInputSource {
     private _tmpVector = new Vector3();
     private _tmpVector = new Vector3();
     private _uniqueId: string;
     private _uniqueId: string;
+    private _disposed = false;
 
 
     /**
     /**
      * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
      * Represents the part of the controller that is held. This may not exist if the controller is the head mounted display itself, if thats the case only the pointer from the head will be availible
@@ -116,6 +117,10 @@ export class WebXRInputSource {
                                 this.motionController.rootMesh.parent = this.grip || this.pointer;
                                 this.motionController.rootMesh.parent = this.grip || this.pointer;
                                 this.motionController.disableAnimation = !!this._options.disableMotionControllerAnimation;
                                 this.motionController.disableAnimation = !!this._options.disableMotionControllerAnimation;
                             }
                             }
+                            // make sure to dispose is the controller is already disposed
+                            if (this._disposed) {
+                                this.motionController?.dispose();
+                            }
                         });
                         });
                     }
                     }
                 },
                 },
@@ -148,6 +153,7 @@ export class WebXRInputSource {
         this.onMeshLoadedObservable.clear();
         this.onMeshLoadedObservable.clear();
         this.onDisposeObservable.notifyObservers(this);
         this.onDisposeObservable.notifyObservers(this);
         this.onDisposeObservable.clear();
         this.onDisposeObservable.clear();
+        this._disposed = true;
     }
     }
 
 
     /**
     /**

+ 3 - 2
src/XR/webXRManagedOutputCanvas.ts

@@ -24,14 +24,15 @@ export class WebXRManagedOutputCanvasOptions {
 
 
     /**
     /**
      * Get the default values of the configuration object
      * Get the default values of the configuration object
+     * @param engine defines the engine to use (can be null)
      * @returns default values of this configuration object
      * @returns default values of this configuration object
      */
      */
-    public static GetDefaults(): WebXRManagedOutputCanvasOptions {
+    public static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions {
         const defaults = new WebXRManagedOutputCanvasOptions();
         const defaults = new WebXRManagedOutputCanvasOptions();
         defaults.canvasOptions = {
         defaults.canvasOptions = {
             antialias: true,
             antialias: true,
             depth: true,
             depth: true,
-            stencil: false,
+            stencil: engine ? engine.isStencilEnable : true,
             alpha: true,
             alpha: true,
             multiview: false,
             multiview: false,
             framebufferScaleFactor: 1,
             framebufferScaleFactor: 1,

+ 1 - 1
src/XR/webXRSessionManager.ts

@@ -137,7 +137,7 @@ export class WebXRSessionManager implements IDisposable {
         if (this._xrNavigator.xr.native) {
         if (this._xrNavigator.xr.native) {
             return this._xrNavigator.xr.getWebXRRenderTarget(engine);
             return this._xrNavigator.xr.getWebXRRenderTarget(engine);
         } else {
         } else {
-            options = options || {};
+            options = options || WebXRManagedOutputCanvasOptions.GetDefaults(engine);
             options.canvasElement = engine.getRenderingCanvas() || undefined;
             options.canvasElement = engine.getRenderingCanvas() || undefined;
             return new WebXRManagedOutputCanvas(this, options);
             return new WebXRManagedOutputCanvas(this, options);
         }
         }