Official VR module


Which kind of compositor do you use ? I’m using an HTC vive with SteamVR.

Can you run the example i previously post (with the app state ) and paste the log output here ?

1 Like

I vwould just add that this kind of error is an OpenVR native error (101) that is recovered from JNA binding. Are you sure that your SteamVR installation is up to date and that you do not use a different version of OpenVR (the one integrated with JME3-vr is 1.0.6). Maybe if your path contains another version of OpenVR you should experiment some problems.

1 Like

I’m using a Vive with SteamVR from 31/1 (1484823399). I have no custom version of OpenVR so I assume it’s the correct one (don’t see a version number anywhere).
It’s a rare issue, happens maybe once every 10 or 20 runs. I’ll provide more info if I find any.

1 Like

I’m investigating here. I’m going to add the OpenVR version checking at initialization in order to know which version the jme3-vr is using.

I’m also investigating the focus problem, it seems to be a problem during initialization, maybe a sync problem…

1 Like

I was not using the SteamVR 1484823399 (it has updated to this version just few minutes ago). I’ll check if the problem occurs with this version.

1 Like

Is VRApplication deprecated?
Because I get:

Exception in thread "main" java.lang.NullPointerException
	at com.jme3.input.vr.OpenVRInput.updateConnectedControllers(
	at com.jme3.input.vr.OpenVR.initialize(

Or maybe I’ve missed something.

(This is another project where I’m trying to switch from jMonkeyVR to jme-vr)

1 Like

Yes, VRApplication should not be used. As asked by the JMonkey contributors, I’'ve now switched to VRAppState. I will remove the VRApplication on next commit.

1 Like

@seinturier thanks for the great work.

If i can access the JMonkey wiki, i will provide a simple tutorial for using VR capabilities within JMonkey environment.

You are welcome to add your PR to JME wiki here : GitHub - jMonkeyEngine/wiki: The official wiki for jMonkeyEngine.
I can merge it for you.

And it would be cool if you can also add the test example to jme3-examples

1 Like


I’m aware that there is a jme3-example module but my problem is that this project has a dependency to jm3-lwjgl and i need jme3-lwjgl3 in order the use the VR. So it’s difficult to integrate a VR example within the jme3-exampls module.

1 Like

Question: Is it possible to change the size of the mirrored window?

1 Like

Hello Rickard, Yes it is possible but i will commit some changes today in order to refactor and separate the VR specific stuff and the JMonkey one. Il will also make settings update and you will be able to set the mirror windows witdh, height and position from the AppSettings.


Hello to all,

I’ve committed the new version of the jme3-vr module and I’ve made a new pull request to the master branch.

This new version now provide a VREnvironment class that gather all VR related stuff. This class can be instantiated and initialized before any call to JMonkey and it is so possible now to know if the system can handle VR rendering before starting JMonkey application and attaching the VRAppState.

I’ve also refactored some classes so the old example should not work anymore. Here is a new version of the VR sample that deal with the new VREnvironment class:
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)");

I’ve tried updating to the latest.
There’s a crash I can’t get past:

	at com.jme3.renderer.Camera.clone(
	at com.jme3.util.VRViewManagerOpenVR.setupMirrorBuffers(
	at com.jme3.util.VRViewManagerOpenVR.setupVRScene(
	at com.jme3.util.VRViewManagerOpenVR.initialize(

It seems the camera isn’t initialized, so when it tries to clone the location there’s an NPE. I haven’t been able to determine if it’s a race condition or not. I guess AppStates won’t ever be initialized before the application?

This happens when “VREnableMirrorWindow” and “SwapBuffers” are true. However, I haven’t been able to turn either of them off by changing settings…

Edit: As expected, commenting out “setupMirrorBuffers(environment.getCamera(), leftEyeTexture, false);” gets me past this, but doesn’t show anything in the mirror view

1 Like

No… but eventually, Application won’t initialize anything either (only app states will). Something to keep in mind as you cast about for solutions.

1 Like

Which branch do you recommend building from? I built
master and no jme3-vr.jar ended up in my dist folder.

1 Like

After adding jme3-vr-3.2.0-SNAPSHOT.jar from the jme3-vr/build/libs folder, the sample code posted above complains about missing VREnvironment and VRGUIPositioningMode.

Help? Thanks!

1 Like

Forgot to mention, I built master pulled from GitHub - jseinturier/jmonkeyengine: A complete 3D game development suite written purely in Java.. I had even worse luck with GitHub - jMonkeyEngine/jmonkeyengine: A complete 3D game development suite written purely in Java..

1 Like


I’m actually away from my computer and i can’t check your problem.

The last version of jme-vr has been pulled to jmonkey master branch a week ago. However, in order to build the module, you have to enable java 1.8 compatibility and jme3-lwjgl3.

I’ll give you more information by next week.



It is curious as the classes you mention are visible within the sources of the jmonkey master branch. You can even unzip the jar to check the classes inside.

Be also sure to use jme3-lwjgl3 and java 1.8

1 Like

Not sure whether that check works. I had the same problem and added an include manually, even though I run Java 8.

From settings.gradle:

if (JavaVersion.current().isJava8Compatible()) {
    include 'jme3-lwjgl3'
    include 'jme3-vr'

@sduensin remove the if statement, or add “include ‘jme3-vr’” outside of it in your jmonkeyengine/settings.gradle and it should build