diff options
author | Sven Gothel <[email protected]> | 2014-07-03 09:35:34 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2014-07-03 09:35:34 +0200 |
commit | d44e8ada30d62149c5d4d4b8fdba7cc33f8c765b (patch) | |
tree | b05fbfb142497d7d62760d8d908236c5f7280fe5 | |
parent | 36327e24cf586b50bf18e87d7d13d53eb41cf1d9 (diff) |
Bug 1021: Refine Stereo Rendering API and OculusVR implementing renderer
Refine API in regards to proper package names, interface
and high-level access to eye specific constant parameter
and variable eye movement.
+++
Commit 36327e24cf586b50bf18e87d7d13d53eb41cf1d9 introduced 'GLEventListener2'
Move javax.media.opengl.GLEventListener2
-> com.jogamp.opengl.util.CustomRendererListener
-> com.jogamp.opengl.util.stereo.StereoRendererListener
StereoRendererListener adds stereoscopic specific:
public void reshapeEye(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height,
final EyeParameter eyeParam, final EyePose eyePose);
.. see below.
++
Add com.jogamp.opengl.util.stereo:
- EyeParameter (Constant eye parameters, like IPD and FOV)
- EyePose (Current eye position and orientation)
+++
Add com.jogamp.opengl.math.FovHVHalves to support
non-centered bi-directional FOV for lenses.
Add respective FloatUtil.makePerspective(.. FovHVHalves fovhv ) variant.
+++
14 files changed, 585 insertions, 169 deletions
diff --git a/make/build-jogl.xml b/make/build-jogl.xml index 9721f4af9..4b58061f3 100644 --- a/make/build-jogl.xml +++ b/make/build-jogl.xml @@ -153,7 +153,7 @@ value="com/jogamp/opengl/**/swt/**"/> <property name="java.part.util" - value="com/jogamp/opengl/util/texture/** com/jogamp/opengl/util/av/* com/jogamp/opengl/util/packrect/** com/jogamp/opengl/util/PNG* jogamp/opengl/util/av/** jogamp/opengl/util/jpeg/** jogamp/opengl/util/pngj/**"/> + value="com/jogamp/opengl/util/texture/** com/jogamp/opengl/util/av/* com/jogamp/opengl/util/stereo/* com/jogamp/opengl/util/packrect/** com/jogamp/opengl/util/PNG* jogamp/opengl/util/av/** jogamp/opengl/util/jpeg/** jogamp/opengl/util/pngj/**"/> <property name="java.part.util.awt" value="com/jogamp/opengl/util/**/awt/**"/> diff --git a/src/jogl/classes/com/jogamp/opengl/math/FloatUtil.java b/src/jogl/classes/com/jogamp/opengl/math/FloatUtil.java index f5200443c..eeedf531c 100644 --- a/src/jogl/classes/com/jogamp/opengl/math/FloatUtil.java +++ b/src/jogl/classes/com/jogamp/opengl/math/FloatUtil.java @@ -548,7 +548,8 @@ public final class FloatUtil { } /** - * Make given matrix the perspective matrix based on given parameters. + * Make given matrix the perspective {@link #makeFrustum(float[], int, boolean, float, float, float, float, float, float) frustum} + * matrix based on given parameters. * <p> * All matrix fields are only set if <code>initM</code> is <code>true</code>. * </p> @@ -556,7 +557,7 @@ public final class FloatUtil { * @param m 4x4 matrix in column-major order (also result) * @param m_offset offset in given array <i>m</i>, i.e. start of the 4x4 matrix * @param initM if true, given matrix will be initialized w/ identity matrix, - * otherwise only the non-zero fields are set. + * otherwise only the frustum fields are set. * @param fovy angle in radians * @param aspect * @param zNear @@ -565,14 +566,81 @@ public final class FloatUtil { */ public static float[] makePerspective(final float[] m, final int m_off, final boolean initM, final float fovy, final float aspect, final float zNear, final float zFar) { - float top=(float)Math.tan(fovy)*zNear; - float bottom=-1.0f*top; - float left=aspect*bottom; - float right=aspect*top; + final float top=(float)Math.tan(fovy)*zNear; + final float bottom=-1.0f*top; + final float left=aspect*bottom; + final float right=aspect*top; return makeFrustum(m, m_off, initM, left, right, bottom, top, zNear, zFar); } /** + * Make given matrix the perspective {@link #makeFrustum(float[], int, boolean, float, float, float, float, float, float) frustum} + * matrix based on given parameters. + * <p> + * All matrix fields are only set if <code>initM</code> is <code>true</code>. + * </p> + * + * @param m 4x4 matrix in column-major order (also result) + * @param m_offset offset in given array <i>m</i>, i.e. start of the 4x4 matrix + * @param initM if true, given matrix will be initialized w/ identity matrix, + * otherwise only the frustum fields are set. + * @param fovhv {@link FovHVHalves} field of view in both directions, may not be centered, either in radians or tangent + * @param zNear + * @param zFar + * @return given matrix for chaining + */ + public static float[] makePerspective(final float[] m, final int m_offset, final boolean initM, + final FovHVHalves fovhv, final float zNear, final float zFar) { + if( initM ) { + // m[m_offset+0+4*0] = 1f; + m[m_offset+1+4*0] = 0f; + m[m_offset+2+4*0] = 0f; + m[m_offset+3+4*0] = 0f; + + m[m_offset+0+4*1] = 0f; + // m[m_offset+1+4*1] = 1f; + m[m_offset+2+4*1] = 0f; + m[m_offset+3+4*1] = 0f; + + // m[m_offset+0+4*2] = 0f; + // m[m_offset+1+4*2] = 0f; + // m[m_offset+2+4*2] = 1f; + // m[m_offset+3+4*2] = 0f; + + m[m_offset+0+4*3] = 0f; + m[m_offset+1+4*3] = 0f; + // m[m_offset+2+4*3] = 0f; + // m[m_offset+3+4*3] = 1f; + } + + final float projScaleX = 2.0f / ( fovhv.left + fovhv.right ); + final float projScaleY = 2.0f / ( fovhv.top + fovhv.bottom ); + final float projOffsetX = ( fovhv.left - fovhv.right ) * projScaleX * 0.5f; + final float projOffsetY = -1f * ( fovhv.top - fovhv.bottom ) * projScaleY * 0.5f; + + // Produces X result, mapping clip edges to [-w,+w] + m[m_offset+0+4*0] = projScaleX; + m[m_offset+0+4*2] = -1f * projOffsetX; + + // Produces Y result, mapping clip edges to [-w,+w] (Y=up) + m[m_offset+1+4*1] = projScaleY; + m[m_offset+1+4*2] = -1f * projOffsetY; + + // Custom Z-buffer result .. same as frustum matrix! + m[m_offset+2+4*2] = -1.0f*(zFar+zNear)/(zFar-zNear); + m[m_offset+2+4*3] = -2.0f*(zFar*zNear)/(zFar-zNear); + // alternative: + // m[m_offset+2+4*2] = -1.0f * zFar / (zNear - zFar); + // m[m_offset+2+4*3] = (zFar * zNear) / (zNear - zFar); + + // Produces W result (= Z in) + m[m_offset+3+4*2] = -1.0f; + m[m_offset+3+4*3] = 0f; + + return m; + } + + /** * Make given matrix the <i>look-at</i> matrix based on given parameters. * <p> * Consist out of two matrix multiplications: @@ -1034,7 +1102,7 @@ public final class FloatUtil { final float[] modelMatrix, final int modelMatrix_offset, final float[] projMatrix, final int projMatrix_offset, final int[] viewport, final int viewport_offset, - final float[] win_pos, int win_pos_offset, + final float[] win_pos, final int win_pos_offset, final float[/*4*/] vec4Tmp1, final float[/*4*/] vec4Tmp2) { vec4Tmp1[0] = objx; vec4Tmp1[1] = objy; @@ -1088,7 +1156,7 @@ public final class FloatUtil { public static boolean mapObjToWinCoords(final float objx, final float objy, final float objz, final float[/*16*/] mat4PMv, final int[] viewport, final int viewport_offset, - final float[] win_pos, int win_pos_offset, + final float[] win_pos, final int win_pos_offset, final float[/*4*/] vec4Tmp1, final float[/*4*/] vec4Tmp2) { vec4Tmp2[0] = objx; vec4Tmp2[1] = objy; @@ -1338,11 +1406,11 @@ public final class FloatUtil { * @return true if successful, otherwise false (failed to invert matrix, or becomes infinity due to zero z) */ public static boolean mapWinToObjCoords(final float winx, final float winy, final float winz, final float clipw, - float[] modelMatrix, int modelMatrix_offset, - float[] projMatrix, int projMatrix_offset, - int[] viewport, int viewport_offset, - float near, float far, - float[] obj_pos, int obj_pos_offset, + final float[] modelMatrix, final int modelMatrix_offset, + final float[] projMatrix, final int projMatrix_offset, + final int[] viewport, final int viewport_offset, + final float near, final float far, + final float[] obj_pos, final int obj_pos_offset, final float[/*16*/] mat4Tmp1, final float[/*16*/] mat4Tmp2) { // mat4Tmp1 = P x Mv multMatrix(projMatrix, projMatrix_offset, modelMatrix, modelMatrix_offset, mat4Tmp1, 0); @@ -1445,7 +1513,7 @@ public final class FloatUtil { * @param d result a*b in column-major order * @return given result matrix <i>d</i> for chaining */ - public static float[] multMatrix(final float[] a, final int a_off, final float[] b, final int b_off, float[] d, final int d_off) { + public static float[] multMatrix(final float[] a, final int a_off, final float[] b, final int b_off, final float[] d, final int d_off) { final float b00 = b[b_off+0+0*4]; final float b10 = b[b_off+1+0*4]; final float b20 = b[b_off+2+0*4]; @@ -1509,7 +1577,7 @@ public final class FloatUtil { * @param d result a*b in column-major order * @return given result matrix <i>d</i> for chaining */ - public static float[] multMatrix(final float[] a, final float[] b, float[] d) { + public static float[] multMatrix(final float[] a, final float[] b, final float[] d) { final float b00 = b[0+0*4]; final float b10 = b[1+0*4]; final float b20 = b[2+0*4]; diff --git a/src/jogl/classes/com/jogamp/opengl/math/FovHVHalves.java b/src/jogl/classes/com/jogamp/opengl/math/FovHVHalves.java new file mode 100644 index 000000000..18bba8c45 --- /dev/null +++ b/src/jogl/classes/com/jogamp/opengl/math/FovHVHalves.java @@ -0,0 +1,90 @@ +/** + * 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.math; + +/** + * Horizontal and vertical field of view (FOV) halves, + * allowing a non-centered projection. + * <p> + * The values might be either in tangent or radians. + * </p> + */ +public final class FovHVHalves { + /** Half horizontal FOV from center to left. */ + public final float left; + /** Half horizontal FOV from center to right. */ + public final float right; + /** Half vertical FOV from center to top. */ + public final float top; + /** Half vertical FOV from center to bottom. */ + public final float bottom; + /** If true, values are in tangent, otherwise radians.*/ + public final boolean inTangents; + + /** + * Constructor for one {@link FovHVHalves} instance. + * + * @param left half horizontal FOV, left side, in tangent or radians + * @param right half horizontal FOV, right side, in tangent or radians + * @param top half vertical FOV, top side, in tangent or radians + * @param bottom half vertical FOV, bottom side, in tangent or radians + * @param inTangents if true, values are in tangent, otherwise radians + */ + public FovHVHalves(final float left, final float right, final float top, final float bottom, final boolean inTangents) { + this.left = left; + this.right = right; + this.top = top; + this.bottom = bottom; + this.inTangents = inTangents; + } + + /** + * Returns a symmetrical centered {@link FovHVHalves} instance in tangents, using: + * <pre> + final float halfHorizFovTan = (float)Math.tan(horizontalFov/2f); + final float halfVertFovTan = (float)Math.tan(verticalFov/2f); + * </pre> + * @param horizontalFov whole horizontal FOV in radians + * @param verticalFov whole vertical FOV in radians + */ + public static FovHVHalves createByRadians(final float horizontalFov, final float verticalFov) { + final float halfHorizFovTan = (float)Math.tan(horizontalFov/2f); + final float halfVertFovTan = (float)Math.tan(verticalFov/2f); + return new FovHVHalves(halfHorizFovTan, halfHorizFovTan, halfVertFovTan, halfVertFovTan, true); + } + + /** Returns the full horizontal FOV, i.e. {@link #left} + {@link #right}. */ + public final float horzFov() { return left+right; } + + /** Returns the full vertical FOV, i.e. {@link #top} + {@link #bottom}. */ + public final float vertFov() { return top+bottom; } + + public final String toString() { + return "FovHVHalves["+(inTangents?"tangents":"radians")+": "+left+" l, "+right+" r, "+top+" t, "+bottom+" b]"; + } +} diff --git a/src/jogl/classes/com/jogamp/opengl/math/VectorUtil.java b/src/jogl/classes/com/jogamp/opengl/math/VectorUtil.java index 607d8ef9d..c11c2bd2b 100644 --- a/src/jogl/classes/com/jogamp/opengl/math/VectorUtil.java +++ b/src/jogl/classes/com/jogamp/opengl/math/VectorUtil.java @@ -33,13 +33,17 @@ public final class VectorUtil { public static final float[] VEC3_ONE = { 1f, 1f, 1f }; public static final float[] VEC3_ZERO = { 0f, 0f, 0f }; + public static final float[] VEC3_UNIT_Y = { 0f, 1f, 0f }; + public static final float[] VEC3_UNIT_Y_NEG = { 0f, -1f, 0f }; + public static final float[] VEC3_UNIT_Z = { 0f, 0f, 1f }; + public static final float[] VEC3_UNIT_Z_NEG = { 0f, 0f, -1f }; public enum Winding { CW(-1), CCW(1); public final int dir; - Winding(int dir) { + Winding(final int dir) { this.dir = dir; } } @@ -52,7 +56,7 @@ public final class VectorUtil { * @param srcOffset offset of src in array * @return copied output vector for chaining */ - public static float[] copyVec2(final float[] dst, int dstOffset, final float[] src, int srcOffset) + public static float[] copyVec2(final float[] dst, final int dstOffset, final float[] src, final int srcOffset) { System.arraycopy(src, srcOffset, dst, dstOffset, 2); return dst; @@ -66,7 +70,7 @@ public final class VectorUtil { * @param srcOffset offset of src in array * @return copied output vector for chaining */ - public static float[] copyVec3(final float[] dst, int dstOffset, final float[] src, int srcOffset) + public static float[] copyVec3(final float[] dst, final int dstOffset, final float[] src, final int srcOffset) { System.arraycopy(src, srcOffset, dst, dstOffset, 3); return dst; @@ -80,7 +84,7 @@ public final class VectorUtil { * @param srcOffset offset of src in array * @return copied output vector for chaining */ - public static float[] copyVec4(final float[] dst, int dstOffset, final float[] src, int srcOffset) + public static float[] copyVec4(final float[] dst, final int dstOffset, final float[] src, final int srcOffset) { System.arraycopy(src, srcOffset, dst, dstOffset, 4); return dst; @@ -92,7 +96,7 @@ public final class VectorUtil { * Implementation uses {@link FloatUtil#isEqual(float, float)}, see API doc for details. * </p> */ - public static boolean isVec2Equal(final float[] vec1, int vec1Offset, final float[] vec2, int vec2Offset) { + public static boolean isVec2Equal(final float[] vec1, final int vec1Offset, final float[] vec2, final int vec2Offset) { return FloatUtil.isEqual(vec1[0+vec1Offset], vec2[0+vec2Offset]) && FloatUtil.isEqual(vec1[1+vec1Offset], vec2[1+vec2Offset]) ; } @@ -103,7 +107,7 @@ public final class VectorUtil { * Implementation uses {@link FloatUtil#isEqual(float, float)}, see API doc for details. * </p> */ - public static boolean isVec3Equal(final float[] vec1, int vec1Offset, final float[] vec2, int vec2Offset) { + public static boolean isVec3Equal(final float[] vec1, final int vec1Offset, final float[] vec2, final int vec2Offset) { return FloatUtil.isEqual(vec1[0+vec1Offset], vec2[0+vec2Offset]) && FloatUtil.isEqual(vec1[1+vec1Offset], vec2[1+vec2Offset]) && FloatUtil.isEqual(vec1[2+vec1Offset], vec2[2+vec2Offset]) ; @@ -115,7 +119,7 @@ public final class VectorUtil { * Implementation uses {@link FloatUtil#isEqual(float, float, float)}, see API doc for details. * </p> */ - public static boolean isVec2Equal(final float[] vec1, int vec1Offset, final float[] vec2, int vec2Offset, final float epsilon) { + public static boolean isVec2Equal(final float[] vec1, final int vec1Offset, final float[] vec2, final int vec2Offset, final float epsilon) { return FloatUtil.isEqual(vec1[0+vec1Offset], vec2[0+vec2Offset], epsilon) && FloatUtil.isEqual(vec1[1+vec1Offset], vec2[1+vec2Offset], epsilon) ; } @@ -126,7 +130,7 @@ public final class VectorUtil { * Implementation uses {@link FloatUtil#isEqual(float, float, float)}, see API doc for details. * </p> */ - public static boolean isVec3Equal(final float[] vec1, int vec1Offset, final float[] vec2, int vec2Offset, final float epsilon) { + public static boolean isVec3Equal(final float[] vec1, final int vec1Offset, final float[] vec2, final int vec2Offset, final float epsilon) { return FloatUtil.isEqual(vec1[0+vec1Offset], vec2[0+vec2Offset], epsilon) && FloatUtil.isEqual(vec1[1+vec1Offset], vec2[1+vec2Offset], epsilon) && FloatUtil.isEqual(vec1[2+vec1Offset], vec2[2+vec2Offset], epsilon) ; @@ -250,7 +254,7 @@ public final class VectorUtil { */ public static float normSquareVec2(final float[] vec, final int offset) { float v = vec[0+offset]; - float r = v*v; + final float r = v*v; v = vec[1+offset]; return r + v*v; } @@ -956,7 +960,7 @@ public final class VectorUtil { * @param epsilon * @return resulting intersecting if exists, otherwise null */ - public static float[] line2PlaneIntersection(final float[] result, final Ray ray, float[/*4*/] plane, final float epsilon) { + public static float[] line2PlaneIntersection(final float[] result, final Ray ray, final float[/*4*/] plane, final float epsilon) { final float tmp = dotVec3(ray.dir, plane) ; if ( Math.abs(tmp) < epsilon ) { diff --git a/src/jogl/classes/javax/media/opengl/GLEventListener2.java b/src/jogl/classes/com/jogamp/opengl/util/CustomRendererListener.java index d4e8e84a4..0e6de5178 100644 --- a/src/jogl/classes/javax/media/opengl/GLEventListener2.java +++ b/src/jogl/classes/com/jogamp/opengl/util/CustomRendererListener.java @@ -25,13 +25,16 @@ * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ -package javax.media.opengl; +package com.jogamp.opengl.util; + +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLEventListener; /** * Extended {@link GLEventListener} interface * supporting more fine grained control over the implementation. */ -public interface GLEventListener2 extends GLEventListener { +public interface CustomRendererListener extends GLEventListener { /** * {@link #display(GLAutoDrawable, int) display flag}: Repeat last produced image. * <p> @@ -57,18 +60,4 @@ public interface GLEventListener2 extends GLEventListener { * @param flags */ public void display(final GLAutoDrawable drawable, final int flags); - - /** - * Might be called instead of {@link #reshape(GLAutoDrawable, int, int, int, int) reshape} - * to specify a custom projection and modelview matrix determined by the caller. - * <p> - * Method is usually called by a custom rendering loop, - * e.g. for manual stereo rendering or the like. - * </p> - * - * @param drawable the triggering {@link GLAutoDrawable} - * @param mat4Projection float[16] projection matrix - * @param mat4Modelview float[16] modelview matrix - */ - public void setProjectionModelview(final GLAutoDrawable drawable, final float[] mat4Projection, final float[] mat4Modelview); } diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/EyeParameter.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/EyeParameter.java new file mode 100644 index 000000000..7774d67e2 --- /dev/null +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/EyeParameter.java @@ -0,0 +1,68 @@ +/** + * 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.util.stereo; + +import com.jogamp.opengl.math.FovHVHalves; + +/** + * Constant parameter for one eye. + */ +public final class EyeParameter { + /** Eye number, <code>0</code> for the left eye and <code>1</code> for the right eye. */ + public final int number; + + /** float[3] eye position vector used to define eye height in meter relative to <i>actor</i>. */ + public final float[] positionOffset; + + /** Field of view in both directions, may not be centered, either in radians or tangent. */ + public final FovHVHalves fovhv; + + /** IPD related horizontal distance from nose to pupil in meter. */ + public final float distNoseToPupilX; + + /** Vertical distance from middle-line to pupil in meter. */ + public final float distMiddleToPupilY; + + /** Z-axis eye relief in meter. */ + public final float eyeReliefZ; + + public EyeParameter(final int number, final float[] positionOffset, final FovHVHalves fovhv, + final float distNoseToPupil, final float verticalDelta, final float eyeRelief) { + this.number = number; + this.positionOffset = new float[3]; + System.arraycopy(positionOffset, 0, this.positionOffset, 0, 3); + this.fovhv = fovhv; + this.distNoseToPupilX = distNoseToPupil; + this.distMiddleToPupilY = verticalDelta; + this.eyeReliefZ = eyeRelief; + } + public final String toString() { + return "EyeParam[num"+number+", posOff["+positionOffset[0]+", "+positionOffset[1]+", "+positionOffset[2]+"], "+fovhv+ + ", distPupil[noseX "+distNoseToPupilX+", middleY "+distMiddleToPupilY+", reliefZ "+eyeReliefZ+"]]"; + } +}
\ No newline at end of file diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/EyePose.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/EyePose.java new file mode 100644 index 000000000..2690097f1 --- /dev/null +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/EyePose.java @@ -0,0 +1,69 @@ +/** + * 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.util.stereo; + +import com.jogamp.opengl.math.Quaternion; + +/** + * Position and orientation of one eye. + */ +public final class EyePose { + /** Eye number, <code>0</code> for the left eye and <code>1</code> for the right eye. */ + public final int number; + + /** float[3] eye position vector. */ + public final float[] position; + + /** Eye orientation */ + public final Quaternion orientation; + + public EyePose(final int number) { + this.number = number; + this.position = new float[3]; + this.orientation = new Quaternion(); + } + public EyePose(final int number, final float[] position, final Quaternion orientation) { + this(number); + set(position, orientation); + } + + /** Set position and orientation of this instance. */ + public final void set(final float[] position, final Quaternion orientation) { + System.arraycopy(position, 0, this.position, 0, 3); + this.orientation.set(orientation); + } + /** Set position and orientation of this instance. */ + public final void setPosition(final float posX, final float posY, final float posZ) { + position[0] = posX; + position[1] = posY; + position[2] = posZ; + } + public final String toString() { + return "EyePose[num"+number+", pos["+position[0]+", "+position[1]+", "+position[2]+"], "+orientation+"]"; + } +}
\ No newline at end of file diff --git a/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoRendererListener.java b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoRendererListener.java new file mode 100644 index 000000000..5e6e40a08 --- /dev/null +++ b/src/jogl/classes/com/jogamp/opengl/util/stereo/StereoRendererListener.java @@ -0,0 +1,73 @@ +/** + * 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.util.stereo; + +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLEventListener; + +import com.jogamp.opengl.math.FloatUtil; +import com.jogamp.opengl.util.CustomRendererListener; + +/** + * Extended {@link GLEventListener} and {@link CustomRendererListener} interface + * supporting stereoscopic client rendering. + */ +public interface StereoRendererListener extends CustomRendererListener { + /** + * Stereo capable specialization of {@link #reshape(GLAutoDrawable, int, int, int, int)}. + * <p> + * Called by the stereo renderer before each {@link #display(GLAutoDrawable)} + * or {@link #display(GLAutoDrawable, int)} call. + * </p> + * <p> + * The client can update it's viewport associated data + * and view volume of the window appropriately. + * </p> + * <p> + * The client shall also update it's projection- and modelview matrices according + * to the given {@link EyeParameter} and {@link EyePose}. + * </p> + * <p> + * For efficiency the GL viewport has already been updated + * via <code>glViewport(x, y, width, height)</code> when this method is called. + * </p> + * + * @param drawable the triggering {@link GLAutoDrawable} + * @param x viewport x-coord in pixel units + * @param y viewport y-coord in pixel units + * @param width viewport width in pixel units + * @param height viewport height in pixel units + * @param eyeParam constant eye parameter, i.e. FOV and IPD + * @param eyePose current eye position and orientation + * @see FloatUtil#makePerspective(float[], int, boolean, com.jogamp.opengl.math.FloatUtil.FovHVHalves, float, float) + */ + public void reshapeEye(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height, + final EyeParameter eyeParam, final EyePose eyePose); + + +} diff --git a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java b/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java index b10a58842..ab53862cd 100644 --- a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java +++ b/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererDualFBO.java @@ -31,7 +31,6 @@ import javax.media.opengl.GL; import javax.media.opengl.GL2ES2; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLEventListener; -import javax.media.opengl.GLEventListener2; import jogamp.opengl.oculusvr.OVRDistortion; @@ -40,35 +39,27 @@ import com.jogamp.oculusvr.ovrFrameTiming; import com.jogamp.opengl.FBObject; import com.jogamp.opengl.FBObject.TextureAttachment; import com.jogamp.opengl.FBObject.Attachment.Type; -import com.jogamp.opengl.math.FloatUtil; +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 GLEventListener2} instance + * 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 float[] mat4Projection = new float[16]; - private final float[] mat4Modelview = new float[16]; private final OVRDistortion dist; private final boolean ownsDist; - private final GLEventListener2 upstream; + private final StereoRendererListener upstream; private final FBObject[] fbos; - // final float[] eyePos = { 0.0f, 1.6f, -5.0f }; - private final float[] eyePos = { 0.0f, 0.0f, -20.0f }; - // EyePos.y = ovrHmd_GetFloat(HMD, OVR_KEY_EYE_HEIGHT, EyePos.y); - private float eyeYaw = FloatUtil.PI; // 180 degrees in radians - private float frustumNear = 0.1f; - private float frustumFar = 7000f; private int numSamples; private final TextureAttachment[] fboTexs; - public OVRSBSRendererDualFBO(final OVRDistortion dist, final boolean ownsDist, final GLEventListener2 upstream, final int numSamples) { + public OVRSBSRendererDualFBO(final OVRDistortion dist, final boolean ownsDist, final StereoRendererListener upstream, final int numSamples) { this.dist = dist; this.ownsDist = ownsDist; this.upstream = upstream; @@ -79,20 +70,6 @@ public class OVRSBSRendererDualFBO implements GLEventListener { fboTexs = new TextureAttachment[2]; } - /** - * - * @param eyePos - * @param eyeYaw - * @param frustumNear - * @param frustumFar - */ - public void setUpstreamPMVParams(final float[] eyePos, final float eyeYaw, final float frustumNear, final float frustumFar) { - System.arraycopy(eyePos, 0, this.eyePos, 0, 3); - this.eyeYaw = eyeYaw; - this.frustumNear = frustumNear; - this.frustumFar = frustumFar; - } - 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 @@ -191,9 +168,10 @@ public class OVRSBSRendererDualFBO implements GLEventListener { final int[] viewport = eyeDist.viewport; gl.glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); - dist.getSBSUpstreamPMV(eyeNum, eyePos, eyeYaw, frustumNear, frustumFar, mat4Projection, mat4Modelview); - upstream.setProjectionModelview(drawable, mat4Projection, mat4Modelview); - upstream.display(drawable, eyeNum > 0 ? GLEventListener2.DISPLAY_REPEAT : 0); + 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()); diff --git a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererSingleFBO.java b/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererSingleFBO.java index 22b8b37ce..c9d307665 100644 --- a/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererSingleFBO.java +++ b/src/oculusvr/classes/com/jogamp/opengl/oculusvr/OVRSBSRendererSingleFBO.java @@ -31,7 +31,6 @@ import javax.media.opengl.GL; import javax.media.opengl.GL2ES2; import javax.media.opengl.GLAutoDrawable; import javax.media.opengl.GLEventListener; -import javax.media.opengl.GLEventListener2; import jogamp.opengl.oculusvr.OVRDistortion; @@ -40,35 +39,28 @@ import com.jogamp.oculusvr.ovrFrameTiming; import com.jogamp.opengl.FBObject; import com.jogamp.opengl.FBObject.TextureAttachment; import com.jogamp.opengl.FBObject.Attachment.Type; -import com.jogamp.opengl.math.FloatUtil; +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 GLEventListener2} instance + * 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 float[] mat4Projection = new float[16]; - private final float[] mat4Modelview = new float[16]; private final OVRDistortion dist; private final boolean ownsDist; - private final GLEventListener2 upstream; + private final StereoRendererListener upstream; private final FBObject fbo0; - // final float[] eyePos = { 0.0f, 1.6f, -5.0f }; - private final float[] eyePos = { 0.0f, 0.0f, -20.0f }; - // EyePos.y = ovrHmd_GetFloat(HMD, OVR_KEY_EYE_HEIGHT, EyePos.y); - private float eyeYaw = FloatUtil.PI; // 180 degrees in radians - private float frustumNear = 0.1f; - private float frustumFar = 7000f; private int numSamples; private TextureAttachment fbo0Tex; - public OVRSBSRendererSingleFBO(final OVRDistortion dist, final boolean ownsDist, final GLEventListener2 upstream, final int numSamples) { + public OVRSBSRendererSingleFBO(final OVRDistortion dist, final boolean ownsDist, final StereoRendererListener upstream, final int numSamples) { this.dist = dist; this.ownsDist = ownsDist; this.upstream = upstream; @@ -76,20 +68,6 @@ public class OVRSBSRendererSingleFBO implements GLEventListener { fbo0 = new FBObject(); } - /** - * - * @param eyePos - * @param eyeYaw - * @param frustumNear - * @param frustumFar - */ - public void setUpstreamPMVParams(final float[] eyePos, final float eyeYaw, final float frustumNear, final float frustumFar) { - System.arraycopy(eyePos, 0, this.eyePos, 0, 3); - this.eyeYaw = eyeYaw; - this.frustumNear = frustumNear; - this.frustumFar = frustumFar; - } - 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 @@ -170,9 +148,9 @@ public class OVRSBSRendererSingleFBO implements GLEventListener { final int[] viewport = eyeDist.viewport; gl.glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); - dist.getSBSUpstreamPMV(eyeNum, eyePos, eyeYaw, frustumNear, frustumFar, mat4Projection, mat4Modelview); - upstream.setProjectionModelview(drawable, mat4Projection, mat4Modelview); - upstream.display(drawable, eyeNum > 0 ? GLEventListener2.DISPLAY_REPEAT | GLEventListener2.DISPLAY_DONTCLEAR : 0); + 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); } fbo0.unbind(gl); gl.glViewport(0, 0, drawable.getSurfaceWidth(), drawable.getSurfaceHeight()); diff --git a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRDistortion.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRDistortion.java index 590b0e834..7a9def985 100644 --- a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRDistortion.java +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRDistortion.java @@ -33,6 +33,7 @@ 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; @@ -50,13 +51,17 @@ 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 @@ -80,7 +85,6 @@ public class OVRDistortion { public final int vertexCount; public final int indexCount; public final int[/*4*/] viewport; - public final float[/*3*/] viewAdjust; public final GLUniformData eyeToSourceUVScale; public final GLUniformData eyeToSourceUVOffset; @@ -92,23 +96,23 @@ public class OVRDistortion { public final GLArrayData vboPos, vboParams, vboTexCoordsR, vboTexCoordsG, vboTexCoordsB; public final GLArrayDataServer indices; - public final ovrEyeRenderDesc eyeRenderDesc; - public final ovrFovPort eyeRenderFov; + public final ovrEyeRenderDesc ovrEyeDesc; + public final ovrFovPort ovrEyeFov; + public final EyeParameter eyeParameter; - public ovrPosef eyeRenderPose; - public final Quaternion eyeRenderPoseOrientation; - public final float[] eyeRenderPosePosition; + 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 ovrEyeRenderDesc eyeRenderDesc, final ovrSizei ovrTextureSize, final int[] eyeRenderViewport) { - this.eyeName = eyeRenderDesc.getEye(); + final float[] eyePositionOffset, final ovrEyeRenderDesc eyeDesc, + final ovrSizei ovrTextureSize, final int[] eyeRenderViewport) { + this.eyeName = eyeDesc.getEye(); this.distortionCaps = distortionCaps; viewport = new int[4]; - viewAdjust = new float[3]; System.arraycopy(eyeRenderViewport, 0, viewport, 0, 4); final FloatBuffer fstash = Buffers.newDirectFloatBuffer(2+2+16+26); @@ -124,15 +128,19 @@ public class OVRDistortion { eyeRotationEnd = null; } - this.eyeRenderDesc = eyeRenderDesc; - this.eyeRenderFov = eyeRenderDesc.getFov(); + this.ovrEyeDesc = eyeDesc; + this.ovrEyeFov = eyeDesc.getFov(); - this.eyeRenderPoseOrientation = new Quaternion(); - this.eyeRenderPosePosition = new float[3]; + 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 = eyeRenderDesc.getFov(); + 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)); @@ -156,7 +164,6 @@ public class OVRDistortion { vboTexCoordsB = null; } indices = GLArrayDataServer.createData(1, GL2ES2.GL_SHORT, indexCount, GL.GL_STATIC_DRAW, GL.GL_ELEMENT_ARRAY_BUFFER); - OVRUtil.copyVec3fToFloat(eyeRenderDesc.getViewAdjust(), viewAdjust); // Setup: eyeToSourceUVScale, eyeToSourceUVOffset { @@ -309,24 +316,26 @@ public class OVRDistortion { } /** - * Updates {@link #eyeRenderPose} and it's extracted + * Updates {@link #ovrEyePose} and it's extracted * {@link #eyeRenderPoseOrientation} and {@link #eyeRenderPosePosition}. - * @param hmdCtx used get the {@link #eyeRenderPose} via {@link OVR#ovrHmd_GetEyePose(OvrHmdContext, int)} + * @param hmdCtx used get the {@link #ovrEyePose} via {@link OVR#ovrHmd_GetEyePose(OvrHmdContext, int)} */ - public void updateEyePose(final OvrHmdContext hmdCtx) { - eyeRenderPose = OVR.ovrHmd_GetEyePose(hmdCtx, eyeName); - OVRUtil.copyToQuaternion(eyeRenderPose.getOrientation(), eyeRenderPoseOrientation); - OVRUtil.copyVec3fToFloat(eyeRenderPose.getPosition(), eyeRenderPosePosition); + 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]+ - " viewAdjust["+viewAdjust[0]+", "+viewAdjust[1]+", "+viewAdjust[2]+ - "], vertices "+vertexCount+", indices "+indexCount+ + ", "+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(eyeRenderDesc)+"]"; + "], desc"+OVRUtil.toString(ovrEyeDesc)+", "+eyePose+"]"; } } @@ -339,9 +348,6 @@ public class OVRDistortion { private final float[] mat4Tmp1 = new float[16]; private final float[] mat4Tmp2 = new float[16]; - private final float[] vec3Tmp1 = new float[3]; - private final float[] vec3Tmp2 = new float[3]; - private final float[] vec3Tmp3 = new float[3]; private ShaderProgram sp; @@ -354,7 +360,8 @@ public class OVRDistortion { } public static OVRDistortion create(final OvrHmdContext hmdCtx, final boolean sbsSingleTexture, - final ovrFovPort[] eyeFov, final float pixelsPerDisplayPixel, final int distortionCaps) { + 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]); @@ -400,10 +407,11 @@ public class OVRDistortion { eyeRenderViewports[1][2] = textureSize[0]; eyeRenderViewports[1][3] = textureSize[1]; } - return new OVRDistortion(hmdCtx, sbsSingleTexture, eyeRenderDesc, textureSize, eyeRenderViewports, distortionCaps, 0); + return new OVRDistortion(hmdCtx, sbsSingleTexture, eyePositionOffset, eyeRenderDesc, textureSize, eyeRenderViewports, distortionCaps, 0); } - public OVRDistortion(final OvrHmdContext hmdCtx, final boolean sbsSingleTexture, final ovrEyeRenderDesc[] eyeRenderDescs, + 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; @@ -416,8 +424,8 @@ public class OVRDistortion { usesDistMesh = true; final ovrSizei ovrTextureSize = OVRUtil.createOVRSizei(textureSize); - eyes[0] = new EyeData(hmdCtx, distortionCaps, eyeRenderDescs[0], ovrTextureSize, eyeRenderViewports[0]); - eyes[1] = new EyeData(hmdCtx, distortionCaps, eyeRenderDescs[1], ovrTextureSize, eyeRenderViewports[1]); + 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; } @@ -440,14 +448,6 @@ public class OVRDistortion { eyes[eyeNum].enableVBO(gl, enable); } - 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); - } - public final ShaderProgram getShaderProgram() { return sp; } public void init(final GL2ES2 gl) { @@ -511,6 +511,35 @@ public class OVRDistortion { 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"); @@ -533,7 +562,7 @@ public class OVRDistortion { for(int eyeNum=0; eyeNum<2; eyeNum++) { final EyeData eye = eyes[eyeNum]; if( useTimewarp() ) { - eye.updateTimewarp(hmdCtx, eye.eyeRenderPose, mat4Tmp1, mat4Tmp2); + eye.updateTimewarp(hmdCtx, eye.ovrEyePose, mat4Tmp1, mat4Tmp2); } eye.updateUniform(gl, sp); eye.enableVBO(gl, true); @@ -548,6 +577,11 @@ public class OVRDistortion { 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"); @@ -568,13 +602,22 @@ public class OVRDistortion { 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.eyeRenderPose, mat4Tmp1, mat4Tmp2); + eye.updateTimewarp(hmdCtx, eye.ovrEyePose, mat4Tmp1, mat4Tmp2); } eye.updateUniform(gl, sp); eye.enableVBO(gl, true); @@ -593,7 +636,12 @@ public class OVRDistortion { /** * Calculates the <i>Side By Side</i>, SBS, projection- and modelview matrix for one eye. * <p> - * Method also issues {@link EyeData#updateEyePose(OvrHmdContext)}. + * {@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 eyePos float[3] eye postion vector @@ -602,17 +650,20 @@ public class OVRDistortion { * @param far 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[] eyePos, final float eyeYaw, final float near, final float far, final float[] mat4Projection, final float[] mat4Modelview) { final EyeData eyeDist = eyes[eyeNum]; - eyeDist.updateEyePose(hmdCtx); + final float[] vec3Tmp1 = new float[3]; + final float[] vec3Tmp2 = new float[3]; + final float[] vec3Tmp3 = new float[3]; // // Projection // - final ovrMatrix4f pm = OVR.ovrMatrix4f_Projection(eyeDist.eyeRenderFov, near, far, true /* rightHanded*/); + final ovrMatrix4f pm = OVR.ovrMatrix4f_Projection(eyeDist.ovrEyeFov, near, far, true /* rightHanded*/); /* final float[] mat4Projection = */ FloatUtil.transposeMatrix(pm.getM(0, mat4Tmp1), mat4Projection); // @@ -620,19 +671,20 @@ public class OVRDistortion { // final Quaternion rollPitchYaw = new Quaternion(); rollPitchYaw.rotateByAngleY(eyeYaw); - final float[] shiftedEyePos = rollPitchYaw.rotateVector(vec3Tmp1, 0, eyeDist.eyeRenderPosePosition, 0); + final float[] shiftedEyePos = rollPitchYaw.rotateVector(vec3Tmp1, 0, eyeDist.eyePose.position, 0); VectorUtil.addVec3(shiftedEyePos, shiftedEyePos, eyePos); - rollPitchYaw.mult(eyeDist.eyeRenderPoseOrientation); + 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.viewAdjust[0], eyeDist.viewAdjust[1], eyeDist.viewAdjust[2]); + 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/OVRUtil.java b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java index e28a50203..6c1cdc015 100644 --- a/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java +++ b/src/oculusvr/classes/jogamp/opengl/oculusvr/OVRUtil.java @@ -37,6 +37,7 @@ import com.jogamp.oculusvr.ovrSizei; import com.jogamp.oculusvr.ovrVector2f; import com.jogamp.oculusvr.ovrVector2i; import com.jogamp.oculusvr.ovrVector3f; +import com.jogamp.opengl.math.FovHVHalves; import com.jogamp.opengl.math.Quaternion; /** @@ -83,6 +84,12 @@ public class OVRUtil { res[2] = v.getZ(); } + public static FovHVHalves getFovHV(final ovrFovPort tanHalfFov) { + return new FovHVHalves(tanHalfFov.getLeftTan(), tanHalfFov.getRightTan(), + tanHalfFov.getUpTan(), tanHalfFov.getDownTan(), + true); + } + public static String toString(final ovrFovPort fov) { return "["+fov.getLeftTan()+" l, "+fov.getRightTan()+" r, "+ fov.getUpTan()+" u, "+fov.getDownTan()+" d]"; @@ -100,12 +107,10 @@ public class OVRUtil { public static String toString(final ovrVector3f v3) { return "["+v3.getX()+", "+v3.getY()+", "+v3.getZ()+"]"; } - public static String toString(final ovrEyeRenderDesc desc) { return "["+desc.getEye()+", fov"+toString(desc.getFov())+ ", viewport"+toString(desc.getDistortedViewport())+ ", pptCtr"+toString(desc.getPixelsPerTanAngleAtCenter())+ ", view-adjust"+toString(desc.getViewAdjust())+"]"; } - } diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/GearsES2.java b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/GearsES2.java index 177b573dd..d1a5f888e 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/GearsES2.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/demos/es2/GearsES2.java @@ -31,12 +31,19 @@ import com.jogamp.newt.event.MouseListener; import com.jogamp.newt.event.PinchToZoomGesture; import com.jogamp.newt.event.GestureHandler.GestureEvent; 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.test.junit.jogl.demos.GearsObject; +import com.jogamp.opengl.util.CustomRendererListener; import com.jogamp.opengl.util.PMVMatrix; import com.jogamp.opengl.util.TileRendererBase; import com.jogamp.opengl.util.glsl.ShaderCode; import com.jogamp.opengl.util.glsl.ShaderProgram; import com.jogamp.opengl.util.glsl.ShaderState; +import com.jogamp.opengl.util.stereo.EyeParameter; +import com.jogamp.opengl.util.stereo.EyePose; +import com.jogamp.opengl.util.stereo.StereoRendererListener; import java.nio.FloatBuffer; @@ -45,7 +52,6 @@ import javax.media.opengl.GL; import javax.media.opengl.GL2ES2; import javax.media.opengl.GLAnimatorControl; import javax.media.opengl.GLAutoDrawable; -import javax.media.opengl.GLEventListener2; import javax.media.opengl.GLProfile; import javax.media.opengl.GLUniformData; @@ -53,7 +59,7 @@ import javax.media.opengl.GLUniformData; * GearsES2.java <BR> * @author Brian Paul (converted to Java by Ron Cemer and Sven Gothel) <P> */ -public class GearsES2 implements GLEventListener2, TileRendererBase.TileRendererListener { +public class GearsES2 implements StereoRendererListener, TileRendererBase.TileRendererListener { private final FloatBuffer lightPos = Buffers.newDirectFloatBuffer( new float[] { 5.0f, 5.0f, 10.0f } ); private ShaderState st = null; @@ -62,6 +68,7 @@ public class GearsES2 implements GLEventListener2, TileRendererBase.TileRenderer private GLUniformData colorU = null; private float view_rotx = 20.0f, view_roty = 30.0f; private boolean flipVerticalInGLOrientation = false; + private final boolean customRendering = false; private final float view_rotz = 0.0f; private float panX = 0.0f, panY = 0.0f, panZ=0.0f; @@ -344,6 +351,10 @@ public class GearsES2 implements GLEventListener2, TileRendererBase.TileRenderer reshapeImpl(gl, tileX, tileY, tileWidth, tileHeight, imageWidth, imageHeight); } + private final float zNear = 2f; + private final float zFar = 10000f; + private final float zViewDist = 20.0f; + void reshapeImpl(final GL2ES2 gl, final int tileX, final int tileY, final int tileWidth, final int tileHeight, final int imageWidth, final int imageHeight) { final boolean msaa = gl.getContext().getGLDrawable().getChosenGLCapabilities().getSampleBuffers(); if(verbose) { @@ -389,21 +400,29 @@ public class GearsES2 implements GLEventListener2, TileRendererBase.TileRenderer if( flipVerticalInGLOrientation && gl.getContext().getGLDrawable().isGLOriented() ) { pmvMatrix.glScalef(1f, -1f, 1f); } - pmvMatrix.glFrustumf(l, r, b, t, 5.0f, 200.0f); + pmvMatrix.glFrustumf(l, r, b, t, zNear, zFar); pmvMatrix.glMatrixMode(PMVMatrix.GL_MODELVIEW); pmvMatrix.glLoadIdentity(); - pmvMatrix.glTranslatef(0.0f, 0.0f, -40.0f); + pmvMatrix.glTranslatef(0.0f, 0.0f, -zViewDist); st.useProgram(gl, true); st.uniform(gl, pmvMatrixUniform); st.useProgram(gl, false); } // private boolean useAndroidDebug = false; + private final float[] mat4Tmp1 = new float[16]; + private final float[] mat4Tmp2 = new float[16]; + private final float[] vec3Tmp1 = new float[3]; + private final float[] vec3Tmp2 = new float[3]; + private final float[] vec3Tmp3 = new float[3]; + @Override - public void setProjectionModelview(final GLAutoDrawable drawable, final float[] mat4Projection, final float[] mat4Modelview) { + public void reshapeEye(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height, + final EyeParameter eyeParam, final EyePose eyePose) { final GL2ES2 gl = drawable.getGL().getGL2ES2(); pmvMatrix.glMatrixMode(PMVMatrix.GL_PROJECTION); + final float[] mat4Projection = FloatUtil.makePerspective(mat4Tmp1, 0, true, eyeParam.fovhv, zNear, zFar); if( flipVerticalInGLOrientation && gl.getContext().getGLDrawable().isGLOriented() ) { pmvMatrix.glLoadIdentity(); pmvMatrix.glScalef(1f, -1f, 1f); @@ -411,8 +430,26 @@ public class GearsES2 implements GLEventListener2, TileRendererBase.TileRenderer } else { pmvMatrix.glLoadMatrixf(mat4Projection, 0); } + pmvMatrix.glMatrixMode(PMVMatrix.GL_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, eyePose.position, 0); + VectorUtil.addVec3(shiftedEyePos, shiftedEyePos, eyeParam.positionOffset); + + rollPitchYaw.mult(eyePose.orientation); + final float[] up = rollPitchYaw.rotateVector(vec3Tmp2, 0, VectorUtil.VEC3_UNIT_Y, 0); + final float[] forward = rollPitchYaw.rotateVector(vec3Tmp3, 0, VectorUtil.VEC3_UNIT_Z_NEG, 0); + final float[] center = VectorUtil.addVec3(forward, shiftedEyePos, forward); + + final float[] mLookAt = FloatUtil.makeLookAt(mat4Tmp1, 0, shiftedEyePos, 0, center, 0, up, 0, mat4Tmp2); + final float[] mViewAdjust = FloatUtil.makeTranslation(mat4Tmp2, true, eyeParam.distNoseToPupilX, eyeParam.distMiddleToPupilY, eyeParam.eyeReliefZ); + final float[] mat4Modelview = FloatUtil.multMatrix(mViewAdjust, mLookAt); + pmvMatrix.glLoadMatrixf(mat4Modelview, 0); + pmvMatrix.glTranslatef(0.0f, 0.0f, -zViewDist); st.useProgram(gl, true); st.uniform(gl, pmvMatrixUniform); st.useProgram(gl, false); @@ -469,8 +506,8 @@ public class GearsES2 implements GLEventListener2, TileRendererBase.TileRenderer System.err.println(Thread.currentThread()+" GearsES2.display "+sid()+" "+drawable.getSurfaceWidth()+"x"+drawable.getSurfaceHeight()+", swapInterval "+swapInterval+", drawable 0x"+Long.toHexString(drawable.getHandle())); } - final boolean repeatedFrame = 0 != ( GLEventListener2.DISPLAY_REPEAT & flags ); - final boolean dontClear = 0 != ( GLEventListener2.DISPLAY_DONTCLEAR & flags ); + final boolean repeatedFrame = 0 != ( CustomRendererListener.DISPLAY_REPEAT & flags ); + final boolean dontClear = 0 != ( CustomRendererListener.DISPLAY_DONTCLEAR & flags ); // Turn the gears' teeth if( doRotate && !repeatedFrame ) { @@ -533,15 +570,15 @@ public class GearsES2 implements GLEventListener2, TileRendererBase.TileRenderer public void setGLStates(final GL2ES2 gl, final boolean enable) { // Culling only possible if we do not flip the projection matrix - final boolean enableCullFace = ! ( flipVerticalInGLOrientation && gl.getContext().getGLDrawable().isGLOriented() ); + final boolean useCullFace = ! ( flipVerticalInGLOrientation && gl.getContext().getGLDrawable().isGLOriented() || customRendering ); if( enable ) { gl.glEnable(GL.GL_DEPTH_TEST); - if( enableCullFace ) { + if( useCullFace ) { gl.glEnable(GL.GL_CULL_FACE); } } else { gl.glDisable(GL.GL_DEPTH_TEST); - if( enableCullFace ) { + if( useCullFace ) { gl.glDisable(GL.GL_CULL_FACE); } } diff --git a/src/test/com/jogamp/opengl/test/junit/jogl/stereo/ovr/OVRDemo01.java b/src/test/com/jogamp/opengl/test/junit/jogl/stereo/ovr/OVRDemo01.java index c20af1389..cb5fa932c 100644 --- a/src/test/com/jogamp/opengl/test/junit/jogl/stereo/ovr/OVRDemo01.java +++ b/src/test/com/jogamp/opengl/test/junit/jogl/stereo/ovr/OVRDemo01.java @@ -133,9 +133,11 @@ public class OVRDemo01 { System.err.println(OVRVersion.getAvailableCapabilitiesInfo(hmdDesc, ovrHmdIndex, null).toString()); // Start the sensor which provides the Rift’s pose and motion. - final int requiredSensorCaps = OVR.ovrSensorCap_Orientation; - final int supportedSensorCaps = requiredSensorCaps | OVR.ovrSensorCap_YawCorrection | OVR.ovrSensorCap_Position; - OVR.ovrHmd_StartSensor(hmdCtx, supportedSensorCaps, requiredSensorCaps); + final int requiredSensorCaps = 0; + final int supportedSensorCaps = requiredSensorCaps | OVR.ovrSensorCap_Orientation | OVR.ovrSensorCap_YawCorrection | OVR.ovrSensorCap_Position; + if( !OVR.ovrHmd_StartSensor(hmdCtx, supportedSensorCaps, requiredSensorCaps) ) { + throw new OVRException("OVR HMD #"+ovrHmdIndex+" required sensors not available"); + } // // @@ -161,12 +163,15 @@ public class OVRDemo01 { // // Oculus Rift setup // + final float[] eyePositionOffset = { 0.0f, 1.6f, -5.0f }; + // EyePos.y = ovrHmd_GetFloat(HMD, OVR_KEY_EYE_HEIGHT, EyePos.y); + final ovrFovPort[] defaultEyeFov = hmdDesc.getDefaultEyeFov(0, new ovrFovPort[2]); final int distortionCaps = ( useVignette ? OVR.ovrDistortionCap_Vignette : 0 ) | ( useChromatic ? OVR.ovrDistortionCap_Chromatic : 0 ) | ( useTimewarp ? OVR.ovrDistortionCap_TimeWarp : 0 ); final float pixelsPerDisplayPixel = 1f; - final OVRDistortion dist = OVRDistortion.create(hmdCtx, useSingleFBO, defaultEyeFov, pixelsPerDisplayPixel, distortionCaps); + final OVRDistortion dist = OVRDistortion.create(hmdCtx, useSingleFBO, eyePositionOffset, defaultEyeFov, pixelsPerDisplayPixel, distortionCaps); System.err.println("OVRDistortion: "+dist); final GearsES2 upstream = new GearsES2(0); |