WebXR changing scenes

Hi @RaananW and all,

what would be the best way to achieve scene switching while in XR? Lets say I have two scenes and both of them have a default XR experience and the user can press a button to enable XR. Now if the user is in scene 1 and goes into XR how can I navigate to scene 2 and enable its XR experience?

I think I have to exit scene 1’s XR and then enter scene 2’s XR and there may be a way of doing this with promises etc but I can’t get it going. What I currently have (which does not work) is something like this before the renderLoop…
if (current) {
if (current.inXR()) {
current.enableXR(false).then( (r) => {
if (!next.inXR())
next.enableXR(true);
});
}
}

where inXR just checks the WebXRState and enableXR is something like…

    enableXR(enable) {
    let xr = this.scene.XR; //this is the helper returned from the createDefaultXRExperienceAsync
    if (!xr) return;if (enable) {
        if (!xr.isOn) { //this flag is updated during onStateChangedObservable
            return xr.baseExperience.enterXRAsync("immersive-vr", "local-floor");
        }
    }
    else {
        if (xr.isOn) {
            return xr.baseExperience.exitXRAsync();
        }
    }
    // not sure what this should be
    return "done";
}

but I haven’t got my mind around all the promise stuff and not sure how to recreate this in a PG.

1 Like

Update:
I have gotten a bit further and now when switching scenes I can stop the XR on scene 1 but when I call enterXRAsync on scene 2 I get this error:
Failed to execute ‘requestSession’ on ‘XRSystem’: The requested session requires user activation.

so it seems it must be driven by a user interaction. Maybe ther eis a way to workaround that with sessionManager but I am doubtful as it is a security thing :frowning:
Close but not yet.

This seems to be the relevant doc: WebXR permissions and security - Web APIs | MDN
My issue is that it is wrapped up in a react app and the user click sets a state indicating what scene I want and then another component responds to that. It fails the security test because that code is outside the user triggered event handler. Only thing I can think of is refactoring the code somehow.

Update:
After refactoring and calling the enterXRAsync from within a user event callback I am still getting the error so there must be something else preventing it. :frowning:

You do require a user interaction to start a UX session, unless you load a new URL that supports the sessiongranted event (which babylon natively supports). Would you be able to share a simple version of the implementation so that I can debug the behavior?

Ok, i will try and setup a minimal example. It may have something to do with how the event is being processed so stripping it out from react etc might help make it clearer.

1 Like

The theory i am working on now is that i am attempting to start the XR session from a callback triggered by a GUI button click - which i assume is not regarded by the DOM as a user iteraction. When starting the XR session initially in scene 1 the user clicks on a div element in the DOM tree which is seen as a user interaction.

I was going to setup a simple PG to show/test this, ie start XR from a GUI button but unfortunately half of my keyboard has died and i have to go get another one (typing this on my phone).

Ok, scratch that theory. I created a simple PG that shows you can start XR from a GUI button.
PG: https://playground.babylonjs.com/#F41V6N#381

(I meant to start a new PG but inadvertantly created a new version of the existing sample - I hope that is ok - I couldn’t see a way to delete a PG that I had created)

Back to the drawing board, I will try to setup a minimal (not)working example.

Ok, so this PG shows that it does work (although the location of the hit regions of the button is hard to hit)
PG: https://playground.babylonjs.com/#ASPKRW#4

I can switch between scenes while in XR. The hit region for the switch button ends up just to left of centre screen (on the bottom beside the “right” toggle). The hit region for the toggle is on the “left” toggle button.

So, end result, there must be something about my project that stops the switching.

It must be related to your event propagation. features requiring user interactions are extra sensitive about that. Try using as little async functions as possible, make sure there is no delay between a user interaction and the enterXR command. If you are able to follow up the event down the rabbit hole of bubbling fun, try finding out if the event is trusted (i.e. was generated by a user action) - Event.isTrusted - Web APIs | MDN . This must be true (and is read only, setting it yourself won’t work).
Sorry I can’t help further… Will be great to know if you managed to solve it and what was the issue!

Ok, I have made some progress and have gotten passed that error. I was trying to do the enterXR inside a .then block returned from the exitXR. I changed that and now mimic exactly what I did in the PG but now I get an error saying I can’t start an XR session because there is already one active. Not sure why this did not happen in the PG (dumb luck maybe) because I am doing the same thing. Presumably just a timing thing. Now the question is how to wait for the previous XR Session to finish before starting the new one…without using asyncs.

Ahh…except the PG is using 5.0 but my project is 4.2…

Update:
I have tested this on an actual headset now (rather than the emulator) and I am getting the same result as in my project, ie when I switch scenes when in XR the exitXR is called but when calling enterXR on the new scene I get an error saying that there is already an XR Session (viewable via remote chrome inpect dev tools console)
The new scene stays in non-XR mode. So I need to find a way to wait for the previous session to end witout running foul of the non-trusted error from previously.

PG: https://playground.babylonjs.com/#ASPKRW#6

1 Like

I am pretty sure this is because you are starting an XR session at the same loop (or even frame) that you are ending it. exit and enter XR are both async operations, the session only truly ends when the promise is fulfilled, but you are running them outside of async/await and not as promises.

I believe a simpler approach would be to stay at the same scene, but use asset containers to clear the scene and create a “new” one from a different asset container. I remember a different discussion on the forum that had a similar question. XR depends on the scene for a few reasons - the previous camera is being used to set the XR camera’s position, there are a few assets that are created when XR initializes, and motion controllers need a scene to load correctly. They are currently tightly coupled. I am going to invest a bit of time to see if we can separate the two, but that won’t happen in the near future.

Another solution would really be having different scenes with a different URL. I really wonder if hash change or the web history API (pushState probably?) will trigger the sessiongranted event (not too sure). This is already supported by babylon.

Thanks @RaananW, i suspected that.
What you describe with asset containers is similar to what I have done in the past in Unity. There I was able to have a bootstrap scene that setup the player and any persistent objects and then load scenes additively. I just wasn’t aware of the asset container approach when i started on this BJS project. I might just take the ability to change scenes while in XR off the table. It was something I thought would be nice to add but I probably don’t have the time to refactor the project now. I work for a University which has been hit hard by covid and I have just found out my position will not be continuing and I only have a few weeks left to tidy up a number of projects, this being one of them. I wont get to a “finished” state but hopefully a “workable” one. Thanks for your assistance, it is really appreciated.