Haptic XR Feedback and buttons

In XR games that are played on VR googles, it would be nice to use also haptic feedback and get callback on button presses.
Is there an interface to this functionality? I saw a pull request but could not find information on how to e.g. activate the vibration of the grabbing hand or how to get alerted to button presses.
@RaananW, can you help with using these features?

You can use heptic feedback using the API provided on the controller - WebXRAbstractMotionController | Babylon.js Documentation (babylonjs.com) this will either work (if your controller supports it) or will fail silently.

1 Like

Thanks! This is great. Is there an example playground?
Is it correct to assume that you do not need to create the actual motion controller object, but access it via the input to the callback function:
‘xr.input.onControllerAddedObservable.add’
as shown in this playgrounds:

Are there other playgrounds to look at?

1 Like

One bad news is that the WebXR emulator under Windows 10 does not seem to present itself as a “game controller” even though it has all the neccessary gizmos. See this code:

            xr.input.onControllerAddedObservable.add((controller => {
                console.log(`Controller is: ${controller}`);
                console.log(`Controller added: ${controller.gamepadController}`);
                if (controller.gamepadController) {
                    console.log(`Controller added. Profile: ${controller.gamepadController.profileId}`);
                for (var key in controller.gamepadController.components) {
                    controller.gamepadController.components[key].onButtonStateChanged.add(() => {
                        console.log(`Button ${key} value changed`);
                    });
                    controller.gamepadController.components[key].onAxisValueChanged.add(() => {
                        console.log(`Axis ${key} value changed`);
                    });
                    }
                }
            }));

Same with the aforementioned workspace.

Yes, this is the right way to deal with that. There are others (if you know it’s already constructed you can get it dorectly from the WebXR Input class), but this is the better one. this doc page discusses the process: WebXR Controllers Support | Babylon.js Documentation

Are you referring to the chrome extension? or to the microsoft mixed reality app with emulated headset?

To be honest, I don’t really know, to be honest. But I guess that it is the microsoft WebXR tool. I set it to “Quest 2”, yet controller.gamepadController is already “undefined” in the code above. When running it on my Pico 4, I also got no response, but since its hard to debug on the device, I cannot for sure pin down the reason. Yet most probably it is also no “gamepadController” being present.

TThis is probably not the microsoft tool, as you can’t set it to be quest 2. microsoft have different device in their emulator.

The chrome-based emulator does generate a gamepad object:

image

So I am not sure what emulator you are using

Thanks. This was very helpful. Apparently I was using completely wrong vocabulary here.

xr.input.controllers[0].inputSource.gamepad.buttons[0].pressed

works nicely and I can monitor state changes. The code above can be modified to

            xr.input.onControllerAddedObservable.add((controller => {
                console.log(`Controller is: ${controller}`);
                console.log(`Controller InputSource is: ${controller.inputSource}`);
                console.log(`Controller added: ${controller.inputSource.gamepad}`);
                console.log(`Controller buttons: ${controller.inputSource.gamepad.buttons}`);
                console.log(`Controller haptic: ${controller.inputSource.gamepad.hapticActuators}`);
                var gamepadController = controller.inputSource.gamepad;
                // Object.keys(controller.inputSource).forEach((prop)=> console.log(prop));
                Object.keys(controller.inputSource.gamepad).forEach((prop)=> console.log(prop));
                if (gamepadController != undefined) {
                    console.log(`Controller added. Profile ID: ${gamepadController.profileId}`);
                n=0;

but then the following does NOT work:

                for (var button in gamepadController.buttons) {  // components                

It looks like I somehow hooked into the wrong interface here and the observable is not an observable. By the way, how did you get such a nice interface to the various properties? In the inspector of the playground, I cannot see these.

Babylon masks this entire interface for you, so you don’t have to deal with the different buttons and axes in the gamepad object. This is documented here - WebXR Controllers Support | Babylon.js Documentation (babylonjs.com)

I am not sure exactly what you are trying to achieve TBH. Want to share a playground so I can have some better context?

1 Like

This is perfect. The example playground in there

works like a charm. Only the vibrate part in missing, but I can probably figure that one out somehow.

2 Likes

For anyone looking for how to do this, the pulse function is available on the controller’s motionController.

// Haptic feedback when the trigger is pressed
xr.input.onControllerAddedObservable.add(controller => {
    controller.onMotionControllerInitObservable.add(motionController => {
        // motionController can be accessed through controller.motionController

        // We need a state for tracking if the trigger was pressed, 
        // as the onButtonStateChangedObservable triggers every frame, 
        // while the trigger is lightly pressed
        let wasPressed = false;

        motionController.getComponent("xr-standard-trigger").onButtonStateChangedObservable.add(component => {
            if (component.pressed && !wasPressed) {
                motionController.pulse(0.05, 100);
            }
            wasPressed = component.pressed;
        });
    });
});

It’s worth noting that the pulse function will override a previous call, even if the previous call had a longer duration or a higher strength.

So, if you have multiple classes that applies haptic feedback. You can use timeout, to ensure that haptic feedback that has a higher priority is always applied.

// Sends haptic feedback one frame later
setTimeout(() => motionController.pulse(0.05, 150));
1 Like