Official VR module

@rickard You are right, I was looking at the Master branch and thought it was the 3.1.0 branch.

jme3-vr is not part of 3.1.0

So I downloaded the Master branch and figured out how to compile it.

It builds successfully but I get this error:

Nov 01, 2017 3:01:57 PM initialize
SEVERE: System does not support VR capabilities.
Nov 01, 2017 3:01:57 PM initialize
SEVERE: Cannot initialize VR environment [FAILED]
BUILD SUCCESSFUL (total time: 0 seconds)

What system is not supported? Ubuntu 14.04?

I’m going to try a more recent Ubuntu version (16.4) later this afternoon.

@iamcreasy Thanks for the link, but I’m not even getting to the point where it tries to “Create OpenVR wrapper”

Thanks for the support!

1 Like

Ah, yeah. I’ve noticed that linux auto-fails. I’ve investigated since my system in some cases isn’t a x64 bit either.
This is line 394 of VREnvironment:

vrSupportedOS = !OS.contains("nux") && System.getProperty("").equalsIgnoreCase("64"); //for the moment, linux/unix causes crashes, 64-bit only

I didn’t write it or have tested in on linux, so I can’t help there. You could try setting it to true and see what happens. Maybe it’s redundant.

1 Like

After removing !OS.contains("nux"), I get to the point where OpenVR tries to initialize, but it crashes on JOpenVRLibrary.VR_InitInternal(hmdErrorStore, JOpenVRLibrary.EVRApplicationType.EVRApplicationType_VRApplication_Scene); in

# A fatal error has been detected by the Java Runtime Environment:
#  SIGSEGV (0xb) at pc=0x00007efe079675f1, pid=12762, tid=0x00007efe07b52700
# JRE version: Java(TM) SE Runtime Environment (8.0_151-b12) (build 1.8.0_151-b12)
# Java VM: Java HotSpot(TM) 64-Bit Server VM (25.151-b12 mixed mode linux-amd64 compressed oops)
# Problematic frame:
# C  []

Full dump: - Share Text & Images the Easy Way

Any idea of what to try next? I will try with OSVR.

1 Like

Drivers seem to be there at least. I don’t see any drivers for OSVR though.

Here’s a page I found regarding SteamVR and Linux. It seems to be a bit experimental, still:

Did you get the openvr/build/samples/hellovr_opengl/run_hellovr sample to run?

1 Like

Hello to all,

I’m sorry to not give answers frequently. The actual implementation of jme3-vr is tested on windows with OpenVR official distribution (not the beta ones).

The next commit will enable to use custom OpenVR implementation (by specifying a DLL or a SO file at startup) and so it will be easier to test other implementations.

For me, the problem with Linux/unix systems is linked to OpenVR implementation and not to jme3-vr module as the module only delegates VR to the underlying native library.

I try to commit the new version by the end of the week.

1 Like


I’ve just updated the jme3-vr module. The pull request is pending but the integration within the master branch should be ok soon. I’ve done many refactoring in order to continue integration of various VR implementation (OpenVR, OSVR, Oculus Rift) without board effects. I’ve also updated the OpenVR underlying implementation to 1.0.9.

Please note that is now possible to specify an external native implementation of openvr. For that, you have to add the option to the command line.

Here is an updated VRSample program that works with the last update of the module:

package sample;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Filter;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

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.VRInputType;
import com.jme3.input.vr.openvr.OpenVR;
import com.jme3.material.Material;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture.MagFilter;
import com.jme3.texture.Texture.MinFilter;
import com.jme3.ui.Picture;
import com.jme3.util.SkyFactory;
import com.jme3.util.VRGUIPositioningMode;

 * A Jmonkey sample that show the use of VR rendering with a JMonkey Application.
 * @author Julien Seinturier
public class VRAppStateSample extends SimpleApplication {

	private static final Logger logger = Logger.getLogger(VRAppStateSample.class.getName());
    // general objects for scene management
    Node boxes = new Node("");
    Spatial observer;
    boolean moveForward, moveBackwards, rotateLeft, rotateRight;
    Material mat;
    Node mainScene;
    Geometry leftHand, rightHand;

    private float distance  = 100f;
    private float prod      = 0f;
    private float placeRate = 0f;
    VRAppState vrAppState = null;
    public VRAppStateSample(AppState... initialStates) {
        vrAppState = getStateManager().getState(VRAppState.class);
    public void simpleInitApp() {    
 "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/Sky/Bright/spheremap.png", SkyFactory.EnvMapType.EquirectMap);
        Geometry box = new Geometry("", new Box(5,5,5));
        mat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        Texture noise = getAssetManager().loadTexture("Textures/noise.png");
        mat.setTexture("ColorMap", noise);
        // 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);
        // hand wands
        leftHand = (Geometry)getAssetManager().loadModel("Models/vive_controller.j3o");
        rightHand = leftHand.clone();
        Material handMat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
        handMat.setTexture("ColorMap", getAssetManager().loadTexture("Textures/vive_controller.png"));
        // gui element
        Vector2f guiCanvasSize = vrAppState.getVRGUIManager().getCanvasSize();
        Picture test = new Picture("testpic");
        test.setImage(getAssetManager(), "Textures/crosshair.png", true);
        test.setPosition(guiCanvasSize.x * 0.5f - 192f * 0.5f, guiCanvasSize.y * 0.5f - 128f * 0.5f);
        // test any positioning mode here (defaults to AUTO_CAM_ALL)
        Geometry box2 = box.clone();
        box2.move(15, 0, 0);
        Geometry box3 = box.clone();
        box3.move(-15, 0, 0);
        observer.setLocalTranslation(new Vector3f(0.0f, 0.0f, 0.0f));

        // use magic VR mouse cusor (same usage as non-VR mouse cursor)
        // filter test (can be added here like this)
        // but we are going to save them for the F key during runtime
        CartoonSSAO cartfilt = new CartoonSSAO();
        FilterPostProcessor fpp = new FilterPostProcessor(assetManager);

     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){
                }else if(name.equals("decShift") && keyPressed){
                }else if(name.equals("filter") && keyPressed){
                    // adding filters in realtime
                    CartoonSSAO cartfilt = new CartoonSSAO(vrAppState.isInstanceRendering());
                    FilterPostProcessor fpp = new FilterPostProcessor(getAssetManager());
                    // filters added to main viewport during runtime,
                    // move them into VR processing
                    // (won't do anything if not in VR mode)
                if( name.equals("toggle") ) {
                        moveForward = true;
                    } else {
                        moveForward = false;
                } else if(name.equals("back")){
                        moveBackwards = true;
                    } else {
                        moveBackwards = false;
                } else if( name.equals("dumpImages") ) {
                }else if(name.equals("left")){
                        rotateLeft = true;
                    } else {
                        rotateLeft = false;
                } else if(name.equals("right")){
                        rotateRight = true;
                    } else {
                        rotateRight = false;
                } else if( name.equals("exit") ) {
        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");
     public void simpleUpdate(float tpf){

         //FPS test
         /*tpfAdder += tpf;
         if( tpfCount == 60 ) {
             System.out.println("FPS: " + Float.toString(1f / (tpfAdder / tpfCount)));
             tpfCount = 0;
             tpfAdder = 0f;
         distance = 100f * FastMath.sin(prod);
         boxes.setLocalTranslation(0, 0, 200f+ distance);
             observer.rotate(0, 0.75f*tpf, 0);
             observer.rotate(0, -0.75f*tpf, 0);
         handleWandInput(0, leftHand);
         handleWandInput(1, rightHand);
         if( placeRate > 0f ) placeRate -= tpf;
     private void handleWandInput(int index, Geometry geo) {
         Quaternion q = vrAppState.getVRinput().getFinalObserverRotation(index);
         Vector3f v = vrAppState.getVRinput().getFinalObserverPosition(index);
         if( q != null && v != null ) {
             geo.setCullHint(CullHint.Dynamic); // make sure we see it
             // place boxes when holding down trigger
             if( vrAppState.getVRinput().getAxis(index, VRInputType.ViveTriggerAxis).x >= 1f &&
                 placeRate <= 0f ) {
                 placeRate = 0.5f;
                 addBox(v, q, 0.1f);
                 vrAppState.getVRinput().triggerHapticPulse(index, 0.1f);
             // print out all of the known information about the controllers here
             /*for(int i=0;i<VRInput.getRawControllerState(index).rAxis.length;i++) {
                 VRControllerAxis_t cs = VRInput.getRawControllerState(index).rAxis[i];
                 System.out.println("Controller#" + Integer.toString(index) + ", Axis#" + Integer.toString(i) + " X: " + Float.toString(cs.x) + ", Y: " + Float.toString(cs.y));
             System.out.println("Button press: " + Long.toString(VRInput.getRawControllerState(index).ulButtonPressed.longValue()) + ", touch: " + Long.toString(VRInput.getRawControllerState(index).ulButtonTouched.longValue()));
         } else {
             geo.setCullHint(CullHint.Always); // hide it             
     private void addAllBoxes() {
        float distance = 8;
        for (int x = 0; x < 35; x++) {
            float cos = FastMath.cos(x * FastMath.PI / 16f) * distance;
            float sin = FastMath.sin(x * FastMath.PI / 16f) * distance;
            Vector3f loc = new Vector3f(cos, 0, sin);
            addBox(loc, null, 1f);
            loc = new Vector3f(0, cos, sin);
            addBox(loc, null, 1f);


    private void addBox(Vector3f location, Quaternion rot, float scale) {
        Box b = new Box(0.3f, 0.3f, 0.3f);

        Geometry leftQuad = new Geometry("Box", b);
        if( rot != null ) {
        } else {
            leftQuad.rotate(0.5f, 0f, 0f);
    private static void initLog(){
    	// Set the logger to display config messages.
    	Logger log = Logger.getLogger("");
    	// Disable Nifty µGUI logs
        Filter filter = new Filter(){
			public boolean isLoggable(LogRecord record) {
				return true;
        Formatter formatter = new Formatter(){

          private final String lineSeparator = System.getProperty("line.separator");
          SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd-HH:mm:ss");
          public String format(LogRecord record) {
            if (record != null){
              String simpleClassName = record.getSourceClassName();
              if (simpleClassName != null){
                int index = simpleClassName.lastIndexOf(".");
                if ((index > -1)&&(index < (simpleClassName.length() - 1))){
                  simpleClassName = simpleClassName.substring(index+1);
              } else {
                simpleClassName = "Unknow,";
              String level =  "";
              if (record.getLevel().equals(Level.FINEST)){
                level = "FINEST ";
              } else if (record.getLevel().equals(Level.FINER)){
                level = "FINER  ";
              } else if (record.getLevel().equals(Level.FINE)){
                level = "FINE   ";
              } else if (record.getLevel().equals(Level.CONFIG)){
                level = "CONFIG ";
              } else if (record.getLevel().equals(Level.INFO)){
                level = "INFO   ";
              } else if (record.getLevel().equals(Level.WARNING)){
                level = "WARNING";
              } else if (record.getLevel().equals(Level.SEVERE)){
                level = "SEVERE ";
              } else {
                level = "???????";
              // Use record parameters
              String message = record.getMessage();
              if (record.getParameters() != null){
                for(int i = 0; i < record.getParameters().length; i++){
                  message = message.replace("{"+i+"}", ""+record.getParameters()[i]);
              if (record.getThrown() == null){
                return "("+sdf.format(new Date(record.getMillis()))+") "+level+" ["+simpleClassName+"] ["+record.getSourceMethodName()+"] "+message+lineSeparator;
              } else {
                String str = "("+sdf.format(new Date(record.getMillis()))+") "+level+" ["+simpleClassName+"] ["+record.getSourceMethodName()+"] caused by "+message+lineSeparator;
                StackTraceElement[] elements = record.getThrown().getStackTrace();
                for(int i = 0; i < elements.length; i++){
                  str += "("+sdf.format(new Date(record.getMillis()))+") "+level+" ["+simpleClassName+"] ["+record.getSourceMethodName()+"] at "+elements[i]+lineSeparator;
                return "("+sdf.format(new Date(record.getMillis()))+") "+level+" ["+record.getSourceClassName()+"] ["+record.getSourceMethodName()+"] "+message+lineSeparator+str;
            } else {
              return null;

    	// If the init is forced from a previous configuration, we remove the older handlers.
    	if (log != null){
          if (log.getHandlers() != null){
            for(int i = log.getHandlers().length - 1; i >= 0; i--){
     * 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){
    	// Init the log to display all the configuration informations.
    	// This is not needed within final application.
    	// 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_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_GUI_CURVED_SURFACE, true);   // Curve the mesh that is displaying the GUI
    	settings.put(VRConstants.SETTING_FLIP_EYES, false);           // Is the HMD eyes have to be inverted.
    	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_DEFAULT_FOV, 108f);          // The default ield Of View (FOV)
    	settings.put(VRConstants.SETTING_DEFAULT_ASPECT_RATIO, 1f);   // The default aspect ratio.
    	settings.setRenderer(AppSettings.LWJGL_OPENGL3); // 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);
    	// Checking if the VR environment is well initialized 
    	// (access to the underlying VR system is effective, VR devices are detected).
    	if (environment.isInitialized()){
        	// 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 VRAppStateSample(vrAppState);

            // Sharing settings between app state and application is recommended.
            // Starting the application.
    	} else {
    		logger.severe("Cannot start VR sample application as VR system is not initialized (see log for details)");

Hi @seinturier

I just cloned jseinturier:jme3-vr-dev and tryed it, hoping it would solve my issue.

In your new VRAppStateSample, I had to change
import com.jme3.input.vr.openvr.OpenVR;
import com.jme3.input.vr.OpenVR;

and had to comment

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);

since .getVRBounds() was an unrecognized symbol.

Sadly, it didn’t solve my problem.

On the OSVR side, I managed to get the OSVR sample app working on windows, but not in jMonkey (on windows still).

I had to set Sealed:false in jwjgl-2.9.3.jar

Then I got the following error:

java.lang.VerifyError: Bad type on operand stack
Exception Details:
    org/lwjgl/opengl/GL.setCapabilities(Lorg/lwjgl/opengl/GLCapabilities;)V @19: invokestatic
    Type 'org/lwjgl/PointerBuffer' (current frame, stack[0]) is not assignable to 'org/lwjgl/system/CustomBuffer'
  Current Frame:
    bci: @19
    flags: { }
    locals: { 'org/lwjgl/opengl/GLCapabilities' }
    stack: { 'org/lwjgl/PointerBuffer' }

    at org.lwjgl.opengl.WGL$Functions.<clinit>(
    at org.lwjgl.opengl.WGL.wglGetCurrentContext(
    at com.jme3.input.vr.OSVR.grabGLFWContext(
    at com.jme3.input.vr.OSVR.initVRCompositor(

On de OSVR, Linux side… Well… I’m able to compile OSVR-Core, get the OSVR server running, but I can’t manage to compile OSVR-Vive.

1 Like

Hey got a question about the VR module.

Does this support the new Microsoft Mixed Reality Headsets?

1 Like

In fact the response is exactly the inverse :wink: The Microsodft compatible headsets are based on SteamVR (and so on OpenVR). See Windows Mixed Reality Headsets Gain SteamVR Support, a Library of VR Games.

So the headsets are compatible with the Jmonkey VR module as soon as you use OpenVR as VR environment (see the sample above).


Hey thanks for the reply.


I’ve updated the wiki by adding a link to this thread


Did you mean for this link to start at the top of the thread or at the bottom(post 67)?

Remove the /67 to make it start from top of thread if that’s what you intended.

See comments on the PR.

1 Like

That’s the thing with these monster threads. How is anyone going to be able to extract useful info from them? The beginning is outdated and the end is out of context. For that reason I don’t think we should encourage people to post new questions in this thread.


I will make a dedicated page on the WIKI for the VR module. I will work intensively on it by the end of February (will update the module and the documentation).


While waiting for proper documentation, a minimal updated code snippet should be posted and the thread locked. Then, the link on the wiki should be updated.

What’s the status of this? I getting into VR development and this module looks like it does what I need.

Do you speak about the documentation or the jme-vr module itself ?

Tje jme3-vr module is now a part of the official distribution (since 3.3.0)

Yea. I only noticed after I posted the question.

Since 3.2.0 actually 3.3.0 has not been released yet