ovr_sdk
diff LibOVR/Src/OVR_Stereo.cpp @ 0:1b39a1b46319
initial 0.4.4
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Wed, 14 Jan 2015 06:51:16 +0200 |
parents | |
children |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/LibOVR/Src/OVR_Stereo.cpp Wed Jan 14 06:51:16 2015 +0200 1.3 @@ -0,0 +1,1874 @@ 1.4 +/************************************************************************************ 1.5 + 1.6 +Filename : OVR_Stereo.cpp 1.7 +Content : Stereo rendering functions 1.8 +Created : November 30, 2013 1.9 +Authors : Tom Fosyth 1.10 + 1.11 +Copyright : Copyright 2014 Oculus VR, LLC All Rights reserved. 1.12 + 1.13 +Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License"); 1.14 +you may not use the Oculus VR Rift SDK except in compliance with the License, 1.15 +which is provided at the time of installation or download, or which 1.16 +otherwise accompanies this software in either electronic or hard copy form. 1.17 + 1.18 +You may obtain a copy of the License at 1.19 + 1.20 +http://www.oculusvr.com/licenses/LICENSE-3.2 1.21 + 1.22 +Unless required by applicable law or agreed to in writing, the Oculus VR SDK 1.23 +distributed under the License is distributed on an "AS IS" BASIS, 1.24 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1.25 +See the License for the specific language governing permissions and 1.26 +limitations under the License. 1.27 + 1.28 +*************************************************************************************/ 1.29 + 1.30 +#include "OVR_Stereo.h" 1.31 +#include "OVR_Profile.h" 1.32 +#include "Kernel/OVR_Log.h" 1.33 +#include "Kernel/OVR_Alg.h" 1.34 + 1.35 +//To allow custom distortion to be introduced to CatMulSpline. 1.36 +float (*CustomDistortion)(float) = NULL; 1.37 +float (*CustomDistortionInv)(float) = NULL; 1.38 + 1.39 + 1.40 +namespace OVR { 1.41 + 1.42 + 1.43 +using namespace Alg; 1.44 + 1.45 +//----------------------------------------------------------------------------------- 1.46 + 1.47 +// Inputs are 4 points (pFitX[0],pFitY[0]) through (pFitX[3],pFitY[3]) 1.48 +// Result is four coefficients in pResults[0] through pResults[3] such that 1.49 +// y = pResult[0] + x * ( pResult[1] + x * ( pResult[2] + x * ( pResult[3] ) ) ); 1.50 +// passes through all four input points. 1.51 +// Return is true if it succeeded, false if it failed (because two control points 1.52 +// have the same pFitX value). 1.53 +bool FitCubicPolynomial ( float *pResult, const float *pFitX, const float *pFitY ) 1.54 +{ 1.55 + float d0 = ( ( pFitX[0]-pFitX[1] ) * ( pFitX[0]-pFitX[2] ) * ( pFitX[0]-pFitX[3] ) ); 1.56 + float d1 = ( ( pFitX[1]-pFitX[2] ) * ( pFitX[1]-pFitX[3] ) * ( pFitX[1]-pFitX[0] ) ); 1.57 + float d2 = ( ( pFitX[2]-pFitX[3] ) * ( pFitX[2]-pFitX[0] ) * ( pFitX[2]-pFitX[1] ) ); 1.58 + float d3 = ( ( pFitX[3]-pFitX[0] ) * ( pFitX[3]-pFitX[1] ) * ( pFitX[3]-pFitX[2] ) ); 1.59 + 1.60 + if ( ( d0 == 0.0f ) || ( d1 == 0.0f ) || ( d2 == 0.0f ) || ( d3 == 0.0f ) ) 1.61 + { 1.62 + return false; 1.63 + } 1.64 + 1.65 + float f0 = pFitY[0] / d0; 1.66 + float f1 = pFitY[1] / d1; 1.67 + float f2 = pFitY[2] / d2; 1.68 + float f3 = pFitY[3] / d3; 1.69 + 1.70 + pResult[0] = -( f0*pFitX[1]*pFitX[2]*pFitX[3] 1.71 + + f1*pFitX[0]*pFitX[2]*pFitX[3] 1.72 + + f2*pFitX[0]*pFitX[1]*pFitX[3] 1.73 + + f3*pFitX[0]*pFitX[1]*pFitX[2] ); 1.74 + pResult[1] = f0*(pFitX[1]*pFitX[2] + pFitX[2]*pFitX[3] + pFitX[3]*pFitX[1]) 1.75 + + f1*(pFitX[0]*pFitX[2] + pFitX[2]*pFitX[3] + pFitX[3]*pFitX[0]) 1.76 + + f2*(pFitX[0]*pFitX[1] + pFitX[1]*pFitX[3] + pFitX[3]*pFitX[0]) 1.77 + + f3*(pFitX[0]*pFitX[1] + pFitX[1]*pFitX[2] + pFitX[2]*pFitX[0]); 1.78 + pResult[2] = -( f0*(pFitX[1]+pFitX[2]+pFitX[3]) 1.79 + + f1*(pFitX[0]+pFitX[2]+pFitX[3]) 1.80 + + f2*(pFitX[0]+pFitX[1]+pFitX[3]) 1.81 + + f3*(pFitX[0]+pFitX[1]+pFitX[2]) ); 1.82 + pResult[3] = f0 + f1 + f2 + f3; 1.83 + 1.84 + return true; 1.85 +} 1.86 + 1.87 +#define TPH_SPLINE_STATISTICS 0 1.88 +#if TPH_SPLINE_STATISTICS 1.89 +static float max_scaledVal = 0; 1.90 +static float average_total_out_of_range = 0; 1.91 +static float average_out_of_range; 1.92 +static int num_total = 0; 1.93 +static int num_out_of_range = 0; 1.94 +static int num_out_of_range_over_1 = 0; 1.95 +static int num_out_of_range_over_2 = 0; 1.96 +static int num_out_of_range_over_3 = 0; 1.97 +static float percent_out_of_range; 1.98 +#endif 1.99 + 1.100 +float EvalCatmullRom10Spline ( float const *K, float scaledVal ) 1.101 +{ 1.102 + int const NumSegments = LensConfig::NumCoefficients; 1.103 + 1.104 + #if TPH_SPLINE_STATISTICS 1.105 + //Value should be in range of 0 to (NumSegments-1) (typically 10) if spline is valid. Right? 1.106 + if (scaledVal > (NumSegments-1)) 1.107 + { 1.108 + num_out_of_range++; 1.109 + average_total_out_of_range+=scaledVal; 1.110 + average_out_of_range = average_total_out_of_range / ((float) num_out_of_range); 1.111 + percent_out_of_range = 100.0f*(num_out_of_range)/num_total; 1.112 + } 1.113 + if (scaledVal > (NumSegments-1+1)) num_out_of_range_over_1++; 1.114 + if (scaledVal > (NumSegments-1+2)) num_out_of_range_over_2++; 1.115 + if (scaledVal > (NumSegments-1+3)) num_out_of_range_over_3++; 1.116 + num_total++; 1.117 + if (scaledVal > max_scaledVal) 1.118 + { 1.119 + max_scaledVal = scaledVal; 1.120 + max_scaledVal = scaledVal; 1.121 + } 1.122 + #endif 1.123 + 1.124 + float scaledValFloor = floorf ( scaledVal ); 1.125 + scaledValFloor = Alg::Max ( 0.0f, Alg::Min ( (float)(NumSegments-1), scaledValFloor ) ); 1.126 + float t = scaledVal - scaledValFloor; 1.127 + int k = (int)scaledValFloor; 1.128 + 1.129 + float p0, p1; 1.130 + float m0, m1; 1.131 + switch ( k ) 1.132 + { 1.133 + case 0: 1.134 + // Curve starts at 1.0 with gradient K[1]-K[0] 1.135 + p0 = 1.0f; 1.136 + m0 = ( K[1] - K[0] ); // general case would have been (K[1]-K[-1])/2 1.137 + p1 = K[1]; 1.138 + m1 = 0.5f * ( K[2] - K[0] ); 1.139 + break; 1.140 + default: 1.141 + // General case 1.142 + p0 = K[k ]; 1.143 + m0 = 0.5f * ( K[k+1] - K[k-1] ); 1.144 + p1 = K[k+1]; 1.145 + m1 = 0.5f * ( K[k+2] - K[k ] ); 1.146 + break; 1.147 + case NumSegments-2: 1.148 + // Last tangent is just the slope of the last two points. 1.149 + p0 = K[NumSegments-2]; 1.150 + m0 = 0.5f * ( K[NumSegments-1] - K[NumSegments-2] ); 1.151 + p1 = K[NumSegments-1]; 1.152 + m1 = K[NumSegments-1] - K[NumSegments-2]; 1.153 + break; 1.154 + case NumSegments-1: 1.155 + // Beyond the last segment it's just a straight line 1.156 + p0 = K[NumSegments-1]; 1.157 + m0 = K[NumSegments-1] - K[NumSegments-2]; 1.158 + p1 = p0 + m0; 1.159 + m1 = m0; 1.160 + break; 1.161 + } 1.162 + 1.163 + float omt = 1.0f - t; 1.164 + float res = ( p0 * ( 1.0f + 2.0f * t ) + m0 * t ) * omt * omt 1.165 + + ( p1 * ( 1.0f + 2.0f * omt ) - m1 * omt ) * t * t; 1.166 + 1.167 + return res; 1.168 +} 1.169 + 1.170 + 1.171 + 1.172 + 1.173 +// Converts a Profile eyecup string into an eyecup enumeration 1.174 +void SetEyeCup(HmdRenderInfo* renderInfo, const char* cup) 1.175 +{ 1.176 + if (OVR_strcmp(cup, "A") == 0) 1.177 + renderInfo->EyeCups = EyeCup_DK1A; 1.178 + else if (OVR_strcmp(cup, "B") == 0) 1.179 + renderInfo->EyeCups = EyeCup_DK1B; 1.180 + else if (OVR_strcmp(cup, "C") == 0) 1.181 + renderInfo->EyeCups = EyeCup_DK1C; 1.182 + else if (OVR_strcmp(cup, "Orange A") == 0) 1.183 + renderInfo->EyeCups = EyeCup_OrangeA; 1.184 + else if (OVR_strcmp(cup, "Red A") == 0) 1.185 + renderInfo->EyeCups = EyeCup_RedA; 1.186 + else if (OVR_strcmp(cup, "Pink A") == 0) 1.187 + renderInfo->EyeCups = EyeCup_PinkA; 1.188 + else if (OVR_strcmp(cup, "Blue A") == 0) 1.189 + renderInfo->EyeCups = EyeCup_BlueA; 1.190 + else 1.191 + renderInfo->EyeCups = EyeCup_DK1A; 1.192 +} 1.193 + 1.194 + 1.195 + 1.196 +//----------------------------------------------------------------------------------- 1.197 + 1.198 + 1.199 +// The result is a scaling applied to the distance. 1.200 +float LensConfig::DistortionFnScaleRadiusSquared (float rsq) const 1.201 +{ 1.202 + float scale = 1.0f; 1.203 + switch ( Eqn ) 1.204 + { 1.205 + case Distortion_Poly4: 1.206 + // This version is deprecated! Prefer one of the other two. 1.207 + scale = ( K[0] + rsq * ( K[1] + rsq * ( K[2] + rsq * K[3] ) ) ); 1.208 + break; 1.209 + case Distortion_RecipPoly4: 1.210 + scale = 1.0f / ( K[0] + rsq * ( K[1] + rsq * ( K[2] + rsq * K[3] ) ) ); 1.211 + break; 1.212 + case Distortion_CatmullRom10:{ 1.213 + // A Catmull-Rom spline through the values 1.0, K[1], K[2] ... K[10] 1.214 + // evenly spaced in R^2 from 0.0 to MaxR^2 1.215 + // K[0] controls the slope at radius=0.0, rather than the actual value. 1.216 + const int NumSegments = LensConfig::NumCoefficients; 1.217 + OVR_ASSERT ( NumSegments <= NumCoefficients ); 1.218 + float scaledRsq = (float)(NumSegments-1) * rsq / ( MaxR * MaxR ); 1.219 + scale = EvalCatmullRom10Spline ( K, scaledRsq ); 1.220 + 1.221 + 1.222 + //Intercept, and overrule if needed 1.223 + if (CustomDistortion) 1.224 + { 1.225 + scale = CustomDistortion(rsq); 1.226 + } 1.227 + 1.228 + }break; 1.229 + default: 1.230 + OVR_ASSERT ( false ); 1.231 + break; 1.232 + } 1.233 + return scale; 1.234 +} 1.235 + 1.236 +// x,y,z components map to r,g,b 1.237 +Vector3f LensConfig::DistortionFnScaleRadiusSquaredChroma (float rsq) const 1.238 +{ 1.239 + float scale = DistortionFnScaleRadiusSquared ( rsq ); 1.240 + Vector3f scaleRGB; 1.241 + scaleRGB.x = scale * ( 1.0f + ChromaticAberration[0] + rsq * ChromaticAberration[1] ); // Red 1.242 + scaleRGB.y = scale; // Green 1.243 + scaleRGB.z = scale * ( 1.0f + ChromaticAberration[2] + rsq * ChromaticAberration[3] ); // Blue 1.244 + return scaleRGB; 1.245 +} 1.246 + 1.247 +// DistortionFnInverse computes the inverse of the distortion function on an argument. 1.248 +float LensConfig::DistortionFnInverse(float r) const 1.249 +{ 1.250 + OVR_ASSERT((r <= 20.0f)); 1.251 + 1.252 + float s, d; 1.253 + float delta = r * 0.25f; 1.254 + 1.255 + // Better to start guessing too low & take longer to converge than too high 1.256 + // and hit singularities. Empirically, r * 0.5f is too high in some cases. 1.257 + s = r * 0.25f; 1.258 + d = fabs(r - DistortionFn(s)); 1.259 + 1.260 + for (int i = 0; i < 20; i++) 1.261 + { 1.262 + float sUp = s + delta; 1.263 + float sDown = s - delta; 1.264 + float dUp = fabs(r - DistortionFn(sUp)); 1.265 + float dDown = fabs(r - DistortionFn(sDown)); 1.266 + 1.267 + if (dUp < d) 1.268 + { 1.269 + s = sUp; 1.270 + d = dUp; 1.271 + } 1.272 + else if (dDown < d) 1.273 + { 1.274 + s = sDown; 1.275 + d = dDown; 1.276 + } 1.277 + else 1.278 + { 1.279 + delta *= 0.5f; 1.280 + } 1.281 + } 1.282 + 1.283 + return s; 1.284 +} 1.285 + 1.286 + 1.287 + 1.288 +float LensConfig::DistortionFnInverseApprox(float r) const 1.289 +{ 1.290 + float rsq = r * r; 1.291 + float scale = 1.0f; 1.292 + switch ( Eqn ) 1.293 + { 1.294 + case Distortion_Poly4: 1.295 + // Deprecated 1.296 + OVR_ASSERT ( false ); 1.297 + break; 1.298 + case Distortion_RecipPoly4: 1.299 + scale = 1.0f / ( InvK[0] + rsq * ( InvK[1] + rsq * ( InvK[2] + rsq * InvK[3] ) ) ); 1.300 + break; 1.301 + case Distortion_CatmullRom10:{ 1.302 + // A Catmull-Rom spline through the values 1.0, K[1], K[2] ... K[9] 1.303 + // evenly spaced in R^2 from 0.0 to MaxR^2 1.304 + // K[0] controls the slope at radius=0.0, rather than the actual value. 1.305 + const int NumSegments = LensConfig::NumCoefficients; 1.306 + OVR_ASSERT ( NumSegments <= NumCoefficients ); 1.307 + float scaledRsq = (float)(NumSegments-1) * rsq / ( MaxInvR * MaxInvR ); 1.308 + scale = EvalCatmullRom10Spline ( InvK, scaledRsq ); 1.309 + 1.310 + //Intercept, and overrule if needed 1.311 + if (CustomDistortionInv) 1.312 + { 1.313 + scale = CustomDistortionInv(rsq); 1.314 + } 1.315 + 1.316 + }break; 1.317 + default: 1.318 + OVR_ASSERT ( false ); 1.319 + break; 1.320 + } 1.321 + return r * scale; 1.322 +} 1.323 + 1.324 +void LensConfig::SetUpInverseApprox() 1.325 +{ 1.326 + float maxR = MaxInvR; 1.327 + 1.328 + switch ( Eqn ) 1.329 + { 1.330 + case Distortion_Poly4: 1.331 + // Deprecated 1.332 + OVR_ASSERT ( false ); 1.333 + break; 1.334 + case Distortion_RecipPoly4:{ 1.335 + 1.336 + float sampleR[4]; 1.337 + float sampleRSq[4]; 1.338 + float sampleInv[4]; 1.339 + float sampleFit[4]; 1.340 + 1.341 + // Found heuristically... 1.342 + sampleR[0] = 0.0f; 1.343 + sampleR[1] = maxR * 0.4f; 1.344 + sampleR[2] = maxR * 0.8f; 1.345 + sampleR[3] = maxR * 1.5f; 1.346 + for ( int i = 0; i < 4; i++ ) 1.347 + { 1.348 + sampleRSq[i] = sampleR[i] * sampleR[i]; 1.349 + sampleInv[i] = DistortionFnInverse ( sampleR[i] ); 1.350 + sampleFit[i] = sampleR[i] / sampleInv[i]; 1.351 + } 1.352 + sampleFit[0] = 1.0f; 1.353 + FitCubicPolynomial ( InvK, sampleRSq, sampleFit ); 1.354 + 1.355 + #if 0 1.356 + // Should be a nearly exact match on the chosen points. 1.357 + OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[0] ) - DistortionFnInverseApprox ( sampleR[0] ) ) / maxR < 0.0001f ); 1.358 + OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[1] ) - DistortionFnInverseApprox ( sampleR[1] ) ) / maxR < 0.0001f ); 1.359 + OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[2] ) - DistortionFnInverseApprox ( sampleR[2] ) ) / maxR < 0.0001f ); 1.360 + OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[3] ) - DistortionFnInverseApprox ( sampleR[3] ) ) / maxR < 0.0001f ); 1.361 + // Should be a decent match on the rest of the range. 1.362 + const int maxCheck = 20; 1.363 + for ( int i = 0; i < maxCheck; i++ ) 1.364 + { 1.365 + float checkR = (float)i * maxR / (float)maxCheck; 1.366 + float realInv = DistortionFnInverse ( checkR ); 1.367 + float testInv = DistortionFnInverseApprox ( checkR ); 1.368 + float error = fabsf ( realInv - testInv ) / maxR; 1.369 + OVR_ASSERT ( error < 0.1f ); 1.370 + } 1.371 + #endif 1.372 + 1.373 + }break; 1.374 + case Distortion_CatmullRom10:{ 1.375 + 1.376 + const int NumSegments = LensConfig::NumCoefficients; 1.377 + OVR_ASSERT ( NumSegments <= NumCoefficients ); 1.378 + for ( int i = 1; i < NumSegments; i++ ) 1.379 + { 1.380 + float scaledRsq = (float)i; 1.381 + float rsq = scaledRsq * MaxInvR * MaxInvR / (float)( NumSegments - 1); 1.382 + float r = sqrtf ( rsq ); 1.383 + float inv = DistortionFnInverse ( r ); 1.384 + InvK[i] = inv / r; 1.385 + InvK[0] = 1.0f; // TODO: fix this. 1.386 + } 1.387 + 1.388 +#if 0 1.389 + const int maxCheck = 20; 1.390 + for ( int i = 0; i <= maxCheck; i++ ) 1.391 + { 1.392 + float checkR = (float)i * MaxInvR / (float)maxCheck; 1.393 + float realInv = DistortionFnInverse ( checkR ); 1.394 + float testInv = DistortionFnInverseApprox ( checkR ); 1.395 + float error = fabsf ( realInv - testInv ) / MaxR; 1.396 + OVR_ASSERT ( error < 0.01f ); 1.397 + } 1.398 +#endif 1.399 + 1.400 + }break; 1.401 + 1.402 + default: 1.403 + break; 1.404 + } 1.405 +} 1.406 + 1.407 + 1.408 +void LensConfig::SetToIdentity() 1.409 +{ 1.410 + for ( int i = 0; i < NumCoefficients; i++ ) 1.411 + { 1.412 + K[i] = 0.0f; 1.413 + InvK[i] = 0.0f; 1.414 + } 1.415 + Eqn = Distortion_RecipPoly4; 1.416 + K[0] = 1.0f; 1.417 + InvK[0] = 1.0f; 1.418 + MaxR = 1.0f; 1.419 + MaxInvR = 1.0f; 1.420 + ChromaticAberration[0] = 0.0f; 1.421 + ChromaticAberration[1] = 0.0f; 1.422 + ChromaticAberration[2] = 0.0f; 1.423 + ChromaticAberration[3] = 0.0f; 1.424 + MetersPerTanAngleAtCenter = 0.05f; 1.425 +} 1.426 + 1.427 + 1.428 +enum LensConfigStoredVersion 1.429 +{ 1.430 + LCSV_CatmullRom10Version1 = 1 1.431 +}; 1.432 + 1.433 +// DO NOT CHANGE THESE ONCE THEY HAVE BEEN BAKED INTO FIRMWARE. 1.434 +// If something needs to change, add a new one! 1.435 +struct LensConfigStored_CatmullRom10Version1 1.436 +{ 1.437 + // All these items must be fixed-length integers - no "float", no "int", etc. 1.438 + uint16_t VersionNumber; // Must be LCSV_CatmullRom10Version1 1.439 + 1.440 + uint16_t K[11]; 1.441 + uint16_t MaxR; 1.442 + uint16_t MetersPerTanAngleAtCenter; 1.443 + uint16_t ChromaticAberration[4]; 1.444 + // InvK and MaxInvR are calculated on load. 1.445 +}; 1.446 + 1.447 +uint16_t EncodeFixedPointUInt16 ( float val, uint16_t zeroVal, int fractionalBits ) 1.448 +{ 1.449 + OVR_ASSERT ( ( fractionalBits >= 0 ) && ( fractionalBits < 31 ) ); 1.450 + float valWhole = val * (float)( 1 << fractionalBits ); 1.451 + valWhole += (float)zeroVal + 0.5f; 1.452 + valWhole = floorf ( valWhole ); 1.453 + OVR_ASSERT ( ( valWhole >= 0.0f ) && ( valWhole < (float)( 1 << 16 ) ) ); 1.454 + return (uint16_t)valWhole; 1.455 +} 1.456 + 1.457 +float DecodeFixedPointUInt16 ( uint16_t val, uint16_t zeroVal, int fractionalBits ) 1.458 +{ 1.459 + OVR_ASSERT ( ( fractionalBits >= 0 ) && ( fractionalBits < 31 ) ); 1.460 + float valFloat = (float)val; 1.461 + valFloat -= (float)zeroVal; 1.462 + valFloat *= 1.0f / (float)( 1 << fractionalBits ); 1.463 + return valFloat; 1.464 +} 1.465 + 1.466 + 1.467 +// Returns true on success. 1.468 +bool LoadLensConfig ( LensConfig *presult, uint8_t const *pbuffer, int bufferSizeInBytes ) 1.469 +{ 1.470 + if ( bufferSizeInBytes < 2 ) 1.471 + { 1.472 + // Can't even tell the version number! 1.473 + return false; 1.474 + } 1.475 + uint16_t version = DecodeUInt16 ( pbuffer + 0 ); 1.476 + switch ( version ) 1.477 + { 1.478 + case LCSV_CatmullRom10Version1: 1.479 + { 1.480 + if ( bufferSizeInBytes < (int)sizeof(LensConfigStored_CatmullRom10Version1) ) 1.481 + { 1.482 + return false; 1.483 + } 1.484 + LensConfigStored_CatmullRom10Version1 lcs; 1.485 + lcs.VersionNumber = DecodeUInt16 ( pbuffer + 0 ); 1.486 + for ( int i = 0; i < 11; i++ ) 1.487 + { 1.488 + lcs.K[i] = DecodeUInt16 ( pbuffer + 2 + 2*i ); 1.489 + } 1.490 + lcs.MaxR = DecodeUInt16 ( pbuffer + 24 ); 1.491 + lcs.MetersPerTanAngleAtCenter = DecodeUInt16 ( pbuffer + 26 ); 1.492 + for ( int i = 0; i < 4; i++ ) 1.493 + { 1.494 + lcs.ChromaticAberration[i] = DecodeUInt16 ( pbuffer + 28 + 2*i ); 1.495 + } 1.496 + OVR_COMPILER_ASSERT ( sizeof(lcs) == 36 ); 1.497 + 1.498 + // Convert to the real thing. 1.499 + LensConfig result; 1.500 + result.Eqn = Distortion_CatmullRom10; 1.501 + for ( int i = 0; i < 11; i++ ) 1.502 + { 1.503 + // K[] are mostly 1.something. They may get significantly bigger, but they never hit 0.0. 1.504 + result.K[i] = DecodeFixedPointUInt16 ( lcs.K[i], 0, 14 ); 1.505 + } 1.506 + // MaxR is tan(angle), so always >0, typically just over 1.0 (45 degrees half-fov), 1.507 + // but may get arbitrarily high. tan(76)=4 is a very reasonable limit! 1.508 + result.MaxR = DecodeFixedPointUInt16 ( lcs.MaxR, 0, 14 ); 1.509 + // MetersPerTanAngleAtCenter is also known as focal length! 1.510 + // Typically around 0.04 for our current screens, minimum of 0, sensible maximum of 0.125 (i.e. 3 "extra" bits of fraction) 1.511 + result.MetersPerTanAngleAtCenter = DecodeFixedPointUInt16 ( lcs.MetersPerTanAngleAtCenter, 0, 16+3 ); 1.512 + for ( int i = 0; i < 4; i++ ) 1.513 + { 1.514 + // 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) 1.515 + result.ChromaticAberration[i] = DecodeFixedPointUInt16 ( lcs.ChromaticAberration[i], 0x8000, 16+3 ); 1.516 + } 1.517 + result.MaxInvR = result.DistortionFn ( result.MaxR ); 1.518 + result.SetUpInverseApprox(); 1.519 + 1.520 + OVR_ASSERT ( version == lcs.VersionNumber ); 1.521 + 1.522 + *presult = result; 1.523 + } 1.524 + break; 1.525 + default: 1.526 + // Unknown format. 1.527 + return false; 1.528 + break; 1.529 + } 1.530 + return true; 1.531 +} 1.532 + 1.533 +// Returns number of bytes needed. 1.534 +int SaveLensConfigSizeInBytes ( LensConfig const &config ) 1.535 +{ 1.536 + OVR_UNUSED ( config ); 1.537 + return sizeof ( LensConfigStored_CatmullRom10Version1 ); 1.538 +} 1.539 + 1.540 +// Returns true on success. 1.541 +bool SaveLensConfig ( uint8_t *pbuffer, int bufferSizeInBytes, LensConfig const &config ) 1.542 +{ 1.543 + if ( bufferSizeInBytes < (int)sizeof ( LensConfigStored_CatmullRom10Version1 ) ) 1.544 + { 1.545 + return false; 1.546 + } 1.547 + 1.548 + // Construct the values. 1.549 + LensConfigStored_CatmullRom10Version1 lcs; 1.550 + lcs.VersionNumber = LCSV_CatmullRom10Version1; 1.551 + for ( int i = 0; i < 11; i++ ) 1.552 + { 1.553 + // K[] are mostly 1.something. They may get significantly bigger, but they never hit 0.0. 1.554 + lcs.K[i] = EncodeFixedPointUInt16 ( config.K[i], 0, 14 ); 1.555 + } 1.556 + // MaxR is tan(angle), so always >0, typically just over 1.0 (45 degrees half-fov), 1.557 + // but may get arbitrarily high. tan(76)=4 is a very reasonable limit! 1.558 + lcs.MaxR = EncodeFixedPointUInt16 ( config.MaxR, 0, 14 ); 1.559 + // MetersPerTanAngleAtCenter is also known as focal length! 1.560 + // Typically around 0.04 for our current screens, minimum of 0, sensible maximum of 0.125 (i.e. 3 "extra" bits of fraction) 1.561 + lcs.MetersPerTanAngleAtCenter = EncodeFixedPointUInt16 ( config.MetersPerTanAngleAtCenter, 0, 16+3 ); 1.562 + for ( int i = 0; i < 4; i++ ) 1.563 + { 1.564 + // 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) 1.565 + lcs.ChromaticAberration[i] = EncodeFixedPointUInt16 ( config.ChromaticAberration[i], 0x8000, 16+3 ); 1.566 + } 1.567 + 1.568 + 1.569 + // Now store them out, sensitive to endianness. 1.570 + EncodeUInt16 ( pbuffer + 0, lcs.VersionNumber ); 1.571 + for ( int i = 0; i < 11; i++ ) 1.572 + { 1.573 + EncodeUInt16 ( pbuffer + 2 + 2*i, lcs.K[i] ); 1.574 + } 1.575 + EncodeUInt16 ( pbuffer + 24, lcs.MaxR ); 1.576 + EncodeUInt16 ( pbuffer + 26, lcs.MetersPerTanAngleAtCenter ); 1.577 + for ( int i = 0; i < 4; i++ ) 1.578 + { 1.579 + EncodeUInt16 ( pbuffer + 28 + 2*i, lcs.ChromaticAberration[i] ); 1.580 + } 1.581 + OVR_COMPILER_ASSERT ( 36 == sizeof(lcs) ); 1.582 + 1.583 + return true; 1.584 +} 1.585 + 1.586 +#ifdef OVR_BUILD_DEBUG 1.587 +void TestSaveLoadLensConfig ( LensConfig const &config ) 1.588 +{ 1.589 + OVR_ASSERT ( config.Eqn == Distortion_CatmullRom10 ); 1.590 + // As a test, make sure this can be encoded and decoded correctly. 1.591 + const int bufferSize = 256; 1.592 + uint8_t buffer[bufferSize]; 1.593 + OVR_ASSERT ( SaveLensConfigSizeInBytes ( config ) < bufferSize ); 1.594 + bool success; 1.595 + success = SaveLensConfig ( buffer, bufferSize, config ); 1.596 + OVR_ASSERT ( success ); 1.597 + LensConfig testConfig; 1.598 + success = LoadLensConfig ( &testConfig, buffer, bufferSize ); 1.599 + OVR_ASSERT ( success ); 1.600 + OVR_ASSERT ( testConfig.Eqn == config.Eqn ); 1.601 + for ( int i = 0; i < 11; i++ ) 1.602 + { 1.603 + OVR_ASSERT ( fabs ( testConfig.K[i] - config.K[i] ) < 0.0001f ); 1.604 + } 1.605 + OVR_ASSERT ( fabsf ( testConfig.MaxR - config.MaxR ) < 0.0001f ); 1.606 + OVR_ASSERT ( fabsf ( testConfig.MetersPerTanAngleAtCenter - config.MetersPerTanAngleAtCenter ) < 0.00001f ); 1.607 + for ( int i = 0; i < 4; i++ ) 1.608 + { 1.609 + OVR_ASSERT ( fabsf ( testConfig.ChromaticAberration[i] - config.ChromaticAberration[i] ) < 0.00001f ); 1.610 + } 1.611 +} 1.612 +#endif 1.613 + 1.614 + 1.615 + 1.616 +//----------------------------------------------------------------------------------- 1.617 + 1.618 +// TBD: There is a question of whether this is the best file for CreateDebugHMDInfo. As long as there are many 1.619 +// constants for HmdRenderInfo here as well it is ok. The alternative would be OVR_Common_HMDDevice.cpp, but 1.620 +// that's specialized per platform... should probably move it there onces the code is in the common base class. 1.621 + 1.622 +HMDInfo CreateDebugHMDInfo(HmdTypeEnum hmdType) 1.623 +{ 1.624 + HMDInfo info; 1.625 + 1.626 + if ((hmdType != HmdType_DK1) && 1.627 + (hmdType != HmdType_CrystalCoveProto) && 1.628 + (hmdType != HmdType_DK2)) 1.629 + { 1.630 + LogText("Debug HMDInfo - HmdType not supported. Defaulting to DK1.\n"); 1.631 + hmdType = HmdType_DK1; 1.632 + } 1.633 + 1.634 + // The alternative would be to initialize info.HmdType to HmdType_None instead. If we did that, 1.635 + // code wouldn't be "maximally compatible" and devs wouldn't know what device we are 1.636 + // simulating... so if differentiation becomes necessary we better add Debug flag in the future. 1.637 + info.HmdType = hmdType; 1.638 + info.Manufacturer = "Oculus VR"; 1.639 + 1.640 + switch(hmdType) 1.641 + { 1.642 + case HmdType_DK1: 1.643 + info.ProductName = "Oculus Rift DK1"; 1.644 + info.ResolutionInPixels = Sizei ( 1280, 800 ); 1.645 + info.ScreenSizeInMeters = Sizef ( 0.1498f, 0.0936f ); 1.646 + info.ScreenGapSizeInMeters = 0.0f; 1.647 + info.CenterFromTopInMeters = 0.0468f; 1.648 + info.LensSeparationInMeters = 0.0635f; 1.649 + info.PelOffsetR = Vector2f ( 0.0f, 0.0f ); 1.650 + info.PelOffsetB = Vector2f ( 0.0f, 0.0f ); 1.651 + info.Shutter.Type = HmdShutter_RollingTopToBottom; 1.652 + info.Shutter.VsyncToNextVsync = ( 1.0f / 60.0f ); 1.653 + info.Shutter.VsyncToFirstScanline = 0.000052f; 1.654 + info.Shutter.FirstScanlineToLastScanline = 0.016580f; 1.655 + info.Shutter.PixelSettleTime = 0.015f; 1.656 + info.Shutter.PixelPersistence = ( 1.0f / 60.0f ); 1.657 + break; 1.658 + 1.659 + case HmdType_CrystalCoveProto: 1.660 + info.ProductName = "Oculus Rift Crystal Cove"; 1.661 + info.ResolutionInPixels = Sizei ( 1920, 1080 ); 1.662 + info.ScreenSizeInMeters = Sizef ( 0.12576f, 0.07074f ); 1.663 + info.ScreenGapSizeInMeters = 0.0f; 1.664 + info.CenterFromTopInMeters = info.ScreenSizeInMeters.h * 0.5f; 1.665 + info.LensSeparationInMeters = 0.0635f; 1.666 + info.PelOffsetR = Vector2f ( 0.0f, 0.0f ); 1.667 + info.PelOffsetB = Vector2f ( 0.0f, 0.0f ); 1.668 + info.Shutter.Type = HmdShutter_RollingRightToLeft; 1.669 + info.Shutter.VsyncToNextVsync = ( 1.0f / 76.0f ); 1.670 + info.Shutter.VsyncToFirstScanline = 0.0000273f; 1.671 + info.Shutter.FirstScanlineToLastScanline = 0.0131033f; 1.672 + info.Shutter.PixelSettleTime = 0.0f; 1.673 + info.Shutter.PixelPersistence = 0.18f * info.Shutter.VsyncToNextVsync; 1.674 + break; 1.675 + 1.676 + case HmdType_DK2: 1.677 + info.ProductName = "Oculus Rift DK2"; 1.678 + info.ResolutionInPixels = Sizei ( 1920, 1080 ); 1.679 + info.ScreenSizeInMeters = Sizef ( 0.12576f, 0.07074f ); 1.680 + info.ScreenGapSizeInMeters = 0.0f; 1.681 + info.CenterFromTopInMeters = info.ScreenSizeInMeters.h * 0.5f; 1.682 + info.LensSeparationInMeters = 0.0635f; 1.683 + info.PelOffsetR = Vector2f ( 0.5f, 0.5f ); 1.684 + info.PelOffsetB = Vector2f ( 0.5f, 0.5f ); 1.685 + info.Shutter.Type = HmdShutter_RollingRightToLeft; 1.686 + info.Shutter.VsyncToNextVsync = ( 1.0f / 76.0f ); 1.687 + info.Shutter.VsyncToFirstScanline = 0.0000273f; 1.688 + info.Shutter.FirstScanlineToLastScanline = 0.0131033f; 1.689 + info.Shutter.PixelSettleTime = 0.0f; 1.690 + info.Shutter.PixelPersistence = 0.18f * info.Shutter.VsyncToNextVsync; 1.691 + break; 1.692 + 1.693 + default: 1.694 + break; 1.695 + } 1.696 + 1.697 + return info; 1.698 +} 1.699 + 1.700 + 1.701 +HmdRenderInfo GenerateHmdRenderInfoFromHmdInfo ( HMDInfo const &hmdInfo, 1.702 + Profile const *profile, 1.703 + DistortionEqnType distortionType /*= Distortion_CatmullRom10*/, 1.704 + EyeCupType eyeCupOverride /*= EyeCup_LAST*/ ) 1.705 +{ 1.706 + HmdRenderInfo renderInfo; 1.707 + 1.708 + OVR_ASSERT(profile); // profiles are required 1.709 + if(!profile) 1.710 + return renderInfo; 1.711 + 1.712 + renderInfo.HmdType = hmdInfo.HmdType; 1.713 + renderInfo.ResolutionInPixels = hmdInfo.ResolutionInPixels; 1.714 + renderInfo.ScreenSizeInMeters = hmdInfo.ScreenSizeInMeters; 1.715 + renderInfo.CenterFromTopInMeters = hmdInfo.CenterFromTopInMeters; 1.716 + renderInfo.ScreenGapSizeInMeters = hmdInfo.ScreenGapSizeInMeters; 1.717 + renderInfo.LensSeparationInMeters = hmdInfo.LensSeparationInMeters; 1.718 + renderInfo.PelOffsetR = hmdInfo.PelOffsetR; 1.719 + renderInfo.PelOffsetB = hmdInfo.PelOffsetB; 1.720 + 1.721 + OVR_ASSERT ( sizeof(renderInfo.Shutter) == sizeof(hmdInfo.Shutter) ); // Try to keep the files in sync! 1.722 + renderInfo.Shutter.Type = hmdInfo.Shutter.Type; 1.723 + renderInfo.Shutter.VsyncToNextVsync = hmdInfo.Shutter.VsyncToNextVsync; 1.724 + renderInfo.Shutter.VsyncToFirstScanline = hmdInfo.Shutter.VsyncToFirstScanline; 1.725 + renderInfo.Shutter.FirstScanlineToLastScanline = hmdInfo.Shutter.FirstScanlineToLastScanline; 1.726 + renderInfo.Shutter.PixelSettleTime = hmdInfo.Shutter.PixelSettleTime; 1.727 + renderInfo.Shutter.PixelPersistence = hmdInfo.Shutter.PixelPersistence; 1.728 + 1.729 + renderInfo.LensDiameterInMeters = 0.035f; 1.730 + renderInfo.LensSurfaceToMidplateInMeters = 0.025f; 1.731 + renderInfo.EyeCups = EyeCup_DK1A; 1.732 + 1.733 +#if 0 // Device settings are out of date - don't use them. 1.734 + if (Contents & Contents_Distortion) 1.735 + { 1.736 + memcpy(renderInfo.DistortionK, DistortionK, sizeof(float)*4); 1.737 + renderInfo.DistortionEqn = Distortion_RecipPoly4; 1.738 + } 1.739 +#endif 1.740 + 1.741 + // Defaults in case of no user profile. 1.742 + renderInfo.EyeLeft.NoseToPupilInMeters = 0.032f; 1.743 + renderInfo.EyeLeft.ReliefInMeters = 0.012f; 1.744 + 1.745 + // 10mm eye-relief laser numbers for DK1 lenses. 1.746 + // These are a decent seed for finding eye-relief and IPD. 1.747 + // These are NOT used for rendering! 1.748 + // Rendering distortions are now in GenerateLensConfigFromEyeRelief() 1.749 + // So, if you're hacking in new distortions, don't do it here! 1.750 + renderInfo.EyeLeft.Distortion.SetToIdentity(); 1.751 + renderInfo.EyeLeft.Distortion.MetersPerTanAngleAtCenter = 0.0449f; 1.752 + renderInfo.EyeLeft.Distortion.Eqn = Distortion_RecipPoly4; 1.753 + renderInfo.EyeLeft.Distortion.K[0] = 1.0f; 1.754 + renderInfo.EyeLeft.Distortion.K[1] = -0.494165344f; 1.755 + renderInfo.EyeLeft.Distortion.K[2] = 0.587046423f; 1.756 + renderInfo.EyeLeft.Distortion.K[3] = -0.841887126f; 1.757 + renderInfo.EyeLeft.Distortion.MaxR = 1.0f; 1.758 + 1.759 + renderInfo.EyeLeft.Distortion.ChromaticAberration[0] = -0.006f; 1.760 + renderInfo.EyeLeft.Distortion.ChromaticAberration[1] = 0.0f; 1.761 + renderInfo.EyeLeft.Distortion.ChromaticAberration[2] = 0.014f; 1.762 + renderInfo.EyeLeft.Distortion.ChromaticAberration[3] = 0.0f; 1.763 + 1.764 + renderInfo.EyeRight = renderInfo.EyeLeft; 1.765 + 1.766 + // Obtain data from profile. 1.767 + char eyecup[16]; 1.768 + if (profile->GetValue(OVR_KEY_EYE_CUP, eyecup, 16)) 1.769 + { 1.770 + SetEyeCup(&renderInfo, eyecup); 1.771 + } 1.772 + 1.773 + switch ( hmdInfo.HmdType ) 1.774 + { 1.775 + case HmdType_None: 1.776 + case HmdType_DKProto: 1.777 + case HmdType_DK1: 1.778 + // Slight hack to improve usability. 1.779 + // If you have a DKHD-style lens profile enabled, 1.780 + // but you plug in DK1 and forget to change the profile, 1.781 + // obviously you don't want those lens numbers. 1.782 + if ( ( renderInfo.EyeCups != EyeCup_DK1A ) && 1.783 + ( renderInfo.EyeCups != EyeCup_DK1B ) && 1.784 + ( renderInfo.EyeCups != EyeCup_DK1C ) ) 1.785 + { 1.786 + renderInfo.EyeCups = EyeCup_DK1A; 1.787 + } 1.788 + break; 1.789 + 1.790 + case HmdType_DKHD2Proto: 1.791 + renderInfo.EyeCups = EyeCup_DKHD2A; 1.792 + break; 1.793 + case HmdType_CrystalCoveProto: 1.794 + renderInfo.EyeCups = EyeCup_PinkA; 1.795 + break; 1.796 + case HmdType_DK2: 1.797 + renderInfo.EyeCups = EyeCup_DK2A; 1.798 + break; 1.799 + default: 1.800 + break; 1.801 + } 1.802 + 1.803 + if ( eyeCupOverride != EyeCup_LAST ) 1.804 + { 1.805 + renderInfo.EyeCups = eyeCupOverride; 1.806 + } 1.807 + 1.808 + switch ( renderInfo.EyeCups ) 1.809 + { 1.810 + case EyeCup_DK1A: 1.811 + case EyeCup_DK1B: 1.812 + case EyeCup_DK1C: 1.813 + renderInfo.LensDiameterInMeters = 0.035f; 1.814 + renderInfo.LensSurfaceToMidplateInMeters = 0.02357f; 1.815 + // Not strictly lens-specific, but still wise to set a reasonable default for relief. 1.816 + renderInfo.EyeLeft.ReliefInMeters = 0.010f; 1.817 + renderInfo.EyeRight.ReliefInMeters = 0.010f; 1.818 + break; 1.819 + case EyeCup_DKHD2A: 1.820 + renderInfo.LensDiameterInMeters = 0.035f; 1.821 + renderInfo.LensSurfaceToMidplateInMeters = 0.02357f; 1.822 + // Not strictly lens-specific, but still wise to set a reasonable default for relief. 1.823 + renderInfo.EyeLeft.ReliefInMeters = 0.010f; 1.824 + renderInfo.EyeRight.ReliefInMeters = 0.010f; 1.825 + break; 1.826 + case EyeCup_PinkA: 1.827 + case EyeCup_DK2A: 1.828 + renderInfo.LensDiameterInMeters = 0.04f; // approximate 1.829 + renderInfo.LensSurfaceToMidplateInMeters = 0.01965f; 1.830 + // Not strictly lens-specific, but still wise to set a reasonable default for relief. 1.831 + renderInfo.EyeLeft.ReliefInMeters = 0.012f; 1.832 + renderInfo.EyeRight.ReliefInMeters = 0.012f; 1.833 + break; 1.834 + default: OVR_ASSERT ( false ); break; 1.835 + } 1.836 + 1.837 + Profile* def = ProfileManager::GetInstance()->GetDefaultProfile(hmdInfo.HmdType); 1.838 + 1.839 + // Set the eye position 1.840 + // Use the user profile value unless they have elected to use the defaults 1.841 + if (!profile->GetBoolValue(OVR_KEY_CUSTOM_EYE_RENDER, true)) 1.842 + profile = def; // use the default 1.843 + 1.844 + char user[32]; 1.845 + profile->GetValue(OVR_KEY_USER, user, 32); // for debugging purposes 1.846 + 1.847 + // TBD: Maybe we should separate custom camera positioning from custom distortion rendering ?? 1.848 + float eye2nose[2] = { OVR_DEFAULT_IPD / 2, OVR_DEFAULT_IPD / 2 }; 1.849 + if (profile->GetFloatValues(OVR_KEY_EYE_TO_NOSE_DISTANCE, eye2nose, 2) == 2) 1.850 + { 1.851 + renderInfo.EyeLeft.NoseToPupilInMeters = eye2nose[0]; 1.852 + renderInfo.EyeRight.NoseToPupilInMeters = eye2nose[1]; 1.853 + } 1.854 + else 1.855 + { // Legacy profiles may not include half-ipd, so use the regular IPD value instead 1.856 + float ipd = profile->GetFloatValue(OVR_KEY_IPD, OVR_DEFAULT_IPD); 1.857 + renderInfo.EyeLeft.NoseToPupilInMeters = 0.5f * ipd; 1.858 + renderInfo.EyeRight.NoseToPupilInMeters = 0.5f * ipd; 1.859 + } 1.860 + 1.861 + float eye2plate[2]; 1.862 + if ((profile->GetFloatValues(OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE, eye2plate, 2) == 2) || 1.863 + (def->GetFloatValues(OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE, eye2plate, 2) == 2)) 1.864 + { // Subtract the eye-cup height from the plate distance to get the eye-to-lens distance 1.865 + // This measurement should be the the distance at maximum dial setting 1.866 + // We still need to adjust with the dial offset 1.867 + renderInfo.EyeLeft.ReliefInMeters = eye2plate[0] - renderInfo.LensSurfaceToMidplateInMeters; 1.868 + renderInfo.EyeRight.ReliefInMeters = eye2plate[1] - renderInfo.LensSurfaceToMidplateInMeters; 1.869 + 1.870 + // Adjust the eye relief with the dial setting (from the assumed max eye relief) 1.871 + int dial = profile->GetIntValue(OVR_KEY_EYE_RELIEF_DIAL, OVR_DEFAULT_EYE_RELIEF_DIAL); 1.872 + renderInfo.EyeLeft.ReliefInMeters -= ((10 - dial) * 0.001f); 1.873 + renderInfo.EyeRight.ReliefInMeters -= ((10 - dial) * 0.001f); 1.874 + } 1.875 + else 1.876 + { 1.877 + // We shouldn't be here. The user or default profile should have the eye relief 1.878 + OVR_ASSERT(false); 1.879 + 1.880 + // Set the eye relief with the user configured dial setting 1.881 + //int dial = profile->GetIntValue(OVR_KEY_EYE_RELIEF_DIAL, OVR_DEFAULT_EYE_RELIEF_DIAL); 1.882 + 1.883 + // Assume a default of 7 to 17 mm eye relief based on the dial. This corresponds 1.884 + // to the sampled and tuned distortion range on the DK1. 1.885 + //renderInfo.EyeLeft.ReliefInMeters = 0.007f + (dial * 0.001f); 1.886 + //renderInfo.EyeRight.ReliefInMeters = 0.007f + (dial * 0.001f); 1.887 + } 1.888 + 1.889 + def->Release(); 1.890 + 1.891 + 1.892 + // Now we know where the eyes are relative to the lenses, we can compute a distortion for each. 1.893 + // TODO: incorporate lateral offset in distortion generation. 1.894 + // TODO: we used a distortion to calculate eye-relief, and now we're making a distortion from that eye-relief. Close the loop! 1.895 + 1.896 + for ( int eyeNum = 0; eyeNum < 2; eyeNum++ ) 1.897 + { 1.898 + HmdRenderInfo::EyeConfig *pHmdEyeConfig = ( eyeNum == 0 ) ? &(renderInfo.EyeLeft) : &(renderInfo.EyeRight); 1.899 + 1.900 + float eye_relief = pHmdEyeConfig->ReliefInMeters; 1.901 + LensConfig distortionConfig = GenerateLensConfigFromEyeRelief ( eye_relief, renderInfo, distortionType ); 1.902 + pHmdEyeConfig->Distortion = distortionConfig; 1.903 + } 1.904 + 1.905 + return renderInfo; 1.906 +} 1.907 + 1.908 + 1.909 +LensConfig GenerateLensConfigFromEyeRelief ( float eyeReliefInMeters, HmdRenderInfo const &hmd, DistortionEqnType distortionType /*= Distortion_CatmullRom10*/ ) 1.910 +{ 1.911 + struct DistortionDescriptor 1.912 + { 1.913 + float EyeRelief; 1.914 + // The three places we're going to sample & lerp the curve at. 1.915 + // One sample is always at 0.0, and the distortion scale should be 1.0 or else! 1.916 + // Only use for poly4 numbers - CR has an implicit scale. 1.917 + float SampleRadius[3]; 1.918 + // Where the distortion has actually been measured/calibrated out to. 1.919 + // Don't try to hallucinate data out beyond here. 1.920 + float MaxRadius; 1.921 + // The config itself. 1.922 + LensConfig Config; 1.923 + }; 1.924 + 1.925 + static const int MaxDistortions = 10; 1.926 + DistortionDescriptor distortions[MaxDistortions]; 1.927 + for (int i = 0; i < MaxDistortions; i++) 1.928 + { 1.929 + distortions[i].EyeRelief = 0.0f; 1.930 + memset(distortions[i].SampleRadius, 0, sizeof(distortions[i].SampleRadius)); 1.931 + distortions[i].MaxRadius = 1.0f; 1.932 + distortions[i].Config.SetToIdentity(); // Note: This line causes a false Microsoft static analysis error -cat 1.933 + } 1.934 + int numDistortions = 0; 1.935 + int defaultDistortion = 0; // index of the default distortion curve to use if zero eye relief supplied 1.936 + 1.937 + if ( ( hmd.EyeCups == EyeCup_DK1A ) || 1.938 + ( hmd.EyeCups == EyeCup_DK1B ) || 1.939 + ( hmd.EyeCups == EyeCup_DK1C ) ) 1.940 + { 1.941 + 1.942 + numDistortions = 0; 1.943 + 1.944 + // Tuned at minimum dial setting - extended to r^2 == 1.8 1.945 + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; 1.946 + distortions[numDistortions].EyeRelief = 0.012760465f - 0.005f; 1.947 + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f; 1.948 + distortions[numDistortions].Config.K[0] = 1.0000f; 1.949 + distortions[numDistortions].Config.K[1] = 1.06505f; 1.950 + distortions[numDistortions].Config.K[2] = 1.14725f; 1.951 + distortions[numDistortions].Config.K[3] = 1.2705f; 1.952 + distortions[numDistortions].Config.K[4] = 1.48f; 1.953 + distortions[numDistortions].Config.K[5] = 1.87f; 1.954 + distortions[numDistortions].Config.K[6] = 2.534f; 1.955 + distortions[numDistortions].Config.K[7] = 3.6f; 1.956 + distortions[numDistortions].Config.K[8] = 5.1f; 1.957 + distortions[numDistortions].Config.K[9] = 7.4f; 1.958 + distortions[numDistortions].Config.K[10] = 11.0f; 1.959 + distortions[numDistortions].MaxRadius = sqrt(1.8f); 1.960 + defaultDistortion = numDistortions; // this is the default 1.961 + numDistortions++; 1.962 + 1.963 + // Tuned at middle dial setting 1.964 + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; 1.965 + distortions[numDistortions].EyeRelief = 0.012760465f; // my average eye-relief 1.966 + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f; 1.967 + distortions[numDistortions].Config.K[0] = 1.0f; 1.968 + distortions[numDistortions].Config.K[1] = 1.032407264f; 1.969 + distortions[numDistortions].Config.K[2] = 1.07160462f; 1.970 + distortions[numDistortions].Config.K[3] = 1.11998388f; 1.971 + distortions[numDistortions].Config.K[4] = 1.1808606f; 1.972 + distortions[numDistortions].Config.K[5] = 1.2590494f; 1.973 + distortions[numDistortions].Config.K[6] = 1.361915f; 1.974 + distortions[numDistortions].Config.K[7] = 1.5014339f; 1.975 + distortions[numDistortions].Config.K[8] = 1.6986004f; 1.976 + distortions[numDistortions].Config.K[9] = 1.9940577f; 1.977 + distortions[numDistortions].Config.K[10] = 2.4783147f; 1.978 + distortions[numDistortions].MaxRadius = 1.0f; 1.979 + numDistortions++; 1.980 + 1.981 + // Tuned at maximum dial setting 1.982 + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; 1.983 + distortions[numDistortions].EyeRelief = 0.012760465f + 0.005f; 1.984 + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f; 1.985 + distortions[numDistortions].Config.K[0] = 1.0102f; 1.986 + distortions[numDistortions].Config.K[1] = 1.0371f; 1.987 + distortions[numDistortions].Config.K[2] = 1.0831f; 1.988 + distortions[numDistortions].Config.K[3] = 1.1353f; 1.989 + distortions[numDistortions].Config.K[4] = 1.2f; 1.990 + distortions[numDistortions].Config.K[5] = 1.2851f; 1.991 + distortions[numDistortions].Config.K[6] = 1.3979f; 1.992 + distortions[numDistortions].Config.K[7] = 1.56f; 1.993 + distortions[numDistortions].Config.K[8] = 1.8f; 1.994 + distortions[numDistortions].Config.K[9] = 2.25f; 1.995 + distortions[numDistortions].Config.K[10] = 3.0f; 1.996 + distortions[numDistortions].MaxRadius = 1.0f; 1.997 + numDistortions++; 1.998 + 1.999 + 1.1000 + 1.1001 + // Chromatic aberration doesn't seem to change with eye relief. 1.1002 + for ( int i = 0; i < numDistortions; i++ ) 1.1003 + { 1.1004 + distortions[i].Config.ChromaticAberration[0] = -0.006f; 1.1005 + distortions[i].Config.ChromaticAberration[1] = 0.0f; 1.1006 + distortions[i].Config.ChromaticAberration[2] = 0.014f; 1.1007 + distortions[i].Config.ChromaticAberration[3] = 0.0f; 1.1008 + } 1.1009 + } 1.1010 + else if ( hmd.EyeCups == EyeCup_DKHD2A ) 1.1011 + { 1.1012 + // Tuned DKHD2 lens 1.1013 + numDistortions = 0; 1.1014 + 1.1015 + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; 1.1016 + distortions[numDistortions].EyeRelief = 0.010f; 1.1017 + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f; 1.1018 + distortions[numDistortions].Config.K[0] = 1.0f; 1.1019 + distortions[numDistortions].Config.K[1] = 1.0425f; 1.1020 + distortions[numDistortions].Config.K[2] = 1.0826f; 1.1021 + distortions[numDistortions].Config.K[3] = 1.130f; 1.1022 + distortions[numDistortions].Config.K[4] = 1.185f; 1.1023 + distortions[numDistortions].Config.K[5] = 1.250f; 1.1024 + distortions[numDistortions].Config.K[6] = 1.338f; 1.1025 + distortions[numDistortions].Config.K[7] = 1.455f; 1.1026 + distortions[numDistortions].Config.K[8] = 1.620f; 1.1027 + distortions[numDistortions].Config.K[9] = 1.840f; 1.1028 + distortions[numDistortions].Config.K[10] = 2.200f; 1.1029 + distortions[numDistortions].MaxRadius = 1.0f; 1.1030 + 1.1031 + defaultDistortion = numDistortions; // this is the default 1.1032 + numDistortions++; 1.1033 + 1.1034 + distortions[numDistortions] = distortions[0]; 1.1035 + distortions[numDistortions].EyeRelief = 0.020f; 1.1036 + numDistortions++; 1.1037 + 1.1038 + // Chromatic aberration doesn't seem to change with eye relief. 1.1039 + for ( int i = 0; i < numDistortions; i++ ) 1.1040 + { 1.1041 + distortions[i].Config.ChromaticAberration[0] = -0.006f; 1.1042 + distortions[i].Config.ChromaticAberration[1] = 0.0f; 1.1043 + distortions[i].Config.ChromaticAberration[2] = 0.014f; 1.1044 + distortions[i].Config.ChromaticAberration[3] = 0.0f; 1.1045 + } 1.1046 + } 1.1047 + else if ( hmd.EyeCups == EyeCup_PinkA || hmd.EyeCups == EyeCup_DK2A ) 1.1048 + { 1.1049 + // Tuned Crystal Cove & DK2 Lens (CES & GDC) 1.1050 + numDistortions = 0; 1.1051 + 1.1052 + 1.1053 + distortions[numDistortions].EyeRelief = 0.008f; 1.1054 + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.036f; 1.1055 + // TODO: Need to retune this distortion for minimum eye relief 1.1056 + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; 1.1057 + distortions[numDistortions].Config.K[0] = 1.003f; 1.1058 + distortions[numDistortions].Config.K[1] = 1.02f; 1.1059 + distortions[numDistortions].Config.K[2] = 1.042f; 1.1060 + distortions[numDistortions].Config.K[3] = 1.066f; 1.1061 + distortions[numDistortions].Config.K[4] = 1.094f; 1.1062 + distortions[numDistortions].Config.K[5] = 1.126f; 1.1063 + distortions[numDistortions].Config.K[6] = 1.162f; 1.1064 + distortions[numDistortions].Config.K[7] = 1.203f; 1.1065 + distortions[numDistortions].Config.K[8] = 1.25f; 1.1066 + distortions[numDistortions].Config.K[9] = 1.31f; 1.1067 + distortions[numDistortions].Config.K[10] = 1.38f; 1.1068 + distortions[numDistortions].MaxRadius = 1.0f; 1.1069 + 1.1070 + distortions[numDistortions].Config.ChromaticAberration[0] = -0.0112f; 1.1071 + distortions[numDistortions].Config.ChromaticAberration[1] = -0.015f; 1.1072 + distortions[numDistortions].Config.ChromaticAberration[2] = 0.0187f; 1.1073 + distortions[numDistortions].Config.ChromaticAberration[3] = 0.015f; 1.1074 + 1.1075 + numDistortions++; 1.1076 + 1.1077 + 1.1078 + 1.1079 + 1.1080 + 1.1081 + distortions[numDistortions].EyeRelief = 0.018f; 1.1082 + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.036f; 1.1083 + 1.1084 + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; 1.1085 + distortions[numDistortions].Config.K[0] = 1.003f; 1.1086 + distortions[numDistortions].Config.K[1] = 1.02f; 1.1087 + distortions[numDistortions].Config.K[2] = 1.042f; 1.1088 + distortions[numDistortions].Config.K[3] = 1.066f; 1.1089 + distortions[numDistortions].Config.K[4] = 1.094f; 1.1090 + distortions[numDistortions].Config.K[5] = 1.126f; 1.1091 + distortions[numDistortions].Config.K[6] = 1.162f; 1.1092 + distortions[numDistortions].Config.K[7] = 1.203f; 1.1093 + distortions[numDistortions].Config.K[8] = 1.25f; 1.1094 + distortions[numDistortions].Config.K[9] = 1.31f; 1.1095 + distortions[numDistortions].Config.K[10] = 1.38f; 1.1096 + distortions[numDistortions].MaxRadius = 1.0f; 1.1097 + 1.1098 + distortions[numDistortions].Config.ChromaticAberration[0] = -0.015f; 1.1099 + distortions[numDistortions].Config.ChromaticAberration[1] = -0.02f; 1.1100 + distortions[numDistortions].Config.ChromaticAberration[2] = 0.025f; 1.1101 + distortions[numDistortions].Config.ChromaticAberration[3] = 0.02f; 1.1102 + 1.1103 + defaultDistortion = numDistortions; // this is the default 1.1104 + numDistortions++; 1.1105 + 1.1106 + /* 1.1107 + // Orange Lens on DK2 1.1108 + distortions[numDistortions].EyeRelief = 0.010f; 1.1109 + distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.031f; 1.1110 + 1.1111 + distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10; 1.1112 + distortions[numDistortions].Config.K[0] = 1.00f; 1.1113 + distortions[numDistortions].Config.K[1] = 1.0169f; 1.1114 + distortions[numDistortions].Config.K[2] = 1.0378f; 1.1115 + distortions[numDistortions].Config.K[3] = 1.0648f; 1.1116 + distortions[numDistortions].Config.K[4] = 1.0990f; 1.1117 + distortions[numDistortions].Config.K[5] = 1.141f; 1.1118 + distortions[numDistortions].Config.K[6] = 1.192f; 1.1119 + distortions[numDistortions].Config.K[7] = 1.255f; 1.1120 + distortions[numDistortions].Config.K[8] = 1.335f; 1.1121 + distortions[numDistortions].Config.K[9] = 1.435f; 1.1122 + distortions[numDistortions].Config.K[10] = 1.56f; 1.1123 + distortions[numDistortions].MaxRadius = 1.0f; 1.1124 + */ 1.1125 + } 1.1126 + else 1.1127 + { 1.1128 + // Unknown lens. 1.1129 + // Use DK1 black lens settings, just so we can continue to run with something. 1.1130 + distortions[0].EyeRelief = 0.005f; 1.1131 + distortions[0].Config.MetersPerTanAngleAtCenter = 0.043875f; 1.1132 + distortions[0].Config.Eqn = Distortion_RecipPoly4; 1.1133 + distortions[0].Config.K[0] = 1.0f; 1.1134 + distortions[0].Config.K[1] = -0.3999f; 1.1135 + distortions[0].Config.K[2] = 0.2408f; 1.1136 + distortions[0].Config.K[3] = -0.4589f; 1.1137 + distortions[0].SampleRadius[0] = 0.2f; 1.1138 + distortions[0].SampleRadius[1] = 0.4f; 1.1139 + distortions[0].SampleRadius[2] = 0.6f; 1.1140 + 1.1141 + distortions[1] = distortions[0]; 1.1142 + distortions[1].EyeRelief = 0.010f; 1.1143 + numDistortions = 2; 1.1144 + 1.1145 + // Chromatic aberration doesn't seem to change with eye relief. 1.1146 + for ( int i = 0; i < numDistortions; i++ ) 1.1147 + { 1.1148 + // These are placeholder, they have not been tuned! 1.1149 + distortions[i].Config.ChromaticAberration[0] = 0.0f; 1.1150 + distortions[i].Config.ChromaticAberration[1] = 0.0f; 1.1151 + distortions[i].Config.ChromaticAberration[2] = 0.0f; 1.1152 + distortions[i].Config.ChromaticAberration[3] = 0.0f; 1.1153 + } 1.1154 + } 1.1155 + 1.1156 + OVR_ASSERT(numDistortions < MaxDistortions); 1.1157 + 1.1158 + DistortionDescriptor *pUpper = NULL; 1.1159 + DistortionDescriptor *pLower = NULL; 1.1160 + float lerpVal = 0.0f; 1.1161 + if (eyeReliefInMeters == 0) 1.1162 + { // Use a constant default distortion if an invalid eye-relief is supplied 1.1163 + pLower = &(distortions[defaultDistortion]); 1.1164 + pUpper = &(distortions[defaultDistortion]); 1.1165 + lerpVal = 0.0f; 1.1166 + } 1.1167 + else 1.1168 + { 1.1169 + for ( int i = 0; i < numDistortions-1; i++ ) 1.1170 + { 1.1171 + OVR_ASSERT ( distortions[i].EyeRelief < distortions[i+1].EyeRelief ); 1.1172 + if ( ( distortions[i].EyeRelief <= eyeReliefInMeters ) && ( distortions[i+1].EyeRelief > eyeReliefInMeters ) ) 1.1173 + { 1.1174 + pLower = &(distortions[i]); 1.1175 + pUpper = &(distortions[i+1]); 1.1176 + lerpVal = ( eyeReliefInMeters - pLower->EyeRelief ) / ( pUpper->EyeRelief - pLower->EyeRelief ); 1.1177 + // No break here - I want the ASSERT to check everything every time! 1.1178 + } 1.1179 + } 1.1180 + } 1.1181 + 1.1182 + if ( pUpper == NULL ) 1.1183 + { 1.1184 +#if 0 1.1185 + // Outside the range, so extrapolate rather than interpolate. 1.1186 + if ( distortions[0].EyeRelief > eyeReliefInMeters ) 1.1187 + { 1.1188 + pLower = &(distortions[0]); 1.1189 + pUpper = &(distortions[1]); 1.1190 + } 1.1191 + else 1.1192 + { 1.1193 + OVR_ASSERT ( distortions[numDistortions-1].EyeRelief <= eyeReliefInMeters ); 1.1194 + pLower = &(distortions[numDistortions-2]); 1.1195 + pUpper = &(distortions[numDistortions-1]); 1.1196 + } 1.1197 + lerpVal = ( eyeReliefInMeters - pLower->EyeRelief ) / ( pUpper->EyeRelief - pLower->EyeRelief ); 1.1198 +#else 1.1199 + // Do not extrapolate, just clamp - slightly worried about people putting in bogus settings. 1.1200 + if ( distortions[0].EyeRelief > eyeReliefInMeters ) 1.1201 + { 1.1202 + pLower = &(distortions[0]); 1.1203 + pUpper = &(distortions[0]); 1.1204 + } 1.1205 + else 1.1206 + { 1.1207 + OVR_ASSERT ( distortions[numDistortions-1].EyeRelief <= eyeReliefInMeters ); 1.1208 + pLower = &(distortions[numDistortions-1]); 1.1209 + pUpper = &(distortions[numDistortions-1]); 1.1210 + } 1.1211 + lerpVal = 0.0f; 1.1212 +#endif 1.1213 + } 1.1214 + float invLerpVal = 1.0f - lerpVal; 1.1215 + 1.1216 + pLower->Config.MaxR = pLower->MaxRadius; 1.1217 + pUpper->Config.MaxR = pUpper->MaxRadius; 1.1218 + 1.1219 + LensConfig result; 1.1220 + // Where is the edge of the lens - no point modelling further than this. 1.1221 + float maxValidRadius = invLerpVal * pLower->MaxRadius + lerpVal * pUpper->MaxRadius; 1.1222 + result.MaxR = maxValidRadius; 1.1223 + 1.1224 + switch ( distortionType ) 1.1225 + { 1.1226 + case Distortion_Poly4: 1.1227 + // Deprecated 1.1228 + OVR_ASSERT ( false ); 1.1229 + break; 1.1230 + case Distortion_RecipPoly4:{ 1.1231 + // Lerp control points and fit an equation to them. 1.1232 + float fitX[4]; 1.1233 + float fitY[4]; 1.1234 + fitX[0] = 0.0f; 1.1235 + fitY[0] = 1.0f; 1.1236 + for ( int ctrlPt = 1; ctrlPt < 4; ctrlPt ++ ) 1.1237 + { 1.1238 + // SampleRadius is not valid for Distortion_RecipPoly4 types. 1.1239 + float radiusLerp = ( invLerpVal * pLower->MaxRadius + lerpVal * pUpper->MaxRadius ) * ( (float)ctrlPt / 4.0f ); 1.1240 + float radiusLerpSq = radiusLerp * radiusLerp; 1.1241 + float fitYLower = pLower->Config.DistortionFnScaleRadiusSquared ( radiusLerpSq ); 1.1242 + float fitYUpper = pUpper->Config.DistortionFnScaleRadiusSquared ( radiusLerpSq ); 1.1243 + fitX[ctrlPt] = radiusLerpSq; 1.1244 + fitY[ctrlPt] = 1.0f / ( invLerpVal * fitYLower + lerpVal * fitYUpper ); 1.1245 + } 1.1246 + 1.1247 + result.Eqn = Distortion_RecipPoly4; 1.1248 + bool bSuccess = FitCubicPolynomial ( result.K, fitX, fitY ); 1.1249 + OVR_ASSERT ( bSuccess ); 1.1250 + OVR_UNUSED ( bSuccess ); 1.1251 + 1.1252 + // Set up the fast inverse. 1.1253 + float maxRDist = result.DistortionFn ( maxValidRadius ); 1.1254 + result.MaxInvR = maxRDist; 1.1255 + result.SetUpInverseApprox(); 1.1256 + 1.1257 + }break; 1.1258 + 1.1259 + case Distortion_CatmullRom10:{ 1.1260 + 1.1261 + // Evenly sample & lerp points on the curve. 1.1262 + const int NumSegments = LensConfig::NumCoefficients; 1.1263 + result.MaxR = maxValidRadius; 1.1264 + // Directly interpolate the K0 values 1.1265 + result.K[0] = invLerpVal * pLower->Config.K[0] + lerpVal * pUpper->Config.K[0]; 1.1266 + 1.1267 + // Sample and interpolate the distortion curves to derive K[1] ... K[n] 1.1268 + for ( int ctrlPt = 1; ctrlPt < NumSegments; ctrlPt++ ) 1.1269 + { 1.1270 + float radiusSq = ( (float)ctrlPt / (float)(NumSegments-1) ) * maxValidRadius * maxValidRadius; 1.1271 + float fitYLower = pLower->Config.DistortionFnScaleRadiusSquared ( radiusSq ); 1.1272 + float fitYUpper = pUpper->Config.DistortionFnScaleRadiusSquared ( radiusSq ); 1.1273 + float fitLerp = invLerpVal * fitYLower + lerpVal * fitYUpper; 1.1274 + result.K[ctrlPt] = fitLerp; 1.1275 + } 1.1276 + 1.1277 + result.Eqn = Distortion_CatmullRom10; 1.1278 + 1.1279 + for ( int ctrlPt = 1; ctrlPt < NumSegments; ctrlPt++ ) 1.1280 + { 1.1281 + float radiusSq = ( (float)ctrlPt / (float)(NumSegments-1) ) * maxValidRadius * maxValidRadius; 1.1282 + float val = result.DistortionFnScaleRadiusSquared ( radiusSq ); 1.1283 + OVR_ASSERT ( Alg::Abs ( val - result.K[ctrlPt] ) < 0.0001f ); 1.1284 + OVR_UNUSED1(val); // For release build. 1.1285 + } 1.1286 + 1.1287 + // Set up the fast inverse. 1.1288 + float maxRDist = result.DistortionFn ( maxValidRadius ); 1.1289 + result.MaxInvR = maxRDist; 1.1290 + result.SetUpInverseApprox(); 1.1291 + 1.1292 + }break; 1.1293 + 1.1294 + default: OVR_ASSERT ( false ); break; 1.1295 + } 1.1296 + 1.1297 + 1.1298 + // Chromatic aberration. 1.1299 + result.ChromaticAberration[0] = invLerpVal * pLower->Config.ChromaticAberration[0] + lerpVal * pUpper->Config.ChromaticAberration[0]; 1.1300 + result.ChromaticAberration[1] = invLerpVal * pLower->Config.ChromaticAberration[1] + lerpVal * pUpper->Config.ChromaticAberration[1]; 1.1301 + result.ChromaticAberration[2] = invLerpVal * pLower->Config.ChromaticAberration[2] + lerpVal * pUpper->Config.ChromaticAberration[2]; 1.1302 + result.ChromaticAberration[3] = invLerpVal * pLower->Config.ChromaticAberration[3] + lerpVal * pUpper->Config.ChromaticAberration[3]; 1.1303 + 1.1304 + // Scale. 1.1305 + result.MetersPerTanAngleAtCenter = pLower->Config.MetersPerTanAngleAtCenter * invLerpVal + 1.1306 + pUpper->Config.MetersPerTanAngleAtCenter * lerpVal; 1.1307 + /* 1.1308 + // Commented out - Causes ASSERT with no HMD plugged in 1.1309 +#ifdef OVR_BUILD_DEBUG 1.1310 + if ( distortionType == Distortion_CatmullRom10 ) 1.1311 + { 1.1312 + TestSaveLoadLensConfig ( result ); 1.1313 + } 1.1314 +#endif 1.1315 + */ 1.1316 + return result; 1.1317 +} 1.1318 + 1.1319 + 1.1320 +DistortionRenderDesc CalculateDistortionRenderDesc ( StereoEye eyeType, HmdRenderInfo const &hmd, 1.1321 + const LensConfig *pLensOverride /*= NULL */ ) 1.1322 +{ 1.1323 + // From eye relief, IPD and device characteristics, we get the distortion mapping. 1.1324 + // This distortion does the following things: 1.1325 + // 1. It undoes the distortion that happens at the edges of the lens. 1.1326 + // 2. It maps the undistorted field into "retina" space. 1.1327 + // So the input is a pixel coordinate - the physical pixel on the display itself. 1.1328 + // The output is the real-world direction of the ray from this pixel as it comes out of the lens and hits the eye. 1.1329 + // However we typically think of rays "coming from" the eye, so the direction (TanAngleX,TanAngleY,1) is the direction 1.1330 + // that the pixel appears to be in real-world space, where AngleX and AngleY are relative to the straight-ahead vector. 1.1331 + // If your renderer is a raytracer, you can use this vector directly (normalize as appropriate). 1.1332 + // However in standard rasterisers, we have rendered a 2D image and are putting it in front of the eye, 1.1333 + // so we then need a mapping from this space to the [-1,1] UV coordinate space, which depends on exactly 1.1334 + // where "in space" the app wants to put that rendertarget. 1.1335 + // Where in space, and how large this rendertarget is, is completely up to the app and/or user, 1.1336 + // though of course we can provide some useful hints. 1.1337 + 1.1338 + // TODO: Use IPD and eye relief to modify distortion (i.e. non-radial component) 1.1339 + // TODO: cope with lenses that don't produce collimated light. 1.1340 + // This means that IPD relative to the lens separation changes the light vergence, 1.1341 + // and so we actually need to change where the image is displayed. 1.1342 + 1.1343 + const HmdRenderInfo::EyeConfig &hmdEyeConfig = ( eyeType == StereoEye_Left ) ? hmd.EyeLeft : hmd.EyeRight; 1.1344 + 1.1345 + DistortionRenderDesc localDistortion; 1.1346 + localDistortion.Lens = hmdEyeConfig.Distortion; 1.1347 + 1.1348 + if ( pLensOverride != NULL ) 1.1349 + { 1.1350 + localDistortion.Lens = *pLensOverride; 1.1351 + } 1.1352 + 1.1353 + Sizef pixelsPerMeter(hmd.ResolutionInPixels.w / ( hmd.ScreenSizeInMeters.w - hmd.ScreenGapSizeInMeters ), 1.1354 + hmd.ResolutionInPixels.h / hmd.ScreenSizeInMeters.h); 1.1355 + 1.1356 + localDistortion.PixelsPerTanAngleAtCenter = (pixelsPerMeter * localDistortion.Lens.MetersPerTanAngleAtCenter).ToVector(); 1.1357 + // Same thing, scaled to [-1,1] for each eye, rather than pixels. 1.1358 + 1.1359 + localDistortion.TanEyeAngleScale = Vector2f(0.25f, 0.5f).EntrywiseMultiply( 1.1360 + (hmd.ScreenSizeInMeters / localDistortion.Lens.MetersPerTanAngleAtCenter).ToVector()); 1.1361 + 1.1362 + // <--------------left eye------------------><-ScreenGapSizeInMeters-><--------------right eye-----------------> 1.1363 + // <------------------------------------------ScreenSizeInMeters.Width-----------------------------------------> 1.1364 + // <----------------LensSeparationInMeters---------------> 1.1365 + // <--centerFromLeftInMeters-> 1.1366 + // ^ 1.1367 + // Center of lens 1.1368 + 1.1369 + // Find the lens centers in scale of [-1,+1] (NDC) in left eye. 1.1370 + float visibleWidthOfOneEye = 0.5f * ( hmd.ScreenSizeInMeters.w - hmd.ScreenGapSizeInMeters ); 1.1371 + float centerFromLeftInMeters = ( hmd.ScreenSizeInMeters.w - hmd.LensSeparationInMeters ) * 0.5f; 1.1372 + localDistortion.LensCenter.x = ( centerFromLeftInMeters / visibleWidthOfOneEye ) * 2.0f - 1.0f; 1.1373 + localDistortion.LensCenter.y = ( hmd.CenterFromTopInMeters / hmd.ScreenSizeInMeters.h ) * 2.0f - 1.0f; 1.1374 + if ( eyeType == StereoEye_Right ) 1.1375 + { 1.1376 + localDistortion.LensCenter.x = -localDistortion.LensCenter.x; 1.1377 + } 1.1378 + 1.1379 + return localDistortion; 1.1380 +} 1.1381 + 1.1382 +FovPort CalculateFovFromEyePosition ( float eyeReliefInMeters, 1.1383 + float offsetToRightInMeters, 1.1384 + float offsetDownwardsInMeters, 1.1385 + float lensDiameterInMeters, 1.1386 + float extraEyeRotationInRadians /*= 0.0f*/ ) 1.1387 +{ 1.1388 + // 2D view of things: 1.1389 + // |-| <--- offsetToRightInMeters (in this case, it is negative) 1.1390 + // |=======C=======| <--- lens surface (C=center) 1.1391 + // \ | _/ 1.1392 + // \ R _/ 1.1393 + // \ | _/ 1.1394 + // \ | _/ 1.1395 + // \|/ 1.1396 + // O <--- center of pupil 1.1397 + 1.1398 + // (technically the lens is round rather than square, so it's not correct to 1.1399 + // separate vertical and horizontal like this, but it's close enough) 1.1400 + float halfLensDiameter = lensDiameterInMeters * 0.5f; 1.1401 + FovPort fovPort; 1.1402 + fovPort.UpTan = ( halfLensDiameter + offsetDownwardsInMeters ) / eyeReliefInMeters; 1.1403 + fovPort.DownTan = ( halfLensDiameter - offsetDownwardsInMeters ) / eyeReliefInMeters; 1.1404 + fovPort.LeftTan = ( halfLensDiameter + offsetToRightInMeters ) / eyeReliefInMeters; 1.1405 + fovPort.RightTan = ( halfLensDiameter - offsetToRightInMeters ) / eyeReliefInMeters; 1.1406 + 1.1407 + if ( extraEyeRotationInRadians > 0.0f ) 1.1408 + { 1.1409 + // That's the basic looking-straight-ahead eye position relative to the lens. 1.1410 + // But if you look left, the pupil moves left as the eyeball rotates, which 1.1411 + // means you can see more to the right than this geometry suggests. 1.1412 + // So add in the bounds for the extra movement of the pupil. 1.1413 + 1.1414 + // Beyond 30 degrees does not increase FOV because the pupil starts moving backwards more than sideways. 1.1415 + extraEyeRotationInRadians = Alg::Min ( DegreeToRad ( 30.0f ), Alg::Max ( 0.0f, extraEyeRotationInRadians ) ); 1.1416 + 1.1417 + // The rotation of the eye is a bit more complex than a simple circle. The center of rotation 1.1418 + // at 13.5mm from cornea is slightly further back than the actual center of the eye. 1.1419 + // Additionally the rotation contains a small lateral component as the muscles pull the eye 1.1420 + const float eyeballCenterToPupil = 0.0135f; // center of eye rotation 1.1421 + const float eyeballLateralPull = 0.001f * (extraEyeRotationInRadians / DegreeToRad ( 30.0f)); // lateral motion as linear function 1.1422 + float extraTranslation = eyeballCenterToPupil * sinf ( extraEyeRotationInRadians ) + eyeballLateralPull; 1.1423 + float extraRelief = eyeballCenterToPupil * ( 1.0f - cosf ( extraEyeRotationInRadians ) ); 1.1424 + 1.1425 + fovPort.UpTan = Alg::Max ( fovPort.UpTan , ( halfLensDiameter + offsetDownwardsInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) ); 1.1426 + fovPort.DownTan = Alg::Max ( fovPort.DownTan , ( halfLensDiameter - offsetDownwardsInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) ); 1.1427 + fovPort.LeftTan = Alg::Max ( fovPort.LeftTan , ( halfLensDiameter + offsetToRightInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) ); 1.1428 + fovPort.RightTan = Alg::Max ( fovPort.RightTan, ( halfLensDiameter - offsetToRightInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) ); 1.1429 + } 1.1430 + 1.1431 + return fovPort; 1.1432 +} 1.1433 + 1.1434 + 1.1435 + 1.1436 +FovPort CalculateFovFromHmdInfo ( StereoEye eyeType, 1.1437 + DistortionRenderDesc const &distortion, 1.1438 + HmdRenderInfo const &hmd, 1.1439 + float extraEyeRotationInRadians /*= 0.0f*/ ) 1.1440 +{ 1.1441 + FovPort fovPort; 1.1442 + float eyeReliefInMeters; 1.1443 + float offsetToRightInMeters; 1.1444 + if ( eyeType == StereoEye_Right ) 1.1445 + { 1.1446 + eyeReliefInMeters = hmd.EyeRight.ReliefInMeters; 1.1447 + offsetToRightInMeters = hmd.EyeRight.NoseToPupilInMeters - 0.5f * hmd.LensSeparationInMeters; 1.1448 + } 1.1449 + else 1.1450 + { 1.1451 + eyeReliefInMeters = hmd.EyeLeft.ReliefInMeters; 1.1452 + offsetToRightInMeters = -(hmd.EyeLeft.NoseToPupilInMeters - 0.5f * hmd.LensSeparationInMeters); 1.1453 + } 1.1454 + 1.1455 + // Limit the eye-relief to 6 mm for FOV calculations since this just tends to spread off-screen 1.1456 + // and get clamped anyways on DK1 (but in Unity it continues to spreads and causes 1.1457 + // unnecessarily large render targets) 1.1458 + eyeReliefInMeters = Alg::Max(eyeReliefInMeters, 0.006f); 1.1459 + 1.1460 + // Central view. 1.1461 + fovPort = CalculateFovFromEyePosition ( eyeReliefInMeters, 1.1462 + offsetToRightInMeters, 1.1463 + 0.0f, 1.1464 + hmd.LensDiameterInMeters, 1.1465 + extraEyeRotationInRadians ); 1.1466 + 1.1467 + // clamp to the screen 1.1468 + fovPort = ClampToPhysicalScreenFov ( eyeType, distortion, fovPort ); 1.1469 + 1.1470 + return fovPort; 1.1471 +} 1.1472 + 1.1473 + 1.1474 + 1.1475 +FovPort GetPhysicalScreenFov ( StereoEye eyeType, DistortionRenderDesc const &distortion ) 1.1476 +{ 1.1477 + OVR_UNUSED1 ( eyeType ); 1.1478 + 1.1479 + FovPort resultFovPort; 1.1480 + 1.1481 + // Figure out the boundaries of the screen. We take the middle pixel of the screen, 1.1482 + // move to each of the four screen edges, and transform those back into TanAngle space. 1.1483 + Vector2f dmiddle = distortion.LensCenter; 1.1484 + 1.1485 + // The gotcha is that for some distortion functions, the map will "wrap around" 1.1486 + // for screen pixels that are not actually visible to the user (especially on DK1, 1.1487 + // which has a lot of invisible pixels), and map to pixels that are close to the middle. 1.1488 + // This means the edges of the screen will actually be 1.1489 + // "closer" than the visible bounds, so we'll clip too aggressively. 1.1490 + 1.1491 + // Solution - step gradually towards the boundary, noting the maximum distance. 1.1492 + struct FunctionHider 1.1493 + { 1.1494 + static FovPort FindRange ( Vector2f from, Vector2f to, int numSteps, 1.1495 + DistortionRenderDesc const &distortionL ) 1.1496 + { 1.1497 + FovPort result; 1.1498 + result.UpTan = 0.0f; 1.1499 + result.DownTan = 0.0f; 1.1500 + result.LeftTan = 0.0f; 1.1501 + result.RightTan = 0.0f; 1.1502 + 1.1503 + float stepScale = 1.0f / ( numSteps - 1 ); 1.1504 + for ( int step = 0; step < numSteps; step++ ) 1.1505 + { 1.1506 + float lerpFactor = stepScale * (float)step; 1.1507 + Vector2f sample = from + (to - from) * lerpFactor; 1.1508 + Vector2f tanEyeAngle = TransformScreenNDCToTanFovSpace ( distortionL, sample ); 1.1509 + 1.1510 + result.LeftTan = Alg::Max ( result.LeftTan, -tanEyeAngle.x ); 1.1511 + result.RightTan = Alg::Max ( result.RightTan, tanEyeAngle.x ); 1.1512 + result.UpTan = Alg::Max ( result.UpTan, -tanEyeAngle.y ); 1.1513 + result.DownTan = Alg::Max ( result.DownTan, tanEyeAngle.y ); 1.1514 + } 1.1515 + return result; 1.1516 + } 1.1517 + }; 1.1518 + 1.1519 + FovPort leftFovPort = FunctionHider::FindRange( dmiddle, Vector2f( -1.0f, dmiddle.y ), 10, distortion ); 1.1520 + FovPort rightFovPort = FunctionHider::FindRange( dmiddle, Vector2f( 1.0f, dmiddle.y ), 10, distortion ); 1.1521 + FovPort upFovPort = FunctionHider::FindRange( dmiddle, Vector2f( dmiddle.x, -1.0f ), 10, distortion ); 1.1522 + FovPort downFovPort = FunctionHider::FindRange( dmiddle, Vector2f( dmiddle.x, 1.0f ), 10, distortion ); 1.1523 + 1.1524 + resultFovPort.LeftTan = leftFovPort.LeftTan; 1.1525 + resultFovPort.RightTan = rightFovPort.RightTan; 1.1526 + resultFovPort.UpTan = upFovPort.UpTan; 1.1527 + resultFovPort.DownTan = downFovPort.DownTan; 1.1528 + 1.1529 + return resultFovPort; 1.1530 +} 1.1531 + 1.1532 +FovPort ClampToPhysicalScreenFov( StereoEye eyeType, DistortionRenderDesc const &distortion, 1.1533 + FovPort inputFovPort ) 1.1534 +{ 1.1535 + FovPort resultFovPort; 1.1536 + FovPort phsyicalFovPort = GetPhysicalScreenFov ( eyeType, distortion ); 1.1537 + resultFovPort.LeftTan = Alg::Min ( inputFovPort.LeftTan, phsyicalFovPort.LeftTan ); 1.1538 + resultFovPort.RightTan = Alg::Min ( inputFovPort.RightTan, phsyicalFovPort.RightTan ); 1.1539 + resultFovPort.UpTan = Alg::Min ( inputFovPort.UpTan, phsyicalFovPort.UpTan ); 1.1540 + resultFovPort.DownTan = Alg::Min ( inputFovPort.DownTan, phsyicalFovPort.DownTan ); 1.1541 + 1.1542 + return resultFovPort; 1.1543 +} 1.1544 + 1.1545 +Sizei CalculateIdealPixelSize ( StereoEye eyeType, DistortionRenderDesc const &distortion, 1.1546 + FovPort tanHalfFov, float pixelsPerDisplayPixel ) 1.1547 +{ 1.1548 + OVR_UNUSED(eyeType); // might be useful in the future if we do overlapping fovs 1.1549 + 1.1550 + Sizei result; 1.1551 + // 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. 1.1552 + result.w = (int)(0.5f + pixelsPerDisplayPixel * distortion.PixelsPerTanAngleAtCenter.x * ( tanHalfFov.LeftTan + tanHalfFov.RightTan ) ); 1.1553 + result.h = (int)(0.5f + pixelsPerDisplayPixel * distortion.PixelsPerTanAngleAtCenter.y * ( tanHalfFov.UpTan + tanHalfFov.DownTan ) ); 1.1554 + return result; 1.1555 +} 1.1556 + 1.1557 +Recti GetFramebufferViewport ( StereoEye eyeType, HmdRenderInfo const &hmd ) 1.1558 +{ 1.1559 + Recti result; 1.1560 + result.w = hmd.ResolutionInPixels.w/2; 1.1561 + result.h = hmd.ResolutionInPixels.h; 1.1562 + result.x = 0; 1.1563 + result.y = 0; 1.1564 + if ( eyeType == StereoEye_Right ) 1.1565 + { 1.1566 + result.x = (hmd.ResolutionInPixels.w+1)/2; // Round up, not down. 1.1567 + } 1.1568 + return result; 1.1569 +} 1.1570 + 1.1571 + 1.1572 +ScaleAndOffset2D CreateNDCScaleAndOffsetFromFov ( FovPort tanHalfFov ) 1.1573 +{ 1.1574 + float projXScale = 2.0f / ( tanHalfFov.LeftTan + tanHalfFov.RightTan ); 1.1575 + float projXOffset = ( tanHalfFov.LeftTan - tanHalfFov.RightTan ) * projXScale * 0.5f; 1.1576 + float projYScale = 2.0f / ( tanHalfFov.UpTan + tanHalfFov.DownTan ); 1.1577 + float projYOffset = ( tanHalfFov.UpTan - tanHalfFov.DownTan ) * projYScale * 0.5f; 1.1578 + 1.1579 + ScaleAndOffset2D result; 1.1580 + result.Scale = Vector2f(projXScale, projYScale); 1.1581 + result.Offset = Vector2f(projXOffset, projYOffset); 1.1582 + // Hey - why is that Y.Offset negated? 1.1583 + // It's because a projection matrix transforms from world coords with Y=up, 1.1584 + // whereas this is from NDC which is Y=down. 1.1585 + 1.1586 + return result; 1.1587 +} 1.1588 + 1.1589 + 1.1590 +ScaleAndOffset2D CreateUVScaleAndOffsetfromNDCScaleandOffset ( ScaleAndOffset2D scaleAndOffsetNDC, 1.1591 + Recti renderedViewport, 1.1592 + Sizei renderTargetSize ) 1.1593 +{ 1.1594 + // scaleAndOffsetNDC takes you to NDC space [-1,+1] within the given viewport on the rendertarget. 1.1595 + // We want a scale to instead go to actual UV coordinates you can sample with, 1.1596 + // which need [0,1] and ignore the viewport. 1.1597 + ScaleAndOffset2D result; 1.1598 + // Scale [-1,1] to [0,1] 1.1599 + result.Scale = scaleAndOffsetNDC.Scale * 0.5f; 1.1600 + result.Offset = scaleAndOffsetNDC.Offset * 0.5f + Vector2f(0.5f); 1.1601 + 1.1602 + // ...but we will have rendered to a subsection of the RT, so scale for that. 1.1603 + Vector2f scale( (float)renderedViewport.w / (float)renderTargetSize.w, 1.1604 + (float)renderedViewport.h / (float)renderTargetSize.h ); 1.1605 + Vector2f offset( (float)renderedViewport.x / (float)renderTargetSize.w, 1.1606 + (float)renderedViewport.y / (float)renderTargetSize.h ); 1.1607 + 1.1608 + result.Scale = result.Scale.EntrywiseMultiply(scale); 1.1609 + result.Offset = result.Offset.EntrywiseMultiply(scale) + offset; 1.1610 + return result; 1.1611 +} 1.1612 + 1.1613 + 1.1614 + 1.1615 +Matrix4f CreateProjection( bool rightHanded, FovPort tanHalfFov, 1.1616 + float zNear /*= 0.01f*/, float zFar /*= 10000.0f*/ ) 1.1617 +{ 1.1618 + // A projection matrix is very like a scaling from NDC, so we can start with that. 1.1619 + ScaleAndOffset2D scaleAndOffset = CreateNDCScaleAndOffsetFromFov ( tanHalfFov ); 1.1620 + 1.1621 + float handednessScale = 1.0f; 1.1622 + if ( rightHanded ) 1.1623 + { 1.1624 + handednessScale = -1.0f; 1.1625 + } 1.1626 + 1.1627 + Matrix4f projection; 1.1628 + // Produces X result, mapping clip edges to [-w,+w] 1.1629 + projection.M[0][0] = scaleAndOffset.Scale.x; 1.1630 + projection.M[0][1] = 0.0f; 1.1631 + projection.M[0][2] = handednessScale * scaleAndOffset.Offset.x; 1.1632 + projection.M[0][3] = 0.0f; 1.1633 + 1.1634 + // Produces Y result, mapping clip edges to [-w,+w] 1.1635 + // Hey - why is that YOffset negated? 1.1636 + // It's because a projection matrix transforms from world coords with Y=up, 1.1637 + // whereas this is derived from an NDC scaling, which is Y=down. 1.1638 + projection.M[1][0] = 0.0f; 1.1639 + projection.M[1][1] = scaleAndOffset.Scale.y; 1.1640 + projection.M[1][2] = handednessScale * -scaleAndOffset.Offset.y; 1.1641 + projection.M[1][3] = 0.0f; 1.1642 + 1.1643 + // Produces Z-buffer result - app needs to fill this in with whatever Z range it wants. 1.1644 + // We'll just use some defaults for now. 1.1645 + projection.M[2][0] = 0.0f; 1.1646 + projection.M[2][1] = 0.0f; 1.1647 + projection.M[2][2] = -handednessScale * zFar / (zNear - zFar); 1.1648 + projection.M[2][3] = (zFar * zNear) / (zNear - zFar); 1.1649 + 1.1650 + // Produces W result (= Z in) 1.1651 + projection.M[3][0] = 0.0f; 1.1652 + projection.M[3][1] = 0.0f; 1.1653 + projection.M[3][2] = handednessScale; 1.1654 + projection.M[3][3] = 0.0f; 1.1655 + 1.1656 + return projection; 1.1657 +} 1.1658 + 1.1659 + 1.1660 +Matrix4f CreateOrthoSubProjection ( bool rightHanded, StereoEye eyeType, 1.1661 + float tanHalfFovX, float tanHalfFovY, 1.1662 + float unitsX, float unitsY, 1.1663 + float distanceFromCamera, float interpupillaryDistance, 1.1664 + Matrix4f const &projection, 1.1665 + float zNear /*= 0.0f*/, float zFar /*= 0.0f*/ ) 1.1666 +{ 1.1667 + OVR_UNUSED1 ( rightHanded ); 1.1668 + 1.1669 + float orthoHorizontalOffset = interpupillaryDistance * 0.5f / distanceFromCamera; 1.1670 + switch ( eyeType ) 1.1671 + { 1.1672 + case StereoEye_Center: 1.1673 + orthoHorizontalOffset = 0.0f; 1.1674 + break; 1.1675 + case StereoEye_Left: 1.1676 + break; 1.1677 + case StereoEye_Right: 1.1678 + orthoHorizontalOffset = -orthoHorizontalOffset; 1.1679 + break; 1.1680 + default: OVR_ASSERT ( false ); break; 1.1681 + } 1.1682 + 1.1683 + // Current projection maps real-world vector (x,y,1) to the RT. 1.1684 + // We want to find the projection that maps the range [-FovPixels/2,FovPixels/2] to 1.1685 + // the physical [-orthoHalfFov,orthoHalfFov] 1.1686 + // Note moving the offset from M[0][2]+M[1][2] to M[0][3]+M[1][3] - this means 1.1687 + // we don't have to feed in Z=1 all the time. 1.1688 + // The horizontal offset math is a little hinky because the destination is 1.1689 + // actually [-orthoHalfFov+orthoHorizontalOffset,orthoHalfFov+orthoHorizontalOffset] 1.1690 + // So we need to first map [-FovPixels/2,FovPixels/2] to 1.1691 + // [-orthoHalfFov+orthoHorizontalOffset,orthoHalfFov+orthoHorizontalOffset]: 1.1692 + // x1 = x0 * orthoHalfFov/(FovPixels/2) + orthoHorizontalOffset; 1.1693 + // = x0 * 2*orthoHalfFov/FovPixels + orthoHorizontalOffset; 1.1694 + // But then we need the sam mapping as the existing projection matrix, i.e. 1.1695 + // x2 = x1 * Projection.M[0][0] + Projection.M[0][2]; 1.1696 + // = x0 * (2*orthoHalfFov/FovPixels + orthoHorizontalOffset) * Projection.M[0][0] + Projection.M[0][2]; 1.1697 + // = x0 * Projection.M[0][0]*2*orthoHalfFov/FovPixels + 1.1698 + // orthoHorizontalOffset*Projection.M[0][0] + Projection.M[0][2]; 1.1699 + // So in the new projection matrix we need to scale by Projection.M[0][0]*2*orthoHalfFov/FovPixels and 1.1700 + // offset by orthoHorizontalOffset*Projection.M[0][0] + Projection.M[0][2]. 1.1701 + 1.1702 + float orthoScaleX = 2.0f * tanHalfFovX / unitsX; 1.1703 + float orthoScaleY = 2.0f * tanHalfFovY / unitsY; 1.1704 + Matrix4f ortho; 1.1705 + ortho.M[0][0] = projection.M[0][0] * orthoScaleX; 1.1706 + ortho.M[0][1] = 0.0f; 1.1707 + ortho.M[0][2] = 0.0f; 1.1708 + ortho.M[0][3] = -projection.M[0][2] + ( orthoHorizontalOffset * projection.M[0][0] ); 1.1709 + 1.1710 + ortho.M[1][0] = 0.0f; 1.1711 + ortho.M[1][1] = -projection.M[1][1] * orthoScaleY; // Note sign flip (text rendering uses Y=down). 1.1712 + ortho.M[1][2] = 0.0f; 1.1713 + ortho.M[1][3] = -projection.M[1][2]; 1.1714 + 1.1715 + if ( fabsf ( zNear - zFar ) < 0.001f ) 1.1716 + { 1.1717 + ortho.M[2][0] = 0.0f; 1.1718 + ortho.M[2][1] = 0.0f; 1.1719 + ortho.M[2][2] = 0.0f; 1.1720 + ortho.M[2][3] = zFar; 1.1721 + } 1.1722 + else 1.1723 + { 1.1724 + ortho.M[2][0] = 0.0f; 1.1725 + ortho.M[2][1] = 0.0f; 1.1726 + ortho.M[2][2] = zFar / (zNear - zFar); 1.1727 + ortho.M[2][3] = (zFar * zNear) / (zNear - zFar); 1.1728 + } 1.1729 + 1.1730 + // No perspective correction for ortho. 1.1731 + ortho.M[3][0] = 0.0f; 1.1732 + ortho.M[3][1] = 0.0f; 1.1733 + ortho.M[3][2] = 0.0f; 1.1734 + ortho.M[3][3] = 1.0f; 1.1735 + 1.1736 + return ortho; 1.1737 +} 1.1738 + 1.1739 + 1.1740 +//----------------------------------------------------------------------------------- 1.1741 +// A set of "forward-mapping" functions, mapping from framebuffer space to real-world and/or texture space. 1.1742 + 1.1743 +// This mimics the first half of the distortion shader's function. 1.1744 +Vector2f TransformScreenNDCToTanFovSpace( DistortionRenderDesc const &distortion, 1.1745 + const Vector2f &framebufferNDC ) 1.1746 +{ 1.1747 + // Scale to TanHalfFov space, but still distorted. 1.1748 + Vector2f tanEyeAngleDistorted; 1.1749 + tanEyeAngleDistorted.x = ( framebufferNDC.x - distortion.LensCenter.x ) * distortion.TanEyeAngleScale.x; 1.1750 + tanEyeAngleDistorted.y = ( framebufferNDC.y - distortion.LensCenter.y ) * distortion.TanEyeAngleScale.y; 1.1751 + // Distort. 1.1752 + float radiusSquared = ( tanEyeAngleDistorted.x * tanEyeAngleDistorted.x ) 1.1753 + + ( tanEyeAngleDistorted.y * tanEyeAngleDistorted.y ); 1.1754 + float distortionScale = distortion.Lens.DistortionFnScaleRadiusSquared ( radiusSquared ); 1.1755 + Vector2f tanEyeAngle; 1.1756 + tanEyeAngle.x = tanEyeAngleDistorted.x * distortionScale; 1.1757 + tanEyeAngle.y = tanEyeAngleDistorted.y * distortionScale; 1.1758 + 1.1759 + return tanEyeAngle; 1.1760 +} 1.1761 + 1.1762 +// Same, with chromatic aberration correction. 1.1763 +void TransformScreenNDCToTanFovSpaceChroma ( Vector2f *resultR, Vector2f *resultG, Vector2f *resultB, 1.1764 + DistortionRenderDesc const &distortion, 1.1765 + const Vector2f &framebufferNDC ) 1.1766 +{ 1.1767 + // Scale to TanHalfFov space, but still distorted. 1.1768 + Vector2f tanEyeAngleDistorted; 1.1769 + tanEyeAngleDistorted.x = ( framebufferNDC.x - distortion.LensCenter.x ) * distortion.TanEyeAngleScale.x; 1.1770 + tanEyeAngleDistorted.y = ( framebufferNDC.y - distortion.LensCenter.y ) * distortion.TanEyeAngleScale.y; 1.1771 + // Distort. 1.1772 + float radiusSquared = ( tanEyeAngleDistorted.x * tanEyeAngleDistorted.x ) 1.1773 + + ( tanEyeAngleDistorted.y * tanEyeAngleDistorted.y ); 1.1774 + Vector3f distortionScales = distortion.Lens.DistortionFnScaleRadiusSquaredChroma ( radiusSquared ); 1.1775 + *resultR = tanEyeAngleDistorted * distortionScales.x; 1.1776 + *resultG = tanEyeAngleDistorted * distortionScales.y; 1.1777 + *resultB = tanEyeAngleDistorted * distortionScales.z; 1.1778 +} 1.1779 + 1.1780 +// This mimics the second half of the distortion shader's function. 1.1781 +Vector2f TransformTanFovSpaceToRendertargetTexUV( ScaleAndOffset2D const &eyeToSourceUV, 1.1782 + Vector2f const &tanEyeAngle ) 1.1783 +{ 1.1784 + Vector2f textureUV; 1.1785 + textureUV.x = tanEyeAngle.x * eyeToSourceUV.Scale.x + eyeToSourceUV.Offset.x; 1.1786 + textureUV.y = tanEyeAngle.y * eyeToSourceUV.Scale.y + eyeToSourceUV.Offset.y; 1.1787 + return textureUV; 1.1788 +} 1.1789 + 1.1790 +Vector2f TransformTanFovSpaceToRendertargetNDC( ScaleAndOffset2D const &eyeToSourceNDC, 1.1791 + Vector2f const &tanEyeAngle ) 1.1792 +{ 1.1793 + Vector2f textureNDC; 1.1794 + textureNDC.x = tanEyeAngle.x * eyeToSourceNDC.Scale.x + eyeToSourceNDC.Offset.x; 1.1795 + textureNDC.y = tanEyeAngle.y * eyeToSourceNDC.Scale.y + eyeToSourceNDC.Offset.y; 1.1796 + return textureNDC; 1.1797 +} 1.1798 + 1.1799 +Vector2f TransformScreenPixelToScreenNDC( Recti const &distortionViewport, 1.1800 + Vector2f const &pixel ) 1.1801 +{ 1.1802 + // Move to [-1,1] NDC coords. 1.1803 + Vector2f framebufferNDC; 1.1804 + framebufferNDC.x = -1.0f + 2.0f * ( ( pixel.x - (float)distortionViewport.x ) / (float)distortionViewport.w ); 1.1805 + framebufferNDC.y = -1.0f + 2.0f * ( ( pixel.y - (float)distortionViewport.y ) / (float)distortionViewport.h ); 1.1806 + return framebufferNDC; 1.1807 +} 1.1808 + 1.1809 +Vector2f TransformScreenPixelToTanFovSpace( Recti const &distortionViewport, 1.1810 + DistortionRenderDesc const &distortion, 1.1811 + Vector2f const &pixel ) 1.1812 +{ 1.1813 + return TransformScreenNDCToTanFovSpace( distortion, 1.1814 + TransformScreenPixelToScreenNDC( distortionViewport, pixel ) ); 1.1815 +} 1.1816 + 1.1817 +Vector2f TransformScreenNDCToRendertargetTexUV( DistortionRenderDesc const &distortion, 1.1818 + StereoEyeParams const &eyeParams, 1.1819 + Vector2f const &pixel ) 1.1820 +{ 1.1821 + return TransformTanFovSpaceToRendertargetTexUV ( eyeParams, 1.1822 + TransformScreenNDCToTanFovSpace ( distortion, pixel ) ); 1.1823 +} 1.1824 + 1.1825 +Vector2f TransformScreenPixelToRendertargetTexUV( Recti const &distortionViewport, 1.1826 + DistortionRenderDesc const &distortion, 1.1827 + StereoEyeParams const &eyeParams, 1.1828 + Vector2f const &pixel ) 1.1829 +{ 1.1830 + return TransformTanFovSpaceToRendertargetTexUV ( eyeParams, 1.1831 + TransformScreenPixelToTanFovSpace ( distortionViewport, distortion, pixel ) ); 1.1832 +} 1.1833 + 1.1834 + 1.1835 +//----------------------------------------------------------------------------------- 1.1836 +// A set of "reverse-mapping" functions, mapping from real-world and/or texture space back to the framebuffer. 1.1837 + 1.1838 +Vector2f TransformTanFovSpaceToScreenNDC( DistortionRenderDesc const &distortion, 1.1839 + const Vector2f &tanEyeAngle, bool usePolyApprox /*= false*/ ) 1.1840 +{ 1.1841 + float tanEyeAngleRadius = tanEyeAngle.Length(); 1.1842 + float tanEyeAngleDistortedRadius = distortion.Lens.DistortionFnInverseApprox ( tanEyeAngleRadius ); 1.1843 + if ( !usePolyApprox ) 1.1844 + { 1.1845 + tanEyeAngleDistortedRadius = distortion.Lens.DistortionFnInverse ( tanEyeAngleRadius ); 1.1846 + } 1.1847 + Vector2f tanEyeAngleDistorted = tanEyeAngle; 1.1848 + if ( tanEyeAngleRadius > 0.0f ) 1.1849 + { 1.1850 + tanEyeAngleDistorted = tanEyeAngle * ( tanEyeAngleDistortedRadius / tanEyeAngleRadius ); 1.1851 + } 1.1852 + 1.1853 + Vector2f framebufferNDC; 1.1854 + framebufferNDC.x = ( tanEyeAngleDistorted.x / distortion.TanEyeAngleScale.x ) + distortion.LensCenter.x; 1.1855 + framebufferNDC.y = ( tanEyeAngleDistorted.y / distortion.TanEyeAngleScale.y ) + distortion.LensCenter.y; 1.1856 + 1.1857 + return framebufferNDC; 1.1858 +} 1.1859 + 1.1860 +Vector2f TransformRendertargetNDCToTanFovSpace( const ScaleAndOffset2D &eyeToSourceNDC, 1.1861 + const Vector2f &textureNDC ) 1.1862 +{ 1.1863 + Vector2f tanEyeAngle = (textureNDC - eyeToSourceNDC.Offset) / eyeToSourceNDC.Scale; 1.1864 + return tanEyeAngle; 1.1865 +} 1.1866 + 1.1867 + 1.1868 + 1.1869 +} //namespace OVR 1.1870 + 1.1871 +//Just want to make a copy disentangled from all these namespaces! 1.1872 +float ExtEvalCatmullRom10Spline ( float const *K, float scaledVal ) 1.1873 +{ 1.1874 + return(OVR::EvalCatmullRom10Spline ( K, scaledVal )); 1.1875 +} 1.1876 + 1.1877 +