JMonkey + VR + Oculus DK2 - Guide?

Ahoy ! …
There wouldn’t be a clear step by step guide for vr with jmonkey would there ?
From searching the forums it seemed like the latest release had that capability, and maven has jme-vr, but I was wondering if there was a starter app or wiki. All wiki references I have seen are 404.
Thanks.


This should be the correct start (I just opened the Wiki and searched for VR)
1 Like

Great ! Thank you … diving in

There are also some really good resources on the forum:


And here is a test VR app that I use when debugging my VR applications as a sort of baseline:

package io.tlf.jme.test;

import com.jme3.app.*;
import com.jme3.app.state.AppState;
import com.jme3.asset.plugins.FileLocator;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.vr.VRTrackedController;
import com.jme3.input.vr.lwjgl_openvr.LWJGLOpenVRTrackedController;
import com.jme3.input.vr.openvr.OpenVR;
import com.jme3.material.Material;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.post.CartoonSSAO;
import com.jme3.post.FilterPostProcessor;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.util.SkyFactory;
import com.jme3.util.VRGUIPositioningMode;
import org.lwjgl.BufferUtils;
import org.lwjgl.openvr.VR;
import org.lwjgl.openvr.VRApplications;
import org.lwjgl.openvr.VRSystem;

import java.io.File;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.logging.Logger;

public class VRTest extends SimpleApplication {

    private static final Logger logger = Logger.getLogger(VRTest.class.getName());

    Spatial observer;
    boolean moveForward, moveBackwards, rotateLeft, rotateRight;
    Material mat;
    Node mainScene;

    VRAppState vrAppState = null;

    public VRTest(AppState... initialStates) {
        super(initialStates);

        vrAppState = getStateManager().getState(VRAppState.class);
    }


    @Override
    public void simpleInitApp() {

        logger.info("Updating asset manager with " + System.getProperty("user.dir"));
        getAssetManager().registerLocator(System.getProperty("user.dir") + File.separator + "assets", FileLocator.class);

        mainScene = new Node("scene");
        observer = new Node("observer");

        Spatial sky = SkyFactory.createSky(getAssetManager(), "Textures/Path.hdr", SkyFactory.EnvMapType.EquirectMap);
        rootNode.attachChild(sky);


        // make the floor according to the size of our play area
        Geometry floor = new Geometry("floor", new Box(1f, 1f, 1f));

        Vector2f playArea = vrAppState.getVREnvironment().getVRBounds().getPlaySize();
        if (playArea == null) {
            // no play area, use default size & height
            floor.setLocalScale(2f, 0.5f, 2f);
            floor.move(0f, -1.5f, 0f);
        } else {
            // cube model is actually 2x as big, cut it down to proper playArea size with * 0.5
            floor.setLocalScale(playArea.x * 0.5f, 0.5f, playArea.y * 0.5f);
            floor.move(0f, -0.5f, 0f);
        }

        mat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        floor.setMaterial(mat);
        rootNode.attachChild(floor);


        // test any positioning mode here (defaults to AUTO_CAM_ALL)
        vrAppState.getVRGUIManager().setPositioningMode(VRGUIPositioningMode.AUTO_OBSERVER_ALL);
        vrAppState.getVRGUIManager().setGuiScale(0.4f);

        observer.setLocalTranslation(new Vector3f(0.0f, 0.0f, 0.0f));

        vrAppState.setObserver(observer);
        mainScene.attachChild(observer);
        rootNode.attachChild(mainScene);

        initInputs();

        getInputManager().setCursorVisible(true);

        for (int i = 0; i < VR.k_unMaxTrackedDeviceCount; i++) {
            int classCallback = VRSystem.VRSystem_GetTrackedDeviceClass(i);
            if (classCallback == VR.ETrackedDeviceClass_TrackedDeviceClass_Controller || classCallback == VR.ETrackedDeviceClass_TrackedDeviceClass_GenericTracker) {
                IntBuffer error = BufferUtils.createIntBuffer(1);
                String modelNumber = "Unknown";
                String manufacturerName = "Unknown";
                modelNumber = VRSystem.VRSystem_GetStringTrackedDeviceProperty(i, VR.ETrackedDeviceProperty_Prop_ModelNumber_String, error);
                manufacturerName = VRSystem.VRSystem_GetStringTrackedDeviceProperty(i, VR.ETrackedDeviceProperty_Prop_ManufacturerName_String, error);

                if (error.get(0) != 0) {
                    logger.warning("Error getting controller information " + modelNumber + " " + manufacturerName + "Code (" + error.get(0) + ")");
                }
                logger.info("  Tracked controller " + (i + 1) + "/" + VR.k_unMaxTrackedDeviceCount + " " + modelNumber + " (" + manufacturerName + ") attached.");
            } else {
                logger.info("  Controller " + (i + 1) + "/" + VR.k_unMaxTrackedDeviceCount + " ignored.");
            }
        }
    }


    private void initInputs() {
        InputManager inputManager = getInputManager();
        inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE));
        inputManager.addMapping("incShift", new KeyTrigger(KeyInput.KEY_Q));
        inputManager.addMapping("decShift", new KeyTrigger(KeyInput.KEY_E));
        inputManager.addMapping("forward", new KeyTrigger(KeyInput.KEY_W));
        inputManager.addMapping("back", new KeyTrigger(KeyInput.KEY_S));
        inputManager.addMapping("left", new KeyTrigger(KeyInput.KEY_A));
        inputManager.addMapping("right", new KeyTrigger(KeyInput.KEY_D));
        inputManager.addMapping("filter", new KeyTrigger(KeyInput.KEY_F));
        inputManager.addMapping("dumpImages", new KeyTrigger(KeyInput.KEY_I));
        inputManager.addMapping("exit", new KeyTrigger(KeyInput.KEY_ESCAPE));

        ActionListener acl = new ActionListener() {

            public void onAction(String name, boolean keyPressed, float tpf) {
                if (name.equals("incShift") && keyPressed) {
                    vrAppState.getVRGUIManager().adjustGuiDistance(-0.1f);
                } else if (name.equals("decShift") && keyPressed) {
                    vrAppState.getVRGUIManager().adjustGuiDistance(0.1f);
                } else if (name.equals("filter") && keyPressed) {
                    // adding filters in realtime
                    CartoonSSAO cartfilt = new CartoonSSAO(vrAppState.isInstanceRendering());
                    FilterPostProcessor fpp = new FilterPostProcessor(getAssetManager());
                    fpp.addFilter(cartfilt);
                    getViewPort().addProcessor(fpp);
                    // filters added to main viewport during runtime,
                    // move them into VR processing
                    // (won't do anything if not in VR mode)
                    vrAppState.moveScreenProcessingToVR();
                }
                if (name.equals("toggle")) {
                    vrAppState.getVRGUIManager().positionGui();
                }
                if (name.equals("forward")) {
                    if (keyPressed) {
                        moveForward = true;
                    } else {
                        moveForward = false;
                    }
                } else if (name.equals("back")) {
                    if (keyPressed) {
                        moveBackwards = true;
                    } else {
                        moveBackwards = false;
                    }
                } else if (name.equals("dumpImages")) {
                    ((OpenVR) vrAppState.getVRHardware()).getCompositor().CompositorDumpImages.apply();
                } else if (name.equals("left")) {
                    if (keyPressed) {
                        rotateLeft = true;
                    } else {
                        rotateLeft = false;
                    }
                } else if (name.equals("right")) {
                    if (keyPressed) {
                        rotateRight = true;
                    } else {
                        rotateRight = false;
                    }
                } else if (name.equals("exit")) {
                    stop(true);
                    System.exit(0);
                }


            }
        };
        inputManager.addListener(acl, "forward");
        inputManager.addListener(acl, "back");
        inputManager.addListener(acl, "left");
        inputManager.addListener(acl, "right");
        inputManager.addListener(acl, "toggle");
        inputManager.addListener(acl, "incShift");
        inputManager.addListener(acl, "decShift");
        inputManager.addListener(acl, "filter");
        inputManager.addListener(acl, "dumpImages");
        inputManager.addListener(acl, "exit");
    }

    @Override
    public void simpleUpdate(float tpf) {

    }


    private static void registerVr() {
        //Register our manifest with SteamVR
        File manifest = new File("C:\\Users\\Trevor\\Desktop\\jme-tests\\src\\main\\resources\\outside.vrmanifest"); //Path to manifest
        if (manifest.exists()) {
            String path = manifest.getAbsolutePath(); //Must be absolute path

            String appKey = "com.example.vrtest";
            int error;

            //The bool is for temporary or permanent registration
            //error = VRApplications.VRApplications_RemoveApplicationManifest(path);
            //logger.info("Unregister VR Manifest: " + VRApplications.VRApplications_GetApplicationsErrorNameFromEnum(error));
            if (!VRApplications.VRApplications_IsApplicationInstalled(appKey)) {
                error = VRApplications.VRApplications_AddApplicationManifest(path, false);
                logger.info("Register VR Manifest: " + VRApplications.VRApplications_GetApplicationsErrorNameFromEnum(error));
            } else {
                logger.info("VR App already installed");
            }
            long pid = ProcessHandle.current().pid();
            logger.info("PID = " + pid);
            error = VRApplications.VRApplications_IdentifyApplication((int) pid, appKey);
            logger.info("Set VR Manifest: " + VRApplications.VRApplications_GetApplicationsErrorNameFromEnum(error));
        } else {
            logger.warning("No VR Manifest found, but VR enabled!");
        }
    }

    /**
     * Create a {@link VRAppState VR app state} and use a Simple application that use it.<br>
     * The recommended procedure is:<br>
     * <ul>
     * <li>Create some {@link AppSettings AppSettings} with VR related parameters.
     * <li>Instanciate the {@link VRAppState VRAppState} attached to the settings.
     * <li>Instanciate your {@link Application Application}.
     * <li>Attach the settings to the application.
     * <li>Start the application.
     * </ul>
     *
     * @param args not used
     */
    public static void main(String[] args) {

        // Prepare settings for VR rendering.
        // It is recommended to share same settings between the VR app state and the application.
        AppSettings settings = new AppSettings(true);

        settings.put(VRConstants.SETTING_VRAPI, VRConstants.SETTING_VRAPI_OPENVR_LWJGL_VALUE); // The VR api to use (need to be present on the system)
        settings.put(VRConstants.SETTING_DISABLE_VR, false);          // Enable VR
        settings.put(VRConstants.SETTING_ENABLE_MIRROR_WINDOW, true); // Enable Mirror rendering oh the screen (disable to be faster)
        settings.put(VRConstants.SETTING_VR_FORCE, false);            // Not forcing VR rendering if no VR system is found.
        settings.put(VRConstants.SETTING_FLIP_EYES, false);           // Is the HMD eyes have to be inverted.
        settings.put(VRConstants.SETTING_DEFAULT_FOV, 108f);          // The default field Of View (FOV)
        settings.put(VRConstants.SETTING_DEFAULT_ASPECT_RATIO, 1f);   // The default aspect ratio.

        settings.setRenderer(AppSettings.LWJGL_OPENGL32); // Setting the renderer. OpenGL 3 is needed if you're using Instance Rendering.

        // The VR Environment.
        // This object is the interface between the JMonkey world (Application, AppState, ...) and the VR specific stuff.
        VREnvironment environment = new VREnvironment(settings);
        environment.initialize();

        registerVr();

        settings.put(VRConstants.SETTING_NO_GUI, false);              // enable gui.
        settings.put(VRConstants.SETTING_GUI_OVERDRAW, true);         // show gui even if it is behind things.
        settings.put(VRConstants.SETTING_GUI_CURVED_SURFACE, true);   // Curve the mesh that is displaying the GUI

        // Checking if the VR environment is well initialized
        // (access to the underlying VR system is effective, VR devices are detected).
        if (environment.isInitialized()) {
            environment.setSettings(settings);
            // Initialise VR AppState with the VR environment.
            VRAppState vrAppState = new VRAppState(settings, environment);

            // Create the sample application with the VRAppState attached.
            // There is no constraint on the Application type.
            SimpleApplication test = new VRTest(vrAppState);
            test.setShowSettings(false);

            // Sharing settings between app state and application is recommended.
            test.setSettings(settings);

            // Starting the application.
            test.start();
        } else {
            logger.severe("Cannot start VR sample application as VR system is not initialized (see log for details)");
        }
    }
}

You probably want to comment out the registerVr call for your initial testing, and read the link above about the VR launch icon and title before diving into registering your VR application with SteamVR, as that is what the registerVr call attempts to do.

Other than that have fun. I have only tested this on a vive pro and would actually really like to know if this works on Oculus. Let me know if it works for you.

Note: Something you might run into is the jme api for dealing with controllers leaves a lot to be desired, and is a little outdated.
Here is the code I use for getting controllers, it is oriented around using device names to get HTC devices, but you can easily add the oculus names in here too:


        //Get models of trackers
        ArrayList<Integer> deviceIndexList = new ArrayList<>();
        for (int i = 0; i < VR.k_unMaxTrackedDeviceCount; i++) {
            int classCallback = VRSystem.VRSystem_GetTrackedDeviceClass(i);
            if (classCallback == VR.ETrackedDeviceClass_TrackedDeviceClass_Controller || classCallback == VR.ETrackedDeviceClass_TrackedDeviceClass_GenericTracker) {
                IntBuffer error = BufferUtils.createIntBuffer(1);
                String controllerModelNumber = VRSystem.VRSystem_GetStringTrackedDeviceProperty(i, VR.ETrackedDeviceProperty_Prop_ModelNumber_String, error);
                if (error.get(0) != 0) {
                    LOGGER.warning("Error getting controller information: " + i);
                }
                deviceIndexList.add(i);
                LOGGER.fine("  Tracked controller " + (i + 1) + "/" + VR.k_unMaxTrackedDeviceCount + " " + controllerModelNumber + " attached.");
            }
        }

        //Setup trackers
        int controllers = vrAppState.getVRinput().getTrackedControllerCount();
        for (int i = 0; i < controllers; i++) { //Assuming 0 & 1 are controllers
            IntBuffer error = BufferUtils.createIntBuffer(1);
            String model = VRSystem.VRSystem_GetStringTrackedDeviceProperty(deviceIndexList.get(i), VR.ETrackedDeviceProperty_Prop_ModelNumber_String, error);
            if (error.get(0) != 0) {
                LOGGER.warning("Error getting controller information: " + deviceIndexList.get(i));
            }
            model = model.toLowerCase();
            if (model.contains("vive") && model.contains("tracker")) {
                Node trackerNode = new Node("vive tracker " + (i - 2));
                Spatial tracker = app.getAssetManager().loadModel("Models/VR/vive_tracker.j3o");
                tracker.setUserData("input_index", i);
                tracker.setUserData("device_index", deviceIndexList.get(i));
                ((SimpleApplication) app).getRootNode().attachChild(tracker);
                trackers.add(trackerNode);
                LOGGER.info("Found Vive Tracker");
            }
        }

And then later in your program:

@Override
    public void update(float tpf) {
        //This assumes that controller index 0 = left, and 1 = right... IDK if this is correct or not when only one controller and a tracker is present...
        moveVrInput(0, leftHand);
        moveVrInput(1, rightHand);
        //Update trackers
        for (Spatial tracker : trackers) {
            moveVrInput(tracker.getUserData("input_index"), tracker);
        }
    }

    private void moveVrInput(int index, Spatial geo) {
        Quaternion q = vrAppState.getVRinput().getFinalObserverRotation(index);
        Vector3f v = vrAppState.getVRinput().getFinalObserverPosition(index);
        if (q != null && v != null) {
            geo.setCullHint(Spatial.CullHint.Dynamic); // make sure we see it
            geo.setLocalTranslation(v);
            geo.setLocalRotation(q);
        } else {
            geo.setCullHint(Spatial.CullHint.Always); // hide it
        }
    }