소스 검색

Merge branch 'master' into resize-editor

Alejandro Toledo 5 년 전
부모
커밋
83b3bb6b50
57개의 변경된 파일4045개의 추가작업 그리고 926개의 파일을 삭제
  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 { GlobalState } from '../globalState';
+import { GlobalState } from "../globalState";
 
 require("../scss/examples.scss");
 
@@ -7,7 +7,7 @@ interface IExamplesComponentProps {
     globalState: GlobalState;
 }
 
-export class ExamplesComponent extends React.Component<IExamplesComponentProps, {filter: string}> {  
+export class ExamplesComponent extends React.Component<IExamplesComponentProps, { filter: string }> {
     private _state = "removed";
     private _rootRef: React.RefObject<HTMLDivElement>;
     private _scripts: {
@@ -19,13 +19,13 @@ export class ExamplesComponent extends React.Component<IExamplesComponentProps,
             PGID: string;
             description: string;
         }[];
-    }[];  
-  
+    }[];
+
     public constructor(props: IExamplesComponentProps) {
         super(props);
         this._loadScripts();
 
-        this.state = {filter: ""};
+        this.state = { filter: "" };
         this._rootRef = React.createRef();
 
         this.props.globalState.onExamplesDisplayChangedObservable.add(() => {
@@ -40,18 +40,18 @@ export class ExamplesComponent extends React.Component<IExamplesComponentProps,
                 this._state = "";
                 setTimeout(() => {
                     this._rootRef.current!.classList.add("removed");
-                }, 200)
+                }, 200);
             }
         });
-    }  
+    }
 
     private _loadScripts() {
         var xhr = new XMLHttpRequest();
 
         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 {
-            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 = () => {
@@ -66,7 +66,7 @@ export class ExamplesComponent extends React.Component<IExamplesComponentProps,
                         return 1;
                     });
 
-                    this._scripts.forEach(s => {
+                    this._scripts.forEach((s) => {
                         s.samples.sort((a, b) => {
                             if (a.title < b.title) {
                                 return -1;
@@ -78,12 +78,11 @@ export class ExamplesComponent extends React.Component<IExamplesComponentProps,
                     this.forceUpdate();
                 }
             }
-        }
+        };
 
         xhr.send(null);
     }
 
-
     private _onLoadPG(id: string) {
         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-header">Examples</div>
                 <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 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>
-        )
+        );
     }
-}
+}

+ 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 {
     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();
         window.addEventListener("hashchange", () => this._checkHash());
 
-        globalState.onLoadRequiredObservable.add(id => {
+        globalState.onLoadRequiredObservable.add((id) => {
             globalState.onDisplayWaitRingObservable.notifyObservers(true);
             this._loadPlayground(id);
         });
     }
 
     private _cleanHash() {
-        var substr = location.hash[1]==='#' ? 2 : 1
+        var substr = location.hash[1] === "#" ? 2 : 1;
         var splits = decodeURIComponent(location.hash.substr(substr)).split("#");
 
         if (splits.length > 2) {
@@ -24,14 +24,14 @@ export class LoadManager {
         }
 
         location.hash = splits.join("#");
-    };
+    }
 
     private _checkHash() {
         let pgHash = "";
-        if (location.search && (!location.pathname  || location.pathname === '/') && !location.hash) {
+        if (location.search && (!location.pathname || location.pathname === "/") && !location.hash) {
             var query = Utilities.ParseQuery();
             if (query.pg) {
-                pgHash = "#" + query.pg + "#" + (query.revision || "0")
+                pgHash = "#" + query.pg + "#" + (query.revision || "0");
             }
         } else if (location.hash) {
             if (this._previousHash !== location.hash) {
@@ -52,27 +52,27 @@ export class LoadManager {
         if (pgHash) {
             var match = pgHash.match(/^(#[A-Za-z\d]*)(%23)([\d]+)$/);
             if (match) {
-                pgHash = match[1] + '#' + match[3];
+                pgHash = match[1] + "#" + match[3];
                 parent.location.hash = pgHash;
             }
             this._previousHash = pgHash;
             this._loadPlayground(pgHash.substr(1));
-        }        
+        }
     }
 
-    private _loadPlayground(id: string) {        
+    private _loadPlayground(id: string) {
         this.globalState.loadingCodeInProgress = true;
         try {
             var xmlHttp = new XMLHttpRequest();
             xmlHttp.onreadystatechange = () => {
                 if (xmlHttp.readyState === 4) {
                     if (xmlHttp.status === 200) {
-
                         if (xmlHttp.responseText.indexOf("class Playground") !== -1) {
                             if (this.globalState.language === "JS") {
                                 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") {
                                 Utilities.SwitchLanguage("JS", this.globalState);
                             }
@@ -100,20 +100,26 @@ export class LoadManager {
                         }
 
                         this.globalState.onCodeLoaded.notifyObservers(JSON.parse(snippet.jsonPayload).code.toString());
-                         
+
                         this.globalState.onMetadataUpdatedObservable.notifyObservers();
                     }
                 }
+            };
+
+            if (id[0] === "#") {
+                id = id.substr(1);
             }
 
             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();
         } catch (e) {
             this.globalState.loadingCodeInProgress = false;
             this.globalState.onCodeLoaded.notifyObservers("");
         }
     }
-}
+}

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 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;
         /**
          * 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
          */
-        static GetDefaults(): WebXRManagedOutputCanvasOptions;
+        static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions;
     }
     /**
      * 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)
          */
         xrNativeFeatureName?: string;
+        /**
+         * A list of (Babylon WebXR) features this feature depends on
+         */
+        dependsOn?: string[];
     }
     /**
      * A list of the currently available features without referencing them
@@ -47119,6 +47124,10 @@ declare module BABYLON {
          * The name of the feature points feature.
          */
         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.
@@ -48065,7 +48074,7 @@ declare module BABYLON {
         /**
          * 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
@@ -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
          */
         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
          * @param axis the index of the axis
@@ -48467,6 +48476,7 @@ declare module BABYLON {
         private _options;
         private _tmpVector;
         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
          */
@@ -48992,7 +49002,10 @@ declare module BABYLON {
         options: WebXREnterExitUIOptions;
         private _activeButton;
         private _buttons;
-        private _overlay;
+        /**
+         * The HTML Div Element to which buttons are added.
+         */
+        readonly overlay: HTMLDivElement;
         /**
          * Fired every time the active button is changed.
          *
@@ -74861,6 +74874,209 @@ 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
      */
     export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
@@ -75589,59 +75805,23 @@ interface Window {
 interface Gamepad {
     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 {
     depthNear?: number;
@@ -75657,6 +75837,7 @@ interface XRInputSource {
     gripSpace: XRSpace | undefined;
     gamepad: Gamepad | undefined;
     profiles: Array<string>;
+    hand: XRHand | undefined;
 }
 
 interface XRSessionInit {
@@ -75682,9 +75863,7 @@ interface XRSession {
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
 
     // legacy plane detection
-    updateWorldTrackingState(options: {
-        planeDetectionState?: { enabled: boolean; }
-    }): void;
+    updateWorldTrackingState(options: { planeDetectionState?: { enabled: boolean } }): void;
 }
 
 interface XRReferenceSpace extends XRSpace {
@@ -75701,7 +75880,7 @@ interface XRFrame {
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
 
     // AR
-    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult> ;
+    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     // Anchors
     trackedAnchors?: XRAnchorSet;
@@ -75710,6 +75889,8 @@ interface XRFrame {
     worldInformation: {
         detectedPlanes?: XRPlaneSet;
     };
+    // Hand tracking
+    getJointPose(joint: XRJointSpace, baseSpace: XRSpace): XRJointPose;
 }
 
 interface XRViewerPose extends XRPose {
@@ -75732,7 +75913,7 @@ interface XRWebGLLayerOptions {
 
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
-    new(session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
+    new (session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
 };
 interface XRWebGLLayer {
     framebuffer: WebGLFramebuffer;
@@ -75777,7 +75958,7 @@ declare class XRRay {
 declare enum XRHitTestTrackableType {
     "point",
     "plane",
-    "mesh"
+    "mesh",
 }
 
 interface XRHitResult {
@@ -75825,7 +76006,49 @@ interface XRPlane {
     planeSpace: XRSpace;
     polygon: Array<DOMPointReadOnly>;
     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.
 // They are intended for use with either Babylon Native https://github.com/BabylonJS/BabylonNative or
 // Babylon React Native: https://github.com/BabylonJS/BabylonReactNative

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 2 - 2
dist/preview release/babylon.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 354 - 27
dist/preview release/babylon.max.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 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" {
     import { Nullable } from "babylonjs/types";
+    import { ThinEngine } from "babylonjs/Engines/thinEngine";
     import { WebXRRenderTarget } from "babylonjs/XR/webXRTypes";
     import { WebXRSessionManager } from "babylonjs/XR/webXRSessionManager";
     import { Observable } from "babylonjs/Misc/observable";
@@ -48564,9 +48565,10 @@ declare module "babylonjs/XR/webXRManagedOutputCanvas" {
         newCanvasCssStyle?: string;
         /**
          * 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
          */
-        static GetDefaults(): WebXRManagedOutputCanvasOptions;
+        static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions;
     }
     /**
      * 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)
          */
         xrNativeFeatureName?: string;
+        /**
+         * A list of (Babylon WebXR) features this feature depends on
+         */
+        dependsOn?: string[];
     }
     /**
      * 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.
          */
         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.
@@ -49868,7 +49878,7 @@ declare module "babylonjs/XR/motionController/webXRAbstractMotionController" {
         /**
          * 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
@@ -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
          */
         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
          * @param axis the index of the axis
@@ -50287,6 +50297,7 @@ declare module "babylonjs/XR/webXRInputSource" {
         private _options;
         private _tmpVector;
         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
          */
@@ -50840,7 +50851,10 @@ declare module "babylonjs/XR/webXREnterExitUI" {
         options: WebXREnterExitUIOptions;
         private _activeButton;
         private _buttons;
-        private _overlay;
+        /**
+         * The HTML Div Element to which buttons are added.
+         */
+        readonly overlay: HTMLDivElement;
         /**
          * Fired every time the active button is changed.
          *
@@ -79029,6 +79043,218 @@ declare module "babylonjs/XR/features/WebXRFeaturePointSystem" {
         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" {
     export * from "babylonjs/XR/features/WebXRHitTestLegacy";
     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/WebXRHitTest";
     export * from "babylonjs/XR/features/WebXRFeaturePointSystem";
+    export * from "babylonjs/XR/features/WebXRHandTracking";
 }
 declare module "babylonjs/XR/motionController/webXRMicrosoftMixedRealityController" {
     import { WebXRAbstractMotionController, IMinimalMotionControllerObject, MotionControllerHandedness } from "babylonjs/XR/motionController/webXRAbstractMotionController";
@@ -126183,9 +126410,10 @@ declare module BABYLON {
         newCanvasCssStyle?: string;
         /**
          * 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
          */
-        static GetDefaults(): WebXRManagedOutputCanvasOptions;
+        static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions;
     }
     /**
      * 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)
          */
         xrNativeFeatureName?: string;
+        /**
+         * A list of (Babylon WebXR) features this feature depends on
+         */
+        dependsOn?: string[];
     }
     /**
      * A list of the currently available features without referencing them
@@ -126502,6 +126734,10 @@ declare module BABYLON {
          * The name of the feature points feature.
          */
         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.
@@ -127448,7 +127684,7 @@ declare module BABYLON {
         /**
          * 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
@@ -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
          */
         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
          * @param axis the index of the axis
@@ -127850,6 +128086,7 @@ declare module BABYLON {
         private _options;
         private _tmpVector;
         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
          */
@@ -128375,7 +128612,10 @@ declare module BABYLON {
         options: WebXREnterExitUIOptions;
         private _activeButton;
         private _buttons;
-        private _overlay;
+        /**
+         * The HTML Div Element to which buttons are added.
+         */
+        readonly overlay: HTMLDivElement;
         /**
          * Fired every time the active button is changed.
          *
@@ -154244,6 +154484,209 @@ 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
      */
     export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
@@ -154972,59 +155415,23 @@ interface Window {
 interface Gamepad {
     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 {
     depthNear?: number;
@@ -155040,6 +155447,7 @@ interface XRInputSource {
     gripSpace: XRSpace | undefined;
     gamepad: Gamepad | undefined;
     profiles: Array<string>;
+    hand: XRHand | undefined;
 }
 
 interface XRSessionInit {
@@ -155065,9 +155473,7 @@ interface XRSession {
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
 
     // legacy plane detection
-    updateWorldTrackingState(options: {
-        planeDetectionState?: { enabled: boolean; }
-    }): void;
+    updateWorldTrackingState(options: { planeDetectionState?: { enabled: boolean } }): void;
 }
 
 interface XRReferenceSpace extends XRSpace {
@@ -155084,7 +155490,7 @@ interface XRFrame {
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
 
     // AR
-    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult> ;
+    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     // Anchors
     trackedAnchors?: XRAnchorSet;
@@ -155093,6 +155499,8 @@ interface XRFrame {
     worldInformation: {
         detectedPlanes?: XRPlaneSet;
     };
+    // Hand tracking
+    getJointPose(joint: XRJointSpace, baseSpace: XRSpace): XRJointPose;
 }
 
 interface XRViewerPose extends XRPose {
@@ -155115,7 +155523,7 @@ interface XRWebGLLayerOptions {
 
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
-    new(session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
+    new (session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
 };
 interface XRWebGLLayer {
     framebuffer: WebGLFramebuffer;
@@ -155160,7 +155568,7 @@ declare class XRRay {
 declare enum XRHitTestTrackableType {
     "point",
     "plane",
-    "mesh"
+    "mesh",
 }
 
 interface XRHitResult {
@@ -155208,7 +155616,49 @@ interface XRPlane {
     planeSpace: XRSpace;
     polygon: Array<DOMPointReadOnly>;
     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.
 // They are intended for use with either Babylon Native https://github.com/BabylonJS/BabylonNative or
 // 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;
         /**
          * 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
          */
-        static GetDefaults(): WebXRManagedOutputCanvasOptions;
+        static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions;
     }
     /**
      * 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)
          */
         xrNativeFeatureName?: string;
+        /**
+         * A list of (Babylon WebXR) features this feature depends on
+         */
+        dependsOn?: string[];
     }
     /**
      * A list of the currently available features without referencing them
@@ -47119,6 +47124,10 @@ declare module BABYLON {
          * The name of the feature points feature.
          */
         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.
@@ -48065,7 +48074,7 @@ declare module BABYLON {
         /**
          * 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
@@ -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
          */
         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
          * @param axis the index of the axis
@@ -48467,6 +48476,7 @@ declare module BABYLON {
         private _options;
         private _tmpVector;
         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
          */
@@ -48992,7 +49002,10 @@ declare module BABYLON {
         options: WebXREnterExitUIOptions;
         private _activeButton;
         private _buttons;
-        private _overlay;
+        /**
+         * The HTML Div Element to which buttons are added.
+         */
+        readonly overlay: HTMLDivElement;
         /**
          * Fired every time the active button is changed.
          *
@@ -74861,6 +74874,209 @@ 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
      */
     export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
@@ -75589,59 +75805,23 @@ interface Window {
 interface Gamepad {
     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 {
     depthNear?: number;
@@ -75657,6 +75837,7 @@ interface XRInputSource {
     gripSpace: XRSpace | undefined;
     gamepad: Gamepad | undefined;
     profiles: Array<string>;
+    hand: XRHand | undefined;
 }
 
 interface XRSessionInit {
@@ -75682,9 +75863,7 @@ interface XRSession {
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
 
     // legacy plane detection
-    updateWorldTrackingState(options: {
-        planeDetectionState?: { enabled: boolean; }
-    }): void;
+    updateWorldTrackingState(options: { planeDetectionState?: { enabled: boolean } }): void;
 }
 
 interface XRReferenceSpace extends XRSpace {
@@ -75701,7 +75880,7 @@ interface XRFrame {
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
 
     // AR
-    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult> ;
+    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     // Anchors
     trackedAnchors?: XRAnchorSet;
@@ -75710,6 +75889,8 @@ interface XRFrame {
     worldInformation: {
         detectedPlanes?: XRPlaneSet;
     };
+    // Hand tracking
+    getJointPose(joint: XRJointSpace, baseSpace: XRSpace): XRJointPose;
 }
 
 interface XRViewerPose extends XRPose {
@@ -75732,7 +75913,7 @@ interface XRWebGLLayerOptions {
 
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
-    new(session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
+    new (session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
 };
 interface XRWebGLLayer {
     framebuffer: WebGLFramebuffer;
@@ -75777,7 +75958,7 @@ declare class XRRay {
 declare enum XRHitTestTrackableType {
     "point",
     "plane",
-    "mesh"
+    "mesh",
 }
 
 interface XRHitResult {
@@ -75825,7 +76006,49 @@ interface XRPlane {
     planeSpace: XRSpace;
     polygon: Array<DOMPointReadOnly>;
     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.
 // They are intended for use with either Babylon Native https://github.com/BabylonJS/BabylonNative or
 // Babylon React Native: https://github.com/BabylonJS/BabylonReactNative

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 5 - 5
dist/preview release/inspector/babylon.inspector.bundle.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 559 - 178
dist/preview release/inspector/babylon.inspector.bundle.max.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 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 {
+    /** @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 {
         R: boolean;
         G: boolean;
@@ -1290,7 +1304,7 @@ declare module INSPECTOR {
     }
     export class TextureHelper {
         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 {
@@ -1327,7 +1341,7 @@ declare module INSPECTOR {
         addTool(url: string): void;
         changeTool(toolIndex: number): void;
         activeToolIndex: number;
-        metadata: any;
+        metadata: IMetadata;
         setMetadata(data: any): void;
     }
     interface IToolBarState {
@@ -1379,6 +1393,7 @@ declare module INSPECTOR {
         private _engine;
         private _scene;
         private _camera;
+        private _cameraPos;
         private _scale;
         private _isPanning;
         private _mouseX;
@@ -1394,6 +1409,7 @@ declare module INSPECTOR {
         private _3DScene;
         private _channels;
         private _face;
+        private _mipLevel;
         private _originalTexture;
         private _target;
         private _originalInternalTexture;
@@ -1409,14 +1425,26 @@ declare module INSPECTOR {
         private static PAN_MOUSE_BUTTON;
         private static MIN_SCALE;
         private static MAX_SCALE;
+        private static SELECT_ALL_KEY;
+        private static DESELECT_KEY;
         private _tool;
         private _setPixelData;
         private _GUI;
         private _window;
-        metadata: any;
+        private _metadata;
         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>;
+        private queueTextureUpdate;
+        startPainting(): CanvasRenderingContext2D;
+        updatePainting(): void;
+        stopPainting(): void;
         private updateDisplay;
         set channels(channels: IChannel[]);
         static paintPixelsOnCanvas(pixelData: Uint8Array, canvas: HTMLCanvasElement): void;
@@ -1428,10 +1456,12 @@ declare module INSPECTOR {
         set tool(tool: BABYLON.Nullable<ITool>);
         get tool(): BABYLON.Nullable<ITool>;
         set face(face: number);
+        set mipLevel(mipLevel: number);
         /** Returns the tool GUI object, allowing tools to access the GUI */
         get GUI(): IToolGUI;
         /** Returns the 3D scene used for postprocesses */
         get scene3D(): BABYLON.Scene;
+        set metadata(metadata: IMetadata);
         private makePlane;
         reset(): void;
         resize(newSize: BABYLON.ISize): Promise<void>;
@@ -1450,6 +1480,8 @@ declare module INSPECTOR {
         resetTexture(): void;
         resizeTexture(width: number, height: number): void;
         uploadTexture(file: File): void;
+        mipLevel: number;
+        setMipLevel: (mipLevel: number) => void;
     }
     interface IPropertiesBarState {
         width: number;
@@ -1473,6 +1505,8 @@ declare module INSPECTOR {
 declare module INSPECTOR {
     interface BottomBarProps {
         name: string;
+        mipLevel: number;
+        hasMips: boolean;
     }
     export class BottomBar extends React.Component<BottomBarProps> {
         render(): JSX.Element;
@@ -1503,6 +1537,9 @@ declare module INSPECTOR {
     export const Contrast: IToolData;
 }
 declare module INSPECTOR {
+    export const RectangleSelect: IToolData;
+}
+declare module INSPECTOR {
     const _default: import("babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent").IToolData[];
     export default _default;
 }
@@ -1512,19 +1549,21 @@ declare module INSPECTOR {
         texture: BABYLON.BaseTexture;
         url: string;
         window: React.RefObject<PopupComponent>;
+        onUpdate: () => void;
     }
     interface ITextureEditorComponentState {
         tools: ITool[];
         activeToolIndex: number;
-        metadata: any;
+        metadata: IMetadata;
         channels: IChannel[];
         pixelData: IPixelData;
         face: number;
+        mipLevel: number;
     }
     export interface IToolParameters {
         /** The visible scene in the editor. Useful for adding pointer and keyboard events. */
         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;
         /** The 3D scene which tools can add post processes to. */
         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. */
         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. */
-        metadata: any;
+        metadata: IMetadata;
         /** Call this when you want to mutate the metadata. */
         setMetadata: (data: any) => void;
         /** Returns the texture coordinates under the cursor */
@@ -1542,6 +1581,12 @@ declare module INSPECTOR {
         GUI: IToolGUI;
         /** Provides access to the BABYLON namespace */
         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 */
     export interface IToolData {
@@ -1568,6 +1613,17 @@ declare module INSPECTOR {
     interface IToolConstructable {
         new (getParameters: () => IToolParameters): IToolType;
     }
+    export interface IMetadata {
+        color: string;
+        alpha: number;
+        select: {
+            x1: number;
+            y1: number;
+            x2: number;
+            y2: number;
+        };
+        [key: string]: any;
+    }
     global {
         var _TOOL_DATA_: IToolData;
     }
@@ -1576,16 +1632,18 @@ declare module INSPECTOR {
         private _UICanvas;
         private _2DCanvas;
         private _3DCanvas;
+        private _timer;
+        private static PREVIEW_UPDATE_DELAY_MS;
         constructor(props: ITextureEditorComponentProps);
         componentDidMount(): void;
         componentDidUpdate(): void;
         componentWillUnmount(): void;
+        textureDidUpdate(): void;
         loadToolFromURL(url: string): void;
         addTools(tools: IToolData[]): void;
         getToolParameters(): IToolParameters;
         changeTool(index: number): void;
         setMetadata(newMetadata: any): void;
-        setFace(face: number): void;
         saveTexture(): void;
         resetTexture(): void;
         resizeTexture(width: number, height: number): void;
@@ -3365,7 +3423,4 @@ declare module INSPECTOR {
         calculateMove(): string;
         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;
     }
 }
+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" {
     import { GlobalState } from "babylonjs-inspector/components/globalState";
     import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
+    import "babylonjs-inspector/lod";
+    import "babylonjs-inspector/lodCube";
     export interface TextureChannelsToDisplay {
         R: boolean;
         G: boolean;
@@ -1459,7 +1475,7 @@ declare module "babylonjs-inspector/textureHelper" {
     }
     export class TextureHelper {
         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" {
@@ -1493,7 +1509,7 @@ declare module "babylonjs-inspector/components/actionTabs/lines/textureLineCompo
 }
 declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/toolBar" {
     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 {
         instance: IToolType;
     }
@@ -1502,7 +1518,7 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         addTool(url: string): void;
         changeTool(toolIndex: number): void;
         activeToolIndex: number;
-        metadata: any;
+        metadata: IMetadata;
         setMetadata(data: any): void;
     }
     interface IToolBarState {
@@ -1547,6 +1563,7 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
     import { StackPanel } from 'babylonjs-gui/2D/controls/stackPanel';
     import { Style } from 'babylonjs-gui/2D/style';
     import { AdvancedDynamicTexture } from 'babylonjs-gui/2D/advancedDynamicTexture';
+    import { IMetadata } from "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent";
     export interface IPixelData {
         x?: number;
         y?: number;
@@ -1566,6 +1583,7 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         private _engine;
         private _scene;
         private _camera;
+        private _cameraPos;
         private _scale;
         private _isPanning;
         private _mouseX;
@@ -1581,6 +1599,7 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         private _3DScene;
         private _channels;
         private _face;
+        private _mipLevel;
         private _originalTexture;
         private _target;
         private _originalInternalTexture;
@@ -1596,14 +1615,26 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         private static PAN_MOUSE_BUTTON;
         private static MIN_SCALE;
         private static MAX_SCALE;
+        private static SELECT_ALL_KEY;
+        private static DESELECT_KEY;
         private _tool;
         private _setPixelData;
         private _GUI;
         private _window;
-        metadata: any;
+        private _metadata;
         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>;
+        private queueTextureUpdate;
+        startPainting(): CanvasRenderingContext2D;
+        updatePainting(): void;
+        stopPainting(): void;
         private updateDisplay;
         set channels(channels: IChannel[]);
         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>);
         get tool(): Nullable<ITool>;
         set face(face: number);
+        set mipLevel(mipLevel: number);
         /** Returns the tool GUI object, allowing tools to access the GUI */
         get GUI(): IToolGUI;
         /** Returns the 3D scene used for postprocesses */
         get scene3D(): Scene;
+        set metadata(metadata: IMetadata);
         private makePlane;
         reset(): void;
         resize(newSize: ISize): Promise<void>;
@@ -1640,6 +1673,8 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         resetTexture(): void;
         resizeTexture(width: number, height: number): void;
         uploadTexture(file: File): void;
+        mipLevel: number;
+        setMipLevel: (mipLevel: number) => void;
     }
     interface IPropertiesBarState {
         width: number;
@@ -1664,6 +1699,8 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
     import * as React from 'react';
     interface BottomBarProps {
         name: string;
+        mipLevel: number;
+        hasMips: boolean;
     }
     export class BottomBar extends React.Component<BottomBarProps> {
         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";
     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" {
     const _default: import("babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent").IToolData[];
     export default _default;
@@ -1720,19 +1761,21 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         texture: BaseTexture;
         url: string;
         window: React.RefObject<PopupComponent>;
+        onUpdate: () => void;
     }
     interface ITextureEditorComponentState {
         tools: ITool[];
         activeToolIndex: number;
-        metadata: any;
+        metadata: IMetadata;
         channels: IChannel[];
         pixelData: IPixelData;
         face: number;
+        mipLevel: number;
     }
     export interface IToolParameters {
         /** The visible scene in the editor. Useful for adding pointer and keyboard events. */
         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;
         /** The 3D scene which tools can add post processes to. */
         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. */
         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. */
-        metadata: any;
+        metadata: IMetadata;
         /** Call this when you want to mutate the metadata. */
         setMetadata: (data: any) => void;
         /** Returns the texture coordinates under the cursor */
@@ -1750,6 +1793,12 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         GUI: IToolGUI;
         /** Provides access to the BABYLON namespace */
         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 */
     export interface IToolData {
@@ -1776,6 +1825,17 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
     interface IToolConstructable {
         new (getParameters: () => IToolParameters): IToolType;
     }
+    export interface IMetadata {
+        color: string;
+        alpha: number;
+        select: {
+            x1: number;
+            y1: number;
+            x2: number;
+            y2: number;
+        };
+        [key: string]: any;
+    }
     global {
         var _TOOL_DATA_: IToolData;
     }
@@ -1784,16 +1844,18 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/mat
         private _UICanvas;
         private _2DCanvas;
         private _3DCanvas;
+        private _timer;
+        private static PREVIEW_UPDATE_DELAY_MS;
         constructor(props: ITextureEditorComponentProps);
         componentDidMount(): void;
         componentDidUpdate(): void;
         componentWillUnmount(): void;
+        textureDidUpdate(): void;
         loadToolFromURL(url: string): void;
         addTools(tools: IToolData[]): void;
         getToolParameters(): IToolParameters;
         changeTool(index: number): void;
         setMetadata(newMetadata: any): void;
-        setFace(face: number): void;
         saveTexture(): void;
         resetTexture(): void;
         resizeTexture(width: number, height: number): void;
@@ -4091,10 +4153,6 @@ declare module "babylonjs-inspector/components/actionTabs/tabs/propertyGrids/ani
         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" {
     export * from "babylonjs-inspector/index";
 }
@@ -5385,6 +5443,20 @@ 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 {
         R: boolean;
         G: boolean;
@@ -5393,7 +5465,7 @@ declare module INSPECTOR {
     }
     export class TextureHelper {
         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 {
@@ -5430,7 +5502,7 @@ declare module INSPECTOR {
         addTool(url: string): void;
         changeTool(toolIndex: number): void;
         activeToolIndex: number;
-        metadata: any;
+        metadata: IMetadata;
         setMetadata(data: any): void;
     }
     interface IToolBarState {
@@ -5482,6 +5554,7 @@ declare module INSPECTOR {
         private _engine;
         private _scene;
         private _camera;
+        private _cameraPos;
         private _scale;
         private _isPanning;
         private _mouseX;
@@ -5497,6 +5570,7 @@ declare module INSPECTOR {
         private _3DScene;
         private _channels;
         private _face;
+        private _mipLevel;
         private _originalTexture;
         private _target;
         private _originalInternalTexture;
@@ -5512,14 +5586,26 @@ declare module INSPECTOR {
         private static PAN_MOUSE_BUTTON;
         private static MIN_SCALE;
         private static MAX_SCALE;
+        private static SELECT_ALL_KEY;
+        private static DESELECT_KEY;
         private _tool;
         private _setPixelData;
         private _GUI;
         private _window;
-        metadata: any;
+        private _metadata;
         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>;
+        private queueTextureUpdate;
+        startPainting(): CanvasRenderingContext2D;
+        updatePainting(): void;
+        stopPainting(): void;
         private updateDisplay;
         set channels(channels: IChannel[]);
         static paintPixelsOnCanvas(pixelData: Uint8Array, canvas: HTMLCanvasElement): void;
@@ -5531,10 +5617,12 @@ declare module INSPECTOR {
         set tool(tool: BABYLON.Nullable<ITool>);
         get tool(): BABYLON.Nullable<ITool>;
         set face(face: number);
+        set mipLevel(mipLevel: number);
         /** Returns the tool GUI object, allowing tools to access the GUI */
         get GUI(): IToolGUI;
         /** Returns the 3D scene used for postprocesses */
         get scene3D(): BABYLON.Scene;
+        set metadata(metadata: IMetadata);
         private makePlane;
         reset(): void;
         resize(newSize: BABYLON.ISize): Promise<void>;
@@ -5553,6 +5641,8 @@ declare module INSPECTOR {
         resetTexture(): void;
         resizeTexture(width: number, height: number): void;
         uploadTexture(file: File): void;
+        mipLevel: number;
+        setMipLevel: (mipLevel: number) => void;
     }
     interface IPropertiesBarState {
         width: number;
@@ -5576,6 +5666,8 @@ declare module INSPECTOR {
 declare module INSPECTOR {
     interface BottomBarProps {
         name: string;
+        mipLevel: number;
+        hasMips: boolean;
     }
     export class BottomBar extends React.Component<BottomBarProps> {
         render(): JSX.Element;
@@ -5606,6 +5698,9 @@ declare module INSPECTOR {
     export const Contrast: IToolData;
 }
 declare module INSPECTOR {
+    export const RectangleSelect: IToolData;
+}
+declare module INSPECTOR {
     const _default: import("babylonjs-inspector/components/actionTabs/tabs/propertyGrids/materials/textures/textureEditorComponent").IToolData[];
     export default _default;
 }
@@ -5615,19 +5710,21 @@ declare module INSPECTOR {
         texture: BABYLON.BaseTexture;
         url: string;
         window: React.RefObject<PopupComponent>;
+        onUpdate: () => void;
     }
     interface ITextureEditorComponentState {
         tools: ITool[];
         activeToolIndex: number;
-        metadata: any;
+        metadata: IMetadata;
         channels: IChannel[];
         pixelData: IPixelData;
         face: number;
+        mipLevel: number;
     }
     export interface IToolParameters {
         /** The visible scene in the editor. Useful for adding pointer and keyboard events. */
         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;
         /** The 3D scene which tools can add post processes to. */
         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. */
         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. */
-        metadata: any;
+        metadata: IMetadata;
         /** Call this when you want to mutate the metadata. */
         setMetadata: (data: any) => void;
         /** Returns the texture coordinates under the cursor */
@@ -5645,6 +5742,12 @@ declare module INSPECTOR {
         GUI: IToolGUI;
         /** Provides access to the BABYLON namespace */
         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 */
     export interface IToolData {
@@ -5671,6 +5774,17 @@ declare module INSPECTOR {
     interface IToolConstructable {
         new (getParameters: () => IToolParameters): IToolType;
     }
+    export interface IMetadata {
+        color: string;
+        alpha: number;
+        select: {
+            x1: number;
+            y1: number;
+            x2: number;
+            y2: number;
+        };
+        [key: string]: any;
+    }
     global {
         var _TOOL_DATA_: IToolData;
     }
@@ -5679,16 +5793,18 @@ declare module INSPECTOR {
         private _UICanvas;
         private _2DCanvas;
         private _3DCanvas;
+        private _timer;
+        private static PREVIEW_UPDATE_DELAY_MS;
         constructor(props: ITextureEditorComponentProps);
         componentDidMount(): void;
         componentDidUpdate(): void;
         componentWillUnmount(): void;
+        textureDidUpdate(): void;
         loadToolFromURL(url: string): void;
         addTools(tools: IToolData[]): void;
         getToolParameters(): IToolParameters;
         changeTool(index: number): void;
         setMetadata(newMetadata: any): void;
-        setFace(face: number): void;
         saveTexture(): void;
         resetTexture(): void;
         resizeTexture(width: number, height: number): void;
@@ -7468,7 +7584,4 @@ declare module INSPECTOR {
         calculateMove(): string;
         render(): JSX.Element;
     }
-}
-declare module INSPECTOR {
-    export const RectangleSelect: IToolData;
 }

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 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" {
     import { Nullable } from "babylonjs/types";
+    import { ThinEngine } from "babylonjs/Engines/thinEngine";
     import { WebXRRenderTarget } from "babylonjs/XR/webXRTypes";
     import { WebXRSessionManager } from "babylonjs/XR/webXRSessionManager";
     import { Observable } from "babylonjs/Misc/observable";
@@ -48564,9 +48565,10 @@ declare module "babylonjs/XR/webXRManagedOutputCanvas" {
         newCanvasCssStyle?: string;
         /**
          * 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
          */
-        static GetDefaults(): WebXRManagedOutputCanvasOptions;
+        static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions;
     }
     /**
      * 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)
          */
         xrNativeFeatureName?: string;
+        /**
+         * A list of (Babylon WebXR) features this feature depends on
+         */
+        dependsOn?: string[];
     }
     /**
      * 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.
          */
         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.
@@ -49868,7 +49878,7 @@ declare module "babylonjs/XR/motionController/webXRAbstractMotionController" {
         /**
          * 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
@@ -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
          */
         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
          * @param axis the index of the axis
@@ -50287,6 +50297,7 @@ declare module "babylonjs/XR/webXRInputSource" {
         private _options;
         private _tmpVector;
         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
          */
@@ -50840,7 +50851,10 @@ declare module "babylonjs/XR/webXREnterExitUI" {
         options: WebXREnterExitUIOptions;
         private _activeButton;
         private _buttons;
-        private _overlay;
+        /**
+         * The HTML Div Element to which buttons are added.
+         */
+        readonly overlay: HTMLDivElement;
         /**
          * Fired every time the active button is changed.
          *
@@ -79029,6 +79043,218 @@ declare module "babylonjs/XR/features/WebXRFeaturePointSystem" {
         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" {
     export * from "babylonjs/XR/features/WebXRHitTestLegacy";
     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/WebXRHitTest";
     export * from "babylonjs/XR/features/WebXRFeaturePointSystem";
+    export * from "babylonjs/XR/features/WebXRHandTracking";
 }
 declare module "babylonjs/XR/motionController/webXRMicrosoftMixedRealityController" {
     import { WebXRAbstractMotionController, IMinimalMotionControllerObject, MotionControllerHandedness } from "babylonjs/XR/motionController/webXRAbstractMotionController";
@@ -126183,9 +126410,10 @@ declare module BABYLON {
         newCanvasCssStyle?: string;
         /**
          * 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
          */
-        static GetDefaults(): WebXRManagedOutputCanvasOptions;
+        static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions;
     }
     /**
      * 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)
          */
         xrNativeFeatureName?: string;
+        /**
+         * A list of (Babylon WebXR) features this feature depends on
+         */
+        dependsOn?: string[];
     }
     /**
      * A list of the currently available features without referencing them
@@ -126502,6 +126734,10 @@ declare module BABYLON {
          * The name of the feature points feature.
          */
         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.
@@ -127448,7 +127684,7 @@ declare module BABYLON {
         /**
          * 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
@@ -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
          */
         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
          * @param axis the index of the axis
@@ -127850,6 +128086,7 @@ declare module BABYLON {
         private _options;
         private _tmpVector;
         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
          */
@@ -128375,7 +128612,10 @@ declare module BABYLON {
         options: WebXREnterExitUIOptions;
         private _activeButton;
         private _buttons;
-        private _overlay;
+        /**
+         * The HTML Div Element to which buttons are added.
+         */
+        readonly overlay: HTMLDivElement;
         /**
          * Fired every time the active button is changed.
          *
@@ -154244,6 +154484,209 @@ 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
      */
     export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionController {
@@ -154972,59 +155415,23 @@ interface Window {
 interface Gamepad {
     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 {
     depthNear?: number;
@@ -155040,6 +155447,7 @@ interface XRInputSource {
     gripSpace: XRSpace | undefined;
     gamepad: Gamepad | undefined;
     profiles: Array<string>;
+    hand: XRHand | undefined;
 }
 
 interface XRSessionInit {
@@ -155065,9 +155473,7 @@ interface XRSession {
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
 
     // legacy plane detection
-    updateWorldTrackingState(options: {
-        planeDetectionState?: { enabled: boolean; }
-    }): void;
+    updateWorldTrackingState(options: { planeDetectionState?: { enabled: boolean } }): void;
 }
 
 interface XRReferenceSpace extends XRSpace {
@@ -155084,7 +155490,7 @@ interface XRFrame {
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
 
     // AR
-    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult> ;
+    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     // Anchors
     trackedAnchors?: XRAnchorSet;
@@ -155093,6 +155499,8 @@ interface XRFrame {
     worldInformation: {
         detectedPlanes?: XRPlaneSet;
     };
+    // Hand tracking
+    getJointPose(joint: XRJointSpace, baseSpace: XRSpace): XRJointPose;
 }
 
 interface XRViewerPose extends XRPose {
@@ -155115,7 +155523,7 @@ interface XRWebGLLayerOptions {
 
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
-    new(session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
+    new (session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
 };
 interface XRWebGLLayer {
     framebuffer: WebGLFramebuffer;
@@ -155160,7 +155568,7 @@ declare class XRRay {
 declare enum XRHitTestTrackableType {
     "point",
     "plane",
-    "mesh"
+    "mesh",
 }
 
 interface XRHitResult {
@@ -155208,7 +155616,49 @@ interface XRPlane {
     planeSpace: XRSpace;
     polygon: Array<DOMPointReadOnly>;
     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.
 // They are intended for use with either Babylon Native https://github.com/BabylonJS/BabylonNative or
 // Babylon React Native: https://github.com/BabylonJS/BabylonReactNative

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 20 - 16
dist/preview release/viewer/babylon.viewer.js


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 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))
 - 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))
-- 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
 
@@ -172,6 +172,9 @@
 - 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))
 - 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
 

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

@@ -801,7 +801,10 @@ $line-padding-left: 2px;
                         width: 256px;
                         margin-top: 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}
                         url={textureUrl}
                         window={this.popoutWindowRef}
+                        onUpdate={() => this.forceRefresh()}
                     />
                 </PopupComponent>)}
                 <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 {
     name: string;
+    mipLevel: number;
+    hasMips: boolean;
 }
 
 export class BottomBar extends React.Component<BottomBarProps> {
     render() {
         return <div id='bottom-bar'>
             <span id='file-url'>{this.props.name}</span>
+            {this.props.hasMips && <span id='mip-level'>MIP Preview: {this.props.mipLevel}</span>}
         </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 */
         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 */
         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() {
             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 { Floodfill } from './floodfill';
 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 { 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 = {
     name: 'Eyedropper',
     type: class {
         getParameters: () => IToolParameters;
-        pointerObservable: any;
+        pointerObserver: Nullable<Observer<PointerInfo>>;
         isPicking: boolean;
 
         constructor(getParameters: () => IToolParameters) {
@@ -13,18 +16,18 @@ export const Eyedropper : IToolData = {
         }
 
         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;
-            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 () {
-            this.pointerObservable = this.getParameters().scene.onPointerObservable.add((pointerInfo) => {
+            this.pointerObserver = this.getParameters().scene.onPointerObservable.add((pointerInfo) => {
                 if (pointerInfo.pickInfo?.hit) {
                     if (pointerInfo.type === PointerEventTypes.POINTERDOWN) {
                         this.isPicking = true;
@@ -41,13 +44,10 @@ export const Eyedropper : IToolData = {
             this.isPicking = false;
         }
         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
     NjhjLTEuNjYtMS42Ni00LjA2LTEtNS41Ni41YTExLjg5LDExLjg5LDAsMCwwLTEuNjYsMi4zMUwyMiwxMy40MWExLjg5LDEuODksMCwwLDAtMi42NiwwbC0uOS45YTEuODksMS44OSwwLDAsMC0uMjIsMi4zOWwtNi4wOSw2LjA5YTIuNzUsMi43NSwwLDAsMC0uNzMs

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

@@ -1,28 +1,30 @@
 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 = {
     name: 'Floodfill',
     type: class {
         getParameters: () => IToolParameters;
-        pointerObservable: any;
-
+        pointerObserver: Nullable<Observer<PointerInfo>>;
         constructor(getParameters: () => IToolParameters) {
             this.getParameters = getParameters;
         }
 
         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 () {
-            this.pointerObservable = this.getParameters().scene.onPointerObservable.add((pointerInfo) => {
+            this.pointerObserver = this.getParameters().scene.onPointerObservable.add((pointerInfo) => {
                 if (pointerInfo.pickInfo?.hit) {
                     if (pointerInfo.type === PointerEventTypes.POINTERDOWN) {
                         this.fill();
@@ -31,8 +33,8 @@ export const Floodfill : IToolData = {
             });
         }
         cleanup () {
-            if (this.pointerObservable) {
-                this.getParameters().scene.onPointerObservable.remove(this.pointerObservable);
+            if (this.pointerObserver) {
+                this.getParameters().scene.onPointerObservable.remove(this.pointerObserver);
             }
         }
     },

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 102 - 50
inspector/src/components/actionTabs/tabs/propertyGrids/materials/textures/defaultTools/paintbrush.ts


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 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;
     resizeTexture(width: number, height: number) : void;
     uploadTexture(file : File) : void;
+    mipLevel: number;
+    setMipLevel: (mipLevel : number) => void;
 }
 
 interface IPropertiesBarState {
@@ -20,7 +22,7 @@ interface IPropertiesBarState {
 
 interface IPixelDataProps {
     name : string;
-    data?: number;
+    data: number | undefined;
 }
 
 export class PropertiesBar extends React.Component<IPropertiesBarProps,IPropertiesBarState> {
@@ -54,7 +56,7 @@ export class PropertiesBar extends React.Component<IPropertiesBarProps,IProperti
     }
 
     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) {
@@ -68,76 +70,78 @@ export class PropertiesBar extends React.Component<IPropertiesBarProps,IProperti
     }
 
     render() {
+        const {mipLevel, setMipLevel, pixelData, resizeTexture, texture, face, setFace, saveTexture, resetTexture, uploadTexture} = this.props;
         return <div id='properties'>
                 <div className='tab' id='logo-tab'>
                     <img className='icon' src={this._babylonLogo}/>
                 </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 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>
-                        <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 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>
-                </>}
+                    {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='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>;
     }

+ 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 { Style } from 'babylonjs-gui/2D/style';
 import { AdvancedDynamicTexture } from 'babylonjs-gui/2D/advancedDynamicTexture';
+import { IMetadata } from './textureEditorComponent';
 
 
 export interface IPixelData {
@@ -57,6 +58,7 @@ export class TextureCanvasManager {
     private _engine: Engine;
     private _scene: Scene;
     private _camera: FreeCamera;
+    private _cameraPos: Vector2;
 
     private _scale : number;
     private _isPanning : boolean = false;
@@ -81,6 +83,7 @@ export class TextureCanvasManager {
 
     private _channels : IChannel[] = [];
     private _face : number = 0;
+    private _mipLevel : number = 0;
 
     /* The texture from the original engine that we invoked the editor on */
     private _originalTexture: BaseTexture;
@@ -108,6 +111,9 @@ export class TextureCanvasManager {
     private static MIN_SCALE : number = 0.01;
     private static MAX_SCALE : number = 10;
 
+    private static SELECT_ALL_KEY = 'KeyA';
+    private static DESELECT_KEY = 'Escape'
+
     private _tool : Nullable<ITool>;
 
     private _setPixelData : (pixelData : IPixelData) => void;
@@ -116,24 +122,39 @@ export class TextureCanvasManager {
 
     private _window : Window;
 
-    public metadata : any = {};
+    private _metadata : IMetadata;
 
     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(
         texture: BaseTexture,
         window: Window,
         canvasUI: HTMLCanvasElement,
         canvas2D: HTMLCanvasElement,
         canvas3D: HTMLCanvasElement,
-        setPixelData: (pixelData : IPixelData) => void
+        setPixelData: (pixelData : IPixelData) => void,
+        metadata: IMetadata,
+        onUpdate: () => void,
+        setMetadata: (metadata: any) => void
     ) {
         this._window = window;
 
         this._UICanvas = canvasUI;
         this._2DCanvas = canvas2D;
         this._3DCanvas = canvas3D;
+        this._paintCanvas = document.createElement('canvas');
         this._setPixelData = setPixelData;
+        this._metadata = metadata;
+        this._onUpdate = onUpdate;
+        this._setMetadata = setMetadata;
 
         this._size = texture.getSize();
         this._originalTexture = texture;
@@ -144,8 +165,9 @@ export class TextureCanvasManager {
 
         this._camera = new FreeCamera('camera', new Vector3(0, 0, -1), this._scene);
         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._3DScene = new Scene(this._3DEngine);
@@ -156,15 +178,13 @@ export class TextureCanvasManager {
         cam.mode = Camera.ORTHOGRAPHIC_CAMERA;
         [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.hasVertexAlpha = true;
         const mat = new StandardMaterial('material', this._3DScene);
         mat.diffuseTexture = this._3DCanvasTexture;
+        mat.useAlphaFromDiffuseTexture = true;
         mat.disableLighting = true;
         mat.emissiveColor = Color3.White();
         this._3DPlane.material = mat;
-        
-
-        this.grabOriginalTexture();
-
 
         this._planeMaterial = new ShaderMaterial(
             'shader',
@@ -194,11 +214,23 @@ export class TextureCanvasManager {
                     uniform bool g;
                     uniform bool b;
                     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;
+
+                    float scl = 200.0;
+                    float speed = 10.0 / 1000.0;
+                    float smoothing = 0.2;
             
                     void main(void) {
-                        float size = 20.0;
                         vec2 pos2 = vec2(gl_FragCoord.x, gl_FragCoord.y);
                         vec2 pos = floor(pos2 * 0.05);
                         float pattern = mod(pos.x + pos.y, 2.0); 
@@ -244,20 +276,42 @@ export class TextureCanvasManager {
                                 col.a = 1.0;
                             }
                         }
-                        gl_FragColor = col;
                         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'],
-            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.setFloat('r', 1.0);
         this._planeMaterial.setFloat('g', 1.0);
         this._planeMaterial.setFloat('b', 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;
         
         const adt = AdvancedDynamicTexture.CreateFullscreenUI('gui', true, this._scene);
@@ -284,7 +338,7 @@ export class TextureCanvasManager {
         topBar.background = '#666666';
         topBar.thickness = 0;
         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';});
 
         const title = new TextBlock();
@@ -295,25 +349,53 @@ export class TextureCanvasManager {
         topBar.addControl(title);
         this._GUI.toolWindow.addControl(topBar);
 
-        this._window.addEventListener('pointermove',  (evt : PointerEvent) => {
+        this._window.addEventListener('pointermove', evt => {
             if (!this._GUI.isDragging) return;
             if (!this._GUI.dragCoords) {
                 this._GUI.dragCoords = new Vector2(evt.x, evt.y);
                 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.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.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._planeMaterial.setInt('time', new Date().getTime());
         });
 
         this._scale = 1.5;
@@ -322,10 +404,11 @@ export class TextureCanvasManager {
         this._scene.onBeforeRenderObservable.add(() => {
             this._scale = Math.min(Math.max(this._scale, TextureCanvasManager.MIN_SCALE), TextureCanvasManager.MAX_SCALE);
             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) => {
@@ -349,16 +432,15 @@ export class TextureCanvasManager {
                     break;
                 case PointerEventTypes.POINTERMOVE:
                     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._mouseY = pointerInfo.event.y;
                     }
                     if (pointerInfo.pickInfo?.hit) {
                         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 {
                         this._setPixelData({});
                     }
@@ -370,11 +452,13 @@ export class TextureCanvasManager {
             switch(kbInfo.type) {
                 case KeyboardEventTypes.KEYDOWN:
                     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;
                 case KeyboardEventTypes.KEYUP:
@@ -384,6 +468,7 @@ export class TextureCanvasManager {
         });
     }
 
+
     public async updateTexture() {
         this._didEdit = true;
         const element = this._editing3D ? this._3DCanvas : this._2DCanvas;
@@ -407,16 +492,102 @@ export class TextureCanvasManager {
             } else {
                 (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._channelsTexture.element = element;
         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() {
-        this._3DScene.render()
-        this._channelsTexture.update();
+        this._3DScene.render();
+        this._channelsTexture.update(true);
     }
 
     public set channels(channels: IChannel[]) {
@@ -456,8 +627,11 @@ export class TextureCanvasManager {
             this._size.width,
             this._size.height,
             this._face,
-            {R:true ,G:true ,B:true ,A:true}
+            {R:true, G:true, B:true, A:true},
+            undefined,
+            this._mipLevel
         ).then(data => {
+            this._imageData = data;
             TextureCanvasManager.paintPixelsOnCanvas(data, this._2DCanvas);
             this._3DCanvasTexture.update();
             this.updateDisplay();
@@ -513,7 +687,6 @@ export class TextureCanvasManager {
         return this._tool;
     }
 
-    // BROKEN : FIX THIS
     public set face(face: number) {
         if (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 */
     public get GUI() {
         return this._GUI;
@@ -532,6 +711,15 @@ export class TextureCanvasManager {
         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() {
         const textureRatio = this._size.width / this._size.height;
         if (this._plane) this._plane.dispose();
@@ -551,6 +739,7 @@ export class TextureCanvasManager {
         this.grabOriginalTexture();
         this.makePlane();
         this._didEdit = false;
+        this._onUpdate();
     }
 
     public async resize(newSize : ISize) {
@@ -567,9 +756,11 @@ export class TextureCanvasManager {
         this._2DCanvas.height = this._size.height;
         this._3DCanvas.width = this._size.width;
         this._3DCanvas.height = this._size.height;
+        this._planeMaterial.setInt('w', this._size.width);
+        this._planeMaterial.setInt('h', this._size.height);
         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.makePlane();
@@ -607,7 +798,7 @@ export class TextureCanvasManager {
                         this._scene,
                         this._originalTexture.noMipmap,
                         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})
                                 .then((pixels) => {
@@ -633,7 +824,8 @@ export class TextureCanvasManager {
         }
         if (this._tool) {
             this._tool.instance.cleanup();
-        }        
+        }
+        this._paintCanvas.parentNode?.removeChild(this._paintCanvas);
         this._3DPlane.dispose();
         this._3DCanvasTexture.dispose();
         this._3DScene.dispose();

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

@@ -47,70 +47,59 @@
     }
     
     #properties {
-        position: absolute;
         width: 100%;
         height: 40px;
         
         display: flex;
-        flex-flow: row nowrap;
         align-items: center;
         font-size: 12px;
         color: white;
         user-select: none;
-        background-color: #1e1e1e;
+        background-color: #333333;
     
         .tab {
-            display: flex;
+            display: inline-flex;
             line-height: 40px;
             height: 40px;
             flex-shrink: 0;
             flex-grow: 0;
-            margin-right: 2px;
+            border-right: 2px solid #1e1e1e;
             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 {
-            flex-grow: 1;
-            flex-shrink: 1;
             margin-right: 0;
-            .content {
-                position: absolute;
-                right: 0px;
-            }
+            flex-grow: 0;
+            flex-shrink: 0;
             
             input[type="file"] {
                 display: none;
@@ -179,10 +168,22 @@
 
         #color {
             margin-top: 8px;
-            #activeColor {
+            #active-color-bg {
+                border-radius: 50%;
+                width: 20px;
+                height: 20px;
                 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;
                 height: 20px;
+                position: absolute;
+                top: 0;
+                left: 0;
                 border-radius: 50%;
             }
         }
@@ -227,20 +228,25 @@
     
     #canvas-ui {
         width: 100%;
-        height: 100%;
+        height: calc(100% - 70px);
+        outline: none;
     }
     
     #bottom-bar {
-        position: absolute;
-        bottom: 0;
         height: 30px;
         width: 100%;
         background-color: #333333;
         font-size: 14px;
         user-select: none;
         line-height: 30px;
+        position: relative;
         #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;
     url: string;
     window: React.RefObject<PopupComponent>;
+    onUpdate: () => void;
 }
 
 interface ITextureEditorComponentState {
     tools: ITool[];
     activeToolIndex: number;
-    metadata: any;
+    metadata: IMetadata;
     channels: IChannel[];
     pixelData : IPixelData;
     face: number;
+    mipLevel: number;
 }
 
 export interface IToolParameters {
     /** The visible scene in the editor. Useful for adding pointer and keyboard events. */
     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;
     /** The 3D scene which tools can add post processes to. */
     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. */
     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. */
-    metadata: any;
+    metadata: IMetadata;
     /** Call this when you want to mutate the metadata. */
     setMetadata: (data : any) => void;
     /** Returns the texture coordinates under the cursor */
@@ -56,6 +58,12 @@ export interface IToolParameters {
     GUI: IToolGUI;
     /** Provides access to the BABYLON namespace */
     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;
 }
 
+export interface IMetadata {
+    color: string;
+    alpha: number;
+    select: {
+        x1: number,
+        y1: number,
+        x2: number,
+        y2: number
+    }
+    [key: string] : any;
+}
+
 declare global {
     var _TOOL_DATA_ : IToolData;
 }
@@ -96,6 +116,8 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
     private _UICanvas = React.createRef<HTMLCanvasElement>();
     private _2DCanvas = React.createRef<HTMLCanvasElement>();
     private _3DCanvas = React.createRef<HTMLCanvasElement>();
+    private _timer : number | null;
+    private static PREVIEW_UPDATE_DELAY_MS = 160;
 
     constructor(props : ITextureEditorComponentProps) {
         super(props);
@@ -114,17 +136,23 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
             activeToolIndex: -1,
             metadata: {
                 color: '#ffffff',
-                opacity: 1
+                alpha: 1,
+                select: {
+                    x1: -1,
+                    y1: -1,
+                    x2: -1,
+                    y2: -1
+                }
             },
             channels,
             pixelData: {},
-            face: 0
+            face: 0,
+            mipLevel: 0
         }
         this.loadToolFromURL = this.loadToolFromURL.bind(this);
         this.changeTool = this.changeTool.bind(this);
         this.setMetadata = this.setMetadata.bind(this);
         this.saveTexture = this.saveTexture.bind(this);
-        this.setFace = this.setFace.bind(this);
         this.resetTexture = this.resetTexture.bind(this);
         this.resizeTexture = this.resizeTexture.bind(this);
         this.uploadTexture = this.uploadTexture.bind(this);
@@ -138,7 +166,10 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
             this._UICanvas.current!,
             this._2DCanvas.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);
     }
@@ -147,14 +178,24 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
         let channelsClone : IChannel[] = [];
         this.state.channels.forEach(channel => channelsClone.push({...channel}));
         this._textureCanvasManager.channels = channelsClone;
-        this._textureCanvasManager.metadata = {...this.state.metadata};
         this._textureCanvasManager.face = this.state.face;
+        this._textureCanvasManager.mipLevel = this.state.mipLevel;
     }
 
     componentWillUnmount() {
         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) {
         Tools.LoadScript(url, () => {
             this.addTools([_TOOL_DATA_]);
@@ -182,6 +223,9 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
             scene3D: this._textureCanvasManager.scene3D,
             size: this._textureCanvasManager.size,
             updateTexture: () => this._textureCanvasManager.updateTexture(),
+            startPainting: () => this._textureCanvasManager.startPainting(),
+            stopPainting: () => this._textureCanvasManager.stopPainting(),
+            updatePainting: () => this._textureCanvasManager.updatePainting(),
             metadata: this.state.metadata,
             setMetadata: (data : any) => this.setMetadata(data),
             getMouseCoordinates: (pointerInfo : PointerInfo) => this._textureCanvasManager.getMouseCoordinates(pointerInfo),
@@ -205,10 +249,7 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
             ...newMetadata
         }
         this.setState({metadata: data});
-    }
-
-    setFace(face: number) {
-        this.setState({face});
+        this._textureCanvasManager.metadata = data;
     }
 
     saveTexture() {
@@ -236,10 +277,12 @@ export class TextureEditorComponent extends React.Component<ITextureEditorCompon
                 saveTexture={this.saveTexture}
                 pixelData={this.state.pixelData}
                 face={this.state.face}
-                setFace={this.setFace}
+                setFace={face => this.setState({face})}
                 resetTexture={this.resetTexture}
                 resizeTexture={this.resizeTexture}
                 uploadTexture={this.uploadTexture}
+                mipLevel={this.state.mipLevel}
+                setMipLevel={mipLevel => this.setState({mipLevel})}
             />
             {!this.props.texture.isCube && <ToolBar
                 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})}}/>
             <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>
     }
 }

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

@@ -1,6 +1,6 @@
 import * as React from 'react';
 import { SketchPicker } from 'react-color';
-import { IToolData, IToolType } from './textureEditorComponent';
+import { IToolData, IToolType, IMetadata } from './textureEditorComponent';
 
 export interface ITool extends IToolData {
     instance: IToolType;
@@ -11,7 +11,7 @@ interface IToolBarProps {
     addTool(url: string): void;
     changeTool(toolIndex : number): void;
     activeToolIndex : number;
-    metadata: any;
+    metadata: IMetadata;
     setMetadata(data : any): void;
 }
 
@@ -37,7 +37,7 @@ export class ToolBar extends React.Component<IToolBarProps, IToolBarState> {
     }
 
     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');
         return `${this.props.metadata.color}${opacityHex}`;
     }
@@ -79,7 +79,9 @@ export class ToolBar extends React.Component<IToolBarProps, IToolBarState> {
                 </div>
             </div>
             <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>
             {
                 this.state.pickerOpen &&
@@ -92,7 +94,7 @@ export class ToolBar extends React.Component<IToolBarProps, IToolBarState> {
                     }}>
                     </div>
                     <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>
                 </>
             }

+ 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 { 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 { RenderTargetTexture } from 'babylonjs/Materials/Textures/renderTargetTexture';
 import { BaseTexture } from 'babylonjs/Materials/Textures/baseTexture';
 import { Nullable } from 'babylonjs/types';
 
+import "./lod";
+import "./lodCube";
+
+
 export interface TextureChannelsToDisplay {
     R: boolean;
     G: boolean;
@@ -16,27 +18,34 @@ export interface TextureChannelsToDisplay {
 
 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 engine = scene.getEngine();
 
-        let passPostProcess: PostProcess;
+        let lodPostProcess: PostProcess;
 
         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 {
-            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
-            passPostProcess.dispose();
+            lodPostProcess.dispose();
 
             setTimeout(() => {
-                this._ProcessAsync(texture, width, height, face, channels, globalState, resolve, reject);
+                this._ProcessAsync(texture, width, height, face, channels, lod, globalState, resolve, reject);
             }, 250);
 
             return;
@@ -51,14 +60,15 @@ export class TextureHelper {
             { width: width, height: height },
             scene, false);
 
-        passPostProcess.onApply = function(effect) {
+        lodPostProcess.onApply = function(effect) {
             effect.setTexture("textureSampler", texture);
+            effect.setFloat("lod", lod);
         };
 
         let internalTexture = rtt.getInternalTexture();
 
         if (internalTexture) {
-            scene.postProcessManager.directRender([passPostProcess], internalTexture);
+            scene.postProcessManager.directRender([lodPostProcess], internalTexture);
 
             // Read the contents of the framebuffer
             var numberOfChannelsByLine = width * 4;
@@ -141,23 +151,23 @@ export class TextureHelper {
         }
 
         rtt.dispose();
-        passPostProcess.dispose();
+        lodPostProcess.dispose();
         
         if (globalState) {
             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) => {
             if (!texture.isReady() && texture._texture) {
                 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;
             }        
 
-            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) {
                 case Animation.ANIMATIONTYPE_FLOAT:
                     key.values = [animationKey.value];
-                    if (animationKey.inTangent) {
+                    if (animationKey.inTangent !== undefined) {
                         key.values.push(animationKey.inTangent);
                     }
-                    if (animationKey.outTangent) {
+                    if (animationKey.outTangent !== undefined) {
                         if (animationKey.inTangent === undefined) {
                             key.values.push(undefined);
                         }
@@ -1045,10 +1045,10 @@ export class Animation {
                 case Animation.ANIMATIONTYPE_COLOR3:
                 case Animation.ANIMATIONTYPE_COLOR4:
                     key.values = animationKey.value.asArray();
-                    if (animationKey.inTangent) {
+                    if (animationKey.inTangent != undefined) {
                         key.values.push(animationKey.inTangent.asArray());
                     }
-                    if (animationKey.outTangent) {
+                    if (animationKey.outTangent != undefined) {
                         if (animationKey.inTangent === undefined) {
                             key.values.push(undefined);
                         }

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

@@ -246,8 +246,8 @@ export class PointerDragBehavior implements Behavior<AbstractMesh> {
      */
     public releaseDrag() {
         if (this.dragging) {
-            this.onDragEndObservable.notifyObservers({ dragPlanePoint: this.lastDragPosition, pointerId: this.currentDraggingPointerID });
             this.dragging = false;
+            this.onDragEndObservable.notifyObservers({ dragPlanePoint: this.lastDragPosition, pointerId: this.currentDraggingPointerID });
         }
 
         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 {
     depthNear?: number;
@@ -66,6 +30,7 @@ interface XRInputSource {
     gripSpace: XRSpace | undefined;
     gamepad: Gamepad | undefined;
     profiles: Array<string>;
+    hand: XRHand | undefined;
 }
 
 interface XRSessionInit {
@@ -91,9 +56,7 @@ interface XRSession {
     requestHitTest(ray: XRRay, referenceSpace: XRReferenceSpace): Promise<XRHitResult[]>;
 
     // legacy plane detection
-    updateWorldTrackingState(options: {
-        planeDetectionState?: { enabled: boolean; }
-    }): void;
+    updateWorldTrackingState(options: { planeDetectionState?: { enabled: boolean } }): void;
 }
 
 interface XRReferenceSpace extends XRSpace {
@@ -110,7 +73,7 @@ interface XRFrame {
     getPose(space: XRSpace, baseSpace: XRSpace): XRPose | undefined;
 
     // AR
-    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult> ;
+    getHitTestResults(hitTestSource: XRHitTestSource): Array<XRHitTestResult>;
     getHitTestResultsForTransientInput(hitTestSource: XRTransientInputHitTestSource): Array<XRTransientInputHitTestResult>;
     // Anchors
     trackedAnchors?: XRAnchorSet;
@@ -119,6 +82,8 @@ interface XRFrame {
     worldInformation: {
         detectedPlanes?: XRPlaneSet;
     };
+    // Hand tracking
+    getJointPose(joint: XRJointSpace, baseSpace: XRSpace): XRJointPose;
 }
 
 interface XRViewerPose extends XRPose {
@@ -141,7 +106,7 @@ interface XRWebGLLayerOptions {
 
 declare var XRWebGLLayer: {
     prototype: XRWebGLLayer;
-    new(session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
+    new (session: XRSession, context: WebGLRenderingContext | undefined, options?: XRWebGLLayerOptions): XRWebGLLayer;
 };
 interface XRWebGLLayer {
     framebuffer: WebGLFramebuffer;
@@ -186,7 +151,7 @@ declare class XRRay {
 declare enum XRHitTestTrackableType {
     "point",
     "plane",
-    "mesh"
+    "mesh",
 }
 
 interface XRHitResult {
@@ -234,4 +199,45 @@ interface XRPlane {
     planeSpace: XRSpace;
     polygon: Array<DOMPointReadOnly>;
     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
             return;
         }
+        // For now no support for hand-tracked input sources!
+        if (xrController.inputSource.hand && !xrController.inputSource.gamepad) {
+            return;
+        }
         // only support tracker 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();
             const controllerData = this._controllers[this._currentTeleportationControllerId];
-            if (controllerData.teleportationState.forward) {
+            if (controllerData && controllerData.teleportationState.forward) {
                 // set the rotation
                 Quaternion.RotationYawPitchRollToRef(controllerData.teleportationState.currentRotation + controllerData.teleportationState.baseRotation, 0, 0, targetMesh.rotationQuaternion);
                 // 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 "./WebXRHitTest";
 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
      */
-    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.
-    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.
-    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
      */
     protected _lerpTransform(axisMap: IMotionControllerMeshMap, axisValue: number, fixValueCoordinates?: boolean): void {
-        if (!axisMap.minMesh || !axisMap.maxMesh) {
+        if (!axisMap.minMesh || !axisMap.maxMesh || !axisMap.valueMesh) {
             return;
         }
 

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

@@ -17,7 +17,7 @@ import { Logger } from "../../Misc/logger";
 export class WebXRProfiledMotionController extends WebXRAbstractMotionController {
     private _buttonMeshMapping: {
         [buttonName: string]: {
-            mainMesh: AbstractMesh;
+            mainMesh?: AbstractMesh;
             states: {
                 [state: string]: IMotionControllerMeshMap;
             };
@@ -89,7 +89,7 @@ export class WebXRProfiledMotionController extends WebXRAbstractMotionController
                         );
                         dot.material = new StandardMaterial(visualResponseKey + "mat", this.scene);
                         (<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;
                         this._touchDots[visualResponseKey] = dot;
                     }
@@ -145,7 +145,10 @@ export class WebXRProfiledMotionController extends WebXRAbstractMotionController
                     this._lerpTransform(meshes.states[visualResponseKey], value, visResponse.componentProperty !== "button");
                 } else {
                     // 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]) {
                         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 {
     private _activeButton: Nullable<WebXREnterExitUIButton> = null;
     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.
@@ -90,8 +93,9 @@ export class WebXREnterExitUI implements IDisposable {
         /** version of the options passed to this UI */
         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.
         // Hopefully the browsers will catch up
@@ -131,7 +135,7 @@ export class WebXREnterExitUI implements IDisposable {
 
         var renderCanvas = scene.getEngine().getInputElement();
         if (renderCanvas && renderCanvas.parentNode) {
-            renderCanvas.parentNode.appendChild(this._overlay);
+            renderCanvas.parentNode.appendChild(this.overlay);
             scene.onDisposeObservable.addOnce(() => {
                 this.dispose();
             });
@@ -158,7 +162,7 @@ export class WebXREnterExitUI implements IDisposable {
         return Promise.all(supportedPromises).then((results) => {
             results.forEach((supported, i) => {
                 if (supported) {
-                    ui._overlay.appendChild(ui._buttons[i].element);
+                    ui.overlay.appendChild(ui._buttons[i].element);
                     ui._buttons[i].element.onclick = async () => {
                         if (helper.state == WebXRState.IN_XR) {
                             await helper.exitXRAsync();
@@ -192,8 +196,8 @@ export class WebXREnterExitUI implements IDisposable {
      */
     public dispose() {
         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();
     }

+ 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)
      */
     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.
      */
     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();
+        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()) {
             this._features[name] = {
                 featureImplementation: constructed,

+ 6 - 0
src/XR/webXRInputSource.ts

@@ -40,6 +40,7 @@ export interface IWebXRControllerOptions {
 export class WebXRInputSource {
     private _tmpVector = new Vector3();
     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
@@ -116,6 +117,10 @@ export class WebXRInputSource {
                                 this.motionController.rootMesh.parent = this.grip || this.pointer;
                                 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.onDisposeObservable.notifyObservers(this);
         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
+     * @param engine defines the engine to use (can be null)
      * @returns default values of this configuration object
      */
-    public static GetDefaults(): WebXRManagedOutputCanvasOptions {
+    public static GetDefaults(engine?: ThinEngine): WebXRManagedOutputCanvasOptions {
         const defaults = new WebXRManagedOutputCanvasOptions();
         defaults.canvasOptions = {
             antialias: true,
             depth: true,
-            stencil: false,
+            stencil: engine ? engine.isStencilEnable : true,
             alpha: true,
             multiview: false,
             framebufferScaleFactor: 1,

+ 1 - 1
src/XR/webXRSessionManager.ts

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