ovr_sdk

view 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 source
1 /************************************************************************************
3 Filename : OVR_Stereo.cpp
4 Content : Stereo rendering functions
5 Created : November 30, 2013
6 Authors : Tom Fosyth
8 Copyright : Copyright 2014 Oculus VR, LLC All Rights reserved.
10 Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License");
11 you may not use the Oculus VR Rift SDK except in compliance with the License,
12 which is provided at the time of installation or download, or which
13 otherwise accompanies this software in either electronic or hard copy form.
15 You may obtain a copy of the License at
17 http://www.oculusvr.com/licenses/LICENSE-3.2
19 Unless required by applicable law or agreed to in writing, the Oculus VR SDK
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
25 *************************************************************************************/
27 #include "OVR_Stereo.h"
28 #include "OVR_Profile.h"
29 #include "Kernel/OVR_Log.h"
30 #include "Kernel/OVR_Alg.h"
32 //To allow custom distortion to be introduced to CatMulSpline.
33 float (*CustomDistortion)(float) = NULL;
34 float (*CustomDistortionInv)(float) = NULL;
37 namespace OVR {
40 using namespace Alg;
42 //-----------------------------------------------------------------------------------
44 // Inputs are 4 points (pFitX[0],pFitY[0]) through (pFitX[3],pFitY[3])
45 // Result is four coefficients in pResults[0] through pResults[3] such that
46 // y = pResult[0] + x * ( pResult[1] + x * ( pResult[2] + x * ( pResult[3] ) ) );
47 // passes through all four input points.
48 // Return is true if it succeeded, false if it failed (because two control points
49 // have the same pFitX value).
50 bool FitCubicPolynomial ( float *pResult, const float *pFitX, const float *pFitY )
51 {
52 float d0 = ( ( pFitX[0]-pFitX[1] ) * ( pFitX[0]-pFitX[2] ) * ( pFitX[0]-pFitX[3] ) );
53 float d1 = ( ( pFitX[1]-pFitX[2] ) * ( pFitX[1]-pFitX[3] ) * ( pFitX[1]-pFitX[0] ) );
54 float d2 = ( ( pFitX[2]-pFitX[3] ) * ( pFitX[2]-pFitX[0] ) * ( pFitX[2]-pFitX[1] ) );
55 float d3 = ( ( pFitX[3]-pFitX[0] ) * ( pFitX[3]-pFitX[1] ) * ( pFitX[3]-pFitX[2] ) );
57 if ( ( d0 == 0.0f ) || ( d1 == 0.0f ) || ( d2 == 0.0f ) || ( d3 == 0.0f ) )
58 {
59 return false;
60 }
62 float f0 = pFitY[0] / d0;
63 float f1 = pFitY[1] / d1;
64 float f2 = pFitY[2] / d2;
65 float f3 = pFitY[3] / d3;
67 pResult[0] = -( f0*pFitX[1]*pFitX[2]*pFitX[3]
68 + f1*pFitX[0]*pFitX[2]*pFitX[3]
69 + f2*pFitX[0]*pFitX[1]*pFitX[3]
70 + f3*pFitX[0]*pFitX[1]*pFitX[2] );
71 pResult[1] = f0*(pFitX[1]*pFitX[2] + pFitX[2]*pFitX[3] + pFitX[3]*pFitX[1])
72 + f1*(pFitX[0]*pFitX[2] + pFitX[2]*pFitX[3] + pFitX[3]*pFitX[0])
73 + f2*(pFitX[0]*pFitX[1] + pFitX[1]*pFitX[3] + pFitX[3]*pFitX[0])
74 + f3*(pFitX[0]*pFitX[1] + pFitX[1]*pFitX[2] + pFitX[2]*pFitX[0]);
75 pResult[2] = -( f0*(pFitX[1]+pFitX[2]+pFitX[3])
76 + f1*(pFitX[0]+pFitX[2]+pFitX[3])
77 + f2*(pFitX[0]+pFitX[1]+pFitX[3])
78 + f3*(pFitX[0]+pFitX[1]+pFitX[2]) );
79 pResult[3] = f0 + f1 + f2 + f3;
81 return true;
82 }
84 #define TPH_SPLINE_STATISTICS 0
85 #if TPH_SPLINE_STATISTICS
86 static float max_scaledVal = 0;
87 static float average_total_out_of_range = 0;
88 static float average_out_of_range;
89 static int num_total = 0;
90 static int num_out_of_range = 0;
91 static int num_out_of_range_over_1 = 0;
92 static int num_out_of_range_over_2 = 0;
93 static int num_out_of_range_over_3 = 0;
94 static float percent_out_of_range;
95 #endif
97 float EvalCatmullRom10Spline ( float const *K, float scaledVal )
98 {
99 int const NumSegments = LensConfig::NumCoefficients;
101 #if TPH_SPLINE_STATISTICS
102 //Value should be in range of 0 to (NumSegments-1) (typically 10) if spline is valid. Right?
103 if (scaledVal > (NumSegments-1))
104 {
105 num_out_of_range++;
106 average_total_out_of_range+=scaledVal;
107 average_out_of_range = average_total_out_of_range / ((float) num_out_of_range);
108 percent_out_of_range = 100.0f*(num_out_of_range)/num_total;
109 }
110 if (scaledVal > (NumSegments-1+1)) num_out_of_range_over_1++;
111 if (scaledVal > (NumSegments-1+2)) num_out_of_range_over_2++;
112 if (scaledVal > (NumSegments-1+3)) num_out_of_range_over_3++;
113 num_total++;
114 if (scaledVal > max_scaledVal)
115 {
116 max_scaledVal = scaledVal;
117 max_scaledVal = scaledVal;
118 }
119 #endif
121 float scaledValFloor = floorf ( scaledVal );
122 scaledValFloor = Alg::Max ( 0.0f, Alg::Min ( (float)(NumSegments-1), scaledValFloor ) );
123 float t = scaledVal - scaledValFloor;
124 int k = (int)scaledValFloor;
126 float p0, p1;
127 float m0, m1;
128 switch ( k )
129 {
130 case 0:
131 // Curve starts at 1.0 with gradient K[1]-K[0]
132 p0 = 1.0f;
133 m0 = ( K[1] - K[0] ); // general case would have been (K[1]-K[-1])/2
134 p1 = K[1];
135 m1 = 0.5f * ( K[2] - K[0] );
136 break;
137 default:
138 // General case
139 p0 = K[k ];
140 m0 = 0.5f * ( K[k+1] - K[k-1] );
141 p1 = K[k+1];
142 m1 = 0.5f * ( K[k+2] - K[k ] );
143 break;
144 case NumSegments-2:
145 // Last tangent is just the slope of the last two points.
146 p0 = K[NumSegments-2];
147 m0 = 0.5f * ( K[NumSegments-1] - K[NumSegments-2] );
148 p1 = K[NumSegments-1];
149 m1 = K[NumSegments-1] - K[NumSegments-2];
150 break;
151 case NumSegments-1:
152 // Beyond the last segment it's just a straight line
153 p0 = K[NumSegments-1];
154 m0 = K[NumSegments-1] - K[NumSegments-2];
155 p1 = p0 + m0;
156 m1 = m0;
157 break;
158 }
160 float omt = 1.0f - t;
161 float res = ( p0 * ( 1.0f + 2.0f * t ) + m0 * t ) * omt * omt
162 + ( p1 * ( 1.0f + 2.0f * omt ) - m1 * omt ) * t * t;
164 return res;
165 }
170 // Converts a Profile eyecup string into an eyecup enumeration
171 void SetEyeCup(HmdRenderInfo* renderInfo, const char* cup)
172 {
173 if (OVR_strcmp(cup, "A") == 0)
174 renderInfo->EyeCups = EyeCup_DK1A;
175 else if (OVR_strcmp(cup, "B") == 0)
176 renderInfo->EyeCups = EyeCup_DK1B;
177 else if (OVR_strcmp(cup, "C") == 0)
178 renderInfo->EyeCups = EyeCup_DK1C;
179 else if (OVR_strcmp(cup, "Orange A") == 0)
180 renderInfo->EyeCups = EyeCup_OrangeA;
181 else if (OVR_strcmp(cup, "Red A") == 0)
182 renderInfo->EyeCups = EyeCup_RedA;
183 else if (OVR_strcmp(cup, "Pink A") == 0)
184 renderInfo->EyeCups = EyeCup_PinkA;
185 else if (OVR_strcmp(cup, "Blue A") == 0)
186 renderInfo->EyeCups = EyeCup_BlueA;
187 else
188 renderInfo->EyeCups = EyeCup_DK1A;
189 }
193 //-----------------------------------------------------------------------------------
196 // The result is a scaling applied to the distance.
197 float LensConfig::DistortionFnScaleRadiusSquared (float rsq) const
198 {
199 float scale = 1.0f;
200 switch ( Eqn )
201 {
202 case Distortion_Poly4:
203 // This version is deprecated! Prefer one of the other two.
204 scale = ( K[0] + rsq * ( K[1] + rsq * ( K[2] + rsq * K[3] ) ) );
205 break;
206 case Distortion_RecipPoly4:
207 scale = 1.0f / ( K[0] + rsq * ( K[1] + rsq * ( K[2] + rsq * K[3] ) ) );
208 break;
209 case Distortion_CatmullRom10:{
210 // A Catmull-Rom spline through the values 1.0, K[1], K[2] ... K[10]
211 // evenly spaced in R^2 from 0.0 to MaxR^2
212 // K[0] controls the slope at radius=0.0, rather than the actual value.
213 const int NumSegments = LensConfig::NumCoefficients;
214 OVR_ASSERT ( NumSegments <= NumCoefficients );
215 float scaledRsq = (float)(NumSegments-1) * rsq / ( MaxR * MaxR );
216 scale = EvalCatmullRom10Spline ( K, scaledRsq );
219 //Intercept, and overrule if needed
220 if (CustomDistortion)
221 {
222 scale = CustomDistortion(rsq);
223 }
225 }break;
226 default:
227 OVR_ASSERT ( false );
228 break;
229 }
230 return scale;
231 }
233 // x,y,z components map to r,g,b
234 Vector3f LensConfig::DistortionFnScaleRadiusSquaredChroma (float rsq) const
235 {
236 float scale = DistortionFnScaleRadiusSquared ( rsq );
237 Vector3f scaleRGB;
238 scaleRGB.x = scale * ( 1.0f + ChromaticAberration[0] + rsq * ChromaticAberration[1] ); // Red
239 scaleRGB.y = scale; // Green
240 scaleRGB.z = scale * ( 1.0f + ChromaticAberration[2] + rsq * ChromaticAberration[3] ); // Blue
241 return scaleRGB;
242 }
244 // DistortionFnInverse computes the inverse of the distortion function on an argument.
245 float LensConfig::DistortionFnInverse(float r) const
246 {
247 OVR_ASSERT((r <= 20.0f));
249 float s, d;
250 float delta = r * 0.25f;
252 // Better to start guessing too low & take longer to converge than too high
253 // and hit singularities. Empirically, r * 0.5f is too high in some cases.
254 s = r * 0.25f;
255 d = fabs(r - DistortionFn(s));
257 for (int i = 0; i < 20; i++)
258 {
259 float sUp = s + delta;
260 float sDown = s - delta;
261 float dUp = fabs(r - DistortionFn(sUp));
262 float dDown = fabs(r - DistortionFn(sDown));
264 if (dUp < d)
265 {
266 s = sUp;
267 d = dUp;
268 }
269 else if (dDown < d)
270 {
271 s = sDown;
272 d = dDown;
273 }
274 else
275 {
276 delta *= 0.5f;
277 }
278 }
280 return s;
281 }
285 float LensConfig::DistortionFnInverseApprox(float r) const
286 {
287 float rsq = r * r;
288 float scale = 1.0f;
289 switch ( Eqn )
290 {
291 case Distortion_Poly4:
292 // Deprecated
293 OVR_ASSERT ( false );
294 break;
295 case Distortion_RecipPoly4:
296 scale = 1.0f / ( InvK[0] + rsq * ( InvK[1] + rsq * ( InvK[2] + rsq * InvK[3] ) ) );
297 break;
298 case Distortion_CatmullRom10:{
299 // A Catmull-Rom spline through the values 1.0, K[1], K[2] ... K[9]
300 // evenly spaced in R^2 from 0.0 to MaxR^2
301 // K[0] controls the slope at radius=0.0, rather than the actual value.
302 const int NumSegments = LensConfig::NumCoefficients;
303 OVR_ASSERT ( NumSegments <= NumCoefficients );
304 float scaledRsq = (float)(NumSegments-1) * rsq / ( MaxInvR * MaxInvR );
305 scale = EvalCatmullRom10Spline ( InvK, scaledRsq );
307 //Intercept, and overrule if needed
308 if (CustomDistortionInv)
309 {
310 scale = CustomDistortionInv(rsq);
311 }
313 }break;
314 default:
315 OVR_ASSERT ( false );
316 break;
317 }
318 return r * scale;
319 }
321 void LensConfig::SetUpInverseApprox()
322 {
323 float maxR = MaxInvR;
325 switch ( Eqn )
326 {
327 case Distortion_Poly4:
328 // Deprecated
329 OVR_ASSERT ( false );
330 break;
331 case Distortion_RecipPoly4:{
333 float sampleR[4];
334 float sampleRSq[4];
335 float sampleInv[4];
336 float sampleFit[4];
338 // Found heuristically...
339 sampleR[0] = 0.0f;
340 sampleR[1] = maxR * 0.4f;
341 sampleR[2] = maxR * 0.8f;
342 sampleR[3] = maxR * 1.5f;
343 for ( int i = 0; i < 4; i++ )
344 {
345 sampleRSq[i] = sampleR[i] * sampleR[i];
346 sampleInv[i] = DistortionFnInverse ( sampleR[i] );
347 sampleFit[i] = sampleR[i] / sampleInv[i];
348 }
349 sampleFit[0] = 1.0f;
350 FitCubicPolynomial ( InvK, sampleRSq, sampleFit );
352 #if 0
353 // Should be a nearly exact match on the chosen points.
354 OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[0] ) - DistortionFnInverseApprox ( sampleR[0] ) ) / maxR < 0.0001f );
355 OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[1] ) - DistortionFnInverseApprox ( sampleR[1] ) ) / maxR < 0.0001f );
356 OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[2] ) - DistortionFnInverseApprox ( sampleR[2] ) ) / maxR < 0.0001f );
357 OVR_ASSERT ( fabs ( DistortionFnInverse ( sampleR[3] ) - DistortionFnInverseApprox ( sampleR[3] ) ) / maxR < 0.0001f );
358 // Should be a decent match on the rest of the range.
359 const int maxCheck = 20;
360 for ( int i = 0; i < maxCheck; i++ )
361 {
362 float checkR = (float)i * maxR / (float)maxCheck;
363 float realInv = DistortionFnInverse ( checkR );
364 float testInv = DistortionFnInverseApprox ( checkR );
365 float error = fabsf ( realInv - testInv ) / maxR;
366 OVR_ASSERT ( error < 0.1f );
367 }
368 #endif
370 }break;
371 case Distortion_CatmullRom10:{
373 const int NumSegments = LensConfig::NumCoefficients;
374 OVR_ASSERT ( NumSegments <= NumCoefficients );
375 for ( int i = 1; i < NumSegments; i++ )
376 {
377 float scaledRsq = (float)i;
378 float rsq = scaledRsq * MaxInvR * MaxInvR / (float)( NumSegments - 1);
379 float r = sqrtf ( rsq );
380 float inv = DistortionFnInverse ( r );
381 InvK[i] = inv / r;
382 InvK[0] = 1.0f; // TODO: fix this.
383 }
385 #if 0
386 const int maxCheck = 20;
387 for ( int i = 0; i <= maxCheck; i++ )
388 {
389 float checkR = (float)i * MaxInvR / (float)maxCheck;
390 float realInv = DistortionFnInverse ( checkR );
391 float testInv = DistortionFnInverseApprox ( checkR );
392 float error = fabsf ( realInv - testInv ) / MaxR;
393 OVR_ASSERT ( error < 0.01f );
394 }
395 #endif
397 }break;
399 default:
400 break;
401 }
402 }
405 void LensConfig::SetToIdentity()
406 {
407 for ( int i = 0; i < NumCoefficients; i++ )
408 {
409 K[i] = 0.0f;
410 InvK[i] = 0.0f;
411 }
412 Eqn = Distortion_RecipPoly4;
413 K[0] = 1.0f;
414 InvK[0] = 1.0f;
415 MaxR = 1.0f;
416 MaxInvR = 1.0f;
417 ChromaticAberration[0] = 0.0f;
418 ChromaticAberration[1] = 0.0f;
419 ChromaticAberration[2] = 0.0f;
420 ChromaticAberration[3] = 0.0f;
421 MetersPerTanAngleAtCenter = 0.05f;
422 }
425 enum LensConfigStoredVersion
426 {
427 LCSV_CatmullRom10Version1 = 1
428 };
430 // DO NOT CHANGE THESE ONCE THEY HAVE BEEN BAKED INTO FIRMWARE.
431 // If something needs to change, add a new one!
432 struct LensConfigStored_CatmullRom10Version1
433 {
434 // All these items must be fixed-length integers - no "float", no "int", etc.
435 uint16_t VersionNumber; // Must be LCSV_CatmullRom10Version1
437 uint16_t K[11];
438 uint16_t MaxR;
439 uint16_t MetersPerTanAngleAtCenter;
440 uint16_t ChromaticAberration[4];
441 // InvK and MaxInvR are calculated on load.
442 };
444 uint16_t EncodeFixedPointUInt16 ( float val, uint16_t zeroVal, int fractionalBits )
445 {
446 OVR_ASSERT ( ( fractionalBits >= 0 ) && ( fractionalBits < 31 ) );
447 float valWhole = val * (float)( 1 << fractionalBits );
448 valWhole += (float)zeroVal + 0.5f;
449 valWhole = floorf ( valWhole );
450 OVR_ASSERT ( ( valWhole >= 0.0f ) && ( valWhole < (float)( 1 << 16 ) ) );
451 return (uint16_t)valWhole;
452 }
454 float DecodeFixedPointUInt16 ( uint16_t val, uint16_t zeroVal, int fractionalBits )
455 {
456 OVR_ASSERT ( ( fractionalBits >= 0 ) && ( fractionalBits < 31 ) );
457 float valFloat = (float)val;
458 valFloat -= (float)zeroVal;
459 valFloat *= 1.0f / (float)( 1 << fractionalBits );
460 return valFloat;
461 }
464 // Returns true on success.
465 bool LoadLensConfig ( LensConfig *presult, uint8_t const *pbuffer, int bufferSizeInBytes )
466 {
467 if ( bufferSizeInBytes < 2 )
468 {
469 // Can't even tell the version number!
470 return false;
471 }
472 uint16_t version = DecodeUInt16 ( pbuffer + 0 );
473 switch ( version )
474 {
475 case LCSV_CatmullRom10Version1:
476 {
477 if ( bufferSizeInBytes < (int)sizeof(LensConfigStored_CatmullRom10Version1) )
478 {
479 return false;
480 }
481 LensConfigStored_CatmullRom10Version1 lcs;
482 lcs.VersionNumber = DecodeUInt16 ( pbuffer + 0 );
483 for ( int i = 0; i < 11; i++ )
484 {
485 lcs.K[i] = DecodeUInt16 ( pbuffer + 2 + 2*i );
486 }
487 lcs.MaxR = DecodeUInt16 ( pbuffer + 24 );
488 lcs.MetersPerTanAngleAtCenter = DecodeUInt16 ( pbuffer + 26 );
489 for ( int i = 0; i < 4; i++ )
490 {
491 lcs.ChromaticAberration[i] = DecodeUInt16 ( pbuffer + 28 + 2*i );
492 }
493 OVR_COMPILER_ASSERT ( sizeof(lcs) == 36 );
495 // Convert to the real thing.
496 LensConfig result;
497 result.Eqn = Distortion_CatmullRom10;
498 for ( int i = 0; i < 11; i++ )
499 {
500 // K[] are mostly 1.something. They may get significantly bigger, but they never hit 0.0.
501 result.K[i] = DecodeFixedPointUInt16 ( lcs.K[i], 0, 14 );
502 }
503 // MaxR is tan(angle), so always >0, typically just over 1.0 (45 degrees half-fov),
504 // but may get arbitrarily high. tan(76)=4 is a very reasonable limit!
505 result.MaxR = DecodeFixedPointUInt16 ( lcs.MaxR, 0, 14 );
506 // MetersPerTanAngleAtCenter is also known as focal length!
507 // Typically around 0.04 for our current screens, minimum of 0, sensible maximum of 0.125 (i.e. 3 "extra" bits of fraction)
508 result.MetersPerTanAngleAtCenter = DecodeFixedPointUInt16 ( lcs.MetersPerTanAngleAtCenter, 0, 16+3 );
509 for ( int i = 0; i < 4; i++ )
510 {
511 // 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)
512 result.ChromaticAberration[i] = DecodeFixedPointUInt16 ( lcs.ChromaticAberration[i], 0x8000, 16+3 );
513 }
514 result.MaxInvR = result.DistortionFn ( result.MaxR );
515 result.SetUpInverseApprox();
517 OVR_ASSERT ( version == lcs.VersionNumber );
519 *presult = result;
520 }
521 break;
522 default:
523 // Unknown format.
524 return false;
525 break;
526 }
527 return true;
528 }
530 // Returns number of bytes needed.
531 int SaveLensConfigSizeInBytes ( LensConfig const &config )
532 {
533 OVR_UNUSED ( config );
534 return sizeof ( LensConfigStored_CatmullRom10Version1 );
535 }
537 // Returns true on success.
538 bool SaveLensConfig ( uint8_t *pbuffer, int bufferSizeInBytes, LensConfig const &config )
539 {
540 if ( bufferSizeInBytes < (int)sizeof ( LensConfigStored_CatmullRom10Version1 ) )
541 {
542 return false;
543 }
545 // Construct the values.
546 LensConfigStored_CatmullRom10Version1 lcs;
547 lcs.VersionNumber = LCSV_CatmullRom10Version1;
548 for ( int i = 0; i < 11; i++ )
549 {
550 // K[] are mostly 1.something. They may get significantly bigger, but they never hit 0.0.
551 lcs.K[i] = EncodeFixedPointUInt16 ( config.K[i], 0, 14 );
552 }
553 // MaxR is tan(angle), so always >0, typically just over 1.0 (45 degrees half-fov),
554 // but may get arbitrarily high. tan(76)=4 is a very reasonable limit!
555 lcs.MaxR = EncodeFixedPointUInt16 ( config.MaxR, 0, 14 );
556 // MetersPerTanAngleAtCenter is also known as focal length!
557 // Typically around 0.04 for our current screens, minimum of 0, sensible maximum of 0.125 (i.e. 3 "extra" bits of fraction)
558 lcs.MetersPerTanAngleAtCenter = EncodeFixedPointUInt16 ( config.MetersPerTanAngleAtCenter, 0, 16+3 );
559 for ( int i = 0; i < 4; i++ )
560 {
561 // 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)
562 lcs.ChromaticAberration[i] = EncodeFixedPointUInt16 ( config.ChromaticAberration[i], 0x8000, 16+3 );
563 }
566 // Now store them out, sensitive to endianness.
567 EncodeUInt16 ( pbuffer + 0, lcs.VersionNumber );
568 for ( int i = 0; i < 11; i++ )
569 {
570 EncodeUInt16 ( pbuffer + 2 + 2*i, lcs.K[i] );
571 }
572 EncodeUInt16 ( pbuffer + 24, lcs.MaxR );
573 EncodeUInt16 ( pbuffer + 26, lcs.MetersPerTanAngleAtCenter );
574 for ( int i = 0; i < 4; i++ )
575 {
576 EncodeUInt16 ( pbuffer + 28 + 2*i, lcs.ChromaticAberration[i] );
577 }
578 OVR_COMPILER_ASSERT ( 36 == sizeof(lcs) );
580 return true;
581 }
583 #ifdef OVR_BUILD_DEBUG
584 void TestSaveLoadLensConfig ( LensConfig const &config )
585 {
586 OVR_ASSERT ( config.Eqn == Distortion_CatmullRom10 );
587 // As a test, make sure this can be encoded and decoded correctly.
588 const int bufferSize = 256;
589 uint8_t buffer[bufferSize];
590 OVR_ASSERT ( SaveLensConfigSizeInBytes ( config ) < bufferSize );
591 bool success;
592 success = SaveLensConfig ( buffer, bufferSize, config );
593 OVR_ASSERT ( success );
594 LensConfig testConfig;
595 success = LoadLensConfig ( &testConfig, buffer, bufferSize );
596 OVR_ASSERT ( success );
597 OVR_ASSERT ( testConfig.Eqn == config.Eqn );
598 for ( int i = 0; i < 11; i++ )
599 {
600 OVR_ASSERT ( fabs ( testConfig.K[i] - config.K[i] ) < 0.0001f );
601 }
602 OVR_ASSERT ( fabsf ( testConfig.MaxR - config.MaxR ) < 0.0001f );
603 OVR_ASSERT ( fabsf ( testConfig.MetersPerTanAngleAtCenter - config.MetersPerTanAngleAtCenter ) < 0.00001f );
604 for ( int i = 0; i < 4; i++ )
605 {
606 OVR_ASSERT ( fabsf ( testConfig.ChromaticAberration[i] - config.ChromaticAberration[i] ) < 0.00001f );
607 }
608 }
609 #endif
613 //-----------------------------------------------------------------------------------
615 // TBD: There is a question of whether this is the best file for CreateDebugHMDInfo. As long as there are many
616 // constants for HmdRenderInfo here as well it is ok. The alternative would be OVR_Common_HMDDevice.cpp, but
617 // that's specialized per platform... should probably move it there onces the code is in the common base class.
619 HMDInfo CreateDebugHMDInfo(HmdTypeEnum hmdType)
620 {
621 HMDInfo info;
623 if ((hmdType != HmdType_DK1) &&
624 (hmdType != HmdType_CrystalCoveProto) &&
625 (hmdType != HmdType_DK2))
626 {
627 LogText("Debug HMDInfo - HmdType not supported. Defaulting to DK1.\n");
628 hmdType = HmdType_DK1;
629 }
631 // The alternative would be to initialize info.HmdType to HmdType_None instead. If we did that,
632 // code wouldn't be "maximally compatible" and devs wouldn't know what device we are
633 // simulating... so if differentiation becomes necessary we better add Debug flag in the future.
634 info.HmdType = hmdType;
635 info.Manufacturer = "Oculus VR";
637 switch(hmdType)
638 {
639 case HmdType_DK1:
640 info.ProductName = "Oculus Rift DK1";
641 info.ResolutionInPixels = Sizei ( 1280, 800 );
642 info.ScreenSizeInMeters = Sizef ( 0.1498f, 0.0936f );
643 info.ScreenGapSizeInMeters = 0.0f;
644 info.CenterFromTopInMeters = 0.0468f;
645 info.LensSeparationInMeters = 0.0635f;
646 info.PelOffsetR = Vector2f ( 0.0f, 0.0f );
647 info.PelOffsetB = Vector2f ( 0.0f, 0.0f );
648 info.Shutter.Type = HmdShutter_RollingTopToBottom;
649 info.Shutter.VsyncToNextVsync = ( 1.0f / 60.0f );
650 info.Shutter.VsyncToFirstScanline = 0.000052f;
651 info.Shutter.FirstScanlineToLastScanline = 0.016580f;
652 info.Shutter.PixelSettleTime = 0.015f;
653 info.Shutter.PixelPersistence = ( 1.0f / 60.0f );
654 break;
656 case HmdType_CrystalCoveProto:
657 info.ProductName = "Oculus Rift Crystal Cove";
658 info.ResolutionInPixels = Sizei ( 1920, 1080 );
659 info.ScreenSizeInMeters = Sizef ( 0.12576f, 0.07074f );
660 info.ScreenGapSizeInMeters = 0.0f;
661 info.CenterFromTopInMeters = info.ScreenSizeInMeters.h * 0.5f;
662 info.LensSeparationInMeters = 0.0635f;
663 info.PelOffsetR = Vector2f ( 0.0f, 0.0f );
664 info.PelOffsetB = Vector2f ( 0.0f, 0.0f );
665 info.Shutter.Type = HmdShutter_RollingRightToLeft;
666 info.Shutter.VsyncToNextVsync = ( 1.0f / 76.0f );
667 info.Shutter.VsyncToFirstScanline = 0.0000273f;
668 info.Shutter.FirstScanlineToLastScanline = 0.0131033f;
669 info.Shutter.PixelSettleTime = 0.0f;
670 info.Shutter.PixelPersistence = 0.18f * info.Shutter.VsyncToNextVsync;
671 break;
673 case HmdType_DK2:
674 info.ProductName = "Oculus Rift DK2";
675 info.ResolutionInPixels = Sizei ( 1920, 1080 );
676 info.ScreenSizeInMeters = Sizef ( 0.12576f, 0.07074f );
677 info.ScreenGapSizeInMeters = 0.0f;
678 info.CenterFromTopInMeters = info.ScreenSizeInMeters.h * 0.5f;
679 info.LensSeparationInMeters = 0.0635f;
680 info.PelOffsetR = Vector2f ( 0.5f, 0.5f );
681 info.PelOffsetB = Vector2f ( 0.5f, 0.5f );
682 info.Shutter.Type = HmdShutter_RollingRightToLeft;
683 info.Shutter.VsyncToNextVsync = ( 1.0f / 76.0f );
684 info.Shutter.VsyncToFirstScanline = 0.0000273f;
685 info.Shutter.FirstScanlineToLastScanline = 0.0131033f;
686 info.Shutter.PixelSettleTime = 0.0f;
687 info.Shutter.PixelPersistence = 0.18f * info.Shutter.VsyncToNextVsync;
688 break;
690 default:
691 break;
692 }
694 return info;
695 }
698 HmdRenderInfo GenerateHmdRenderInfoFromHmdInfo ( HMDInfo const &hmdInfo,
699 Profile const *profile,
700 DistortionEqnType distortionType /*= Distortion_CatmullRom10*/,
701 EyeCupType eyeCupOverride /*= EyeCup_LAST*/ )
702 {
703 HmdRenderInfo renderInfo;
705 OVR_ASSERT(profile); // profiles are required
706 if(!profile)
707 return renderInfo;
709 renderInfo.HmdType = hmdInfo.HmdType;
710 renderInfo.ResolutionInPixels = hmdInfo.ResolutionInPixels;
711 renderInfo.ScreenSizeInMeters = hmdInfo.ScreenSizeInMeters;
712 renderInfo.CenterFromTopInMeters = hmdInfo.CenterFromTopInMeters;
713 renderInfo.ScreenGapSizeInMeters = hmdInfo.ScreenGapSizeInMeters;
714 renderInfo.LensSeparationInMeters = hmdInfo.LensSeparationInMeters;
715 renderInfo.PelOffsetR = hmdInfo.PelOffsetR;
716 renderInfo.PelOffsetB = hmdInfo.PelOffsetB;
718 OVR_ASSERT ( sizeof(renderInfo.Shutter) == sizeof(hmdInfo.Shutter) ); // Try to keep the files in sync!
719 renderInfo.Shutter.Type = hmdInfo.Shutter.Type;
720 renderInfo.Shutter.VsyncToNextVsync = hmdInfo.Shutter.VsyncToNextVsync;
721 renderInfo.Shutter.VsyncToFirstScanline = hmdInfo.Shutter.VsyncToFirstScanline;
722 renderInfo.Shutter.FirstScanlineToLastScanline = hmdInfo.Shutter.FirstScanlineToLastScanline;
723 renderInfo.Shutter.PixelSettleTime = hmdInfo.Shutter.PixelSettleTime;
724 renderInfo.Shutter.PixelPersistence = hmdInfo.Shutter.PixelPersistence;
726 renderInfo.LensDiameterInMeters = 0.035f;
727 renderInfo.LensSurfaceToMidplateInMeters = 0.025f;
728 renderInfo.EyeCups = EyeCup_DK1A;
730 #if 0 // Device settings are out of date - don't use them.
731 if (Contents & Contents_Distortion)
732 {
733 memcpy(renderInfo.DistortionK, DistortionK, sizeof(float)*4);
734 renderInfo.DistortionEqn = Distortion_RecipPoly4;
735 }
736 #endif
738 // Defaults in case of no user profile.
739 renderInfo.EyeLeft.NoseToPupilInMeters = 0.032f;
740 renderInfo.EyeLeft.ReliefInMeters = 0.012f;
742 // 10mm eye-relief laser numbers for DK1 lenses.
743 // These are a decent seed for finding eye-relief and IPD.
744 // These are NOT used for rendering!
745 // Rendering distortions are now in GenerateLensConfigFromEyeRelief()
746 // So, if you're hacking in new distortions, don't do it here!
747 renderInfo.EyeLeft.Distortion.SetToIdentity();
748 renderInfo.EyeLeft.Distortion.MetersPerTanAngleAtCenter = 0.0449f;
749 renderInfo.EyeLeft.Distortion.Eqn = Distortion_RecipPoly4;
750 renderInfo.EyeLeft.Distortion.K[0] = 1.0f;
751 renderInfo.EyeLeft.Distortion.K[1] = -0.494165344f;
752 renderInfo.EyeLeft.Distortion.K[2] = 0.587046423f;
753 renderInfo.EyeLeft.Distortion.K[3] = -0.841887126f;
754 renderInfo.EyeLeft.Distortion.MaxR = 1.0f;
756 renderInfo.EyeLeft.Distortion.ChromaticAberration[0] = -0.006f;
757 renderInfo.EyeLeft.Distortion.ChromaticAberration[1] = 0.0f;
758 renderInfo.EyeLeft.Distortion.ChromaticAberration[2] = 0.014f;
759 renderInfo.EyeLeft.Distortion.ChromaticAberration[3] = 0.0f;
761 renderInfo.EyeRight = renderInfo.EyeLeft;
763 // Obtain data from profile.
764 char eyecup[16];
765 if (profile->GetValue(OVR_KEY_EYE_CUP, eyecup, 16))
766 {
767 SetEyeCup(&renderInfo, eyecup);
768 }
770 switch ( hmdInfo.HmdType )
771 {
772 case HmdType_None:
773 case HmdType_DKProto:
774 case HmdType_DK1:
775 // Slight hack to improve usability.
776 // If you have a DKHD-style lens profile enabled,
777 // but you plug in DK1 and forget to change the profile,
778 // obviously you don't want those lens numbers.
779 if ( ( renderInfo.EyeCups != EyeCup_DK1A ) &&
780 ( renderInfo.EyeCups != EyeCup_DK1B ) &&
781 ( renderInfo.EyeCups != EyeCup_DK1C ) )
782 {
783 renderInfo.EyeCups = EyeCup_DK1A;
784 }
785 break;
787 case HmdType_DKHD2Proto:
788 renderInfo.EyeCups = EyeCup_DKHD2A;
789 break;
790 case HmdType_CrystalCoveProto:
791 renderInfo.EyeCups = EyeCup_PinkA;
792 break;
793 case HmdType_DK2:
794 renderInfo.EyeCups = EyeCup_DK2A;
795 break;
796 default:
797 break;
798 }
800 if ( eyeCupOverride != EyeCup_LAST )
801 {
802 renderInfo.EyeCups = eyeCupOverride;
803 }
805 switch ( renderInfo.EyeCups )
806 {
807 case EyeCup_DK1A:
808 case EyeCup_DK1B:
809 case EyeCup_DK1C:
810 renderInfo.LensDiameterInMeters = 0.035f;
811 renderInfo.LensSurfaceToMidplateInMeters = 0.02357f;
812 // Not strictly lens-specific, but still wise to set a reasonable default for relief.
813 renderInfo.EyeLeft.ReliefInMeters = 0.010f;
814 renderInfo.EyeRight.ReliefInMeters = 0.010f;
815 break;
816 case EyeCup_DKHD2A:
817 renderInfo.LensDiameterInMeters = 0.035f;
818 renderInfo.LensSurfaceToMidplateInMeters = 0.02357f;
819 // Not strictly lens-specific, but still wise to set a reasonable default for relief.
820 renderInfo.EyeLeft.ReliefInMeters = 0.010f;
821 renderInfo.EyeRight.ReliefInMeters = 0.010f;
822 break;
823 case EyeCup_PinkA:
824 case EyeCup_DK2A:
825 renderInfo.LensDiameterInMeters = 0.04f; // approximate
826 renderInfo.LensSurfaceToMidplateInMeters = 0.01965f;
827 // Not strictly lens-specific, but still wise to set a reasonable default for relief.
828 renderInfo.EyeLeft.ReliefInMeters = 0.012f;
829 renderInfo.EyeRight.ReliefInMeters = 0.012f;
830 break;
831 default: OVR_ASSERT ( false ); break;
832 }
834 Profile* def = ProfileManager::GetInstance()->GetDefaultProfile(hmdInfo.HmdType);
836 // Set the eye position
837 // Use the user profile value unless they have elected to use the defaults
838 if (!profile->GetBoolValue(OVR_KEY_CUSTOM_EYE_RENDER, true))
839 profile = def; // use the default
841 char user[32];
842 profile->GetValue(OVR_KEY_USER, user, 32); // for debugging purposes
844 // TBD: Maybe we should separate custom camera positioning from custom distortion rendering ??
845 float eye2nose[2] = { OVR_DEFAULT_IPD / 2, OVR_DEFAULT_IPD / 2 };
846 if (profile->GetFloatValues(OVR_KEY_EYE_TO_NOSE_DISTANCE, eye2nose, 2) == 2)
847 {
848 renderInfo.EyeLeft.NoseToPupilInMeters = eye2nose[0];
849 renderInfo.EyeRight.NoseToPupilInMeters = eye2nose[1];
850 }
851 else
852 { // Legacy profiles may not include half-ipd, so use the regular IPD value instead
853 float ipd = profile->GetFloatValue(OVR_KEY_IPD, OVR_DEFAULT_IPD);
854 renderInfo.EyeLeft.NoseToPupilInMeters = 0.5f * ipd;
855 renderInfo.EyeRight.NoseToPupilInMeters = 0.5f * ipd;
856 }
858 float eye2plate[2];
859 if ((profile->GetFloatValues(OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE, eye2plate, 2) == 2) ||
860 (def->GetFloatValues(OVR_KEY_MAX_EYE_TO_PLATE_DISTANCE, eye2plate, 2) == 2))
861 { // Subtract the eye-cup height from the plate distance to get the eye-to-lens distance
862 // This measurement should be the the distance at maximum dial setting
863 // We still need to adjust with the dial offset
864 renderInfo.EyeLeft.ReliefInMeters = eye2plate[0] - renderInfo.LensSurfaceToMidplateInMeters;
865 renderInfo.EyeRight.ReliefInMeters = eye2plate[1] - renderInfo.LensSurfaceToMidplateInMeters;
867 // Adjust the eye relief with the dial setting (from the assumed max eye relief)
868 int dial = profile->GetIntValue(OVR_KEY_EYE_RELIEF_DIAL, OVR_DEFAULT_EYE_RELIEF_DIAL);
869 renderInfo.EyeLeft.ReliefInMeters -= ((10 - dial) * 0.001f);
870 renderInfo.EyeRight.ReliefInMeters -= ((10 - dial) * 0.001f);
871 }
872 else
873 {
874 // We shouldn't be here. The user or default profile should have the eye relief
875 OVR_ASSERT(false);
877 // Set the eye relief with the user configured dial setting
878 //int dial = profile->GetIntValue(OVR_KEY_EYE_RELIEF_DIAL, OVR_DEFAULT_EYE_RELIEF_DIAL);
880 // Assume a default of 7 to 17 mm eye relief based on the dial. This corresponds
881 // to the sampled and tuned distortion range on the DK1.
882 //renderInfo.EyeLeft.ReliefInMeters = 0.007f + (dial * 0.001f);
883 //renderInfo.EyeRight.ReliefInMeters = 0.007f + (dial * 0.001f);
884 }
886 def->Release();
889 // Now we know where the eyes are relative to the lenses, we can compute a distortion for each.
890 // TODO: incorporate lateral offset in distortion generation.
891 // TODO: we used a distortion to calculate eye-relief, and now we're making a distortion from that eye-relief. Close the loop!
893 for ( int eyeNum = 0; eyeNum < 2; eyeNum++ )
894 {
895 HmdRenderInfo::EyeConfig *pHmdEyeConfig = ( eyeNum == 0 ) ? &(renderInfo.EyeLeft) : &(renderInfo.EyeRight);
897 float eye_relief = pHmdEyeConfig->ReliefInMeters;
898 LensConfig distortionConfig = GenerateLensConfigFromEyeRelief ( eye_relief, renderInfo, distortionType );
899 pHmdEyeConfig->Distortion = distortionConfig;
900 }
902 return renderInfo;
903 }
906 LensConfig GenerateLensConfigFromEyeRelief ( float eyeReliefInMeters, HmdRenderInfo const &hmd, DistortionEqnType distortionType /*= Distortion_CatmullRom10*/ )
907 {
908 struct DistortionDescriptor
909 {
910 float EyeRelief;
911 // The three places we're going to sample & lerp the curve at.
912 // One sample is always at 0.0, and the distortion scale should be 1.0 or else!
913 // Only use for poly4 numbers - CR has an implicit scale.
914 float SampleRadius[3];
915 // Where the distortion has actually been measured/calibrated out to.
916 // Don't try to hallucinate data out beyond here.
917 float MaxRadius;
918 // The config itself.
919 LensConfig Config;
920 };
922 static const int MaxDistortions = 10;
923 DistortionDescriptor distortions[MaxDistortions];
924 for (int i = 0; i < MaxDistortions; i++)
925 {
926 distortions[i].EyeRelief = 0.0f;
927 memset(distortions[i].SampleRadius, 0, sizeof(distortions[i].SampleRadius));
928 distortions[i].MaxRadius = 1.0f;
929 distortions[i].Config.SetToIdentity(); // Note: This line causes a false Microsoft static analysis error -cat
930 }
931 int numDistortions = 0;
932 int defaultDistortion = 0; // index of the default distortion curve to use if zero eye relief supplied
934 if ( ( hmd.EyeCups == EyeCup_DK1A ) ||
935 ( hmd.EyeCups == EyeCup_DK1B ) ||
936 ( hmd.EyeCups == EyeCup_DK1C ) )
937 {
939 numDistortions = 0;
941 // Tuned at minimum dial setting - extended to r^2 == 1.8
942 distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10;
943 distortions[numDistortions].EyeRelief = 0.012760465f - 0.005f;
944 distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f;
945 distortions[numDistortions].Config.K[0] = 1.0000f;
946 distortions[numDistortions].Config.K[1] = 1.06505f;
947 distortions[numDistortions].Config.K[2] = 1.14725f;
948 distortions[numDistortions].Config.K[3] = 1.2705f;
949 distortions[numDistortions].Config.K[4] = 1.48f;
950 distortions[numDistortions].Config.K[5] = 1.87f;
951 distortions[numDistortions].Config.K[6] = 2.534f;
952 distortions[numDistortions].Config.K[7] = 3.6f;
953 distortions[numDistortions].Config.K[8] = 5.1f;
954 distortions[numDistortions].Config.K[9] = 7.4f;
955 distortions[numDistortions].Config.K[10] = 11.0f;
956 distortions[numDistortions].MaxRadius = sqrt(1.8f);
957 defaultDistortion = numDistortions; // this is the default
958 numDistortions++;
960 // Tuned at middle dial setting
961 distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10;
962 distortions[numDistortions].EyeRelief = 0.012760465f; // my average eye-relief
963 distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f;
964 distortions[numDistortions].Config.K[0] = 1.0f;
965 distortions[numDistortions].Config.K[1] = 1.032407264f;
966 distortions[numDistortions].Config.K[2] = 1.07160462f;
967 distortions[numDistortions].Config.K[3] = 1.11998388f;
968 distortions[numDistortions].Config.K[4] = 1.1808606f;
969 distortions[numDistortions].Config.K[5] = 1.2590494f;
970 distortions[numDistortions].Config.K[6] = 1.361915f;
971 distortions[numDistortions].Config.K[7] = 1.5014339f;
972 distortions[numDistortions].Config.K[8] = 1.6986004f;
973 distortions[numDistortions].Config.K[9] = 1.9940577f;
974 distortions[numDistortions].Config.K[10] = 2.4783147f;
975 distortions[numDistortions].MaxRadius = 1.0f;
976 numDistortions++;
978 // Tuned at maximum dial setting
979 distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10;
980 distortions[numDistortions].EyeRelief = 0.012760465f + 0.005f;
981 distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f;
982 distortions[numDistortions].Config.K[0] = 1.0102f;
983 distortions[numDistortions].Config.K[1] = 1.0371f;
984 distortions[numDistortions].Config.K[2] = 1.0831f;
985 distortions[numDistortions].Config.K[3] = 1.1353f;
986 distortions[numDistortions].Config.K[4] = 1.2f;
987 distortions[numDistortions].Config.K[5] = 1.2851f;
988 distortions[numDistortions].Config.K[6] = 1.3979f;
989 distortions[numDistortions].Config.K[7] = 1.56f;
990 distortions[numDistortions].Config.K[8] = 1.8f;
991 distortions[numDistortions].Config.K[9] = 2.25f;
992 distortions[numDistortions].Config.K[10] = 3.0f;
993 distortions[numDistortions].MaxRadius = 1.0f;
994 numDistortions++;
998 // Chromatic aberration doesn't seem to change with eye relief.
999 for ( int i = 0; i < numDistortions; i++ )
1001 distortions[i].Config.ChromaticAberration[0] = -0.006f;
1002 distortions[i].Config.ChromaticAberration[1] = 0.0f;
1003 distortions[i].Config.ChromaticAberration[2] = 0.014f;
1004 distortions[i].Config.ChromaticAberration[3] = 0.0f;
1007 else if ( hmd.EyeCups == EyeCup_DKHD2A )
1009 // Tuned DKHD2 lens
1010 numDistortions = 0;
1012 distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10;
1013 distortions[numDistortions].EyeRelief = 0.010f;
1014 distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.0425f;
1015 distortions[numDistortions].Config.K[0] = 1.0f;
1016 distortions[numDistortions].Config.K[1] = 1.0425f;
1017 distortions[numDistortions].Config.K[2] = 1.0826f;
1018 distortions[numDistortions].Config.K[3] = 1.130f;
1019 distortions[numDistortions].Config.K[4] = 1.185f;
1020 distortions[numDistortions].Config.K[5] = 1.250f;
1021 distortions[numDistortions].Config.K[6] = 1.338f;
1022 distortions[numDistortions].Config.K[7] = 1.455f;
1023 distortions[numDistortions].Config.K[8] = 1.620f;
1024 distortions[numDistortions].Config.K[9] = 1.840f;
1025 distortions[numDistortions].Config.K[10] = 2.200f;
1026 distortions[numDistortions].MaxRadius = 1.0f;
1028 defaultDistortion = numDistortions; // this is the default
1029 numDistortions++;
1031 distortions[numDistortions] = distortions[0];
1032 distortions[numDistortions].EyeRelief = 0.020f;
1033 numDistortions++;
1035 // Chromatic aberration doesn't seem to change with eye relief.
1036 for ( int i = 0; i < numDistortions; i++ )
1038 distortions[i].Config.ChromaticAberration[0] = -0.006f;
1039 distortions[i].Config.ChromaticAberration[1] = 0.0f;
1040 distortions[i].Config.ChromaticAberration[2] = 0.014f;
1041 distortions[i].Config.ChromaticAberration[3] = 0.0f;
1044 else if ( hmd.EyeCups == EyeCup_PinkA || hmd.EyeCups == EyeCup_DK2A )
1046 // Tuned Crystal Cove & DK2 Lens (CES & GDC)
1047 numDistortions = 0;
1050 distortions[numDistortions].EyeRelief = 0.008f;
1051 distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.036f;
1052 // TODO: Need to retune this distortion for minimum eye relief
1053 distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10;
1054 distortions[numDistortions].Config.K[0] = 1.003f;
1055 distortions[numDistortions].Config.K[1] = 1.02f;
1056 distortions[numDistortions].Config.K[2] = 1.042f;
1057 distortions[numDistortions].Config.K[3] = 1.066f;
1058 distortions[numDistortions].Config.K[4] = 1.094f;
1059 distortions[numDistortions].Config.K[5] = 1.126f;
1060 distortions[numDistortions].Config.K[6] = 1.162f;
1061 distortions[numDistortions].Config.K[7] = 1.203f;
1062 distortions[numDistortions].Config.K[8] = 1.25f;
1063 distortions[numDistortions].Config.K[9] = 1.31f;
1064 distortions[numDistortions].Config.K[10] = 1.38f;
1065 distortions[numDistortions].MaxRadius = 1.0f;
1067 distortions[numDistortions].Config.ChromaticAberration[0] = -0.0112f;
1068 distortions[numDistortions].Config.ChromaticAberration[1] = -0.015f;
1069 distortions[numDistortions].Config.ChromaticAberration[2] = 0.0187f;
1070 distortions[numDistortions].Config.ChromaticAberration[3] = 0.015f;
1072 numDistortions++;
1078 distortions[numDistortions].EyeRelief = 0.018f;
1079 distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.036f;
1081 distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10;
1082 distortions[numDistortions].Config.K[0] = 1.003f;
1083 distortions[numDistortions].Config.K[1] = 1.02f;
1084 distortions[numDistortions].Config.K[2] = 1.042f;
1085 distortions[numDistortions].Config.K[3] = 1.066f;
1086 distortions[numDistortions].Config.K[4] = 1.094f;
1087 distortions[numDistortions].Config.K[5] = 1.126f;
1088 distortions[numDistortions].Config.K[6] = 1.162f;
1089 distortions[numDistortions].Config.K[7] = 1.203f;
1090 distortions[numDistortions].Config.K[8] = 1.25f;
1091 distortions[numDistortions].Config.K[9] = 1.31f;
1092 distortions[numDistortions].Config.K[10] = 1.38f;
1093 distortions[numDistortions].MaxRadius = 1.0f;
1095 distortions[numDistortions].Config.ChromaticAberration[0] = -0.015f;
1096 distortions[numDistortions].Config.ChromaticAberration[1] = -0.02f;
1097 distortions[numDistortions].Config.ChromaticAberration[2] = 0.025f;
1098 distortions[numDistortions].Config.ChromaticAberration[3] = 0.02f;
1100 defaultDistortion = numDistortions; // this is the default
1101 numDistortions++;
1103 /*
1104 // Orange Lens on DK2
1105 distortions[numDistortions].EyeRelief = 0.010f;
1106 distortions[numDistortions].Config.MetersPerTanAngleAtCenter = 0.031f;
1108 distortions[numDistortions].Config.Eqn = Distortion_CatmullRom10;
1109 distortions[numDistortions].Config.K[0] = 1.00f;
1110 distortions[numDistortions].Config.K[1] = 1.0169f;
1111 distortions[numDistortions].Config.K[2] = 1.0378f;
1112 distortions[numDistortions].Config.K[3] = 1.0648f;
1113 distortions[numDistortions].Config.K[4] = 1.0990f;
1114 distortions[numDistortions].Config.K[5] = 1.141f;
1115 distortions[numDistortions].Config.K[6] = 1.192f;
1116 distortions[numDistortions].Config.K[7] = 1.255f;
1117 distortions[numDistortions].Config.K[8] = 1.335f;
1118 distortions[numDistortions].Config.K[9] = 1.435f;
1119 distortions[numDistortions].Config.K[10] = 1.56f;
1120 distortions[numDistortions].MaxRadius = 1.0f;
1121 */
1123 else
1125 // Unknown lens.
1126 // Use DK1 black lens settings, just so we can continue to run with something.
1127 distortions[0].EyeRelief = 0.005f;
1128 distortions[0].Config.MetersPerTanAngleAtCenter = 0.043875f;
1129 distortions[0].Config.Eqn = Distortion_RecipPoly4;
1130 distortions[0].Config.K[0] = 1.0f;
1131 distortions[0].Config.K[1] = -0.3999f;
1132 distortions[0].Config.K[2] = 0.2408f;
1133 distortions[0].Config.K[3] = -0.4589f;
1134 distortions[0].SampleRadius[0] = 0.2f;
1135 distortions[0].SampleRadius[1] = 0.4f;
1136 distortions[0].SampleRadius[2] = 0.6f;
1138 distortions[1] = distortions[0];
1139 distortions[1].EyeRelief = 0.010f;
1140 numDistortions = 2;
1142 // Chromatic aberration doesn't seem to change with eye relief.
1143 for ( int i = 0; i < numDistortions; i++ )
1145 // These are placeholder, they have not been tuned!
1146 distortions[i].Config.ChromaticAberration[0] = 0.0f;
1147 distortions[i].Config.ChromaticAberration[1] = 0.0f;
1148 distortions[i].Config.ChromaticAberration[2] = 0.0f;
1149 distortions[i].Config.ChromaticAberration[3] = 0.0f;
1153 OVR_ASSERT(numDistortions < MaxDistortions);
1155 DistortionDescriptor *pUpper = NULL;
1156 DistortionDescriptor *pLower = NULL;
1157 float lerpVal = 0.0f;
1158 if (eyeReliefInMeters == 0)
1159 { // Use a constant default distortion if an invalid eye-relief is supplied
1160 pLower = &(distortions[defaultDistortion]);
1161 pUpper = &(distortions[defaultDistortion]);
1162 lerpVal = 0.0f;
1164 else
1166 for ( int i = 0; i < numDistortions-1; i++ )
1168 OVR_ASSERT ( distortions[i].EyeRelief < distortions[i+1].EyeRelief );
1169 if ( ( distortions[i].EyeRelief <= eyeReliefInMeters ) && ( distortions[i+1].EyeRelief > eyeReliefInMeters ) )
1171 pLower = &(distortions[i]);
1172 pUpper = &(distortions[i+1]);
1173 lerpVal = ( eyeReliefInMeters - pLower->EyeRelief ) / ( pUpper->EyeRelief - pLower->EyeRelief );
1174 // No break here - I want the ASSERT to check everything every time!
1179 if ( pUpper == NULL )
1181 #if 0
1182 // Outside the range, so extrapolate rather than interpolate.
1183 if ( distortions[0].EyeRelief > eyeReliefInMeters )
1185 pLower = &(distortions[0]);
1186 pUpper = &(distortions[1]);
1188 else
1190 OVR_ASSERT ( distortions[numDistortions-1].EyeRelief <= eyeReliefInMeters );
1191 pLower = &(distortions[numDistortions-2]);
1192 pUpper = &(distortions[numDistortions-1]);
1194 lerpVal = ( eyeReliefInMeters - pLower->EyeRelief ) / ( pUpper->EyeRelief - pLower->EyeRelief );
1195 #else
1196 // Do not extrapolate, just clamp - slightly worried about people putting in bogus settings.
1197 if ( distortions[0].EyeRelief > eyeReliefInMeters )
1199 pLower = &(distortions[0]);
1200 pUpper = &(distortions[0]);
1202 else
1204 OVR_ASSERT ( distortions[numDistortions-1].EyeRelief <= eyeReliefInMeters );
1205 pLower = &(distortions[numDistortions-1]);
1206 pUpper = &(distortions[numDistortions-1]);
1208 lerpVal = 0.0f;
1209 #endif
1211 float invLerpVal = 1.0f - lerpVal;
1213 pLower->Config.MaxR = pLower->MaxRadius;
1214 pUpper->Config.MaxR = pUpper->MaxRadius;
1216 LensConfig result;
1217 // Where is the edge of the lens - no point modelling further than this.
1218 float maxValidRadius = invLerpVal * pLower->MaxRadius + lerpVal * pUpper->MaxRadius;
1219 result.MaxR = maxValidRadius;
1221 switch ( distortionType )
1223 case Distortion_Poly4:
1224 // Deprecated
1225 OVR_ASSERT ( false );
1226 break;
1227 case Distortion_RecipPoly4:{
1228 // Lerp control points and fit an equation to them.
1229 float fitX[4];
1230 float fitY[4];
1231 fitX[0] = 0.0f;
1232 fitY[0] = 1.0f;
1233 for ( int ctrlPt = 1; ctrlPt < 4; ctrlPt ++ )
1235 // SampleRadius is not valid for Distortion_RecipPoly4 types.
1236 float radiusLerp = ( invLerpVal * pLower->MaxRadius + lerpVal * pUpper->MaxRadius ) * ( (float)ctrlPt / 4.0f );
1237 float radiusLerpSq = radiusLerp * radiusLerp;
1238 float fitYLower = pLower->Config.DistortionFnScaleRadiusSquared ( radiusLerpSq );
1239 float fitYUpper = pUpper->Config.DistortionFnScaleRadiusSquared ( radiusLerpSq );
1240 fitX[ctrlPt] = radiusLerpSq;
1241 fitY[ctrlPt] = 1.0f / ( invLerpVal * fitYLower + lerpVal * fitYUpper );
1244 result.Eqn = Distortion_RecipPoly4;
1245 bool bSuccess = FitCubicPolynomial ( result.K, fitX, fitY );
1246 OVR_ASSERT ( bSuccess );
1247 OVR_UNUSED ( bSuccess );
1249 // Set up the fast inverse.
1250 float maxRDist = result.DistortionFn ( maxValidRadius );
1251 result.MaxInvR = maxRDist;
1252 result.SetUpInverseApprox();
1254 }break;
1256 case Distortion_CatmullRom10:{
1258 // Evenly sample & lerp points on the curve.
1259 const int NumSegments = LensConfig::NumCoefficients;
1260 result.MaxR = maxValidRadius;
1261 // Directly interpolate the K0 values
1262 result.K[0] = invLerpVal * pLower->Config.K[0] + lerpVal * pUpper->Config.K[0];
1264 // Sample and interpolate the distortion curves to derive K[1] ... K[n]
1265 for ( int ctrlPt = 1; ctrlPt < NumSegments; ctrlPt++ )
1267 float radiusSq = ( (float)ctrlPt / (float)(NumSegments-1) ) * maxValidRadius * maxValidRadius;
1268 float fitYLower = pLower->Config.DistortionFnScaleRadiusSquared ( radiusSq );
1269 float fitYUpper = pUpper->Config.DistortionFnScaleRadiusSquared ( radiusSq );
1270 float fitLerp = invLerpVal * fitYLower + lerpVal * fitYUpper;
1271 result.K[ctrlPt] = fitLerp;
1274 result.Eqn = Distortion_CatmullRom10;
1276 for ( int ctrlPt = 1; ctrlPt < NumSegments; ctrlPt++ )
1278 float radiusSq = ( (float)ctrlPt / (float)(NumSegments-1) ) * maxValidRadius * maxValidRadius;
1279 float val = result.DistortionFnScaleRadiusSquared ( radiusSq );
1280 OVR_ASSERT ( Alg::Abs ( val - result.K[ctrlPt] ) < 0.0001f );
1281 OVR_UNUSED1(val); // For release build.
1284 // Set up the fast inverse.
1285 float maxRDist = result.DistortionFn ( maxValidRadius );
1286 result.MaxInvR = maxRDist;
1287 result.SetUpInverseApprox();
1289 }break;
1291 default: OVR_ASSERT ( false ); break;
1295 // Chromatic aberration.
1296 result.ChromaticAberration[0] = invLerpVal * pLower->Config.ChromaticAberration[0] + lerpVal * pUpper->Config.ChromaticAberration[0];
1297 result.ChromaticAberration[1] = invLerpVal * pLower->Config.ChromaticAberration[1] + lerpVal * pUpper->Config.ChromaticAberration[1];
1298 result.ChromaticAberration[2] = invLerpVal * pLower->Config.ChromaticAberration[2] + lerpVal * pUpper->Config.ChromaticAberration[2];
1299 result.ChromaticAberration[3] = invLerpVal * pLower->Config.ChromaticAberration[3] + lerpVal * pUpper->Config.ChromaticAberration[3];
1301 // Scale.
1302 result.MetersPerTanAngleAtCenter = pLower->Config.MetersPerTanAngleAtCenter * invLerpVal +
1303 pUpper->Config.MetersPerTanAngleAtCenter * lerpVal;
1304 /*
1305 // Commented out - Causes ASSERT with no HMD plugged in
1306 #ifdef OVR_BUILD_DEBUG
1307 if ( distortionType == Distortion_CatmullRom10 )
1309 TestSaveLoadLensConfig ( result );
1311 #endif
1312 */
1313 return result;
1317 DistortionRenderDesc CalculateDistortionRenderDesc ( StereoEye eyeType, HmdRenderInfo const &hmd,
1318 const LensConfig *pLensOverride /*= NULL */ )
1320 // From eye relief, IPD and device characteristics, we get the distortion mapping.
1321 // This distortion does the following things:
1322 // 1. It undoes the distortion that happens at the edges of the lens.
1323 // 2. It maps the undistorted field into "retina" space.
1324 // So the input is a pixel coordinate - the physical pixel on the display itself.
1325 // The output is the real-world direction of the ray from this pixel as it comes out of the lens and hits the eye.
1326 // However we typically think of rays "coming from" the eye, so the direction (TanAngleX,TanAngleY,1) is the direction
1327 // that the pixel appears to be in real-world space, where AngleX and AngleY are relative to the straight-ahead vector.
1328 // If your renderer is a raytracer, you can use this vector directly (normalize as appropriate).
1329 // However in standard rasterisers, we have rendered a 2D image and are putting it in front of the eye,
1330 // so we then need a mapping from this space to the [-1,1] UV coordinate space, which depends on exactly
1331 // where "in space" the app wants to put that rendertarget.
1332 // Where in space, and how large this rendertarget is, is completely up to the app and/or user,
1333 // though of course we can provide some useful hints.
1335 // TODO: Use IPD and eye relief to modify distortion (i.e. non-radial component)
1336 // TODO: cope with lenses that don't produce collimated light.
1337 // This means that IPD relative to the lens separation changes the light vergence,
1338 // and so we actually need to change where the image is displayed.
1340 const HmdRenderInfo::EyeConfig &hmdEyeConfig = ( eyeType == StereoEye_Left ) ? hmd.EyeLeft : hmd.EyeRight;
1342 DistortionRenderDesc localDistortion;
1343 localDistortion.Lens = hmdEyeConfig.Distortion;
1345 if ( pLensOverride != NULL )
1347 localDistortion.Lens = *pLensOverride;
1350 Sizef pixelsPerMeter(hmd.ResolutionInPixels.w / ( hmd.ScreenSizeInMeters.w - hmd.ScreenGapSizeInMeters ),
1351 hmd.ResolutionInPixels.h / hmd.ScreenSizeInMeters.h);
1353 localDistortion.PixelsPerTanAngleAtCenter = (pixelsPerMeter * localDistortion.Lens.MetersPerTanAngleAtCenter).ToVector();
1354 // Same thing, scaled to [-1,1] for each eye, rather than pixels.
1356 localDistortion.TanEyeAngleScale = Vector2f(0.25f, 0.5f).EntrywiseMultiply(
1357 (hmd.ScreenSizeInMeters / localDistortion.Lens.MetersPerTanAngleAtCenter).ToVector());
1359 // <--------------left eye------------------><-ScreenGapSizeInMeters-><--------------right eye----------------->
1360 // <------------------------------------------ScreenSizeInMeters.Width----------------------------------------->
1361 // <----------------LensSeparationInMeters--------------->
1362 // <--centerFromLeftInMeters->
1363 // ^
1364 // Center of lens
1366 // Find the lens centers in scale of [-1,+1] (NDC) in left eye.
1367 float visibleWidthOfOneEye = 0.5f * ( hmd.ScreenSizeInMeters.w - hmd.ScreenGapSizeInMeters );
1368 float centerFromLeftInMeters = ( hmd.ScreenSizeInMeters.w - hmd.LensSeparationInMeters ) * 0.5f;
1369 localDistortion.LensCenter.x = ( centerFromLeftInMeters / visibleWidthOfOneEye ) * 2.0f - 1.0f;
1370 localDistortion.LensCenter.y = ( hmd.CenterFromTopInMeters / hmd.ScreenSizeInMeters.h ) * 2.0f - 1.0f;
1371 if ( eyeType == StereoEye_Right )
1373 localDistortion.LensCenter.x = -localDistortion.LensCenter.x;
1376 return localDistortion;
1379 FovPort CalculateFovFromEyePosition ( float eyeReliefInMeters,
1380 float offsetToRightInMeters,
1381 float offsetDownwardsInMeters,
1382 float lensDiameterInMeters,
1383 float extraEyeRotationInRadians /*= 0.0f*/ )
1385 // 2D view of things:
1386 // |-| <--- offsetToRightInMeters (in this case, it is negative)
1387 // |=======C=======| <--- lens surface (C=center)
1388 // \ | _/
1389 // \ R _/
1390 // \ | _/
1391 // \ | _/
1392 // \|/
1393 // O <--- center of pupil
1395 // (technically the lens is round rather than square, so it's not correct to
1396 // separate vertical and horizontal like this, but it's close enough)
1397 float halfLensDiameter = lensDiameterInMeters * 0.5f;
1398 FovPort fovPort;
1399 fovPort.UpTan = ( halfLensDiameter + offsetDownwardsInMeters ) / eyeReliefInMeters;
1400 fovPort.DownTan = ( halfLensDiameter - offsetDownwardsInMeters ) / eyeReliefInMeters;
1401 fovPort.LeftTan = ( halfLensDiameter + offsetToRightInMeters ) / eyeReliefInMeters;
1402 fovPort.RightTan = ( halfLensDiameter - offsetToRightInMeters ) / eyeReliefInMeters;
1404 if ( extraEyeRotationInRadians > 0.0f )
1406 // That's the basic looking-straight-ahead eye position relative to the lens.
1407 // But if you look left, the pupil moves left as the eyeball rotates, which
1408 // means you can see more to the right than this geometry suggests.
1409 // So add in the bounds for the extra movement of the pupil.
1411 // Beyond 30 degrees does not increase FOV because the pupil starts moving backwards more than sideways.
1412 extraEyeRotationInRadians = Alg::Min ( DegreeToRad ( 30.0f ), Alg::Max ( 0.0f, extraEyeRotationInRadians ) );
1414 // The rotation of the eye is a bit more complex than a simple circle. The center of rotation
1415 // at 13.5mm from cornea is slightly further back than the actual center of the eye.
1416 // Additionally the rotation contains a small lateral component as the muscles pull the eye
1417 const float eyeballCenterToPupil = 0.0135f; // center of eye rotation
1418 const float eyeballLateralPull = 0.001f * (extraEyeRotationInRadians / DegreeToRad ( 30.0f)); // lateral motion as linear function
1419 float extraTranslation = eyeballCenterToPupil * sinf ( extraEyeRotationInRadians ) + eyeballLateralPull;
1420 float extraRelief = eyeballCenterToPupil * ( 1.0f - cosf ( extraEyeRotationInRadians ) );
1422 fovPort.UpTan = Alg::Max ( fovPort.UpTan , ( halfLensDiameter + offsetDownwardsInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) );
1423 fovPort.DownTan = Alg::Max ( fovPort.DownTan , ( halfLensDiameter - offsetDownwardsInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) );
1424 fovPort.LeftTan = Alg::Max ( fovPort.LeftTan , ( halfLensDiameter + offsetToRightInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) );
1425 fovPort.RightTan = Alg::Max ( fovPort.RightTan, ( halfLensDiameter - offsetToRightInMeters + extraTranslation ) / ( eyeReliefInMeters + extraRelief ) );
1428 return fovPort;
1433 FovPort CalculateFovFromHmdInfo ( StereoEye eyeType,
1434 DistortionRenderDesc const &distortion,
1435 HmdRenderInfo const &hmd,
1436 float extraEyeRotationInRadians /*= 0.0f*/ )
1438 FovPort fovPort;
1439 float eyeReliefInMeters;
1440 float offsetToRightInMeters;
1441 if ( eyeType == StereoEye_Right )
1443 eyeReliefInMeters = hmd.EyeRight.ReliefInMeters;
1444 offsetToRightInMeters = hmd.EyeRight.NoseToPupilInMeters - 0.5f * hmd.LensSeparationInMeters;
1446 else
1448 eyeReliefInMeters = hmd.EyeLeft.ReliefInMeters;
1449 offsetToRightInMeters = -(hmd.EyeLeft.NoseToPupilInMeters - 0.5f * hmd.LensSeparationInMeters);
1452 // Limit the eye-relief to 6 mm for FOV calculations since this just tends to spread off-screen
1453 // and get clamped anyways on DK1 (but in Unity it continues to spreads and causes
1454 // unnecessarily large render targets)
1455 eyeReliefInMeters = Alg::Max(eyeReliefInMeters, 0.006f);
1457 // Central view.
1458 fovPort = CalculateFovFromEyePosition ( eyeReliefInMeters,
1459 offsetToRightInMeters,
1460 0.0f,
1461 hmd.LensDiameterInMeters,
1462 extraEyeRotationInRadians );
1464 // clamp to the screen
1465 fovPort = ClampToPhysicalScreenFov ( eyeType, distortion, fovPort );
1467 return fovPort;
1472 FovPort GetPhysicalScreenFov ( StereoEye eyeType, DistortionRenderDesc const &distortion )
1474 OVR_UNUSED1 ( eyeType );
1476 FovPort resultFovPort;
1478 // Figure out the boundaries of the screen. We take the middle pixel of the screen,
1479 // move to each of the four screen edges, and transform those back into TanAngle space.
1480 Vector2f dmiddle = distortion.LensCenter;
1482 // The gotcha is that for some distortion functions, the map will "wrap around"
1483 // for screen pixels that are not actually visible to the user (especially on DK1,
1484 // which has a lot of invisible pixels), and map to pixels that are close to the middle.
1485 // This means the edges of the screen will actually be
1486 // "closer" than the visible bounds, so we'll clip too aggressively.
1488 // Solution - step gradually towards the boundary, noting the maximum distance.
1489 struct FunctionHider
1491 static FovPort FindRange ( Vector2f from, Vector2f to, int numSteps,
1492 DistortionRenderDesc const &distortionL )
1494 FovPort result;
1495 result.UpTan = 0.0f;
1496 result.DownTan = 0.0f;
1497 result.LeftTan = 0.0f;
1498 result.RightTan = 0.0f;
1500 float stepScale = 1.0f / ( numSteps - 1 );
1501 for ( int step = 0; step < numSteps; step++ )
1503 float lerpFactor = stepScale * (float)step;
1504 Vector2f sample = from + (to - from) * lerpFactor;
1505 Vector2f tanEyeAngle = TransformScreenNDCToTanFovSpace ( distortionL, sample );
1507 result.LeftTan = Alg::Max ( result.LeftTan, -tanEyeAngle.x );
1508 result.RightTan = Alg::Max ( result.RightTan, tanEyeAngle.x );
1509 result.UpTan = Alg::Max ( result.UpTan, -tanEyeAngle.y );
1510 result.DownTan = Alg::Max ( result.DownTan, tanEyeAngle.y );
1512 return result;
1514 };
1516 FovPort leftFovPort = FunctionHider::FindRange( dmiddle, Vector2f( -1.0f, dmiddle.y ), 10, distortion );
1517 FovPort rightFovPort = FunctionHider::FindRange( dmiddle, Vector2f( 1.0f, dmiddle.y ), 10, distortion );
1518 FovPort upFovPort = FunctionHider::FindRange( dmiddle, Vector2f( dmiddle.x, -1.0f ), 10, distortion );
1519 FovPort downFovPort = FunctionHider::FindRange( dmiddle, Vector2f( dmiddle.x, 1.0f ), 10, distortion );
1521 resultFovPort.LeftTan = leftFovPort.LeftTan;
1522 resultFovPort.RightTan = rightFovPort.RightTan;
1523 resultFovPort.UpTan = upFovPort.UpTan;
1524 resultFovPort.DownTan = downFovPort.DownTan;
1526 return resultFovPort;
1529 FovPort ClampToPhysicalScreenFov( StereoEye eyeType, DistortionRenderDesc const &distortion,
1530 FovPort inputFovPort )
1532 FovPort resultFovPort;
1533 FovPort phsyicalFovPort = GetPhysicalScreenFov ( eyeType, distortion );
1534 resultFovPort.LeftTan = Alg::Min ( inputFovPort.LeftTan, phsyicalFovPort.LeftTan );
1535 resultFovPort.RightTan = Alg::Min ( inputFovPort.RightTan, phsyicalFovPort.RightTan );
1536 resultFovPort.UpTan = Alg::Min ( inputFovPort.UpTan, phsyicalFovPort.UpTan );
1537 resultFovPort.DownTan = Alg::Min ( inputFovPort.DownTan, phsyicalFovPort.DownTan );
1539 return resultFovPort;
1542 Sizei CalculateIdealPixelSize ( StereoEye eyeType, DistortionRenderDesc const &distortion,
1543 FovPort tanHalfFov, float pixelsPerDisplayPixel )
1545 OVR_UNUSED(eyeType); // might be useful in the future if we do overlapping fovs
1547 Sizei result;
1548 // 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.
1549 result.w = (int)(0.5f + pixelsPerDisplayPixel * distortion.PixelsPerTanAngleAtCenter.x * ( tanHalfFov.LeftTan + tanHalfFov.RightTan ) );
1550 result.h = (int)(0.5f + pixelsPerDisplayPixel * distortion.PixelsPerTanAngleAtCenter.y * ( tanHalfFov.UpTan + tanHalfFov.DownTan ) );
1551 return result;
1554 Recti GetFramebufferViewport ( StereoEye eyeType, HmdRenderInfo const &hmd )
1556 Recti result;
1557 result.w = hmd.ResolutionInPixels.w/2;
1558 result.h = hmd.ResolutionInPixels.h;
1559 result.x = 0;
1560 result.y = 0;
1561 if ( eyeType == StereoEye_Right )
1563 result.x = (hmd.ResolutionInPixels.w+1)/2; // Round up, not down.
1565 return result;
1569 ScaleAndOffset2D CreateNDCScaleAndOffsetFromFov ( FovPort tanHalfFov )
1571 float projXScale = 2.0f / ( tanHalfFov.LeftTan + tanHalfFov.RightTan );
1572 float projXOffset = ( tanHalfFov.LeftTan - tanHalfFov.RightTan ) * projXScale * 0.5f;
1573 float projYScale = 2.0f / ( tanHalfFov.UpTan + tanHalfFov.DownTan );
1574 float projYOffset = ( tanHalfFov.UpTan - tanHalfFov.DownTan ) * projYScale * 0.5f;
1576 ScaleAndOffset2D result;
1577 result.Scale = Vector2f(projXScale, projYScale);
1578 result.Offset = Vector2f(projXOffset, projYOffset);
1579 // Hey - why is that Y.Offset negated?
1580 // It's because a projection matrix transforms from world coords with Y=up,
1581 // whereas this is from NDC which is Y=down.
1583 return result;
1587 ScaleAndOffset2D CreateUVScaleAndOffsetfromNDCScaleandOffset ( ScaleAndOffset2D scaleAndOffsetNDC,
1588 Recti renderedViewport,
1589 Sizei renderTargetSize )
1591 // scaleAndOffsetNDC takes you to NDC space [-1,+1] within the given viewport on the rendertarget.
1592 // We want a scale to instead go to actual UV coordinates you can sample with,
1593 // which need [0,1] and ignore the viewport.
1594 ScaleAndOffset2D result;
1595 // Scale [-1,1] to [0,1]
1596 result.Scale = scaleAndOffsetNDC.Scale * 0.5f;
1597 result.Offset = scaleAndOffsetNDC.Offset * 0.5f + Vector2f(0.5f);
1599 // ...but we will have rendered to a subsection of the RT, so scale for that.
1600 Vector2f scale( (float)renderedViewport.w / (float)renderTargetSize.w,
1601 (float)renderedViewport.h / (float)renderTargetSize.h );
1602 Vector2f offset( (float)renderedViewport.x / (float)renderTargetSize.w,
1603 (float)renderedViewport.y / (float)renderTargetSize.h );
1605 result.Scale = result.Scale.EntrywiseMultiply(scale);
1606 result.Offset = result.Offset.EntrywiseMultiply(scale) + offset;
1607 return result;
1612 Matrix4f CreateProjection( bool rightHanded, FovPort tanHalfFov,
1613 float zNear /*= 0.01f*/, float zFar /*= 10000.0f*/ )
1615 // A projection matrix is very like a scaling from NDC, so we can start with that.
1616 ScaleAndOffset2D scaleAndOffset = CreateNDCScaleAndOffsetFromFov ( tanHalfFov );
1618 float handednessScale = 1.0f;
1619 if ( rightHanded )
1621 handednessScale = -1.0f;
1624 Matrix4f projection;
1625 // Produces X result, mapping clip edges to [-w,+w]
1626 projection.M[0][0] = scaleAndOffset.Scale.x;
1627 projection.M[0][1] = 0.0f;
1628 projection.M[0][2] = handednessScale * scaleAndOffset.Offset.x;
1629 projection.M[0][3] = 0.0f;
1631 // Produces Y result, mapping clip edges to [-w,+w]
1632 // Hey - why is that YOffset negated?
1633 // It's because a projection matrix transforms from world coords with Y=up,
1634 // whereas this is derived from an NDC scaling, which is Y=down.
1635 projection.M[1][0] = 0.0f;
1636 projection.M[1][1] = scaleAndOffset.Scale.y;
1637 projection.M[1][2] = handednessScale * -scaleAndOffset.Offset.y;
1638 projection.M[1][3] = 0.0f;
1640 // Produces Z-buffer result - app needs to fill this in with whatever Z range it wants.
1641 // We'll just use some defaults for now.
1642 projection.M[2][0] = 0.0f;
1643 projection.M[2][1] = 0.0f;
1644 projection.M[2][2] = -handednessScale * zFar / (zNear - zFar);
1645 projection.M[2][3] = (zFar * zNear) / (zNear - zFar);
1647 // Produces W result (= Z in)
1648 projection.M[3][0] = 0.0f;
1649 projection.M[3][1] = 0.0f;
1650 projection.M[3][2] = handednessScale;
1651 projection.M[3][3] = 0.0f;
1653 return projection;
1657 Matrix4f CreateOrthoSubProjection ( bool rightHanded, StereoEye eyeType,
1658 float tanHalfFovX, float tanHalfFovY,
1659 float unitsX, float unitsY,
1660 float distanceFromCamera, float interpupillaryDistance,
1661 Matrix4f const &projection,
1662 float zNear /*= 0.0f*/, float zFar /*= 0.0f*/ )
1664 OVR_UNUSED1 ( rightHanded );
1666 float orthoHorizontalOffset = interpupillaryDistance * 0.5f / distanceFromCamera;
1667 switch ( eyeType )
1669 case StereoEye_Center:
1670 orthoHorizontalOffset = 0.0f;
1671 break;
1672 case StereoEye_Left:
1673 break;
1674 case StereoEye_Right:
1675 orthoHorizontalOffset = -orthoHorizontalOffset;
1676 break;
1677 default: OVR_ASSERT ( false ); break;
1680 // Current projection maps real-world vector (x,y,1) to the RT.
1681 // We want to find the projection that maps the range [-FovPixels/2,FovPixels/2] to
1682 // the physical [-orthoHalfFov,orthoHalfFov]
1683 // Note moving the offset from M[0][2]+M[1][2] to M[0][3]+M[1][3] - this means
1684 // we don't have to feed in Z=1 all the time.
1685 // The horizontal offset math is a little hinky because the destination is
1686 // actually [-orthoHalfFov+orthoHorizontalOffset,orthoHalfFov+orthoHorizontalOffset]
1687 // So we need to first map [-FovPixels/2,FovPixels/2] to
1688 // [-orthoHalfFov+orthoHorizontalOffset,orthoHalfFov+orthoHorizontalOffset]:
1689 // x1 = x0 * orthoHalfFov/(FovPixels/2) + orthoHorizontalOffset;
1690 // = x0 * 2*orthoHalfFov/FovPixels + orthoHorizontalOffset;
1691 // But then we need the sam mapping as the existing projection matrix, i.e.
1692 // x2 = x1 * Projection.M[0][0] + Projection.M[0][2];
1693 // = x0 * (2*orthoHalfFov/FovPixels + orthoHorizontalOffset) * Projection.M[0][0] + Projection.M[0][2];
1694 // = x0 * Projection.M[0][0]*2*orthoHalfFov/FovPixels +
1695 // orthoHorizontalOffset*Projection.M[0][0] + Projection.M[0][2];
1696 // So in the new projection matrix we need to scale by Projection.M[0][0]*2*orthoHalfFov/FovPixels and
1697 // offset by orthoHorizontalOffset*Projection.M[0][0] + Projection.M[0][2].
1699 float orthoScaleX = 2.0f * tanHalfFovX / unitsX;
1700 float orthoScaleY = 2.0f * tanHalfFovY / unitsY;
1701 Matrix4f ortho;
1702 ortho.M[0][0] = projection.M[0][0] * orthoScaleX;
1703 ortho.M[0][1] = 0.0f;
1704 ortho.M[0][2] = 0.0f;
1705 ortho.M[0][3] = -projection.M[0][2] + ( orthoHorizontalOffset * projection.M[0][0] );
1707 ortho.M[1][0] = 0.0f;
1708 ortho.M[1][1] = -projection.M[1][1] * orthoScaleY; // Note sign flip (text rendering uses Y=down).
1709 ortho.M[1][2] = 0.0f;
1710 ortho.M[1][3] = -projection.M[1][2];
1712 if ( fabsf ( zNear - zFar ) < 0.001f )
1714 ortho.M[2][0] = 0.0f;
1715 ortho.M[2][1] = 0.0f;
1716 ortho.M[2][2] = 0.0f;
1717 ortho.M[2][3] = zFar;
1719 else
1721 ortho.M[2][0] = 0.0f;
1722 ortho.M[2][1] = 0.0f;
1723 ortho.M[2][2] = zFar / (zNear - zFar);
1724 ortho.M[2][3] = (zFar * zNear) / (zNear - zFar);
1727 // No perspective correction for ortho.
1728 ortho.M[3][0] = 0.0f;
1729 ortho.M[3][1] = 0.0f;
1730 ortho.M[3][2] = 0.0f;
1731 ortho.M[3][3] = 1.0f;
1733 return ortho;
1737 //-----------------------------------------------------------------------------------
1738 // A set of "forward-mapping" functions, mapping from framebuffer space to real-world and/or texture space.
1740 // This mimics the first half of the distortion shader's function.
1741 Vector2f TransformScreenNDCToTanFovSpace( DistortionRenderDesc const &distortion,
1742 const Vector2f &framebufferNDC )
1744 // Scale to TanHalfFov space, but still distorted.
1745 Vector2f tanEyeAngleDistorted;
1746 tanEyeAngleDistorted.x = ( framebufferNDC.x - distortion.LensCenter.x ) * distortion.TanEyeAngleScale.x;
1747 tanEyeAngleDistorted.y = ( framebufferNDC.y - distortion.LensCenter.y ) * distortion.TanEyeAngleScale.y;
1748 // Distort.
1749 float radiusSquared = ( tanEyeAngleDistorted.x * tanEyeAngleDistorted.x )
1750 + ( tanEyeAngleDistorted.y * tanEyeAngleDistorted.y );
1751 float distortionScale = distortion.Lens.DistortionFnScaleRadiusSquared ( radiusSquared );
1752 Vector2f tanEyeAngle;
1753 tanEyeAngle.x = tanEyeAngleDistorted.x * distortionScale;
1754 tanEyeAngle.y = tanEyeAngleDistorted.y * distortionScale;
1756 return tanEyeAngle;
1759 // Same, with chromatic aberration correction.
1760 void TransformScreenNDCToTanFovSpaceChroma ( Vector2f *resultR, Vector2f *resultG, Vector2f *resultB,
1761 DistortionRenderDesc const &distortion,
1762 const Vector2f &framebufferNDC )
1764 // Scale to TanHalfFov space, but still distorted.
1765 Vector2f tanEyeAngleDistorted;
1766 tanEyeAngleDistorted.x = ( framebufferNDC.x - distortion.LensCenter.x ) * distortion.TanEyeAngleScale.x;
1767 tanEyeAngleDistorted.y = ( framebufferNDC.y - distortion.LensCenter.y ) * distortion.TanEyeAngleScale.y;
1768 // Distort.
1769 float radiusSquared = ( tanEyeAngleDistorted.x * tanEyeAngleDistorted.x )
1770 + ( tanEyeAngleDistorted.y * tanEyeAngleDistorted.y );
1771 Vector3f distortionScales = distortion.Lens.DistortionFnScaleRadiusSquaredChroma ( radiusSquared );
1772 *resultR = tanEyeAngleDistorted * distortionScales.x;
1773 *resultG = tanEyeAngleDistorted * distortionScales.y;
1774 *resultB = tanEyeAngleDistorted * distortionScales.z;
1777 // This mimics the second half of the distortion shader's function.
1778 Vector2f TransformTanFovSpaceToRendertargetTexUV( ScaleAndOffset2D const &eyeToSourceUV,
1779 Vector2f const &tanEyeAngle )
1781 Vector2f textureUV;
1782 textureUV.x = tanEyeAngle.x * eyeToSourceUV.Scale.x + eyeToSourceUV.Offset.x;
1783 textureUV.y = tanEyeAngle.y * eyeToSourceUV.Scale.y + eyeToSourceUV.Offset.y;
1784 return textureUV;
1787 Vector2f TransformTanFovSpaceToRendertargetNDC( ScaleAndOffset2D const &eyeToSourceNDC,
1788 Vector2f const &tanEyeAngle )
1790 Vector2f textureNDC;
1791 textureNDC.x = tanEyeAngle.x * eyeToSourceNDC.Scale.x + eyeToSourceNDC.Offset.x;
1792 textureNDC.y = tanEyeAngle.y * eyeToSourceNDC.Scale.y + eyeToSourceNDC.Offset.y;
1793 return textureNDC;
1796 Vector2f TransformScreenPixelToScreenNDC( Recti const &distortionViewport,
1797 Vector2f const &pixel )
1799 // Move to [-1,1] NDC coords.
1800 Vector2f framebufferNDC;
1801 framebufferNDC.x = -1.0f + 2.0f * ( ( pixel.x - (float)distortionViewport.x ) / (float)distortionViewport.w );
1802 framebufferNDC.y = -1.0f + 2.0f * ( ( pixel.y - (float)distortionViewport.y ) / (float)distortionViewport.h );
1803 return framebufferNDC;
1806 Vector2f TransformScreenPixelToTanFovSpace( Recti const &distortionViewport,
1807 DistortionRenderDesc const &distortion,
1808 Vector2f const &pixel )
1810 return TransformScreenNDCToTanFovSpace( distortion,
1811 TransformScreenPixelToScreenNDC( distortionViewport, pixel ) );
1814 Vector2f TransformScreenNDCToRendertargetTexUV( DistortionRenderDesc const &distortion,
1815 StereoEyeParams const &eyeParams,
1816 Vector2f const &pixel )
1818 return TransformTanFovSpaceToRendertargetTexUV ( eyeParams,
1819 TransformScreenNDCToTanFovSpace ( distortion, pixel ) );
1822 Vector2f TransformScreenPixelToRendertargetTexUV( Recti const &distortionViewport,
1823 DistortionRenderDesc const &distortion,
1824 StereoEyeParams const &eyeParams,
1825 Vector2f const &pixel )
1827 return TransformTanFovSpaceToRendertargetTexUV ( eyeParams,
1828 TransformScreenPixelToTanFovSpace ( distortionViewport, distortion, pixel ) );
1832 //-----------------------------------------------------------------------------------
1833 // A set of "reverse-mapping" functions, mapping from real-world and/or texture space back to the framebuffer.
1835 Vector2f TransformTanFovSpaceToScreenNDC( DistortionRenderDesc const &distortion,
1836 const Vector2f &tanEyeAngle, bool usePolyApprox /*= false*/ )
1838 float tanEyeAngleRadius = tanEyeAngle.Length();
1839 float tanEyeAngleDistortedRadius = distortion.Lens.DistortionFnInverseApprox ( tanEyeAngleRadius );
1840 if ( !usePolyApprox )
1842 tanEyeAngleDistortedRadius = distortion.Lens.DistortionFnInverse ( tanEyeAngleRadius );
1844 Vector2f tanEyeAngleDistorted = tanEyeAngle;
1845 if ( tanEyeAngleRadius > 0.0f )
1847 tanEyeAngleDistorted = tanEyeAngle * ( tanEyeAngleDistortedRadius / tanEyeAngleRadius );
1850 Vector2f framebufferNDC;
1851 framebufferNDC.x = ( tanEyeAngleDistorted.x / distortion.TanEyeAngleScale.x ) + distortion.LensCenter.x;
1852 framebufferNDC.y = ( tanEyeAngleDistorted.y / distortion.TanEyeAngleScale.y ) + distortion.LensCenter.y;
1854 return framebufferNDC;
1857 Vector2f TransformRendertargetNDCToTanFovSpace( const ScaleAndOffset2D &eyeToSourceNDC,
1858 const Vector2f &textureNDC )
1860 Vector2f tanEyeAngle = (textureNDC - eyeToSourceNDC.Offset) / eyeToSourceNDC.Scale;
1861 return tanEyeAngle;
1866 } //namespace OVR
1868 //Just want to make a copy disentangled from all these namespaces!
1869 float ExtEvalCatmullRom10Spline ( float const *K, float scaledVal )
1871 return(OVR::EvalCatmullRom10Spline ( K, scaledVal ));