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