diff options
Diffstat (limited to 'LibOVR/Src/OVR_Stereo.cpp')
-rw-r--r-- | LibOVR/Src/OVR_Stereo.cpp | 1805 |
1 files changed, 1805 insertions, 0 deletions
diff --git a/LibOVR/Src/OVR_Stereo.cpp b/LibOVR/Src/OVR_Stereo.cpp new file mode 100644 index 0000000..936a02a --- /dev/null +++ b/LibOVR/Src/OVR_Stereo.cpp @@ -0,0 +1,1805 @@ +/************************************************************************************ + +Filename : OVR_Stereo.cpp +Content : Stereo rendering functions +Created : November 30, 2013 +Authors : Tom Fosyth + +Copyright : Copyright 2014 Oculus VR, Inc. All Rights reserved. + +Licensed under the Oculus VR Rift SDK License Version 3.1 (the "License"); +you may not use the Oculus VR Rift SDK except in compliance with the License, +which is provided at the time of installation or download, or which +otherwise accompanies this software in either electronic or hard copy form. + +You may obtain a copy of the License at + +http://www.oculusvr.com/licenses/LICENSE-3.1 + +Unless required by applicable law or agreed to in writing, the Oculus VR SDK +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*************************************************************************************/ + +#include "OVR_Stereo.h" +#include "OVR_Profile.h" +#include "Kernel/OVR_Log.h" +#include "Kernel/OVR_Alg.h" + +//To allow custom distortion to be introduced to CatMulSpline. +float (*CustomDistortion)(float) = NULL; +float (*CustomDistortionInv)(float) = NULL; + + +namespace OVR { + + +using namespace Alg; + +//----------------------------------------------------------------------------------- + +// 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). +bool FitCubicPolynomial ( float *pResult, const float *pFitX, const float *pFitY ) +{ + float d0 = ( ( pFitX[0]-pFitX[1] ) * ( pFitX[0]-pFitX[2] ) * ( pFitX[0]-pFitX[3] ) ); + float d1 = ( ( pFitX[1]-pFitX[2] ) * ( pFitX[1]-pFitX[3] ) * ( pFitX[1]-pFitX[0] ) ); + float d2 = ( ( pFitX[2]-pFitX[3] ) * ( pFitX[2]-pFitX[0] ) * ( pFitX[2]-pFitX[1] ) ); + 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; + } + + float f0 = pFitY[0] / d0; + float f1 = pFitY[1] / d1; + float f2 = pFitY[2] / d2; + 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; +} + + + +float EvalCatmullRom10Spline ( float const *K, float scaledVal ) +{ + int const NumSegments = LensConfig::NumCoefficients; + + float scaledValFloor = floorf ( scaledVal ); + scaledValFloor = Alg::Max ( 0.0f, Alg::Min ( (float)(NumSegments-1), scaledValFloor ) ); + float t = scaledVal - scaledValFloor; + 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; + } + + float omt = 1.0f - t; + float res = ( p0 * ( 1.0f + 2.0f * t ) + m0 * t ) * omt * omt + + ( p1 * ( 1.0f + 2.0f * omt ) - m1 * omt ) * t * t; + + return res; +} + + + + +// Converts a Profile eyecup string into an eyecup enumeration +void SetEyeCup(HmdRenderInfo* renderInfo, const char* cup) +{ + if (OVR_strcmp(cup, "A") == 0) + renderInfo->EyeCups = EyeCup_DK1A; + else if (OVR_strcmp(cup, "B") == 0) + renderInfo->EyeCups = EyeCup_DK1B; + else if (OVR_strcmp(cup, "C") == 0) + renderInfo->EyeCups = EyeCup_DK1C; + else if (OVR_strcmp(cup, "Orange A") == 0) + renderInfo->EyeCups = EyeCup_OrangeA; + else if (OVR_strcmp(cup, "Red A") == 0) + renderInfo->EyeCups = EyeCup_RedA; + else if (OVR_strcmp(cup, "Pink A") == 0) + renderInfo->EyeCups = EyeCup_PinkA; + else if (OVR_strcmp(cup, "Blue A") == 0) + renderInfo->EyeCups = EyeCup_BlueA; + else + renderInfo->EyeCups = EyeCup_DK1A; +} + + + +//----------------------------------------------------------------------------------- + + +// The result is a scaling applied to the distance. +float LensConfig::DistortionFnScaleRadiusSquared (float rsq) const +{ + float scale = 1.0f; + switch ( Eqn ) + { + case Distortion_Poly4: + // This version is deprecated! Prefer one of the other two. + scale = ( K[0] + rsq * ( K[1] + rsq * ( K[2] + rsq * K[3] ) ) ); + break; + case Distortion_RecipPoly4: + scale = 1.0f / ( K[0] + rsq * ( K[1] + rsq * ( K[2] + rsq * K[3] ) ) ); + break; + case Distortion_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. + const int NumSegments = LensConfig::NumCoefficients; + OVR_ASSERT ( NumSegments <= NumCoefficients ); + float scaledRsq = (float)(NumSegments-1) * rsq / ( MaxR * MaxR ); + scale = EvalCatmullRom10Spline ( K, scaledRsq ); + + + //Intercept, and overrule if needed + if (CustomDistortion) + { + scale = CustomDistortion(rsq); + } + + }break; + default: + OVR_ASSERT ( false ); + break; + } + return scale; +} + +// x,y,z components map to r,g,b +Vector3f LensConfig::DistortionFnScaleRadiusSquaredChroma (float rsq) const +{ + float scale = DistortionFnScaleRadiusSquared ( rsq ); + Vector3f scaleRGB; + scaleRGB.x = scale * ( 1.0f + ChromaticAberration[0] + rsq * ChromaticAberration[1] ); // Red + scaleRGB.y = scale; // Green + scaleRGB.z = scale * ( 1.0f + ChromaticAberration[2] + rsq * ChromaticAberration[3] ); // Blue + return scaleRGB; +} + +// DistortionFnInverse computes the inverse of the distortion function on an argument. +float LensConfig::DistortionFnInverse(float r) const +{ + OVR_ASSERT((r <= 20.0f)); + + 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 = fabs(r - DistortionFn(s)); + + for (int i = 0; i < 20; i++) + { + float sUp = s + delta; + float sDown = s - delta; + float dUp = fabs(r - DistortionFn(sUp)); + float dDown = fabs(r - DistortionFn(sDown)); + + if (dUp < d) + { + s = sUp; + d = dUp; + } + else if (dDown < d) + { + s = sDown; + d = dDown; + } + else + { + delta *= 0.5f; + } + } + + return s; +} + + + +float LensConfig::DistortionFnInverseApprox(float r) const +{ + float rsq = r * r; + float scale = 1.0f; + switch ( Eqn ) + { + case Distortion_Poly4: + // Deprecated + OVR_ASSERT ( false ); + break; + case Distortion_RecipPoly4: + scale = 1.0f / ( InvK[0] + rsq * ( InvK[1] + rsq * ( InvK[2] + rsq * InvK[3] ) ) ); + break; + case Distortion_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. + const int NumSegments = LensConfig::NumCoefficients; + OVR_ASSERT ( NumSegments <= NumCoefficients ); + float scaledRsq = (float)(NumSegments-1) * rsq / ( MaxInvR * MaxInvR ); + scale = EvalCatmullRom10Spline ( InvK, scaledRsq ); + + //Intercept, and overrule if needed + if (CustomDistortionInv) + { + scale = CustomDistortionInv(rsq); + } + + }break; + default: + OVR_ASSERT ( false ); + break; + } + return r * scale; +} + +void LensConfig::SetUpInverseApprox() +{ + float maxR = MaxInvR; + + switch ( Eqn ) + { + case Distortion_Poly4: + // Deprecated + OVR_ASSERT ( false ); + break; + case Distortion_RecipPoly4:{ + + float sampleR[4]; + float sampleRSq[4]; + float sampleInv[4]; + float sampleFit[4]; + + // 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 ); + + #if 0 + // Should be a nearly exact match on the chosen points. + OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[0] ) - DistortionFnInverseApprox ( sampleR[0] ) ) / maxR < 0.0001f ); + OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[1] ) - DistortionFnInverseApprox ( sampleR[1] ) ) / maxR < 0.0001f ); + OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[2] ) - DistortionFnInverseApprox ( sampleR[2] ) ) / maxR < 0.0001f ); + OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[3] ) - DistortionFnInverseApprox ( sampleR[3] ) ) / maxR < 0.0001f ); + // Should be a decent match on the rest of the range. + const int maxCheck = 20; + for ( int i = 0; i < maxCheck; i++ ) + { + float checkR = (float)i * maxR / (float)maxCheck; + float realInv = DistortionFnInverse ( checkR ); + float testInv = DistortionFnInverseApprox ( checkR ); + float error = fabsf ( realInv - testInv ) / maxR; + OVR_ASSERT ( error < 0.1f ); + } + #endif + + }break; + case Distortion_CatmullRom10:{ + + const int NumSegments = LensConfig::NumCoefficients; + OVR_ASSERT ( NumSegments <= NumCoefficients ); + for ( int i = 1; i < NumSegments; i++ ) + { + float scaledRsq = (float)i; + float rsq = scaledRsq * MaxInvR * MaxInvR / (float)( NumSegments - 1); + float r = sqrtf ( rsq ); + float inv = DistortionFnInverse ( r ); + InvK[i] = inv / r; + InvK[0] = 1.0f; // TODO: fix this. + } + +#if 0 + const int maxCheck = 20; + for ( int i = 0; i <= maxCheck; i++ ) + { + float checkR = (float)i * MaxInvR / (float)maxCheck; + float realInv = DistortionFnInverse ( checkR ); + float testInv = DistortionFnInverseApprox ( checkR ); + float error = fabsf ( realInv - testInv ) / MaxR; + OVR_ASSERT ( error < 0.01f ); + } +#endif + + }break; + + default: + break; + } +} + + +void LensConfig::SetToIdentity() +{ + for ( int i = 0; i < NumCoefficients; i++ ) + { + K[i] = 0.0f; + InvK[i] = 0.0f; + } + Eqn = Distortion_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; +} + + +enum LensConfigStoredVersion +{ + LCSV_CatmullRom10Version1 = 1 +}; + +// DO NOT CHANGE THESE ONCE THEY HAVE BEEN BAKED INTO FIRMWARE. +// If something needs to change, add a new one! +struct LensConfigStored_CatmullRom10Version1 +{ + // All these items must be fixed-length integers - no "float", no "int", etc. + UInt16 VersionNumber; // Must be LCSV_CatmullRom10Version1 + + UInt16 K[11]; + UInt16 MaxR; + UInt16 MetersPerTanAngleAtCenter; + UInt16 ChromaticAberration[4]; + // InvK and MaxInvR are calculated on load. +}; + +UInt16 EncodeFixedPointUInt16 ( float val, UInt16 zeroVal, int fractionalBits ) +{ + OVR_ASSERT ( ( fractionalBits >= 0 ) && ( fractionalBits < 31 ) ); + float valWhole = val * (float)( 1 << fractionalBits ); + valWhole += (float)zeroVal + 0.5f; + valWhole = floorf ( valWhole ); + OVR_ASSERT ( ( valWhole >= 0.0f ) && ( valWhole < (float)( 1 << 16 ) ) ); + return (UInt16)valWhole; +} + +float DecodeFixedPointUInt16 ( UInt16 val, UInt16 zeroVal, int fractionalBits ) +{ + OVR_ASSERT ( ( fractionalBits >= 0 ) && ( fractionalBits < 31 ) ); + float valFloat = (float)val; + valFloat -= (float)zeroVal; + valFloat *= 1.0f / (float)( 1 << fractionalBits ); + return valFloat; +} + + +// Returns true on success. +bool LoadLensConfig ( LensConfig *presult, UByte const *pbuffer, int bufferSizeInBytes ) +{ + if ( bufferSizeInBytes < 2 ) + { + // Can't even tell the version number! + return false; + } + UInt16 version = DecodeUInt16 ( pbuffer + 0 ); + switch ( version ) + { + case LCSV_CatmullRom10Version1: + { + if ( bufferSizeInBytes < (int)sizeof(LensConfigStored_CatmullRom10Version1) ) + { + return false; + } + LensConfigStored_CatmullRom10Version1 lcs; + lcs.VersionNumber = DecodeUInt16 ( pbuffer + 0 ); + for ( int i = 0; i < 11; i++ ) + { + lcs.K[i] = DecodeUInt16 ( pbuffer + 2 + 2*i ); + } + lcs.MaxR = DecodeUInt16 ( pbuffer + 24 ); + lcs.MetersPerTanAngleAtCenter = DecodeUInt16 ( pbuffer + 26 ); + for ( int i = 0; i < 4; i++ ) + { + lcs.ChromaticAberration[i] = DecodeUInt16 ( pbuffer + 28 + 2*i ); + } + OVR_COMPILER_ASSERT ( sizeof(lcs) == 36 ); + + // Convert to the real thing. + LensConfig result; + result.Eqn = Distortion_CatmullRom10; + for ( int i = 0; i < 11; i++ ) + { + // K[] are mostly 1.something. They may get significantly bigger, but they never hit 0.0. + result.K[i] = DecodeFixedPointUInt16 ( lcs.K[i], 0, 14 ); + } + // MaxR is tan(angle), so always >0, typically just over 1.0 (45 degrees half-fov), + // but may get arbitrarily high. tan(76)=4 is a very reasonable limit! + result.MaxR = DecodeFixedPointUInt16 ( lcs.MaxR, 0, 14 ); + // MetersPerTanAngleAtCenter is also known as focal length! + // Typically around 0.04 for our current screens, minimum of 0, sensible maximum of 0.125 (i.e. 3 "extra" bits of fraction) + result.MetersPerTanAngleAtCenter = DecodeFixedPointUInt16 ( lcs.MetersPerTanAngleAtCenter, 0, 16+3 ); + for ( int i = 0; i < 4; i++ ) + { + // ChromaticAberration[] are mostly 0.0something, centered on 0.0. Largest seen is 0.04, so set max to 0.125 (i.e. 3 "extra" bits of fraction) + result.ChromaticAberration[i] = DecodeFixedPointUInt16 ( lcs.ChromaticAberration[i], 0x8000, 16+3 ); + } + result.MaxInvR = result.DistortionFn ( result.MaxR ); + result.SetUpInverseApprox(); + + OVR_ASSERT ( version == lcs.VersionNumber ); + + *presult = result; + } + break; + default: + // Unknown format. + return false; + break; + } + return true; +} + +// Returns number of bytes needed. +int SaveLensConfigSizeInBytes ( LensConfig const &config ) +{ + OVR_UNUSED ( config ); + return sizeof ( LensConfigStored_CatmullRom10Version1 ); +} + +// Returns true on success. +bool SaveLensConfig ( UByte *pbuffer, int bufferSizeInBytes, LensConfig const &config ) +{ + if ( bufferSizeInBytes < (int)sizeof ( LensConfigStored_CatmullRom10Version1 ) ) + { + return false; + } + + // Construct the values. + LensConfigStored_CatmullRom10Version1 lcs; + lcs.VersionNumber = LCSV_CatmullRom10Version1; + for ( int i = 0; i < 11; i++ ) + { + // K[] are mostly 1.something. They may get significantly bigger, but they never hit 0.0. + lcs.K[i] = EncodeFixedPointUInt16 ( config.K[i], 0, 14 ); + } + // MaxR is tan(angle), so always >0, typically just over 1.0 (45 degrees half-fov), + // but may get arbitrarily high. tan(76)=4 is a very reasonable limit! + lcs.MaxR = EncodeFixedPointUInt16 ( config.MaxR, 0, 14 ); + // MetersPerTanAngleAtCenter is also known as focal length! + // Typically around 0.04 for our current screens, minimum of 0, sensible maximum of 0.125 (i.e. 3 "extra" bits of fraction) + lcs.MetersPerTanAngleAtCenter = EncodeFixedPointUInt16 ( config.MetersPerTanAngleAtCenter, 0, 16+3 ); + for ( int i = 0; i < 4; i++ ) + { + // ChromaticAberration[] are mostly 0.0something, centered on 0.0. Largest seen is 0.04, so set max to 0.125 (i.e. 3 "extra" bits of fraction) + lcs.ChromaticAberration[i] = EncodeFixedPointUInt16 ( config.ChromaticAberration[i], 0x8000, 16+3 ); + } + + + // Now store them out, sensitive to endinness. + EncodeUInt16 ( pbuffer + 0, lcs.VersionNumber ); + for ( int i = 0; i < 11; i++ ) + { + EncodeUInt16 ( pbuffer + 2 + 2*i, lcs.K[i] ); + } + EncodeUInt16 ( pbuffer + 24, lcs.MaxR ); + EncodeUInt16 ( pbuffer + 26, lcs.MetersPerTanAngleAtCenter ); + for ( int i = 0; i < 4; i++ ) + { + EncodeUInt16 ( pbuffer + 28 + 2*i, lcs.ChromaticAberration[i] ); + } + OVR_COMPILER_ASSERT ( 36 == sizeof(lcs) ); + + return true; +} + +#ifdef OVR_BUILD_DEBUG +void TestSaveLoadLensConfig ( LensConfig const &config ) +{ + OVR_ASSERT ( config.Eqn == Distortion_CatmullRom10 ); + // As a test, make sure this can be encoded and decoded correctly. + const int bufferSize = 256; + UByte buffer[bufferSize]; + OVR_ASSERT ( SaveLensConfigSizeInBytes ( config ) < bufferSize ); + bool success; + success = SaveLensConfig ( buffer, bufferSize, config ); + OVR_ASSERT ( success ); + LensConfig testConfig; + success = LoadLensConfig ( &testConfig, buffer, bufferSize ); + OVR_ASSERT ( success ); + OVR_ASSERT ( testConfig.Eqn == config.Eqn ); + for ( int i = 0; i < 11; i++ ) + { + OVR_ASSERT ( fabs ( testConfig.K[i] - config.K[i] ) < 0.0001f ); + } + OVR_ASSERT ( fabsf ( testConfig.MaxR - config.MaxR ) < 0.0001f ); + OVR_ASSERT ( fabsf ( testConfig.MetersPerTanAngleAtCenter - config.MetersPerTanAngleAtCenter ) < 0.00001f ); + for ( int i = 0; i < 4; i++ ) + { + OVR_ASSERT ( fabsf ( testConfig.ChromaticAberration[i] - config.ChromaticAberration[i] ) < 0.00001f ); + } +} +#endif + + + +//----------------------------------------------------------------------------------- + +// TBD: There is a question of whether this is the best file for CreateDebugHMDInfo. As long as there are many +// constants for HmdRenderInfo here as well it is ok. The alternative would be OVR_Common_HMDDevice.cpp, but +// that's specialized per platform... should probably move it there onces the code is in the common base class. + +HMDInfo CreateDebugHMDInfo(HmdTypeEnum hmdType) +{ + HMDInfo info; + + if ((hmdType != HmdType_DK1) && + (hmdType != HmdType_CrystalCoveProto)) + { + LogText("Debug HMDInfo - HmdType not supported. Defaulting to DK1.\n"); + hmdType = HmdType_DK1; + } + + // The alternative would be to initialize info.HmdType to HmdType_None instead. If we did that, + // code wouldn't be "maximally compatible" and devs wouldn't know what device we are + // simulating... so if differentiation becomes necessary we better add Debug flag in the future. + info.HmdType = hmdType; + info.Manufacturer = "Oculus VR"; + + switch(hmdType) + { + case HmdType_DK1: + info.ProductName = "Oculus Rift DK1"; + info.ResolutionInPixels = Sizei ( 1280, 800 ); + info.ScreenSizeInMeters = Sizef ( 0.1498f, 0.0936f ); + info.ScreenGapSizeInMeters = 0.0f; + info.CenterFromTopInMeters = 0.0468f; + info.LensSeparationInMeters = 0.0635f; + info.Shutter.Type = HmdShutter_RollingTopToBottom; + info.Shutter.VsyncToNextVsync = ( 1.0f / 60.0f ); + info.Shutter.VsyncToFirstScanline = 0.000052f; + info.Shutter.FirstScanlineToLastScanline = 0.016580f; + info.Shutter.PixelSettleTime = 0.015f; + info.Shutter.PixelPersistence = ( 1.0f / 60.0f ); + break; + + case HmdType_CrystalCoveProto: + info.ProductName = "Oculus Rift Crystal Cove"; + info.ResolutionInPixels = Sizei ( 1920, 1080 ); + info.ScreenSizeInMeters = Sizef ( 0.12576f, 0.07074f ); + info.ScreenGapSizeInMeters = 0.0f; + info.CenterFromTopInMeters = info.ScreenSizeInMeters.h * 0.5f; + info.LensSeparationInMeters = 0.0635f; + info.Shutter.Type = HmdShutter_RollingRightToLeft; + info.Shutter.VsyncToNextVsync = ( 1.0f / 76.0f ); + info.Shutter.VsyncToFirstScanline = 0.0000273f; + info.Shutter.FirstScanlineToLastScanline = 0.0131033f; + info.Shutter.PixelSettleTime = 0.0f; + info.Shutter.PixelPersistence = 0.18f * info.Shutter.VsyncToNextVsync; + break; + + case HmdType_DK2: + info.ProductName = "Oculus Rift DK2"; + info.ResolutionInPixels = Sizei ( 1920, 1080 ); + info.ScreenSizeInMeters = Sizef ( 0.12576f, 0.07074f ); + info.ScreenGapSizeInMeters = 0.0f; + info.CenterFromTopInMeters = info.ScreenSizeInMeters.h * 0.5f; + info.LensSeparationInMeters = 0.0635f; + info.Shutter.Type = HmdShutter_RollingRightToLeft; + info.Shutter.VsyncToNextVsync = ( 1.0f / 76.0f ); + info.Shutter.VsyncToFirstScanline = 0.0000273f; + info.Shutter.FirstScanlineToLastScanline = 0.0131033f; + info.Shutter.PixelSettleTime = 0.0f; + info.Shutter.PixelPersistence = 0.18f * info.Shutter.VsyncToNextVsync; + break; + + default: + break; + } + + return info; +} + + + +// profile may be NULL, in which case it uses the hard-coded defaults. +HmdRenderInfo GenerateHmdRenderInfoFromHmdInfo ( HMDInfo const &hmdInfo, + Profile const *profile /*=NULL*/, + DistortionEqnType distortionType /*= Distortion_CatmullRom10*/, + EyeCupType eyeCupOverride /*= EyeCup_LAST*/ ) +{ + HmdRenderInfo renderInfo; + + renderInfo.HmdType = hmdInfo.HmdType; + renderInfo.ResolutionInPixels = hmdInfo.ResolutionInPixels; + renderInfo.ScreenSizeInMeters = hmdInfo.ScreenSizeInMeters; + renderInfo.CenterFromTopInMeters = hmdInfo.CenterFromTopInMeters; + renderInfo.ScreenGapSizeInMeters = hmdInfo.ScreenGapSizeInMeters; + renderInfo.LensSeparationInMeters = hmdInfo.LensSeparationInMeters; + + OVR_ASSERT ( sizeof(renderInfo.Shutter) == sizeof(hmdInfo.Shutter) ); // Try to keep the files in sync! + renderInfo.Shutter.Type = hmdInfo.Shutter.Type; + renderInfo.Shutter.VsyncToNextVsync = hmdInfo.Shutter.VsyncToNextVsync; + renderInfo.Shutter.VsyncToFirstScanline = hmdInfo.Shutter.VsyncToFirstScanline; + renderInfo.Shutter.FirstScanlineToLastScanline = hmdInfo.Shutter.FirstScanlineToLastScanline; + renderInfo.Shutter.PixelSettleTime = hmdInfo.Shutter.PixelSettleTime; + renderInfo.Shutter.PixelPersistence = hmdInfo.Shutter.PixelPersistence; + + renderInfo.LensDiameterInMeters = 0.035f; + renderInfo.LensSurfaceToMidplateInMeters = 0.025f; + renderInfo.EyeCups = EyeCup_DK1A; + +#if 0 // Device settings are out of date - don't use them. + if (Contents & Contents_Distortion) + { + memcpy(renderInfo.DistortionK, DistortionK, sizeof(float)*4); + renderInfo.DistortionEqn = Distortion_RecipPoly4; + } +#endif + + // Defaults in case of no user profile. + renderInfo.EyeLeft.NoseToPupilInMeters = 0.032f; + renderInfo.EyeLeft.ReliefInMeters = 0.012f; + + // 10mm eye-relief laser numbers for DK1 lenses. + // These are a decent seed for finding eye-relief and IPD. + // These are NOT used for rendering! + // Rendering distortions are now in GenerateLensConfigFromEyeRelief() + // So, if you're hacking in new distortions, don't do it here! + renderInfo.EyeLeft.Distortion.SetToIdentity(); + renderInfo.EyeLeft.Distortion.MetersPerTanAngleAtCenter = 0.0449f; + renderInfo.EyeLeft.Distortion.Eqn = Distortion_RecipPoly4; + renderInfo.EyeLeft.Distortion.K[0] = 1.0f; + renderInfo.EyeLeft.Distortion.K[1] = -0.494165344f; + renderInfo.EyeLeft.Distortion.K[2] = 0.587046423f; + renderInfo.EyeLeft.Distortion.K[3] = -0.841887126f; + renderInfo.EyeLeft.Distortion.MaxR = 1.0f; + + renderInfo.EyeLeft.Distortion.ChromaticAberration[0] = -0.006f; + renderInfo.EyeLeft.Distortion.ChromaticAberration[1] = 0.0f; + renderInfo.EyeLeft.Distortion.ChromaticAberration[2] = 0.014f; + renderInfo.EyeLeft.Distortion.ChromaticAberration[3] = 0.0f; + + renderInfo.EyeRight = renderInfo.EyeLeft; + + + // Obtain data from profile. + if ( profile != NULL ) + { + char eyecup[16]; + if (profile->GetValue(OVR_KEY_EYE_CUP, eyecup, 16)) + SetEyeCup(&renderInfo, eyecup); + } + + switch ( hmdInfo.HmdType ) + { + case HmdType_None: + case HmdType_DKProto: + case HmdType_DK1: + // Slight hack to improve usability. + // If you have a DKHD-style lens profile enabled, + // but you plug in DK1 and forget to change the profile, + // obviously you don't want those lens numbers. + if ( ( renderInfo.EyeCups != EyeCup_DK1A ) && + ( renderInfo.EyeCups != EyeCup_DK1B ) && + ( renderInfo.EyeCups != EyeCup_DK1C ) ) + { + renderInfo.EyeCups = EyeCup_DK1A; + } + break; + + case HmdType_DKHD2Proto: + renderInfo.EyeCups = EyeCup_DKHD2A; + break; + case HmdType_CrystalCoveProto: + renderInfo.EyeCups = EyeCup_PinkA; + break; + case HmdType_DK2: + renderInfo.EyeCups = EyeCup_DK2A; + break; + default: + break; + } + + if ( eyeCupOverride != EyeCup_LAST ) + { + renderInfo.EyeCups = eyeCupOverride; + } + + switch ( renderInfo.EyeCups ) + { + case EyeCup_DK1A: + case EyeCup_DK1B: + case EyeCup_DK1C: + renderInfo.LensDiameterInMeters = 0.035f; + renderInfo.LensSurfaceToMidplateInMeters = 0.02357f; + // Not strictly lens-specific, but still wise to set a reasonable default for relief. + renderInfo.EyeLeft.ReliefInMeters = 0.010f; + renderInfo.EyeRight.ReliefInMeters = 0.010f; + break; + case EyeCup_DKHD2A: + renderInfo.LensDiameterInMeters = 0.035f; + renderInfo.LensSurfaceToMidplateInMeters = 0.02357f; + // Not strictly lens-specific, but still wise to set a reasonable default for relief. + renderInfo.EyeLeft.ReliefInMeters = 0.010f; + renderInfo.EyeRight.ReliefInMeters = 0.010f; + break; + case EyeCup_PinkA: + case EyeCup_DK2A: + renderInfo.LensDiameterInMeters = 0.04f; // approximate + renderInfo.LensSurfaceToMidplateInMeters = 0.01965f; + // Not strictly lens-specific, but still wise to set a reasonable default for relief. + renderInfo.EyeLeft.ReliefInMeters = 0.012f; + renderInfo.EyeRight.ReliefInMeters = 0.012f; + break; + default: OVR_ASSERT ( false ); break; + } + + if ( profile != NULL ) + { + // Set the customized user eye position + // TBD: Maybe we should separate custom camera positioning from custom distortion rendering ?? + if (profile->GetBoolValue(OVR_KEY_CUSTOM_EYE_RENDER, true)) + { + float eye2nose[2]; + if (profile->GetFloatValues(OVR_KEY_EYE_TO_NOSE_DISTANCE, eye2nose, 2) == 2) + { // Load per-eye half-IPD + renderInfo.EyeLeft.NoseToPupilInMeters = eye2nose[0]; + renderInfo.EyeRight.NoseToPupilInMeters = eye2nose[1]; + } + else + { // Use a centered IPD instead + float ipd = profile->GetFloatValue(OVR_KEY_IPD, OVR_DEFAULT_IPD); + renderInfo.EyeLeft.NoseToPupilInMeters = 0.5f * ipd; + renderInfo.EyeRight.NoseToPupilInMeters = 0.5f * ipd; + } + + float eye2plate[2]; + if (profile->GetFloatValues(OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE, eye2plate, 2) == 2) + { // Subtract the eye-cup height from the plate distance to get the eye-to-lens distance + // This measurement should be the the distance at maximum dial setting + // We still need to adjust with the dial offset + renderInfo.EyeLeft.ReliefInMeters = eye2plate[0] - renderInfo.LensSurfaceToMidplateInMeters; + renderInfo.EyeRight.ReliefInMeters = eye2plate[1] - renderInfo.LensSurfaceToMidplateInMeters; + + // Adjust the eye relief with the dial setting (from the assumed max eye relief) + int dial = profile->GetIntValue(OVR_KEY_EYE_RELIEF_DIAL, -1); + if (dial >= 0) + { + renderInfo.EyeLeft.ReliefInMeters -= ((10 - dial) * 0.001f); + renderInfo.EyeRight.ReliefInMeters -= ((10 - dial) * 0.001f); + } + } + else + { + // Set the eye relief with the user configured dial setting + int dial = profile->GetIntValue(OVR_KEY_EYE_RELIEF_DIAL, -1); + if (dial >= 0) + { // Assume a default of 7 to 17 mm eye relief based on the dial. This corresponds + // to the sampled and tuned distortion range on the DK1. + renderInfo.EyeLeft.ReliefInMeters = 0.007f + (dial * 0.001f); + renderInfo.EyeRight.ReliefInMeters = 0.007f + (dial * 0.001f); + } + } + } + } + + // Now we know where the eyes are relative to the lenses, we can compute a distortion for each. + // TODO: incorporate lateral offset in distortion generation. + // TODO: we used a distortion to calculate eye-relief, and now we're making a distortion from that eye-relief. Close the loop! + + for ( int eyeNum = 0; eyeNum < 2; eyeNum++ ) + { + HmdRenderInfo::EyeConfig *pHmdEyeConfig = ( eyeNum == 0 ) ? &(renderInfo.EyeLeft) : &(renderInfo.EyeRight); + + float eye_relief = pHmdEyeConfig->ReliefInMeters; + LensConfig distortionConfig = GenerateLensConfigFromEyeRelief ( eye_relief, renderInfo, distortionType ); + pHmdEyeConfig->Distortion = distortionConfig; + } + + return renderInfo; +} + + +LensConfig GenerateLensConfigFromEyeRelief ( float eyeReliefInMeters, HmdRenderInfo const &hmd, DistortionEqnType distortionType /*= Distortion_CatmullRom10*/ ) +{ + struct DistortionDescriptor + { + float EyeRelief; + // The three places we're going to sample & lerp the curve at. + // One sample is always at 0.0, and the distortion scale should be 1.0 or else! + // Only use for poly4 numbers - CR has an implicit scale. + float SampleRadius[3]; + // Where the distortion has actually been measured/calibrated out to. + // Don't try to hallucinate data out beyond here. + float MaxRadius; + // The config itself. + LensConfig Config; + }; + + DistortionDescriptor distortions[10]; + for ( unsigned int i = 0; i < sizeof(distortions)/sizeof(distortions[0]); i++ ) + { + distortions[i].Config.SetToIdentity(); + distortions[i].EyeRelief = 0.0f; + distortions[i].MaxRadius = 1.0f; + } + int numDistortions = 0; + int defaultDistortion = 0; // index of the default distortion curve to use if zero eye relief supplied + + if ( ( hmd.EyeCups == EyeCup_DK1A ) || + ( hmd.EyeCups == EyeCup_DK1B ) || + ( hmd.EyeCups == EyeCup_DK1C ) ) + { + + numDistortions = 0; + + // Tuned at minimum dial setting - extended to r^2 == 1.8 + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; + distortions[numDistortions].EyeRelief = 0.012760465f - 0.005f; + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f; + distortions[numDistortions].Config.K[0] = 1.0000f; + distortions[numDistortions].Config.K[1] = 1.06505f; + distortions[numDistortions].Config.K[2] = 1.14725f; + distortions[numDistortions].Config.K[3] = 1.2705f; + distortions[numDistortions].Config.K[4] = 1.48f; + distortions[numDistortions].Config.K[5] = 1.87f; + distortions[numDistortions].Config.K[6] = 2.534f; + distortions[numDistortions].Config.K[7] = 3.6f; + distortions[numDistortions].Config.K[8] = 5.1f; + distortions[numDistortions].Config.K[9] = 7.4f; + distortions[numDistortions].Config.K[10] = 11.0f; + distortions[numDistortions].SampleRadius[0] = 0.222717149f; + distortions[numDistortions].SampleRadius[1] = 0.512249443f; + distortions[numDistortions].SampleRadius[2] = 0.712694878f; + distortions[numDistortions].MaxRadius = sqrt(1.8f); + defaultDistortion = numDistortions; // this is the default + numDistortions++; + + // Tuned at middle dial setting + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; + distortions[numDistortions].EyeRelief = 0.012760465f; // my average eye-relief + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f; + distortions[numDistortions].Config.K[0] = 1.0f; + distortions[numDistortions].Config.K[1] = 1.032407264f; + distortions[numDistortions].Config.K[2] = 1.07160462f; + distortions[numDistortions].Config.K[3] = 1.11998388f; + distortions[numDistortions].Config.K[4] = 1.1808606f; + distortions[numDistortions].Config.K[5] = 1.2590494f; + distortions[numDistortions].Config.K[6] = 1.361915f; + distortions[numDistortions].Config.K[7] = 1.5014339f; + distortions[numDistortions].Config.K[8] = 1.6986004f; + distortions[numDistortions].Config.K[9] = 1.9940577f; + distortions[numDistortions].Config.K[10] = 2.4783147f; + distortions[numDistortions].SampleRadius[0] = 0.222717149f; + distortions[numDistortions].SampleRadius[1] = 0.512249443f; + distortions[numDistortions].SampleRadius[2] = 0.712694878f; + distortions[numDistortions].MaxRadius = 1.0f; + numDistortions++; + + // Tuned at maximum dial setting + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; + distortions[numDistortions].EyeRelief = 0.012760465f + 0.005f; + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f; + distortions[numDistortions].Config.K[0] = 1.0102f; + distortions[numDistortions].Config.K[1] = 1.0371f; + distortions[numDistortions].Config.K[2] = 1.0831f; + distortions[numDistortions].Config.K[3] = 1.1353f; + distortions[numDistortions].Config.K[4] = 1.2f; + distortions[numDistortions].Config.K[5] = 1.2851f; + distortions[numDistortions].Config.K[6] = 1.3979f; + distortions[numDistortions].Config.K[7] = 1.56f; + distortions[numDistortions].Config.K[8] = 1.8f; + distortions[numDistortions].Config.K[9] = 2.25f; + distortions[numDistortions].Config.K[10] = 3.0f; + distortions[numDistortions].SampleRadius[0] = 0.222717149f; + distortions[numDistortions].SampleRadius[1] = 0.512249443f; + distortions[numDistortions].SampleRadius[2] = 0.712694878f; + distortions[numDistortions].MaxRadius = 1.0f; + numDistortions++; + + // Chromatic aberration doesn't seem to change with eye relief. + for ( int i = 0; i < numDistortions; i++ ) + { + distortions[i].Config.ChromaticAberration[0] = -0.006f; + distortions[i].Config.ChromaticAberration[1] = 0.0f; + distortions[i].Config.ChromaticAberration[2] = 0.014f; + distortions[i].Config.ChromaticAberration[3] = 0.0f; + } + } + else if ( hmd.EyeCups == EyeCup_DKHD2A ) + { + // Tuned DKHD2 lens + numDistortions = 0; + + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; + distortions[numDistortions].EyeRelief = 0.010f; + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f; + distortions[numDistortions].Config.K[0] = 1.0f; + distortions[numDistortions].Config.K[1] = 1.0425f; + distortions[numDistortions].Config.K[2] = 1.0826f; + distortions[numDistortions].Config.K[3] = 1.130f; + distortions[numDistortions].Config.K[4] = 1.185f; + distortions[numDistortions].Config.K[5] = 1.250f; + distortions[numDistortions].Config.K[6] = 1.338f; + distortions[numDistortions].Config.K[7] = 1.455f; + distortions[numDistortions].Config.K[8] = 1.620f; + distortions[numDistortions].Config.K[9] = 1.840f; + distortions[numDistortions].Config.K[10] = 2.200f; + distortions[numDistortions].SampleRadius[0] = 0.222717149f; + distortions[numDistortions].SampleRadius[1] = 0.512249443f; + distortions[numDistortions].SampleRadius[2] = 0.712694878f; + distortions[numDistortions].MaxRadius = 1.0f; + + distortions[numDistortions].SampleRadius[0] = 0.405405405f; + distortions[numDistortions].SampleRadius[1] = 0.675675676f; + distortions[numDistortions].SampleRadius[2] = 0.945945946f; + defaultDistortion = numDistortions; // this is the default + numDistortions++; + + distortions[numDistortions] = distortions[0]; + distortions[numDistortions].EyeRelief = 0.020f; + numDistortions++; + + // Chromatic aberration doesn't seem to change with eye relief. + for ( int i = 0; i < numDistortions; i++ ) + { + distortions[i].Config.ChromaticAberration[0] = -0.006f; + distortions[i].Config.ChromaticAberration[1] = 0.0f; + distortions[i].Config.ChromaticAberration[2] = 0.014f; + distortions[i].Config.ChromaticAberration[3] = 0.0f; + } + } + else if ( hmd.EyeCups == EyeCup_PinkA || hmd.EyeCups == EyeCup_DK2A ) + { + // Tuned Crystal Cove & DK2 Lens (CES & GDC) + numDistortions = 0; + + distortions[numDistortions].EyeRelief = 0.010f; + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.036f; + + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; + distortions[numDistortions].Config.K[0] = 1.003f; + distortions[numDistortions].Config.K[1] = 1.02f; + distortions[numDistortions].Config.K[2] = 1.042f; + distortions[numDistortions].Config.K[3] = 1.066f; + distortions[numDistortions].Config.K[4] = 1.094f; //1.0945f; + distortions[numDistortions].Config.K[5] = 1.126f; //1.127f; + distortions[numDistortions].Config.K[6] = 1.162f; //1.167f; + distortions[numDistortions].Config.K[7] = 1.203f; //1.218f; + distortions[numDistortions].Config.K[8] = 1.25f; //1.283f; + distortions[numDistortions].Config.K[9] = 1.31f; //1.37f; + distortions[numDistortions].Config.K[10] = 1.38f; //1.48f; + distortions[numDistortions].MaxRadius = 1.0f; + + + distortions[numDistortions].SampleRadius[0] = 0.405405405f; + distortions[numDistortions].SampleRadius[1] = 0.675675676f; + distortions[numDistortions].SampleRadius[2] = 0.945945946f; + defaultDistortion = numDistortions; // this is the default + numDistortions++; + + distortions[numDistortions] = distortions[0]; + distortions[numDistortions].EyeRelief = 0.020f; + numDistortions++; + + // Chromatic aberration doesn't seem to change with eye relief. + for ( int i = 0; i < numDistortions; i++ ) + { + distortions[i].Config.ChromaticAberration[0] = -0.015f; + distortions[i].Config.ChromaticAberration[1] = -0.02f; + distortions[i].Config.ChromaticAberration[2] = 0.025f; + distortions[i].Config.ChromaticAberration[3] = 0.02f; + } + } + else + { + // Unknown lens. + // Use DK1 black lens settings, just so we can continue to run with something. + distortions[0].EyeRelief = 0.005f; + distortions[0].Config.MetersPerTanAngleAtCenter = 0.043875f; + distortions[0].Config.Eqn = Distortion_RecipPoly4; + distortions[0].Config.K[0] = 1.0f; + distortions[0].Config.K[1] = -0.3999f; + distortions[0].Config.K[2] = 0.2408f; + distortions[0].Config.K[3] = -0.4589f; + distortions[0].SampleRadius[0] = 0.2f; + distortions[0].SampleRadius[1] = 0.4f; + distortions[0].SampleRadius[2] = 0.6f; + + distortions[1] = distortions[0]; + distortions[1].EyeRelief = 0.010f; + numDistortions = 2; + + // Chromatic aberration doesn't seem to change with eye relief. + for ( int i = 0; i < numDistortions; i++ ) + { + // These are placeholder, they have not been tuned! + distortions[i].Config.ChromaticAberration[0] = 0.0f; + distortions[i].Config.ChromaticAberration[1] = 0.0f; + distortions[i].Config.ChromaticAberration[2] = 0.0f; + distortions[i].Config.ChromaticAberration[3] = 0.0f; + } + } + + OVR_ASSERT ( numDistortions < (sizeof(distortions)/sizeof(distortions[0])) ); + + + 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++ ) + { + OVR_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 ) + { +#if 0 + // Outside the range, so extrapolate rather than interpolate. + if ( distortions[0].EyeRelief > eyeReliefInMeters ) + { + pLower = &(distortions[0]); + pUpper = &(distortions[1]); + } + else + { + OVR_ASSERT ( distortions[numDistortions-1].EyeRelief <= eyeReliefInMeters ); + pLower = &(distortions[numDistortions-2]); + pUpper = &(distortions[numDistortions-1]); + } + lerpVal = ( eyeReliefInMeters - pLower->EyeRelief ) / ( pUpper->EyeRelief - pLower->EyeRelief ); +#else + // 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 + { + OVR_ASSERT ( distortions[numDistortions-1].EyeRelief <= eyeReliefInMeters ); + pLower = &(distortions[numDistortions-1]); + pUpper = &(distortions[numDistortions-1]); + } + lerpVal = 0.0f; +#endif + } + float invLerpVal = 1.0f - lerpVal; + + pLower->Config.MaxR = pLower->MaxRadius; + pUpper->Config.MaxR = pUpper->MaxRadius; + + LensConfig result; + // Where is the edge of the lens - no point modelling further than this. + float maxValidRadius = invLerpVal * pLower->MaxRadius + lerpVal * pUpper->MaxRadius; + result.MaxR = maxValidRadius; + + switch ( distortionType ) + { + case Distortion_Poly4: + // Deprecated + OVR_ASSERT ( false ); + break; + case Distortion_RecipPoly4:{ + // Lerp control points and fit an equation to them. + float fitX[4]; + float fitY[4]; + fitX[0] = 0.0f; + fitY[0] = 1.0f; + for ( int ctrlPt = 1; ctrlPt < 4; ctrlPt ++ ) + { + float radiusLerp = invLerpVal * pLower->SampleRadius[ctrlPt-1] + lerpVal * pUpper->SampleRadius[ctrlPt-1]; + float radiusLerpSq = radiusLerp * radiusLerp; + float fitYLower = pLower->Config.DistortionFnScaleRadiusSquared ( radiusLerpSq ); + float fitYUpper = pUpper->Config.DistortionFnScaleRadiusSquared ( radiusLerpSq ); + fitX[ctrlPt] = radiusLerpSq; + fitY[ctrlPt] = 1.0f / ( invLerpVal * fitYLower + lerpVal * fitYUpper ); + } + + result.Eqn = Distortion_RecipPoly4; + bool bSuccess = FitCubicPolynomial ( result.K, fitX, fitY ); + OVR_ASSERT ( bSuccess ); + OVR_UNUSED ( bSuccess ); + + // Set up the fast inverse. + float maxRDist = result.DistortionFn ( maxValidRadius ); + result.MaxInvR = maxRDist; + result.SetUpInverseApprox(); + + }break; + + case Distortion_CatmullRom10:{ + + // Evenly sample & lerp points on the curve. + const 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++ ) + { + float radiusSq = ( (float)ctrlPt / (float)(NumSegments-1) ) * maxValidRadius * maxValidRadius; + float fitYLower = pLower->Config.DistortionFnScaleRadiusSquared ( radiusSq ); + float fitYUpper = pUpper->Config.DistortionFnScaleRadiusSquared ( radiusSq ); + float fitLerp = invLerpVal * fitYLower + lerpVal * fitYUpper; + result.K[ctrlPt] = fitLerp; + } + + result.Eqn = Distortion_CatmullRom10; + + for ( int ctrlPt = 1; ctrlPt < NumSegments; ctrlPt++ ) + { + float radiusSq = ( (float)ctrlPt / (float)(NumSegments-1) ) * maxValidRadius * maxValidRadius; + float val = result.DistortionFnScaleRadiusSquared ( radiusSq ); + OVR_ASSERT ( Alg::Abs ( val - result.K[ctrlPt] ) < 0.0001f ); + OVR_UNUSED1(val); // For release build. + } + + // Set up the fast inverse. + float maxRDist = result.DistortionFn ( maxValidRadius ); + result.MaxInvR = maxRDist; + result.SetUpInverseApprox(); + + }break; + + default: OVR_ASSERT ( false ); break; + } + + + // 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; + /* + // Commented out - Causes ASSERT with no HMD plugged in +#ifdef OVR_BUILD_DEBUG + if ( distortionType == Distortion_CatmullRom10 ) + { + TestSaveLoadLensConfig ( result ); + } +#endif + */ + return result; +} + + + + + +DistortionRenderDesc CalculateDistortionRenderDesc ( StereoEye eyeType, HmdRenderInfo const &hmd, + const LensConfig *pLensOverride /*= NULL */ ) +{ + // 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. + + const HmdRenderInfo::EyeConfig &hmdEyeConfig = ( eyeType == StereoEye_Left ) ? hmd.EyeLeft : hmd.EyeRight; + + DistortionRenderDesc localDistortion; + localDistortion.Lens = hmdEyeConfig.Distortion; + + if ( pLensOverride != NULL ) + { + localDistortion.Lens = *pLensOverride; + } + + Sizef pixelsPerMeter(hmd.ResolutionInPixels.w / ( hmd.ScreenSizeInMeters.w - hmd.ScreenGapSizeInMeters ), + hmd.ResolutionInPixels.h / hmd.ScreenSizeInMeters.h); + + localDistortion.PixelsPerTanAngleAtCenter = (pixelsPerMeter * localDistortion.Lens.MetersPerTanAngleAtCenter).ToVector(); + // Same thing, scaled to [-1,1] for each eye, rather than pixels. + + localDistortion.TanEyeAngleScale = Vector2f(0.25f, 0.5f).EntrywiseMultiply( + (hmd.ScreenSizeInMeters / localDistortion.Lens.MetersPerTanAngleAtCenter).ToVector()); + + // <--------------left eye------------------><-ScreenGapSizeInMeters-><--------------right eye-----------------> + // <------------------------------------------ScreenSizeInMeters.Width-----------------------------------------> + // <----------------LensSeparationInMeters---------------> + // <--centerFromLeftInMeters-> + // ^ + // Center of lens + + // Find the lens centers in scale of [-1,+1] (NDC) in left eye. + float visibleWidthOfOneEye = 0.5f * ( hmd.ScreenSizeInMeters.w - hmd.ScreenGapSizeInMeters ); + float centerFromLeftInMeters = ( hmd.ScreenSizeInMeters.w - hmd.LensSeparationInMeters ) * 0.5f; + localDistortion.LensCenter.x = ( centerFromLeftInMeters / visibleWidthOfOneEye ) * 2.0f - 1.0f; + localDistortion.LensCenter.y = ( hmd.CenterFromTopInMeters / hmd.ScreenSizeInMeters.h ) * 2.0f - 1.0f; + if ( eyeType == StereoEye_Right ) + { + localDistortion.LensCenter.x = -localDistortion.LensCenter.x; + } + + return localDistortion; +} + +FovPort CalculateFovFromEyePosition ( float eyeReliefInMeters, + float offsetToRightInMeters, + float offsetDownwardsInMeters, + float lensDiameterInMeters, + float extraEyeRotationInRadians /*= 0.0f*/ ) +{ + // 2D view of things: + // |-| <--- offsetToRightInMeters (in this case, it is negative) + // |=======C=======| <--- lens surface (C=center) + // \ | _/ + // \ R _/ + // \ | _/ + // \ | _/ + // \|/ + // O <--- center of pupil + + // (technically the lens is round rather than square, so it's not correct to + // separate vertical and horizontal like this, but it's close enough) + float halfLensDiameter = lensDiameterInMeters * 0.5f; + FovPort fovPort; + fovPort.UpTan = ( halfLensDiameter + offsetDownwardsInMeters ) / eyeReliefInMeters; + fovPort.DownTan = ( halfLensDiameter - offsetDownwardsInMeters ) / eyeReliefInMeters; + fovPort.LeftTan = ( halfLensDiameter + offsetToRightInMeters ) / eyeReliefInMeters; + fovPort.RightTan = ( halfLensDiameter - offsetToRightInMeters ) / eyeReliefInMeters; + + if ( extraEyeRotationInRadians > 0.0f ) + { + // That's the basic looking-straight-ahead eye position relative to the lens. + // But if you look left, the pupil moves left as the eyeball rotates, which + // means you can see more to the right than this geometry suggests. + // So add in the bounds for the extra movement of the pupil. + + // Beyond 30 degrees does not increase FOV because the pupil starts moving backwards more than sideways. + extraEyeRotationInRadians = Alg::Min ( DegreeToRad ( 30.0f ), Alg::Max ( 0.0f, extraEyeRotationInRadians ) ); + + // The rotation of the eye is a bit more complex than a simple circle. The center of rotation + // at 13.5mm from cornea is slightly further back than the actual center of the eye. + // Additionally the rotation contains a small lateral component as the muscles pull the eye + const float eyeballCenterToPupil = 0.0135f; // center of eye rotation + const float eyeballLateralPull = 0.001f * (extraEyeRotationInRadians / DegreeToRad ( 30.0f)); // lateral motion as linear function + float extraTranslation = eyeballCenterToPupil * sinf ( extraEyeRotationInRadians ) + eyeballLateralPull; + float extraRelief = eyeballCenterToPupil * ( 1.0f - cosf ( extraEyeRotationInRadians ) ); + + fovPort.UpTan = Alg::Max ( fovPort.UpTan , ( halfLensDiameter + offsetDownwardsInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) ); + fovPort.DownTan = Alg::Max ( fovPort.DownTan , ( halfLensDiameter - offsetDownwardsInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) ); + fovPort.LeftTan = Alg::Max ( fovPort.LeftTan , ( halfLensDiameter + offsetToRightInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) ); + fovPort.RightTan = Alg::Max ( fovPort.RightTan, ( halfLensDiameter - offsetToRightInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) ); + } + + return fovPort; +} + + + +FovPort CalculateFovFromHmdInfo ( StereoEye eyeType, + DistortionRenderDesc const &distortion, + HmdRenderInfo const &hmd, + float extraEyeRotationInRadians /*= 0.0f*/ ) +{ + FovPort fovPort; + float eyeReliefInMeters; + float offsetToRightInMeters; + if ( eyeType == StereoEye_Right ) + { + eyeReliefInMeters = hmd.EyeRight.ReliefInMeters; + offsetToRightInMeters = hmd.EyeRight.NoseToPupilInMeters - 0.5f * hmd.LensSeparationInMeters; + } + else + { + eyeReliefInMeters = hmd.EyeLeft.ReliefInMeters; + offsetToRightInMeters = -(hmd.EyeLeft.NoseToPupilInMeters - 0.5f * hmd.LensSeparationInMeters); + } + + // Limit the eye-relief to 6 mm for FOV calculations since this just tends to spread off-screen + // and get clamped anyways on DK1 (but in Unity it continues to spreads and causes + // unnecessarily large render targets) + eyeReliefInMeters = Alg::Max(eyeReliefInMeters, 0.006f); + + // Central view. + fovPort = CalculateFovFromEyePosition ( eyeReliefInMeters, + offsetToRightInMeters, + 0.0f, + hmd.LensDiameterInMeters, + extraEyeRotationInRadians ); + + // clamp to the screen + fovPort = ClampToPhysicalScreenFov ( eyeType, distortion, fovPort ); + + return fovPort; +} + + + +FovPort GetPhysicalScreenFov ( StereoEye eyeType, DistortionRenderDesc const &distortion ) +{ + OVR_UNUSED1 ( eyeType ); + + FovPort resultFovPort; + + // Figure out the boundaries of the screen. We take the middle pixel of the screen, + // move to each of the four screen edges, and transform those back into TanAngle space. + Vector2f dmiddle = distortion.LensCenter; + + // The gotcha is that for some distortion functions, the map will "wrap around" + // for screen pixels that are not actually visible to the user (especially on DK1, + // which has a lot of invisible pixels), and map to pixels that are close to the middle. + // This means the edges of the screen will actually be + // "closer" than the visible bounds, so we'll clip too aggressively. + + // Solution - step gradually towards the boundary, noting the maximum distance. + struct FunctionHider + { + static FovPort FindRange ( Vector2f from, Vector2f to, int numSteps, + DistortionRenderDesc const &distortion ) + { + FovPort result; + result.UpTan = 0.0f; + result.DownTan = 0.0f; + result.LeftTan = 0.0f; + result.RightTan = 0.0f; + + float stepScale = 1.0f / ( numSteps - 1 ); + for ( int step = 0; step < numSteps; step++ ) + { + float lerpFactor = stepScale * (float)step; + Vector2f sample = from + (to - from) * lerpFactor; + Vector2f tanEyeAngle = TransformScreenNDCToTanFovSpace ( distortion, sample ); + + result.LeftTan = Alg::Max ( result.LeftTan, -tanEyeAngle.x ); + result.RightTan = Alg::Max ( result.RightTan, tanEyeAngle.x ); + result.UpTan = Alg::Max ( result.UpTan, -tanEyeAngle.y ); + result.DownTan = Alg::Max ( result.DownTan, tanEyeAngle.y ); + } + return result; + } + }; + + FovPort leftFovPort = FunctionHider::FindRange( dmiddle, Vector2f( -1.0f, dmiddle.y ), 10, distortion ); + FovPort rightFovPort = FunctionHider::FindRange( dmiddle, Vector2f( 1.0f, dmiddle.y ), 10, distortion ); + FovPort upFovPort = FunctionHider::FindRange( dmiddle, Vector2f( dmiddle.x, -1.0f ), 10, distortion ); + FovPort downFovPort = FunctionHider::FindRange( dmiddle, Vector2f( dmiddle.x, 1.0f ), 10, distortion ); + + resultFovPort.LeftTan = leftFovPort.LeftTan; + resultFovPort.RightTan = rightFovPort.RightTan; + resultFovPort.UpTan = upFovPort.UpTan; + resultFovPort.DownTan = downFovPort.DownTan; + + return resultFovPort; +} + +FovPort ClampToPhysicalScreenFov( StereoEye eyeType, DistortionRenderDesc const &distortion, + FovPort inputFovPort ) +{ + FovPort resultFovPort; + FovPort phsyicalFovPort = GetPhysicalScreenFov ( eyeType, distortion ); + resultFovPort.LeftTan = Alg::Min ( inputFovPort.LeftTan, phsyicalFovPort.LeftTan ); + resultFovPort.RightTan = Alg::Min ( inputFovPort.RightTan, phsyicalFovPort.RightTan ); + resultFovPort.UpTan = Alg::Min ( inputFovPort.UpTan, phsyicalFovPort.UpTan ); + resultFovPort.DownTan = Alg::Min ( inputFovPort.DownTan, phsyicalFovPort.DownTan ); + + return resultFovPort; +} + +Sizei CalculateIdealPixelSize ( StereoEye eyeType, DistortionRenderDesc const &distortion, + FovPort tanHalfFov, float pixelsPerDisplayPixel ) +{ + OVR_UNUSED(eyeType); // might be useful in the future if we do overlapping fovs + + Sizei result; + // TODO: if the app passes in a FOV that doesn't cover the centre, use the distortion values for the nearest edge/corner to match pixel size. + result.w = (int)(0.5f + pixelsPerDisplayPixel * distortion.PixelsPerTanAngleAtCenter.x * ( tanHalfFov.LeftTan + tanHalfFov.RightTan ) ); + result.h = (int)(0.5f + pixelsPerDisplayPixel * distortion.PixelsPerTanAngleAtCenter.y * ( tanHalfFov.UpTan + tanHalfFov.DownTan ) ); + return result; +} + +Recti GetFramebufferViewport ( StereoEye eyeType, HmdRenderInfo const &hmd ) +{ + Recti result; + result.w = hmd.ResolutionInPixels.w/2; + result.h = hmd.ResolutionInPixels.h; + result.x = 0; + result.y = 0; + if ( eyeType == StereoEye_Right ) + { + result.x = (hmd.ResolutionInPixels.w+1)/2; // Round up, not down. + } + return result; +} + + +ScaleAndOffset2D CreateNDCScaleAndOffsetFromFov ( FovPort tanHalfFov ) +{ + float projXScale = 2.0f / ( tanHalfFov.LeftTan + tanHalfFov.RightTan ); + float projXOffset = ( tanHalfFov.LeftTan - tanHalfFov.RightTan ) * projXScale * 0.5f; + float projYScale = 2.0f / ( tanHalfFov.UpTan + tanHalfFov.DownTan ); + float projYOffset = ( tanHalfFov.UpTan - tanHalfFov.DownTan ) * projYScale * 0.5f; + + ScaleAndOffset2D result; + result.Scale = Vector2f(projXScale, projYScale); + result.Offset = Vector2f(projXOffset, projYOffset); + // Hey - why is that Y.Offset negated? + // It's because a projection matrix transforms from world coords with Y=up, + // whereas this is from NDC which is Y=down. + + return result; +} + + +ScaleAndOffset2D CreateUVScaleAndOffsetfromNDCScaleandOffset ( ScaleAndOffset2D scaleAndOffsetNDC, + Recti renderedViewport, + Sizei renderTargetSize ) +{ + // scaleAndOffsetNDC takes you to NDC space [-1,+1] within the given viewport on the rendertarget. + // We want a scale to instead go to actual UV coordinates you can sample with, + // which need [0,1] and ignore the viewport. + ScaleAndOffset2D result; + // Scale [-1,1] to [0,1] + result.Scale = scaleAndOffsetNDC.Scale * 0.5f; + result.Offset = scaleAndOffsetNDC.Offset * 0.5f + Vector2f(0.5f); + + // ...but we will have rendered to a subsection of the RT, so scale for that. + Vector2f scale( (float)renderedViewport.w / (float)renderTargetSize.w, + (float)renderedViewport.h / (float)renderTargetSize.h ); + Vector2f offset( (float)renderedViewport.x / (float)renderTargetSize.w, + (float)renderedViewport.y / (float)renderTargetSize.h ); + + result.Scale = result.Scale.EntrywiseMultiply(scale); + result.Offset = result.Offset.EntrywiseMultiply(scale) + offset; + return result; +} + + + +Matrix4f CreateProjection( bool rightHanded, FovPort tanHalfFov, + float zNear /*= 0.01f*/, float zFar /*= 10000.0f*/ ) +{ + // A projection matrix is very like a scaling from NDC, so we can start with that. + ScaleAndOffset2D scaleAndOffset = CreateNDCScaleAndOffsetFromFov ( tanHalfFov ); + + float handednessScale = 1.0f; + if ( rightHanded ) + { + handednessScale = -1.0f; + } + + Matrix4f projection; + // Produces X result, mapping clip edges to [-w,+w] + projection.M[0][0] = scaleAndOffset.Scale.x; + projection.M[0][1] = 0.0f; + projection.M[0][2] = handednessScale * scaleAndOffset.Offset.x; + projection.M[0][3] = 0.0f; + + // Produces Y result, mapping clip edges to [-w,+w] + // Hey - why is that YOffset negated? + // It's because a projection matrix transforms from world coords with Y=up, + // whereas this is derived from an NDC scaling, which is Y=down. + projection.M[1][0] = 0.0f; + projection.M[1][1] = scaleAndOffset.Scale.y; + projection.M[1][2] = handednessScale * -scaleAndOffset.Offset.y; + projection.M[1][3] = 0.0f; + + // Produces Z-buffer result - app needs to fill this in with whatever Z range it wants. + // We'll just use some defaults for now. + projection.M[2][0] = 0.0f; + projection.M[2][1] = 0.0f; + projection.M[2][2] = -handednessScale * zFar / (zNear - zFar); + projection.M[2][3] = (zFar * zNear) / (zNear - zFar); + + // Produces W result (= Z in) + projection.M[3][0] = 0.0f; + projection.M[3][1] = 0.0f; + projection.M[3][2] = handednessScale; + projection.M[3][3] = 0.0f; + + return projection; +} + + +Matrix4f CreateOrthoSubProjection ( bool rightHanded, StereoEye eyeType, + float tanHalfFovX, float tanHalfFovY, + float unitsX, float unitsY, + float distanceFromCamera, float interpupillaryDistance, + Matrix4f const &projection, + float zNear /*= 0.0f*/, float zFar /*= 0.0f*/ ) +{ + OVR_UNUSED1 ( rightHanded ); + + float orthoHorizontalOffset = interpupillaryDistance * 0.5f / distanceFromCamera; + switch ( eyeType ) + { + case StereoEye_Center: + orthoHorizontalOffset = 0.0f; + break; + case StereoEye_Left: + break; + case StereoEye_Right: + orthoHorizontalOffset = -orthoHorizontalOffset; + break; + default: OVR_ASSERT ( false ); break; + } + + // Current projection maps real-world vector (x,y,1) to the RT. + // We want to find the projection that maps the range [-FovPixels/2,FovPixels/2] to + // the physical [-orthoHalfFov,orthoHalfFov] + // Note moving the offset from M[0][2]+M[1][2] to M[0][3]+M[1][3] - this means + // we don't have to feed in Z=1 all the time. + // The horizontal offset math is a little hinky because the destination is + // actually [-orthoHalfFov+orthoHorizontalOffset,orthoHalfFov+orthoHorizontalOffset] + // So we need to first map [-FovPixels/2,FovPixels/2] to + // [-orthoHalfFov+orthoHorizontalOffset,orthoHalfFov+orthoHorizontalOffset]: + // x1 = x0 * orthoHalfFov/(FovPixels/2) + orthoHorizontalOffset; + // = x0 * 2*orthoHalfFov/FovPixels + orthoHorizontalOffset; + // But then we need the sam mapping as the existing projection matrix, i.e. + // x2 = x1 * Projection.M[0][0] + Projection.M[0][2]; + // = x0 * (2*orthoHalfFov/FovPixels + orthoHorizontalOffset) * Projection.M[0][0] + Projection.M[0][2]; + // = x0 * Projection.M[0][0]*2*orthoHalfFov/FovPixels + + // orthoHorizontalOffset*Projection.M[0][0] + Projection.M[0][2]; + // So in the new projection matrix we need to scale by Projection.M[0][0]*2*orthoHalfFov/FovPixels and + // offset by orthoHorizontalOffset*Projection.M[0][0] + Projection.M[0][2]. + + float orthoScaleX = 2.0f * tanHalfFovX / unitsX; + float orthoScaleY = 2.0f * tanHalfFovY / unitsY; + Matrix4f ortho; + ortho.M[0][0] = projection.M[0][0] * orthoScaleX; + ortho.M[0][1] = 0.0f; + ortho.M[0][2] = 0.0f; + ortho.M[0][3] = -projection.M[0][2] + ( orthoHorizontalOffset * projection.M[0][0] ); + + ortho.M[1][0] = 0.0f; + ortho.M[1][1] = -projection.M[1][1] * orthoScaleY; // Note sign flip (text rendering uses Y=down). + ortho.M[1][2] = 0.0f; + ortho.M[1][3] = -projection.M[1][2]; + + if ( fabsf ( zNear - zFar ) < 0.001f ) + { + ortho.M[2][0] = 0.0f; + ortho.M[2][1] = 0.0f; + ortho.M[2][2] = 0.0f; + ortho.M[2][3] = zFar; + } + else + { + ortho.M[2][0] = 0.0f; + ortho.M[2][1] = 0.0f; + ortho.M[2][2] = zFar / (zNear - zFar); + ortho.M[2][3] = (zFar * zNear) / (zNear - zFar); + } + + // No perspective correction for ortho. + ortho.M[3][0] = 0.0f; + ortho.M[3][1] = 0.0f; + ortho.M[3][2] = 0.0f; + ortho.M[3][3] = 1.0f; + + return ortho; +} + + +//----------------------------------------------------------------------------------- +// A set of "forward-mapping" functions, mapping from framebuffer space to real-world and/or texture space. + +// This mimics the first half of the distortion shader's function. +Vector2f TransformScreenNDCToTanFovSpace( DistortionRenderDesc const &distortion, + const Vector2f &framebufferNDC ) +{ + // Scale to TanHalfFov space, but still distorted. + Vector2f tanEyeAngleDistorted; + tanEyeAngleDistorted.x = ( framebufferNDC.x - distortion.LensCenter.x ) * distortion.TanEyeAngleScale.x; + tanEyeAngleDistorted.y = ( framebufferNDC.y - distortion.LensCenter.y ) * distortion.TanEyeAngleScale.y; + // Distort. + float radiusSquared = ( tanEyeAngleDistorted.x * tanEyeAngleDistorted.x ) + + ( tanEyeAngleDistorted.y * tanEyeAngleDistorted.y ); + float distortionScale = distortion.Lens.DistortionFnScaleRadiusSquared ( radiusSquared ); + Vector2f tanEyeAngle; + tanEyeAngle.x = tanEyeAngleDistorted.x * distortionScale; + tanEyeAngle.y = tanEyeAngleDistorted.y * distortionScale; + + return tanEyeAngle; +} + +// Same, with chromatic aberration correction. +void TransformScreenNDCToTanFovSpaceChroma ( Vector2f *resultR, Vector2f *resultG, Vector2f *resultB, + DistortionRenderDesc const &distortion, + const Vector2f &framebufferNDC ) +{ + // Scale to TanHalfFov space, but still distorted. + Vector2f tanEyeAngleDistorted; + tanEyeAngleDistorted.x = ( framebufferNDC.x - distortion.LensCenter.x ) * distortion.TanEyeAngleScale.x; + tanEyeAngleDistorted.y = ( framebufferNDC.y - distortion.LensCenter.y ) * distortion.TanEyeAngleScale.y; + // Distort. + float radiusSquared = ( tanEyeAngleDistorted.x * tanEyeAngleDistorted.x ) + + ( tanEyeAngleDistorted.y * tanEyeAngleDistorted.y ); + Vector3f distortionScales = distortion.Lens.DistortionFnScaleRadiusSquaredChroma ( radiusSquared ); + *resultR = tanEyeAngleDistorted * distortionScales.x; + *resultG = tanEyeAngleDistorted * distortionScales.y; + *resultB = tanEyeAngleDistorted * distortionScales.z; +} + +// This mimics the second half of the distortion shader's function. +Vector2f TransformTanFovSpaceToRendertargetTexUV( StereoEyeParams const &eyeParams, + Vector2f const &tanEyeAngle ) +{ + Vector2f textureUV; + textureUV.x = tanEyeAngle.x * eyeParams.EyeToSourceUV.Scale.x + eyeParams.EyeToSourceUV.Offset.x; + textureUV.y = tanEyeAngle.y * eyeParams.EyeToSourceUV.Scale.y + eyeParams.EyeToSourceUV.Offset.y; + return textureUV; +} + +Vector2f TransformTanFovSpaceToRendertargetNDC( StereoEyeParams const &eyeParams, + Vector2f const &tanEyeAngle ) +{ + Vector2f textureNDC; + textureNDC.x = tanEyeAngle.x * eyeParams.EyeToSourceNDC.Scale.x + eyeParams.EyeToSourceNDC.Offset.x; + textureNDC.y = tanEyeAngle.y * eyeParams.EyeToSourceNDC.Scale.y + eyeParams.EyeToSourceNDC.Offset.y; + return textureNDC; +} + +Vector2f TransformScreenPixelToScreenNDC( Recti const &distortionViewport, + Vector2f const &pixel ) +{ + // Move to [-1,1] NDC coords. + Vector2f framebufferNDC; + framebufferNDC.x = -1.0f + 2.0f * ( ( pixel.x - (float)distortionViewport.x ) / (float)distortionViewport.w ); + framebufferNDC.y = -1.0f + 2.0f * ( ( pixel.y - (float)distortionViewport.y ) / (float)distortionViewport.h ); + return framebufferNDC; +} + +Vector2f TransformScreenPixelToTanFovSpace( Recti const &distortionViewport, + DistortionRenderDesc const &distortion, + Vector2f const &pixel ) +{ + return TransformScreenNDCToTanFovSpace( distortion, + TransformScreenPixelToScreenNDC( distortionViewport, pixel ) ); +} + +Vector2f TransformScreenNDCToRendertargetTexUV( DistortionRenderDesc const &distortion, + StereoEyeParams const &eyeParams, + Vector2f const &pixel ) +{ + return TransformTanFovSpaceToRendertargetTexUV ( eyeParams, + TransformScreenNDCToTanFovSpace ( distortion, pixel ) ); +} + +Vector2f TransformScreenPixelToRendertargetTexUV( Recti const &distortionViewport, + DistortionRenderDesc const &distortion, + StereoEyeParams const &eyeParams, + Vector2f const &pixel ) +{ + return TransformTanFovSpaceToRendertargetTexUV ( eyeParams, + TransformScreenPixelToTanFovSpace ( distortionViewport, distortion, pixel ) ); +} + + +//----------------------------------------------------------------------------------- +// A set of "reverse-mapping" functions, mapping from real-world and/or texture space back to the framebuffer. + +Vector2f TransformTanFovSpaceToScreenNDC( DistortionRenderDesc const &distortion, + const Vector2f &tanEyeAngle, bool usePolyApprox /*= false*/ ) +{ + float tanEyeAngleRadius = tanEyeAngle.Length(); + float tanEyeAngleDistortedRadius = distortion.Lens.DistortionFnInverseApprox ( tanEyeAngleRadius ); + if ( !usePolyApprox ) + { + tanEyeAngleDistortedRadius = distortion.Lens.DistortionFnInverse ( tanEyeAngleRadius ); + } + Vector2f tanEyeAngleDistorted = tanEyeAngle; + if ( tanEyeAngleRadius > 0.0f ) + { + tanEyeAngleDistorted = tanEyeAngle * ( tanEyeAngleDistortedRadius / tanEyeAngleRadius ); + } + + Vector2f framebufferNDC; + framebufferNDC.x = ( tanEyeAngleDistorted.x / distortion.TanEyeAngleScale.x ) + distortion.LensCenter.x; + framebufferNDC.y = ( tanEyeAngleDistorted.y / distortion.TanEyeAngleScale.y ) + distortion.LensCenter.y; + + return framebufferNDC; +} + +Vector2f TransformRendertargetNDCToTanFovSpace( const ScaleAndOffset2D &eyeToSourceNDC, + const Vector2f &textureNDC ) +{ + Vector2f tanEyeAngle = (textureNDC - eyeToSourceNDC.Offset) / eyeToSourceNDC.Scale; + return tanEyeAngle; +} + + + +} //namespace OVR + +//Just want to make a copy disentangled from all these namespaces! +float ExtEvalCatmullRom10Spline ( float const *K, float scaledVal ) +{ + return(OVR::EvalCatmullRom10Spline ( K, scaledVal )); +} + + |