diff options
author | Sven Gothel <[email protected]> | 2014-07-05 04:04:43 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-07-05 04:04:43 +0200 |
commit | f8f0f051604721bceaee214b8e5218fd47d2eb9e (patch) | |
tree | e9c3103498984fbc5d2f8567e88009c49e8810f7 /src/oculusvr | |
parent | 2293a53ba04a8cf2881e8919f8be97c16a9af336 (diff) |
Bug 1021: Make OVR access vendor agnostic: Package 'com.jogamp.opengl.util.stereo' contains all public interfaces/classes
Renamed interfaces:
CustomRendererListener -> CustomGLEventListener
StereoRendererListener -> StereoGLEventListener
New vendor agnostic 'stuff' in com.jogamp.opengl.util.stereo:
1 - StereoDeviceFactory
To create a vendor specific StereoDeviceFactory instance,
which creates the StereoDevice.
2 - StereoDevice
For vendor specific implementation.
Can create StereoDeviceRenderer.
3 - StereoDeviceRenderer
For vendor specific implementation.
4 - StereoClientRenderer
Vendor agnostic client StereoGLEventListener renderer,
using a StereoDeviceRenderer.
Now supports multiple StereoGLEventListener, via add/remove.
- MovieSBSStereo demo-able via StereoDemo01
can show SBS 3D movies.
Diffstat (limited to 'src/oculusvr')
7 files changed, 852 insertions, 1136 deletions
diff --git a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java b/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java deleted file mode 100644 index 9af3397cf..000000000 --- a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java +++ /dev/null @@ -1,233 +0,0 @@ -/** - * Copyright 2014 JogAmp Community. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of JogAmp Community. - */ -package com.jogamp.opengl.oculusvr; - -import javax.media.opengl.GL; -import javax.media.opengl.GL2ES2; -import javax.media.opengl.GLAutoDrawable; -import javax.media.opengl.GLEventListener; - -import jogamp.opengl.oculusvr.OVRDistortion; - -import com.jogamp.oculusvr.OVR; -import com.jogamp.oculusvr.ovrFrameTiming; -import com.jogamp.opengl.FBObject; -import com.jogamp.opengl.FBObject.Attachment; -import com.jogamp.opengl.FBObject.TextureAttachment; -import com.jogamp.opengl.FBObject.Attachment.Type; -import com.jogamp.opengl.util.CustomRendererListener; -import com.jogamp.opengl.util.stereo.StereoRendererListener; - -/** - * OculusVR (OVR) <i>Side By Side</i> Distortion Renderer utilizing {@link OVRDistortion} - * implementing {@link GLEventListener} for convenience. - * <p> - * Implementation renders an {@link StereoRendererListener} instance - * side-by-side using two {@link FBObject}s according to {@link OVRDistortion}. - * </p> - */ -public class OVRSBSRendererDualFBO implements GLEventListener { - private final OVRDistortion dist; - private final boolean ownsDist; - private final StereoRendererListener upstream; - private final FBObject[] fbos; - private final int magFilter; - private final int minFilter; - - private int numSamples; - private final TextureAttachment[] fboTexs; - - public OVRSBSRendererDualFBO(final OVRDistortion dist, final boolean ownsDist, final StereoRendererListener upstream, - final int magFilter, final int minFilter, final int numSamples) { - this.dist = dist; - this.ownsDist = ownsDist; - this.upstream = upstream; - this.fbos = new FBObject[2]; - this.fbos[0] = new FBObject(); - this.fbos[1] = new FBObject(); - this.magFilter = magFilter; - this.minFilter = minFilter; - - this.numSamples = numSamples; - this.fboTexs = new TextureAttachment[2]; - } - - private void initFBOs(final GL gl, final int width, final int height) { - // remove all texture attachments, since MSAA uses just color-render-buffer - // and non-MSAA uses texture2d-buffer - fbos[0].detachAllColorbuffer(gl); - fbos[1].detachAllColorbuffer(gl); - - fbos[0].reset(gl, width, height, numSamples, false); - fbos[1].reset(gl, width, height, numSamples, false); - if(fbos[0].getNumSamples() != fbos[1].getNumSamples()) { - throw new InternalError("sample size mismatch: \n\t0: "+fbos[0]+"\n\t1: "+fbos[1]); - } - numSamples = fbos[0].getNumSamples(); - - if(numSamples>0) { - fbos[0].attachColorbuffer(gl, 0, true); // MSAA requires alpha - fbos[0].attachRenderbuffer(gl, Type.DEPTH, 24); - final FBObject ssink0 = new FBObject(); - { - ssink0.reset(gl, width, height); - ssink0.attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - ssink0.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); - } - fbos[0].setSamplingSink(ssink0); - fbos[0].resetSamplingSink(gl); // validate - fboTexs[0] = fbos[0].getSamplingSink(); - - fbos[1].attachColorbuffer(gl, 0, true); // MSAA requires alpha - fbos[1].attachRenderbuffer(gl, Type.DEPTH, 24); - final FBObject ssink1 = new FBObject(); - { - ssink1.reset(gl, width, height); - ssink1.attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - ssink1.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); - } - fbos[1].setSamplingSink(ssink1); - fbos[1].resetSamplingSink(gl); // validate - fboTexs[1] = fbos[1].getSamplingSink(); - } else { - fboTexs[0] = fbos[0].attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - fbos[0].attachRenderbuffer(gl, Type.DEPTH, 24); - fboTexs[1] = fbos[1].attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - fbos[1].attachRenderbuffer(gl, Type.DEPTH, 24); - } - fbos[0].unbind(gl); - fbos[1].unbind(gl); - } - - @SuppressWarnings("unused") - private void resetFBOs(final GL gl, final int width, final int height) { - fbos[0].reset(gl, width, height, numSamples, true); - fbos[1].reset(gl, width, height, numSamples, true); - if(fbos[0].getNumSamples() != fbos[1].getNumSamples()) { - throw new InternalError("sample size mismatch: \n\t0: "+fbos[0]+"\n\t1: "+fbos[1]); - } - numSamples = fbos[0].getNumSamples(); - if(numSamples>0) { - fboTexs[0] = fbos[0].getSamplingSink(); - fboTexs[1] = fbos[1].getSamplingSink(); - } else { - fboTexs[0] = (TextureAttachment) fbos[0].getColorbuffer(0); - fboTexs[1] = (TextureAttachment) fbos[1].getColorbuffer(0); - } - } - - @Override - public void init(final GLAutoDrawable drawable) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - dist.init(gl); - - // We will do some offscreen rendering, setup FBO... - if( null != upstream ) { - final int[] textureSize = dist.textureSize; - initFBOs(gl, textureSize[0], textureSize[1]); - upstream.init(drawable); - } - - gl.setSwapInterval(1); - } - - @Override - public void dispose(final GLAutoDrawable drawable) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - // FIXME complete release - if( null != upstream ) { - upstream.dispose(drawable); - fbos[0].destroy(gl); - fbos[1].destroy(gl); - } - if( ownsDist ) { - dist.dispose(gl); - } - } - - @Override - public void display(final GLAutoDrawable drawable) { - final ovrFrameTiming frameTiming = OVR.ovrHmd_BeginFrameTiming(dist.hmdCtx, 0); - - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - if(0 < numSamples) { - gl.glEnable(GL.GL_MULTISAMPLE); - } - - // FIXME: Instead of setting the viewport, - // it's better to change the projection matrix! - if( null != upstream ) { - for(int eyeNum=0; eyeNum<2; eyeNum++) { - // final ovrPosef eyeRenderPose = OVR.ovrHmd_GetEyePose(hmdCtx, eyeNum); - // final float[] eyePos = OVRUtil.getVec3f(eyeRenderPose.getPosition()); - fbos[eyeNum].bind(gl); - - final OVRDistortion.EyeData eyeDist = dist.eyes[eyeNum]; - final int[] viewport = eyeDist.viewport; - gl.glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); - - dist.updateEyePose(eyeNum); - upstream.reshapeEye(drawable, viewport[0], viewport[1], viewport[2], viewport[3], - dist.getEyeParam(eyeNum), dist.updateEyePose(eyeNum)); - upstream.display(drawable, eyeNum > 0 ? CustomRendererListener.DISPLAY_REPEAT : 0); - fbos[eyeNum].unbind(gl); - } - gl.glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); - } - - gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - gl.glClear(GL.GL_COLOR_BUFFER_BIT); - gl.glActiveTexture(GL.GL_TEXTURE0 + dist.texUnit0.intValue()); - - if( null != upstream ) { - dist.displayOneEyePre(gl, frameTiming.getTimewarpPointSeconds()); - fbos[0].use(gl, fboTexs[0]); - dist.displayOneEye(gl, 0); - fbos[0].unuse(gl); - fbos[1].use(gl, fboTexs[1]); - dist.displayOneEye(gl, 1); - fbos[1].unuse(gl); - dist.displayOneEyePost(gl); - } else { - dist.display(gl, frameTiming.getTimewarpPointSeconds()); - } - - if( !drawable.getAutoSwapBufferMode() ) { - drawable.swapBuffers(); - } - OVR.ovrHmd_EndFrameTiming(dist.hmdCtx); - } - - @Override - public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { - if( !drawable.getAutoSwapBufferMode() ) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - gl.glViewport(0, 0, width, height); - } - } -} diff --git a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererSingleFBO.java b/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererSingleFBO.java deleted file mode 100644 index b18d1634e..000000000 --- a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererSingleFBO.java +++ /dev/null @@ -1,206 +0,0 @@ -/** - * Copyright 2014 JogAmp Community. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of JogAmp Community. - */ -package com.jogamp.opengl.oculusvr; - -import javax.media.opengl.GL; -import javax.media.opengl.GL2ES2; -import javax.media.opengl.GLAutoDrawable; -import javax.media.opengl.GLEventListener; - -import jogamp.opengl.oculusvr.OVRDistortion; - -import com.jogamp.oculusvr.OVR; -import com.jogamp.oculusvr.ovrFrameTiming; -import com.jogamp.opengl.FBObject; -import com.jogamp.opengl.FBObject.Attachment; -import com.jogamp.opengl.FBObject.TextureAttachment; -import com.jogamp.opengl.FBObject.Attachment.Type; -import com.jogamp.opengl.util.CustomRendererListener; -import com.jogamp.opengl.util.stereo.StereoRendererListener; - -/** - * OculusVR (OVR) <i>Side By Side</i> Distortion Renderer utilizing {@link OVRDistortion} - * implementing {@link GLEventListener} for convenience. - * <p> - * Implementation renders an {@link StereoRendererListener} instance - * side-by-side within one {@link FBObject} according to {@link OVRDistortion}. - * </p> - */ -public class OVRSBSRendererSingleFBO implements GLEventListener { - - private final OVRDistortion dist; - private final boolean ownsDist; - private final StereoRendererListener upstream; - private final FBObject fbo; - private final int magFilter; - private final int minFilter; - - private int numSamples; - private TextureAttachment fboTex; - - /** - * @param dist {@link OVRDistortion} instance used for rendering. - * @param ownsDist if true, {@link OVRDistortion#dispose(GL2ES2)} is issued on this instance's {@link #dispose(GLAutoDrawable)} method, otherwise not. - * @param upstream the upstream {@link StereoRendererListener}, a.k.a the <i>content</i> to render for both eyes - * @param magFilter if > 0 value for {@link GL#GL_TEXTURE_MAG_FILTER} - * @param minFilter if > 0 value for {@link GL#GL_TEXTURE_MIN_FILTER} - * @param numSamples sample-count, if > 0 using multisampling w/ given samples, otherwise no multisampling applies - */ - public OVRSBSRendererSingleFBO(final OVRDistortion dist, final boolean ownsDist, final StereoRendererListener upstream, - final int magFilter, final int minFilter, final int numSamples) { - this.dist = dist; - this.ownsDist = ownsDist; - this.upstream = upstream; - this.fbo = new FBObject(); - this.magFilter = magFilter; - this.minFilter = minFilter; - - this.numSamples = numSamples; - } - - private void initFBOs(final GL gl, final int width, final int height) { - // remove all texture attachments, since MSAA uses just color-render-buffer - // and non-MSAA uses texture2d-buffer - fbo.detachAllColorbuffer(gl); - - fbo.reset(gl, width, height, numSamples, false); - numSamples = fbo.getNumSamples(); - - if(numSamples>0) { - fbo.attachColorbuffer(gl, 0, true); // MSAA requires alpha - fbo.attachRenderbuffer(gl, Type.DEPTH, 24); - final FBObject ssink = new FBObject(); - { - ssink.reset(gl, width, height); - ssink.attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - ssink.attachRenderbuffer(gl, Attachment.Type.DEPTH, 24); - } - fbo.setSamplingSink(ssink); - fbo.resetSamplingSink(gl); // validate - fboTex = fbo.getSamplingSink(); - } else { - fboTex = fbo.attachTexture2D(gl, 0, false, magFilter, minFilter, GL.GL_CLAMP_TO_EDGE, GL.GL_CLAMP_TO_EDGE); - fbo.attachRenderbuffer(gl, Type.DEPTH, 24); - } - fbo.unbind(gl); - } - - @SuppressWarnings("unused") - private void resetFBOs(final GL gl, final int width, final int height) { - fbo.reset(gl, width, height, numSamples, true); - numSamples = fbo.getNumSamples(); - if(numSamples>0) { - fboTex = fbo.getSamplingSink(); - } else { - fboTex = (TextureAttachment) fbo.getColorbuffer(0); - } - } - - @Override - public void init(final GLAutoDrawable drawable) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - dist.init(gl); - - // We will do some offscreen rendering, setup FBO... - if( null != upstream ) { - final int[] textureSize = dist.textureSize; - initFBOs(gl, textureSize[0], textureSize[1]); - upstream.init(drawable); - } - - gl.setSwapInterval(1); - } - - @Override - public void dispose(final GLAutoDrawable drawable) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - // FIXME complete release - if( null != upstream ) { - upstream.dispose(drawable); - fbo.destroy(gl); - } - if( ownsDist ) { - dist.dispose(gl); - } - } - - @Override - public void display(final GLAutoDrawable drawable) { - final ovrFrameTiming frameTiming = OVR.ovrHmd_BeginFrameTiming(dist.hmdCtx, 0); - - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - if(0 < numSamples) { - gl.glEnable(GL.GL_MULTISAMPLE); - } - - // FIXME: Instead of setting the viewport, - // it's better to change the projection matrix! - if( null != upstream ) { - fbo.bind(gl); - - for(int eyeNum=0; eyeNum<2; eyeNum++) { - // final ovrPosef eyeRenderPose = OVR.ovrHmd_GetEyePose(hmdCtx, eyeNum); - // final float[] eyePos = OVRUtil.getVec3f(eyeRenderPose.getPosition()); - final OVRDistortion.EyeData eyeDist = dist.eyes[eyeNum]; - final int[] viewport = eyeDist.viewport; - gl.glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); - - upstream.reshapeEye(drawable, viewport[0], viewport[1], viewport[2], viewport[3], - dist.getEyeParam(eyeNum), dist.updateEyePose(eyeNum)); - upstream.display(drawable, eyeNum > 0 ? CustomRendererListener.DISPLAY_REPEAT | CustomRendererListener.DISPLAY_DONTCLEAR : 0); - } - fbo.unbind(gl); - gl.glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); - } - - gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); - gl.glClear(GL.GL_COLOR_BUFFER_BIT); - gl.glActiveTexture(GL.GL_TEXTURE0 + dist.texUnit0.intValue()); - - if( null != upstream ) { - fbo.use(gl, fboTex); - dist.display(gl, frameTiming.getTimewarpPointSeconds()); - fbo.unuse(gl); - } else { - dist.display(gl, frameTiming.getTimewarpPointSeconds()); - } - - if( !drawable.getAutoSwapBufferMode() ) { - drawable.swapBuffers(); - } - OVR.ovrHmd_EndFrameTiming(dist.hmdCtx); - } - - @Override - public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { - if( !drawable.getAutoSwapBufferMode() ) { - final GL2ES2 gl = drawable.getGL().getGL2ES2(); - gl.glViewport(0, 0, width, height); - } - } -} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRDistortion.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRDistortion.java deleted file mode 100644 index c32270ac4..000000000 --- a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRDistortion.java +++ /dev/null @@ -1,690 +0,0 @@ -/** - * Copyright 2014 JogAmp Community. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF - * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * The views and conclusions contained in the software and documentation are those of the - * authors and should not be interpreted as representing official policies, either expressed - * or implied, of JogAmp Community. - */ -package jogamp.opengl.oculusvr; - -import java.nio.FloatBuffer; -import java.nio.ShortBuffer; - -import javax.media.opengl.GL; -import javax.media.opengl.GL2ES2; -import javax.media.opengl.GLArrayData; -import javax.media.opengl.GLEventListener; -import javax.media.opengl.GLException; -import javax.media.opengl.GLUniformData; - -import jogamp.common.os.PlatformPropsImpl; - -import com.jogamp.common.nio.Buffers; -import com.jogamp.common.os.Platform; -import com.jogamp.oculusvr.OVR; -import com.jogamp.oculusvr.OVRException; -import com.jogamp.oculusvr.OvrHmdContext; -import com.jogamp.oculusvr.ovrDistortionMesh; -import com.jogamp.oculusvr.ovrDistortionVertex; -import com.jogamp.oculusvr.ovrEyeRenderDesc; -import com.jogamp.oculusvr.ovrFovPort; -import com.jogamp.oculusvr.ovrMatrix4f; -import com.jogamp.oculusvr.ovrPosef; -import com.jogamp.oculusvr.ovrRecti; -import com.jogamp.oculusvr.ovrSizei; -import com.jogamp.oculusvr.ovrVector2f; -import com.jogamp.oculusvr.ovrVector3f; -import com.jogamp.opengl.JoglVersion; -import com.jogamp.opengl.math.FloatUtil; -import com.jogamp.opengl.math.Quaternion; -import com.jogamp.opengl.math.VectorUtil; -import com.jogamp.opengl.util.CustomRendererListener; -import com.jogamp.opengl.util.GLArrayDataServer; -import com.jogamp.opengl.util.glsl.ShaderCode; -import com.jogamp.opengl.util.glsl.ShaderProgram; -import com.jogamp.opengl.util.stereo.EyeParameter; -import com.jogamp.opengl.util.stereo.EyePose; - -/** - * OculusVR Distortion Data and OpenGL Renderer Utility - */ -public class OVRDistortion { - public static final float[] VEC3_UP = { 0f, 1f, 0f }; - public static final float[] VEC3_FORWARD = { 0f, 0f, -1f }; - - private static final String shaderPrefix01 = "dist01"; - private static final String shaderTimewarpSuffix = "_timewarp"; - private static final String shaderChromaSuffix = "_chroma"; - private static final String shaderPlainSuffix = "_plain"; - - public static boolean useTimewarp(final int distortionCaps) { return 0 != ( distortionCaps & OVR.ovrDistortionCap_TimeWarp ) ; } - public static boolean useChromatic(final int distortionCaps) { return 0 != ( distortionCaps & OVR.ovrDistortionCap_Chromatic ) ; } - public static boolean useVignette(final int distortionCaps) { return 0 != ( distortionCaps & OVR.ovrDistortionCap_Vignette ) ; } - - public static class EyeData { - public final int eyeName; - public final int distortionCaps; - public final int vertexCount; - public final int indexCount; - public final int[/*4*/] viewport; - - public final GLUniformData eyeToSourceUVScale; - public final GLUniformData eyeToSourceUVOffset; - public final GLUniformData eyeRotationStart; - public final GLUniformData eyeRotationEnd; - - /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */ - public final GLArrayDataServer iVBO; - public final GLArrayData vboPos, vboParams, vboTexCoordsR, vboTexCoordsG, vboTexCoordsB; - public final GLArrayDataServer indices; - - public final ovrEyeRenderDesc ovrEyeDesc; - public final ovrFovPort ovrEyeFov; - public final EyeParameter eyeParameter; - - public ovrPosef ovrEyePose; - public EyePose eyePose; - - public final boolean useTimewarp() { return OVRDistortion.useTimewarp(distortionCaps); } - public final boolean useChromatic() { return OVRDistortion.useChromatic(distortionCaps); } - public final boolean useVignette() { return OVRDistortion.useVignette(distortionCaps); } - - private EyeData(final OvrHmdContext hmdCtx, final int distortionCaps, - final float[] eyePositionOffset, final ovrEyeRenderDesc eyeDesc, - final ovrSizei ovrTextureSize, final int[] eyeRenderViewport) { - this.eyeName = eyeDesc.getEye(); - this.distortionCaps = distortionCaps; - viewport = new int[4]; - System.arraycopy(eyeRenderViewport, 0, viewport, 0, 4); - - final FloatBuffer fstash = Buffers.newDirectFloatBuffer(2+2+16+26); - - eyeToSourceUVScale = new GLUniformData("ovr_EyeToSourceUVScale", 2, Buffers.slice2Float(fstash, 0, 2)); - eyeToSourceUVOffset = new GLUniformData("ovr_EyeToSourceUVOffset", 2, Buffers.slice2Float(fstash, 2, 2)); - - if( useTimewarp() ) { - eyeRotationStart = new GLUniformData("ovr_EyeRotationStart", 4, 4, Buffers.slice2Float(fstash, 4, 16)); - eyeRotationEnd = new GLUniformData("ovr_EyeRotationEnd", 4, 4, Buffers.slice2Float(fstash, 20, 16)); - } else { - eyeRotationStart = null; - eyeRotationEnd = null; - } - - this.ovrEyeDesc = eyeDesc; - this.ovrEyeFov = eyeDesc.getFov(); - - final ovrVector3f eyeViewAdjust = eyeDesc.getViewAdjust(); - this.eyeParameter = new EyeParameter(eyeName, eyePositionOffset, OVRUtil.getFovHV(ovrEyeFov), - eyeViewAdjust.getX(), eyeViewAdjust.getY(), eyeViewAdjust.getZ()); - - this.eyePose = new EyePose(eyeName); - - updateEyePose(hmdCtx); - - final ovrDistortionMesh meshData = ovrDistortionMesh.create(); - final ovrFovPort fov = eyeDesc.getFov(); - - if( !OVR.ovrHmd_CreateDistortionMesh(hmdCtx, eyeName, fov, distortionCaps, meshData) ) { - throw new OVRException("Failed to create meshData for eye "+eyeName+" and "+OVRUtil.toString(fov)); - } - vertexCount = meshData.getVertexCount(); - indexCount = meshData.getIndexCount(); - - /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */ - final boolean useChromatic = useChromatic(); - final boolean useVignette = useVignette(); - final int compsPerElement = 2+2+2+( useChromatic ? 2+2 /* texCoordG + texCoordB */: 0 ); - iVBO = GLArrayDataServer.createGLSLInterleaved(compsPerElement, GL.GL_FLOAT, false, vertexCount, GL.GL_STATIC_DRAW); - vboPos = iVBO.addGLSLSubArray("ovr_Position", 2, GL.GL_ARRAY_BUFFER); - vboParams = iVBO.addGLSLSubArray("ovr_Params", 2, GL.GL_ARRAY_BUFFER); - vboTexCoordsR = iVBO.addGLSLSubArray("ovr_TexCoordR", 2, GL.GL_ARRAY_BUFFER); - if( useChromatic ) { - vboTexCoordsG = iVBO.addGLSLSubArray("ovr_TexCoordG", 2, GL.GL_ARRAY_BUFFER); - vboTexCoordsB = iVBO.addGLSLSubArray("ovr_TexCoordB", 2, GL.GL_ARRAY_BUFFER); - } else { - vboTexCoordsG = null; - vboTexCoordsB = null; - } - indices = GLArrayDataServer.createData(1, GL.GL_SHORT, indexCount, GL.GL_STATIC_DRAW, GL.GL_ELEMENT_ARRAY_BUFFER); - - // Setup: eyeToSourceUVScale, eyeToSourceUVOffset - { - final ovrVector2f[] uvScaleOffsetOut = new ovrVector2f[2]; - uvScaleOffsetOut[0] = ovrVector2f.create(); // FIXME: remove ctor / double check - uvScaleOffsetOut[1] = ovrVector2f.create(); - - final ovrRecti ovrEyeRenderViewport = OVRUtil.createOVRRecti(eyeRenderViewport); - OVR.ovrHmd_GetRenderScaleAndOffset(fov, ovrTextureSize, ovrEyeRenderViewport, uvScaleOffsetOut); - if( OVRUtil.DEBUG ) { - System.err.println("XXX."+eyeName+": fov "+OVRUtil.toString(fov)); - System.err.println("XXX."+eyeName+": uvScale "+OVRUtil.toString(uvScaleOffsetOut[0])); - System.err.println("XXX."+eyeName+": uvOffset "+OVRUtil.toString(uvScaleOffsetOut[0])); - System.err.println("XXX."+eyeName+": textureSize "+OVRUtil.toString(ovrTextureSize)); - System.err.println("XXX."+eyeName+": viewport "+OVRUtil.toString(ovrEyeRenderViewport)); - } - final FloatBuffer eyeToSourceUVScaleFB = eyeToSourceUVScale.floatBufferValue(); - eyeToSourceUVScaleFB.put(0, uvScaleOffsetOut[0].getX()); - eyeToSourceUVScaleFB.put(1, uvScaleOffsetOut[0].getY()); - final FloatBuffer eyeToSourceUVOffsetFB = eyeToSourceUVOffset.floatBufferValue(); - eyeToSourceUVOffsetFB.put(0, uvScaleOffsetOut[1].getX()); - eyeToSourceUVOffsetFB.put(1, uvScaleOffsetOut[1].getY()); - } - - /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */ - final FloatBuffer iVBOFB = (FloatBuffer)iVBO.getBuffer(); - final ovrDistortionVertex[] ovRes = new ovrDistortionVertex[1]; - ovRes[0] = ovrDistortionVertex.create(); // FIXME: remove ctor / double check - - for ( int vertNum = 0; vertNum < vertexCount; vertNum++ ) { - final ovrDistortionVertex ov = meshData.getPVertexData(vertNum, ovRes)[0]; - ovrVector2f v; - - // pos - v = ov.getPos(); - iVBOFB.put(v.getX()); - iVBOFB.put(v.getY()); - - // params - if( useVignette ) { - iVBOFB.put(ov.getVignetteFactor()); - } else { - iVBOFB.put(1.0f); - } - iVBOFB.put(ov.getTimeWarpFactor()); - - // texCoordR - v = ov.getTexR(); - iVBOFB.put(v.getX()); - iVBOFB.put(v.getY()); - - if( useChromatic ) { - // texCoordG - v = ov.getTexG(); - iVBOFB.put(v.getX()); - iVBOFB.put(v.getY()); - - // texCoordB - v = ov.getTexB(); - iVBOFB.put(v.getX()); - iVBOFB.put(v.getY()); - } - } - if( OVRUtil.DEBUG ) { - System.err.println("XXX."+eyeName+": iVBO "+iVBO); - } - { - final ShortBuffer in = meshData.getPIndexData(); - final ShortBuffer out = (ShortBuffer) indices.getBuffer(); - out.put(in); - } - if( OVRUtil.DEBUG ) { - System.err.println("XXX."+eyeName+": idx "+indices); - System.err.println("XXX."+eyeName+": distEye "+this); - } - OVR.ovrHmd_DestroyDistortionMesh(meshData); - } - - private void linkData(final GL2ES2 gl, final ShaderProgram sp) { - if( 0 > vboPos.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+vboPos); - } - if( 0 > vboParams.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+vboParams); - } - if( 0 > vboTexCoordsR.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+vboTexCoordsR); - } - if( useChromatic() ) { - if( 0 > vboTexCoordsG.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+vboTexCoordsG); - } - if( 0 > vboTexCoordsB.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+vboTexCoordsB); - } - } - if( 0 > eyeToSourceUVScale.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+eyeToSourceUVScale); - } - if( 0 > eyeToSourceUVOffset.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+eyeToSourceUVOffset); - } - if( useTimewarp() ) { - if( 0 > eyeRotationStart.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+eyeRotationStart); - } - if( 0 > eyeRotationEnd.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+eyeRotationEnd); - } - } - iVBO.seal(gl, true); - iVBO.enableBuffer(gl, false); - indices.seal(gl, true); - indices.enableBuffer(gl, false); - } - - public void dispose(final GL2ES2 gl) { - iVBO.destroy(gl); - indices.destroy(gl); - } - public void enableVBO(final GL2ES2 gl, final boolean enable) { - iVBO.enableBuffer(gl, enable); - indices.bindBuffer(gl, enable); // keeps VBO binding if enable:=true - } - - public void updateUniform(final GL2ES2 gl, final ShaderProgram sp) { - gl.glUniform(eyeToSourceUVScale); - gl.glUniform(eyeToSourceUVOffset); - if( useTimewarp() ) { - gl.glUniform(eyeRotationStart); - gl.glUniform(eyeRotationEnd); - } - } - - public void updateTimewarp(final OvrHmdContext hmdCtx, final ovrPosef eyeRenderPose, final float[] mat4Tmp1, final float[] mat4Tmp2) { - final ovrMatrix4f[] timeWarpMatrices = new ovrMatrix4f[2]; - timeWarpMatrices[0] = ovrMatrix4f.create(); // FIXME: remove ctor / double check - timeWarpMatrices[1] = ovrMatrix4f.create(); - OVR.ovrHmd_GetEyeTimewarpMatrices(hmdCtx, eyeName, eyeRenderPose, timeWarpMatrices); - - final float[] eyeRotationStartM = FloatUtil.transposeMatrix(timeWarpMatrices[0].getM(0, mat4Tmp1), mat4Tmp2); - final FloatBuffer eyeRotationStartU = eyeRotationStart.floatBufferValue(); - eyeRotationStartU.put(eyeRotationStartM); - eyeRotationStartU.rewind(); - - final float[] eyeRotationEndM = FloatUtil.transposeMatrix(timeWarpMatrices[1].getM(0, mat4Tmp1), mat4Tmp2); - final FloatBuffer eyeRotationEndU = eyeRotationEnd.floatBufferValue(); - eyeRotationEndU.put(eyeRotationEndM); - eyeRotationEndU.rewind(); - } - - /** - * Updates {@link #ovrEyePose} and it's extracted - * {@link #eyeRenderPoseOrientation} and {@link #eyeRenderPosePosition}. - * @param hmdCtx used get the {@link #ovrEyePose} via {@link OVR#ovrHmd_GetEyePose(OvrHmdContext, int)} - */ - public EyePose updateEyePose(final OvrHmdContext hmdCtx) { - ovrEyePose = OVR.ovrHmd_GetEyePose(hmdCtx, eyeName); - final ovrVector3f pos = ovrEyePose.getPosition(); - eyePose.setPosition(pos.getX(), pos.getY(), pos.getZ()); - OVRUtil.copyToQuaternion(ovrEyePose.getOrientation(), eyePose.orientation); - return eyePose; - } - - @Override - public String toString() { - return "Eye["+eyeName+", viewport "+viewport[0]+"/"+viewport[1]+" "+viewport[2]+"x"+viewport[3]+ - ", "+eyeParameter+ - ", vertices "+vertexCount+", indices "+indexCount+ - ", uvScale["+eyeToSourceUVScale.floatBufferValue().get(0)+", "+eyeToSourceUVScale.floatBufferValue().get(1)+ - "], uvOffset["+eyeToSourceUVOffset.floatBufferValue().get(0)+", "+eyeToSourceUVOffset.floatBufferValue().get(1)+ - "], desc"+OVRUtil.toString(ovrEyeDesc)+", "+eyePose+"]"; - } - } - - public final OvrHmdContext hmdCtx; - public final EyeData[] eyes; - public final int distortionCaps; - public final int[/*2*/] textureSize; - public final GLUniformData texUnit0; - public final boolean usesDistMesh; - - private final float[] mat4Tmp1 = new float[16]; - private final float[] mat4Tmp2 = new float[16]; - - private ShaderProgram sp; - - @Override - public String toString() { - return "OVRDist[caps 0x"+Integer.toHexString(distortionCaps)+", "+ - ", tex "+textureSize[0]+"x"+textureSize[1]+ - ", vignette "+useVignette()+", chromatic "+useChromatic()+", timewarp "+useTimewarp()+ - ", "+PlatformPropsImpl.NEWLINE+" "+eyes[0]+", "+PlatformPropsImpl.NEWLINE+" "+eyes[1]+"]"; - } - - public static OVRDistortion create(final OvrHmdContext hmdCtx, final boolean sbsSingleTexture, - final float[] eyePositionOffset, final ovrFovPort[] eyeFov, - final float pixelsPerDisplayPixel, final int distortionCaps) { - final ovrEyeRenderDesc[] eyeRenderDesc = new ovrEyeRenderDesc[2]; - eyeRenderDesc[0] = OVR.ovrHmd_GetRenderDesc(hmdCtx, OVR.ovrEye_Left, eyeFov[0]); - eyeRenderDesc[1] = OVR.ovrHmd_GetRenderDesc(hmdCtx, OVR.ovrEye_Right, eyeFov[1]); - if( OVRUtil.DEBUG ) { - System.err.println("XXX: eyeRenderDesc[0] "+OVRUtil.toString(eyeRenderDesc[0])); - System.err.println("XXX: eyeRenderDesc[1] "+OVRUtil.toString(eyeRenderDesc[1])); - } - - final ovrSizei recommenedTex0Size = OVR.ovrHmd_GetFovTextureSize(hmdCtx, OVR.ovrEye_Left, eyeRenderDesc[0].getFov(), pixelsPerDisplayPixel); - final ovrSizei recommenedTex1Size = OVR.ovrHmd_GetFovTextureSize(hmdCtx, OVR.ovrEye_Right, eyeRenderDesc[1].getFov(), pixelsPerDisplayPixel); - if( OVRUtil.DEBUG ) { - System.err.println("XXX: recommenedTex0Size "+OVRUtil.toString(recommenedTex0Size)); - System.err.println("XXX: recommenedTex1Size "+OVRUtil.toString(recommenedTex1Size)); - } - final int[] textureSize = new int[2]; - if( sbsSingleTexture ) { - textureSize[0] = recommenedTex0Size.getW() + recommenedTex1Size.getW(); - } else { - textureSize[0] = Math.max(recommenedTex0Size.getW(), recommenedTex1Size.getW()); - } - textureSize[1] = Math.max(recommenedTex0Size.getH(), recommenedTex1Size.getH()); - if( OVRUtil.DEBUG ) { - System.err.println("XXX: textureSize "+textureSize[0]+"x"+textureSize[1]); - } - - final int[][] eyeRenderViewports = new int[2][4]; - if( sbsSingleTexture ) { - eyeRenderViewports[0][0] = 0; - eyeRenderViewports[0][1] = 0; - eyeRenderViewports[0][2] = textureSize[0] / 2; - eyeRenderViewports[0][3] = textureSize[1]; - eyeRenderViewports[1][0] = (textureSize[0] + 1) / 2; - eyeRenderViewports[1][1] = 0; - eyeRenderViewports[1][2] = textureSize[0] / 2; - eyeRenderViewports[1][3] = textureSize[1]; - } else { - eyeRenderViewports[0][0] = 0; - eyeRenderViewports[0][1] = 0; - eyeRenderViewports[0][2] = textureSize[0]; - eyeRenderViewports[0][3] = textureSize[1]; - eyeRenderViewports[1][0] = 0; - eyeRenderViewports[1][1] = 0; - eyeRenderViewports[1][2] = textureSize[0]; - eyeRenderViewports[1][3] = textureSize[1]; - } - return new OVRDistortion(hmdCtx, sbsSingleTexture, eyePositionOffset, eyeRenderDesc, textureSize, eyeRenderViewports, distortionCaps, 0); - } - - public OVRDistortion(final OvrHmdContext hmdCtx, final boolean sbsSingleTexture, - final float[] eyePositionOffset, final ovrEyeRenderDesc[] eyeRenderDescs, - final int[] textureSize, final int[][] eyeRenderViewports, - final int distortionCaps, final int textureUnit) { - this.hmdCtx = hmdCtx; - this.eyes = new EyeData[2]; - this.distortionCaps = distortionCaps; - this.textureSize = new int[2]; - System.arraycopy(textureSize, 0, this.textureSize, 0, 2); - - texUnit0 = new GLUniformData("ovr_Texture0", textureUnit); - usesDistMesh = true; - - final ovrSizei ovrTextureSize = OVRUtil.createOVRSizei(textureSize); - eyes[0] = new EyeData(hmdCtx, distortionCaps, eyePositionOffset, eyeRenderDescs[0], ovrTextureSize, eyeRenderViewports[0]); - eyes[1] = new EyeData(hmdCtx, distortionCaps, eyePositionOffset, eyeRenderDescs[1], ovrTextureSize, eyeRenderViewports[1]); - sp = null; - } - - public final boolean useTimewarp() { return useTimewarp(distortionCaps); } - public final boolean useChromatic() { return useChromatic(distortionCaps); } - public final boolean useVignette() { return useVignette(distortionCaps); } - - public void updateTimewarp(final ovrPosef eyeRenderPose, final int eyeNum) { - eyes[eyeNum].updateTimewarp(hmdCtx, eyeRenderPose, mat4Tmp1, mat4Tmp2); - } - public void updateTimewarp(final ovrPosef[] eyeRenderPoses) { - eyes[0].updateTimewarp(hmdCtx, eyeRenderPoses[0], mat4Tmp1, mat4Tmp2); - eyes[1].updateTimewarp(hmdCtx, eyeRenderPoses[1], mat4Tmp1, mat4Tmp2); - } - - public void enableVBO(final GL2ES2 gl, final boolean enable, final int eyeNum) { - if( null == sp ) { - throw new IllegalStateException("Not initialized"); - } - eyes[eyeNum].enableVBO(gl, enable); - } - - public final ShaderProgram getShaderProgram() { return sp; } - - public void init(final GL2ES2 gl) { - if( OVRUtil.DEBUG ) { - System.err.println(JoglVersion.getGLInfo(gl, null).toString()); - } - if( null != sp ) { - throw new IllegalStateException("Already initialized"); - } - final String vertexShaderBasename; - final String fragmentShaderBasename; - { - final StringBuilder sb = new StringBuilder(); - sb.append(shaderPrefix01); - if( !useChromatic() && !useTimewarp() ) { - sb.append(shaderPlainSuffix); - } else if( useChromatic() && !useTimewarp() ) { - sb.append(shaderChromaSuffix); - } else if( useTimewarp() ) { - sb.append(shaderTimewarpSuffix); - if( useChromatic() ) { - sb.append(shaderChromaSuffix); - } - } - vertexShaderBasename = sb.toString(); - sb.setLength(0); - sb.append(shaderPrefix01); - if( useChromatic() ) { - sb.append(shaderChromaSuffix); - } else { - sb.append(shaderPlainSuffix); - } - fragmentShaderBasename = sb.toString(); - } - final ShaderCode vp0 = ShaderCode.create(gl, GL2ES2.GL_VERTEX_SHADER, OVRDistortion.class, "shader", - "shader/bin", vertexShaderBasename, true); - final ShaderCode fp0 = ShaderCode.create(gl, GL2ES2.GL_FRAGMENT_SHADER, OVRDistortion.class, "shader", - "shader/bin", fragmentShaderBasename, true); - vp0.defaultShaderCustomization(gl, true, true); - fp0.defaultShaderCustomization(gl, true, true); - - sp = new ShaderProgram(); - sp.add(gl, vp0, System.err); - sp.add(gl, fp0, System.err); - if(!sp.link(gl, System.err)) { - throw new GLException("could not link program: "+sp); - } - sp.useProgram(gl, true); - if( 0 > texUnit0.setLocation(gl, sp.program()) ) { - throw new OVRException("Couldn't locate "+texUnit0); - } - eyes[0].linkData(gl, sp); - eyes[1].linkData(gl, sp); - sp.useProgram(gl, false); - } - - public void dispose(final GL2ES2 gl) { - sp.useProgram(gl, false); - eyes[0].dispose(gl); - eyes[1].dispose(gl); - sp.destroy(gl); - } - - public EyeParameter getEyeParam(final int eyeNum) { - return eyes[eyeNum].eyeParameter; - } - - /** - * Updates the {@link EyeData#ovrEyePose} via {@link EyeData#updateEyePose(OvrHmdContext)} - * for the denoted eye. - */ - public EyePose updateEyePose(final int eyeNum) { - return eyes[eyeNum].updateEyePose(hmdCtx); - } - - public void updateUniforms(final GL2ES2 gl, final int eyeNum) { - if( null == sp ) { - throw new IllegalStateException("Not initialized"); - } - gl.glUniform(texUnit0); - eyes[eyeNum].updateUniform(gl, sp); - } - - /** - * <p> - * {@link #updateEyePose(int)} must be called upfront - * when rendering upstream {@link GLEventListener}. - * </p> - * - * @param gl - * @param timewarpPointSeconds - */ - public void display(final GL2ES2 gl, final double timewarpPointSeconds) { - if( null == sp ) { - throw new IllegalStateException("Not initialized"); - } - if( useTimewarp() ) { - OVR.ovr_WaitTillTime(timewarpPointSeconds); - } - gl.glDisable(GL.GL_CULL_FACE); - gl.glDisable(GL.GL_DEPTH_TEST); - gl.glDisable(GL.GL_BLEND); - - if( !gl.isGLcore() ) { - gl.glEnable(GL.GL_TEXTURE_2D); - } - - sp.useProgram(gl, true); - - gl.glUniform(texUnit0); - - for(int eyeNum=0; eyeNum<2; eyeNum++) { - final EyeData eye = eyes[eyeNum]; - if( useTimewarp() ) { - eye.updateTimewarp(hmdCtx, eye.ovrEyePose, mat4Tmp1, mat4Tmp2); - } - eye.updateUniform(gl, sp); - eye.enableVBO(gl, true); - if( usesDistMesh ) { - gl.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL.GL_UNSIGNED_SHORT, 0); - } else { - gl.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, eye.vertexCount); - } - eyes[eyeNum].enableVBO(gl, false); - } - - sp.useProgram(gl, false); - } - - /** - * - * @param gl - * @param timewarpPointSeconds - */ - public void displayOneEyePre(final GL2ES2 gl, final double timewarpPointSeconds) { - if( null == sp ) { - throw new IllegalStateException("Not initialized"); - } - if( useTimewarp() ) { - OVR.ovr_WaitTillTime(timewarpPointSeconds); - } - gl.glDisable(GL.GL_CULL_FACE); - gl.glDisable(GL.GL_DEPTH_TEST); - gl.glDisable(GL.GL_BLEND); - - if( !gl.isGLcore() ) { - gl.glEnable(GL.GL_TEXTURE_2D); - } - - sp.useProgram(gl, true); - - gl.glUniform(texUnit0); - } - - /** - * <p> - * {@link #updateEyePose(int)} must be called upfront - * when rendering upstream {@link GLEventListener}. - * </p> - * - * @param gl - * @param eyeNum - */ - public void displayOneEye(final GL2ES2 gl, final int eyeNum) { - if( null == sp ) { - throw new IllegalStateException("Not initialized"); - } - final EyeData eye = eyes[eyeNum]; - if( useTimewarp() ) { - eye.updateTimewarp(hmdCtx, eye.ovrEyePose, mat4Tmp1, mat4Tmp2); - } - eye.updateUniform(gl, sp); - eye.enableVBO(gl, true); - if( usesDistMesh ) { - gl.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL.GL_UNSIGNED_SHORT, 0); - } else { - gl.glDrawArrays(GL.GL_TRIANGLE_STRIP, 0, eye.vertexCount); - } - eyes[eyeNum].enableVBO(gl, false); - } - - public void displayOneEyePost(final GL2ES2 gl) { - sp.useProgram(gl, false); - } - - /** - * Calculates the <i>Side By Side</i>, SBS, projection- and modelview matrix for one eye. - * <p> - * {@link #updateEyePose(int)} must be called upfront. - * </p> - * <p> - * This method merely exist as an example implementation to compute the matrices, - * which shall be adopted by the - * {@link CustomRendererListener#reshape(javax.media.opengl.GLAutoDrawable, int, int, int, int, EyeParameter, EyePose) upstream client code}. - * </p> - * @param eyeNum eye denominator - * @param zNear frustum near value - * @param zFar frustum far value - * @param mat4Projection float[16] projection matrix result - * @param mat4Modelview float[16] modelview matrix result - * @deprecated Only an example implementation, which should be adopted by the {@link CustomRendererListener#reshape(javax.media.opengl.GLAutoDrawable, int, int, int, int, EyeParameter, EyePose) upstream client code}. - */ - public void getSBSUpstreamPMV(final int eyeNum, final float zNear, final float zFar, - final float[] mat4Projection, final float[] mat4Modelview) { - final EyeData eyeDist = eyes[eyeNum]; - - final float[] vec3Tmp1 = new float[3]; - final float[] vec3Tmp2 = new float[3]; - final float[] vec3Tmp3 = new float[3]; - - // - // Projection - // - FloatUtil.makePerspective(mat4Projection, 0, true, eyeDist.eyeParameter.fovhv, zNear, zFar); - - // - // Modelview - // - final Quaternion rollPitchYaw = new Quaternion(); - // private final float eyeYaw = FloatUtil.PI; // 180 degrees in radians - // rollPitchYaw.rotateByAngleY(eyeYaw); - final float[] shiftedEyePos = rollPitchYaw.rotateVector(vec3Tmp1, 0, eyeDist.eyePose.position, 0); - VectorUtil.addVec3(shiftedEyePos, shiftedEyePos, eyeDist.eyeParameter.positionOffset); - - rollPitchYaw.mult(eyeDist.eyePose.orientation); - final float[] up = rollPitchYaw.rotateVector(vec3Tmp2, 0, VEC3_UP, 0); - final float[] forward = rollPitchYaw.rotateVector(vec3Tmp3, 0, VEC3_FORWARD, 0); - final float[] center = VectorUtil.addVec3(forward, shiftedEyePos, forward); - - final float[] mLookAt = FloatUtil.makeLookAt(mat4Tmp2, 0, shiftedEyePos, 0, center, 0, up, 0, mat4Tmp1); - final float[] mViewAdjust = FloatUtil.makeTranslation(mat4Modelview, true, - eyeDist.eyeParameter.distNoseToPupilX, - eyeDist.eyeParameter.distMiddleToPupilY, - eyeDist.eyeParameter.eyeReliefZ); - - /* mat4Modelview = */ FloatUtil.multMatrix(mViewAdjust, mLookAt); - } -} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDevice.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDevice.java new file mode 100644 index 000000000..09a348c46 --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDevice.java @@ -0,0 +1,158 @@ +/** + * Copyright 2014 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.oculusvr; + +import javax.media.nativewindow.util.Dimension; +import javax.media.nativewindow.util.DimensionImmutable; +import javax.media.nativewindow.util.PointImmutable; +import javax.media.nativewindow.util.Rectangle; +import javax.media.nativewindow.util.RectangleImmutable; + +import com.jogamp.oculusvr.OVR; +import com.jogamp.oculusvr.OvrHmdContext; +import com.jogamp.oculusvr.ovrEyeRenderDesc; +import com.jogamp.oculusvr.ovrFovPort; +import com.jogamp.oculusvr.ovrHmdDesc; +import com.jogamp.oculusvr.ovrSizei; +import com.jogamp.opengl.math.FovHVHalves; +import com.jogamp.opengl.util.stereo.StereoDevice; +import com.jogamp.opengl.util.stereo.StereoDeviceRenderer; + +public class OVRStereoDevice implements StereoDevice { + public final OvrHmdContext handle; + public final int deviceIndex; + public final ovrHmdDesc hmdDesc; + + private boolean sensorsStarted = false; + + public OVRStereoDevice(final OvrHmdContext nativeContext, final int deviceIndex) { + this.handle = nativeContext; + this.deviceIndex = deviceIndex; + this.hmdDesc = ovrHmdDesc.create(); + OVR.ovrHmd_GetDesc(handle, hmdDesc); + } + + @Override + public final void dispose() { + // NOP + } + + @Override + public final PointImmutable getPosition() { + return OVRUtil.getVec2iAsPoint(hmdDesc.getWindowsPos()); + } + + @Override + public final DimensionImmutable getSurfaceSize() { + return OVRUtil.getOVRSizei(hmdDesc.getResolution()); + } + + @Override + public final FovHVHalves[] getDefaultFOV() { + final ovrFovPort[] defaultEyeFov = hmdDesc.getDefaultEyeFov(0, new ovrFovPort[2]); + final FovHVHalves[] eyeFov = new FovHVHalves[2]; + eyeFov[0] = OVRUtil.getFovHV(defaultEyeFov[0]); + eyeFov[1] = OVRUtil.getFovHV(defaultEyeFov[1]); + return eyeFov; + } + + @Override + public final boolean startSensors(final boolean start) { + if( start && !sensorsStarted ) { + // Start the sensor which provides the Rift’s pose and motion. + final int requiredSensorCaps = 0; + final int supportedSensorCaps = requiredSensorCaps | OVR.ovrSensorCap_Orientation | OVR.ovrSensorCap_YawCorrection | OVR.ovrSensorCap_Position; + if( OVR.ovrHmd_StartSensor(handle, supportedSensorCaps, requiredSensorCaps) ) { + sensorsStarted = true; + return true; + } else { + sensorsStarted = false; + return false; + } + } else if( sensorsStarted ) { + OVR.ovrHmd_StopSensor(handle); + sensorsStarted = false; + return true; + } else { + // No state change -> Success + return true; + } + } + @Override + public boolean getSensorsStarted() { return sensorsStarted; } + + @Override + public final StereoDeviceRenderer createRenderer(final int distortionBits, + final int textureCount, final float[] eyePositionOffset, + final FovHVHalves[] eyeFov, final float pixelsPerDisplayPixel, final int textureUnit) { + final ovrFovPort ovrEyeFov0 = OVRUtil.getOVRFovPort(eyeFov[0]); + final ovrFovPort ovrEyeFov1 = OVRUtil.getOVRFovPort(eyeFov[1]); + + final ovrEyeRenderDesc[] eyeRenderDesc = new ovrEyeRenderDesc[2]; + eyeRenderDesc[0] = OVR.ovrHmd_GetRenderDesc(handle, OVR.ovrEye_Left, ovrEyeFov0); + eyeRenderDesc[1] = OVR.ovrHmd_GetRenderDesc(handle, OVR.ovrEye_Right, ovrEyeFov1); + if( StereoDevice.DEBUG ) { + System.err.println("XXX: eyeRenderDesc[0] "+OVRUtil.toString(eyeRenderDesc[0])); + System.err.println("XXX: eyeRenderDesc[1] "+OVRUtil.toString(eyeRenderDesc[1])); + } + + final ovrSizei recommenedTex0Size = OVR.ovrHmd_GetFovTextureSize(handle, OVR.ovrEye_Left, eyeRenderDesc[0].getFov(), pixelsPerDisplayPixel); + final ovrSizei recommenedTex1Size = OVR.ovrHmd_GetFovTextureSize(handle, OVR.ovrEye_Right, eyeRenderDesc[1].getFov(), pixelsPerDisplayPixel); + if( StereoDevice.DEBUG ) { + System.err.println("XXX: recommenedTex0Size "+OVRUtil.toString(recommenedTex0Size)); + System.err.println("XXX: recommenedTex1Size "+OVRUtil.toString(recommenedTex1Size)); + } + final int unifiedW = Math.max(recommenedTex0Size.getW(), recommenedTex1Size.getW()); + final int unifiedH = Math.max(recommenedTex0Size.getH(), recommenedTex1Size.getH()); + + final DimensionImmutable singleTextureSize = new Dimension(unifiedW, unifiedH); + final DimensionImmutable totalTextureSize = new Dimension(recommenedTex0Size.getW() + recommenedTex1Size.getW(), unifiedH); + if( StereoDevice.DEBUG ) { + System.err.println("XXX: textureSize Single "+singleTextureSize); + System.err.println("XXX: textureSize Total "+totalTextureSize); + } + + final RectangleImmutable[] eyeRenderViewports = new RectangleImmutable[2]; + if( 1 == textureCount ) { // validated in ctor below! + eyeRenderViewports[0] = new Rectangle(0, 0, + totalTextureSize.getWidth() / 2, + totalTextureSize.getHeight()); + + eyeRenderViewports[1] = new Rectangle((totalTextureSize.getWidth() + 1) / 2, 0, + totalTextureSize.getWidth() / 2, + totalTextureSize.getHeight()); + } else { + eyeRenderViewports[0] = new Rectangle(0, 0, + singleTextureSize.getWidth(), + singleTextureSize.getHeight()); + eyeRenderViewports[1] = eyeRenderViewports[0]; + } + return new OVRStereoDeviceRenderer(this, distortionBits, textureCount, eyePositionOffset, + eyeRenderDesc, singleTextureSize, totalTextureSize, eyeRenderViewports, textureUnit); + } +}
\ No newline at end of file diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceFactory.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceFactory.java new file mode 100644 index 000000000..06454e443 --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceFactory.java @@ -0,0 +1,51 @@ +/** + * Copyright 2014 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.oculusvr; + +import com.jogamp.oculusvr.OVR; +import com.jogamp.oculusvr.OVRVersion; +import com.jogamp.oculusvr.OvrHmdContext; +import com.jogamp.opengl.util.stereo.StereoDevice; +import com.jogamp.opengl.util.stereo.StereoDeviceFactory; + +public class OVRStereoDeviceFactory extends StereoDeviceFactory { + + public static boolean isAvailable() { + return OVR.ovr_Initialize(); // recursive .. + } + + @Override + public final StereoDevice createDevice(final int deviceIndex, final boolean verbose) { + final OvrHmdContext hmdCtx = OVR.ovrHmd_Create(deviceIndex); + final OVRStereoDevice ctx = new OVRStereoDevice(hmdCtx, deviceIndex); + if( verbose ) { + System.err.println(OVRVersion.getAvailableCapabilitiesInfo(ctx.hmdDesc, deviceIndex, null).toString()); + } + return ctx; + } +} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceRenderer.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceRenderer.java new file mode 100644 index 000000000..012ad183d --- /dev/null +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRStereoDeviceRenderer.java @@ -0,0 +1,590 @@ +/** + * Copyright 2014 JogAmp Community. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are + * permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list + * of conditions and the following disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * The views and conclusions contained in the software and documentation are those of the + * authors and should not be interpreted as representing official policies, either expressed + * or implied, of JogAmp Community. + */ +package jogamp.opengl.oculusvr; + +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +import javax.media.nativewindow.util.DimensionImmutable; +import javax.media.nativewindow.util.RectangleImmutable; +import javax.media.opengl.GL; +import javax.media.opengl.GL2ES2; +import javax.media.opengl.GLArrayData; +import javax.media.opengl.GLException; +import javax.media.opengl.GLUniformData; + +import jogamp.common.os.PlatformPropsImpl; + +import com.jogamp.common.nio.Buffers; +import com.jogamp.oculusvr.OVR; +import com.jogamp.oculusvr.OVRException; +import com.jogamp.oculusvr.OvrHmdContext; +import com.jogamp.oculusvr.ovrDistortionMesh; +import com.jogamp.oculusvr.ovrDistortionVertex; +import com.jogamp.oculusvr.ovrEyeRenderDesc; +import com.jogamp.oculusvr.ovrFovPort; +import com.jogamp.oculusvr.ovrFrameTiming; +import com.jogamp.oculusvr.ovrMatrix4f; +import com.jogamp.oculusvr.ovrPosef; +import com.jogamp.oculusvr.ovrRecti; +import com.jogamp.oculusvr.ovrSizei; +import com.jogamp.oculusvr.ovrVector2f; +import com.jogamp.oculusvr.ovrVector3f; +import com.jogamp.opengl.JoglVersion; +import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.util.GLArrayDataServer; +import com.jogamp.opengl.util.glsl.ShaderCode; +import com.jogamp.opengl.util.glsl.ShaderProgram; +import com.jogamp.opengl.util.stereo.EyeParameter; +import com.jogamp.opengl.util.stereo.EyePose; +import com.jogamp.opengl.util.stereo.StereoDevice; +import com.jogamp.opengl.util.stereo.StereoDeviceRenderer; +import com.jogamp.opengl.util.stereo.StereoUtil; + +/** + * OculusVR Distortion Data and OpenGL Renderer Utility + */ +public class OVRStereoDeviceRenderer implements StereoDeviceRenderer { + private static final String shaderPrefix01 = "dist01"; + private static final String shaderTimewarpSuffix = "_timewarp"; + private static final String shaderChromaSuffix = "_chroma"; + private static final String shaderPlainSuffix = "_plain"; + + public static class OVREye implements StereoDeviceRenderer.Eye { + private final int eyeName; + private final int distortionBits; + private final int vertexCount; + private final int indexCount; + private final RectangleImmutable viewport; + + private final GLUniformData eyeToSourceUVScale; + private final GLUniformData eyeToSourceUVOffset; + private final GLUniformData eyeRotationStart; + private final GLUniformData eyeRotationEnd; + + /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */ + private final GLArrayDataServer iVBO; + private final GLArrayData vboPos, vboParams, vboTexCoordsR, vboTexCoordsG, vboTexCoordsB; + private final GLArrayDataServer indices; + + private final ovrEyeRenderDesc ovrEyeDesc; + private final ovrFovPort ovrEyeFov; + private final EyeParameter eyeParameter; + + private ovrPosef ovrEyePose; + private final EyePose eyePose; + + @Override + public final RectangleImmutable getViewport() { return viewport; } + + @Override + public final EyeParameter getEyeParameter() { return eyeParameter; } + + @Override + public final EyePose getLastEyePose() { return eyePose; } + + private OVREye(final OvrHmdContext hmdCtx, final int distortionBits, + final float[] eyePositionOffset, final ovrEyeRenderDesc eyeDesc, + final ovrSizei ovrTextureSize, final RectangleImmutable eyeViewport) { + this.eyeName = eyeDesc.getEye(); + this.distortionBits = distortionBits; + this.viewport = eyeViewport; + + final boolean usesTimewarp = StereoUtil.usesTimewarpDistortion(distortionBits); + final FloatBuffer fstash = Buffers.newDirectFloatBuffer( 2 + 2 + ( usesTimewarp ? 16 + 16 : 0 ) ) ; + + eyeToSourceUVScale = new GLUniformData("ovr_EyeToSourceUVScale", 2, Buffers.slice2Float(fstash, 0, 2)); + eyeToSourceUVOffset = new GLUniformData("ovr_EyeToSourceUVOffset", 2, Buffers.slice2Float(fstash, 2, 2)); + + if( usesTimewarp ) { + eyeRotationStart = new GLUniformData("ovr_EyeRotationStart", 4, 4, Buffers.slice2Float(fstash, 4, 16)); + eyeRotationEnd = new GLUniformData("ovr_EyeRotationEnd", 4, 4, Buffers.slice2Float(fstash, 20, 16)); + } else { + eyeRotationStart = null; + eyeRotationEnd = null; + } + + this.ovrEyeDesc = eyeDesc; + this.ovrEyeFov = eyeDesc.getFov(); + + final ovrVector3f eyeViewAdjust = eyeDesc.getViewAdjust(); + this.eyeParameter = new EyeParameter(eyeName, eyePositionOffset, OVRUtil.getFovHV(ovrEyeFov), + eyeViewAdjust.getX(), eyeViewAdjust.getY(), eyeViewAdjust.getZ()); + + this.eyePose = new EyePose(eyeName); + + updateEyePose(hmdCtx); // 1st init + + final ovrDistortionMesh meshData = ovrDistortionMesh.create(); + final ovrFovPort fov = eyeDesc.getFov(); + + final int ovrDistortionCaps = distBits2OVRDistCaps(distortionBits); + if( !OVR.ovrHmd_CreateDistortionMesh(hmdCtx, eyeName, fov, ovrDistortionCaps, meshData) ) { + throw new OVRException("Failed to create meshData for eye "+eyeName+", "+OVRUtil.toString(fov)+" and "+StereoUtil.distortionBitsToString(distortionBits)); + } + vertexCount = meshData.getVertexCount(); + indexCount = meshData.getIndexCount(); + + /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */ + final boolean useChromatic = StereoUtil.usesChromaticDistortion(distortionBits); + final boolean useVignette = StereoUtil.usesVignetteDistortion(distortionBits); + + final int compsPerElement = 2+2+2+( useChromatic ? 2+2 /* texCoordG + texCoordB */: 0 ); + iVBO = GLArrayDataServer.createGLSLInterleaved(compsPerElement, GL.GL_FLOAT, false, vertexCount, GL.GL_STATIC_DRAW); + vboPos = iVBO.addGLSLSubArray("ovr_Position", 2, GL.GL_ARRAY_BUFFER); + vboParams = iVBO.addGLSLSubArray("ovr_Params", 2, GL.GL_ARRAY_BUFFER); + vboTexCoordsR = iVBO.addGLSLSubArray("ovr_TexCoordR", 2, GL.GL_ARRAY_BUFFER); + if( useChromatic ) { + vboTexCoordsG = iVBO.addGLSLSubArray("ovr_TexCoordG", 2, GL.GL_ARRAY_BUFFER); + vboTexCoordsB = iVBO.addGLSLSubArray("ovr_TexCoordB", 2, GL.GL_ARRAY_BUFFER); + } else { + vboTexCoordsG = null; + vboTexCoordsB = null; + } + indices = GLArrayDataServer.createData(1, GL.GL_SHORT, indexCount, GL.GL_STATIC_DRAW, GL.GL_ELEMENT_ARRAY_BUFFER); + + // Setup: eyeToSourceUVScale, eyeToSourceUVOffset + { + final ovrVector2f[] uvScaleOffsetOut = new ovrVector2f[2]; + uvScaleOffsetOut[0] = ovrVector2f.create(); // FIXME: remove ctor / double check + uvScaleOffsetOut[1] = ovrVector2f.create(); + + final ovrRecti ovrEyeRenderViewport = OVRUtil.createOVRRecti(eyeViewport); + OVR.ovrHmd_GetRenderScaleAndOffset(fov, ovrTextureSize, ovrEyeRenderViewport, uvScaleOffsetOut); + if( StereoDevice.DEBUG ) { + System.err.println("XXX."+eyeName+": fov "+OVRUtil.toString(fov)); + System.err.println("XXX."+eyeName+": uvScale "+OVRUtil.toString(uvScaleOffsetOut[0])); + System.err.println("XXX."+eyeName+": uvOffset "+OVRUtil.toString(uvScaleOffsetOut[1])); + System.err.println("XXX."+eyeName+": textureSize "+OVRUtil.toString(ovrTextureSize)); + System.err.println("XXX."+eyeName+": viewport "+OVRUtil.toString(ovrEyeRenderViewport)); + } + final FloatBuffer eyeToSourceUVScaleFB = eyeToSourceUVScale.floatBufferValue(); + eyeToSourceUVScaleFB.put(0, uvScaleOffsetOut[0].getX()); + eyeToSourceUVScaleFB.put(1, uvScaleOffsetOut[0].getY()); + final FloatBuffer eyeToSourceUVOffsetFB = eyeToSourceUVOffset.floatBufferValue(); + eyeToSourceUVOffsetFB.put(0, uvScaleOffsetOut[1].getX()); + eyeToSourceUVOffsetFB.put(1, uvScaleOffsetOut[1].getY()); + } + + /** 2+2+2+2+2: { vec2 position, vec2 color, vec2 texCoordR, vec2 texCoordG, vec2 texCoordB } */ + final FloatBuffer iVBOFB = (FloatBuffer)iVBO.getBuffer(); + final ovrDistortionVertex[] ovRes = new ovrDistortionVertex[1]; + ovRes[0] = ovrDistortionVertex.create(); // FIXME: remove ctor / double check + + for ( int vertNum = 0; vertNum < vertexCount; vertNum++ ) { + final ovrDistortionVertex ov = meshData.getPVertexData(vertNum, ovRes)[0]; + ovrVector2f v; + + // pos + v = ov.getPos(); + iVBOFB.put(v.getX()); + iVBOFB.put(v.getY()); + + // params + if( useVignette ) { + iVBOFB.put(ov.getVignetteFactor()); + } else { + iVBOFB.put(1.0f); + } + iVBOFB.put(ov.getTimeWarpFactor()); + + // texCoordR + v = ov.getTexR(); + iVBOFB.put(v.getX()); + iVBOFB.put(v.getY()); + + if( useChromatic ) { + // texCoordG + v = ov.getTexG(); + iVBOFB.put(v.getX()); + iVBOFB.put(v.getY()); + + // texCoordB + v = ov.getTexB(); + iVBOFB.put(v.getX()); + iVBOFB.put(v.getY()); + } + } + if( StereoDevice.DEBUG ) { + System.err.println("XXX."+eyeName+": iVBO "+iVBO); + } + { + final ShortBuffer in = meshData.getPIndexData(); + final ShortBuffer out = (ShortBuffer) indices.getBuffer(); + out.put(in); + } + if( StereoDevice.DEBUG ) { + System.err.println("XXX."+eyeName+": idx "+indices); + System.err.println("XXX."+eyeName+": "+this); + } + OVR.ovrHmd_DestroyDistortionMesh(meshData); + } + + private void linkData(final GL2ES2 gl, final ShaderProgram sp) { + if( 0 > vboPos.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+vboPos); + } + if( 0 > vboParams.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+vboParams); + } + if( 0 > vboTexCoordsR.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+vboTexCoordsR); + } + if( StereoUtil.usesChromaticDistortion(distortionBits) ) { + if( 0 > vboTexCoordsG.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+vboTexCoordsG); + } + if( 0 > vboTexCoordsB.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+vboTexCoordsB); + } + } + if( 0 > eyeToSourceUVScale.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+eyeToSourceUVScale); + } + if( 0 > eyeToSourceUVOffset.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+eyeToSourceUVOffset); + } + if( StereoUtil.usesTimewarpDistortion(distortionBits) ) { + if( 0 > eyeRotationStart.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+eyeRotationStart); + } + if( 0 > eyeRotationEnd.setLocation(gl, sp.program()) ) { + throw new OVRException("Couldn't locate "+eyeRotationEnd); + } + } + iVBO.seal(gl, true); + iVBO.enableBuffer(gl, false); + indices.seal(gl, true); + indices.enableBuffer(gl, false); + } + + private void dispose(final GL2ES2 gl) { + iVBO.destroy(gl); + indices.destroy(gl); + } + private void enableVBO(final GL2ES2 gl, final boolean enable) { + iVBO.enableBuffer(gl, enable); + indices.bindBuffer(gl, enable); // keeps VBO binding if enable:=true + } + + private void updateUniform(final GL2ES2 gl, final ShaderProgram sp) { + gl.glUniform(eyeToSourceUVScale); + gl.glUniform(eyeToSourceUVOffset); + if( StereoUtil.usesTimewarpDistortion(distortionBits) ) { + gl.glUniform(eyeRotationStart); + gl.glUniform(eyeRotationEnd); + } + } + + private void updateTimewarp(final OvrHmdContext hmdCtx, final ovrPosef eyeRenderPose, final float[] mat4Tmp1, final float[] mat4Tmp2) { + final ovrMatrix4f[] timeWarpMatrices = new ovrMatrix4f[2]; + timeWarpMatrices[0] = ovrMatrix4f.create(); // FIXME: remove ctor / double check + timeWarpMatrices[1] = ovrMatrix4f.create(); + OVR.ovrHmd_GetEyeTimewarpMatrices(hmdCtx, eyeName, eyeRenderPose, timeWarpMatrices); + + final float[] eyeRotationStartM = FloatUtil.transposeMatrix(timeWarpMatrices[0].getM(0, mat4Tmp1), mat4Tmp2); + final FloatBuffer eyeRotationStartU = eyeRotationStart.floatBufferValue(); + eyeRotationStartU.put(eyeRotationStartM); + eyeRotationStartU.rewind(); + + final float[] eyeRotationEndM = FloatUtil.transposeMatrix(timeWarpMatrices[1].getM(0, mat4Tmp1), mat4Tmp2); + final FloatBuffer eyeRotationEndU = eyeRotationEnd.floatBufferValue(); + eyeRotationEndU.put(eyeRotationEndM); + eyeRotationEndU.rewind(); + } + + /** + * Updates {@link #ovrEyePose} and it's extracted + * {@link #eyeRenderPoseOrientation} and {@link #eyeRenderPosePosition}. + * @param hmdCtx used get the {@link #ovrEyePose} via {@link OVR#ovrHmd_GetEyePose(OvrHmdContext, int)} + */ + private EyePose updateEyePose(final OvrHmdContext hmdCtx) { + ovrEyePose = OVR.ovrHmd_GetEyePose(hmdCtx, eyeName); + final ovrVector3f pos = ovrEyePose.getPosition(); + eyePose.setPosition(pos.getX(), pos.getY(), pos.getZ()); + OVRUtil.copyToQuaternion(ovrEyePose.getOrientation(), eyePose.orientation); + return eyePose; + } + + @Override + public String toString() { + return "Eye["+eyeName+", viewport "+viewport+ + ", "+eyeParameter+ + ", vertices "+vertexCount+", indices "+indexCount+ + ", uvScale["+eyeToSourceUVScale.floatBufferValue().get(0)+", "+eyeToSourceUVScale.floatBufferValue().get(1)+ + "], uvOffset["+eyeToSourceUVOffset.floatBufferValue().get(0)+", "+eyeToSourceUVOffset.floatBufferValue().get(1)+ + "], desc"+OVRUtil.toString(ovrEyeDesc)+", "+eyePose+"]"; + } + } + + private final OVRStereoDevice context; + private final OVREye[] eyes; + private final int distortionBits; + private final int textureCount; + private final DimensionImmutable singleTextureSize; + private final DimensionImmutable totalTextureSize; + private final GLUniformData texUnit0; + + + private final float[] mat4Tmp1 = new float[16]; + private final float[] mat4Tmp2 = new float[16]; + + private ShaderProgram sp; + private ovrFrameTiming frameTiming; + + @Override + public String toString() { + return "OVRDist[distortion["+StereoUtil.distortionBitsToString(distortionBits)+ + "], singleSize "+singleTextureSize+ + ", sbsSize "+totalTextureSize+ + ", texCount "+textureCount+", texUnit "+getTextureUnit()+ + ", "+PlatformPropsImpl.NEWLINE+" "+eyes[0]+", "+PlatformPropsImpl.NEWLINE+" "+eyes[1]+"]"; + } + + + private static int distBits2OVRDistCaps(final int distortionBits) { + int bits = 0; + if( StereoUtil.usesTimewarpDistortion(distortionBits) ) { + bits |= OVR.ovrDistortionCap_TimeWarp; + } + if( StereoUtil.usesChromaticDistortion(distortionBits) ) { + bits |= OVR.ovrDistortionCap_Chromatic; + } + if( StereoUtil.usesVignetteDistortion(distortionBits) ) { + bits |= OVR.ovrDistortionCap_Vignette; + } + return bits; + } + + /* pp */ OVRStereoDeviceRenderer(final OVRStereoDevice context, final int distortionBits, + final int textureCount, final float[] eyePositionOffset, + final ovrEyeRenderDesc[] eyeRenderDescs, final DimensionImmutable singleTextureSize, final DimensionImmutable totalTextureSize, + final RectangleImmutable[] eyeViewports, final int textureUnit) { + if( 1 > textureCount || 2 < textureCount ) { + throw new IllegalArgumentException("textureCount can only be 1 or 2, has "+textureCount); + } + this.context = context; + this.eyes = new OVREye[2]; + this.distortionBits = distortionBits | StereoDeviceRenderer.DISTORTION_BARREL /* always */; + this.textureCount = textureCount; + this.singleTextureSize = singleTextureSize; + this.totalTextureSize = totalTextureSize; + + texUnit0 = new GLUniformData("ovr_Texture0", textureUnit); + + final ovrSizei ovrTextureSize = OVRUtil.createOVRSizei( 1 == textureCount ? totalTextureSize : singleTextureSize ); + eyes[0] = new OVREye(context.handle, this.distortionBits, eyePositionOffset, eyeRenderDescs[0], ovrTextureSize, eyeViewports[0]); + eyes[1] = new OVREye(context.handle, this.distortionBits, eyePositionOffset, eyeRenderDescs[1], ovrTextureSize, eyeViewports[1]); + sp = null; + frameTiming = null; + } + + @Override + public StereoDevice getDevice() { + return context; + } + + @Override + public final int getDistortionBits() { return distortionBits; } + + @Override + public final boolean usesSideBySideStereo() { return true; } + + @Override + public final DimensionImmutable getSingleSurfaceSize() { return singleTextureSize; } + + @Override + public final DimensionImmutable getTotalSurfaceSize() { return totalTextureSize; } + + @Override + public final int getTextureCount() { return textureCount; } + + @Override + public final int getTextureUnit() { return texUnit0.intValue(); } + + @Override + public final boolean ppRequired() { return true; } + + @Override + public final void init(final GL gl) { + if( StereoDevice.DEBUG ) { + System.err.println(JoglVersion.getGLInfo(gl, null).toString()); + } + if( null != sp ) { + throw new IllegalStateException("Already initialized"); + } + final GL2ES2 gl2es2 = gl.getGL2ES2(); + + final String vertexShaderBasename; + final String fragmentShaderBasename; + { + final boolean usesTimewarp = StereoUtil.usesTimewarpDistortion(distortionBits); + final boolean usesChromatic = StereoUtil.usesChromaticDistortion(distortionBits); + + final StringBuilder sb = new StringBuilder(); + sb.append(shaderPrefix01); + if( !usesChromatic && !usesTimewarp ) { + sb.append(shaderPlainSuffix); + } else if( usesChromatic && !usesTimewarp ) { + sb.append(shaderChromaSuffix); + } else if( usesTimewarp ) { + sb.append(shaderTimewarpSuffix); + if( usesChromatic ) { + sb.append(shaderChromaSuffix); + } + } + vertexShaderBasename = sb.toString(); + sb.setLength(0); + sb.append(shaderPrefix01); + if( usesChromatic ) { + sb.append(shaderChromaSuffix); + } else { + sb.append(shaderPlainSuffix); + } + fragmentShaderBasename = sb.toString(); + } + final ShaderCode vp0 = ShaderCode.create(gl2es2, GL2ES2.GL_VERTEX_SHADER, OVRStereoDeviceRenderer.class, "shader", + "shader/bin", vertexShaderBasename, true); + final ShaderCode fp0 = ShaderCode.create(gl2es2, GL2ES2.GL_FRAGMENT_SHADER, OVRStereoDeviceRenderer.class, "shader", + "shader/bin", fragmentShaderBasename, true); + vp0.defaultShaderCustomization(gl2es2, true, true); + fp0.defaultShaderCustomization(gl2es2, true, true); + + sp = new ShaderProgram(); + sp.add(gl2es2, vp0, System.err); + sp.add(gl2es2, fp0, System.err); + if(!sp.link(gl2es2, System.err)) { + throw new GLException("could not link program: "+sp); + } + sp.useProgram(gl2es2, true); + if( 0 > texUnit0.setLocation(gl2es2, sp.program()) ) { + throw new OVRException("Couldn't locate "+texUnit0); + } + eyes[0].linkData(gl2es2, sp); + eyes[1].linkData(gl2es2, sp); + sp.useProgram(gl2es2, false); + } + + @Override + public final void dispose(final GL gl) { + final GL2ES2 gl2es2 = gl.getGL2ES2(); + sp.useProgram(gl2es2, false); + eyes[0].dispose(gl2es2); + eyes[1].dispose(gl2es2); + sp.destroy(gl2es2); + frameTiming = null; + } + + @Override + public final Eye getEye(final int eyeNum) { + return eyes[eyeNum]; + } + + @Override + public final EyePose updateEyePose(final int eyeNum) { + return eyes[eyeNum].updateEyePose(context.handle); + } + + @Override + public final void beginFrame(final GL gl) { + frameTiming = OVR.ovrHmd_BeginFrameTiming(context.handle, 0); + } + + @Override + public final void endFrame(final GL gl) { + if( null == frameTiming ) { + throw new IllegalStateException("beginFrame not called"); + } + OVR.ovrHmd_EndFrameTiming(context.handle); + frameTiming = null; + } + + @Override + public final void ppBegin(final GL gl) { + if( null == sp ) { + throw new IllegalStateException("Not initialized"); + } + if( null == frameTiming ) { + throw new IllegalStateException("beginFrame not called"); + } + if( StereoUtil.usesTimewarpDistortion(distortionBits) ) { + OVR.ovr_WaitTillTime(frameTiming.getTimewarpPointSeconds()); + } + final GL2ES2 gl2es2 = gl.getGL2ES2(); + + gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + gl.glClear(GL.GL_COLOR_BUFFER_BIT); + gl.glActiveTexture(GL.GL_TEXTURE0 + getTextureUnit()); + + gl2es2.glDisable(GL.GL_CULL_FACE); + gl2es2.glDisable(GL.GL_DEPTH_TEST); + gl2es2.glDisable(GL.GL_BLEND); + + if( !gl2es2.isGLcore() ) { + gl2es2.glEnable(GL.GL_TEXTURE_2D); + } + + sp.useProgram(gl2es2, true); + + gl2es2.glUniform(texUnit0); + } + + @Override + public final void ppBothEyes(final GL gl) { + final GL2ES2 gl2es2 = gl.getGL2ES2(); + for(int eyeNum=0; eyeNum<2; eyeNum++) { + final OVREye eye = eyes[eyeNum]; + if( StereoUtil.usesTimewarpDistortion(distortionBits) ) { + eye.updateTimewarp(context.handle, eye.ovrEyePose, mat4Tmp1, mat4Tmp2); + } + eye.updateUniform(gl2es2, sp); + eye.enableVBO(gl2es2, true); + gl2es2.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL.GL_UNSIGNED_SHORT, 0); + eyes[eyeNum].enableVBO(gl2es2, false); + } + } + + @Override + public final void ppOneEye(final GL gl, final int eyeNum) { + final OVREye eye = eyes[eyeNum]; + if( StereoUtil.usesTimewarpDistortion(distortionBits) ) { + eye.updateTimewarp(context.handle, eye.ovrEyePose, mat4Tmp1, mat4Tmp2); + } + final GL2ES2 gl2es2 = gl.getGL2ES2(); + + eye.updateUniform(gl2es2, sp); + eye.enableVBO(gl2es2, true); + gl2es2.glDrawElements(GL.GL_TRIANGLES, eye.indexCount, GL.GL_UNSIGNED_SHORT, 0); + eyes[eyeNum].enableVBO(gl2es2, false); + } + + @Override + public final void ppEnd(final GL gl) { + sp.useProgram(gl.getGL2ES2(), false); + } +} diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java index 6c1cdc015..4de05fc92 100644 --- a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java @@ -27,7 +27,11 @@ */ package jogamp.opengl.oculusvr; -import jogamp.opengl.Debug; +import javax.media.nativewindow.util.Dimension; +import javax.media.nativewindow.util.DimensionImmutable; +import javax.media.nativewindow.util.Point; +import javax.media.nativewindow.util.PointImmutable; +import javax.media.nativewindow.util.RectangleImmutable; import com.jogamp.oculusvr.ovrEyeRenderDesc; import com.jogamp.oculusvr.ovrFovPort; @@ -44,8 +48,6 @@ import com.jogamp.opengl.math.Quaternion; * OculusVR Data Conversion Helper Functions */ public class OVRUtil { - public static final boolean DEBUG = Debug.debug("OVR"); - public static ovrRecti createOVRRecti(final int[] rect) { final ovrRecti res = ovrRecti.create(); final ovrVector2i pos = res.getPos(); @@ -56,6 +58,16 @@ public class OVRUtil { size.setH(rect[3]); return res; } + public static ovrRecti createOVRRecti(final RectangleImmutable rect) { + final ovrRecti res = ovrRecti.create(); + final ovrVector2i pos = res.getPos(); + final ovrSizei size = res.getSize(); + pos.setX(rect.getX()); + pos.setY(rect.getY()); + size.setW(rect.getWidth()); + size.setH(rect.getHeight()); + return res; + } public static ovrRecti[] createOVRRectis(final int[][] rects) { final ovrRecti[] res = new ovrRecti[rects.length]; for(int i=0; i<res.length; i++) { @@ -69,11 +81,24 @@ public class OVRUtil { res.setH(size[1]); return res; } - public static Quaternion getQuaternion(final ovrQuatf q) { - return new Quaternion(q.getX(), q.getY(), q.getZ(), q.getW()); + public static ovrSizei createOVRSizei(final DimensionImmutable size) { + final ovrSizei res = ovrSizei.create(); + res.setW(size.getWidth()); + res.setH(size.getHeight()); + return res; } - public static void copyToQuaternion(final ovrQuatf in, final Quaternion out) { - out.set(in.getX(), in.getY(), in.getZ(), in.getW()); + public static DimensionImmutable getOVRSizei(final ovrSizei v) { + return new Dimension(v.getW(), v.getH()); + } + public static PointImmutable getVec2iAsPoint(final ovrVector2i v) { + return new Point(v.getX(), v.getY()); + } + public static int[] getVec2i(final ovrVector2i v) { + return new int[] { v.getX(), v.getY() }; + } + public static void copyVec2iToInt(final ovrVector2i v, final int[] res) { + res[0] = v.getX(); + res[1] = v.getY(); } public static float[] getVec3f(final ovrVector3f v) { return new float[] { v.getX(), v.getY(), v.getZ() }; @@ -83,12 +108,33 @@ public class OVRUtil { res[1] = v.getY(); res[2] = v.getZ(); } + public static Quaternion getQuaternion(final ovrQuatf q) { + return new Quaternion(q.getX(), q.getY(), q.getZ(), q.getW()); + } + public static void copyToQuaternion(final ovrQuatf in, final Quaternion out) { + out.set(in.getX(), in.getY(), in.getZ(), in.getW()); + } public static FovHVHalves getFovHV(final ovrFovPort tanHalfFov) { return new FovHVHalves(tanHalfFov.getLeftTan(), tanHalfFov.getRightTan(), tanHalfFov.getUpTan(), tanHalfFov.getDownTan(), true); } + public static ovrFovPort getOVRFovPort(final FovHVHalves fovHVHalves) { + final ovrFovPort tanHalfFov = ovrFovPort.create(); + if( fovHVHalves.inTangents ) { + tanHalfFov.setLeftTan(fovHVHalves.left); + tanHalfFov.setRightTan(fovHVHalves.right); + tanHalfFov.setUpTan(fovHVHalves.top); + tanHalfFov.setDownTan(fovHVHalves.bottom); + } else { + tanHalfFov.setLeftTan((float)Math.tan(fovHVHalves.left)); + tanHalfFov.setRightTan((float)Math.tan(fovHVHalves.right)); + tanHalfFov.setUpTan((float)Math.tan(fovHVHalves.top)); + tanHalfFov.setDownTan((float)Math.tan(fovHVHalves.bottom)); + } + return tanHalfFov; + } public static String toString(final ovrFovPort fov) { return "["+fov.getLeftTan()+" l, "+fov.getRightTan()+" r, "+ |