1 /** @file vrconfig.cpp Virtual reality configuration.
2 *
3 * @authors Copyright (c) 2014-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
4 * @authors Copyright (c) 2013 Christopher Bruns <cmbruns@rotatingpenguin.com>
5 *
6 * @par License
7 * LGPL: http://www.gnu.org/licenses/lgpl.html
8 *
9 * <small>This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU Lesser General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or (at your
12 * option) any later version. This program is distributed in the hope that it
13 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
14 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
15 * General Public License for more details. You should have received a copy of
16 * the GNU Lesser General Public License along with this program; if not, see:
17 * http://www.gnu.org/licenses</small>
18 */
19
20 #include "de/VRConfig"
21 #include "de/math.h"
22
23 namespace de {
24
DENG2_PIMPL(VRConfig)25 DENG2_PIMPL(VRConfig)
26 {
27 StereoMode mode;
28 Eye currentEye = NeitherEye;
29 de::OculusRift ovr;
30 float screenDistance;
31 float ipd;
32 float eyeHeightInMapUnits;
33 float eyeShift;
34 float playerPhysicalHeight;
35 bool swapEyes;
36 int riftFramebufferSamples; // Multisampling used in unwarped Rift framebuffer
37
38 /**
39 * Unlike most 3D modes, Oculus Rift typically uses no frustum shift. (or if we did,
40 * it would be different and complicated)
41 */
42 bool frustumShift;
43
44 float dominantEye; ///< Kludge for aim-down-weapon-sight modes
45
46 Impl(Public *i)
47 : Base(i)
48 , mode(Mono)
49 , screenDistance(20.f)
50 , ipd(.064f) // average male IPD
51 , eyeHeightInMapUnits(41)
52 , eyeShift(0)
53 , playerPhysicalHeight(1.75f)
54 , swapEyes(false)
55 , riftFramebufferSamples(1)
56 , frustumShift(true)
57 , dominantEye(0.0f)
58 {}
59
60 float mapUnitsPerMeter() const
61 {
62 // 0.925 because eyes are not at top of head
63 return eyeHeightInMapUnits / (0.925 * playerPhysicalHeight);
64 }
65 };
66
VRConfig()67 VRConfig::VRConfig() : d(new Impl(this))
68 {}
69
setMode(StereoMode newMode)70 void VRConfig::setMode(StereoMode newMode)
71 {
72 d->mode = newMode;
73 }
74
setScreenDistance(float distance)75 void VRConfig::setScreenDistance(float distance)
76 {
77 d->screenDistance = distance;
78 }
79
setEyeHeightInMapUnits(float eyeHeightInMapUnits)80 void VRConfig::setEyeHeightInMapUnits(float eyeHeightInMapUnits)
81 {
82 d->eyeHeightInMapUnits = eyeHeightInMapUnits;
83 }
84
setInterpupillaryDistance(float ipd)85 void VRConfig::setInterpupillaryDistance(float ipd)
86 {
87 d->ipd = ipd;
88 }
89
setPhysicalPlayerHeight(float heightInMeters)90 void VRConfig::setPhysicalPlayerHeight(float heightInMeters)
91 {
92 d->playerPhysicalHeight = heightInMeters;
93 }
94
setCurrentEye(Eye eye)95 void VRConfig::setCurrentEye(Eye eye)
96 {
97 float eyePos = (eye == NeitherEye? 0 : eye == LeftEye? -1 : 1);
98
99 d->currentEye = eye;
100 d->eyeShift = d->mapUnitsPerMeter() * (eyePos - d->dominantEye) * 0.5 * d->ipd;
101 if (d->swapEyes)
102 {
103 d->eyeShift *= -1;
104 }
105 }
106
currentEye() const107 VRConfig::Eye VRConfig::currentEye() const
108 {
109 return d->currentEye;
110 }
111
enableFrustumShift(bool enable)112 void VRConfig::enableFrustumShift(bool enable)
113 {
114 d->frustumShift = enable;
115 }
116
setRiftFramebufferSampleCount(int samples)117 void VRConfig::setRiftFramebufferSampleCount(int samples)
118 {
119 d->riftFramebufferSamples = samples;
120 }
121
setSwapEyes(bool swapped)122 void VRConfig::setSwapEyes(bool swapped)
123 {
124 d->swapEyes = swapped;
125 }
126
setDominantEye(float value)127 void VRConfig::setDominantEye(float value)
128 {
129 d->dominantEye = value;
130 }
131
mode() const132 VRConfig::StereoMode VRConfig::mode() const
133 {
134 return d->mode;
135 }
136
screenDistance() const137 float VRConfig::screenDistance() const
138 {
139 return d->screenDistance;
140 }
141
needsStereoGLFormat() const142 bool VRConfig::needsStereoGLFormat() const
143 {
144 return modeNeedsStereoGLFormat(mode());
145 }
146
modeNeedsStereoGLFormat(StereoMode mode)147 bool VRConfig::modeNeedsStereoGLFormat(StereoMode mode)
148 {
149 return mode == QuadBuffered;
150 }
151
interpupillaryDistance() const152 float VRConfig::interpupillaryDistance() const
153 {
154 return d->ipd;
155 }
156
eyeHeightInMapUnits() const157 float VRConfig::eyeHeightInMapUnits() const
158 {
159 return d->eyeHeightInMapUnits;
160 }
161
mapUnitsPerMeter() const162 float VRConfig::mapUnitsPerMeter() const
163 {
164 return d->mapUnitsPerMeter();
165 }
166
physicalPlayerHeight() const167 float VRConfig::physicalPlayerHeight() const
168 {
169 return d->playerPhysicalHeight;
170 }
171
eyeShift() const172 float VRConfig::eyeShift() const
173 {
174 return d->eyeShift;
175 }
176
frustumShift() const177 bool VRConfig::frustumShift() const
178 {
179 return d->frustumShift;
180 }
181
swapEyes() const182 bool VRConfig::swapEyes() const
183 {
184 return d->swapEyes;
185 }
186
dominantEye() const187 float VRConfig::dominantEye() const
188 {
189 return d->dominantEye;
190 }
191
riftFramebufferSampleCount() const192 int VRConfig::riftFramebufferSampleCount() const
193 {
194 return d->riftFramebufferSamples;
195 }
196
viewAspect(Vector2f const & viewPortSize) const197 float VRConfig::viewAspect(Vector2f const &viewPortSize) const
198 {
199 /*if (mode() == OculusRift)
200 {
201 // Override with the Oculus Rift's aspect ratio.
202 return oculusRift().aspect();
203 }*/
204
205 // We're assuming pixels are squares.
206 return viewPortSize.x / viewPortSize.y;
207 }
208
verticalFieldOfView(float horizFovDegrees,Vector2f const & viewPortSize) const209 float VRConfig::verticalFieldOfView(float horizFovDegrees, Vector2f const &viewPortSize) const
210 {
211 // We're assuming pixels are squares.
212 float const aspect = viewAspect(viewPortSize);
213
214 if (mode() == OculusRift)
215 {
216 // A little trigonometry to apply aspect ratio to angles
217 float x = std::tan(.5f * degreeToRadian(horizFovDegrees));
218 return radianToDegree(2.f * std::atan2(x / aspect, 1.f));
219 }
220
221 return clamp(1.f, horizFovDegrees / aspect, 179.f);
222 }
223
projectionMatrix(float fovDegrees,Vector2f const & viewPortSize,float nearClip,float farClip) const224 Matrix4f VRConfig::projectionMatrix(float fovDegrees,
225 Vector2f const &viewPortSize,
226 float nearClip, float farClip) const
227 {
228 if (mode() == OculusRift && oculusRift().isReady())
229 {
230 // OVR will calculate our projection matrix.
231 float const mapUnits = d->mapUnitsPerMeter();
232 return oculusRift().projection(nearClip, farClip) *
233 Matrix4f::translate(oculusRift().eyeOffset() * mapUnits);
234 }
235 return Matrix4f::perspective(fovDegrees, viewAspect(viewPortSize), nearClip, farClip) *
236 Matrix4f::translate(Vector3f(-eyeShift(), 0, 0));
237
238 #if 0
239 float const yfov = verticalFieldOfView(fovDegrees, viewPortSize);
240 float const fH = std::tan(.5f * degreeToRadian(yfov)) * nearClip;
241 float const fW = fH * viewAspect(viewPortSize);
242
243 /*
244 * Asymmetric frustum shift is computed to realign screen-depth items after view point has shifted.
245 * Asymmetric frustum shift method is probably superior to competing toe-in stereo 3D method:
246 * - AFS preserves identical near and far clipping planes in both views
247 * - AFS shows items at/near infinity better
248 * - AFS conforms to what stereo 3D photographers call "ortho stereo"
249 * Asymmetric frustum shift is used for all stereo 3D modes except Oculus Rift mode, which only
250 * applies the viewpoint shift.
251 */
252 float shift = 0;
253 if (frustumShift())
254 {
255 shift = eyeShift() * nearClip / screenDistance();
256 }
257
258 return Matrix4f::frustum(-fW - shift, fW - shift,
259 -fH, fH,
260 nearClip, farClip) *
261 Matrix4f::translate(Vector3f(-eyeShift(), 0, 0));
262 #endif
263 }
264
oculusRift()265 OculusRift &VRConfig::oculusRift()
266 {
267 return d->ovr;
268 }
269
oculusRift() const270 OculusRift const &VRConfig::oculusRift() const
271 {
272 return d->ovr;
273 }
274
modeAppliesDisplacement(StereoMode mode)275 bool VRConfig::modeAppliesDisplacement(StereoMode mode)
276 {
277 switch (mode)
278 {
279 case Mono:
280 case GreenMagenta:
281 case RedCyan:
282 case LeftOnly:
283 case RightOnly:
284 case QuadBuffered:
285 case RowInterleaved:
286 return false;
287
288 default:
289 break;
290 }
291 return true;
292 }
293
294 } // namespace de
295