Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automatically requestPresent / enter VR on vrdisplayactivate event to support link traversal. #13105

Closed
4 of 11 tasks
ngokevin opened this issue Jan 13, 2018 · 9 comments
Closed
4 of 11 tasks

Comments

@ngokevin
Copy link
Contributor

Description of the problem

Firefox (and probably other browser) supports onvrdisplayactivate event to automatically enter VR when navigating from VR site to VR site (link traversal) or refreshing.

onvrdisplayactivate: A user agent MAY dispatch this event type to indicate that something has occured which suggests the VRDisplay should be presented to. For example, if the VRDisplay is capable of detecting when the user has put it on, this event SHOULD fire when they do so with the reason "mounted".

Most three.js-based WebVR applications don't handle this making it so we have to click an Enter VR button every time. The three.js WebVR renderer/manager should automatically start presenting if it detects onvrdisplayactivate event from the browser. A-Frame currently handles this such that link traversal works for all A-Frame sites.

Something like:

window.addEventListener('vrdisplayactivate', function () {
  device.requestPresent([{source: renderer.domElement}]);
});
Three.js version
  • Dev
Browser

WebVR supported browser.

  • All of them
  • Chrome
  • Firefox
  • Internet Explorer
OS
  • All of them
  • Windows
  • macOS
  • Linux
  • Android
  • iOS
Hardware Requirements (graphics card, VR Device, ...)

VR headset (Vive, Rift)

@vt5491
Copy link
Contributor

vt5491 commented Jan 19, 2018

Since I am currently working on a project that requires this very ability, this discussion is very apropos to me and indeed the information supplied from @ngokevin was the catalyst to my working solution. The project intends to be a gallery of three.js examples that have been "lifted" (or 'vrized' ) to vr-mode. The front-end is in a-frame, with links to lifted three-js examples. Thus I need the ability to retain vr-mode across:

  • aframe to lifted three.js script.
  • lifted three.js script to aframe.
  • lifted three.js script to lifted three.js script.

The only mode that was working out of the box was lifted three.js script to a-frame, since aframe has the 'vrdisplayactivate' handler already. In order to get the other modes working I initially just added a listener to each script:

window.addEventListener('vrdisplayactivate', function () {  
    renderer.vr.getDevice().requestPresent([{ source: renderer.domElement }])  
});  

This worked, but wasn't ideal because:

  1. I have to add it manually to every script.
  2. It requires I extract out the name of the renderer variable i.e. hard-coding 'renderer' won't work if the actual renderer is called "myGreatVRRenderer" or something.

This is where @ngokevin's suggestion that it be added to the three.js system code makes a lot of sense.

Investigating, I determined the best module to put this in is 'examples/js/vr/WebVR.js', which already has several other vr event handlers. The full updated module in my project can be seen here, but here is some code snippets.

Basically after the event handler for 'vrdisplaypresentchange' add the following:

			window.addEventListener( 'vrdisplaypresentchange', function ( event ) {

				button.textContent = event.display.isPresenting ? 'EXIT VR' : 'ENTER VR';

			}, false );
			//newcode-start
			window.addEventListener( 'vrdisplayactivate', function ( event ) {

				event.display.requestPresent([{ source: renderer.domElement }]);

			}, false );
			//newcode-end

			navigator.getVRDisplays()
				.then( function ( displays ) {

					if ( displays.length > 0 ) {

						showEnterVR( displays[ 0 ] );

					} else {

There are some minor nuances about this version:

  1. It gets the display device directly from the event itself (an instance of VRDisplayEvent), instead of using 'renderer.vr.getDevice()'
  2. Since this event handler is actually defined inside the function 'createButton', it's a closure and thus 'renderer' is a free-variable that will be mapped to the actual renderer var name when 'createButton' is called e.g container.appendChild(WEBVR.createButton(myGreatVRRender));, so the code should work with whatever name has been assigned to the renderer.

This works in retaining vr-mode in all three use cases with an Oculus Rift on Firefox (and, yes, I did remove the client eventHandler). Unfortunately, Chrome Canary is not working for me with the OR (in spite of the fact that it supposedly should with the OpenVR support), so I can't test with Chrome. I do have an HTC Vive, but I won't be able to test that until next week.

@vt5491
Copy link
Contributor

vt5491 commented Jan 23, 2018

Unless I hear any objections, I'm going to proceed with a pull request on this.

I created a glitch that can be used for testing.

The glitch starts off with an aframe scene with links to two pure-three.js vr scripts ('webgl_geometry_cube.html', and 'webgl_mirror.html', lifted with "vr-ize"). WASD keys are in effect. From any transferred scene press 'b' to go back to the prior scene. When you are in a three.js scene you can press 't' to transfer to the other three.js script.

You can try the following use cases:

  • Enter vr mode on a-frame, and then use the VR controller to select one of the three.js links. Verify that the vr-mode is transferred to the new link. Verify you see message ('vt:WebVR: now driving vrdisplayactivate') on the log proving the 'vrdisplayactiveate' was driven (Note: this console message will not be in the pull request).

  • press 'b' (back) or 'h' (home) to return to a-frame. Verify vr-mode is still in effect (this use-case has always worked, so you're really just verifying that nothing is broken)

  • Enter a three.js scene in vr-mode and press 't'. You will transfer to the other three.js scene, bypassing a-frame. Verify that vr-mode is carried over.

  • Test transfrering when not in vr-mode. In this case, the 'vrdisplayactive' should not be driven and you should verify that vr-mode is not activated.

Basically, just play around transferring among the scenes and make sure everything works as expected.

Note: if you have a keypress handler on your browser such as vimium, you'll want to add a rule to exclude this site, otherwise vimium will intercept all the key presses (I was having this problem so I know).

vt5491 added a commit to vt5491/three.js that referenced this issue Jan 23, 2018
Propagate VR-mode upon link traversal so the the user doesn't have
to manually click on the 'enter VR' button each time.
@dustinkerstein
Copy link

FYI - something in my app didn't like having this event listener inside WebVR.js. It wouldn't automatically invoke VR mode on page launch with a Rift connected, though it would work for page reloads if I was already in VR.

Once I moved the "vrdisplayactivate" listener outside of WebVR.js I was able to enter VR on page load. My code now looks like:

renderer = new THREE.WebGLRenderer({antialiasing: false, alpha: false });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
container.appendChild(renderer.domElement);
document.body.appendChild( WEBVR.createButton( renderer ) );
window.addEventListener('vrdisplayactivate',  () => {renderer.vr.getDevice().requestPresent( [ { source: renderer.domElement } ] ); }, false);

Figured I'd post here as it's possible this would affect other sites, but I'm not entirely sure why it was happening.

@vt5491
Copy link
Contributor

vt5491 commented Mar 15, 2018

... It wouldn't automatically invoke VR mode on page launch with a Rift connected, though it would work for page reloads if I was already in VR.

It's my understanding that the webVr spec requires a user gesture in order to first enter VR mode. That means clicking a button with a mouse, or, in the case of a-frame, optionally pressing the "f" key. From the webvr 1.1 spec:

If requestPresent() is called outside of an engagement gesture, the promise MUST be rejected unless the VRDisplay was already presenting.

I do realize that "putting on the HMD" could also be considered a user gesture, and indeed the OR store, and the VIVE front-end acknowledge that as a suitable event to cause themselves to activate.

But I've never known a web browser to auto-invoke VR without clicking a mouse of pressing a key first (don't get me wrong, I would personally like this ability). I think the reasoning is security as well as people just getting confused e.g. popping into VR unexpectedly, so you make them explicitly request VR mode, at least initially. But once they're in VR mode, linking and refreshing -- that's where 'vrdisplayactivate' plays it's role. So the linking and refreshing is working as you say, and that's what's this fix is intended to do.

So I guess I'm confused. Are you saying that VR-mode should auto-activate upon entering a VR-enabled page, and that by adding

window.addEventListener('vrdisplayactivate', () => {renderer.vr.getDevice().requestPresent( [ { source: renderer.domElement } ] ); }, false);

directly to the script this gives you that ability? I added this line to an example I have, and this made no difference -- I still had to press the "Enter VR" button to enter VR mode. So I am unable to reproduce your problem and this simply seems like the browser working as expected vis a vis the WebVR spec to me.

Of course there's now the WebXR spec, which I'm not familiar with. I don't know if that allows auto-enter of VR.

What browser are you using: Firefox, firefox nightly, Chromium Canary?

@dustinkerstein
Copy link

dustinkerstein commented Mar 15, 2018

In my testing with Firefox Nightly, if the Rift is connected (with Oculus Home running) I am put into VR mode immediately when going to https://webvr.info/samples/03-vr-presentation.html without any user interaction. This is in fact the behavior I'm looking for, and is needed to handle inter-site link traversal in VR. Whether that's what the WebVR 1.1 spec says is supposed to happen, I can't say... But it's definitely something I am personally looking to enable.

Are you saying that VR-mode should auto-activate upon entering a VR-enabled page, and that by adding

window.addEventListener('vrdisplayactivate', () => {
    renderer.vr.getDevice().requestPresent( [ { source: renderer.domElement } ] );
}, false);

Yep, that change is what allowed my site to auto-invoke VR without any user interaction.

I haven't been able to test any of this in Chrome as I've had no luck in getting my Rift to cooperate with Chrome recently.

@dustinkerstein
Copy link

Though I just had someone in my office quickly test https://webvr.info/samples/03-vr-presentation.html with their Rift + Nightly and it didn't auto-invoke VR... So I guess I'll need to get my Rift setup again and do some more testing.

@autuus
Copy link

autuus commented Feb 7, 2019

display.requestPresent( [ { source: renderer.domElement } ] )
Promise { : "rejected" }
uncaught exception: undefined

@cstensonpv
Copy link

cstensonpv commented Mar 1, 2019

Do we have a working sample for this?

I can't get it to work.

Seems like https://vr.with.in/ has solved it ? Tested on my oculus go and if I'm inside VR and click the links I stay in VR

@timothyallan
Copy link

@cstensonpv They don't navigate anywhere, take a look at the network history in your dev tools. Looks like they're just updating the titlebar via history and loading things like a regular SPA does.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants