How to properly detach VR states?

Hello, I am trying to check some feature of the engine in order to decide whether and how to properly advance in some of my case studies.
Right now I am checking for how properly you can toggle VR back and forth and still maintain all the states as expected, ie returning to the same as they were before, and being able to toggle at will and always (without moving the VR headset of course) keep oscillating between the two behaviors.

The dependencies I am using are, engine version: 3.6.0-beta1, artifacts: jme3-core, jme3-desktop, jme3-lwjgl3, jme3-vr.

Here is the source code:

package mk.test;

import com.jme3.app.*;
import com.jme3.input.*;
import com.jme3.input.controls.*;
import com.jme3.material.*;
import com.jme3.scene.*;
import com.jme3.scene.shape.*;
import com.jme3.system.*;
import java.util.*;

import static com.jme3.app.VRConstants.SETTING_ENABLE_MIRROR_WINDOW;
import static com.jme3.app.VRConstants.SETTING_VRAPI;
import static com.jme3.app.VRConstants.SETTING_VRAPI_OPENVR_LWJGL_VALUE;
import static java.util.Optional.empty;
import static java.util.function.Predicate.not;

public class VRToggleTest extends SimpleApplication {

  public static void main(String[] args) {
    new VRToggleTest().start();
  }

  @Override
  public void simpleInitApp() {
    inputManager.addMapping("V", new KeyTrigger(KeyInput.KEY_V));
    inputManager.addListener((ActionListener) (String name, boolean pressed, float timePerFrame) -> {
      if (!pressed)
        VRTOGGLE();
    }, "V");

    var b = new Box(1, 1, 1);
    var geom = new Geometry("Box", b);
    var mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
    geom.setMaterial(mat);
    rootNode.attachChild(geom);
  }

  Optional<VRAppState> vrState = empty();

  private void VRTOGGLE() {
    if (vrState.isEmpty())
      VRON();
    else
      VROFF();
  }

  private void VRON() {
    if (vrState.isEmpty())
      vrState = createVR();

    vrState
      .filter(not(stateManager::hasState))
      .ifPresent(stateManager::attach);
  }

  private void VROFF() {
    vrState.ifPresent(stateManager::detach);
    vrState.ifPresent(VRAppState::cleanup);
    vrState = empty();
  }

  private Optional<VRAppState> createVR() {
    try {
      var vrSettings = new AppSettings(true);
      vrSettings.put(SETTING_VRAPI, SETTING_VRAPI_OPENVR_LWJGL_VALUE);
      vrSettings.put(SETTING_ENABLE_MIRROR_WINDOW, true);
      return Optional.of(new VRAppState(new VREnvironment(vrSettings)));
    } catch (RuntimeException e) {
      return Optional.empty();
    }
  }
}

Sorry needed to mention: it’s not doing what I was expecting it to do.
The first time I return from VR controls are blocked and the camera remains from the last VR position. Then I enable VR again and I can’t see the cube anymore, no idea where the PoV ends up.

Just so i understand your use case. Why do you want to toggle back and forth in and out of VR?

Mainly for user experience and my own standards of quality, I expect things to be properly toggleable.
I am just assuming that there is something missing that I need to do.

Are you suggesting that it is not possible?

I don’t think toggling back and forth is possible with the current VR plugin.

Games I’ve seen that support both VR and PC (which are quite rare to begin with¹) usually present that as launch options rather than toggling mid game. I really would suggest you look at what the use case is for making it toggleable mid game, it may be effort for something end users don’t want if you do it just for the sake of it unless you have a use case.

¹ what makes a good VR game and a good desktop game are usually quite different, both in terms of controls but also pacing

If you are making a VR game you may find this example project useful

It uses a library i wrote, Tamarin, to try to make VR easier in JMonkey. It does a lot of the hand interactions for you and supports OpenVR (which is a relatively modern VR framework, although OpenXR is the new kid on the block)

Possible use cases that just pop in my head:

One example is a game like Demeo. Or any long running game where one player is the host or required to be present otherwise the whole party won’t continue. You need for any reason to put down VR and go back to desktop. Battery reason, dizziness reasons, and so on. However, closing steamvr (or steam) while open vr is still attached to the game will force close the game. Now you lost everything.

More cases involve social VR platforms where longer sessions are also usually common, and people do switch back and forth in VR. I can confirm the use case here because I saw it happening myself, but the problem is the application needs to be started in steamvr to begin with, forcing to attach the process again and risk termination.

Other situations may also be if you don’t want to pump GPU for no reason while switching to normal PC game mode.

And so on…

I may remind just like I tried to explain in the beginning, that I am not making a game right now but exploring the engine for capabilities.

PS. Yes I know of Tamarin, I tried it both with and without before posting, the issue is just the same.

For the Demeo example I’d suggest separate server and client applications (even if one client is running on the same machine as the server), the two have clearly separate concerns and lifecycles and that feels cleaner. Possibly that is the solution to all your use cases, a locally running server that is connected to by either a VR or desktop client?

For non multi-party applications I’d suggest just saving the state and restarting the JME context (which you could probably make semi transparent to the user).

(Enabling and disabling the VRState will toggle VR control, but I don’t think that gives you what you want)

Just for completeness and future readers:

  • With VRAppState#setEnabled(false) → app hangs/crashes.
  • Doing a restart() after disabling/removing VRState → app hangs/crashes.

I remember I managed to make this work on C++ time ago: test-vr/application.cpp at master · raffaeleragni/test-vr · GitHub so I suppose it’s only about the integration.

Could it be related to the detach being pretty much unimplemented, while the attach/initialize does a bunch of stuff that changes the app?

As far as app states go, I make the pretty general assumption that if an app state does not extend BaseAppState that it automatically must have some lifecycle bugs.

It’s certainly possible to mess up lifecycle when subclassing BaseAppState but usually those extending BaseAppState are already thinking about lifecycle (which is the main reason to use it) and those who directly implement AppState or extend the thin AbstractAppState are not thinking beyond “attach”.

I think your assessment is correct.

For those that remember, there used to be a VRApplication. Some effort was put into making VRAppState instead, because that theoretically meant the same launcher application could be used.

In practice, this pretty much meant the functionality was shoved into the appstate, instead.

There’s so much underlying stuff going on to make VR work that it will just not be possible to revert it back (imo). Not the way it’s implemented today.

But I’m often wrong, so anyone is welcome to try :slight_smile: But first check what’s going on in the VREnvironment and VRViewManager classes. VRAppState is just the tip.