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

Feature openxr #2012

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions jme3-xr/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
dependencies {
api project(':jme3-core')
api project(':jme3-lwjgl3')
api project(':jme3-desktop')
api project(':jme3-effects')

// https://mvnrepository.com/artifact/net.java.dev.jna/jna
implementation 'net.java.dev.jna:jna:5.10.0'
implementation 'com.nativelibs4java:jnaerator-runtime:0.12'

// Native OpenXR/LWJGL support
api "org.lwjgl:lwjgl-openxr:${lwjgl3Version}"
// implementation "org.lwjgl:lwjgl-openxr:${lwjgl3Version}:natives-linux"
// implementation "org.lwjgl:lwjgl-openxr:${lwjgl3Version}:natives-macos"
runtimeOnly "org.lwjgl:lwjgl-openxr:${lwjgl3Version}:natives-windows"
runtimeOnly "org.lwjgl:lwjgl-openxr:${lwjgl3Version}:natives-linux"
// runtimeOnly "org.lwjgl:lwjgl-openxr:${lwjgl3Version}:natives-macos"

// Necessary by lwjgl-openxr
api "org.joml:joml:1.10.4"
api "org.lwjgl:lwjgl-egl:${lwjgl3Version}"
// api "org.lwjgl:lwjgl-vulkan:${lwjgl3Version}"
}

javadoc {
// Disable doclint for JDK8+.
if (JavaVersion.current().isJava8Compatible()){
options.addStringOption('Xdoclint:none', '-quiet')
}
}
94 changes: 94 additions & 0 deletions jme3-xr/src/main/java/com/jme3/input/xr/Eye.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.jme3.input.xr;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.Camera;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.texture.FrameBuffer;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
import com.jme3.texture.FrameBuffer.FrameBufferTarget;
import com.jme3.texture.Image.Format;

public class Eye {
static int index = 0;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like there may be some tabs vs spaces issues going on here

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can fix this, when I am back from holiday next week.
Have no access to my account currently.

SimpleApplication app;
float posX;
Vector3f tmpVec = new Vector3f();
Texture2D offTex;
Geometry offGeo;
Camera offCamera;
Vector3f centerPos = new Vector3f(0f, 0f, -5f);
Quaternion centerRot = new Quaternion();

public Eye(SimpleApplication app)
{
this.app = app;
setupOffscreenView(app);
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTexture("ColorMap", offTex);

offGeo = new Geometry("box", new Box(1, 1, 1));
offGeo.setMaterial(mat);
}

public void setPosX(float posX) { this.posX = posX; }
public float getPosX() { return posX; }

/** Moves the camera.
* @param The new absolute center position. */
public void moveAbs(Vector3f newPos)
{
centerPos.set(newPos);
rotateAbs(offCamera.getRotation());
}

/** Rotates the camera, and moves left/right.
* @param The new rotation. */
public void rotateAbs(Quaternion newRot)
{
tmpVec.set(posX, 0.0f, 0.0f);
newRot.multLocal(tmpVec);
offCamera.setLocation(tmpVec.addLocal(centerPos));
offCamera.setRotation(newRot);
}

private void setupOffscreenView(SimpleApplication app)
{
int w = app.getContext().getSettings().getWidth();
int h = app.getContext().getSettings().getHeight();
offCamera = new Camera(w, h);

ViewPort offView = app.getRenderManager().createPreView("OffscreenViewX" + (index++), offCamera);
offView.setClearFlags(true, true, true);
offView.setBackgroundColor(ColorRGBA.DarkGray);
FrameBuffer offBuffer = new FrameBuffer(w, h, 1);

//setup framebuffer's cam
offCamera.setFrustumPerspective(45f, 1f, 1f, 1000f);
offCamera.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y);

//setup framebuffer's texture
offTex = new Texture2D(w, h, Format.RGBA8);
offTex.setMinFilter(Texture.MinFilter.Trilinear);
offTex.setMagFilter(Texture.MagFilter.Bilinear);

//setup framebuffer to use texture
offBuffer.setDepthTarget(FrameBufferTarget.newTarget(Format.Depth));
offBuffer.addColorTarget(FrameBufferTarget.newTarget(offTex));

//set viewport to render to offscreen framebuffer
offView.setOutputFrameBuffer(offBuffer);
offView.attachScene(app.getRootNode());
}

public void render()
{
app.getRenderManager().renderGeometry(offGeo);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this offgeo for exactly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rendering offscreen onto texture of offgeo, and then render this offgeo on framebuffer of openxr was the only way I found, to get a 3d picture onto the HMD.
If we find an other way, we can change this of course.
But it renders very smooth.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I tried removing it and it stopped working. I wonder if rendering the geometry triggers all the dependant materials and textures to be rendered and that's what actually needed?

I'm going to have a play and see if that can be streamlined

}
}
105 changes: 105 additions & 0 deletions jme3-xr/src/main/java/com/jme3/input/xr/XrHmd.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package com.jme3.input.xr;

import java.util.ArrayList;

import com.jme3.app.SimpleApplication;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.system.AppSettings;
import com.jme3.system.lwjgl.LwjglWindowXr;

public class XrHmd
{
public static final float DEFAULT_X_DIST = 0.4f;
public static final float DEFAULT_X_ROT = 0.028f;
Copy link
Member

@richardTingle richardTingle May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is DEFAULT_X_ROT for? It looks to be 1.6 degrees which is a tiny amount. O, its making the eyes not face exactly forwards. Is that right?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it is Rotation.
Without this value, far objects looks like they appear twice.

public static final float DEFAULT_POS_MULT = 10.0f;
Quaternion tmpQ = new Quaternion();
float[] tmpArr = new float[3];
Quaternion tmpQdistRotL = new Quaternion().fromAngles(0, -DEFAULT_X_ROT, 0);
Quaternion tmpQdistRotR = new Quaternion().fromAngles(0, DEFAULT_X_ROT, 0);
SimpleApplication app;
Eye leftEye;
Eye rightEye;
ArrayList<XrListener.OrientationListener> hmdListeners = new ArrayList<XrListener.OrientationListener>();
ArrayList<XrListener.ButtonPressedListener> contr1Listeners = new ArrayList<XrListener.ButtonPressedListener>();
ArrayList<XrListener.ButtonPressedListener> contr2Listeners = new ArrayList<XrListener.ButtonPressedListener>();

public XrHmd(SimpleApplication app)
{
this.app = app;
leftEye = new Eye(app);
rightEye = new Eye(app);
setXDistance(DEFAULT_X_DIST);
hmdListeners.add((p,r) -> doMoveRotate(p,r));
}

Vector3f initPos;
Vector3f diffPos = new Vector3f();
private void doMoveRotate(Vector3f p, Quaternion r)
{
if (initPos == null) { initPos = new Vector3f(p.getX(), p.getY(), p.getZ()); }
initPos.subtract(p,diffPos);
leftEye.moveAbs(diffPos);
rightEye.moveAbs(diffPos);
tmpQ.set(r);
tmpQ.inverseLocal();
tmpQ.set(tmpQ.getX(), tmpQ.getY(), -tmpQ.getZ(), tmpQ.getW());
leftEye.rotateAbs(tmpQ.multLocal(tmpQdistRotL));
tmpQ.set(r);
tmpQ.inverseLocal();
tmpQ.set(tmpQ.getX(), tmpQ.getY(), -tmpQ.getZ(), tmpQ.getW());
rightEye.rotateAbs(tmpQ.multLocal(tmpQdistRotR));
}

public Eye getLeftEye() { return leftEye; }
public Eye getRightEye() { return rightEye; }

public ArrayList<XrListener.OrientationListener> getHmdOrientationListeners() { return hmdListeners; }
public ArrayList<XrListener.ButtonPressedListener> getContr1ButtonPressedListeners() { return contr1Listeners; }
public ArrayList<XrListener.ButtonPressedListener> getContr2ButtonPressedListeners() { return contr2Listeners; }

Vector3f multPos = new Vector3f();
public void onUpdateHmdOrientation(Vector3f viewPos, Quaternion viewRot)
{
viewPos.mult(DEFAULT_POS_MULT, multPos);
multPos.setX(0.0f-multPos.getX());
multPos.setY(0.0f-multPos.getY());
for (XrListener.OrientationListener l : hmdListeners) { l.onUpdateOrientation(multPos, viewRot); }
}

/** Must be called in main function before init.
* @param s The appSettings that must be used with app.setSettings(s). */
public static void setRendererForSettings(AppSettings s)
{
s.setRenderer("CUSTOM" + com.jme3.system.lwjgl.LwjglWindowXr.class.getName()); //see JmeDesktopSystem.newContext(...)
}

/** Must be called in simpleInitApp-function of SimpleApplication.
* @return The head-mounted-device object. */
public static XrHmd initHmd(SimpleApplication app)
{
XrHmd xrHmd = new XrHmd(app);
((LwjglWindowXr)app.getContext()).getXr().setHmd(xrHmd);
return xrHmd;
}

/** Gets the distance between the eyes. Default is DEFAULT_X_DIST */
public float getXDistance() { return rightEye.getPosX() * 2f; }

/** Sets the distance between the eyes. Default is DEFAULT_X_DIST */
public void setXDistance(float xDist)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest calling this the interpupillary distance as that seems to be the formal term for this

{
rightEye.setPosX(xDist / 2f);
leftEye.setPosX(xDist / -2f);
}

/** Gets the rotation angle between the eyes. Default is DEFAULT_X_ROT */
public float getXRotation() { return tmpQdistRotR.toAngles(tmpArr)[1]; }

/** Sets the rotation angle between the eyes. Default is DEFAULT_X_ROT */
public void setXRotation(float xRot)
{
tmpQdistRotR.fromAngles(0, xRot, 0);
tmpQdistRotL.fromAngles(0, xRot, 0);
}
}
16 changes: 16 additions & 0 deletions jme3-xr/src/main/java/com/jme3/input/xr/XrListener.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.jme3.input.xr;

import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;

public interface XrListener {
public interface OrientationListener
{
public void onUpdateOrientation(Vector3f pos, Quaternion rot);
}

public interface ButtonPressedListener
{
public void onButtonPressed(int num);
}
}
40 changes: 40 additions & 0 deletions jme3-xr/src/main/java/com/jme3/system/lwjgl/EmptyKeyInput.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.jme3.system.lwjgl;

import static org.lwjgl.glfw.GLFW.glfwGetTime;

import com.jme3.input.KeyInput;
import com.jme3.input.RawInputListener;

public class EmptyKeyInput implements KeyInput {

public EmptyKeyInput() { }
@Override
public void initialize() {}

@Override
public boolean isInitialized() {return false;}

@Override
public void update() {}
@Override
public void destroy() {
// TODO Auto-generated method stub

}
@Override
public void setInputListener(RawInputListener listener) {
// TODO Auto-generated method stub

}
@Override
public long getInputTimeNanos() {
return (long) (glfwGetTime() * 1000000000);
}
@Override
public String getKeyName(int key) {
// TODO Auto-generated method stub
return null;
}

public void resetContext() {}
}
52 changes: 52 additions & 0 deletions jme3-xr/src/main/java/com/jme3/system/lwjgl/EmptyMouseInput.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.jme3.system.lwjgl;

import static org.lwjgl.glfw.GLFW.glfwGetTime;

import com.jme3.cursors.plugins.JmeCursor;
import com.jme3.input.MouseInput;
import com.jme3.input.RawInputListener;

public class EmptyMouseInput implements MouseInput {

public EmptyMouseInput() { }
@Override
public void initialize() {}

@Override
public boolean isInitialized() {return false;}
@Override
public void update() {
// TODO Auto-generated method stub

}
@Override
public void destroy() {
// TODO Auto-generated method stub

}
@Override
public void setInputListener(RawInputListener listener) {
// TODO Auto-generated method stub

}
@Override
public long getInputTimeNanos() {
return (long) (glfwGetTime() * 1000000000);
}
@Override
public void setCursorVisible(boolean visible) {
// TODO Auto-generated method stub

}
@Override
public int getButtonCount() {
// TODO Auto-generated method stub
return 3;
}
@Override
public void setNativeCursor(JmeCursor cursor) {
// TODO Auto-generated method stub
}

public void resetContext() {}
}
Loading
Loading