aboutsummaryrefslogtreecommitdiffstats
path: root/jogl/src/classes/jogamp
diff options
context:
space:
mode:
Diffstat (limited to 'jogl/src/classes/jogamp')
-rw-r--r--jogl/src/classes/jogamp/opengl/oculusvr/stereo/lense/DistortionMeshProducer.java358
-rw-r--r--jogl/src/classes/jogamp/opengl/oculusvr/stereo/lense/DistortionSpec.java178
-rw-r--r--jogl/src/classes/jogamp/opengl/oculusvr/stereo/lense/LensConfig.java600
3 files changed, 1136 insertions, 0 deletions
diff --git a/jogl/src/classes/jogamp/opengl/oculusvr/stereo/lense/DistortionMeshProducer.java b/jogl/src/classes/jogamp/opengl/oculusvr/stereo/lense/DistortionMeshProducer.java
new file mode 100644
index 0000000..aa64f6c
--- /dev/null
+++ b/jogl/src/classes/jogamp/opengl/oculusvr/stereo/lense/DistortionMeshProducer.java
@@ -0,0 +1,358 @@
+/**
+ * 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.
+ *
+ * 3. Compliance with Oculus VR RIFT SDK LICENSE (see below)
+ *
+ * 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.
+ *
+ * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ *
+ * This file contains mathematical equations, comments and algorithms
+ * used in the Oculus VR RIFT SDK 0.3.2.
+ *
+ * Due to unknown legal status, the 'requested' Copyright tag and disclaimer
+ * below has been added.
+ *
+ * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ *
+ * Copyright © 2014 Oculus VR, Inc. All rights reserved
+ *
+ * Oculus VR, Inc. Software Development Kit License Agreement
+ *
+ * Human-Readable Summary*:
+ *
+ * You are Free to:
+ *
+ * Use, modify, and distribute the Oculus VR Rift SDK in source and binary
+ * form with your applications/software.
+ *
+ * With the Following Restrictions:
+ *
+ * You can only distribute or re-distribute the source code to LibOVR in
+ * whole, not in part.
+ *
+ * Modifications to the Oculus VR Rift SDK in source or binary form must
+ * be shared with Oculus VR.
+ *
+ * If your applications cause health and safety issues, you may lose your
+ * right to use the Oculus VR Rift SDK, including LibOVR.
+ *
+ * The Oculus VR Rift SDK may not be used to interface with unapproved commercial
+ * virtual reality mobile or non-mobile products or hardware.
+
+ * * - This human-readable Summary is not a license. It is simply a convenient
+ * reference for understanding the full Oculus VR Rift SDK License Agreement.
+ * The Summary is written as a user-friendly interface to the full Oculus VR Rift
+ * SDK License below. This Summary itself has no legal value, and its contents do
+ * not appear in the actual license.
+ *
+ * Full-length Legal Copy may be found at:
+ * http://www.oculusvr.com/licenses/LICENSE-3.1
+ * http://jogamp.org/git/?p=oculusvr-sdk.git;a=blob;f=LICENSE.txt;hb=HEAD
+ * Or within this repository: oculusvr-sdk/LICENSE.txt
+ *
+ * THIS RIFT SDK AND ANY COMPONENT THEREOF IS PROVIDED BY OCULUS VR AND
+ * ITS CONTRIBUTORS "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 OCULUS VR AS THE
+ * COPYRIGHT OWNER OR ITS 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 RIFT
+ * SDK OR THE RIFT SDK DERIVATIVES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package jogamp.opengl.oculusvr.stereo.lense;
+
+import jogamp.opengl.util.stereo.DistortionMesh;
+import jogamp.opengl.util.stereo.GenericStereoDevice;
+import jogamp.opengl.util.stereo.ScaleAndOffset2D;
+import jogamp.opengl.util.stereo.DistortionMesh.DistortionVertex;
+
+import com.jogamp.opengl.math.FloatUtil;
+import com.jogamp.opengl.math.VectorUtil;
+import com.jogamp.opengl.util.stereo.EyeParameter;
+
+public class DistortionMeshProducer implements DistortionMesh.Producer {
+ private DistortionSpec[] distortionSpecs;
+ private GenericStereoDevice.Config deviceConfig;
+ private final float[] eyeReliefInMeters;
+
+ public DistortionMeshProducer() {
+ this.distortionSpecs = null;
+ this.deviceConfig = null;
+ this.eyeReliefInMeters = new float[] { 0, 0 };
+ }
+
+ @Override
+ public void init(final GenericStereoDevice.Config deviceConfig, final float[] eyeReliefInMeters) {
+ if( this.deviceConfig != deviceConfig ||
+ this.eyeReliefInMeters[0] != eyeReliefInMeters[0] ||
+ this.eyeReliefInMeters[1] != eyeReliefInMeters[1] ) {
+ System.arraycopy(eyeReliefInMeters, 0, this.eyeReliefInMeters, 0, 2);
+ this.distortionSpecs = DistortionSpec.CalculateDistortionSpec(deviceConfig, eyeReliefInMeters);
+ this.deviceConfig = deviceConfig;
+ }
+ }
+
+ // Pow2 for the Morton order to work!
+ // 4 is too low - it is easy to see the 'wobbles' in the HMD.
+ // 5 is close but you can see pixel differences with even/odd frame checking.
+ // 6 is indistinguishable on a monitor on even/odd frames.
+ private static final int DMA_GridSizeLog2 = 6;
+ private static final int DMA_GridSize = 1<<DMA_GridSizeLog2;
+ private static final int DMA_NumVertsPerEye = (DMA_GridSize+1)*(DMA_GridSize+1);
+ private static final int DMA_NumTrisPerEye = (DMA_GridSize)*(DMA_GridSize)*2;
+
+ private static float[] TransformTanFovSpaceToScreenNDC(final DistortionSpec distortion,
+ final float[] tanEyeAngle, final boolean usePolyApprox /*= false*/ ) {
+ final float tanEyeAngleRadius = VectorUtil.normVec2(tanEyeAngle);
+ float tanEyeAngleDistortedRadius = distortion.lens.DistortionFnInverseApprox ( tanEyeAngleRadius );
+ if ( !usePolyApprox ) {
+ tanEyeAngleDistortedRadius = distortion.lens.DistortionFnInverse ( tanEyeAngleRadius );
+ }
+ final float[] vec2Tmp1 = new float[2];
+ final float[] tanEyeAngleDistorted;
+ if ( tanEyeAngleRadius > 0.0f ) {
+ tanEyeAngleDistorted = VectorUtil.scaleVec2(vec2Tmp1, tanEyeAngle, tanEyeAngleDistortedRadius / tanEyeAngleRadius );
+ } else {
+ tanEyeAngleDistorted = tanEyeAngle;
+ }
+
+ final float[] framebufferNDC =
+ VectorUtil.addVec2(vec2Tmp1,
+ VectorUtil.divVec2(vec2Tmp1, tanEyeAngleDistorted, distortion.tanEyeAngleScale),
+ distortion.lensCenter );
+ return framebufferNDC;
+ }
+
+ private static void TransformScreenNDCToTanFovSpaceChroma (final float[] resultR, final float[] resultG, final float[] resultB,
+ final DistortionSpec distortion, final float[] framebufferNDC) {
+ // Scale to TanHalfFov space, but still distorted.
+ final float[] vec2Tmp1 = new float[2];
+ final float[] tanEyeAngleDistorted =
+ VectorUtil.scaleVec2(vec2Tmp1, VectorUtil.subVec2(vec2Tmp1, framebufferNDC, distortion.lensCenter), distortion.tanEyeAngleScale);
+ // Distort.
+ final float radiusSquared = ( tanEyeAngleDistorted[0] * tanEyeAngleDistorted[0] )
+ + ( tanEyeAngleDistorted[1] * tanEyeAngleDistorted[1] );
+ final float[] distortionScales = distortion.lens.DistortionFnScaleRadiusSquaredChroma (radiusSquared);
+ VectorUtil.scaleVec2(resultR, tanEyeAngleDistorted, distortionScales[0]);
+ VectorUtil.scaleVec2(resultG, tanEyeAngleDistorted, distortionScales[1]);
+ VectorUtil.scaleVec2(resultB, tanEyeAngleDistorted, distortionScales[2]);
+ }
+
+ public final DistortionMesh create(final EyeParameter eyeParam, final int distortionBits) {
+ // Find the mapping from TanAngle space to target NDC space.
+ final ScaleAndOffset2D eyeToSourceNDC = new ScaleAndOffset2D(eyeParam.fovhv);
+ final boolean rightEye = 1 == eyeParam.number;
+
+ // When does the fade-to-black edge start? Chosen heuristically.
+ final float fadeOutBorderFraction = 0.075f;
+
+
+ // Populate vertex buffer info
+ float xOffset = 0.0f;
+ @SuppressWarnings("unused")
+ float uOffset = 0.0f;
+
+ if (rightEye)
+ {
+ xOffset = 1.0f;
+ uOffset = 0.5f;
+ }
+
+ // First pass - build up raw vertex data.
+ final DistortionVertex[] vertices = new DistortionVertex[DMA_NumVertsPerEye];
+ int currentVertex = 0;
+ final float[] tanEyeAnglesR = new float[2], tanEyeAnglesG = new float[2], tanEyeAnglesB = new float[2];
+
+ for ( int y = 0; y <= DMA_GridSize; y++ ) {
+ for ( int x = 0; x <= DMA_GridSize; x++ ) {
+ final float[] pcurVert = new float[DistortionVertex.def_total_size];
+ int idx = 0;
+
+ final float[] sourceCoordNDC = new float[2];
+ // NDC texture coords [-1,+1]
+ sourceCoordNDC[0] = 2.0f * ( (float)x / (float)DMA_GridSize ) - 1.0f;
+ sourceCoordNDC[1] = 2.0f * ( (float)y / (float)DMA_GridSize ) - 1.0f;
+ final float[] tanEyeAngle = eyeToSourceNDC.convertToTanFovSpace(sourceCoordNDC);
+
+ // This is the function that does the really heavy lifting.
+ final float[] screenNDC = TransformTanFovSpaceToScreenNDC ( distortionSpecs[eyeParam.number], tanEyeAngle, false );
+
+ // VIGNETTE - Fade out at texture edges.
+ float edgeFadeIn = ( 1.0f / fadeOutBorderFraction ) *
+ ( 1.0f - Math.max( FloatUtil.abs( sourceCoordNDC[0] ), FloatUtil.abs( sourceCoordNDC[0] ) ) );
+ // Also fade out at screen edges.
+ final float edgeFadeInScreen = ( 2.0f / fadeOutBorderFraction ) *
+ ( 1.0f - Math.max( FloatUtil.abs( screenNDC[0] ), FloatUtil.abs( screenNDC[1] ) ) );
+ edgeFadeIn = Math.min( edgeFadeInScreen, edgeFadeIn );
+
+ // TIMEWARP
+ float timewarpLerp;
+ {
+ switch ( deviceConfig.shutterType )
+ {
+ case Global:
+ timewarpLerp = 0.0f;
+ break;
+ case RollingLeftToRight:
+ // Retrace is left to right - left eye goes 0.0 -> 0.5, then right goes 0.5 -> 1.0
+ timewarpLerp = screenNDC[0] * 0.25f + 0.25f;
+ if (rightEye)
+ {
+ timewarpLerp += 0.5f;
+ }
+ break;
+ case RollingRightToLeft:
+ // Retrace is right to left - right eye goes 0.0 -> 0.5, then left goes 0.5 -> 1.0
+ timewarpLerp = 0.75f - screenNDC[0] * 0.25f;
+ if (rightEye)
+ {
+ timewarpLerp -= 0.5f;
+ }
+ break;
+ case RollingTopToBottom:
+ // Retrace is top to bottom on both eyes at the same time.
+ timewarpLerp = screenNDC[1] * 0.5f + 0.5f;
+ break;
+ default:
+ timewarpLerp = 0.0f;
+ break;
+ }
+
+ }
+
+ // We then need RGB UVs. Since chromatic aberration is generated from screen coords, not
+ // directly from texture NDCs, we can't just use tanEyeAngle, we need to go the long way round.
+ TransformScreenNDCToTanFovSpaceChroma ( tanEyeAnglesR, tanEyeAnglesG, tanEyeAnglesB,
+ distortionSpecs[eyeParam.number], screenNDC );
+
+ // Don't let verts overlap to the other eye.
+ screenNDC[0] = Math.max ( -1.0f, Math.min( screenNDC[0], 1.0f ) );
+ screenNDC[1] = Math.max ( -1.0f, Math.min( screenNDC[1], 1.0f ) );
+
+ // POS
+ pcurVert[idx++] = 0.5f * screenNDC[0] - 0.5f + xOffset;
+ pcurVert[idx++] = -screenNDC[1];
+
+ // VIGNETTE
+ pcurVert[idx++] = Math.max( 0.0f, Math.min( edgeFadeIn, 1.0f ) );
+
+ // TIMEWARP
+ pcurVert[idx++] = timewarpLerp;
+
+ // Chroma RGB
+ pcurVert[idx++] = tanEyeAnglesR[0];
+ pcurVert[idx++] = tanEyeAnglesR[1];
+ pcurVert[idx++] = tanEyeAnglesG[0];
+ pcurVert[idx++] = tanEyeAnglesG[1];
+ pcurVert[idx++] = tanEyeAnglesB[0];
+ pcurVert[idx++] = tanEyeAnglesB[1];
+
+ vertices[currentVertex++] = new DistortionVertex(
+ pcurVert, DistortionVertex.def_pos_size,
+ DistortionVertex.def_vignetteFactor_size, DistortionVertex.def_timewarpFactor_size, DistortionVertex.def_texR_size,
+ DistortionVertex.def_texG_size, DistortionVertex.def_texB_size);
+ }
+ }
+
+
+ // Populate index buffer info
+ final short[] indices = new short[DMA_NumTrisPerEye*3];
+ int idx = 0;
+
+ for ( int triNum = 0; triNum < DMA_GridSize * DMA_GridSize; triNum++ )
+ {
+ // Use a Morton order to help locality of FB, texture and vertex cache.
+ // (0.325ms raster order -> 0.257ms Morton order)
+ final int x = ( ( triNum & 0x0001 ) >> 0 ) |
+ ( ( triNum & 0x0004 ) >> 1 ) |
+ ( ( triNum & 0x0010 ) >> 2 ) |
+ ( ( triNum & 0x0040 ) >> 3 ) |
+ ( ( triNum & 0x0100 ) >> 4 ) |
+ ( ( triNum & 0x0400 ) >> 5 ) |
+ ( ( triNum & 0x1000 ) >> 6 ) |
+ ( ( triNum & 0x4000 ) >> 7 );
+
+ final int y = ( ( triNum & 0x0002 ) >> 1 ) |
+ ( ( triNum & 0x0008 ) >> 2 ) |
+ ( ( triNum & 0x0020 ) >> 3 ) |
+ ( ( triNum & 0x0080 ) >> 4 ) |
+ ( ( triNum & 0x0200 ) >> 5 ) |
+ ( ( triNum & 0x0800 ) >> 6 ) |
+ ( ( triNum & 0x2000 ) >> 7 ) |
+ ( ( triNum & 0x8000 ) >> 8 );
+
+ final int FirstVertex = x * (DMA_GridSize+1) + y;
+ // Another twist - we want the top-left and bottom-right quadrants to
+ // have the triangles split one way, the other two split the other.
+ // +---+---+---+---+
+ // | /| /|\ |\ |
+ // | / | / | \ | \ |
+ // |/ |/ | \| \|
+ // +---+---+---+---+
+ // | /| /|\ |\ |
+ // | / | / | \ | \ |
+ // |/ |/ | \| \|
+ // +---+---+---+---+
+ // |\ |\ | /| /|
+ // | \ | \ | / | / |
+ // | \| \|/ |/ |
+ // +---+---+---+---+
+ // |\ |\ | /| /|
+ // | \ | \ | / | / |
+ // | \| \|/ |/ |
+ // +---+---+---+---+
+ // This way triangle edges don't span long distances over the distortion function,
+ // so linear interpolation works better & we can use fewer tris.
+ if ( ( x < DMA_GridSize/2 ) != ( y < DMA_GridSize/2 ) ) // != is logical XOR
+ {
+ indices[idx++] = (short)(FirstVertex);
+ indices[idx++] = (short)(FirstVertex+1);
+ indices[idx++] = (short)(FirstVertex+(DMA_GridSize+1)+1);
+
+ indices[idx++] = (short)(FirstVertex+(DMA_GridSize+1)+1);
+ indices[idx++] = (short)(FirstVertex+(DMA_GridSize+1));
+ indices[idx++] = (short)(FirstVertex);
+ }
+ else
+ {
+ indices[idx++] = (short)(FirstVertex);
+ indices[idx++] = (short)(FirstVertex+1);
+ indices[idx++] = (short)(FirstVertex+(DMA_GridSize+1));
+
+ indices[idx++] = (short)(FirstVertex+1);
+ indices[idx++] = (short)(FirstVertex+(DMA_GridSize+1)+1);
+ indices[idx++] = (short)(FirstVertex+(DMA_GridSize+1));
+ }
+ }
+
+ return new DistortionMesh(vertices, vertices.length,
+ indices, indices.length);
+ }
+
+}
diff --git a/jogl/src/classes/jogamp/opengl/oculusvr/stereo/lense/DistortionSpec.java b/jogl/src/classes/jogamp/opengl/oculusvr/stereo/lense/DistortionSpec.java
new file mode 100644
index 0000000..f62a3c1
--- /dev/null
+++ b/jogl/src/classes/jogamp/opengl/oculusvr/stereo/lense/DistortionSpec.java
@@ -0,0 +1,178 @@
+/**
+ * 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.
+ *
+ * 3. Compliance with Oculus VR RIFT SDK LICENSE (see below)
+ *
+ * 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.
+ *
+ * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ *
+ * This file contains mathematical equations, comments and algorithms
+ * used in the Oculus VR RIFT SDK 0.3.2.
+ *
+ * Due to unknown legal status, the 'requested' Copyright tag and disclaimer
+ * below has been added.
+ *
+ * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ *
+ * Copyright © 2014 Oculus VR, Inc. All rights reserved
+ *
+ * Oculus VR, Inc. Software Development Kit License Agreement
+ *
+ * Human-Readable Summary*:
+ *
+ * You are Free to:
+ *
+ * Use, modify, and distribute the Oculus VR Rift SDK in source and binary
+ * form with your applications/software.
+ *
+ * With the Following Restrictions:
+ *
+ * You can only distribute or re-distribute the source code to LibOVR in
+ * whole, not in part.
+ *
+ * Modifications to the Oculus VR Rift SDK in source or binary form must
+ * be shared with Oculus VR.
+ *
+ * If your applications cause health and safety issues, you may lose your
+ * right to use the Oculus VR Rift SDK, including LibOVR.
+ *
+ * The Oculus VR Rift SDK may not be used to interface with unapproved commercial
+ * virtual reality mobile or non-mobile products or hardware.
+
+ * * - This human-readable Summary is not a license. It is simply a convenient
+ * reference for understanding the full Oculus VR Rift SDK License Agreement.
+ * The Summary is written as a user-friendly interface to the full Oculus VR Rift
+ * SDK License below. This Summary itself has no legal value, and its contents do
+ * not appear in the actual license.
+ *
+ * Full-length Legal Copy may be found at:
+ * http://www.oculusvr.com/licenses/LICENSE-3.1
+ * http://jogamp.org/git/?p=oculusvr-sdk.git;a=blob;f=LICENSE.txt;hb=HEAD
+ * Or within this repository: oculusvr-sdk/LICENSE.txt
+ *
+ * THIS RIFT SDK AND ANY COMPONENT THEREOF IS PROVIDED BY OCULUS VR AND
+ * ITS CONTRIBUTORS "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 OCULUS VR AS THE
+ * COPYRIGHT OWNER OR ITS 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 RIFT
+ * SDK OR THE RIFT SDK DERIVATIVES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package jogamp.opengl.oculusvr.stereo.lense;
+
+import javax.media.nativewindow.util.DimensionImmutable;
+
+import jogamp.opengl.util.stereo.GenericStereoDevice;
+
+import com.jogamp.opengl.math.VectorUtil;
+
+public class DistortionSpec {
+ public DistortionSpec(final LensConfig lens) {
+ this.lens = lens;
+ this.pixelsPerTanAngleAtCenter = new float[2];
+ this.tanEyeAngleScale = new float[2];
+ this.lensCenter = new float[2];
+ }
+
+ private static final float[] quaterHalf = new float[] { 0.25f, 0.5f };
+
+ final LensConfig lens;
+
+ final float[] pixelsPerTanAngleAtCenter;
+ final float[] tanEyeAngleScale;
+ final float[] lensCenter;
+
+ public static DistortionSpec[] CalculateDistortionSpec (final GenericStereoDevice.Config deviceConfig, final float[] eyeReliefInMeters) {
+ final DistortionSpec[] result = new DistortionSpec[2];
+ /// FIXME: Add 'pluggable' lense configuration
+ final LensConfig[] lensConfig = LensConfig.GenerateLensConfigFromEyeRelief(eyeReliefInMeters, LensConfig.DistortionEquation.CatmullRom10);
+ result[0] = CalculateDistortionSpec (deviceConfig, 0, eyeReliefInMeters[0], lensConfig[0]);
+ result[1] = CalculateDistortionSpec (deviceConfig, 1, eyeReliefInMeters[1], lensConfig[1]);
+ return result;
+ }
+
+
+ private static DistortionSpec CalculateDistortionSpec (final GenericStereoDevice.Config deviceConfig, final int eyeName,
+ final float eyeReliefInMeters, final LensConfig lensConfig) {
+ // From eye relief, IPD and device characteristics, we get the distortion mapping.
+ // This distortion does the following things:
+ // 1. It undoes the distortion that happens at the edges of the lens.
+ // 2. It maps the undistorted field into "retina" space.
+ // So the input is a pixel coordinate - the physical pixel on the display itself.
+ // The output is the real-world direction of the ray from this pixel as it comes out of the lens and hits the eye.
+ // However we typically think of rays "coming from" the eye, so the direction (TanAngleX,TanAngleY,1) is the direction
+ // that the pixel appears to be in real-world space, where AngleX and AngleY are relative to the straight-ahead vector.
+ // If your renderer is a raytracer, you can use this vector directly (normalize as appropriate).
+ // However in standard rasterisers, we have rendered a 2D image and are putting it in front of the eye,
+ // so we then need a mapping from this space to the [-1,1] UV coordinate space, which depends on exactly
+ // where "in space" the app wants to put that rendertarget.
+ // Where in space, and how large this rendertarget is, is completely up to the app and/or user,
+ // though of course we can provide some useful hints.
+
+ // TODO: Use IPD and eye relief to modify distortion (i.e. non-radial component)
+ // TODO: cope with lenses that don't produce collimated light.
+ // This means that IPD relative to the lens separation changes the light vergence,
+ // and so we actually need to change where the image is displayed.
+ final DistortionSpec localDistortion = new DistortionSpec(lensConfig);
+
+ final DimensionImmutable resolutionInPixels = deviceConfig.surfaceSizeInPixels;
+
+ final float[] pixelsPerMeter = new float[] { resolutionInPixels.getWidth() / deviceConfig.screenSizeInMeters[0],
+ resolutionInPixels.getHeight() / deviceConfig.screenSizeInMeters[1] };
+
+ VectorUtil.scaleVec2(localDistortion.pixelsPerTanAngleAtCenter, pixelsPerMeter, localDistortion.lens.MetersPerTanAngleAtCenter);
+
+ // Same thing, scaled to [-1,1] for each eye, rather than pixels.
+
+ VectorUtil.scaleVec2(localDistortion.tanEyeAngleScale, quaterHalf,
+ VectorUtil.divVec2(new float[2], deviceConfig.screenSizeInMeters, localDistortion.lens.MetersPerTanAngleAtCenter) );
+
+
+ // <--------------left eye------------------><-ScreenGapSizeInMeters-><--------------right eye----------------->
+ // <------------------------------------------ScreenSizeInMeters.Width----------------------------------------->
+ // <------------interpupillaryDistanceInMeters-------------->
+ // <--centerFromLeftInMeters->
+ // ^
+ // Center of lens
+
+ // Find the lens centers in scale of [-1,+1] (NDC) in left eye.
+ final float visibleWidthOfOneEye = 0.5f * ( deviceConfig.screenSizeInMeters[0] );
+ final float centerFromLeftInMeters = ( deviceConfig.screenSizeInMeters[0] - deviceConfig.interpupillaryDistanceInMeters ) * 0.5f;
+ localDistortion.lensCenter[0] = ( centerFromLeftInMeters / visibleWidthOfOneEye ) * 2.0f - 1.0f;
+ localDistortion.lensCenter[1] = ( deviceConfig.pupilCenterFromScreenTopInMeters / deviceConfig.screenSizeInMeters[1] ) * 2.0f - 1.0f;
+ if ( 1 == eyeName ) {
+ localDistortion.lensCenter[0] = -localDistortion.lensCenter[0];
+ }
+
+ return localDistortion;
+ }
+
+
+} \ No newline at end of file
diff --git a/jogl/src/classes/jogamp/opengl/oculusvr/stereo/lense/LensConfig.java b/jogl/src/classes/jogamp/opengl/oculusvr/stereo/lense/LensConfig.java
new file mode 100644
index 0000000..f90470b
--- /dev/null
+++ b/jogl/src/classes/jogamp/opengl/oculusvr/stereo/lense/LensConfig.java
@@ -0,0 +1,600 @@
+/**
+ * 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.
+ *
+ * 3. Compliance with Oculus VR RIFT SDK LICENSE (see below)
+ *
+ * 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.
+ *
+ * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ *
+ * This file contains mathematical equations, comments and algorithms
+ * used in the Oculus VR RIFT SDK 0.3.2.
+ *
+ * Due to unknown legal status, the 'requested' Copyright tag and disclaimer
+ * below has been added.
+ *
+ * XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+ *
+ * Copyright © 2014 Oculus VR, Inc. All rights reserved
+ *
+ * Oculus VR, Inc. Software Development Kit License Agreement
+ *
+ * Human-Readable Summary*:
+ *
+ * You are Free to:
+ *
+ * Use, modify, and distribute the Oculus VR Rift SDK in source and binary
+ * form with your applications/software.
+ *
+ * With the Following Restrictions:
+ *
+ * You can only distribute or re-distribute the source code to LibOVR in
+ * whole, not in part.
+ *
+ * Modifications to the Oculus VR Rift SDK in source or binary form must
+ * be shared with Oculus VR.
+ *
+ * If your applications cause health and safety issues, you may lose your
+ * right to use the Oculus VR Rift SDK, including LibOVR.
+ *
+ * The Oculus VR Rift SDK may not be used to interface with unapproved commercial
+ * virtual reality mobile or non-mobile products or hardware.
+
+ * * - This human-readable Summary is not a license. It is simply a convenient
+ * reference for understanding the full Oculus VR Rift SDK License Agreement.
+ * The Summary is written as a user-friendly interface to the full Oculus VR Rift
+ * SDK License below. This Summary itself has no legal value, and its contents do
+ * not appear in the actual license.
+ *
+ * Full-length Legal Copy may be found at:
+ * http://www.oculusvr.com/licenses/LICENSE-3.1
+ * http://jogamp.org/git/?p=oculusvr-sdk.git;a=blob;f=LICENSE.txt;hb=HEAD
+ * Or within this repository: oculusvr-sdk/LICENSE.txt
+ *
+ * THIS RIFT SDK AND ANY COMPONENT THEREOF IS PROVIDED BY OCULUS VR AND
+ * ITS CONTRIBUTORS "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 OCULUS VR AS THE
+ * COPYRIGHT OWNER OR ITS 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 RIFT
+ * SDK OR THE RIFT SDK DERIVATIVES, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package jogamp.opengl.oculusvr.stereo.lense;
+
+public class LensConfig {
+ public static enum DistortionEquation { RecipPoly4, CatmullRom10 };
+
+ public static final int NumCoefficients = 11;
+
+ private DistortionEquation eqn;
+ /* pp */ float MetersPerTanAngleAtCenter;
+ private final float[] K;
+ private final float[] InvK;
+
+ private float MaxR; // The highest R you're going to query for - the curve is unpredictable beyond it.
+ private float MaxInvR;
+
+ // Additional per-channel scaling is applied after distortion:
+ // Index [0] - Red channel constant coefficient.
+ // Index [1] - Red channel r^2 coefficient.
+ // Index [2] - Blue channel constant coefficient.
+ // Index [3] - Blue channel r^2 coefficient.
+ private final float[] ChromaticAberration = new float[4];
+
+ public LensConfig() {
+ this.K = new float[NumCoefficients];
+ this.InvK = new float[NumCoefficients];
+ SetToIdentity();
+ }
+
+ public LensConfig(final DistortionEquation eqn, final float MetersPerTanAngleAtCenter, final float[] K) {
+ this.K = new float[NumCoefficients];
+ this.InvK = new float[NumCoefficients];
+ SetToIdentity();
+ this.eqn = eqn;
+ this.MetersPerTanAngleAtCenter = MetersPerTanAngleAtCenter;
+ System.arraycopy(K, 0, this.K, 0, K.length);
+
+ // Chromatic aberration doesn't seem to change with eye relief.
+ ChromaticAberration[0] = -0.006f;
+ ChromaticAberration[1] = 0.0f;
+ ChromaticAberration[2] = 0.014f;
+ ChromaticAberration[3] = 0.0f;
+ }
+
+ private void SetUpInverseApprox() {
+ switch ( eqn )
+ {
+ case RecipPoly4: {
+ final float[] sampleR = new float[4];
+ final float[] sampleRSq = new float[4];
+ final float[] sampleInv = new float[4];
+ final float[] sampleFit = new float[4];
+ final float maxR = MaxInvR;
+
+ // Found heuristically...
+ sampleR[0] = 0.0f;
+ sampleR[1] = maxR * 0.4f;
+ sampleR[2] = maxR * 0.8f;
+ sampleR[3] = maxR * 1.5f;
+ for ( int i = 0; i < 4; i++ ) {
+ sampleRSq[i] = sampleR[i] * sampleR[i];
+ sampleInv[i] = DistortionFnInverse ( sampleR[i] );
+ sampleFit[i] = sampleR[i] / sampleInv[i];
+ }
+ sampleFit[0] = 1.0f;
+ FitCubicPolynomial ( InvK, sampleRSq, sampleFit );
+ }
+ break;
+ case CatmullRom10: {
+ final int NumSegments = NumCoefficients;
+ for ( int i = 1; i < NumSegments; i++ ) {
+ final float scaledRsq = i;
+ final float rsq = scaledRsq * MaxInvR * MaxInvR / ( NumSegments - 1);
+ final float r = (float)Math.sqrt ( rsq );
+ final float inv = DistortionFnInverse ( r );
+ InvK[i] = inv / r;
+ InvK[0] = 1.0f; // TODO: fix this.
+ }
+ }
+ break;
+ default:
+ throw new InternalError("unsupported EQ "+eqn);
+ }
+
+ }
+
+ private void SetToIdentity() {
+ for ( int i = 0; i < NumCoefficients; i++ )
+ {
+ K[i] = 0.0f;
+ InvK[i] = 0.0f;
+ }
+ eqn = DistortionEquation.RecipPoly4;
+ K[0] = 1.0f;
+ InvK[0] = 1.0f;
+ MaxR = 1.0f;
+ MaxInvR = 1.0f;
+ ChromaticAberration[0] = 0.0f;
+ ChromaticAberration[1] = 0.0f;
+ ChromaticAberration[2] = 0.0f;
+ ChromaticAberration[3] = 0.0f;
+ MetersPerTanAngleAtCenter = 0.05f;
+ }
+
+ // DistortionFn applies distortion to the argument.
+ // Input: the distance in TanAngle/NIC space from the optical center to the input pixel.
+ // Output: the resulting distance after distortion.
+ public float DistortionFn(final float r) {
+ return r * DistortionFnScaleRadiusSquared ( r * r );
+ }
+
+ // The result is a scaling applied to the distance.
+ public float DistortionFnInverseApprox(final float r) {
+ final float rsq = r * r;
+ final float scale;
+
+ switch ( eqn )
+ {
+ case RecipPoly4: {
+ scale = 1.0f / ( InvK[0] + rsq * ( InvK[1] + rsq * ( InvK[2] + rsq * InvK[3] ) ) );
+ }
+ break;
+ case CatmullRom10: {
+ // A Catmull-Rom spline through the values 1.0, K[1], K[2] ... K[9]
+ // evenly spaced in R^2 from 0.0 to MaxR^2
+ // K[0] controls the slope at radius=0.0, rather than the actual value.
+ final int NumSegments = NumCoefficients;
+ final float scaledRsq = (NumSegments-1) * rsq / ( MaxInvR * MaxInvR );
+ scale = EvalCatmullRom10Spline ( InvK, scaledRsq );
+ }
+ break;
+ default:
+ throw new InternalError("unsupported EQ "+eqn);
+ }
+ return r * scale;
+ }
+
+ // DistortionFnInverse computes the inverse of the distortion function on an argument.
+ public float DistortionFnInverse(final float r) {
+ float s, d;
+ float delta = r * 0.25f;
+
+ // Better to start guessing too low & take longer to converge than too high
+ // and hit singularities. Empirically, r * 0.5f is too high in some cases.
+ s = r * 0.25f;
+ d = Math.abs(r - DistortionFn(s));
+
+ for (int i = 0; i < 20; i++)
+ {
+ final float sUp = s + delta;
+ final float sDown = s - delta;
+ final float dUp = Math.abs(r - DistortionFn(sUp));
+ final float dDown = Math.abs(r - DistortionFn(sDown));
+
+ if (dUp < d)
+ {
+ s = sUp;
+ d = dUp;
+ }
+ else if (dDown < d)
+ {
+ s = sDown;
+ d = dDown;
+ }
+ else
+ {
+ delta *= 0.5f;
+ }
+ }
+ return s;
+ }
+
+ // The result is a scaling applied to the distance from the center of the lens.
+ public float DistortionFnScaleRadiusSquared (final float rsq) {
+ final float scale;
+ switch ( eqn )
+ {
+ case RecipPoly4: {
+ scale = 1.0f / ( K[0] + rsq * ( K[1] + rsq * ( K[2] + rsq * K[3] ) ) );
+ }
+ break;
+ case CatmullRom10: {
+ // A Catmull-Rom spline through the values 1.0, K[1], K[2] ... K[10]
+ // evenly spaced in R^2 from 0.0 to MaxR^2
+ // K[0] controls the slope at radius=0.0, rather than the actual value.
+ final int NumSegments = LensConfig.NumCoefficients;
+ final float scaledRsq = (NumSegments-1) * rsq / ( MaxR * MaxR );
+ scale = EvalCatmullRom10Spline ( K, scaledRsq );
+ }
+ break;
+ default:
+ throw new InternalError("unsupported EQ "+eqn);
+ }
+ return scale;
+ }
+
+ // x,y,z components map to r,g,b
+ public float[] DistortionFnScaleRadiusSquaredChroma(final float radiusSquared) {
+ final float scale = DistortionFnScaleRadiusSquared ( radiusSquared );
+ final float[] scaleRGB = new float[3];
+ scaleRGB[0] = scale * ( 1.0f + ChromaticAberration[0] + radiusSquared * ChromaticAberration[1] ); // Red
+ scaleRGB[1] = scale; // Green
+ scaleRGB[2] = scale * ( 1.0f + ChromaticAberration[2] + radiusSquared * ChromaticAberration[3] ); // Blue
+ return scaleRGB;
+ }
+
+ /**
+ // Inputs are 4 points (pFitX[0],pFitY[0]) through (pFitX[3],pFitY[3])
+ // Result is four coefficients in pResults[0] through pResults[3] such that
+ // y = pResult[0] + x * ( pResult[1] + x * ( pResult[2] + x * ( pResult[3] ) ) );
+ // passes through all four input points.
+ // Return is true if it succeeded, false if it failed (because two control points
+ // have the same pFitX value).
+ *
+ * @param pResult
+ * @param pFitX
+ * @param pFitY
+ * @return
+ */
+ private static boolean FitCubicPolynomial ( final float[/*4*/] pResult, final float[/*4*/] pFitX, final float[/*4*/] pFitY ) {
+ final float d0 = ( ( pFitX[0]-pFitX[1] ) * ( pFitX[0]-pFitX[2] ) * ( pFitX[0]-pFitX[3] ) );
+ final float d1 = ( ( pFitX[1]-pFitX[2] ) * ( pFitX[1]-pFitX[3] ) * ( pFitX[1]-pFitX[0] ) );
+ final float d2 = ( ( pFitX[2]-pFitX[3] ) * ( pFitX[2]-pFitX[0] ) * ( pFitX[2]-pFitX[1] ) );
+ final float d3 = ( ( pFitX[3]-pFitX[0] ) * ( pFitX[3]-pFitX[1] ) * ( pFitX[3]-pFitX[2] ) );
+
+ if ( ( d0 == 0.0f ) || ( d1 == 0.0f ) || ( d2 == 0.0f ) || ( d3 == 0.0f ) )
+ {
+ return false;
+ }
+
+ final float f0 = pFitY[0] / d0;
+ final float f1 = pFitY[1] / d1;
+ final float f2 = pFitY[2] / d2;
+ final float f3 = pFitY[3] / d3;
+
+ pResult[0] = -( f0*pFitX[1]*pFitX[2]*pFitX[3]
+ + f1*pFitX[0]*pFitX[2]*pFitX[3]
+ + f2*pFitX[0]*pFitX[1]*pFitX[3]
+ + f3*pFitX[0]*pFitX[1]*pFitX[2] );
+ pResult[1] = f0*(pFitX[1]*pFitX[2] + pFitX[2]*pFitX[3] + pFitX[3]*pFitX[1])
+ + f1*(pFitX[0]*pFitX[2] + pFitX[2]*pFitX[3] + pFitX[3]*pFitX[0])
+ + f2*(pFitX[0]*pFitX[1] + pFitX[1]*pFitX[3] + pFitX[3]*pFitX[0])
+ + f3*(pFitX[0]*pFitX[1] + pFitX[1]*pFitX[2] + pFitX[2]*pFitX[0]);
+ pResult[2] = -( f0*(pFitX[1]+pFitX[2]+pFitX[3])
+ + f1*(pFitX[0]+pFitX[2]+pFitX[3])
+ + f2*(pFitX[0]+pFitX[1]+pFitX[3])
+ + f3*(pFitX[0]+pFitX[1]+pFitX[2]) );
+ pResult[3] = f0 + f1 + f2 + f3;
+
+ return true;
+ }
+
+ private static float EvalCatmullRom10Spline ( final float[] K, final float scaledVal ) {
+ final int NumSegments = NumCoefficients;
+
+ float scaledValFloor = (float)Math.floor( scaledVal );
+ scaledValFloor = Math.max( 0.0f, Math.min( NumSegments-1, scaledValFloor ) );
+ final float t = scaledVal - scaledValFloor;
+ final int k = (int)scaledValFloor;
+
+ float p0, p1;
+ float m0, m1;
+ switch ( k )
+ {
+ case 0:
+ // Curve starts at 1.0 with gradient K[1]-K[0]
+ p0 = 1.0f;
+ m0 = ( K[1] - K[0] ); // general case would have been (K[1]-K[-1])/2
+ p1 = K[1];
+ m1 = 0.5f * ( K[2] - K[0] );
+ break;
+ default:
+ // General case
+ p0 = K[k ];
+ m0 = 0.5f * ( K[k+1] - K[k-1] );
+ p1 = K[k+1];
+ m1 = 0.5f * ( K[k+2] - K[k ] );
+ break;
+ case NumSegments-2:
+ // Last tangent is just the slope of the last two points.
+ p0 = K[NumSegments-2];
+ m0 = 0.5f * ( K[NumSegments-1] - K[NumSegments-2] );
+ p1 = K[NumSegments-1];
+ m1 = K[NumSegments-1] - K[NumSegments-2];
+ break;
+ case NumSegments-1:
+ // Beyond the last segment it's just a straight line
+ p0 = K[NumSegments-1];
+ m0 = K[NumSegments-1] - K[NumSegments-2];
+ p1 = p0 + m0;
+ m1 = m0;
+ break;
+ }
+
+ final float omt = 1.0f - t;
+ final float res = ( p0 * ( 1.0f + 2.0f * t ) + m0 * t ) * omt * omt
+ + ( p1 * ( 1.0f + 2.0f * omt ) - m1 * omt ) * t * t;
+
+ return res;
+ }
+
+ /** FIXME: Add 'pluggable' lense configuration */
+ public static LensConfig[] GenerateLensConfigFromEyeRelief(final float[] eyeReliefInMeters, final DistortionEquation eqn) {
+ final LensConfig[] result = new LensConfig[2];
+ final DistortionDescriptor[] distortions = LensConfig.CreateDistortionDescriptorsforOVRDK1_CupsABC();
+ result[0] = GenerateLensConfigFromEyeRelief(eyeReliefInMeters[0], distortions, eqn);
+ result[1] = GenerateLensConfigFromEyeRelief(eyeReliefInMeters[1], distortions, eqn);
+ return result;
+ }
+
+ private static LensConfig GenerateLensConfigFromEyeRelief(final float eyeReliefInMeters, final DistortionDescriptor[] distortions, final DistortionEquation eqn) {
+ final int numDistortions = distortions.length;
+ final int defaultDistortion = 0; // index of the default distortion curve to use if zero eye relief supplied
+
+ DistortionDescriptor pUpper = null;
+ DistortionDescriptor pLower = null;
+ float lerpVal = 0.0f;
+ if (eyeReliefInMeters == 0)
+ { // Use a constant default distortion if an invalid eye-relief is supplied
+ pLower = distortions[defaultDistortion];
+ pUpper = distortions[defaultDistortion];
+ lerpVal = 0.0f;
+ } else {
+ for ( int i = 0; i < numDistortions-1; i++ )
+ {
+ assert( distortions[i].eyeRelief < distortions[i+1].eyeRelief );
+ if ( ( distortions[i].eyeRelief <= eyeReliefInMeters ) && ( distortions[i+1].eyeRelief > eyeReliefInMeters ) )
+ {
+ pLower = distortions[i];
+ pUpper = distortions[i+1];
+ lerpVal = ( eyeReliefInMeters - pLower.eyeRelief ) / ( pUpper.eyeRelief - pLower.eyeRelief );
+ // No break here - I want the ASSERT to check everything every time!
+ }
+ }
+ }
+
+ if ( pUpper == null )
+ {
+ // Do not extrapolate, just clamp - slightly worried about people putting in bogus settings.
+ if ( distortions[0].eyeRelief > eyeReliefInMeters )
+ {
+ pLower = distortions[0];
+ pUpper = distortions[0];
+ }
+ else
+ {
+ assert ( distortions[numDistortions-1].eyeRelief <= eyeReliefInMeters );
+ pLower = distortions[numDistortions-1];
+ pUpper = distortions[numDistortions-1];
+ }
+ lerpVal = 0.0f;
+ }
+ final float invLerpVal = 1.0f - lerpVal;
+
+ pLower.config.MaxR = pLower.maxRadius;
+ pUpper.config.MaxR = pUpper.maxRadius;
+
+ final LensConfig result = new LensConfig();
+ // Where is the edge of the lens - no point modelling further than this.
+ final float maxValidRadius = invLerpVal * pLower.maxRadius + lerpVal * pUpper.maxRadius;
+ result.MaxR = maxValidRadius;
+
+ switch ( eqn )
+ {
+ case RecipPoly4:{
+ // Lerp control points and fit an equation to them.
+ final float[] fitX = new float[4];
+ final float[] fitY = new float[4];
+ fitX[0] = 0.0f;
+ fitY[0] = 1.0f;
+ for ( int ctrlPt = 1; ctrlPt < 4; ctrlPt ++ )
+ {
+ final float radiusLerp = invLerpVal * pLower.sampleRadius[ctrlPt-1] + lerpVal * pUpper.sampleRadius[ctrlPt-1];
+ final float radiusLerpSq = radiusLerp * radiusLerp;
+ final float fitYLower = pLower.config.DistortionFnScaleRadiusSquared ( radiusLerpSq );
+ final float fitYUpper = pUpper.config.DistortionFnScaleRadiusSquared ( radiusLerpSq );
+ fitX[ctrlPt] = radiusLerpSq;
+ fitY[ctrlPt] = 1.0f / ( invLerpVal * fitYLower + lerpVal * fitYUpper );
+ }
+ result.eqn = DistortionEquation.RecipPoly4;
+ final boolean bSuccess = LensConfig.FitCubicPolynomial ( result.K, fitX, fitY );
+ assert ( bSuccess );
+
+ // Set up the fast inverse.
+ final float maxRDist = result.DistortionFn ( maxValidRadius );
+ result.MaxInvR = maxRDist;
+ result.SetUpInverseApprox();
+ }
+ break;
+ case CatmullRom10: {
+ // Evenly sample & lerp points on the curve.
+ final int NumSegments = LensConfig.NumCoefficients;
+ result.MaxR = maxValidRadius;
+ // Directly interpolate the K0 values
+ result.K[0] = invLerpVal * pLower.config.K[0] + lerpVal * pUpper.config.K[0];
+
+ // Sample and interpolate the distortion curves to derive K[1] ... K[n]
+ for ( int ctrlPt = 1; ctrlPt < NumSegments; ctrlPt++ )
+ {
+ final float radiusSq = ( (float)ctrlPt / (float)(NumSegments-1) ) * maxValidRadius * maxValidRadius;
+ final float fitYLower = pLower.config.DistortionFnScaleRadiusSquared ( radiusSq );
+ final float fitYUpper = pUpper.config.DistortionFnScaleRadiusSquared ( radiusSq );
+ final float fitLerp = invLerpVal * fitYLower + lerpVal * fitYUpper;
+ result.K[ctrlPt] = fitLerp;
+ }
+
+ result.eqn = DistortionEquation.CatmullRom10;
+
+ for ( int ctrlPt = 1; ctrlPt < NumSegments; ctrlPt++ )
+ {
+ final float radiusSq = ( (float)ctrlPt / (float)(NumSegments-1) ) * maxValidRadius * maxValidRadius;
+ final float val = result.DistortionFnScaleRadiusSquared ( radiusSq );
+ assert ( Math.abs( val - result.K[ctrlPt] ) < 0.0001f );
+ }
+
+ // Set up the fast inverse.
+ final float maxRDist = result.DistortionFn ( maxValidRadius );
+ result.MaxInvR = maxRDist;
+ result.SetUpInverseApprox();
+ }
+ break;
+ default:
+ throw new InternalError("unsupported EQ "+eqn);
+ }
+
+ // Chromatic aberration.
+ result.ChromaticAberration[0] = invLerpVal * pLower.config.ChromaticAberration[0] + lerpVal * pUpper.config.ChromaticAberration[0];
+ result.ChromaticAberration[1] = invLerpVal * pLower.config.ChromaticAberration[1] + lerpVal * pUpper.config.ChromaticAberration[1];
+ result.ChromaticAberration[2] = invLerpVal * pLower.config.ChromaticAberration[2] + lerpVal * pUpper.config.ChromaticAberration[2];
+ result.ChromaticAberration[3] = invLerpVal * pLower.config.ChromaticAberration[3] + lerpVal * pUpper.config.ChromaticAberration[3];
+
+ // Scale.
+ result.MetersPerTanAngleAtCenter = pLower.config.MetersPerTanAngleAtCenter * invLerpVal +
+ pUpper.config.MetersPerTanAngleAtCenter * lerpVal;
+
+ return result;
+ }
+
+ public static class DistortionDescriptor {
+ public DistortionDescriptor(final LensConfig lens, final float eyeRelief,
+ final float[] sampleRadius, final float maxRadius) {
+ this.config = lens;
+ this.eyeRelief = eyeRelief;
+ this.sampleRadius = sampleRadius;
+ this.maxRadius = maxRadius;
+ }
+
+ final LensConfig config;
+ final float eyeRelief;
+ final float[] sampleRadius;
+ final float maxRadius;
+ }
+
+ /*** Hardcoded OculusVR DK1 A, B, C eye cups (lenses) */
+ public static DistortionDescriptor[] CreateDistortionDescriptorsforOVRDK1_CupsABC() {
+ return new DistortionDescriptor[] {
+ // Tuned at minimum dial setting - extended to r^2 == 1.8
+ new DistortionDescriptor(
+ new LensConfig(DistortionEquation.CatmullRom10,
+ 0.0425f, // MetersPerTanAngleAtCenter
+ new float[] { 1.0000f, // K00
+ 1.06505f, // K01
+ 1.14725f, // K02
+ 1.2705f, // K03
+ 1.48f, // K04
+ 1.87f, // K05
+ 2.534f, // K06
+ 3.6f, // K07
+ 5.1f, // K08
+ 7.4f, // K09
+ 11.0f} ), // K10
+ 0.012760465f - 0.005f, // eyeRelief
+ new float[] { 0.222717149f, 0.512249443f, 0.712694878f }, // sampleRadius
+ (float)Math.sqrt(1.8f) ), // maxRadius
+ // Tuned at middle dial setting
+ new DistortionDescriptor(
+ new LensConfig(DistortionEquation.CatmullRom10,
+ 0.0425f, // MetersPerTanAngleAtCenter
+ new float[] { 1.0000f, // K00
+ 1.032407264f, // K01
+ 1.07160462f, // K02
+ 1.11998388f, // K03
+ 1.1808606f, // K04
+ 1.2590494f, // K05
+ 1.361915f, // K06
+ 1.5014339f, // K07
+ 1.6986004f, // K08
+ 1.9940577f, // K09
+ 2.4783147f} ), // K10
+ 0.012760465f, // eyeRelief
+ new float[] { 0.222717149f, 0.512249443f, 0.712694878f }, // sampleRadius
+ 1.0f ), // maxRadius
+ // Tuned at maximum dial setting
+ new DistortionDescriptor(
+ new LensConfig(DistortionEquation.CatmullRom10,
+ 0.0425f, // MetersPerTanAngleAtCenter
+ new float[] { 1.0102f, // K00
+ 1.0371f, // K01
+ 1.0831f, // K02
+ 1.1353f, // K03
+ 1.2f, // K04
+ 1.2851f, // K05
+ 1.3979f, // K06
+ 1.56f, // K07
+ 1.8f, // K08
+ 2.25f, // K09
+ 3.0f} ), // K10
+ 0.012760465f + 0.005f, // eyeRelief
+ new float[] { 0.222717149f, 0.512249443f, 0.712694878f }, // sampleRadius
+ 1.0f ), // maxRadius
+ };
+ }
+
+} \ No newline at end of file