Explorar o código

added profiled controller
This will use the public xr input repository to load controllers

Raanan Weber %!s(int64=5) %!d(string=hai) anos
pai
achega
6f7a698c93

+ 34 - 35
src/Cameras/XR/features/WebXRControllerPointerSelection.ts

@@ -349,51 +349,50 @@ export class WebXRControllerPointerSelection extends WebXRAbstractFeature implem
     }
 
     private _attachTrackedPointerRayMode(xrController: WebXRController) {
-        if (!xrController.motionController) {
-            return;
-        }
-
-        if (this._options.forceGazeMode) {
-            return this._attachGazeMode(xrController);
-        }
-
-        const controllerData = this._controllers[xrController.uniqueId];
+        xrController.onMotionControllerProfileLoaded.add((motionController) => {
+            if (this._options.forceGazeMode) {
+                return this._attachGazeMode(xrController);
+            }
 
-        if (this._options.overrideButtonId) {
-            controllerData.selectionComponent = xrController.motionController.getComponent(this._options.overrideButtonId);
-        }
-        if (!controllerData.selectionComponent) {
-            controllerData.selectionComponent = xrController.motionController.getMainComponent();
-        }
+            const controllerData = this._controllers[xrController.uniqueId];
 
-        controllerData.onFrameObserver = this._xrSessionManager.onXRFrameObservable.add(() => {
-            if (controllerData.selectionComponent && controllerData.selectionComponent.pressed) {
-                (<StandardMaterial>controllerData.selectionMesh.material).emissiveColor = this.selectionMeshPickedColor;
-                (<StandardMaterial>controllerData.laserPointer.material).emissiveColor = this.laserPointerPickedColor;
-            } else {
-                (<StandardMaterial>controllerData.selectionMesh.material).emissiveColor = this.selectionMeshDefaultColor;
-                (<StandardMaterial>controllerData.laserPointer.material).emissiveColor = this.lasterPointerDefaultColor;
+            if (this._options.overrideButtonId) {
+                controllerData.selectionComponent = motionController.getComponent(this._options.overrideButtonId);
+            }
+            if (!controllerData.selectionComponent) {
+                controllerData.selectionComponent = motionController.getMainComponent();
             }
 
-            controllerData.laserPointer.isVisible = this.displayLaserPointer;
+            controllerData.onFrameObserver = this._xrSessionManager.onXRFrameObservable.add(() => {
+                if (controllerData.selectionComponent && controllerData.selectionComponent.pressed) {
+                    (<StandardMaterial>controllerData.selectionMesh.material).emissiveColor = this.selectionMeshPickedColor;
+                    (<StandardMaterial>controllerData.laserPointer.material).emissiveColor = this.laserPointerPickedColor;
+                } else {
+                    (<StandardMaterial>controllerData.selectionMesh.material).emissiveColor = this.selectionMeshDefaultColor;
+                    (<StandardMaterial>controllerData.laserPointer.material).emissiveColor = this.lasterPointerDefaultColor;
+                }
 
-            if (controllerData.pick) {
-                this._scene.simulatePointerMove(controllerData.pick, { pointerId: controllerData.id });
-            }
-        });
+                controllerData.laserPointer.isVisible = this.displayLaserPointer;
 
-        controllerData.onButtonChangedObserver = controllerData.selectionComponent.onButtonStateChanged.add((component) => {
-            if (component.changes.pressed) {
-                const pressed = component.changes.pressed.current;
                 if (controllerData.pick) {
-                    if (pressed) {
-                        this._scene.simulatePointerDown(controllerData.pick, { pointerId: controllerData.id });
-                    } else {
-                        this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
+                    this._scene.simulatePointerMove(controllerData.pick, { pointerId: controllerData.id });
+                }
+            });
+
+            controllerData.onButtonChangedObserver = controllerData.selectionComponent.onButtonStateChanged.add((component) => {
+                if (component.changes.pressed) {
+                    const pressed = component.changes.pressed.current;
+                    if (controllerData.pick) {
+                        if (pressed) {
+                            this._scene.simulatePointerDown(controllerData.pick, { pointerId: controllerData.id });
+                        } else {
+                            this._scene.simulatePointerUp(controllerData.pick, { pointerId: controllerData.id });
+                        }
                     }
                 }
-            }
+            });
         });
+
     }
 
     private _detachController(xrControllerUniqueId: string) {

+ 2 - 1
src/Cameras/XR/motionController/index.ts

@@ -4,4 +4,5 @@ export * from "./webXRGenericMotionController";
 export * from "./webXRMicrosoftMixedRealityController";
 export * from "./webXRMotionControllerManager";
 export * from "./webXROculusTouchMotionController";
-export * from "./webXRHTCViveMotionController";
+export * from "./webXRHTCViveMotionController";
+export * from "./webXRProfiledMotionController";

+ 51 - 86
src/Cameras/XR/motionController/webXRAbstractController.ts

@@ -11,13 +11,15 @@ import { Mesh } from '../../../Meshes/mesh';
 /**
  * Handness type in xrInput profiles. These can be used to define layouts in the Layout Map.
  */
-export type MotionControllerHandness = "none" | "left" | "right" | "left-right" | "left-right-none";
+export type MotionControllerHandness = "none" | "left" | "right";
 /**
  * The type of components available in motion controllers.
  * This is not the name of the component.
  */
 export type MotionControllerComponentType = "trigger" | "squeeze" | "touchpad" | "thumbstick" | "button";
 
+export type MotionControllerComponentStateType = "default" | "touched" | "pressed";
+
 /**
  * The schema of motion controller layout.
  * No object will be initialized using this interface
@@ -40,38 +42,28 @@ export interface IMotionControllerLayout {
              * The type of input the component outputs
              */
             type: MotionControllerComponentType;
+            gamepadIndices: {
+                button?: number;
+                xAxis?: number;
+                yAxis?: number;
+            };
+            rootNodeName: string;
+            visualResponses: {
+                [state: string]: {
+                    componentProperty: "xAxis" | "yAxis" | "button" | "state";
+                    states: MotionControllerComponentStateType[];
+                    valueNodeProperty: "transform" | "visibility";
+                    valueNodeName?: string;
+                    minNodeName?: string;
+                    maxNodeName?: string;
+                }
+            }
+            touchPointNodeName?: string;
         }
     };
-    /**
-     * An optional gamepad object. If no gamepad object is not defined, no models will be loaded
-     */
-    gamepad?: {
-        /**
-         * Is the mapping based on the xr-standard defined here:
-         * https://www.w3.org/TR/webxr-gamepads-module-1/#xr-standard-gamepad-mapping
-         */
-        mapping: "" | "xr-standard";
-        /**
-         * The buttons available in this input in the right order
-         * index of this button will be the index in the gamepadObject.buttons array
-         * correlates to the componentId in components
-         */
-        buttons: Array<string | null>;
-        /**
-         * Definition of the axes of the gamepad input, sorted
-         * Correlates to componentIds in the components map
-         */
-        axes: Array<{
-            /**
-             * The component id that the axis correlates to
-             */
-            componentId: string;
-            /**
-             * X or Y Axis
-             */
-            axis: "x-axis" | "y-axis";
-        } | null>;
-    };
+    gamepadMapping: "" | "xr-standard";
+    rootNodeName: string;
+    assetPath: string;
 }
 
 /**
@@ -131,7 +123,7 @@ export interface IMotionControllerButtonMeshMap {
  * This will be expanded when touchpad animations are fully supported
  * The meshes are provided to the _lerpAxisTransform function to calculate the current position of the value mesh
  */
-export interface IMotionControllerAxisMeshMap {
+export interface IMotionControllerMeshMap {
     /**
      * The mesh that will be changed when axis value changes
      */
@@ -139,11 +131,11 @@ export interface IMotionControllerAxisMeshMap {
     /**
      * the mesh that defines the minimum value mesh position.
      */
-    minMesh: AbstractMesh;
+    minMesh?: AbstractMesh;
     /**
      * the mesh that defines the maximum value mesh position.
      */
-    maxMesh: AbstractMesh;
+    maxMesh?: AbstractMesh;
 }
 
 /**
@@ -181,17 +173,6 @@ export interface IMinimalMotionControllerObject {
 export abstract class WebXRAbstractMotionController implements IDisposable {
 
     /**
-     * Component type map
-     */
-    public static ComponentType = {
-        TRIGGER: "trigger",
-        SQUEEZE: "squeeze",
-        TOUCHPAD: "touchpad",
-        THUMBSTICK: "thumbstick",
-        BUTTON: "button"
-    };
-
-    /**
      * The profile id of this motion controller
      */
     public abstract profileId: string;
@@ -214,6 +195,8 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
      */
     public rootMesh: Nullable<AbstractMesh>;
 
+    public disableAnimation: boolean = false;
+
     private _modelReady: boolean = false;
 
     /**
@@ -235,27 +218,23 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
         public handness: MotionControllerHandness,
         _doNotLoadControllerMesh: boolean = false) {
         // initialize the components
-        if (layout.gamepad) {
-            layout.gamepad.buttons.forEach(this._initComponent);
+        if (layout.components) {
+            Object.keys(layout.components).forEach(this._initComponent);
         }
         // Model is loaded in WebXRInput
     }
 
-    private _initComponent = (id: string | null) => {
-        if (!this.layout.gamepad || !id) { return; }
-        const type = this.layout.components[id].type;
-        const buttonIndex = this.layout.gamepad.buttons.indexOf(id);
+    private _initComponent = (id: string) => {
+        if (!id) { return; }
+        const componentDef = this.layout.components[id];
+        const type = componentDef.type;
+        const buttonIndex = componentDef.gamepadIndices.button;
         // search for axes
         let axes: number[] = [];
-        this.layout.gamepad.axes.forEach((axis, index) => {
-            if (axis && axis.componentId === id) {
-                if (axis.axis === "x-axis") {
-                    axes[0] = index;
-                } else {
-                    axes[1] = index;
-                }
-            }
-        });
+        if (componentDef.gamepadIndices.xAxis !== undefined && componentDef.gamepadIndices.yAxis !== undefined) {
+            axes.push(componentDef.gamepadIndices.xAxis, componentDef.gamepadIndices.yAxis);
+        }
+
         this.components[id] = new WebXRControllerComponent(id, type, buttonIndex, axes);
     }
 
@@ -343,14 +322,17 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
      * @param axisValue the value of the axis which determines the meshes new position
      * @hidden
      */
-    protected _lerpAxisTransform(axisMap: IMotionControllerAxisMeshMap, axisValue: number): void {
+    protected _lerpTransform(axisMap: IMotionControllerMeshMap, axisValue: number, fixValueCoordinates?: boolean): void {
+        if (!axisMap.minMesh || !axisMap.maxMesh) {
+            return;
+        }
 
         if (!axisMap.minMesh.rotationQuaternion || !axisMap.maxMesh.rotationQuaternion || !axisMap.valueMesh.rotationQuaternion) {
             return;
         }
 
         // Convert from gamepad value range (-1 to +1) to lerp range (0 to 1)
-        let lerpValue = axisValue * 0.5 + 0.5;
+        let lerpValue = fixValueCoordinates ? axisValue * 0.5 + 0.5 : axisValue;
         Quaternion.SlerpToRef(
             axisMap.minMesh.rotationQuaternion,
             axisMap.maxMesh.rotationQuaternion,
@@ -363,30 +345,13 @@ export abstract class WebXRAbstractMotionController implements IDisposable {
             axisMap.valueMesh.position);
     }
 
-    /**
-     * Moves the buttons on the controller mesh based on their current state
-     * @param buttonName the name of the button to move
-     * @param buttonValue the value of the button which determines the buttons new position
-     */
-    protected _lerpButtonTransform(buttonMap: IMotionControllerButtonMeshMap, buttonValue: number): void {
-
-        if (!buttonMap
-            || !buttonMap.unpressedMesh.rotationQuaternion
-            || !buttonMap.pressedMesh.rotationQuaternion
-            || !buttonMap.valueMesh.rotationQuaternion) {
-            return;
-        }
-
-        Quaternion.SlerpToRef(
-            buttonMap.unpressedMesh.rotationQuaternion,
-            buttonMap.pressedMesh.rotationQuaternion,
-            buttonValue,
-            buttonMap.valueMesh.rotationQuaternion);
-        Vector3.LerpToRef(
-            buttonMap.unpressedMesh.position,
-            buttonMap.pressedMesh.position,
-            buttonValue,
-            buttonMap.valueMesh.position);
+    // 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];
+    }
+    // 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];
     }
 
     private _getGenericFilenameAndPath(): { filename: string, path: string } {

+ 4 - 0
src/Cameras/XR/motionController/webXRControllerComponent.ts

@@ -177,6 +177,10 @@ export class WebXRControllerComponent implements IDisposable {
 
         if (this.isButton()) {
             const button = nativeController.buttons[this._buttonIndex];
+            // defensive, in case a profile was forced
+            if (!button) {
+                return;
+            }
             if (this._currentValue !== button.value) {
                 this.changes.value = {
                     current: button.value,

+ 44 - 10
src/Cameras/XR/motionController/webXRGenericMotionController.ts

@@ -11,20 +11,54 @@ import { Quaternion } from '../../../Maths/math.vector';
 
 // https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/generic/generic-trigger-touchpad-thumbstick.json
 const GenericTriggerLayout: IMotionControllerLayoutMap = {
-    "left-right-none": {
+    "left": {
         "selectComponentId": "xr-standard-trigger",
         "components": {
-            "xr-standard-trigger": { "type": "trigger" }
+            "xr-standard-trigger": {
+                "type": "trigger",
+                "gamepadIndices": {
+                    "button": 0
+                },
+                "rootNodeName": "xr_standard_trigger",
+                "visualResponses": {}
+            }
+        },
+        "gamepadMapping": "xr-standard",
+        "rootNodeName": "generic-trigger-left",
+        "assetPath": "left.glb"
+    },
+    "right": {
+        "selectComponentId": "xr-standard-trigger",
+        "components": {
+            "xr-standard-trigger": {
+                "type": "trigger",
+                "gamepadIndices": {
+                    "button": 0
+                },
+                "rootNodeName": "xr_standard_trigger",
+                "visualResponses": {}
+            }
         },
-        "gamepad": {
-            "mapping": "xr-standard",
-            "buttons": [
-                "xr-standard-trigger"
-            ],
-            "axes": []
-        }
+        "gamepadMapping": "xr-standard",
+        "rootNodeName": "generic-trigger-right",
+        "assetPath": "right.glb"
+    },
+    "none": {
+        "selectComponentId": "xr-standard-trigger",
+        "components": {
+            "xr-standard-trigger": {
+                "type": "trigger",
+                "gamepadIndices": {
+                    "button": 0
+                },
+                "rootNodeName": "xr_standard_trigger",
+                "visualResponses": {}
+            }
+        },
+        "gamepadMapping": "xr-standard",
+        "rootNodeName": "generic-trigger-none",
+        "assetPath": "none.glb"
     }
-
 };
 
 // TODO support all generic models with xr-standard mapping at:

+ 190 - 73
src/Cameras/XR/motionController/webXRHTCViveMotionController.ts

@@ -11,56 +11,181 @@ import { Quaternion } from '../../../Maths/math.vector';
 import { WebXRMotionControllerManager } from './webXRMotionControllerManager';
 
 const HTCViveLayout: IMotionControllerLayoutMap = {
-    "left-right-none": {
+    "left": {
         "selectComponentId": "xr-standard-trigger",
         "components": {
-            "xr-standard-trigger": { "type": "trigger" },
-            "xr-standard-squeeze": { "type": "squeeze" },
-            "xr-standard-touchpad": { "type": "touchpad" },
-            "menu": { "type": "button" }
+            "xr-standard-trigger": {
+                "type": "trigger",
+                "gamepadIndices": {
+                    "button": 0
+                },
+                "rootNodeName": "xr_standard_trigger",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-squeeze": {
+                "type": "squeeze",
+                "gamepadIndices": {
+                    "button": 1
+                },
+                "rootNodeName": "xr_standard_squeeze",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-touchpad": {
+                "type": "touchpad",
+                "gamepadIndices": {
+                    "button": 2,
+                    "xAxis": 0,
+                    "yAxis": 1
+                },
+                "rootNodeName": "xr_standard_touchpad",
+                "visualResponses": {
+
+                },
+            },
+            "menu": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 4
+                },
+                "rootNodeName": "menu",
+                "visualResponses": {
+
+                }
+            }
         },
-        "gamepad": {
-            "mapping": "xr-standard",
-            "buttons": [
-                "xr-standard-trigger",
-                "xr-standard-squeeze",
-                "xr-standard-touchpad",
-                null,
-                "menu"
-            ],
-            "axes": [
-                { "componentId": "xr-standard-touchpad", "axis": "x-axis" },
-                { "componentId": "xr-standard-touchpad", "axis": "y-axis" }
-            ]
-        }
-    }
-};
+        "gamepadMapping": "xr-standard",
+        "rootNodeName": "htc_vive_none",
+        "assetPath": "none.glb"
+    },
+    "right": {
+        "selectComponentId": "xr-standard-trigger",
+        "components": {
+            "xr-standard-trigger": {
+                "type": "trigger",
+                "gamepadIndices": {
+                    "button": 0
+                },
+                "rootNodeName": "xr_standard_trigger",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-squeeze": {
+                "type": "squeeze",
+                "gamepadIndices": {
+                    "button": 1
+                },
+                "rootNodeName": "xr_standard_squeeze",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-touchpad": {
+                "type": "touchpad",
+                "gamepadIndices": {
+                    "button": 2,
+                    "xAxis": 0,
+                    "yAxis": 1
+                },
+                "rootNodeName": "xr_standard_touchpad",
+                "visualResponses": {
+
+                },
+            },
+            "menu": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 4
+                },
+                "rootNodeName": "menu",
+                "visualResponses": {
 
-const HTCViveLegacyLayout: IMotionControllerLayoutMap = {
-    "left-right-none": {
+                }
+            }
+        },
+        "gamepadMapping": "xr-standard",
+        "rootNodeName": "htc_vive_none",
+        "assetPath": "none.glb"
+    },
+    "none": {
         "selectComponentId": "xr-standard-trigger",
         "components": {
-            "xr-standard-trigger": { "type": "trigger" },
-            "xr-standard-squeeze": { "type": "squeeze" },
-            "xr-standard-touchpad": { "type": "touchpad" },
-            "menu": { "type": "button" }
+            "xr-standard-trigger": {
+                "type": "trigger",
+                "gamepadIndices": {
+                    "button": 0
+                },
+                "rootNodeName": "xr_standard_trigger",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-squeeze": {
+                "type": "squeeze",
+                "gamepadIndices": {
+                    "button": 1
+                },
+                "rootNodeName": "xr_standard_squeeze",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-touchpad": {
+                "type": "touchpad",
+                "gamepadIndices": {
+                    "button": 2,
+                    "xAxis": 0,
+                    "yAxis": 1
+                },
+                "rootNodeName": "xr_standard_touchpad",
+                "visualResponses": {
+
+                },
+            },
+            "menu": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 4
+                },
+                "rootNodeName": "menu",
+                "visualResponses": {
+
+                }
+            }
         },
-        "gamepad": {
-            "mapping": "",
-            "buttons": [
-                "xr-standard-touchpad",
-                "xr-standard-trigger",
-                "xr-standard-squeeze",
-                "menu"
-            ],
-            "axes": [
-                { "componentId": "xr-standard-touchpad", "axis": "x-axis" },
-                { "componentId": "xr-standard-touchpad", "axis": "y-axis" }
-            ]
-        }
+        "gamepadMapping": "xr-standard",
+        "rootNodeName": "htc-vive-none",
+        "assetPath": "none.glb"
     }
 };
 
+// const HTCViveLegacyLayout: IMotionControllerLayoutMap = {
+//     "left-right-none": {
+//         "selectComponentId": "xr-standard-trigger",
+//         "components": {
+//             "xr-standard-trigger": { "type": "trigger" },
+//             "xr-standard-squeeze": { "type": "squeeze" },
+//             "xr-standard-touchpad": { "type": "touchpad" }
+//         },
+//         "gamepad": {
+//             "mapping": "",
+//             "buttons": [
+//                 "xr-standard-touchpad",
+//                 "xr-standard-trigger",
+//                 "xr-standard-squeeze"
+//             ],
+//             "axes": [
+//                 { "componentId": "xr-standard-touchpad", "axis": "x-axis" },
+//                 { "componentId": "xr-standard-touchpad", "axis": "y-axis" }
+//             ]
+//         }
+//     }
+// };
+
 /**
  * The motion controller class for the standard HTC-Vive controllers
  */
@@ -80,39 +205,31 @@ export class WebXRHTCViveMotionController extends WebXRAbstractMotionController
 
     constructor(scene: Scene,
         gamepadObject: IMinimalMotionControllerObject,
-        handness: MotionControllerHandness,
-        legacyMapping: boolean = false) {
-        super(scene, legacyMapping ? HTCViveLegacyLayout["left-right-none"] : HTCViveLayout["left-right-none"], gamepadObject, handness);
+        handness: MotionControllerHandness/*,
+        private _legacyMapping: boolean = false*/) {
+        super(scene, HTCViveLayout[handness], gamepadObject, handness);
     }
 
     protected _processLoadedModel(_meshes: AbstractMesh[]): void {
-        this.layout.gamepad!.buttons.forEach((buttonName) => {
-            const comp = buttonName && this.getComponent(buttonName);
-            if (comp) {
-                comp.onButtonStateChanged.add((component) => {
-
-                    if (!this.rootMesh) { return; }
-
-                    switch (buttonName) {
-                        case "xr-standard-trigger":
-                            (<AbstractMesh>(this._modelRootNode.getChildren()[6])).rotation.x = -component.value * 0.15;
-                            return;
-                        case "xr-standard-touchpad":
-                            return;
-                        case "xr-standard-squeeze":
-                            return;
-                        case "menu":
-                            if (component.pressed) {
-                                (<AbstractMesh>(this._modelRootNode.getChildren()[2])).position.y = -0.001;
-                            }
-                            else {
-                                (<AbstractMesh>(this._modelRootNode.getChildren()[2])).position.y = 0;
-                            }
-                            return;
-                    }
-                }, undefined, true);
-            }
-        });
+        // this.layout.gamepad!.buttons.forEach((buttonName) => {
+        //     const comp = buttonName && this.getComponent(buttonName);
+        //     if (comp) {
+        //         comp.onButtonStateChanged.add((component) => {
+
+        //             if (!this.rootMesh) { return; }
+
+        //             switch (buttonName) {
+        //                 case "xr-standard-trigger":
+        //                     (<AbstractMesh>(this._modelRootNode.getChildren()[6])).rotation.x = -component.value * 0.15;
+        //                     return;
+        //                 case "xr-standard-touchpad":
+        //                     return;
+        //                 case "xr-standard-squeeze":
+        //                     return;
+        //             }
+        //         }, undefined, true);
+        //     }
+        // });
     }
 
     protected _getFilenameAndPath(): { filename: string; path: string; } {
@@ -149,6 +266,6 @@ WebXRMotionControllerManager.RegisterController("htc-vive", (xrInput: XRInputSou
     return new WebXRHTCViveMotionController(scene, <any>(xrInput.gamepad), xrInput.handedness);
 });
 
-WebXRMotionControllerManager.RegisterController("htc-vive-legacy", (xrInput: XRInputSource, scene: Scene) => {
-    return new WebXRHTCViveMotionController(scene, <any>(xrInput.gamepad), xrInput.handedness, true);
-});
+// WebXRMotionControllerManager.RegisterController("htc-vive-legacy", (xrInput: XRInputSource, scene: Scene) => {
+//     return new WebXRHTCViveMotionController(scene, <any>(xrInput.gamepad), xrInput.handedness, true);
+// });

+ 356 - 34
src/Cameras/XR/motionController/webXRMicrosoftMixedRealityController.ts

@@ -7,36 +7,367 @@ import {
 import { WebXRMotionControllerManager } from './webXRMotionControllerManager';
 import { AbstractMesh } from '../../../Meshes/abstractMesh';
 import { Scene } from '../../../scene';
-import { Logger } from '../../../Misc/logger';
 import { Mesh } from '../../../Meshes/mesh';
 import { Quaternion } from '../../../Maths/math.vector';
 import { SceneLoader } from '../../../Loading/sceneLoader';
 
 // https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/microsoft/microsoft-mixed-reality.json
 const MixedRealityProfile: IMotionControllerLayoutMap = {
-    "left-right": {
+    "left": {
         "selectComponentId": "xr-standard-trigger",
         "components": {
-            "xr-standard-trigger": { "type": "trigger" },
-            "xr-standard-squeeze": { "type": "squeeze" },
-            "xr-standard-touchpad": { "type": "touchpad" },
-            "xr-standard-thumbstick": { "type": "thumbstick" }
+            "xr-standard-trigger": {
+                "type": "trigger",
+                "gamepadIndices": {
+                    "button": 0
+                },
+                "rootNodeName": "xr_standard_trigger",
+                "visualResponses": {
+                    "xr_standard_trigger_pressed": {
+                        "componentProperty": "button",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_trigger_pressed_value",
+                        "minNodeName": "xr_standard_trigger_pressed_min",
+                        "maxNodeName": "xr_standard_trigger_pressed_max"
+                    }
+                }
+            },
+            "xr-standard-squeeze": {
+                "type": "squeeze",
+                "gamepadIndices": {
+                    "button": 1
+                },
+                "rootNodeName": "xr_standard_squeeze",
+                "visualResponses": {
+                    "xr_standard_squeeze_pressed": {
+                        "componentProperty": "button",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_squeeze_pressed_value",
+                        "minNodeName": "xr_standard_squeeze_pressed_min",
+                        "maxNodeName": "xr_standard_squeeze_pressed_max"
+                    }
+                }
+            },
+            "xr-standard-touchpad": {
+                "type": "touchpad",
+                "gamepadIndices": {
+                    "button": 2,
+                    "xAxis": 0,
+                    "yAxis": 1
+                },
+                "rootNodeName": "xr_standard_touchpad",
+                "visualResponses": {
+                    "xr_standard_touchpad_pressed": {
+                        "componentProperty": "button",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_touchpad_pressed_value",
+                        "minNodeName": "xr_standard_touchpad_pressed_min",
+                        "maxNodeName": "xr_standard_touchpad_pressed_max"
+                    },
+                    "xr_standard_touchpad_xaxis_pressed": {
+                        "componentProperty": "xAxis",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_touchpad_xaxis_pressed_value",
+                        "minNodeName": "xr_standard_touchpad_xaxis_pressed_min",
+                        "maxNodeName": "xr_standard_touchpad_xaxis_pressed_max"
+                    },
+                    "xr_standard_touchpad_yaxis_pressed": {
+                        "componentProperty": "yAxis",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_touchpad_yaxis_pressed_value",
+                        "minNodeName": "xr_standard_touchpad_yaxis_pressed_min",
+                        "maxNodeName": "xr_standard_touchpad_yaxis_pressed_max"
+                    },
+                    "xr_standard_touchpad_xaxis_touched": {
+                        "componentProperty": "xAxis",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_touchpad_xaxis_touched_value",
+                        "minNodeName": "xr_standard_touchpad_xaxis_touched_min",
+                        "maxNodeName": "xr_standard_touchpad_xaxis_touched_max"
+                    },
+                    "xr_standard_touchpad_yaxis_touched": {
+                        "componentProperty": "yAxis",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_touchpad_yaxis_touched_value",
+                        "minNodeName": "xr_standard_touchpad_yaxis_touched_min",
+                        "maxNodeName": "xr_standard_touchpad_yaxis_touched_max"
+                    },
+                    "xr_standard_touchpad_axes_touched": {
+                        "componentProperty": "state",
+                        "states": [
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "visibility",
+                        "valueNodeName": "xr_standard_touchpad_axes_touched_value"
+                    }
+                },
+                "touchPointNodeName": "xr_standard_touchpad_axes_touched_value"
+            },
+            "xr-standard-thumbstick": {
+                "type": "thumbstick",
+                "gamepadIndices": {
+                    "button": 3,
+                    "xAxis": 2,
+                    "yAxis": 3
+                },
+                "rootNodeName": "xr_standard_thumbstick",
+                "visualResponses": {
+                    "xr_standard_thumbstick_pressed": {
+                        "componentProperty": "button",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_thumbstick_pressed_value",
+                        "minNodeName": "xr_standard_thumbstick_pressed_min",
+                        "maxNodeName": "xr_standard_thumbstick_pressed_max"
+                    },
+                    "xr_standard_thumbstick_xaxis_pressed": {
+                        "componentProperty": "xAxis",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_thumbstick_xaxis_pressed_value",
+                        "minNodeName": "xr_standard_thumbstick_xaxis_pressed_min",
+                        "maxNodeName": "xr_standard_thumbstick_xaxis_pressed_max"
+                    },
+                    "xr_standard_thumbstick_yaxis_pressed": {
+                        "componentProperty": "yAxis",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_thumbstick_yaxis_pressed_value",
+                        "minNodeName": "xr_standard_thumbstick_yaxis_pressed_min",
+                        "maxNodeName": "xr_standard_thumbstick_yaxis_pressed_max"
+                    }
+                }
+            }
         },
-        "gamepad": {
-            "mapping": "xr-standard",
-            "buttons": [
-                "xr-standard-trigger",
-                "xr-standard-squeeze",
-                "xr-standard-touchpad",
-                "xr-standard-thumbstick"
-            ],
-            "axes": [
-                { "componentId": "xr-standard-touchpad", "axis": "x-axis" },
-                { "componentId": "xr-standard-touchpad", "axis": "y-axis" },
-                { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
-                { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
-            ]
-        }
+        "gamepadMapping": "xr-standard",
+        "rootNodeName": "microsoft-mixed-reality-left",
+        "assetPath": "left.glb"
+    },
+    "right": {
+        "selectComponentId": "xr-standard-trigger",
+        "components": {
+            "xr-standard-trigger": {
+                "type": "trigger",
+                "gamepadIndices": {
+                    "button": 0
+                },
+                "rootNodeName": "xr_standard_trigger",
+                "visualResponses": {
+                    "xr_standard_trigger_pressed": {
+                        "componentProperty": "button",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_trigger_pressed_value",
+                        "minNodeName": "xr_standard_trigger_pressed_min",
+                        "maxNodeName": "xr_standard_trigger_pressed_max"
+                    }
+                }
+            },
+            "xr-standard-squeeze": {
+                "type": "squeeze",
+                "gamepadIndices": {
+                    "button": 1
+                },
+                "rootNodeName": "xr_standard_squeeze",
+                "visualResponses": {
+                    "xr_standard_squeeze_pressed": {
+                        "componentProperty": "button",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_squeeze_pressed_value",
+                        "minNodeName": "xr_standard_squeeze_pressed_min",
+                        "maxNodeName": "xr_standard_squeeze_pressed_max"
+                    }
+                }
+            },
+            "xr-standard-touchpad": {
+                "type": "touchpad",
+                "gamepadIndices": {
+                    "button": 2,
+                    "xAxis": 0,
+                    "yAxis": 1
+                },
+                "rootNodeName": "xr_standard_touchpad",
+                "visualResponses": {
+                    "xr_standard_touchpad_pressed": {
+                        "componentProperty": "button",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_touchpad_pressed_value",
+                        "minNodeName": "xr_standard_touchpad_pressed_min",
+                        "maxNodeName": "xr_standard_touchpad_pressed_max"
+                    },
+                    "xr_standard_touchpad_xaxis_pressed": {
+                        "componentProperty": "xAxis",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_touchpad_xaxis_pressed_value",
+                        "minNodeName": "xr_standard_touchpad_xaxis_pressed_min",
+                        "maxNodeName": "xr_standard_touchpad_xaxis_pressed_max"
+                    },
+                    "xr_standard_touchpad_yaxis_pressed": {
+                        "componentProperty": "yAxis",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_touchpad_yaxis_pressed_value",
+                        "minNodeName": "xr_standard_touchpad_yaxis_pressed_min",
+                        "maxNodeName": "xr_standard_touchpad_yaxis_pressed_max"
+                    },
+                    "xr_standard_touchpad_xaxis_touched": {
+                        "componentProperty": "xAxis",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_touchpad_xaxis_touched_value",
+                        "minNodeName": "xr_standard_touchpad_xaxis_touched_min",
+                        "maxNodeName": "xr_standard_touchpad_xaxis_touched_max"
+                    },
+                    "xr_standard_touchpad_yaxis_touched": {
+                        "componentProperty": "yAxis",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_touchpad_yaxis_touched_value",
+                        "minNodeName": "xr_standard_touchpad_yaxis_touched_min",
+                        "maxNodeName": "xr_standard_touchpad_yaxis_touched_max"
+                    },
+                    "xr_standard_touchpad_axes_touched": {
+                        "componentProperty": "state",
+                        "states": [
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "visibility",
+                        "valueNodeName": "xr_standard_touchpad_axes_touched_value"
+                    }
+                },
+                "touchPointNodeName": "xr_standard_touchpad_axes_touched_value"
+            },
+            "xr-standard-thumbstick": {
+                "type": "thumbstick",
+                "gamepadIndices": {
+                    "button": 3,
+                    "xAxis": 2,
+                    "yAxis": 3
+                },
+                "rootNodeName": "xr_standard_thumbstick",
+                "visualResponses": {
+                    "xr_standard_thumbstick_pressed": {
+                        "componentProperty": "button",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_thumbstick_pressed_value",
+                        "minNodeName": "xr_standard_thumbstick_pressed_min",
+                        "maxNodeName": "xr_standard_thumbstick_pressed_max"
+                    },
+                    "xr_standard_thumbstick_xaxis_pressed": {
+                        "componentProperty": "xAxis",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_thumbstick_xaxis_pressed_value",
+                        "minNodeName": "xr_standard_thumbstick_xaxis_pressed_min",
+                        "maxNodeName": "xr_standard_thumbstick_xaxis_pressed_max"
+                    },
+                    "xr_standard_thumbstick_yaxis_pressed": {
+                        "componentProperty": "yAxis",
+                        "states": [
+                            "default",
+                            "touched",
+                            "pressed"
+                        ],
+                        "valueNodeProperty": "transform",
+                        "valueNodeName": "xr_standard_thumbstick_yaxis_pressed_value",
+                        "minNodeName": "xr_standard_thumbstick_yaxis_pressed_min",
+                        "maxNodeName": "xr_standard_thumbstick_yaxis_pressed_max"
+                    }
+                }
+            }
+        },
+        "gamepadMapping": "xr-standard",
+        "rootNodeName": "microsoft-mixed-reality-right",
+        "assetPath": "right.glb"
     }
 };
 
@@ -118,7 +449,7 @@ export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionCon
     }
 
     protected _processLoadedModel(_meshes: AbstractMesh[]): void {
-        if (!this.rootMesh) { return; }
+        /*if (!this.rootMesh) { return; }
 
         // Button Meshes
         for (let i = 0; i < this.layout.gamepad!.buttons.length; i++) {
@@ -145,7 +476,7 @@ export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionCon
                     const comp = this.getComponent(buttonName);
                     if (comp) {
                         comp.onButtonStateChanged.add((component) => {
-                            this._lerpButtonTransform(buttonMap, component.value);
+                            this._lerpTransform(buttonMap, component.value);
                         }, undefined, true);
                     }
                 } else {
@@ -181,7 +512,7 @@ export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionCon
                 if (comp) {
                     comp.onAxisValueChanged.add((axisValues) => {
                         const value = axisData.axis === "x-axis" ? axisValues.x : axisValues.y;
-                        this._lerpAxisTransform(axisMap, value);
+                        this._lerpTransform(axisMap, value, true);
                     }, undefined, true);
                 }
 
@@ -189,16 +520,7 @@ export class WebXRMicrosoftMixedRealityController extends WebXRAbstractMotionCon
                 // If we didn't find the mesh, it simply means this button won't have transforms applied as mapped button value changes.
                 Logger.Warn('Missing axis submesh under mesh with name: ' + axisMap.rootNodeName);
             }
-        }
-    }
-
-    // Look through all children recursively. This will return null if no mesh exists with the given name.
-    private _getChildByName(node: AbstractMesh, name: string): AbstractMesh {
-        return <AbstractMesh>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.
-    private _getImmediateChildByName(node: AbstractMesh, name: string): AbstractMesh {
-        return <AbstractMesh>node.getChildren((n) => n.name == name, true)[0];
+        }*/
     }
 
     protected _getFilenameAndPath(): { filename: string; path: string; } {

+ 94 - 27
src/Cameras/XR/motionController/webXRMotionControllerManager.ts

@@ -1,8 +1,10 @@
 import {
-    WebXRAbstractMotionController,
+    WebXRAbstractMotionController, IMotionControllerProfile,
 } from './webXRAbstractController';
 import { WebXRGenericTriggerMotionController } from './webXRGenericMotionController';
 import { Scene } from '../../../scene';
+import { Tools } from '../../../Misc/tools';
+import { WebXRProfiledMotionController } from './webXRProfiledMotionController';
 
 /**
  * A construction function type to create a new controller based on an xrInput object
@@ -18,6 +20,7 @@ export type MotionControllerConstructor = (xrInput: XRInputSource, scene: Scene)
  * When using a model try to stay as generic as possible. Eventually there will be no need in any of the controller classes
  */
 export class WebXRMotionControllerManager {
+    public static BaseRepositoryUrl = "https://immersive-web.github.io/webxr-input-profiles/packages/viewer/dist";
     private static _AvailableControllers: { [type: string]: MotionControllerConstructor } = {};
     private static _Fallbacks: { [profileId: string]: string[] } = {};
 
@@ -45,49 +48,113 @@ export class WebXRMotionControllerManager {
      * @param xrInput the xrInput to which a new controller is initialized
      * @param scene the scene to which the model will be added
      * @param forceProfile force a certain profile for this controller
-     * @return the motion controller class for this profile id or the generic standard class if none was found
+     * @return A promise that fulfils with the motion controller class for this profile id or the generic standard class if none was found
      */
-    public static GetMotionControllerWithXRInput(xrInput: XRInputSource, scene: Scene, forceProfile?: string): WebXRAbstractMotionController {
-        //if a type was forced, try creating a controller using it. Continue if not found.
+    public static GetMotionControllerWithXRInput(xrInput: XRInputSource, scene: Scene, forceProfile?: string): Promise<WebXRAbstractMotionController> {
+        const profileArray: string[] = [];
         if (forceProfile) {
-            const constructionFunction = this._AvailableControllers[forceProfile];
-            if (constructionFunction) {
-                return constructionFunction(xrInput, scene);
-            }
+            profileArray.push(forceProfile);
         }
+        profileArray.push(...(xrInput.profiles || []));
 
-        for (let i = 0; i < xrInput.profiles.length; ++i) {
-            const constructionFunction = this._AvailableControllers[xrInput.profiles[i]];
-            if (constructionFunction) {
-                return constructionFunction(xrInput, scene);
-            }
-        }
         // try using the gamepad id
         if (xrInput.gamepad && xrInput.gamepad.id) {
             switch (xrInput.gamepad.id) {
                 case (xrInput.gamepad.id.match(/oculus touch/gi) ? xrInput.gamepad.id : undefined):
                     // oculus in gamepad id - legacy mapping
-                    return this._AvailableControllers["oculus-touch-legacy"](xrInput, scene);
+                    // return Promise.resolve(this._AvailableControllers["oculus-touch-legacy"](xrInput, scene));
+                    profileArray.push("oculus-touch-v2");
+                    break;
                 case (xrInput.gamepad.id.match(/Spatial Controller/gi) ? xrInput.gamepad.id : undefined):
                     // oculus in gamepad id - legacy mapping
-                    return this._AvailableControllers["microsoft-mixed-reality"](xrInput, scene);
+                    // return Promise.resolve(this._AvailableControllers["microsoft-mixed-reality"](xrInput, scene));
+                    break;
                 case (xrInput.gamepad.id.match(/openvr/gi) ? xrInput.gamepad.id : undefined):
                     // oculus in gamepad id - legacy mapping
-                    return this._AvailableControllers["htc-vive-legacy"](xrInput, scene);
+                    // return Promise.resolve(this._AvailableControllers["htc-vive-legacy"](xrInput, scene));
+                    break;
             }
         }
-        // check fallbacks
-        for (let i = 0; i < xrInput.profiles.length; ++i) {
-            const fallbacks = this.FindFallbackWithProfileId(xrInput.profiles[i]);
-            for (let j = 0; j < fallbacks.length; ++j) {
-                const constructionFunction = this._AvailableControllers[fallbacks[j]];
-                if (constructionFunction) {
-                    return constructionFunction(xrInput, scene);
-                }
-            }
+
+        // make sure microsoft/windows mixed reality works correctly
+        const windowsMRIdx = profileArray.indexOf("windows-mixed-reality");
+        if (windowsMRIdx !== -1) {
+            profileArray.splice(windowsMRIdx, 1, "microsoft-mixed-reality");
         }
+
+        // for (let i = 0; i < profileArray.length; ++i) {
+        //     const constructionFunction = this._AvailableControllers[profileArray[i]];
+        //     if (constructionFunction) {
+        //         return Promise.resolve(constructionFunction(xrInput, scene));
+        //     }
+        // }
+
+        if (!profileArray.length) {
+            profileArray.push("generic-button");
+        }
+
+        // TODO use local repository, if asked by the user
+
+        return this._LoadProfileFromRepository(profileArray, xrInput, scene);
+        // // check fallbacks
+        // for (let i = 0; i < profileArray.length; ++i) {
+        //     const fallbacks = this.FindFallbackWithProfileId(profileArray[i]);
+        //     for (let j = 0; j < fallbacks.length; ++j) {
+        //         const constructionFunction = this._AvailableControllers[fallbacks[j]];
+        //         if (constructionFunction) {
+        //             return Promise.resolve(constructionFunction(xrInput, scene));
+        //         }
+        //     }
+        // }
         // return the most generic thing we have
-        return this._AvailableControllers[WebXRGenericTriggerMotionController.ProfileId](xrInput, scene);
+        // return this._AvailableControllers[WebXRGenericTriggerMotionController.ProfileId](xrInput, scene);
+
+        // Check if there is an online profile and use it if possible
+    }
+
+    private static _ProfilesList: { [profile: string]: string };
+
+    // cache for loading
+    private static _ProfileLoadingPromises: { [profileName: string]: Promise<IMotionControllerProfile> } = {};
+
+    private static _LoadProfileFromRepository(profileArray: string[], xrInput: XRInputSource, scene: Scene): Promise<WebXRAbstractMotionController> {
+        return Promise.resolve().then(() => {
+            if (!this._ProfilesList) {
+                return this.UpdateProfilesList();
+            } else {
+                return this._ProfilesList;
+            }
+        }).then((profilesList: { [profile: string]: string }) => {
+            // load the right profile
+            for (let i = 0; i < profileArray.length; ++i) {
+                if (profilesList[profileArray[i]]) {
+                    return profileArray[i];
+                }
+            }
+
+            return "generic-button";
+        }).then((profileToLoad: string) => {
+            // load the profile
+            if (!this._ProfileLoadingPromises[profileToLoad]) {
+                this._ProfileLoadingPromises[profileToLoad] = Tools.LoadFileAsync(`${this.BaseRepositoryUrl}/profiles/${profileToLoad}/profile.json`, false).then((data) => <IMotionControllerProfile>JSON.parse(data as string));
+            }
+            return this._ProfileLoadingPromises[profileToLoad];
+        }).then((profile: IMotionControllerProfile) => {
+            return new WebXRProfiledMotionController(scene, xrInput, profile);
+        });
+
+    }
+
+    public static ClearProfileCache() {
+        delete this._ProfilesList;
+        this._ProfileLoadingPromises = {};
+    }
+
+    public static UpdateProfilesList() {
+        return Tools.LoadFileAsync(this.BaseRepositoryUrl + '/profiles/profilesList.json', false).then((data) => {
+            this._ProfilesList = JSON.parse(data.toString());
+            return this._ProfilesList;
+        });
     }
 
     /**

+ 262 - 92
src/Cameras/XR/motionController/webXROculusTouchMotionController.ts

@@ -15,60 +15,142 @@ const OculusTouchLayouts: IMotionControllerLayoutMap = {
     "left": {
         "selectComponentId": "xr-standard-trigger",
         "components": {
-            "xr-standard-trigger": { "type": "trigger" },
-            "xr-standard-squeeze": { "type": "squeeze" },
-            "xr-standard-thumbstick": { "type": "thumbstick" },
-            "a-button": { "type": "button" },
-            "b-button": { "type": "button" },
-            "thumbrest": { "type": "button" }
+            "xr-standard-trigger": {
+                "type": "trigger",
+                "gamepadIndices": {
+                    "button": 0
+                },
+                "rootNodeName": "xr_standard_trigger",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-squeeze": {
+                "type": "squeeze",
+                "gamepadIndices": {
+                    "button": 1
+                },
+                "rootNodeName": "xr_standard_squeeze",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-thumbstick": {
+                "type": "thumbstick",
+                "gamepadIndices": {
+                    "button": 3,
+                    "xAxis": 2,
+                    "yAxis": 3
+                },
+                "rootNodeName": "xr_standard_thumbstick",
+                "visualResponses": {
+
+                }
+            },
+            "x-button": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 4
+                },
+                "rootNodeName": "x_button",
+                "visualResponses": {
+
+                }
+            },
+            "y-button": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 5
+                },
+                "rootNodeName": "y_button",
+                "visualResponses": {
+
+                }
+            },
+            "thumbrest": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 6
+                },
+                "rootNodeName": "thumbrest",
+                "visualResponses": {
+
+                }
+            }
         },
-        "gamepad": {
-            "mapping": "xr-standard",
-            "buttons": [
-                "xr-standard-trigger",
-                "xr-standard-squeeze",
-                null,
-                "xr-standard-thumbstick",
-                "a-button",
-                "b-button",
-                "thumbrest"
-            ],
-            "axes": [
-                null,
-                null,
-                { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
-                { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
-            ]
-        }
+        "gamepadMapping": "xr-standard",
+        "rootNodeName": "oculus-touch-v2-left",
+        "assetPath": "left.glb"
     },
     "right": {
         "selectComponentId": "xr-standard-trigger",
         "components": {
-            "xr-standard-trigger": { "type": "trigger" },
-            "xr-standard-squeeze": { "type": "squeeze" },
-            "xr-standard-thumbstick": { "type": "thumbstick" },
-            "x-button": { "type": "button" },
-            "y-button": { "type": "button" },
-            "thumbrest": { "type": "button" }
+            "xr-standard-trigger": {
+                "type": "trigger",
+                "gamepadIndices": {
+                    "button": 0
+                },
+                "rootNodeName": "xr_standard_trigger",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-squeeze": {
+                "type": "squeeze",
+                "gamepadIndices": {
+                    "button": 1
+                },
+                "rootNodeName": "xr_standard_squeeze",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-thumbstick": {
+                "type": "thumbstick",
+                "gamepadIndices": {
+                    "button": 3,
+                    "xAxis": 2,
+                    "yAxis": 3
+                },
+                "rootNodeName": "xr_standard_thumbstick",
+                "visualResponses": {
+
+                }
+            },
+            "a-button": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 4
+                },
+                "rootNodeName": "a_button",
+                "visualResponses": {
+
+                }
+            },
+            "b-button": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 5
+                },
+                "rootNodeName": "b_button",
+                "visualResponses": {
+
+                }
+            },
+            "thumbrest": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 6
+                },
+                "rootNodeName": "thumbrest",
+                "visualResponses": {
+
+                }
+            }
         },
-        "gamepad": {
-            "mapping": "xr-standard",
-            "buttons": [
-                "xr-standard-trigger",
-                "xr-standard-squeeze",
-                null,
-                "xr-standard-thumbstick",
-                "x-button",
-                "y-button",
-                "thumbrest"
-            ],
-            "axes": [
-                null,
-                null,
-                { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
-                { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
-            ]
-        }
+        "gamepadMapping": "xr-standard",
+        "rootNodeName": "oculus-touch-v2-right",
+        "assetPath": "right.glb"
     }
 };
 
@@ -76,54 +158,142 @@ const OculusTouchLegacyLayouts: IMotionControllerLayoutMap = {
     "left": {
         "selectComponentId": "xr-standard-trigger",
         "components": {
-            "xr-standard-trigger": { "type": "trigger" },
-            "xr-standard-squeeze": { "type": "squeeze" },
-            "xr-standard-thumbstick": { "type": "thumbstick" },
-            "a-button": { "type": "button" },
-            "b-button": { "type": "button" },
-            "thumbrest": { "type": "button" }
+            "xr-standard-trigger": {
+                "type": "trigger",
+                "gamepadIndices": {
+                    "button": 1
+                },
+                "rootNodeName": "xr_standard_trigger",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-squeeze": {
+                "type": "squeeze",
+                "gamepadIndices": {
+                    "button": 2
+                },
+                "rootNodeName": "xr_standard_squeeze",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-thumbstick": {
+                "type": "thumbstick",
+                "gamepadIndices": {
+                    "button": 0,
+                    "xAxis": 0,
+                    "yAxis": 1
+                },
+                "rootNodeName": "xr_standard_thumbstick",
+                "visualResponses": {
+
+                }
+            },
+            "x-button": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 3
+                },
+                "rootNodeName": "x_button",
+                "visualResponses": {
+
+                }
+            },
+            "y-button": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 4
+                },
+                "rootNodeName": "y_button",
+                "visualResponses": {
+
+                }
+            },
+            "thumbrest": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 5
+                },
+                "rootNodeName": "thumbrest",
+                "visualResponses": {
+
+                }
+            }
         },
-        "gamepad": {
-            "mapping": "",
-            "buttons": [
-                "xr-standard-thumbstick",
-                "xr-standard-trigger",
-                "xr-standard-squeeze",
-                "a-button",
-                "b-button",
-                "thumbrest"
-            ],
-            "axes": [
-                { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
-                { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
-            ]
-        }
+        "gamepadMapping": "xr-standard",
+        "rootNodeName": "oculus-touch-v2-left",
+        "assetPath": "left.glb"
     },
     "right": {
         "selectComponentId": "xr-standard-trigger",
         "components": {
-            "xr-standard-trigger": { "type": "trigger" },
-            "xr-standard-squeeze": { "type": "squeeze" },
-            "xr-standard-thumbstick": { "type": "thumbstick" },
-            "x-button": { "type": "button" },
-            "y-button": { "type": "button" },
-            "thumbrest": { "type": "button" }
+            "xr-standard-trigger": {
+                "type": "trigger",
+                "gamepadIndices": {
+                    "button": 1
+                },
+                "rootNodeName": "xr_standard_trigger",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-squeeze": {
+                "type": "squeeze",
+                "gamepadIndices": {
+                    "button": 2
+                },
+                "rootNodeName": "xr_standard_squeeze",
+                "visualResponses": {
+
+                }
+            },
+            "xr-standard-thumbstick": {
+                "type": "thumbstick",
+                "gamepadIndices": {
+                    "button": 0,
+                    "xAxis": 0,
+                    "yAxis": 1
+                },
+                "rootNodeName": "xr_standard_thumbstick",
+                "visualResponses": {
+
+                }
+            },
+            "a-button": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 3
+                },
+                "rootNodeName": "a_button",
+                "visualResponses": {
+
+                }
+            },
+            "b-button": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 4
+                },
+                "rootNodeName": "b_button",
+                "visualResponses": {
+
+                }
+            },
+            "thumbrest": {
+                "type": "button",
+                "gamepadIndices": {
+                    "button": 5
+                },
+                "rootNodeName": "thumbrest",
+                "visualResponses": {
+
+                }
+            }
         },
-        "gamepad": {
-            "mapping": "",
-            "buttons": [
-                "xr-standard-thumbstick",
-                "xr-standard-trigger",
-                "xr-standard-squeeze",
-                "x-button",
-                "y-button",
-                "thumbrest"
-            ],
-            "axes": [
-                { "componentId": "xr-standard-thumbstick", "axis": "x-axis" },
-                { "componentId": "xr-standard-thumbstick", "axis": "y-axis" }
-            ]
-        }
+        "gamepadMapping": "xr-standard",
+        "rootNodeName": "oculus-touch-v2-right",
+        "assetPath": "right.glb"
     }
 };
 
@@ -164,7 +334,7 @@ export class WebXROculusTouchMotionController extends WebXRAbstractMotionControl
 
     protected _processLoadedModel(_meshes: AbstractMesh[]): void {
 
-        const isQuest = this._isQuest();
+        /*const isQuest = this._isQuest();
         const triggerDirection = this.handness === 'right' ? -1 : 1;
 
         this.layout.gamepad!.buttons.forEach((buttonName) => {
@@ -214,7 +384,7 @@ export class WebXROculusTouchMotionController extends WebXRAbstractMotionControl
                     }
                 }, undefined, true);
             }
-        });
+        });*/
     }
 
     protected _getFilenameAndPath(): { filename: string; path: string; } {

+ 148 - 0
src/Cameras/XR/motionController/webXRProfiledMotionController.ts

@@ -0,0 +1,148 @@
+import { AbstractMesh } from '../../../Meshes/abstractMesh';
+import { WebXRAbstractMotionController, IMotionControllerProfile, IMotionControllerMeshMap } from './webXRAbstractController';
+import { Scene } from '../../../scene';
+import { SceneLoader } from '../../../Loading/sceneLoader';
+import { WebXRMotionControllerManager } from './webXRMotionControllerManager';
+import { Mesh } from '../../../Meshes/mesh';
+import { Axis, Space } from '../../../Maths/math.axis';
+
+export class WebXRProfiledMotionController extends WebXRAbstractMotionController {
+    public profileId: string;
+
+    private _buttonMeshMapping: {
+        [buttonName: string]: {
+            mainMesh: AbstractMesh;
+            states: {
+                [state: string]: IMotionControllerMeshMap
+            }
+        }
+    } = {};
+    constructor(scene: Scene, xrInput: XRInputSource, _profile: IMotionControllerProfile) {
+        super(scene, _profile.layouts[xrInput.handedness || "none"], xrInput.gamepad as any, xrInput.handedness);
+        this.profileId = _profile.profileId;
+    }
+
+    protected _getFilenameAndPath(): { filename: string; path: string; } {
+        return {
+            filename: this.layout.assetPath,
+            path: `${WebXRMotionControllerManager.BaseRepositoryUrl}/profiles/${this.profileId}/`
+        };
+    }
+    protected _processLoadedModel(meshes: AbstractMesh[]): void {
+        this.getComponentTypes().forEach((type) => {
+            const componentInLayout = this.layout.components[type];
+            this._buttonMeshMapping[type] = {
+                mainMesh: this._getChildByName(this.rootMesh!, componentInLayout.rootNodeName),
+                states: {}
+            };
+            Object.keys(componentInLayout.visualResponses).forEach((visualResponseKey) => {
+                const visResponse = componentInLayout.visualResponses[visualResponseKey];
+                if (visResponse.valueNodeProperty === "transform") {
+                    this._buttonMeshMapping[type].states[visualResponseKey] = {
+                        valueMesh: this._getChildByName(this.rootMesh!, visResponse.valueNodeName!),
+                        minMesh: this._getChildByName(this.rootMesh!, visResponse.minNodeName!),
+                        maxMesh: this._getChildByName(this.rootMesh!, visResponse.maxNodeName!)
+                    };
+                } else {
+                    // visibility
+                    this._buttonMeshMapping[type].states[visualResponseKey] = {
+                        valueMesh: this._getChildByName(this.rootMesh!, visResponse.valueNodeName!)
+                    };
+                }
+            });
+        });
+    }
+    protected _setRootMesh(meshes: AbstractMesh[]): void {
+        this.rootMesh = new Mesh(this.profileId + " " + this.handness, this.scene);
+        this.rootMesh.isPickable = false;
+        let rootMesh;
+        // Find the root node in the loaded glTF scene, and attach it as a child of 'parentMesh'
+        for (let i = 0; i < meshes.length; i++) {
+            let mesh = meshes[i];
+
+            mesh.isPickable = false;
+
+            if (!mesh.parent) {
+                // Handle root node, attach to the new parentMesh
+                rootMesh = mesh;
+            }
+        }
+
+        if (rootMesh) {
+            rootMesh.setParent(this.rootMesh);
+        }
+        let min = {
+            x: 0,
+            y: 0,
+            z: 0
+        };
+        let max = {
+            x: 0,
+            y: 0,
+            z: 0
+        };
+        this.rootMesh.getChildMeshes().forEach((mesh) => {
+            var bi = mesh.getBoundingInfo();
+            var minimum = bi.boundingBox.minimumWorld;
+            var maximum = bi.boundingBox.maximumWorld;
+
+            if (minimum.x < min.x) {
+                min.x = minimum.x;
+            }
+            if (minimum.y < min.y) {
+                min.y = minimum.y;
+            }
+            if (minimum.z < min.z) {
+                min.z = minimum.z;
+            }
+
+            if (maximum.x > max.x) {
+                max.x = maximum.x;
+            }
+            if (maximum.y > max.y) {
+                max.y = maximum.y;
+            }
+            if (maximum.z > max.z) {
+                max.z = maximum.z;
+            }
+        });
+
+        console.log(min, max, { x: max.x + min.x, y: max.y + min.y, z: max.z + min.z });
+        const center = Mesh.Center(this.rootMesh.getChildMeshes());
+        console.log(center);
+        this.rootMesh.position.subtractInPlace(center.scaleInPlace(2));
+        this.rootMesh.position.z *= -1;
+
+        this.rootMesh.rotate(Axis.Y, Math.PI, Space.WORLD);
+        this.rootMesh.rotate(Axis.X, -Math.PI / 4, Space.WORLD);
+    }
+    protected _updateModel(_xrFrame: XRFrame): void {
+        if (this.disableAnimation) {
+            return;
+        }
+        this.getComponentTypes().forEach((type) => {
+            const component = this.getComponent(type);
+            const meshes = this._buttonMeshMapping[type];
+            const componentInLayout = this.layout.components[type];
+            Object.keys(componentInLayout.visualResponses).forEach((visualResponseKey) => {
+                const visResponse = componentInLayout.visualResponses[visualResponseKey];
+                let value = component.value;
+                if (visResponse.componentProperty === "xAxis") {
+                    value = component.axes.x;
+                } else if (visResponse.componentProperty === "yAxis") {
+                    value = component.axes.y;
+                }
+                if (visResponse.valueNodeProperty === "transform") {
+                    this._lerpTransform(meshes.states[visualResponseKey], value, visResponse.componentProperty !== "button");
+                } else {
+                    // visibility
+                    meshes.states[visualResponseKey].valueMesh.visibility = value;
+                }
+            });
+        });
+    }
+    protected _getModelLoadingConstraints(): boolean {
+        return SceneLoader.IsPluginForExtensionAvailable(".glb");
+    }
+
+}

+ 29 - 4
src/Cameras/XR/webXRController.ts

@@ -17,6 +17,17 @@ export interface IWebXRControllerOptions {
      * This can be used when creating your own profile or when testing different controllers
      */
     forceControllerProfile?: string;
+
+    /**
+     * Do not load the controller mesh, in case a different mesh needs to be loaded.
+     */
+    doNotLoadControllerMesh?: boolean;
+
+    /**
+     * Should the controller mesh be animated when a user interacts with it
+     * The pressed buttons / thumbstick and touchpad animations will be disabled
+     */
+    disableMotionControllerAnimation?: boolean;
 }
 
 /**
@@ -39,6 +50,11 @@ export class WebXRController {
     public motionController?: WebXRAbstractMotionController;
 
     /**
+     * Observers registered here will trigger when a motion controller profile was assigned to this xr controller
+     */
+    public onMotionControllerProfileLoaded = new Observable<WebXRAbstractMotionController>();
+
+    /**
      * Event that fires when the controller is removed/disposed
      */
     public onDisposeObservable = new Observable<{}>();
@@ -71,10 +87,18 @@ export class WebXRController {
 
         // for now only load motion controllers if gamepad available
         if (this.inputSource.gamepad) {
-            this.motionController = WebXRMotionControllerManager.GetMotionControllerWithXRInput(inputSource, _scene, this._options.forceControllerProfile);
-            // if the model is loaded, do your thing
-            this.motionController.onModelLoadedObservable.addOnce(() => {
-                this.motionController!.rootMesh!.parent = this.pointer;
+            WebXRMotionControllerManager.GetMotionControllerWithXRInput(inputSource, _scene, this._options.forceControllerProfile).then((motionController) => {
+                this.motionController = motionController;
+                this.onMotionControllerProfileLoaded.notifyObservers(motionController);
+                // should the model be loaded?
+                if (!this._options.doNotLoadControllerMesh) {
+                    this.motionController.loadModel().then((success) => {
+                        if (success) {
+                            this.motionController!.rootMesh!.parent = this.pointer;
+                            this.motionController!.disableAnimation = !!this._options.disableMotionControllerAnimation;
+                        }
+                    });
+                }
             });
         }
     }
@@ -148,6 +172,7 @@ export class WebXRController {
         if (this.motionController) {
             this.motionController.dispose();
         }
+        this.onMotionControllerProfileLoaded.clear();
         this.pointer.dispose();
         this.onDisposeObservable.notifyObservers({});
     }

+ 13 - 4
src/Cameras/XR/webXRInput.ts

@@ -4,6 +4,7 @@ import { IDisposable } from "../../scene";
 import { WebXRController } from './webXRController';
 import { WebXRSessionManager } from './webXRSessionManager';
 import { WebXRCamera } from './webXRCamera';
+import { WebXRMotionControllerManager } from './motionController/webXRMotionControllerManager';
 
 /**
  * The schema for initialization options of the XR Input class
@@ -20,6 +21,13 @@ export interface IWebXRInputOptions {
      * Profiles are defined here - https://github.com/immersive-web/webxr-input-profiles/
      */
     forceInputProfile?: string;
+
+    /**
+     * Do not send a request to the controlle repository to load the profile.
+     *
+     * Instead, use the controllers available in babylon itself.
+     */
+    useOnlyLocalControllers?: boolean;
 }
 /**
  * XR input used to track XR inputs such as controllers/rays
@@ -73,6 +81,10 @@ export class WebXRInput implements IDisposable {
                 controller.updateFromXRFrame(frame, this.xrSessionManager.referenceSpace);
             });
         });
+
+        if (!this.options.useOnlyLocalControllers) {
+            WebXRMotionControllerManager.UpdateProfilesList();
+        }
     }
 
     private _onInputSourcesChange = (event: XRInputSourceChangeEvent) => {
@@ -84,11 +96,8 @@ export class WebXRInput implements IDisposable {
         let sources = this.controllers.map((c) => { return c.inputSource; });
         for (let input of addInputs) {
             if (sources.indexOf(input) === -1) {
-                let controller = new WebXRController(this.xrSessionManager.scene, input, { forceControllerProfile: this.options.forceInputProfile });
+                let controller = new WebXRController(this.xrSessionManager.scene, input, { forceControllerProfile: this.options.forceInputProfile, doNotLoadControllerMesh: this.options.doNotLoadControllerMeshes });
                 this.controllers.push(controller);
-                if (!this.options.doNotLoadControllerMeshes && controller.motionController) {
-                    controller.motionController.loadModel();
-                }
                 this.onControllerAddedObservable.notifyObservers(controller);
             }
         }