FoveHMD.cpp 49 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629
  1. #include "FoveHMD.h"
  2. #include "FoveHMDPrivatePCH.h"
  3. #include "Core.h"
  4. #include "Engine.h"
  5. #include "FoveVRFunctionLibrary.h"
  6. #include "IFVRCompositor.h"
  7. #include "IFVRHeadset.h"
  8. #include "IFoveHMDPlugin.h"
  9. #include "IPluginManager.h"
  10. #include "PostProcessParameters.h"
  11. #include "PostProcess/PostProcessHMD.h"
  12. #include "RendererPrivate.h"
  13. #include "ScenePrivate.h"
  14. #include "SceneViewport.h"
  15. // Compile time developer option to change what mode you want to run in
  16. enum class FoveUnrealPluginMode
  17. {
  18. PositionAndOrientation, // The game will enable position and orientation tracking (if possible) and Unreal cameras will move/rotate with the users head
  19. OrientationOnly, // The game will only enable orientation tracking, the position tracking camera will not be used
  20. FixedToHMDScreen, // The rendered results of the game will not rotate/move with the head, and rendered content will be "fixed" to the HMD screen
  21. };
  22. // Developers can change this mode to change the behavior of the fove plugin at runtime
  23. constexpr FoveUnrealPluginMode FoveMode = FoveUnrealPluginMode::PositionAndOrientation;
  24. // Define or include the LogHMD category, depending on whether we are in Unreal 4.17+ or not
  25. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 17
  26. #include "LogCategory.h"
  27. #else
  28. DEFINE_LOG_CATEGORY_STATIC(LogHMD, Log, All);
  29. #endif
  30. // 4.16+ uses PipelineStateCache
  31. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 16
  32. #define FOVE_USE_PIPLINE_STATE_CACHE
  33. #include "PipelineStateCache.h"
  34. #include "RHIStaticStates.h"
  35. #endif
  36. #if WITH_EDITOR
  37. #include "Editor/UnrealEd/Classes/Editor/EditorEngine.h"
  38. #endif
  39. #if PLATFORM_WINDOWS
  40. #include "AllowWindowsPlatformTypes.h"
  41. #include <d3d11.h>
  42. #include "HideWindowsPlatformTypes.h"
  43. #endif // PLATFORM_WINDOWS
  44. #define LOCTEXT_NAMESPACE "FFoveHMD"
  45. //---------------------------------------------------
  46. // Helpers
  47. //---------------------------------------------------
  48. #ifdef _MSC_VER
  49. #pragma region Helpers
  50. #else
  51. #pragma mark Helpers
  52. #endif
  53. FMatrix ToUnreal(const Fove::SFVR_Matrix44& tm)
  54. {
  55. return FMatrix(
  56. FPlane(tm.mat[0][0], tm.mat[1][0], tm.mat[2][0], tm.mat[3][0]),
  57. FPlane(tm.mat[0][1], tm.mat[1][1], tm.mat[2][1], tm.mat[3][1]),
  58. FPlane(tm.mat[0][2], tm.mat[1][2], tm.mat[2][2], tm.mat[3][2]),
  59. FPlane(tm.mat[0][3], tm.mat[1][3], tm.mat[2][3], tm.mat[3][3]));
  60. }
  61. FQuat ToUnreal(const Fove::SFVR_Quaternion quat)
  62. {
  63. return FQuat(quat.z, quat.x, quat.y, quat.w);
  64. }
  65. FVector ToUnreal(const Fove::SFVR_Vec3 vec, const float scale)
  66. {
  67. return FVector(vec.z * scale, vec.x * scale, vec.y * scale);
  68. }
  69. FTransform ToUnreal(const Fove::SFVR_Pose& pose, const float scale)
  70. {
  71. FQuat FoveOrientation = ToUnreal(pose.orientation);
  72. FVector FovePosition = ToUnreal(pose.position, scale);
  73. return FTransform(FoveOrientation, FovePosition);
  74. }
  75. // Helper function for acquiring the appropriate FSceneViewport
  76. FSceneViewport* FoveFindSceneViewport()
  77. {
  78. if (!GIsEditor)
  79. {
  80. UGameEngine* const GameEngine = Cast<UGameEngine>(GEngine);
  81. return GameEngine->SceneViewport.Get();
  82. }
  83. #if WITH_EDITOR
  84. else
  85. {
  86. UEditorEngine* const EditorEngine = CastChecked<UEditorEngine>(GEngine);
  87. FSceneViewport* const PIEViewport = (FSceneViewport*)EditorEngine->GetPIEViewport();
  88. if (PIEViewport != nullptr && PIEViewport->IsStereoRenderingAllowed())
  89. {
  90. // PIE is setup for stereo rendering
  91. return PIEViewport;
  92. }
  93. else
  94. {
  95. // Check to see if the active editor viewport is drawing in stereo mode
  96. FSceneViewport* EditorViewport = (FSceneViewport*)EditorEngine->GetActiveViewport();
  97. if (EditorViewport != nullptr && EditorViewport->IsStereoRenderingAllowed())
  98. {
  99. return EditorViewport;
  100. }
  101. }
  102. }
  103. #endif
  104. return nullptr;
  105. }
  106. // Helper function to determine if a fove is connected
  107. bool IsFoveConnected(Fove::IFVRHeadset& headset, Fove::IFVRCompositor& compositor)
  108. {
  109. // Headset must be plugged in
  110. bool isHardwareConnected = false;
  111. Fove::EFVR_ErrorCode Error = headset.IsHardwareConnected(&isHardwareConnected);
  112. if (Error != Fove::EFVR_ErrorCode::None)
  113. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::IsHardwareConnected failed: %d"), static_cast<int>(Error));
  114. if (!isHardwareConnected)
  115. return false;
  116. // Check if we are connected to the compositor
  117. // This is an important step because there are potentially other Unreal plugins that support FOVE (such as SteamVR and OSVR)
  118. // In all cases, the FOVE headset may be connected, but we should only use the FOVE plugin when the FOVE compositor is running
  119. bool isCompositorReady = false;
  120. Error = compositor.IsReady(&isCompositorReady);
  121. if (Error != Fove::EFVR_ErrorCode::None)
  122. UE_LOG(LogHMD, Warning, TEXT("IFVRCompositor::IsReady failed: %d"), static_cast<int>(Error));
  123. if (!isCompositorReady)
  124. return false;
  125. return true;
  126. }
  127. #ifdef _MSC_VER
  128. #pragma endregion
  129. #endif
  130. //---------------------------------------------------
  131. // FoveRenderingBridge
  132. //---------------------------------------------------
  133. #ifdef _MSC_VER
  134. #pragma region FoveRenderingBridge
  135. #else
  136. #pragma mark FoveRenderingBridge
  137. #endif
  138. class FoveRenderingBridge : public FRHICustomPresent
  139. {
  140. public:
  141. FoveRenderingBridge(const TSharedRef<Fove::IFVRCompositor, ESPMode::ThreadSafe>& compositor) : FRHICustomPresent(nullptr), Compositor(compositor) {}
  142. virtual ~FoveRenderingBridge() {}
  143. void OnBackBufferResize() override {} // Ignored
  144. const void SetRenderPose(const Fove::SFVR_Pose& pose, const float WorldToMetersScale)
  145. {
  146. FovePose = pose;
  147. Pose = ToUnreal(FovePose, WorldToMetersScale);
  148. }
  149. const FTransform& GetRenderPose() const
  150. {
  151. return Pose;
  152. }
  153. virtual void UpdateViewport(const FViewport& Viewport) = 0;
  154. protected:
  155. const TSharedRef<Fove::IFVRCompositor, ESPMode::ThreadSafe> Compositor; // Pointer back to the Fove plugin object that owns us
  156. Fove::SFVR_Pose FovePose; // Pose fetched out via WaitForRenderPose, used internally to submit frames back to fove
  157. FTransform Pose; // Same as RenderPose, but converted to Unreal coordinates
  158. };
  159. #ifdef _MSC_VER
  160. #pragma endregion
  161. #endif
  162. //---------------------------------------------------
  163. // FoveD3D11Bridge
  164. //---------------------------------------------------
  165. #ifdef _MSC_VER
  166. #pragma region FoveD3D11Bridge
  167. #else
  168. #pragma mark FoveD3D11Bridge
  169. #endif
  170. #if PLATFORM_WINDOWS
  171. class FoveD3D11Bridge : public FoveRenderingBridge
  172. {
  173. ID3D11Texture2D* RenderTargetTexture = nullptr;
  174. const Fove::SFVR_CompositorLayer FoveCompositorLayer;
  175. public:
  176. FoveD3D11Bridge(const TSharedRef<Fove::IFVRCompositor, ESPMode::ThreadSafe>& Compositor, Fove::SFVR_CompositorLayer Layer)
  177. : FoveRenderingBridge(Compositor)
  178. , FoveCompositorLayer(Layer)
  179. {
  180. }
  181. ~FoveD3D11Bridge()
  182. {
  183. if (RenderTargetTexture)
  184. RenderTargetTexture->Release();
  185. }
  186. bool Present(int& SyncInterval) override
  187. {
  188. check(IsInRenderingThread());
  189. if (!RenderTargetTexture)
  190. {
  191. UE_LOG(LogHMD, Warning, TEXT("FOVE present without render texture"));
  192. return false;
  193. }
  194. // Clear rasterizer state to avoid Unreal messing with FOVE submit
  195. ID3D11Device* Dev = nullptr;
  196. ID3D11DeviceContext* Ctx = nullptr;
  197. ID3D11RasterizerState* RasterizerState = nullptr;
  198. RenderTargetTexture->GetDevice(&Dev);
  199. if (Dev)
  200. {
  201. Dev->GetImmediateContext(&Ctx);
  202. if (Ctx)
  203. {
  204. D3D11_RASTERIZER_DESC desc = {};
  205. Ctx->RSGetState(&RasterizerState);
  206. Ctx->RSSetState(nullptr);
  207. }
  208. }
  209. // Submit eye images
  210. Fove::SFVR_CompositorLayerSubmitInfo info;
  211. info.layerId = FoveCompositorLayer.layerId;
  212. info.pose = FovePose;
  213. info.left.texInfo = RenderTargetTexture;
  214. info.right.texInfo = RenderTargetTexture;
  215. info.left.bounds.left = 0.0f;
  216. info.left.bounds.right = 0.5f;
  217. info.left.bounds.bottom = 1.0f;
  218. info.left.bounds.top = 0.0f;
  219. info.right.bounds.left = 0.5f;
  220. info.right.bounds.right = 1.0f;
  221. info.right.bounds.bottom = 1.0f;
  222. info.right.bounds.top = 0.0f;
  223. Compositor->Submit(info);
  224. // Restore state
  225. if (Ctx)
  226. {
  227. D3D11_RASTERIZER_DESC desc = {};
  228. Ctx->RSSetState(RasterizerState);
  229. }
  230. return true;
  231. }
  232. void UpdateViewport(const FViewport& Viewport) override
  233. {
  234. check(IsInGameThread());
  235. // Update render target
  236. const FTexture2DRHIRef& textureRef = Viewport.GetRenderTargetTexture();
  237. ID3D11Texture2D* const newRT = textureRef ? (ID3D11Texture2D*)textureRef->GetNativeResource() : nullptr;
  238. if (newRT != RenderTargetTexture)
  239. {
  240. if (RenderTargetTexture)
  241. RenderTargetTexture->Release();
  242. RenderTargetTexture = newRT;
  243. if (RenderTargetTexture)
  244. RenderTargetTexture->AddRef();
  245. }
  246. }
  247. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 18
  248. bool NeedsNativePresent() override
  249. {
  250. return true;
  251. }
  252. #endif
  253. };
  254. #endif // PLATFORM_WINDOWS
  255. #ifdef _MSC_VER
  256. #pragma endregion
  257. #endif
  258. //---------------------------------------------------
  259. // UFoveVRFunctionLibrary
  260. //---------------------------------------------------
  261. #ifdef _MSC_VER
  262. #pragma region UFoveVRFunctionLibrary
  263. #else
  264. #pragma mark UFoveVRFunctionLibrary
  265. #endif
  266. UFoveVRFunctionLibrary::UFoveVRFunctionLibrary(const FObjectInitializer& ObjectInitializer)
  267. : Super(ObjectInitializer)
  268. {
  269. }
  270. bool UFoveVRFunctionLibrary::IsHardwareConnected()
  271. {
  272. if (FFoveHMD* const hmd = FFoveHMD::Get())
  273. return hmd->IsHardwareConnected();
  274. return false;
  275. }
  276. bool UFoveVRFunctionLibrary::IsHardwareReady()
  277. {
  278. if (FFoveHMD* const hmd = FFoveHMD::Get())
  279. return hmd->IsHardwareReady();
  280. return false;
  281. }
  282. bool UFoveVRFunctionLibrary::IsEyeTrackingCalibrating()
  283. {
  284. if (FFoveHMD* const hmd = FFoveHMD::Get())
  285. {
  286. hmd->GetHeadset().EnsureEyeTrackingCalibration();
  287. return true;
  288. }
  289. return false;
  290. }
  291. bool UFoveVRFunctionLibrary::EnsureEyeTrackingCalibration()
  292. {
  293. if (FFoveHMD* const hmd = FFoveHMD::Get())
  294. return hmd->EnsureEyeTrackingCalibration();
  295. return false;
  296. }
  297. bool UFoveVRFunctionLibrary::GetGazeConvergence(const bool bRelativeToHMD, FVector& outRayOrigin, FVector& outRayDirection, float& outDistance, float& outAccuracy)
  298. {
  299. if (FFoveHMD* const hmd = FFoveHMD::Get())
  300. return hmd->GetGazeConvergence(bRelativeToHMD, &outRayOrigin, &outRayDirection, &outDistance, &outAccuracy);
  301. return false;
  302. }
  303. bool UFoveVRFunctionLibrary::GetGazeVector(const bool bRelativeToHMD, FVector& outLeft, FVector& outRight)
  304. {
  305. if (FFoveHMD* const hmd = FFoveHMD::Get())
  306. return hmd->GetGazeVector(bRelativeToHMD, &outLeft, &outRight);
  307. return false;
  308. }
  309. bool UFoveVRFunctionLibrary::GetGazeVector2D(FVector2D& outLeft, FVector2D& outRight)
  310. {
  311. if (FFoveHMD* const hmd = FFoveHMD::Get())
  312. return hmd->GetGazeVector2D(&outLeft, &outRight);
  313. return false;
  314. }
  315. bool UFoveVRFunctionLibrary::ManualDriftCorrection3D(const FVector Location)
  316. {
  317. if (FFoveHMD* const hmd = FFoveHMD::Get())
  318. {
  319. hmd->ManualDriftCorrection3D(Location);
  320. return true;
  321. }
  322. return false;
  323. }
  324. bool UFoveVRFunctionLibrary::CheckEyesTracked(bool& outLeft, bool& outRight)
  325. {
  326. if (FFoveHMD* const hmd = FFoveHMD::Get())
  327. return hmd->CheckEyesTracked(&outLeft, &outRight);
  328. return false;
  329. }
  330. bool UFoveVRFunctionLibrary::CheckEyesClosed(bool& outLeft, bool& outRight)
  331. {
  332. if (FFoveHMD* const hmd = FFoveHMD::Get())
  333. return hmd->CheckEyesClosed(&outLeft, &outRight);
  334. return false;
  335. }
  336. bool UFoveVRFunctionLibrary::IsPositionReady()
  337. {
  338. bool Ret = false;
  339. if (FFoveHMD* const hmd = FFoveHMD::Get())
  340. {
  341. const Fove::EFVR_ErrorCode Error = hmd->GetHeadset().IsPositionReady(&Ret);
  342. if (Error != Fove::EFVR_ErrorCode::None)
  343. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::IsPositionReady failed: %d"), static_cast<int>(Error));
  344. }
  345. return Ret;
  346. }
  347. #ifdef _MSC_VER
  348. #pragma endregion
  349. #endif
  350. //---------------------------------------------------
  351. // FFoveHMDPlugin
  352. //---------------------------------------------------
  353. #ifdef _MSC_VER
  354. #pragma region FFoveHMDPlugin
  355. #else
  356. #pragma mark FFoveHMDPlugin
  357. #endif
  358. class FFoveHMDPlugin : public IFoveHMDPlugin
  359. {
  360. public: // IHeadMountedDisplayModule implementation
  361. void StartupModule() override
  362. {
  363. IFoveHMDPlugin::StartupModule();
  364. // On windows, we delay loading of the DLL, so the game can function if it's missing
  365. // This is not implemented on other platforms currently
  366. #if PLATFORM_WINDOWS
  367. if (!dllHandle)
  368. {
  369. // Get the library path based on the base dir of this plugin
  370. const FString baseDir = IPluginManager::Get().FindPlugin("FoveHMD")->GetBaseDir();
  371. const FString foveLibDir = FString::Printf(TEXT("Binaries/ThirdParty/FoveVR/FoveVR_SDK_%s/x64/FoveClient.dll"), FOVEVR_SDK_VER);
  372. const FString libraryPath = FPaths::Combine(*baseDir, *foveLibDir);
  373. // Load the fove client dll and show an error if it fails
  374. dllHandle = !libraryPath.IsEmpty() ? FPlatformProcess::GetDllHandle(*libraryPath) : nullptr;
  375. if (!dllHandle)
  376. {
  377. UE_LOG(LogHMD, Warning, TEXT("Failed to load FoveVR DLL handle"));
  378. FMessageDialog::Open(EAppMsgType::Ok, FText::FromString("Failed to load FoveClient: " + libraryPath));
  379. return;
  380. }
  381. }
  382. #endif
  383. // We do not create the Headset and Compositor objects here, hence the CreateObjectsIfNeeded() function
  384. // If we do so, it causes a "SECURE CRT: Invalid parameter detected" error when packing projects with the FOVE plugin
  385. // The reason for this is unknown
  386. }
  387. void ShutdownModule() override
  388. {
  389. // Clear headset & compositor
  390. // It is assumed that all other references are cleared by now as well
  391. Headset.Reset();
  392. Compositor.Reset();
  393. // Unload the fove client dll
  394. if (dllHandle)
  395. {
  396. FPlatformProcess::FreeDllHandle(dllHandle);
  397. dllHandle = nullptr;
  398. }
  399. // Call base class last per standard ordering
  400. IFoveHMDPlugin::ShutdownModule();
  401. }
  402. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 18
  403. TSharedPtr<class IXRTrackingSystem , ESPMode::ThreadSafe> CreateTrackingSystem() override
  404. #else
  405. TSharedPtr<class IHeadMountedDisplay, ESPMode::ThreadSafe> CreateHeadMountedDisplay() override
  406. #endif
  407. {
  408. CreateObjectsIfNeeded();
  409. if (!Headset.IsValid() || !Compositor.IsValid())
  410. return nullptr;
  411. // Create a compositor layer
  412. Fove::SFVR_CompositorLayer Layer;
  413. Fove::SFVR_CompositorLayerCreateInfo LayerCreateInfo;
  414. LayerCreateInfo.disableTimeWarp = FoveMode == FoveUnrealPluginMode::FixedToHMDScreen;
  415. Compositor->CreateLayer(LayerCreateInfo, &Layer);
  416. TSharedPtr<FFoveHMD, ESPMode::ThreadSafe> FoveHMD(new FFoveHMD(Headset.ToSharedRef(), MoveTemp(Compositor), Layer));
  417. // Compositor should be moved into the FFoveHMD class, but clear it just in cas
  418. // This ensures that, if we create antoher FFoveHMD, it will get it's own compositor with it's own layer
  419. // The old FFoveHMD will destroy it's own compositor (and layer) when it dies
  420. // Currently there is no destroy layer functionality so we must destroy the IFVRCompositor object itself
  421. Compositor = TUniquePtr<Fove::IFVRCompositor>();
  422. return FoveHMD;
  423. }
  424. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 14
  425. FString GetModuleKeyName() const override
  426. #else
  427. FString GetModulePriorityKeyName() const override
  428. #endif
  429. {
  430. return FString(TEXT("FoveHMD"));
  431. }
  432. bool IsHMDConnected() override
  433. {
  434. check(IsInGameThread());
  435. CreateObjectsIfNeeded();
  436. return Headset.IsValid() && Compositor.IsValid() && IsFoveConnected(*Headset, *Compositor);
  437. }
  438. private:
  439. void CreateObjectsIfNeeded()
  440. {
  441. if (!Headset.IsValid())
  442. {
  443. // Create the headset object
  444. Headset = TSharedPtr<Fove::IFVRHeadset, ESPMode::ThreadSafe>(Fove::GetFVRHeadset());
  445. if (!Headset.IsValid())
  446. {
  447. UE_LOG(LogHMD, Warning, TEXT("Failed to create IFVRHeadset"));
  448. return;
  449. }
  450. // Determine what FOVE capabilities we want to enable
  451. Fove::EFVR_ClientCapabilities Capabilities = Fove::EFVR_ClientCapabilities::Gaze; // Change Gaze to None to disable gaze tracking
  452. if (FoveMode == FoveUnrealPluginMode::PositionAndOrientation)
  453. Capabilities = Capabilities | Fove::EFVR_ClientCapabilities::Position;
  454. if (FoveMode == FoveUnrealPluginMode::PositionAndOrientation || FoveMode == FoveUnrealPluginMode::OrientationOnly)
  455. Capabilities = Capabilities | Fove::EFVR_ClientCapabilities::Orientation;
  456. // Initialize headset
  457. Headset->Initialise(Capabilities);
  458. }
  459. if (!Compositor.IsValid())
  460. {
  461. // Create or destroy the compositor object as needed
  462. // To lower overhead and not open IPC to the compositor, we do this only once the headset is plugged in
  463. Compositor = TUniquePtr<Fove::IFVRCompositor>(Fove::GetFVRCompositor());
  464. if (!Compositor.IsValid())
  465. {
  466. UE_LOG(LogHMD, Warning, TEXT("Failed to create IFVRCompositor"));
  467. return;
  468. }
  469. }
  470. }
  471. // Headset and compositor objects, these are shared with the FFoveHMD devices that we create
  472. TSharedPtr<Fove::IFVRHeadset, ESPMode::ThreadSafe> Headset;
  473. TUniquePtr<Fove::IFVRCompositor> Compositor;
  474. void* dllHandle = nullptr;
  475. };
  476. IMPLEMENT_MODULE(FFoveHMDPlugin, FoveHMD)
  477. #ifdef _MSC_VER
  478. #pragma endregion
  479. #endif
  480. //---------------------------------------------------
  481. // FFoveHMD
  482. //---------------------------------------------------
  483. #ifdef _MSC_VER
  484. #pragma region FFoveHMD
  485. #else
  486. #pragma mark FFoveHMD
  487. #endif
  488. FFoveHMD::FFoveHMD(TSharedRef<Fove::IFVRHeadset, ESPMode::ThreadSafe> headset, TUniquePtr<Fove::IFVRCompositor> compositor, Fove::SFVR_CompositorLayer layer)
  489. : ZNear(GNearClippingPlane)
  490. , ZFar(GNearClippingPlane)
  491. , FoveHeadset(MoveTemp(headset))
  492. , FoveCompositor(compositor.Release())
  493. , FoveCompositorLayer(layer)
  494. , Bridge(*(new TRefCountPtr<FoveRenderingBridge>))
  495. {
  496. IHeadMountedDisplay::StartupModule();
  497. // Grab a pointer to the renderer module
  498. static const FName RendererModuleName("Renderer");
  499. RendererModule = FModuleManager::GetModulePtr<IRendererModule>(RendererModuleName);
  500. #if PLATFORM_WINDOWS
  501. if (IsPCPlatform(GMaxRHIShaderPlatform) && !IsOpenGLPlatform(GMaxRHIShaderPlatform))
  502. {
  503. Bridge = TRefCountPtr<FoveRenderingBridge>(new FoveD3D11Bridge(FoveCompositor, FoveCompositorLayer));
  504. }
  505. #endif
  506. UE_LOG(LogHMD, Log, TEXT("FFoveHMD initialized"));
  507. }
  508. FFoveHMD::~FFoveHMD()
  509. {
  510. UE_LOG(LogHMD, Log, TEXT("FFoveHMD destructing"));
  511. delete &Bridge;
  512. }
  513. FFoveHMD* FFoveHMD::Get()
  514. {
  515. // Get the global HMD object
  516. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 18
  517. IHeadMountedDisplay* const Hmd = GEngine->XRSystem->GetHMDDevice();
  518. #else
  519. IHeadMountedDisplay* const Hmd = GEngine->HMDDevice.Get();
  520. #endif
  521. if (!Hmd)
  522. return nullptr;
  523. // Check if the HMD object is a FoveHMD device
  524. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 18
  525. if (GEngine->XRSystem->GetSystemName() != TEXT("FoveHMD"))
  526. #elif ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 13
  527. if (Hmd->GetDeviceName() != TEXT("FoveHMD"))
  528. #else
  529. if (Hmd->GetHMDDeviceType() != EHMDDeviceType::DT_ES2GenericStereoMesh)
  530. #endif
  531. return nullptr;
  532. return static_cast<FFoveHMD*>(Hmd);
  533. }
  534. bool FFoveHMD::IsHardwareConnected() const
  535. {
  536. bool Ret = false;
  537. const Fove::EFVR_ErrorCode Error = FoveHeadset->IsHardwareConnected(&Ret);
  538. if (Error != Fove::EFVR_ErrorCode::None)
  539. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::IsHardwareConnected: %d"), static_cast<int>(Error));
  540. return Ret;
  541. }
  542. bool FFoveHMD::IsHardwareReady() const
  543. {
  544. bool Ret = false;
  545. const Fove::EFVR_ErrorCode Error = FoveHeadset->IsHardwareReady(&Ret);
  546. if (Error != Fove::EFVR_ErrorCode::None)
  547. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::IsHardwareReady: %d"), static_cast<int>(Error));
  548. return Ret;
  549. }
  550. bool FFoveHMD::IsEyeTrackingCalibrating() const
  551. {
  552. bool Ret = false;
  553. const Fove::EFVR_ErrorCode Error = FoveHeadset->IsEyeTrackingCalibrating(&Ret);
  554. if (Error != Fove::EFVR_ErrorCode::None)
  555. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::IsEyeTrackingCalibrating: %d"), static_cast<int>(Error));
  556. return Ret;
  557. }
  558. bool FFoveHMD::EnsureEyeTrackingCalibration()
  559. {
  560. const Fove::EFVR_ErrorCode Error = FoveHeadset->EnsureEyeTrackingCalibration();
  561. if (Error != Fove::EFVR_ErrorCode::None)
  562. {
  563. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::EnsureEyeTrackingCalibration failed: %d"), static_cast<int>(Error));
  564. return false;
  565. }
  566. return true;
  567. }
  568. bool FFoveHMD::GetGazeConvergence(const bool bRelativeToHMD, FVector* const outRayOrigin, FVector* const outRayDirection, float* const outDistance, float* const outAccuracy) const
  569. {
  570. // Get latest pose. We always use the latest instead of the cached pose for maximum accuracy since the gaze data is the latest.
  571. FQuat HMDOrientation;
  572. if (bRelativeToHMD)
  573. {
  574. Fove::SFVR_Pose Pose;
  575. const Fove::EFVR_ErrorCode Error = FoveHeadset->GetHMDPose(&Pose);
  576. if (Error != Fove::EFVR_ErrorCode::None)
  577. {
  578. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::GetHMDPose failed: %d"), static_cast<int>(Error));
  579. return false;
  580. }
  581. HMDOrientation = ToUnreal(Pose.orientation);
  582. }
  583. // Get gaze convergence
  584. Fove::SFVR_GazeConvergenceData convergence;
  585. const Fove::EFVR_ErrorCode Error = FoveHeadset->GetGazeConvergence(&convergence);
  586. if (Error != Fove::EFVR_ErrorCode::None)
  587. {
  588. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::GetGazeConvergence failed: %d"), static_cast<int>(Error));
  589. return false;
  590. }
  591. if (outRayOrigin)
  592. {
  593. *outRayOrigin = ToUnreal(convergence.ray.origin, WorldToMetersScale);
  594. if (bRelativeToHMD)
  595. *outRayOrigin = HMDOrientation.RotateVector(*outRayOrigin);
  596. }
  597. if (outRayDirection)
  598. {
  599. *outRayDirection = ToUnreal(convergence.ray.direction, 1.0f);
  600. if (bRelativeToHMD)
  601. *outRayDirection = HMDOrientation.RotateVector(*outRayDirection);
  602. }
  603. if (outDistance)
  604. *outDistance = WorldToMetersScale * convergence.distance;
  605. if (outAccuracy)
  606. *outAccuracy = convergence.accuracy;
  607. return true;
  608. }
  609. bool FFoveHMD::GetGazeVector(const bool bRelativeToHMD, FVector* const outLeft, FVector* const outRight) const
  610. {
  611. // Get latest pose. We always use the latest instead of the cached pose for maximum accuracy since the gaze data is the latest.
  612. FQuat HMDOrientation;
  613. if (bRelativeToHMD)
  614. {
  615. Fove::SFVR_Pose Pose;
  616. const Fove::EFVR_ErrorCode Error = FoveHeadset->GetHMDPose(&Pose);
  617. if (Error != Fove::EFVR_ErrorCode::None)
  618. {
  619. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::GetHMDPose failed: %d"), static_cast<int>(Error));
  620. return false;
  621. }
  622. HMDOrientation = ToUnreal(Pose.orientation);
  623. }
  624. // Get left and/or right gaze
  625. Fove::SFVR_GazeVector lGaze, rGaze;
  626. const Fove::EFVR_ErrorCode error = FoveHeadset->GetGazeVectors(outLeft ? &lGaze : nullptr, outRight ? &rGaze : nullptr);
  627. if (error != Fove::EFVR_ErrorCode::None)
  628. {
  629. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::GetGazeVectors failed: %d"), static_cast<int>(error));
  630. return false;
  631. }
  632. // Output left gaze
  633. if (outLeft)
  634. {
  635. *outLeft = ToUnreal(lGaze.vector, 1.0f);
  636. if (!bRelativeToHMD)
  637. *outLeft = HMDOrientation.RotateVector(*outLeft);
  638. }
  639. // Output right gaze
  640. if (outRight)
  641. {
  642. *outRight = ToUnreal(rGaze.vector, 1.0f);
  643. if (!bRelativeToHMD)
  644. *outRight = HMDOrientation.RotateVector(*outRight);
  645. }
  646. return true;
  647. }
  648. bool FFoveHMD::GetGazeVector2D(FVector2D* const outLeft, FVector2D* const outRight) const
  649. {
  650. const auto Compute2DGaze = [] (const Fove::SFVR_Matrix44& proj, const Fove::SFVR_Vec3 gaze, FVector2D& out) -> bool
  651. {
  652. // Project gaze to get screen coordinates
  653. const float projX = proj.mat[0][0] * gaze.x + proj.mat[1][0] * gaze.y + proj.mat[2][0] * gaze.z + proj.mat[3][0];
  654. const float projY = proj.mat[0][1] * gaze.x + proj.mat[1][1] * gaze.y + proj.mat[2][1] * gaze.z + proj.mat[3][1];
  655. const float projW = proj.mat[0][3] * gaze.x + proj.mat[1][3] * gaze.y + proj.mat[2][3] * gaze.z + proj.mat[3][3];
  656. out = FVector2D{ projX / projW, projY / projW };
  657. return true;
  658. };
  659. // Get left/right projection
  660. Fove::SFVR_Matrix44 lProj, rProj;
  661. const Fove::EFVR_ErrorCode Error = FoveHeadset->GetProjectionMatricesLH(0.01f, 1000.0f, outLeft ? &lProj : nullptr, outRight ? &rProj : nullptr);
  662. if (Error != Fove::EFVR_ErrorCode::None)
  663. {
  664. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::GetProjectionMatricesLH failed: %d"), static_cast<int>(Error));
  665. return false;
  666. }
  667. // Get left and/or right gaze
  668. Fove::SFVR_GazeVector lGaze, rGaze;
  669. const Fove::EFVR_ErrorCode error = FoveHeadset->GetGazeVectors(outLeft ? &lGaze : nullptr, outRight ? &rGaze : nullptr);
  670. if (error != Fove::EFVR_ErrorCode::None)
  671. {
  672. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::GetGazeVectors failed: %d"), static_cast<int>(error));
  673. return false;
  674. }
  675. FVector2D retLeft, retRight;
  676. if (outLeft && !Compute2DGaze(lProj, lGaze.vector, retLeft))
  677. return false;
  678. if (outRight && !Compute2DGaze(rProj, rGaze.vector, retRight))
  679. return false;
  680. if (outLeft)
  681. *outLeft = retLeft;
  682. if (outRight)
  683. *outRight = retRight;
  684. return true;
  685. }
  686. bool FFoveHMD::ManualDriftCorrection3D(const FVector Location)
  687. {
  688. const Fove::SFVR_Vec3 vec(Location.Y / WorldToMetersScale, Location.Z / WorldToMetersScale, Location.X / WorldToMetersScale);
  689. const Fove::EFVR_ErrorCode error = FoveHeadset->ManualDriftCorrection3D(vec);
  690. return error == Fove::EFVR_ErrorCode::None;
  691. }
  692. bool FFoveHMD::CheckEyesTracked(bool* outLeft, bool* outRight)
  693. {
  694. Fove::EFVR_Eye eye = Fove::EFVR_Eye::Neither;
  695. const Fove::EFVR_ErrorCode Error = FoveHeadset->CheckEyesTracked(&eye);
  696. if (Error != Fove::EFVR_ErrorCode::None)
  697. {
  698. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::CheckEyesTracked failed: %d"), static_cast<int>(Error));
  699. return false;
  700. }
  701. if (outLeft && (eye == Fove::EFVR_Eye::Both || eye == Fove::EFVR_Eye::Left))
  702. *outLeft = true;
  703. if (outRight && (eye == Fove::EFVR_Eye::Both || eye == Fove::EFVR_Eye::Right))
  704. *outRight = true;
  705. return true;
  706. }
  707. bool FFoveHMD::CheckEyesClosed(bool* outLeft, bool* outRight)
  708. {
  709. Fove::EFVR_Eye eye = Fove::EFVR_Eye::Neither;
  710. const Fove::EFVR_ErrorCode Error = FoveHeadset->CheckEyesClosed(&eye);
  711. if (Error != Fove::EFVR_ErrorCode::None)
  712. {
  713. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::CheckEyesClosed failed: %d"), static_cast<int>(Error));
  714. return false;
  715. }
  716. if (outLeft && (eye == Fove::EFVR_Eye::Both || eye == Fove::EFVR_Eye::Left))
  717. *outLeft = true;
  718. if (outRight && (eye == Fove::EFVR_Eye::Both || eye == Fove::EFVR_Eye::Right))
  719. *outRight = true;
  720. return true;
  721. }
  722. bool FFoveHMD::IsPositionReady() const
  723. {
  724. bool Ret = false;
  725. const Fove::EFVR_ErrorCode Error = FoveHeadset->IsPositionReady(&Ret);
  726. if (Error != Fove::EFVR_ErrorCode::None)
  727. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::IsPositionReady failed: %d"), static_cast<int>(Error));
  728. return Ret;
  729. }
  730. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 18
  731. FName FFoveHMD::GetSystemName() const
  732. {
  733. static FName name(TEXT("FoveHMD"));
  734. return name;
  735. }
  736. bool FFoveHMD::EnumerateTrackedDevices(TArray<int, FDefaultAllocator>& OutDevices, const EXRTrackedDeviceType Type)
  737. {
  738. if (Type == EXRTrackedDeviceType::Any || Type == EXRTrackedDeviceType::HeadMountedDisplay)
  739. {
  740. static const int32 DeviceId = IXRTrackingSystem::HMDDeviceId;
  741. OutDevices.Add(DeviceId);
  742. return true;
  743. }
  744. return false;
  745. }
  746. void FFoveHMD::RefreshPoses()
  747. {
  748. // TODO
  749. }
  750. bool FFoveHMD::GetCurrentPose(const int32 deviceId, FQuat& OutQuat, FVector& OutVec)
  751. {
  752. if (deviceId != 0)
  753. return false;
  754. PrivOrientationAndPosition(OutQuat, OutVec);
  755. return true;
  756. }
  757. #endif
  758. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 13 && ENGINE_MINOR_VERSION < 18
  759. FName FFoveHMD::GetDeviceName() const
  760. {
  761. static FName name(TEXT("FoveHMD"));
  762. return name;
  763. }
  764. #endif
  765. bool FFoveHMD::IsHMDConnected()
  766. {
  767. return IsFoveConnected(*FoveHeadset, *FoveCompositor);
  768. }
  769. bool FFoveHMD::IsHMDEnabled() const
  770. {
  771. return bHmdEnabled;
  772. }
  773. void FFoveHMD::EnableHMD(const bool enable)
  774. {
  775. // Early out
  776. if (bHmdEnabled == enable)
  777. return;
  778. // The documentation for this function in unreal simply states: "Enables or disables switching to stereo."
  779. // The meaning of the statement is unclear and could be either:
  780. // a) Enables/disables stereo directly
  781. // b) Enables/disables the ability to enable stereo (but enabling stereo would be a separate call)
  782. // We've taken it to mean the latter, so we don't enable stereo when the hmd is enabled
  783. // However, if you disable the hmd, we no longer have the ability to be in stereo, so we disable that
  784. if (!enable)
  785. EnableStereo(false);
  786. // Update cached state
  787. // This happens after the call to EnableStereo(false) as that function becomes a no-op when bHmdEnabled is true
  788. bHmdEnabled = enable;
  789. }
  790. EHMDDeviceType::Type FFoveHMD::GetHMDDeviceType() const
  791. {
  792. return EHMDDeviceType::DT_ES2GenericStereoMesh;
  793. }
  794. bool FFoveHMD::GetHMDMonitorInfo(MonitorInfo& outInfo)
  795. {
  796. // Write default values
  797. outInfo.MonitorName = "";
  798. outInfo.MonitorId = 0;
  799. outInfo.DesktopX = outInfo.DesktopY = outInfo.ResolutionX = outInfo.ResolutionY = outInfo.WindowSizeX = outInfo.WindowSizeX = 0;
  800. // Write resolution
  801. outInfo.ResolutionX = outInfo.WindowSizeX = FoveCompositorLayer.idealResolutionPerEye.x * 2; // Stereo rendering places the two eyes side by side horizontally
  802. outInfo.ResolutionY = outInfo.WindowSizeY = FoveCompositorLayer.idealResolutionPerEye.y;
  803. return true;
  804. }
  805. void FFoveHMD::GetFieldOfView(float& OutHFOVInDegrees, float& OutVFOVInDegrees) const
  806. {
  807. OutHFOVInDegrees = 0.0f; // TODO
  808. OutVFOVInDegrees = 0.0f;
  809. }
  810. bool FFoveHMD::IsChromaAbCorrectionEnabled() const
  811. {
  812. // Note from Unreal after being asked why the engine needs to know this:
  813. // Generally, we don't! However, on certain platforms, there are options to turn on and off
  814. // chromatic aberration correction to trade off performance and quality, which is why we
  815. // provide the option in the interface. It's fine to always return true if you're doing it.
  816. return true;
  817. }
  818. void FFoveHMD::SetInterpupillaryDistance(float NewInterpupillaryDistance)
  819. {
  820. UE_LOG(LogHMD, Warning, TEXT("FOVE does not support SetInterpupillaryDistance"));
  821. }
  822. float FFoveHMD::GetInterpupillaryDistance() const
  823. {
  824. // Fetch inter-ocular distance from Fove service
  825. float Ret = 0.064f; // Sane default in the event of error
  826. const Fove::EFVR_ErrorCode Error = FoveHeadset->GetIOD(&Ret);
  827. if (Error != Fove::EFVR_ErrorCode::None)
  828. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::GetIOD failed: %d"), static_cast<int>(Error));
  829. return Ret;
  830. }
  831. bool FFoveHMD::DoesSupportPositionalTracking() const
  832. {
  833. // Todo: Fove supports position tracking in general,
  834. // but should we query whether the position camera is connected for this?
  835. return true;
  836. }
  837. bool FFoveHMD::HasValidTrackingPosition()
  838. {
  839. // Todo: FOVE API has no way to return whether we currently have a valid position, simply that position tracking is running
  840. bool Ret = false;
  841. const Fove::EFVR_ErrorCode Error = FoveHeadset->IsPositionReady(&Ret);
  842. if (Error != Fove::EFVR_ErrorCode::None)
  843. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::IsPositionReady: %d"), static_cast<int>(Error));
  844. return Ret;
  845. }
  846. void FFoveHMD::RebaseObjectOrientationAndPosition(FVector& Position, FQuat& Orientation) const
  847. {
  848. UE_LOG(LogHMD, Warning, TEXT("FOVE does not support RebaseObjectOrientationAndPosition"));
  849. }
  850. bool FFoveHMD::IsHeadTrackingAllowed() const
  851. {
  852. return GEngine && GEngine->IsStereoscopic3D();
  853. }
  854. void FFoveHMD::ResetOrientationAndPosition(float yaw)
  855. {
  856. // Note from Unreal about this function:
  857. // The intention of these functions is to allow the user to reset the calibrated
  858. // position at any point in the experience. Generally, this takes the form of
  859. // saving a base orientation and position, and then using those to modify the
  860. // pose returned from the SDK as a "poor man's calibration."
  861. ResetOrientation(yaw);
  862. ResetPosition();
  863. }
  864. void FFoveHMD::ResetOrientation(float yaw)
  865. {
  866. // Fixme: what to do with yaw?
  867. FoveHeadset->TareOrientationSensor();
  868. }
  869. void FFoveHMD::ResetPosition()
  870. {
  871. FoveHeadset->TarePositionSensors();
  872. }
  873. void FFoveHMD::SetBaseRotation(const FRotator& BaseRot)
  874. {
  875. BaseOrientation = BaseRot.Quaternion();
  876. }
  877. FRotator FFoveHMD::GetBaseRotation() const
  878. {
  879. return BaseOrientation.Rotator();
  880. }
  881. void FFoveHMD::SetBaseOrientation(const FQuat& BaseOrient)
  882. {
  883. BaseOrientation = BaseOrient;
  884. }
  885. FQuat FFoveHMD::GetBaseOrientation() const
  886. {
  887. return BaseOrientation;
  888. }
  889. void FFoveHMD::OnBeginPlay(FWorldContext& InWorldContext)
  890. {
  891. EnableStereo(true);
  892. }
  893. void FFoveHMD::OnEndPlay(FWorldContext& InWorldContext)
  894. {
  895. EnableStereo(false);
  896. }
  897. void FFoveHMD::SetTrackingOrigin(EHMDTrackingOrigin::Type NewOrigin)
  898. {
  899. // Note from Unreal:
  900. // This basically allows you to consider the calibrated origin in two locations, depending on the style of game and hardware.
  901. // EHMDTrackingOrigin::Eye means that the "zero" position is where the player's eyes are. Floor means that it's on the floor.
  902. // The difference matters to how people set up their content. Generally, games that require the player to stand use the Floor
  903. // origin, and the player's pawn is set up so that the Camera Component's parent is located at the bottom of their collision (at the feet).
  904. // This is nice, because you know that the player's height in game will be the same as their real world height.
  905. // Alternatively, for games where the player is disembodied, or sitting in a chair, it's more useful to consider the origin of the
  906. // camera at their eyes, so you can place the parent there. The player is no longer their true height, but for cockpit games, etc. this doesn't matter.
  907. switch (NewOrigin)
  908. {
  909. case EHMDTrackingOrigin::Eye:
  910. break;
  911. default:
  912. // Fove currently only supports sitting experiences, if a game tries to set this, log a warning
  913. UE_LOG(LogHMD, Warning, TEXT("FOVE only supports EHMDTrackingOrigin::Eye"));
  914. break;
  915. }
  916. }
  917. EHMDTrackingOrigin::Type FFoveHMD::GetTrackingOrigin()
  918. {
  919. // Currently, FOVE only supports sitting experiences. See comment in SetTrackingOrigin
  920. return EHMDTrackingOrigin::Eye;
  921. }
  922. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION < 18 // Removed in 4.18
  923. void FFoveHMD::GetPositionalTrackingCameraProperties(FVector&, FQuat&, float&, float&, float&, float&, float&) const
  924. {
  925. UE_LOG(LogHMD, Warning, TEXT("FOVE does not support GetPositionalTrackingCameraProperties"));
  926. }
  927. void FFoveHMD::GetCurrentOrientationAndPosition(FQuat& CurrentOrientation, FVector& CurrentPosition)
  928. {
  929. PrivOrientationAndPosition(CurrentOrientation, CurrentPosition);
  930. }
  931. TSharedPtr< class ISceneViewExtension, ESPMode::ThreadSafe > FFoveHMD::GetViewExtension()
  932. {
  933. TSharedPtr< FFoveHMD, ESPMode::ThreadSafe > ptr(AsShared());
  934. return StaticCastSharedPtr< ISceneViewExtension >(ptr);
  935. }
  936. void FFoveHMD::ApplyHmdRotation(APlayerController* PC, FRotator& ViewRotation)
  937. {
  938. ViewRotation.Normalize();
  939. FQuat hmdOrientation;
  940. FVector hmdPosition;
  941. GetCurrentOrientationAndPosition(hmdOrientation, hmdPosition);
  942. const FRotator DeltaRot = ViewRotation - PC->GetControlRotation();
  943. ControlRotation = (ControlRotation + DeltaRot).GetNormalized();
  944. // Pitch from other sources is never good, because there is an absolute up and down that must be respected to avoid motion sickness.
  945. // Same with roll. Retain yaw by default - mouse/controller based yaw movement still isn't pleasant, but
  946. // it's necessary for sitting VR experiences.
  947. ControlRotation.Pitch = 0;
  948. ControlRotation.Roll = 0;
  949. ViewRotation = FRotator(ControlRotation.Quaternion() * hmdOrientation);
  950. AppliedHmdOrientation = FRotator(hmdOrientation);
  951. AppliedHmdOrientation.Pitch = 0;
  952. AppliedHmdOrientation.Roll = 0;
  953. }
  954. bool FFoveHMD::UpdatePlayerCamera(FQuat& CurrentOrientation, FVector& CurrentPosition)
  955. {
  956. FQuat hmdOrientation;
  957. FVector hmdPosition;
  958. GetCurrentOrientationAndPosition(hmdOrientation, hmdPosition);
  959. CurrentOrientation = hmdOrientation;
  960. CurrentPosition = AppliedHmdOrientation.Quaternion().Inverse().RotateVector(hmdPosition);
  961. return true;
  962. }
  963. bool FFoveHMD::IsPositionalTrackingEnabled() const
  964. {
  965. return true;
  966. }
  967. #endif
  968. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION < 16
  969. bool FFoveHMD::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar)
  970. {
  971. if (FParse::Command(&Cmd, TEXT("STEREO")))
  972. {
  973. if (FParse::Command(&Cmd, TEXT("ON")))
  974. {
  975. if (!IsHMDEnabled())
  976. {
  977. Ar.Logf(TEXT("HMD is disabled. Use 'hmd enable' to re-enable it"));
  978. }
  979. EnableStereo(true);
  980. return true;
  981. }
  982. else if (FParse::Command(&Cmd, TEXT("OFF")))
  983. {
  984. EnableStereo(false);
  985. return true;
  986. }
  987. }
  988. else if (FParse::Command(&Cmd, TEXT("HMD")))
  989. {
  990. if (FParse::Command(&Cmd, TEXT("ENABLE")))
  991. {
  992. EnableHMD(true);
  993. return true;
  994. }
  995. else if (FParse::Command(&Cmd, TEXT("DISABLE")))
  996. {
  997. EnableHMD(false);
  998. return true;
  999. }
  1000. }
  1001. else if (FParse::Command(&Cmd, TEXT("UNCAPFPS")))
  1002. {
  1003. GEngine->bSmoothFrameRate = false;
  1004. return true;
  1005. }
  1006. else if (FParse::Command(&Cmd, TEXT("HEADTRACKING")))
  1007. {
  1008. FString val;
  1009. if (FParse::Value(Cmd, TEXT("SOURCE="), val))
  1010. {
  1011. EnablePositionalTracking(false);
  1012. //OSVRInterfaceName = val;
  1013. EnablePositionalTracking(true);
  1014. }
  1015. if (FParse::Command(&Cmd, TEXT("ENABLE")))
  1016. {
  1017. EnablePositionalTracking(true);
  1018. return true;
  1019. }
  1020. else if (FParse::Command(&Cmd, TEXT("DISABLE")))
  1021. {
  1022. EnablePositionalTracking(false);
  1023. return true;
  1024. }
  1025. }
  1026. return false;
  1027. }
  1028. bool FFoveHMD::EnablePositionalTracking(bool bEnable)
  1029. {
  1030. UE_LOG(LogHMD, Warning, TEXT("FOVE does not support EnablePositionalTracking"));
  1031. return true;
  1032. }
  1033. bool FFoveHMD::IsInLowPersistenceMode() const
  1034. {
  1035. return true; // Not supported, game can think of us as always in low persistence mode
  1036. }
  1037. void FFoveHMD::EnableLowPersistenceMode(bool bEnable)
  1038. {
  1039. UE_LOG(LogHMD, Warning, TEXT("FOVE does not support EnableLowPersistenceMode"));
  1040. }
  1041. #endif
  1042. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 18
  1043. FMatrix FFoveHMD::GetStereoProjectionMatrix(const EStereoscopicPass StereoPass) const
  1044. {
  1045. return PrivStereoProjectionMatrix(StereoPass);
  1046. }
  1047. #endif
  1048. void FFoveHMD::SetClippingPlanes(float NCP, float FCP)
  1049. {
  1050. ZNear = NCP;
  1051. ZFar = FCP;
  1052. }
  1053. void FFoveHMD::GetEyeRenderParams_RenderThread(const FRenderingCompositePassContext& Context, FVector2D& EyeToSrcUVScaleValue, FVector2D& EyeToSrcUVOffsetValue) const
  1054. {
  1055. if (Context.View.StereoPass == eSSP_LEFT_EYE)
  1056. {
  1057. EyeToSrcUVOffsetValue.X = 0.0f;
  1058. EyeToSrcUVOffsetValue.Y = 0.0f;
  1059. }
  1060. else
  1061. {
  1062. EyeToSrcUVOffsetValue.X = 0.5f;
  1063. EyeToSrcUVOffsetValue.Y = 0.0f;
  1064. }
  1065. EyeToSrcUVScaleValue = FVector2D(0.5f, 1.0f);
  1066. }
  1067. bool FFoveHMD::IsStereoEnabled() const
  1068. {
  1069. check(!bStereoEnabled || bHmdEnabled); // bHmdEnabled must be true for bStereoEnabled to be true
  1070. return bStereoEnabled;
  1071. }
  1072. bool FFoveHMD::EnableStereo(const bool enable)
  1073. {
  1074. // Early out
  1075. if (enable == bStereoEnabled)
  1076. return enable;
  1077. // Don't allow enablement of stereo on while the headset is disabled (see comment in EnableHMD)
  1078. if (!bHmdEnabled)
  1079. {
  1080. check(!bStereoEnabled);
  1081. return false;
  1082. }
  1083. // Edit scene viewport
  1084. if (FSceneViewport* const sceneVP = FoveFindSceneViewport())
  1085. {
  1086. const TSharedPtr<SWindow> window = sceneVP->FindWindow();
  1087. if (enable)
  1088. {
  1089. // If we're enabling stereo rendering, set resolution to headset resolution
  1090. MonitorInfo info;
  1091. if (GetHMDMonitorInfo(info))
  1092. {
  1093. sceneVP->SetViewportSize(info.ResolutionX, info.ResolutionY);
  1094. }
  1095. }
  1096. else if (window.IsValid())
  1097. {
  1098. // If we're disabling stereo rendering, set screen resolution to window size
  1099. FVector2D size = window->GetSizeInScreen();
  1100. sceneVP->SetViewportSize(size.X, size.Y);
  1101. //UE_LOG(LogHMD, Warning, TEXT(text.c_str()));
  1102. }
  1103. // Viewport driven by window only when not in stereo mode
  1104. if (window.IsValid())
  1105. window->SetViewportSizeDrivenByWindow(!enable);
  1106. }
  1107. // Uncap fps to ensure we render at the framerate that FOVE needs
  1108. GEngine->bForceDisableFrameRateSmoothing = enable;
  1109. // Cache state of stereo enablement
  1110. bStereoEnabled = enable;
  1111. return bStereoEnabled;
  1112. }
  1113. void FFoveHMD::AdjustViewRect(EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const
  1114. {
  1115. SizeX = SizeX / 2;
  1116. if (StereoPass == eSSP_RIGHT_EYE)
  1117. {
  1118. X += SizeX;
  1119. }
  1120. }
  1121. void FFoveHMD::GetOrthoProjection(int32 RTWidth, int32 RTHeight, float OrthoDistance, FMatrix OrthoProjection[2]) const
  1122. {
  1123. const float HudOffset = 50.0f;
  1124. OrthoProjection[0] = FTranslationMatrix(FVector(HudOffset, 0.0f, 0.0f));
  1125. OrthoProjection[1] = FTranslationMatrix(FVector(-HudOffset + RTWidth * 0.5f, 0.0f, 0.0f));
  1126. }
  1127. void FFoveHMD::InitCanvasFromView(FSceneView* InView, UCanvas* Canvas)
  1128. {
  1129. // Couldn't find any other HMD plugins that do anything here
  1130. // Leaving blank for now
  1131. }
  1132. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION < 18 // Removed in 4.18
  1133. void FFoveHMD::CalculateStereoViewOffset(const EStereoscopicPass StereoPassType, const FRotator& ViewRotation, const float WorldToMeters, FVector& ViewLocation)
  1134. {
  1135. if (StereoPassType == eSSP_LEFT_EYE || StereoPassType == eSSP_RIGHT_EYE)
  1136. {
  1137. const float EyeOffset = GetInterpupillaryDistance() * WorldToMeters / (StereoPassType == eSSP_LEFT_EYE ? -2.0f : 2.0f);
  1138. ViewLocation += ViewRotation.Quaternion().RotateVector(FVector(0, EyeOffset, 0));
  1139. }
  1140. }
  1141. FMatrix FFoveHMD::GetStereoProjectionMatrix(enum EStereoscopicPass StereoPass, const float FOV_ignored) const
  1142. {
  1143. return PrivStereoProjectionMatrix(StereoPass);
  1144. }
  1145. void FFoveHMD::RenderTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FTexture2DRHIParamRef BackBuffer, FTexture2DRHIParamRef SrcTexture) const
  1146. {
  1147. check(IsInRenderingThread());
  1148. // Abort if we have not enabled mirroring
  1149. if (WindowMirrorMode == 0)
  1150. {
  1151. return;
  1152. }
  1153. const uint32 ViewportWidth = BackBuffer->GetSizeX();
  1154. const uint32 ViewportHeight = BackBuffer->GetSizeY();
  1155. // Set & clear the render target
  1156. {
  1157. // Need to clear when rendering only one eye since the borders won't be touched by the DrawRect below
  1158. const bool needsToClear = WindowMirrorMode == 1;
  1159. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 14
  1160. const ERenderTargetLoadAction loadAction = needsToClear ? ERenderTargetLoadAction::EClear : ERenderTargetLoadAction::ENoAction;
  1161. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION >= 16
  1162. FRHIRenderTargetView target(BackBuffer, loadAction);
  1163. #else
  1164. FRHIRenderTargetView target(BackBuffer);
  1165. target.LoadAction = loadAction;
  1166. #endif
  1167. RHICmdList.SetRenderTargetsAndClear(FRHISetRenderTargetsInfo(1, &target, FRHIDepthRenderTargetView()));
  1168. #else
  1169. SetRenderTarget(RHICmdList, BackBuffer, FTextureRHIRef());
  1170. #endif
  1171. // Issue clear command on older versions that don't do it with the render target set
  1172. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION < 14
  1173. if (needsToClear)
  1174. {
  1175. RHICmdList.Clear(true, FLinearColor::Black, false, 0, false, 0, FIntRect());
  1176. }
  1177. #endif
  1178. RHICmdList.SetViewport(0, 0, 0, ViewportWidth, ViewportHeight, 1.0f);
  1179. }
  1180. // Get shaders
  1181. const auto FeatureLevel = GMaxRHIFeatureLevel;
  1182. auto ShaderMap = GetGlobalShaderMap(FeatureLevel);
  1183. TShaderMapRef<FScreenVS> VertexShader(ShaderMap);
  1184. TShaderMapRef<FScreenPS> PixelShader(ShaderMap);
  1185. // Set render state
  1186. #ifdef FOVE_USE_PIPLINE_STATE_CACHE
  1187. FGraphicsPipelineStateInitializer piplineState;
  1188. RHICmdList.ApplyCachedRenderTargets(piplineState);
  1189. piplineState.BlendState = TStaticBlendState<>::GetRHI();
  1190. piplineState.RasterizerState = TStaticRasterizerState<>::GetRHI();
  1191. piplineState.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
  1192. piplineState.BoundShaderState.VertexDeclarationRHI = RendererModule->GetFilterVertexDeclaration().VertexDeclarationRHI;
  1193. piplineState.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader);
  1194. piplineState.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader);
  1195. piplineState.PrimitiveType = PT_TriangleList;
  1196. SetGraphicsPipelineState(RHICmdList, piplineState);
  1197. #else
  1198. RHICmdList.SetBlendState(TStaticBlendState<>::GetRHI());
  1199. RHICmdList.SetRasterizerState(TStaticRasterizerState<>::GetRHI());
  1200. RHICmdList.SetDepthStencilState(TStaticDepthStencilState<false, CF_Always>::GetRHI());
  1201. static FGlobalBoundShaderState BoundShaderState;
  1202. SetGlobalBoundShaderState(RHICmdList, FeatureLevel, BoundShaderState, RendererModule->GetFilterVertexDeclaration().VertexDeclarationRHI, *VertexShader, *PixelShader);
  1203. #endif
  1204. // Set shader properties
  1205. PixelShader->SetParameters(RHICmdList, TStaticSamplerState<SF_Bilinear>::GetRHI(), SrcTexture);
  1206. // Draw a rectangle with the content of one or both of the eye images, depending on the mirror mode
  1207. RendererModule->DrawRectangle(
  1208. RHICmdList,
  1209. WindowMirrorMode == 1 ? ViewportWidth / 4 : 0, // X
  1210. 0, // Y
  1211. WindowMirrorMode == 1 ? ViewportWidth / 2 : ViewportWidth, // SizeX
  1212. ViewportHeight, // SizeY
  1213. WindowMirrorMode == 1 ? 0.1f : 0.0f, // U
  1214. WindowMirrorMode == 1 ? 0.2f : 0.0f, // V
  1215. WindowMirrorMode == 1 ? 0.3f : 1.0f, // SizeU
  1216. WindowMirrorMode == 1 ? 0.6f : 1.0f, // SizeV
  1217. FIntPoint(ViewportWidth, ViewportHeight),
  1218. FIntPoint(1, 1),
  1219. *VertexShader,
  1220. EDRF_Default);
  1221. }
  1222. void FFoveHMD::CalculateRenderTargetSize(const FViewport& Viewport, uint32& InOutSizeX, uint32& InOutSizeY)
  1223. {
  1224. check(IsInGameThread());
  1225. // if (Flags.bScreenPercentageEnabled)
  1226. {
  1227. static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataFloat(TEXT("r.ScreenPercentage"));
  1228. float value = CVar->GetValueOnGameThread();
  1229. if (value > 0.0f)
  1230. {
  1231. InOutSizeX = FMath::CeilToInt(InOutSizeX * value / 100.f);
  1232. InOutSizeY = FMath::CeilToInt(InOutSizeY * value / 100.f);
  1233. }
  1234. }
  1235. }
  1236. bool FFoveHMD::NeedReAllocateViewportRenderTarget(const FViewport& Viewport)
  1237. {
  1238. check(IsInGameThread());
  1239. if (IsStereoEnabled())
  1240. {
  1241. const uint32 InSizeX = Viewport.GetSizeXY().X;
  1242. const uint32 InSizeY = Viewport.GetSizeXY().Y;
  1243. FIntPoint RenderTargetSize;
  1244. RenderTargetSize.X = Viewport.GetRenderTargetTexture()->GetSizeX();
  1245. RenderTargetSize.Y = Viewport.GetRenderTargetTexture()->GetSizeY();
  1246. uint32 NewSizeX = InSizeX, NewSizeY = InSizeY;
  1247. CalculateRenderTargetSize(Viewport, NewSizeX, NewSizeY);
  1248. if (NewSizeX != RenderTargetSize.X || NewSizeY != RenderTargetSize.Y)
  1249. {
  1250. return true;
  1251. }
  1252. }
  1253. return false;
  1254. }
  1255. bool FFoveHMD::ShouldUseSeparateRenderTarget() const
  1256. {
  1257. check(IsInGameThread());
  1258. return IsStereoEnabled();
  1259. }
  1260. void FFoveHMD::UpdateViewport(bool bUseSeparateRenderTarget, const FViewport& InViewport, SViewport* ViewportWidget)
  1261. {
  1262. check(IsInGameThread());
  1263. const FViewportRHIRef& viewportRef = InViewport.GetViewportRHI();
  1264. if (viewportRef)
  1265. {
  1266. if (Bridge && IsStereoEnabled())
  1267. {
  1268. viewportRef->SetCustomPresent(Bridge);
  1269. Bridge->UpdateViewport(InViewport);
  1270. }
  1271. else
  1272. {
  1273. viewportRef->SetCustomPresent(nullptr);
  1274. }
  1275. }
  1276. }
  1277. #endif
  1278. void FFoveHMD::SetupViewFamily(FSceneViewFamily& InViewFamily)
  1279. {
  1280. InViewFamily.EngineShowFlags.MotionBlur = 0;
  1281. InViewFamily.EngineShowFlags.HMDDistortion = false;
  1282. InViewFamily.EngineShowFlags.StereoRendering = IsStereoEnabled();
  1283. }
  1284. void FFoveHMD::SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView)
  1285. {
  1286. PrivOrientationAndPosition(InView.BaseHmdOrientation, InView.BaseHmdLocation);
  1287. WorldToMetersScale = InView.WorldToMetersScale;
  1288. InViewFamily.bUseSeparateRenderTarget = true;
  1289. }
  1290. void FFoveHMD::PreRenderView_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView)
  1291. {
  1292. check(IsInRenderingThread());
  1293. // Update the view rotation with the latest value, sampled just beforehand in PreRenderViewFamily_RenderThread
  1294. if (Bridge)
  1295. {
  1296. const FQuat DeltaOrient = InView.BaseHmdOrientation.Inverse() * Bridge->GetRenderPose().GetRotation();
  1297. InView.ViewRotation = FRotator(InView.ViewRotation.Quaternion() * DeltaOrient);
  1298. InView.UpdateViewMatrix();
  1299. }
  1300. }
  1301. void FFoveHMD::PreRenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& ViewFamily)
  1302. {
  1303. check(IsInRenderingThread());
  1304. if (Bridge)
  1305. {
  1306. // Blocks until the next time we need to render, as determined by the compositor, and fetches a new pose to use during rendering
  1307. // This allows the compositor to cap rendering at exactly the frame rate needed, so we don't draw more frames than the compositor can use
  1308. // Vsync and any other frame rate limiting options within Unreal should be disabled when using with FOVE to ensure this works well
  1309. // This also lets us update the pose just before rendering, so time warp only needs to correct by a small amount
  1310. Fove::SFVR_Pose FovePose;
  1311. const Fove::EFVR_ErrorCode Error = GetCompositor().WaitForRenderPose(&FovePose);
  1312. if (Error != Fove::EFVR_ErrorCode::None)
  1313. {
  1314. UE_LOG(LogHMD, Warning, TEXT("IFVRCompositor::WaitForRenderPose failed: %d"), static_cast<int>(Error));
  1315. }
  1316. else
  1317. {
  1318. // We will be moving the view location just before rendering, so camera-attached objects need a late update to stay locked to the view
  1319. // The API was removed for this so apparently it no longer needs up happen in 4.18+?
  1320. #if ENGINE_MAJOR_VERSION >= 4 && ENGINE_MINOR_VERSION < 18
  1321. const FTransform LastPose = Bridge->GetRenderPose();
  1322. Bridge->SetRenderPose(FovePose, WorldToMetersScale);
  1323. const FTransform NewPose = Bridge->GetRenderPose();
  1324. ApplyLateUpdate(ViewFamily.Scene, LastPose, NewPose);
  1325. #endif
  1326. }
  1327. }
  1328. }
  1329. void FFoveHMD::PrivOrientationAndPosition(FQuat& OutOrientation, FVector& OutPosition)
  1330. {
  1331. checkf(IsInGameThread(), TEXT("PrivOrientationAndPosition called from not game thread"));
  1332. FTransform transform;
  1333. if (Bridge)
  1334. {
  1335. transform = Bridge->GetRenderPose();
  1336. }
  1337. else
  1338. {
  1339. Fove::SFVR_Pose Pose;
  1340. const Fove::EFVR_ErrorCode Error = FoveHeadset->GetHMDPose(&Pose);
  1341. if (Error != Fove::EFVR_ErrorCode::None)
  1342. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::GetHMDPose failed: %d"), static_cast<int>(Error));
  1343. transform = ToUnreal(Pose, WorldToMetersScale);
  1344. }
  1345. OutOrientation = transform.GetRotation();
  1346. OutPosition = transform.GetLocation();
  1347. }
  1348. FMatrix FFoveHMD::PrivStereoProjectionMatrix(const EStereoscopicPass StereoPass) const
  1349. {
  1350. check(IsStereoEnabled());
  1351. // Query Fove SDK for projection matrix for this eye
  1352. Fove::SFVR_Matrix44 FoveMat;
  1353. const Fove::EFVR_ErrorCode Error = FoveHeadset->GetProjectionMatricesLH(ZNear, ZFar, StereoPass == eSSP_LEFT_EYE ? &FoveMat : nullptr, StereoPass != eSSP_LEFT_EYE ? &FoveMat : nullptr);
  1354. if (Error != Fove::EFVR_ErrorCode::None)
  1355. UE_LOG(LogHMD, Warning, TEXT("IFVRHeadset::IsPositionReady: %d"), static_cast<int>(Error));
  1356. // Convert to Unreal matrix and correct near/far clip (which use reversed-Z in Unreal)
  1357. FMatrix Ret = ToUnreal(FoveMat);
  1358. Ret.M[3][3] = 0.0f;
  1359. Ret.M[2][3] = 1.0f;
  1360. Ret.M[2][2] = ZNear == ZFar ? 0.0f : ZNear / (ZNear - ZFar);
  1361. Ret.M[3][2] = ZNear == ZFar ? ZNear : -ZFar * ZNear / (ZNear - ZFar);
  1362. return Ret;
  1363. }
  1364. #ifdef _MSC_VER
  1365. #pragma endregion
  1366. #endif