Version 1.24
[libovr-mingw-w64-jolly.git] / Shim / OVR_StereoProjection.cpp
1 /************************************************************************************\r
2 \r
3 Filename    :   OVR_StereoProjection.cpp\r
4 Content     :   Stereo rendering functions\r
5 Created     :   November 30, 2013\r
6 Authors     :   Tom Fosyth\r
7 \r
8 Copyright   :   Copyright 2014-2016 Oculus VR, LLC All Rights reserved.\r
9 \r
10 Licensed under the Oculus VR Rift SDK License Version 3.3 (the "License");\r
11 you may not use the Oculus VR Rift SDK except in compliance with the License,\r
12 which is provided at the time of installation or download, or which\r
13 otherwise accompanies this software in either electronic or hard copy form.\r
14 \r
15 You may obtain a copy of the License at\r
16 \r
17 http://www.oculusvr.com/licenses/LICENSE-3.3\r
18 \r
19 Unless required by applicable law or agreed to in writing, the Oculus VR SDK\r
20 distributed under the License is distributed on an "AS IS" BASIS,\r
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
22 See the License for the specific language governing permissions and\r
23 limitations under the License.\r
24 \r
25 *************************************************************************************/\r
26 \r
27 #include <Extras/OVR_StereoProjection.h>\r
28 \r
29 namespace OVR {\r
30 \r
31 ScaleAndOffset2D CreateNDCScaleAndOffsetFromFov(FovPort tanHalfFov) {\r
32   float projXScale = 2.0f / (tanHalfFov.LeftTan + tanHalfFov.RightTan);\r
33   float projXOffset = (tanHalfFov.LeftTan - tanHalfFov.RightTan) * projXScale * 0.5f;\r
34   float projYScale = 2.0f / (tanHalfFov.UpTan + tanHalfFov.DownTan);\r
35   float projYOffset = (tanHalfFov.UpTan - tanHalfFov.DownTan) * projYScale * 0.5f;\r
36 \r
37   ScaleAndOffset2D result;\r
38   result.Scale = Vector2f(projXScale, projYScale);\r
39   result.Offset = Vector2f(projXOffset, projYOffset);\r
40   // Hey - why is that Y.Offset negated?\r
41   // It's because a projection matrix transforms from world coords with Y=up,\r
42   // whereas this is from NDC which is Y=down.\r
43 \r
44   return result;\r
45 }\r
46 \r
47 Matrix4f CreateProjection(\r
48     bool leftHanded,\r
49     bool isOpenGL,\r
50     FovPort tanHalfFov,\r
51     StereoEye /*eye*/,\r
52     float zNear /*= 0.01f*/,\r
53     float zFar /*= 10000.0f*/,\r
54     bool flipZ /*= false*/,\r
55     bool farAtInfinity /*= false*/) {\r
56   if (!flipZ && farAtInfinity) {\r
57     // OVR_ASSERT_M(false, "Cannot push Far Clip to Infinity when Z-order is not flipped");\r
58     // Assertion disabled because this code no longer has access to LibOVRKernel assertion\r
59     // functionality.\r
60     farAtInfinity = false;\r
61   }\r
62 \r
63   // A projection matrix is very like a scaling from NDC, so we can start with that.\r
64   ScaleAndOffset2D scaleAndOffset = CreateNDCScaleAndOffsetFromFov(tanHalfFov);\r
65 \r
66   float handednessScale = leftHanded ? 1.0f : -1.0f;\r
67 \r
68   Matrix4f projection;\r
69   // Produces X result, mapping clip edges to [-w,+w]\r
70   projection.M[0][0] = scaleAndOffset.Scale.x;\r
71   projection.M[0][1] = 0.0f;\r
72   projection.M[0][2] = handednessScale * scaleAndOffset.Offset.x;\r
73   projection.M[0][3] = 0.0f;\r
74 \r
75   // Produces Y result, mapping clip edges to [-w,+w]\r
76   // Hey - why is that YOffset negated?\r
77   // It's because a projection matrix transforms from world coords with Y=up,\r
78   // whereas this is derived from an NDC scaling, which is Y=down.\r
79   projection.M[1][0] = 0.0f;\r
80   projection.M[1][1] = scaleAndOffset.Scale.y;\r
81   projection.M[1][2] = handednessScale * -scaleAndOffset.Offset.y;\r
82   projection.M[1][3] = 0.0f;\r
83 \r
84   // Produces Z-buffer result - app needs to fill this in with whatever Z range it wants.\r
85   // We'll just use some defaults for now.\r
86   projection.M[2][0] = 0.0f;\r
87   projection.M[2][1] = 0.0f;\r
88 \r
89   if (farAtInfinity) {\r
90     if (isOpenGL) {\r
91       // It's not clear this makes sense for OpenGL - you don't get the same precision benefits you\r
92       // do in D3D.\r
93       projection.M[2][2] = -handednessScale;\r
94       projection.M[2][3] = 2.0f * zNear;\r
95     } else {\r
96       projection.M[2][2] = 0.0f;\r
97       projection.M[2][3] = zNear;\r
98     }\r
99   } else {\r
100     if (isOpenGL) {\r
101       // Clip range is [-w,+w], so 0 is at the middle of the range.\r
102       projection.M[2][2] =\r
103           -handednessScale * (flipZ ? -1.0f : 1.0f) * (zNear + zFar) / (zNear - zFar);\r
104       projection.M[2][3] = 2.0f * ((flipZ ? -zFar : zFar) * zNear) / (zNear - zFar);\r
105     } else {\r
106       // Clip range is [0,+w], so 0 is at the start of the range.\r
107       projection.M[2][2] = -handednessScale * (flipZ ? -zNear : zFar) / (zNear - zFar);\r
108       projection.M[2][3] = ((flipZ ? -zFar : zFar) * zNear) / (zNear - zFar);\r
109     }\r
110   }\r
111 \r
112   // Produces W result (= Z in)\r
113   projection.M[3][0] = 0.0f;\r
114   projection.M[3][1] = 0.0f;\r
115   projection.M[3][2] = handednessScale;\r
116   projection.M[3][3] = 0.0f;\r
117 \r
118   return projection;\r
119 }\r
120 \r
121 Matrix4f CreateOrthoSubProjection(\r
122     bool /*rightHanded*/,\r
123     StereoEye eyeType,\r
124     float tanHalfFovX,\r
125     float tanHalfFovY,\r
126     float unitsX,\r
127     float unitsY,\r
128     float distanceFromCamera,\r
129     float interpupillaryDistance,\r
130     Matrix4f const& projection,\r
131     float zNear /*= 0.0f*/,\r
132     float zFar /*= 0.0f*/,\r
133     bool flipZ /*= false*/,\r
134     bool farAtInfinity /*= false*/) {\r
135   if (!flipZ && farAtInfinity) {\r
136     // OVR_ASSERT_M(false, "Cannot push Far Clip to Infinity when Z-order is not flipped");\r
137     // Assertion disabled because this code no longer has access to LibOVRKernel assertion\r
138     // functionality.\r
139     farAtInfinity = false;\r
140   }\r
141 \r
142   float orthoHorizontalOffset = interpupillaryDistance * 0.5f / distanceFromCamera;\r
143   switch (eyeType) {\r
144     case StereoEye_Left:\r
145       break;\r
146     case StereoEye_Right:\r
147       orthoHorizontalOffset = -orthoHorizontalOffset;\r
148       break;\r
149     case StereoEye_Center:\r
150       orthoHorizontalOffset = 0.0f;\r
151       break;\r
152     default:\r
153       break;\r
154   }\r
155 \r
156   // Current projection maps real-world vector (x,y,1) to the RT.\r
157   // We want to find the projection that maps the range [-FovPixels/2,FovPixels/2] to\r
158   // the physical [-orthoHalfFov,orthoHalfFov]\r
159   // Note moving the offset from M[0][2]+M[1][2] to M[0][3]+M[1][3] - this means\r
160   // we don't have to feed in Z=1 all the time.\r
161   // The horizontal offset math is a little hinky because the destination is\r
162   // actually [-orthoHalfFov+orthoHorizontalOffset,orthoHalfFov+orthoHorizontalOffset]\r
163   // So we need to first map [-FovPixels/2,FovPixels/2] to\r
164   //                         [-orthoHalfFov+orthoHorizontalOffset,orthoHalfFov+orthoHorizontalOffset]:\r
165   // x1 = x0 * orthoHalfFov/(FovPixels/2) + orthoHorizontalOffset;\r
166   //    = x0 * 2*orthoHalfFov/FovPixels + orthoHorizontalOffset;\r
167   // But then we need the sam mapping as the existing projection matrix, i.e.\r
168   // x2 = x1 * Projection.M[0][0] + Projection.M[0][2];\r
169   //    = x0 * (2*orthoHalfFov/FovPixels + orthoHorizontalOffset) * Projection.M[0][0] +\r
170   //    Projection.M[0][2];\r
171   //    = x0 * Projection.M[0][0]*2*orthoHalfFov/FovPixels +\r
172   //      orthoHorizontalOffset*Projection.M[0][0] + Projection.M[0][2];\r
173   // So in the new projection matrix we need to scale by Projection.M[0][0]*2*orthoHalfFov/FovPixels\r
174   // and\r
175   // offset by orthoHorizontalOffset*Projection.M[0][0] + Projection.M[0][2].\r
176 \r
177   float orthoScaleX = 2.0f * tanHalfFovX / unitsX;\r
178   float orthoScaleY = 2.0f * tanHalfFovY / unitsY;\r
179   Matrix4f ortho;\r
180   ortho.M[0][0] = projection.M[0][0] * orthoScaleX;\r
181   ortho.M[0][1] = 0.0f;\r
182   ortho.M[0][2] = 0.0f;\r
183   ortho.M[0][3] = -projection.M[0][2] + (orthoHorizontalOffset * projection.M[0][0]);\r
184 \r
185   ortho.M[1][0] = 0.0f;\r
186   ortho.M[1][1] = -projection.M[1][1] * orthoScaleY; // Note sign flip (text rendering uses Y=down).\r
187   ortho.M[1][2] = 0.0f;\r
188   ortho.M[1][3] = -projection.M[1][2];\r
189 \r
190   const float zDiff = zNear - zFar;\r
191   if (fabsf(zDiff) < 0.001f) {\r
192     ortho.M[2][0] = 0.0f;\r
193     ortho.M[2][1] = 0.0f;\r
194     ortho.M[2][2] = 0.0f;\r
195     ortho.M[2][3] = flipZ ? zNear : zFar;\r
196   } else {\r
197     ortho.M[2][0] = 0.0f;\r
198     ortho.M[2][1] = 0.0f;\r
199 \r
200     if (farAtInfinity) {\r
201       ortho.M[2][2] = 0.0f;\r
202       ortho.M[2][3] = zNear;\r
203     } else if (zDiff != 0.0f) {\r
204       ortho.M[2][2] = (flipZ ? zNear : zFar) / zDiff;\r
205       ortho.M[2][3] = ((flipZ ? -zFar : zFar) * zNear) / zDiff;\r
206     }\r
207   }\r
208 \r
209   // No perspective correction for ortho.\r
210   ortho.M[3][0] = 0.0f;\r
211   ortho.M[3][1] = 0.0f;\r
212   ortho.M[3][2] = 0.0f;\r
213   ortho.M[3][3] = 1.0f;\r
214 \r
215   return ortho;\r
216 }\r
217 \r
218 } // namespace OVR\r