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