/** * Copyright 2014 JogAmp Community. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are * permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of * conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list * of conditions and the following disclaimer in the documentation and/or other materials * provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY JogAmp Community ``AS IS'' AND ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JogAmp Community OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation are those of the * authors and should not be interpreted as representing official policies, either expressed * or implied, of JogAmp Community. */ package jogamp.opengl.util.stereo; import java.util.Arrays; import javax.media.nativewindow.util.Dimension; import javax.media.nativewindow.util.DimensionImmutable; import javax.media.nativewindow.util.Point; import javax.media.nativewindow.util.PointImmutable; import javax.media.nativewindow.util.Rectangle; import javax.media.nativewindow.util.RectangleImmutable; import com.jogamp.common.util.ReflectionUtil; import com.jogamp.opengl.math.FloatUtil; import com.jogamp.opengl.math.FovHVHalves; import com.jogamp.opengl.util.stereo.EyeParameter; import com.jogamp.opengl.util.stereo.StereoDevice; import com.jogamp.opengl.util.stereo.StereoDeviceFactory; import com.jogamp.opengl.util.stereo.StereoDeviceRenderer; import com.jogamp.opengl.util.stereo.StereoUtil; public class GenericStereoDevice implements StereoDevice { public static enum ShutterType { Global, RollingLeftToRight, RollingRightToLeft, RollingTopToBottom } public static class Config extends StereoDevice.Config { public Config(final String name, final ShutterType shutterType, final DimensionImmutable surfaceSizeInPixels, final float[] screenSizeInMeters, final DimensionImmutable eyeTextureSize, final float pupilCenterFromScreenTopInMeters, final float interpupillaryDistanceInMeters, final int[] eyeRenderOrder, final EyeParameter[] defaultEyeParam, final DistortionMesh.Producer distortionMeshProducer, final int supportedDistortionBits, final int recommendedDistortionBits, final int minimumDistortionBits ) { this.name = name; this.shutterType = shutterType; this.surfaceSizeInPixels = surfaceSizeInPixels; this.screenSizeInMeters = screenSizeInMeters; this.eyeTextureSize = eyeTextureSize; this.pupilCenterFromScreenTopInMeters = pupilCenterFromScreenTopInMeters; this.interpupillaryDistanceInMeters = interpupillaryDistanceInMeters; this.eyeRenderOrder = eyeRenderOrder; this.defaultEyeParam = defaultEyeParam; this.distortionMeshProducer = distortionMeshProducer; this.supportedDistortionBits = supportedDistortionBits; this.recommendedDistortionBits = recommendedDistortionBits; this.minimumDistortionBits = minimumDistortionBits; this.pupilCenterFromTopLeft = new float[2][2]; calcPupilCenterFromTopLeft(); } /** A variation w/ different surface/screen specs */ public Config(final Config source, final DimensionImmutable surfaceSizeInPixels, final float[] screenSizeInMeters, final DimensionImmutable eyeTextureSize) { this.name = source.name; this.shutterType = source.shutterType; this.surfaceSizeInPixels = surfaceSizeInPixels; this.screenSizeInMeters = screenSizeInMeters; this.eyeTextureSize = eyeTextureSize; this.pupilCenterFromScreenTopInMeters = source.pupilCenterFromScreenTopInMeters; this.interpupillaryDistanceInMeters = source.interpupillaryDistanceInMeters; this.eyeRenderOrder = source.eyeRenderOrder; this.defaultEyeParam = source.defaultEyeParam; this.distortionMeshProducer = source.distortionMeshProducer; this.supportedDistortionBits = source.supportedDistortionBits; this.recommendedDistortionBits = source.recommendedDistortionBits; this.minimumDistortionBits = source.minimumDistortionBits; this.pupilCenterFromTopLeft = new float[2][2]; calcPupilCenterFromTopLeft(); } private void calcPupilCenterFromTopLeft() { final float visibleWidthOfOneEye = 0.5f * screenSizeInMeters[0]; final float leftPupilCenterFromLeftInMeters = ( screenSizeInMeters[0] - interpupillaryDistanceInMeters ) * 0.5f; final float rightPupilCenterFromMiddleInMeters = leftPupilCenterFromLeftInMeters + interpupillaryDistanceInMeters - visibleWidthOfOneEye; pupilCenterFromTopLeft[0][0] = leftPupilCenterFromLeftInMeters / visibleWidthOfOneEye; pupilCenterFromTopLeft[0][1] = pupilCenterFromScreenTopInMeters / screenSizeInMeters[1]; pupilCenterFromTopLeft[1][0] = rightPupilCenterFromMiddleInMeters / visibleWidthOfOneEye; pupilCenterFromTopLeft[1][1] = pupilCenterFromTopLeft[0][1]; } /** * Return the vertical pupil center from the screen top in the range [0..1]. * @param screenHeightInMeters * @param pupilCenterFromScreenTopInMeters */ public static float getVertPupilCenterFromTop(final float screenHeightInMeters, final float pupilCenterFromScreenTopInMeters) { return pupilCenterFromScreenTopInMeters / screenHeightInMeters; } /** * Return the horizontal pupil center from the left side for both eyes in the range [0..1]. *
<-------------left eye------------->| |<-----------right eye--------------> <------------------------------------screenSizeInMeters.Width-----------------------------------> <------interpupillaryDistanceInMeters------> <--centerFromLeftInMeters-> ^ center of pupil ** * @param screenWidthInMeters * @param interpupillaryDistanceInMeters */ public static float[] getHorizPupilCenterFromLeft(final float screenWidthInMeters, final float interpupillaryDistanceInMeters) { final float visibleWidthOfOneEye = 0.5f * screenWidthInMeters; final float leftPupilCenterFromLeftInMeters = ( screenWidthInMeters - interpupillaryDistanceInMeters ) * 0.5f; final float rightPupilCenterFromMiddleInMeters = leftPupilCenterFromLeftInMeters + interpupillaryDistanceInMeters - visibleWidthOfOneEye; return new float[] { leftPupilCenterFromLeftInMeters / visibleWidthOfOneEye, rightPupilCenterFromMiddleInMeters / visibleWidthOfOneEye }; } void init() { final float[] eyeReliefInMeters = new float[defaultEyeParam.length]; if( 0 < defaultEyeParam.length ) { eyeReliefInMeters[0] = defaultEyeParam[0].eyeReliefZ; } if( 1 < defaultEyeParam.length ) { eyeReliefInMeters[1] = defaultEyeParam[1].eyeReliefZ; } if( null != distortionMeshProducer ) { distortionMeshProducer.init(this, eyeReliefInMeters); } } @Override public String toString() { return "StereoConfig["+name+", shutter "+shutterType+", surfaceSize "+surfaceSizeInPixels+ ", screenSize "+screenSizeInMeters[0]+" x "+screenSizeInMeters[0]+ " [m], eyeTexSize "+eyeTextureSize+", IPD "+interpupillaryDistanceInMeters+ " [m], eyeParam "+Arrays.toString(defaultEyeParam)+ ", distortionBits[supported ["+StereoUtil.distortionBitsToString(supportedDistortionBits)+ "], recommended ["+StereoUtil.distortionBitsToString(recommendedDistortionBits)+ "], minimum ["+StereoUtil.distortionBitsToString(minimumDistortionBits)+"]]]"; } /** Configuration Name */ public final String name; public final ShutterType shutterType; public final DimensionImmutable surfaceSizeInPixels; public final float[] screenSizeInMeters; /** Texture size per eye */ public final DimensionImmutable eyeTextureSize; /** Vertical distance from pupil to screen-top in meters */ public final float pupilCenterFromScreenTopInMeters; /** Horizontal interpupillary distance (IPD) in meters */ public final float interpupillaryDistanceInMeters; /** * Pupil center from top left per eye, ranging from [0..1], maybe used to produce FovHVHalves, * see {@link #getHorizPupilCenterFromLeft(float, float)} and {@link #getVertPupilCenterFromTop(float, float)}. */ public final float[/*per-eye*/][/*xy*/] pupilCenterFromTopLeft; public final int[] eyeRenderOrder; public final EyeParameter[] defaultEyeParam; public final DistortionMesh.Producer distortionMeshProducer; public final int supportedDistortionBits; public final int recommendedDistortionBits; public final int minimumDistortionBits; } /** A mono view configuration, only one eye is supported */ public static final Config config01Mono01; /** A default stereo SBS view configuration */ public static final Config config02StereoSBS01; /** A default stereo SBS lense view configuration, utilizing similar settings as OculusVR DK1 */ public static final Config config03StereoSBSLense01; private static final Config[] configs; static { final float[] DEFAULT_EYE_POSITION_OFFSET_STEREO_LENSES = { 0.0f, 1.6f, -5.0f }; // 1.6 up, 5 forward final float[] DEFAULT_EYE_POSITION_OFFSET_STEREO = { 0.0f, 0.3f, 3.0f }; // 0.3 up, 3 back final float[] DEFAULT_EYE_POSITION_OFFSET_MONO = { 0.0f, 0.0f, 3.0f }; // 3 back final DimensionImmutable surfaceSizeInPixel = new Dimension(1280, 800); final float[] screenSizeInMeters = new float[] { 0.1498f, 0.0936f }; final float interpupillaryDistanceInMeters = 0.0635f; final float pupilCenterFromScreenTopInMeters = screenSizeInMeters[1] / 2f; final float d2r = FloatUtil.PI / 180.0f; { config01Mono01 = new Config( "Def01Mono01", ShutterType.RollingTopToBottom, surfaceSizeInPixel, // resolution screenSizeInMeters, // screenSize [m] surfaceSizeInPixel, // eye textureSize pupilCenterFromScreenTopInMeters, // pupilCenterFromScreenTop [m] interpupillaryDistanceInMeters, // IPD [m] new int[] { 0 }, // eye order new EyeParameter[] { new EyeParameter(0, DEFAULT_EYE_POSITION_OFFSET_MONO, // degrees: 45/2 l, 45/2 r, 45/2 * aspect t, 45/2 * aspect b FovHVHalves.byFovyRadianAndAspect(45f*d2r, 1280f / 800f), 0f /* distNoseToPupil */, 0f /* verticalDelta */, 0f /* eyeReliefInMeters */) }, null, // mash producer distortion bits 0, // supported distortion bits 0, // recommended distortion bits 0 // minimum distortion bits ); } { final DimensionImmutable eyeTextureSize = new Dimension(surfaceSizeInPixel.getWidth()/2, surfaceSizeInPixel.getHeight()); final float[] horizPupilCenterFromLeft = Config.getHorizPupilCenterFromLeft(screenSizeInMeters[0], interpupillaryDistanceInMeters); final float vertPupilCenterFromTop = Config.getVertPupilCenterFromTop(screenSizeInMeters[1], pupilCenterFromScreenTopInMeters); final float fovy = 45f; final float aspect = (float)eyeTextureSize.getWidth() / (float)eyeTextureSize.getHeight(); final FovHVHalves defaultSBSEyeFovLeft = FovHVHalves.byFovyRadianAndAspect(fovy * d2r, vertPupilCenterFromTop, aspect, horizPupilCenterFromLeft[0]); final FovHVHalves defaultSBSEyeFovRight = FovHVHalves.byFovyRadianAndAspect(fovy * d2r, vertPupilCenterFromTop, aspect, horizPupilCenterFromLeft[1]); config02StereoSBS01 = new Config( "Def02StereoSBS01", ShutterType.RollingTopToBottom, surfaceSizeInPixel, // resolution screenSizeInMeters, // screenSize [m] eyeTextureSize, // eye textureSize pupilCenterFromScreenTopInMeters, // pupilCenterFromScreenTop [m] interpupillaryDistanceInMeters, // IPD [m] new int[] { 0, 1 }, // eye order new EyeParameter[] { new EyeParameter(0, DEFAULT_EYE_POSITION_OFFSET_STEREO, defaultSBSEyeFovLeft, 0.032f /* distNoseToPupil */, 0f /* verticalDelta */, 0.010f /* eyeReliefInMeters */), new EyeParameter(1, DEFAULT_EYE_POSITION_OFFSET_STEREO, defaultSBSEyeFovRight, -0.032f /* distNoseToPupil */, 0f /* verticalDelta */, 0.010f /* eyeReliefInMeters */) }, null, // mash producer distortion bits 0, // supported distortion bits 0, // recommended distortion bits 0 // minimum distortion bits ); } { DistortionMesh.Producer lenseDistMeshProduce = null; try { lenseDistMeshProduce = (DistortionMesh.Producer) ReflectionUtil.createInstance("jogamp.opengl.oculusvr.stereo.lense.DistortionMeshProducer", GenericStereoDevice.class.getClassLoader()); } catch (final Throwable t) { if(StereoDevice.DEBUG) { System.err.println("Caught: "+t.getMessage()); t.printStackTrace(); } } final DimensionImmutable eyeTextureSize = new Dimension(1122, 1553); final float[] horizPupilCenterFromLeft = Config.getHorizPupilCenterFromLeft(screenSizeInMeters[0], interpupillaryDistanceInMeters); final float vertPupilCenterFromTop = Config.getVertPupilCenterFromTop(screenSizeInMeters[1], pupilCenterFromScreenTopInMeters); final float fovy = 129f; final float aspect = (float)eyeTextureSize.getWidth() / (float)eyeTextureSize.getHeight(); final FovHVHalves defaultSBSEyeFovLenseLeft = FovHVHalves.byFovyRadianAndAspect(fovy * d2r, vertPupilCenterFromTop, aspect, horizPupilCenterFromLeft[0]); final FovHVHalves defaultSBSEyeFovLenseRight = FovHVHalves.byFovyRadianAndAspect(fovy * d2r, vertPupilCenterFromTop, aspect, horizPupilCenterFromLeft[1]); config03StereoSBSLense01 = null == lenseDistMeshProduce ? null : new Config( "Def03StereoSBSLense01", ShutterType.RollingTopToBottom, surfaceSizeInPixel, // resolution screenSizeInMeters, // screenSize [m] eyeTextureSize, // eye textureSize pupilCenterFromScreenTopInMeters, // pupilCenterFromScreenTop [m] interpupillaryDistanceInMeters, // IPD [m] new int[] { 0, 1 }, // eye order new EyeParameter[] { new EyeParameter(0, DEFAULT_EYE_POSITION_OFFSET_STEREO_LENSES, defaultSBSEyeFovLenseLeft, 0.032f /* distNoseToPupil */, 0f /* verticalDelta */, 0.010f /* eyeReliefInMeters */), new EyeParameter(1, DEFAULT_EYE_POSITION_OFFSET_STEREO_LENSES, defaultSBSEyeFovLenseRight, -0.032f /* distNoseToPupil */, 0f /* verticalDelta */, 0.010f /* eyeReliefInMeters */) }, lenseDistMeshProduce, // supported distortion bits StereoDeviceRenderer.DISTORTION_BARREL | StereoDeviceRenderer.DISTORTION_CHROMATIC | StereoDeviceRenderer.DISTORTION_VIGNETTE, // recommended distortion bits StereoDeviceRenderer.DISTORTION_BARREL | StereoDeviceRenderer.DISTORTION_CHROMATIC | StereoDeviceRenderer.DISTORTION_VIGNETTE, // minimum distortion bits StereoDeviceRenderer.DISTORTION_BARREL ); } configs = new Config[] { config01Mono01, config02StereoSBS01, config03StereoSBSLense01 }; } private final StereoDeviceFactory factory; public final int deviceIndex; public final Config config; public final Point surfacePos; private final FovHVHalves[] defaultEyeFov; private boolean sensorsStarted = false; public GenericStereoDevice(final StereoDeviceFactory factory, final int deviceIndex, final StereoDevice.Config customConfig) { this.factory = factory; this.deviceIndex = deviceIndex; if( customConfig instanceof GenericStereoDevice.Config) { this.config = (GenericStereoDevice.Config) customConfig; } else { final int cfgIdx = Math.min(deviceIndex % 10, configs.length-1); this.config = null != configs[cfgIdx] ? configs[cfgIdx] : config02StereoSBS01; } config.init(); this.surfacePos = new Point(0, 0); defaultEyeFov = new FovHVHalves[config.defaultEyeParam.length]; for(int i=0; i