2 * \file stereo_libvr.cpp
4 * \date 2013-08-19 19:17MT
5 * \author Thibaut Girka (ThibG)
9 // NeL - MMORPG Framework <https://wiki.ryzom.dev/>
10 // Copyright (C) 2013 Thibaut GIRKA (ThibG) <thib@sitedethib.com>
12 // This source file has been modified by the following contributors:
13 // Copyright (C) 2014 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
15 // This program is free software: you can redistribute it and/or modify
16 // it under the terms of the GNU Affero General Public License as
17 // published by the Free Software Foundation, either version 3 of the
18 // License, or (at your option) any later version.
20 // This program is distributed in the hope that it will be useful,
21 // but WITHOUT ANY WARRANTY; without even the implied warranty of
22 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 // GNU Affero General Public License for more details.
25 // You should have received a copy of the GNU Affero General Public License
26 // along with this program. If not, see <http://www.gnu.org/licenses/>.
31 #include "nel/misc/time_nl.h"
32 #include "nel/3d/stereo_libvr.h"
43 // #include <nel/misc/debug.h>
44 #include "nel/3d/u_camera.h"
45 #include "nel/3d/u_driver.h"
46 #include "nel/3d/material.h"
47 #include "nel/3d/texture_bloom.h"
48 #include "nel/3d/texture_user.h"
49 #include "nel/3d/driver_user.h"
50 #include "nel/3d/u_texture.h"
55 // using namespace NLMISC;
63 extern const char *g_StereoOVR_fp40
; //TODO: what?
64 extern const char *g_StereoOVR_arbfp1
; //TODO: what?
65 extern const char *g_StereoOVR_ps_2_0
; //TODO: what?
68 sint s_DeviceCounter
= 0;
71 class CStereoLibVRDeviceHandle
: public IStereoDeviceFactory
74 // fixme: virtual destructor???
75 IStereoDisplay
*createDevice() const
77 CStereoLibVR
*stereo
= new CStereoLibVR(this);
78 if (stereo
->isDeviceCreated())
85 class CStereoLibVRDevicePtr
88 struct hmd
*HMDDevice
;
89 struct display_info HMDInfo
;
90 float InterpupillaryDistance
;
93 CStereoLibVR::CStereoLibVR(const CStereoLibVRDeviceHandle
*handle
) : m_Stage(0), m_SubStage(0), m_OrientationCached(false), m_Driver(NULL
), m_BarrelTexU(NULL
), m_PixelProgram(NULL
), m_EyePosition(0.0f
, 0.09f
, 0.15f
), m_Scale(1.0f
)
95 struct stereo_config st_conf
;
98 // For now, LibVR doesn't support multiple devices...
99 m_DevicePtr
= new CStereoLibVRDevicePtr();
100 m_DevicePtr
->HMDDevice
= hmd_open_first(0);
101 m_DevicePtr
->InterpupillaryDistance
= 0.0647; //TODO
103 if (m_DevicePtr
->HMDDevice
)
105 hmd_get_display_info(m_DevicePtr
->HMDDevice
, &m_DevicePtr
->HMDInfo
);
106 hmd_get_stereo_config(m_DevicePtr
->HMDDevice
, &st_conf
);
107 nldebug("LibVR: HScreenSize: %f, VScreenSize: %f", m_DevicePtr
->HMDInfo
.h_screen_size
, m_DevicePtr
->HMDInfo
.v_screen_size
);
108 nldebug("LibVR: VScreenCenter: %f", m_DevicePtr
->HMDInfo
.v_center
);
109 nldebug("LibVR: EyeToScreenDistance: %f", m_DevicePtr
->HMDInfo
.eye_to_screen
[0]);
110 nldebug("LibVR: LensSeparationDistance: %f", m_DevicePtr
->HMDInfo
.lens_separation
);
111 nldebug("LibVR: HResolution: %i, VResolution: %i", m_DevicePtr
->HMDInfo
.h_resolution
, m_DevicePtr
->HMDInfo
.v_resolution
);
112 nldebug("LibVR: DistortionK[0]: %f, DistortionK[1]: %f", m_DevicePtr
->HMDInfo
.distortion_k
[0], m_DevicePtr
->HMDInfo
.distortion_k
[1]);
113 nldebug("LibVR: DistortionK[2]: %f, DistortionK[3]: %f", m_DevicePtr
->HMDInfo
.distortion_k
[2], m_DevicePtr
->HMDInfo
.distortion_k
[3]);
114 nldebug("LibVR: Scale: %f", st_conf
.distort
.scale
);
115 m_LeftViewport
.init(0.f
, 0.f
, 0.5f
, 1.0f
);
116 m_RightViewport
.init(0.5f
, 0.f
, 0.5f
, 1.0f
);
120 CStereoLibVR::~CStereoLibVR()
122 if (!m_BarrelMat
.empty())
124 m_BarrelMat
.getObjectPtr()->setTexture(0, NULL
);
125 m_Driver
->deleteMaterial(m_BarrelMat
);
129 m_BarrelTex
= NULL
; // CSmartPtr
131 delete m_PixelProgram
;
132 m_PixelProgram
= NULL
;
136 if (m_DevicePtr
->HMDDevice
)
137 hmd_close(m_DevicePtr
->HMDDevice
);
145 void CStereoLibVR::setDriver(NL3D::UDriver
*driver
)
147 nlassert(!m_PixelProgram
);
149 NL3D::IDriver
*drvInternal
= (static_cast<CDriverUser
*>(driver
))->getDriver();
150 if (drvInternal
->supportPixelProgram(CPixelProgram::fp40
) && drvInternal
->supportBloomEffect() && drvInternal
->supportNonPowerOfTwoTextures())
153 m_PixelProgram
= new CPixelProgram(g_StereoOVR_fp40
);
155 else if (drvInternal
->supportPixelProgram(CPixelProgram::arbfp1
) && drvInternal
->supportBloomEffect() && drvInternal
->supportNonPowerOfTwoTextures())
157 nldebug("VR: arbfp1");
158 m_PixelProgram
= new CPixelProgram(g_StereoOVR_arbfp1
);
160 else if (drvInternal
->supportPixelProgram(CPixelProgram::ps_2_0
))
162 nldebug("VR: ps_2_0");
163 m_PixelProgram
= new CPixelProgram(g_StereoOVR_ps_2_0
);
170 m_BarrelTex
= new CTextureBloom(); // lol bloom
171 m_BarrelTex
->setRenderTarget(true);
172 m_BarrelTex
->setReleasable(false);
173 m_BarrelTex
->resize(m_DevicePtr
->HMDInfo
.h_resolution
, m_DevicePtr
->HMDInfo
.v_resolution
);
174 m_BarrelTex
->setFilterMode(ITexture::Linear
, ITexture::LinearMipMapOff
);
175 m_BarrelTex
->setWrapS(ITexture::Clamp
);
176 m_BarrelTex
->setWrapT(ITexture::Clamp
);
177 drvInternal
->setupTexture(*m_BarrelTex
);
178 m_BarrelTexU
= new CTextureUser(m_BarrelTex
);
180 m_BarrelMat
= m_Driver
->createMaterial();
181 m_BarrelMat
.initUnlit();
182 m_BarrelMat
.setColor(CRGBA::White
);
183 m_BarrelMat
.setBlend (false);
184 m_BarrelMat
.setAlphaTest (false);
185 NL3D::CMaterial
*barrelMat
= m_BarrelMat
.getObjectPtr();
186 barrelMat
->setShader(NL3D::CMaterial::PostProcessing
);
187 barrelMat
->setBlendFunc(CMaterial::one
, CMaterial::zero
);
188 barrelMat
->setZWrite(false);
189 barrelMat
->setZFunc(CMaterial::always
);
190 barrelMat
->setDoubleSided(true);
191 barrelMat
->setTexture(0, m_BarrelTex
);
193 m_BarrelQuadLeft
.V0
= CVector(0.f
, 0.f
, 0.5f
);
194 m_BarrelQuadLeft
.V1
= CVector(0.5f
, 0.f
, 0.5f
);
195 m_BarrelQuadLeft
.V2
= CVector(0.5f
, 1.f
, 0.5f
);
196 m_BarrelQuadLeft
.V3
= CVector(0.f
, 1.f
, 0.5f
);
198 m_BarrelQuadRight
.V0
= CVector(0.5f
, 0.f
, 0.5f
);
199 m_BarrelQuadRight
.V1
= CVector(1.f
, 0.f
, 0.5f
);
200 m_BarrelQuadRight
.V2
= CVector(1.f
, 1.f
, 0.5f
);
201 m_BarrelQuadRight
.V3
= CVector(0.5f
, 1.f
, 0.5f
);
203 nlassert(!drvInternal
->isTextureRectangle(m_BarrelTex
)); // not allowed
205 m_BarrelQuadLeft
.Uv0
= CUV(0.f
, 0.f
);
206 m_BarrelQuadLeft
.Uv1
= CUV(0.5f
, 0.f
);
207 m_BarrelQuadLeft
.Uv2
= CUV(0.5f
, 1.f
);
208 m_BarrelQuadLeft
.Uv3
= CUV(0.f
, 1.f
);
210 m_BarrelQuadRight
.Uv0
= CUV(0.5f
, 0.f
);
211 m_BarrelQuadRight
.Uv1
= CUV(1.f
, 0.f
);
212 m_BarrelQuadRight
.Uv2
= CUV(1.f
, 1.f
);
213 m_BarrelQuadRight
.Uv3
= CUV(0.5f
, 1.f
);
217 nlwarning("VR: No pixel program support");
221 bool CStereoLibVR::getScreenResolution(uint
&width
, uint
&height
)
223 width
= m_DevicePtr
->HMDInfo
.h_resolution
;
224 height
= m_DevicePtr
->HMDInfo
.v_resolution
;
228 void CStereoLibVR::initCamera(uint cid
, const NL3D::UCamera
*camera
)
230 struct stereo_config st_conf
;
231 hmd_get_stereo_config(m_DevicePtr
->HMDDevice
, &st_conf
);
233 float ar
= st_conf
.proj
.aspect_ratio
;
234 float fov
= st_conf
.proj
.yfov
;
235 m_LeftFrustum
[cid
].initPerspective(fov
, ar
, camera
->getFrustum().Near
, camera
->getFrustum().Far
);
236 m_RightFrustum
[cid
] = m_LeftFrustum
[cid
];
238 float projectionCenterOffset
= st_conf
.proj
.projection_offset
* 0.5 * (m_LeftFrustum
[cid
].Right
- m_LeftFrustum
[cid
].Left
);
239 nldebug("LibVR: projectionCenterOffset = %f", projectionCenterOffset
);
241 m_LeftFrustum
[cid
].Left
-= projectionCenterOffset
;
242 m_LeftFrustum
[cid
].Right
-= projectionCenterOffset
;
243 m_RightFrustum
[cid
].Left
+= projectionCenterOffset
;
244 m_RightFrustum
[cid
].Right
+= projectionCenterOffset
;
246 // TODO: Clipping frustum should also take into account the IPD
247 m_ClippingFrustum
[cid
] = m_LeftFrustum
[cid
];
248 m_ClippingFrustum
[cid
].Left
= min(m_LeftFrustum
[cid
].Left
, m_RightFrustum
[cid
].Left
);
249 m_ClippingFrustum
[cid
].Right
= max(m_LeftFrustum
[cid
].Right
, m_RightFrustum
[cid
].Right
);
252 /// Get the frustum to use for clipping
253 void CStereoLibVR::getClippingFrustum(uint cid
, NL3D::UCamera
*camera
) const
255 camera
->setFrustum(m_ClippingFrustum
[cid
]);
258 void CStereoLibVR::updateCamera(uint cid
, const NL3D::UCamera
*camera
)
260 if (camera
->getFrustum().Near
!= m_LeftFrustum
[cid
].Near
261 || camera
->getFrustum().Far
!= m_LeftFrustum
[cid
].Far
)
262 CStereoLibVR::initCamera(cid
, camera
);
263 m_CameraMatrix
[cid
] = camera
->getMatrix();
266 bool CStereoLibVR::nextPass()
268 // Do not allow weird stuff.
269 uint32 width
, height
;
270 m_Driver
->getWindowSize(width
, height
);
271 nlassert(width
== m_DevicePtr
->HMDInfo
.h_resolution
);
272 nlassert(height
== m_DevicePtr
->HMDInfo
.v_resolution
);
274 if (m_Driver
->getPolygonMode() == UDriver::Filled
)
297 // draw interface 3d left
303 // draw interface 3d right
309 // (endInterfacesDisplayBloom)
310 // draw interface 2d left
316 // draw interface 2d right
322 m_OrientationCached
= false;
340 nlerror("Invalid stage");
343 m_OrientationCached
= false;
347 const NL3D::CViewport
&CStereoLibVR::getCurrentViewport() const
349 if (m_Stage
% 2) return m_LeftViewport
;
350 else return m_RightViewport
;
353 const NL3D::CFrustum
&CStereoLibVR::getCurrentFrustum(uint cid
) const
355 if (m_Stage
% 2) return m_LeftFrustum
[cid
];
356 else return m_RightFrustum
[cid
];
359 void CStereoLibVR::getCurrentFrustum(uint cid
, NL3D::UCamera
*camera
) const
361 if (m_Stage
% 2) camera
->setFrustum(m_LeftFrustum
[cid
]);
362 else camera
->setFrustum(m_RightFrustum
[cid
]);
365 void CStereoLibVR::getCurrentMatrix(uint cid
, NL3D::UCamera
*camera
) const
368 if (m_Stage
% 2) translate
.translate(CVector((m_DevicePtr
->InterpupillaryDistance
* m_Scale
) * -0.5f
, 0.f
, 0.f
));
369 else translate
.translate(CVector((m_DevicePtr
->InterpupillaryDistance
* m_Scale
) * 0.5f
, 0.f
, 0.f
));
370 CMatrix mat
= m_CameraMatrix
[cid
] * translate
;
371 if (camera
->getTransformMode() == NL3D::UTransformable::RotQuat
)
373 camera
->setPos(mat
.getPos());
374 camera
->setRotQuat(mat
.getRot());
378 // camera->setTransformMode(NL3D::UTransformable::DirectMatrix);
379 camera
->setMatrix(mat
);
383 bool CStereoLibVR::wantClear()
391 return m_Driver
->getPolygonMode() != UDriver::Filled
;
394 bool CStereoLibVR::wantScene()
403 return m_Driver
->getPolygonMode() != UDriver::Filled
;
406 bool CStereoLibVR::wantInterface3D()
415 return m_Driver
->getPolygonMode() != UDriver::Filled
;
418 bool CStereoLibVR::wantInterface2D()
427 return m_Driver
->getPolygonMode() != UDriver::Filled
;
431 /// Returns non-NULL if a new render target was set
432 bool CStereoLibVR::beginRenderTarget()
434 // render target always set before driver clear
435 // nlassert(m_SubStage <= 1);
436 if (m_Driver
&& m_Stage
== 1 && (m_Driver
->getPolygonMode() == UDriver::Filled
))
438 static_cast<CDriverUser
*>(m_Driver
)->setRenderTarget(*m_BarrelTexU
, 0, 0, 0, 0);
444 /// Returns true if a render target was fully drawn
445 bool CStereoLibVR::endRenderTarget()
447 // after rendering of course
448 // nlassert(m_SubStage > 1);
449 if (m_Driver
&& m_Stage
== 6 && (m_Driver
->getPolygonMode() == UDriver::Filled
)) // set to 4 to turn off distortion of 2d gui
451 struct stereo_config st_conf
;
452 hmd_get_stereo_config(m_DevicePtr
->HMDDevice
, &st_conf
);
454 (static_cast<CDriverUser
*>(m_Driver
))->setRenderTarget(cu
);
455 bool fogEnabled
= m_Driver
->fogEnabled();
456 m_Driver
->enableFog(false);
458 m_Driver
->setMatrixMode2D11();
459 CViewport vp
= CViewport();
460 m_Driver
->setViewport(vp
);
461 uint32 width
, height
;
462 m_Driver
->getWindowSize(width
, height
);
463 NL3D::IDriver
*drvInternal
= (static_cast<CDriverUser
*>(m_Driver
))->getDriver();
464 NL3D::CMaterial
*barrelMat
= m_BarrelMat
.getObjectPtr();
465 barrelMat
->setTexture(0, m_BarrelTex
);
466 drvInternal
->activePixelProgram(m_PixelProgram
);
468 float w
= float(m_BarrelQuadLeft
.V1
.x
),// / float(width),
469 h
= float(m_BarrelQuadLeft
.V2
.y
),// / float(height),
470 x
= float(m_BarrelQuadLeft
.V0
.x
),/// / float(width),
471 y
= float(m_BarrelQuadLeft
.V0
.y
);// / float(height);
473 //TODO: stereo_config stuff
474 float lensViewportShift
= st_conf
.proj
.projection_offset
;
476 float lensCenterX
= x
+ (w
+ lensViewportShift
* 0.5f
) * 0.5f
;
477 float lensCenterY
= y
+ h
* 0.5f
;
478 float screenCenterX
= x
+ w
* 0.5f
;
479 float screenCenterY
= y
+ h
* 0.5f
;
480 float scaleX
= (w
/ 2 / st_conf
.distort
.scale
);
481 float scaleY
= (h
/ 2 / st_conf
.distort
.scale
);
482 float scaleInX
= (2 / w
);
483 float scaleInY
= (2 / h
);
484 drvInternal
->setPixelProgramConstant(0, lensCenterX
, lensCenterY
, 0.f
, 0.f
);
485 drvInternal
->setPixelProgramConstant(1, screenCenterX
, screenCenterY
, 0.f
, 0.f
);
486 drvInternal
->setPixelProgramConstant(2, scaleX
, scaleY
, 0.f
, 0.f
);
487 drvInternal
->setPixelProgramConstant(3, scaleInX
, scaleInY
, 0.f
, 0.f
);
488 drvInternal
->setPixelProgramConstant(4, 1, st_conf
.distort
.distortion_k
);
491 m_Driver
->drawQuad(m_BarrelQuadLeft
, m_BarrelMat
);
494 lensCenterX
= x
+ (w
- lensViewportShift
* 0.5f
) * 0.5f
;
495 screenCenterX
= x
+ w
* 0.5f
;
496 drvInternal
->setPixelProgramConstant(0, lensCenterX
, lensCenterY
, 0.f
, 0.f
);
497 drvInternal
->setPixelProgramConstant(1, screenCenterX
, screenCenterY
, 0.f
, 0.f
);
499 m_Driver
->drawQuad(m_BarrelQuadRight
, m_BarrelMat
);
501 drvInternal
->activePixelProgram(NULL
);
502 m_Driver
->enableFog(fogEnabled
);
509 NLMISC::CQuat
CStereoLibVR::getOrientation() const
511 if (m_OrientationCached
)
512 return m_OrientationCache
;
514 unsigned int t
= NLMISC::CTime::getLocalTime();
515 hmd_update(m_DevicePtr
->HMDDevice
, &t
);
518 hmd_get_rotation(m_DevicePtr
->HMDDevice
, quat
);
519 NLMISC::CMatrix coordsys
;
521 1.0f
, 0.0f
, 0.0f
, 0.0f
,
522 0.0f
, 0.0f
, -1.0f
, 0.0f
,
523 0.0f
, 1.0f
, 0.0f
, 0.0f
,
524 0.0f
, 0.0f
, 0.0f
, 1.0f
,
527 NLMISC::CMatrix matovr
;
528 matovr
.setRot(NLMISC::CQuat(quat
[1], quat
[2], quat
[3], quat
[0]));
529 NLMISC::CMatrix matr
;
530 matr
.rotateX(NLMISC::Pi
* 0.5f
); // fix this properly... :) (note: removing this allows you to use rift while lying down)
531 NLMISC::CMatrix matnel
= matr
* matovr
* coordsys
;
532 NLMISC::CQuat finalquat
= matnel
.getRot();
533 m_OrientationCache
= finalquat
;
534 m_OrientationCached
= true;
539 void CStereoLibVR::getInterface2DShift(uint cid
, float &x
, float &y
, float distance
) const
543 // todo: take into account m_EyePosition
545 NLMISC::CVector vector
= CVector(0.f
, -distance
, 0.f
);
546 NLMISC::CQuat rot
= getOrientation();
550 //if (m_Stage % 2) mat.translate(CVector(m_DevicePtr->HMDInfo.InterpupillaryDistance * -0.5f, 0.f, 0.f));
551 //else mat.translate(CVector(m_DevicePtr->HMDInfo.InterpupillaryDistance * 0.5f, 0.f, 0.f));
552 mat
.translate(vector
);
553 CVector proj
= CStereoOVR::getCurrentFrustum(cid
).project(mat
.getPos());
556 if (m_Stage
% 2) ipd
= CVector(m_DevicePtr
->HMDInfo
.InterpupillaryDistance
* -0.5f
, 0.f
, 0.f
);
557 else ipd
= CVector(m_DevicePtr
->HMDInfo
.InterpupillaryDistance
* 0.5f
, 0.f
, 0.f
);
558 CVector projipd
= CStereoOVR::getCurrentFrustum(cid
).project(vector
+ ipd
);
559 CVector projvec
= CStereoOVR::getCurrentFrustum(cid
).project(vector
);
561 x
= (proj
.x
+ projipd
.x
- projvec
.x
- 0.5f
);
562 y
= (proj
.y
+ projipd
.y
- projvec
.y
- 0.5f
);
566 // Alternative method
567 // todo: take into account m_EyePosition
569 NLMISC::CVector vec
= CVector(0.f
, -distance
, 0.f
);
571 if (m_Stage
% 2) ipd
= CVector((m_DevicePtr
->InterpupillaryDistance
* m_Scale
) * -0.5f
, 0.f
, 0.f
);
572 else ipd
= CVector((m_DevicePtr
->InterpupillaryDistance
* m_Scale
) * 0.5f
, 0.f
, 0.f
);
575 NLMISC::CQuat rot
= getOrientation();
576 NLMISC::CQuat modrot
= NLMISC::CQuat(CVector(0.f
, 1.f
, 0.f
), NLMISC::Pi
);
578 float p
= NLMISC::Pi
+ atan2f(2.0f
* ((rot
.x
* rot
.y
) + (rot
.z
* rot
.w
)), 1.0f
- 2.0f
* ((rot
.y
* rot
.y
) + (rot
.w
* rot
.w
)));
579 if (p
> NLMISC::Pi
) p
-= NLMISC::Pi
* 2.0f
;
580 float t
= -atan2f(2.0f
* ((rot
.x
* rot
.w
) + (rot
.y
* rot
.z
)), 1.0f
- 2.0f
* ((rot
.z
* rot
.z
) + (rot
.w
* rot
.w
)));// // asinf(2.0f * ((rot.x * rot.z) - (rot.w * rot.y)));
582 CVector rotshift
= CVector(p
, 0.f
, t
) * -distance
;
584 CVector proj
= CStereoLibVR::getCurrentFrustum(cid
).project(vec
+ ipd
+ rotshift
);
592 void CStereoLibVR::setEyePosition(const NLMISC::CVector
&v
)
597 const NLMISC::CVector
&CStereoLibVR::getEyePosition() const
599 return m_EyePosition
;
602 void CStereoLibVR::setScale(float s
)
604 m_EyePosition
= m_EyePosition
* (s
/ m_Scale
);
608 void CStereoLibVR::listDevices(std::vector
<CStereoDeviceInfo
> &devicesOut
)
610 // For now, LibVR doesn't support multiple devices
611 struct hmd
*hmd
= hmd_open_first(0);
614 CStereoDeviceInfo deviceInfoOut
;
615 CStereoLibVRDeviceHandle
*handle
= new CStereoLibVRDeviceHandle();
616 deviceInfoOut
.Factory
= static_cast<IStereoDeviceFactory
*>(handle
);
617 deviceInfoOut
.Class
= CStereoDeviceInfo::StereoHMD
;
618 deviceInfoOut
.Library
= CStereoDeviceInfo::LibVR
;
619 deviceInfoOut
.AllowAuto
= true;
620 //TODO: manufacturer, produc name
622 devicesOut
.push_back(deviceInfoOut
);
627 bool CStereoLibVR::isLibraryInUse()
629 nlassert(s_DeviceCounter
>= 0);
630 return s_DeviceCounter
> 0;
633 void CStereoLibVR::releaseLibrary()
635 nlassert(s_DeviceCounter
== 0);
638 bool CStereoLibVR::isDeviceCreated()
640 return m_DevicePtr
->HMDDevice
!= NULL
;
643 } /* namespace NL3D */
645 #endif /* HAVE_LIBVR */