/************************************************************************************ Filename : OculusWorldDemo.cpp Content : First-person view test application for Oculus Rift Created : October 4, 2012 Authors : Michael Antonov, Andrew Reisse, Steve LaValle Peter Hoff, Dan Goodman, Bryan Croteau Copyright : Copyright 2012 Oculus VR, Inc. All Rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. *************************************************************************************/ #include "OVR.h" #include "../CommonSrc/Platform/Platform_Default.h" #include "../CommonSrc/Render/Render_Device.h" #include "../CommonSrc/Render/Render_XmlSceneLoader.h" #include "../CommonSrc/Render/Render_FontEmbed_DejaVu48.h" #include "../CommonSrc/Platform/Gamepad.h" #include #include #include #include "Player.h" // Filename to be loaded by default, searching specified paths. #define WORLDDEMO_ASSET_FILE "Tuscany.xml" #define WORLDDEMO_ASSET_PATH1 "Assets/Tuscany/" #define WORLDDEMO_ASSET_PATH2 "../Assets/Tuscany/" // This path allows the shortcut to work. #define WORLDDEMO_ASSET_PATH3 "Samples/OculusWorldDemo/Assets/Tuscany/" using namespace OVR; using namespace OVR::Platform; using namespace OVR::Render; //------------------------------------------------------------------------------------- // ***** OculusWorldDemo Description // This app renders a simple flat-shaded room allowing the user to move along the // floor and look around with an HMD, mouse and keyboard. The following keys work: // // 'W', 'S', 'A', 'D' and Arrow Keys - Move forward, back; strafe left/right. // F1 - No stereo, no distortion. // F2 - Stereo, no distortion. // F3 - Stereo and distortion. // F4 - Toggle MSAA. // F9 - Cycle through fullscreen and windowed modes. Necessary for previewing content with Rift. // // Important Oculus-specific logic can be found at following locations: // // OculusWorldDemoApp::OnStartup - This function will initialize OVR::DeviceManager and HMD, // creating SensorDevice and attaching it to SensorFusion. // This needs to be done before obtaining sensor data. // // OculusWorldDemoApp::OnIdle - Here we poll SensorFusion for orientation, apply it // to the scene and handle movement. // Stereo rendering is also done here, by delegating to // to Render function for each eye. // //------------------------------------------------------------------------------------- // ***** OculusWorldDemo Application class // An instance of this class is created on application startup (main/WinMain). // It then works as follows: // - Graphics and HMD setup is done OculusWorldDemoApp::OnStartup(). This function // also creates the room model from Slab declarations. // - Per-frame processing is done in OnIdle(). This function processes // sensor and movement input and then renders the frame. // - Additional input processing is done in OnMouse, OnKey. class OculusWorldDemoApp : public Application, public MessageHandler { public: OculusWorldDemoApp(); ~OculusWorldDemoApp(); virtual int OnStartup(int argc, const char** argv); virtual void OnIdle(); virtual void OnMouseMove(int x, int y, int modifiers); virtual void OnKey(OVR::KeyCode key, int chr, bool down, int modifiers); virtual void OnResize(int width, int height); virtual void OnMessage(const Message& msg); void Render(const StereoEyeParams& stereo); // Sets temporarily displayed message for adjustments void SetAdjustMessage(const char* format, ...); // Overrides current timeout, in seconds (not the future default value); // intended to be called right after SetAdjustMessage. void SetAdjustMessageTimeout(float timeout); // Stereo setting adjustment functions. // Called with deltaTime when relevant key is held. void AdjustFov(float dt); void AdjustAspect(float dt); void AdjustIPD(float dt); void AdjustEyeHeight(float dt); void AdjustMotionPrediction(float dt); void AdjustDistortion(float dt, int kIndex, const char* label); void AdjustDistortionK0(float dt) { AdjustDistortion(dt, 0, "K0"); } void AdjustDistortionK1(float dt) { AdjustDistortion(dt, 1, "K1"); } void AdjustDistortionK2(float dt) { AdjustDistortion(dt, 2, "K2"); } void AdjustDistortionK3(float dt) { AdjustDistortion(dt, 3, "K3"); } // Adds room model to scene. void PopulateScene(const char* fileName); void PopulatePreloadScene(); void ClearScene(); // Magnetometer calibration procedure void UpdateManualMagCalibration(); protected: RenderDevice* pRender; RendererParams RenderParams; int Width, Height; int Screen; int FirstScreenInCycle; // Magnetometer calibration and yaw correction Util::MagCalibration MagCal; bool MagAwaitingForwardLook; // *** Oculus HMD Variables Ptr pManager; Ptr pSensor; Ptr pHMD; Ptr pUserProfile; SensorFusion SFusion; HMDInfo TheHMDInfo; Ptr pLatencyTester; Util::LatencyTest LatencyUtil; double LastUpdate; int FPS; int FrameCounter; double NextFPSUpdate; Array > CollisionModels; Array > GroundCollisionModels; // Loading process displays screenshot in first frame // and then proceeds to load until finished. enum LoadingStateType { LoadingState_Frame0, LoadingState_DoLoad, LoadingState_Finished }; // Player Player ThePlayer; Matrix4f View; Scene MainScene; Scene LoadingScene; Scene GridScene; Scene YawMarkGreenScene; Scene YawMarkRedScene; Scene YawLinesScene; LoadingStateType LoadingState; Ptr LitSolid, LitTextures[4]; // Stereo view parameters. StereoConfig SConfig; PostProcessType PostProcess; // LOD String MainFilePath; Array LODFilePaths; int ConsecutiveLowFPSFrames; int CurrentLODFileIndex; float DistortionK0; float DistortionK1; float DistortionK2; float DistortionK3; String AdjustMessage; double AdjustMessageTimeout; // Saved distortion state. float SavedK0, SavedK1, SavedK2, SavedK3; float SavedESD, SavedAspect, SavedEyeDistance; // Allows toggling color around distortion. Color DistortionClearColor; // Stereo settings adjustment state. typedef void (OculusWorldDemoApp::*AdjustFuncType)(float); bool ShiftDown; AdjustFuncType pAdjustFunc; float AdjustDirection; enum SceneRenderMode { Scene_World, Scene_Grid, Scene_Both, Scene_YawView }; SceneRenderMode SceneMode; enum TextScreen { Text_None, Text_Orientation, Text_Config, Text_Help, Text_Count }; TextScreen TextScreen; struct DeviceStatusNotificationDesc { DeviceHandle Handle; MessageType Action; DeviceStatusNotificationDesc():Action(Message_None) {} DeviceStatusNotificationDesc(MessageType mt, const DeviceHandle& dev) : Handle(dev), Action(mt) {} }; Array DeviceStatusNotificationsQueue; Model* CreateModel(Vector3f pos, struct SlabModel* sm); Model* CreateBoundingModel(CollisionModel &cm); void PopulateLODFileNames(); void DropLOD(); void RaiseLOD(); void CycleDisplay(); void GamepadStateChanged(const GamepadState& pad); // Variable used by UpdateManualCalibration Anglef FirstMagYaw; int ManualMagCalStage; int ManualMagFailures; }; //------------------------------------------------------------------------------------- OculusWorldDemoApp::OculusWorldDemoApp() : pRender(0), LastUpdate(0), LoadingState(LoadingState_Frame0), // Initial location SConfig(), PostProcess(PostProcess_Distortion), DistortionClearColor(0, 0, 0), ShiftDown(false), pAdjustFunc(0), AdjustDirection(1.0f), SceneMode(Scene_World), TextScreen(Text_None) { Width = 1280; Height = 800; Screen = 0; FirstScreenInCycle = 0; FPS = 0; FrameCounter = 0; NextFPSUpdate = 0; ConsecutiveLowFPSFrames = 0; CurrentLODFileIndex = 0; AdjustMessageTimeout = 0; } OculusWorldDemoApp::~OculusWorldDemoApp() { RemoveHandlerFromDevices(); if(DejaVu.fill) { DejaVu.fill->Release(); } pLatencyTester.Clear(); pSensor.Clear(); pHMD.Clear(); CollisionModels.ClearAndRelease(); GroundCollisionModels.ClearAndRelease(); } int OculusWorldDemoApp::OnStartup(int argc, const char** argv) { // *** Oculus HMD & Sensor Initialization // Create DeviceManager and first available HMDDevice from it. // Sensor object is created from the HMD, to ensure that it is on the // correct device. pManager = *DeviceManager::Create(); // We'll handle it's messages in this case. pManager->SetMessageHandler(this); pHMD = *pManager->EnumerateDevices().CreateDevice(); if (pHMD) { pSensor = *pHMD->GetSensor(); // This will initialize HMDInfo with information about configured IPD, // screen size and other variables needed for correct projection. // We pass HMD DisplayDeviceName into the renderer to select the // correct monitor in full-screen mode. if(pHMD->GetDeviceInfo(&TheHMDInfo)) { //RenderParams.MonitorName = hmd.DisplayDeviceName; SConfig.SetHMDInfo(TheHMDInfo); } // Retrieve relevant profile settings. pUserProfile = pHMD->GetProfile(); if (pUserProfile) { ThePlayer.EyeHeight = pUserProfile->GetEyeHeight(); ThePlayer.EyePos.y = ThePlayer.EyeHeight; } } else { // If we didn't detect an HMD, try to create the sensor directly. // This is useful for debugging sensor interaction; it is not needed in // a shipping app. pSensor = *pManager->EnumerateDevices().CreateDevice(); } // Create the Latency Tester device and assign it to the LatencyTesterUtil object. pLatencyTester = *pManager->EnumerateDevices().CreateDevice(); if (pLatencyTester) { LatencyUtil.SetDevice(pLatencyTester); } // Make the user aware which devices are present. if(pHMD == NULL && pSensor == NULL) { SetAdjustMessage("---------------------------------\nNO HMD DETECTED\nNO SENSOR DETECTED\n---------------------------------"); } else if(pHMD == NULL) { SetAdjustMessage("----------------------------\nNO HMD DETECTED\n----------------------------"); } else if(pSensor == NULL) { SetAdjustMessage("---------------------------------\nNO SENSOR DETECTED\n---------------------------------"); } else { SetAdjustMessage("--------------------------------------------\n" "Press F9 for Full-Screen on Rift\n" "--------------------------------------------"); } // First message should be extra-long. SetAdjustMessageTimeout(10.0f); if(TheHMDInfo.HResolution > 0) { Width = TheHMDInfo.HResolution; Height = TheHMDInfo.VResolution; } if(!pPlatform->SetupWindow(Width, Height)) { return 1; } String Title = "Oculus World Demo"; if(TheHMDInfo.ProductName[0]) { Title += " : "; Title += TheHMDInfo.ProductName; } pPlatform->SetWindowTitle(Title); // Report relative mouse motion in OnMouseMove pPlatform->SetMouseMode(Mouse_Relative); if(pSensor) { // We need to attach sensor to SensorFusion object for it to receive // body frame messages and update orientation. SFusion.GetOrientation() // is used in OnIdle() to orient the view. SFusion.AttachToSensor(pSensor); SFusion.SetDelegateMessageHandler(this); SFusion.SetPredictionEnabled(true); } // *** Initialize Rendering const char* graphics = "d3d11"; // Select renderer based on command line arguments. for(int i = 1; i < argc; i++) { if(!strcmp(argv[i], "-r") && i < argc - 1) { graphics = argv[i + 1]; } else if(!strcmp(argv[i], "-fs")) { RenderParams.Fullscreen = true; } } // Enable multi-sampling by default. RenderParams.Multisample = 4; pRender = pPlatform->SetupGraphics(OVR_DEFAULT_RENDER_DEVICE_SET, graphics, RenderParams); // *** Configure Stereo settings. SConfig.SetFullViewport(Viewport(0, 0, Width, Height)); SConfig.SetStereoMode(Stereo_LeftRight_Multipass); // Configure proper Distortion Fit. // For 7" screen, fit to touch left side of the view, leaving a bit of // invisible screen on the top (saves on rendering cost). // For smaller screens (5.5"), fit to the top. if (TheHMDInfo.HScreenSize > 0.0f) { if (TheHMDInfo.HScreenSize > 0.140f) // 7" SConfig.SetDistortionFitPointVP(-1.0f, 0.0f); else SConfig.SetDistortionFitPointVP(0.0f, 1.0f); } pRender->SetSceneRenderScale(SConfig.GetDistortionScale()); //pRender->SetSceneRenderScale(1.0f); SConfig.Set2DAreaFov(DegreeToRad(85.0f)); // *** Identify Scene File & Prepare for Loading // This creates lights and models. if (argc == 2) { MainFilePath = argv[1]; PopulateLODFileNames(); } else { fprintf(stderr, "Usage: OculusWorldDemo [input XML]\n"); MainFilePath = WORLDDEMO_ASSET_FILE; } // Try to modify path for correctness in case specified file is not found. if (!SysFile(MainFilePath).IsValid()) { String prefixPath1(pPlatform->GetContentDirectory() + "/" + WORLDDEMO_ASSET_PATH1), prefixPath2(WORLDDEMO_ASSET_PATH2), prefixPath3(WORLDDEMO_ASSET_PATH3); if (SysFile(prefixPath1 + MainFilePath).IsValid()) MainFilePath = prefixPath1 + MainFilePath; else if (SysFile(prefixPath2 + MainFilePath).IsValid()) MainFilePath = prefixPath2 + MainFilePath; else if (SysFile(prefixPath3 + MainFilePath).IsValid()) MainFilePath = prefixPath3 + MainFilePath; } PopulatePreloadScene(); LastUpdate = pPlatform->GetAppTime(); //pPlatform->PlayMusicFile(L"Loop.wav"); return 0; } void OculusWorldDemoApp::OnMessage(const Message& msg) { if (msg.Type == Message_DeviceAdded || msg.Type == Message_DeviceRemoved) { if (msg.pDevice == pManager) { const MessageDeviceStatus& statusMsg = static_cast(msg); { // limit the scope of the lock Lock::Locker lock(pManager->GetHandlerLock()); DeviceStatusNotificationsQueue.PushBack( DeviceStatusNotificationDesc(statusMsg.Type, statusMsg.Handle)); } switch (statusMsg.Type) { case OVR::Message_DeviceAdded: LogText("DeviceManager reported device added.\n"); break; case OVR::Message_DeviceRemoved: LogText("DeviceManager reported device removed.\n"); break; default: OVR_ASSERT(0); // unexpected type } } } } void OculusWorldDemoApp::OnResize(int width, int height) { Width = width; Height = height; SConfig.SetFullViewport(Viewport(0, 0, Width, Height)); } void OculusWorldDemoApp::OnMouseMove(int x, int y, int modifiers) { if(modifiers & Mod_MouseRelative) { // Get Delta int dx = x, dy = y; const float maxPitch = ((3.1415f / 2) * 0.98f); // Apply to rotation. Subtract for right body frame rotation, // since yaw rotation is positive CCW when looking down on XZ plane. ThePlayer.EyeYaw -= (Sensitivity * dx) / 360.0f; if(!pSensor) { ThePlayer.EyePitch -= (Sensitivity * dy) / 360.0f; if(ThePlayer.EyePitch > maxPitch) { ThePlayer.EyePitch = maxPitch; } if(ThePlayer.EyePitch < -maxPitch) { ThePlayer.EyePitch = -maxPitch; } } } } void OculusWorldDemoApp::OnKey(OVR::KeyCode key, int chr, bool down, int modifiers) { OVR_UNUSED(chr); switch(key) { case Key_Q: if (down && (modifiers & Mod_Control)) { pPlatform->Exit(0); } break; // Handle player movement keys. // We just update movement state here, while the actual translation is done in OnIdle() // based on time. case Key_W: ThePlayer.MoveForward = down ? (ThePlayer.MoveForward | 1) : (ThePlayer.MoveForward & ~1); break; case Key_S: ThePlayer.MoveBack = down ? (ThePlayer.MoveBack | 1) : (ThePlayer.MoveBack & ~1); break; case Key_A: ThePlayer.MoveLeft = down ? (ThePlayer.MoveLeft | 1) : (ThePlayer.MoveLeft & ~1); break; case Key_D: ThePlayer.MoveRight = down ? (ThePlayer.MoveRight | 1) : (ThePlayer.MoveRight & ~1); break; case Key_Up: ThePlayer.MoveForward = down ? (ThePlayer.MoveForward | 2) : (ThePlayer.MoveForward & ~2); break; case Key_Down: ThePlayer.MoveBack = down ? (ThePlayer.MoveBack | 2) : (ThePlayer.MoveBack & ~2); break; case Key_Left: ThePlayer.MoveLeft = down ? (ThePlayer.MoveLeft | 2) : (ThePlayer.MoveLeft & ~2); break; case Key_Right: ThePlayer.MoveRight = down ? (ThePlayer.MoveRight | 2) : (ThePlayer.MoveRight & ~2); break; case Key_Minus: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustEyeHeight : 0; AdjustDirection = -1; break; case Key_Equal: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustEyeHeight : 0; AdjustDirection = 1; break; case Key_B: if (down) { if(SConfig.GetDistortionScale() == 1.0f) { if(SConfig.GetHMDInfo().HScreenSize > 0.140f) // 7" { SConfig.SetDistortionFitPointVP(-1.0f, 0.0f); } else { SConfig.SetDistortionFitPointVP(0.0f, 1.0f); } } else { // No fitting; scale == 1.0. SConfig.SetDistortionFitPointVP(0, 0); } } break; // Support toggling background color for distortion so that we can see // the effect on the periphery. case Key_V: if (down) { if(DistortionClearColor.B == 0) { DistortionClearColor = Color(0, 128, 255); } else { DistortionClearColor = Color(0, 0, 0); } pRender->SetDistortionClearColor(DistortionClearColor); } break; case Key_F1: SConfig.SetStereoMode(Stereo_None); PostProcess = PostProcess_None; SetAdjustMessage("StereoMode: None"); break; case Key_F2: SConfig.SetStereoMode(Stereo_LeftRight_Multipass); PostProcess = PostProcess_None; SetAdjustMessage("StereoMode: Stereo + No Distortion"); break; case Key_F3: SConfig.SetStereoMode(Stereo_LeftRight_Multipass); PostProcess = PostProcess_Distortion; SetAdjustMessage("StereoMode: Stereo + Distortion"); break; case Key_R: SFusion.Reset(); if (MagCal.IsAutoCalibrating() || MagCal.IsManuallyCalibrating()) MagCal.AbortCalibration(); SetAdjustMessage("Sensor Fusion Reset"); break; case Key_Space: if (!down) { TextScreen = (enum TextScreen)((TextScreen + 1) % Text_Count); } break; case Key_F4: if (!down) { RenderParams = pRender->GetParams(); RenderParams.Multisample = RenderParams.Multisample > 1 ? 1 : 4; pRender->SetParams(RenderParams); if(RenderParams.Multisample > 1) { SetAdjustMessage("Multisampling On"); } else { SetAdjustMessage("Multisampling Off"); } } break; case Key_F9: #ifndef OVR_OS_LINUX // On Linux F9 does the same as F11. if (!down) { CycleDisplay(); } break; #endif #ifdef OVR_OS_MAC case Key_F10: // F11 is reserved on Mac #else case Key_F11: #endif if (!down) { RenderParams = pRender->GetParams(); RenderParams.Display = DisplayId(SConfig.GetHMDInfo().DisplayDeviceName,SConfig.GetHMDInfo().DisplayId); pRender->SetParams(RenderParams); pPlatform->SetMouseMode(Mouse_Normal); pPlatform->SetFullscreen(RenderParams, pRender->IsFullscreen() ? Display_Window : Display_FakeFullscreen); pPlatform->SetMouseMode(Mouse_Relative); // Avoid mode world rotation jump. // If using an HMD, enable post-process (for distortion) and stereo. if(RenderParams.IsDisplaySet() && pRender->IsFullscreen()) { SConfig.SetStereoMode(Stereo_LeftRight_Multipass); PostProcess = PostProcess_Distortion; } } break; case Key_Escape: if(!down) { if (MagCal.IsAutoCalibrating() || MagCal.IsManuallyCalibrating()) { MagCal.AbortCalibration(); SetAdjustMessage("Aborting Magnetometer Calibration"); } else { // switch to primary screen windowed mode pPlatform->SetFullscreen(RenderParams, Display_Window); RenderParams.Display = pPlatform->GetDisplay(0); pRender->SetParams(RenderParams); Screen = 0; } } break; // Stereo adjustments. case Key_BracketLeft: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustFov : 0; AdjustDirection = 1; break; case Key_BracketRight: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustFov : 0; AdjustDirection = -1; break; case Key_Insert: case Key_Num0: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustIPD : 0; AdjustDirection = 1; break; case Key_Delete: case Key_Num9: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustIPD : 0; AdjustDirection = -1; break; case Key_PageUp: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustAspect : 0; AdjustDirection = 1; break; case Key_PageDown: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustAspect : 0; AdjustDirection = -1; break; // Distortion correction adjustments case Key_H: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustDistortionK0 : NULL; AdjustDirection = -1; break; case Key_Y: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustDistortionK0 : NULL; AdjustDirection = 1; break; case Key_J: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustDistortionK1 : NULL; AdjustDirection = -1; break; case Key_U: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustDistortionK1 : NULL; AdjustDirection = 1; break; case Key_K: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustDistortionK2 : NULL; AdjustDirection = -1; break; case Key_I: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustDistortionK2 : NULL; AdjustDirection = 1; break; case Key_L: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustDistortionK3 : NULL; AdjustDirection = -1; break; case Key_O: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustDistortionK3 : NULL; AdjustDirection = 1; break; case Key_Tab: if (down) { float t0 = SConfig.GetDistortionK(0), t1 = SConfig.GetDistortionK(1), t2 = SConfig.GetDistortionK(2), t3 = SConfig.GetDistortionK(3); float tESD = SConfig.GetEyeToScreenDistance(), taspect = SConfig.GetAspectMultiplier(), tipd = SConfig.GetIPD(); if(SavedK0 > 0.0f) { SConfig.SetDistortionK(0, SavedK0); SConfig.SetDistortionK(1, SavedK1); SConfig.SetDistortionK(2, SavedK2); SConfig.SetDistortionK(3, SavedK3); SConfig.SetEyeToScreenDistance(SavedESD); SConfig.SetAspectMultiplier(SavedAspect); SConfig.SetIPD(SavedEyeDistance); if ( ShiftDown ) { // Swap saved and current values. Good for doing direct comparisons. SetAdjustMessage("Swapped current and saved. New settings:\n" "ESD:\t120 %.3f\t350 Eye:\t490 %.3f\n" "K0: \t120 %.4f\t350 K2: \t490 %.4f\n" "K1: \t120 %.4f\t350 K3: \t490 %.4f\n", SavedESD, SavedEyeDistance, SavedK0, SavedK2, SavedK1, SavedK3); SavedK0 = t0; SavedK1 = t1; SavedK2 = t2; SavedK3 = t3; SavedESD = tESD; SavedAspect = taspect; SavedEyeDistance = tipd; } else { SetAdjustMessage("Restored:\n" "ESD:\t120 %.3f\t350 Eye:\t490 %.3f\n" "K0: \t120 %.4f\t350 K2: \t490 %.4f\n" "K1: \t120 %.4f\t350 K3: \t490 %.4f\n", SavedESD, SavedEyeDistance, SavedK0, SavedK2, SavedK1, SavedK3); } } else { SetAdjustMessage("Setting Saved"); SavedK0 = t0; SavedK1 = t1; SavedK2 = t2; SavedK3 = t3; SavedESD = tESD; SavedAspect = taspect; SavedEyeDistance = tipd; } } break; case Key_G: if (down) { if(SceneMode == Scene_World) { SceneMode = Scene_Grid; SetAdjustMessage("Grid Only"); } else if(SceneMode == Scene_Grid) { SceneMode = Scene_Both; SetAdjustMessage("Grid Overlay"); } else if(SceneMode == Scene_Both) { SceneMode = Scene_World; SetAdjustMessage("Grid Off"); } } break; // Holding down Shift key accelerates adjustment velocity. case Key_Shift: ShiftDown = down; break; // Reset the camera position in case we get stuck case Key_T: ThePlayer.EyePos = Vector3f(10.0f, 1.6f, 10.0f); break; case Key_F5: if (!down) { UPInt numNodes = MainScene.Models.GetSize(); for(UPInt i = 0; i < numNodes; i++) { Ptr nodePtr = MainScene.Models[i]; Render::Model* pNode = nodePtr.GetPtr(); if(pNode->IsCollisionModel) { pNode->Visible = !pNode->Visible; } } } break; case Key_N: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustMotionPrediction : NULL; AdjustDirection = -1; break; case Key_M: pAdjustFunc = down ? &OculusWorldDemoApp::AdjustMotionPrediction : NULL; AdjustDirection = 1; break; /* case Key_N: RaiseLOD(); break; case Key_M: DropLOD(); break; */ // Start calibrating magnetometer case Key_Z: if (down) { ManualMagCalStage = 0; if (MagCal.IsManuallyCalibrating()) MagAwaitingForwardLook = false; else { MagCal.BeginManualCalibration(SFusion); MagAwaitingForwardLook = true; } } break; case Key_X: if (down) { MagCal.BeginAutoCalibration(SFusion); SetAdjustMessage("Starting Auto Mag Calibration"); } break; // Show view of yaw angles (for mag calibration/analysis) case Key_F6: if (down) { if (SceneMode != Scene_YawView) { SceneMode = Scene_YawView; SetAdjustMessage("Magnetometer Yaw Angle Marks"); } else { SceneMode = Scene_World; SetAdjustMessage("Magnetometer Marks Off"); } } break; case Key_C: if (down) { // Toggle chromatic aberration correction on/off. RenderDevice::PostProcessShader shader = pRender->GetPostProcessShader(); if (shader == RenderDevice::PostProcessShader_Distortion) { pRender->SetPostProcessShader(RenderDevice::PostProcessShader_DistortionAndChromAb); SetAdjustMessage("Chromatic Aberration Correction On"); } else if (shader == RenderDevice::PostProcessShader_DistortionAndChromAb) { pRender->SetPostProcessShader(RenderDevice::PostProcessShader_Distortion); SetAdjustMessage("Chromatic Aberration Correction Off"); } else OVR_ASSERT(false); } break; case Key_P: if (down) { // Toggle motion prediction. if (SFusion.IsPredictionEnabled()) { SFusion.SetPredictionEnabled(false); SetAdjustMessage("Motion Prediction Off"); } else { SFusion.SetPredictionEnabled(true); SetAdjustMessage("Motion Prediction On"); } } break; default: break; } } void OculusWorldDemoApp::OnIdle() { double curtime = pPlatform->GetAppTime(); float dt = float(curtime - LastUpdate); LastUpdate = curtime; // Update gamepad. GamepadState gamepadState; if (GetPlatformCore()->GetGamepadManager()->GetGamepadState(0, &gamepadState)) { GamepadStateChanged(gamepadState); } if (LoadingState == LoadingState_DoLoad) { PopulateScene(MainFilePath.ToCStr()); LoadingState = LoadingState_Finished; return; } // Check if any new devices were connected. { bool queueIsEmpty = false; while (!queueIsEmpty) { DeviceStatusNotificationDesc desc; { Lock::Locker lock(pManager->GetHandlerLock()); if (DeviceStatusNotificationsQueue.GetSize() == 0) break; desc = DeviceStatusNotificationsQueue.Front(); // We can't call Clear under the lock since this may introduce a dead lock: // this thread is locked by HandlerLock and the Clear might cause // call of Device->Release, which will use Manager->DeviceLock. The bkg // thread is most likely locked by opposite way: // Manager->DeviceLock ==> HandlerLock, therefore - a dead lock. // So, just grab the first element, save a copy of it and remove // the element (Device->Release won't be called since we made a copy). DeviceStatusNotificationsQueue.RemoveAt(0); queueIsEmpty = (DeviceStatusNotificationsQueue.GetSize() == 0); } bool wasAlreadyCreated = desc.Handle.IsCreated(); if (desc.Action == Message_DeviceAdded) { switch(desc.Handle.GetType()) { case Device_Sensor: if (desc.Handle.IsAvailable() && !desc.Handle.IsCreated()) { if (!pSensor) { pSensor = *desc.Handle.CreateDeviceTyped(); SFusion.AttachToSensor(pSensor); SetAdjustMessage("---------------------------\n" "SENSOR connected\n" "---------------------------"); } else if (!wasAlreadyCreated) { LogText("A new SENSOR has been detected, but it is not currently used."); } } break; case Device_LatencyTester: if (desc.Handle.IsAvailable() && !desc.Handle.IsCreated()) { if (!pLatencyTester) { pLatencyTester = *desc.Handle.CreateDeviceTyped(); LatencyUtil.SetDevice(pLatencyTester); if (!wasAlreadyCreated) SetAdjustMessage("----------------------------------------\n" "LATENCY TESTER connected\n" "----------------------------------------"); } } break; case Device_HMD: { OVR::HMDInfo info; desc.Handle.GetDeviceInfo(&info); // if strlen(info.DisplayDeviceName) == 0 then // this HMD is 'fake' (created using sensor). if (strlen(info.DisplayDeviceName) > 0 && (!pHMD || !info.IsSameDisplay(TheHMDInfo))) { SetAdjustMessage("------------------------\n" "HMD connected\n" "------------------------"); if (!pHMD || !desc.Handle.IsDevice(pHMD)) pHMD = *desc.Handle.CreateDeviceTyped(); // update stereo config with new HMDInfo if (pHMD && pHMD->GetDeviceInfo(&TheHMDInfo)) { //RenderParams.MonitorName = hmd.DisplayDeviceName; SConfig.SetHMDInfo(TheHMDInfo); } LogText("HMD device added.\n"); } break; } default:; } } else if (desc.Action == Message_DeviceRemoved) { if (desc.Handle.IsDevice(pSensor)) { LogText("Sensor reported device removed.\n"); SFusion.AttachToSensor(NULL); pSensor.Clear(); SetAdjustMessage("-------------------------------\n" "SENSOR disconnected.\n" "-------------------------------"); } else if (desc.Handle.IsDevice(pLatencyTester)) { LogText("Latency Tester reported device removed.\n"); LatencyUtil.SetDevice(NULL); pLatencyTester.Clear(); SetAdjustMessage("---------------------------------------------\n" "LATENCY SENSOR disconnected.\n" "---------------------------------------------"); } else if (desc.Handle.IsDevice(pHMD)) { if (pHMD && !pHMD->IsDisconnected()) { SetAdjustMessage("---------------------------\n" "HMD disconnected\n" "---------------------------"); // Disconnect HMD. pSensor is used to restore 'fake' HMD device // (can be NULL). pHMD = pHMD->Disconnect(pSensor); // This will initialize TheHMDInfo with information about configured IPD, // screen size and other variables needed for correct projection. // We pass HMD DisplayDeviceName into the renderer to select the // correct monitor in full-screen mode. if (pHMD && pHMD->GetDeviceInfo(&TheHMDInfo)) { //RenderParams.MonitorName = hmd.DisplayDeviceName; SConfig.SetHMDInfo(TheHMDInfo); } LogText("HMD device removed.\n"); } } } else OVR_ASSERT(0); // unexpected action } } // If one of Stereo setting adjustment keys is pressed, adjust related state. if (pAdjustFunc) { (this->*pAdjustFunc)(dt * AdjustDirection * (ShiftDown ? 5.0f : 1.0f)); } // Process latency tester results. const char* results = LatencyUtil.GetResultsString(); if (results != NULL) { LogText("LATENCY TESTER: %s\n", results); } // Have to place this as close as possible to where the HMD orientation is read. LatencyUtil.ProcessInputs(); // Magnetometer calibration procedure if (MagCal.IsManuallyCalibrating()) UpdateManualMagCalibration(); if (MagCal.IsAutoCalibrating()) { MagCal.UpdateAutoCalibration(SFusion); int n = MagCal.NumberOfSamples(); if (n == 1) SetAdjustMessage(" Magnetometer Calibration Has 1 Sample \n %d Remaining - Please Keep Looking Around ",4-n); else if (n < 4) SetAdjustMessage(" Magnetometer Calibration Has %d Samples \n %d Remaining - Please Keep Looking Around ",n,4-n); if (MagCal.IsCalibrated()) { SFusion.SetYawCorrectionEnabled(true); Vector3f mc = MagCal.GetMagCenter(); SetAdjustMessage(" Magnetometer Calibration Complete \nCenter: %f %f %f",mc.x,mc.y,mc.z); } } // Handle Sensor motion. // We extract Yaw, Pitch, Roll instead of directly using the orientation // to allow "additional" yaw manipulation with mouse/controller. if(pSensor) { Quatf hmdOrient = SFusion.GetPredictedOrientation(); float yaw = 0.0f; hmdOrient.GetEulerAngles(&yaw, &ThePlayer.EyePitch, &ThePlayer.EyeRoll); ThePlayer.EyeYaw += (yaw - ThePlayer.LastSensorYaw); ThePlayer.LastSensorYaw = yaw; // NOTE: We can get a matrix from orientation as follows: // Matrix4f hmdMat(hmdOrient); // Test logic - assign quaternion result directly to view: // Quatf hmdOrient = SFusion.GetOrientation(); // View = Matrix4f(hmdOrient.Inverted()) * Matrix4f::Translation(-EyePos); } if(curtime >= NextFPSUpdate) { NextFPSUpdate = curtime + 1.0; FPS = FrameCounter; FrameCounter = 0; } FrameCounter++; if(FPS < 40) { ConsecutiveLowFPSFrames++; } else { ConsecutiveLowFPSFrames = 0; } if(ConsecutiveLowFPSFrames > 200) { DropLOD(); ConsecutiveLowFPSFrames = 0; } ThePlayer.EyeYaw -= ThePlayer.GamepadRotate.x * dt; ThePlayer.HandleCollision(dt, &CollisionModels, &GroundCollisionModels, ShiftDown); if(!pSensor) { ThePlayer.EyePitch -= ThePlayer.GamepadRotate.y * dt; const float maxPitch = ((3.1415f / 2) * 0.98f); if(ThePlayer.EyePitch > maxPitch) { ThePlayer.EyePitch = maxPitch; } if(ThePlayer.EyePitch < -maxPitch) { ThePlayer.EyePitch = -maxPitch; } } // Rotate and position View Camera, using YawPitchRoll in BodyFrame coordinates. // Matrix4f rollPitchYaw = Matrix4f::RotationY(ThePlayer.EyeYaw) * Matrix4f::RotationX(ThePlayer.EyePitch) * Matrix4f::RotationZ(ThePlayer.EyeRoll); Vector3f up = rollPitchYaw.Transform(UpVector); Vector3f forward = rollPitchYaw.Transform(ForwardVector); // Minimal head modeling; should be moved as an option to SensorFusion. float headBaseToEyeHeight = 0.15f; // Vertical height of eye from base of head float headBaseToEyeProtrusion = 0.09f; // Distance forward of eye from base of head Vector3f eyeCenterInHeadFrame(0.0f, headBaseToEyeHeight, -headBaseToEyeProtrusion); Vector3f shiftedEyePos = ThePlayer.EyePos + rollPitchYaw.Transform(eyeCenterInHeadFrame); shiftedEyePos.y -= eyeCenterInHeadFrame.y; // Bring the head back down to original height View = Matrix4f::LookAtRH(shiftedEyePos, shiftedEyePos + forward, up); // Transformation without head modeling. // View = Matrix4f::LookAtRH(EyePos, EyePos + forward, up); // This is an alternative to LookAtRH: // Here we transpose the rotation matrix to get its inverse. // View = (Matrix4f::RotationY(EyeYaw) * Matrix4f::RotationX(EyePitch) * // Matrix4f::RotationZ(EyeRoll)).Transposed() * // Matrix4f::Translation(-EyePos); switch(SConfig.GetStereoMode()) { case Stereo_None: Render(SConfig.GetEyeRenderParams(StereoEye_Center)); break; case Stereo_LeftRight_Multipass: //case Stereo_LeftDouble_Multipass: Render(SConfig.GetEyeRenderParams(StereoEye_Left)); Render(SConfig.GetEyeRenderParams(StereoEye_Right)); break; } pRender->Present(); // Force GPU to flush the scene, resulting in the lowest possible latency. pRender->ForceFlushGPU(); } void OculusWorldDemoApp::UpdateManualMagCalibration() { float tyaw, pitch, roll; Anglef yaw; Quatf hmdOrient = SFusion.GetOrientation(); hmdOrient.GetEulerAngles(&tyaw, &pitch, &roll); Vector3f mag = SFusion.GetMagnetometer(); float dtr = Math::DegreeToRadFactor; yaw.Set(tyaw); // Using Angle class to handle angle wraparound arithmetic const int timeout = 100; switch(ManualMagCalStage) { case 0: if (MagAwaitingForwardLook) SetAdjustMessage("Magnetometer Calibration\n** Step 1: Please Look Forward **\n** and Press Z When Ready **"); else if (fabs(pitch) < 10.0f*dtr) { MagCal.InsertIfAcceptable(hmdOrient, mag); FirstMagYaw = yaw; MagAwaitingForwardLook = false; if (MagCal.NumberOfSamples() == 1) { ManualMagCalStage = 1; ManualMagFailures = 0; } } else MagAwaitingForwardLook = true; break; case 1: SetAdjustMessage("Magnetometer Calibration\n** Step 2: Please Look Up **"); yaw -= FirstMagYaw; if ((pitch > 50.0f*dtr) && (yaw.Abs() < 20.0f*dtr)) { MagCal.InsertIfAcceptable(hmdOrient, mag); ManualMagFailures++; if ((MagCal.NumberOfSamples() == 2)||(ManualMagFailures > timeout)) { ManualMagCalStage = 2; ManualMagFailures = 0; } } break; case 2: SetAdjustMessage("Magnetometer Calibration\n** Step 3: Please Look Left **"); yaw -= FirstMagYaw; if (yaw.Get() > 60.0f*dtr) { MagCal.InsertIfAcceptable(hmdOrient, mag); ManualMagFailures++; if ((MagCal.NumberOfSamples() == 3)||(ManualMagFailures > timeout)) { ManualMagCalStage = 3; ManualMagFailures = 0; } } break; case 3: SetAdjustMessage("Magnetometer Calibration\n** Step 4: Please Look Right **"); yaw -= FirstMagYaw; if (yaw.Get() < -60.0f*dtr) { MagCal.InsertIfAcceptable(hmdOrient, mag); ManualMagFailures++; if (MagCal.NumberOfSamples() == 4) ManualMagCalStage = 6; else { if (ManualMagFailures > timeout) { ManualMagCalStage = 4; ManualMagFailures = 0; } } } break; case 4: SetAdjustMessage("Magnetometer Calibration\n** Step 5: Please Look Upper Right **"); yaw -= FirstMagYaw; if ((yaw.Get() < -50.0f*dtr) && (pitch > 40.0f*dtr)) { MagCal.InsertIfAcceptable(hmdOrient, mag); if (MagCal.NumberOfSamples() == 4) ManualMagCalStage = 6; else { if (ManualMagFailures > timeout) { ManualMagCalStage = 5; ManualMagFailures = 0; } else ManualMagFailures++; } } break; case 5: SetAdjustMessage("Calibration Failed\n** Try Again From Another Location **"); MagCal.AbortCalibration(); break; case 6: if (!MagCal.IsCalibrated()) { MagCal.SetCalibration(SFusion); SFusion.SetYawCorrectionEnabled(true); Vector3f mc = MagCal.GetMagCenter(); SetAdjustMessage(" Magnetometer Calibration and Activation \nCenter: %f %f %f", mc.x,mc.y,mc.z); } } } static const char* HelpText = "F1 \t100 NoStereo \t420 Z \t520 Manual Mag Calib\n" "F2 \t100 Stereo \t420 X \t520 Auto Mag Calib\n" "F3 \t100 StereoHMD \t420 ; \t520 Mag Set Ref Point\n" "F4 \t100 MSAA \t420 F6 \t520 Mag Info\n" "F9 \t100 FullScreen \t420 R \t520 Reset SensorFusion\n" "F11 \t100 Fast FullScreen \t500 - + \t660 Adj EyeHeight\n" "C \t100 Chromatic Ab \t500 [ ] \t660 Adj FOV\n" "P \t100 Motion Pred \t500 Shift \t660 Adj Faster\n" "N/M \t180 Adj Motion Pred\n" "( / ) \t180 Adj EyeDistance" ; enum DrawTextCenterType { DrawText_NoCenter= 0, DrawText_VCenter = 0x1, DrawText_HCenter = 0x2, DrawText_Center = DrawText_VCenter | DrawText_HCenter }; static void DrawTextBox(RenderDevice* prender, float x, float y, float textSize, const char* text, DrawTextCenterType centerType = DrawText_NoCenter) { float ssize[2] = {0.0f, 0.0f}; prender->MeasureText(&DejaVu, text, textSize, ssize); // Treat 0 a VCenter. if (centerType & DrawText_HCenter) { x = -ssize[0]/2; } if (centerType & DrawText_VCenter) { y = -ssize[1]/2; } prender->FillRect(x-0.02f, y-0.02f, x+ssize[0]+0.02f, y+ssize[1]+0.02f, Color(40,40,100,210)); prender->RenderText(&DejaVu, text, x, y, textSize, Color(255,255,0,210)); } void OculusWorldDemoApp::Render(const StereoEyeParams& stereo) { pRender->BeginScene(PostProcess); // *** 3D - Configures Viewport/Projection and Render pRender->ApplyStereoParams(stereo); pRender->Clear(); pRender->SetDepthMode(true, true); if (SceneMode != Scene_Grid) { MainScene.Render(pRender, stereo.ViewAdjust * View); } if (SceneMode == Scene_YawView) { Matrix4f calView = Matrix4f(); float viewYaw = -ThePlayer.LastSensorYaw + SFusion.GetMagRefYaw(); calView.M[0][0] = calView.M[2][2] = cos(viewYaw); calView.M[0][2] = sin(viewYaw); calView.M[2][0] = -sin(viewYaw); //LogText("yaw: %f\n",SFusion.GetMagRefYaw()); if (SFusion.IsYawCorrectionInProgress()) YawMarkGreenScene.Render(pRender, stereo.ViewAdjust); else YawMarkRedScene.Render(pRender, stereo.ViewAdjust); if (fabs(ThePlayer.EyePitch) < Math::Pi * 0.33) YawLinesScene.Render(pRender, stereo.ViewAdjust * calView); } // *** 2D Text & Grid - Configure Orthographic rendering. // Render UI in 2D orthographic coordinate system that maps [-1,1] range // to a readable FOV area centered at your eye and properly adjusted. pRender->ApplyStereoParams2D(stereo); pRender->SetDepthMode(false, false); float unitPixel = SConfig.Get2DUnitPixel(); float textHeight= unitPixel * 22; if ((SceneMode == Scene_Grid)||(SceneMode == Scene_Both)) { // Draw grid two pixels thick. GridScene.Render(pRender, Matrix4f()); GridScene.Render(pRender, Matrix4f::Translation(unitPixel,unitPixel,0)); } // Display Loading screen-shot in frame 0. if (LoadingState != LoadingState_Finished) { LoadingScene.Render(pRender, Matrix4f()); String loadMessage = String("Loading ") + MainFilePath; DrawTextBox(pRender, 0.0f, 0.25f, textHeight, loadMessage.ToCStr(), DrawText_HCenter); LoadingState = LoadingState_DoLoad; } if(!AdjustMessage.IsEmpty() && AdjustMessageTimeout > pPlatform->GetAppTime()) { DrawTextBox(pRender,0.0f,0.4f, textHeight, AdjustMessage.ToCStr(), DrawText_HCenter); } switch(TextScreen) { case Text_Orientation: { char buf[256], gpustat[256]; OVR_sprintf(buf, sizeof(buf), " Yaw:%4.0f Pitch:%4.0f Roll:%4.0f \n" " FPS: %d Frame: %d \n Pos: %3.2f, %3.2f, %3.2f \n" " EyeHeight: %3.2f", RadToDegree(ThePlayer.EyeYaw), RadToDegree(ThePlayer.EyePitch), RadToDegree(ThePlayer.EyeRoll), FPS, FrameCounter, ThePlayer.EyePos.x, ThePlayer.EyePos.y, ThePlayer.EyePos.z, ThePlayer.EyePos.y); size_t texMemInMB = pRender->GetTotalTextureMemoryUsage() / 1058576; if (texMemInMB) { OVR_sprintf(gpustat, sizeof(gpustat), "\n GPU Tex: %u MB", texMemInMB); OVR_strcat(buf, sizeof(buf), gpustat); } DrawTextBox(pRender, 0.0f, -0.15f, textHeight, buf, DrawText_HCenter); } break; case Text_Config: { char textBuff[2048]; OVR_sprintf(textBuff, sizeof(textBuff), "Fov\t300 %9.4f\n" "EyeDistance\t300 %9.4f\n" "DistortionK0\t300 %9.4f\n" "DistortionK1\t300 %9.4f\n" "DistortionK2\t300 %9.4f\n" "DistortionK3\t300 %9.4f\n" "TexScale\t300 %9.4f", SConfig.GetYFOVDegrees(), SConfig.GetIPD(), SConfig.GetDistortionK(0), SConfig.GetDistortionK(1), SConfig.GetDistortionK(2), SConfig.GetDistortionK(3), SConfig.GetDistortionScale()); DrawTextBox(pRender, 0.0f, 0.0f, textHeight, textBuff, DrawText_Center); } break; case Text_Help: DrawTextBox(pRender, 0.0f, -0.1f, textHeight, HelpText, DrawText_Center); default: break; } // Display colored quad if we're doing a latency test. Color colorToDisplay; if (LatencyUtil.DisplayScreenColor(colorToDisplay)) { pRender->FillRect(-0.4f, -0.4f, 0.4f, 0.4f, colorToDisplay); } pRender->FinishScene(); } // Sets temporarily displayed message for adjustments void OculusWorldDemoApp::SetAdjustMessage(const char* format, ...) { Lock::Locker lock(pManager->GetHandlerLock()); char textBuff[2048]; va_list argList; va_start(argList, format); OVR_vsprintf(textBuff, sizeof(textBuff), format, argList); va_end(argList); // Message will time out in 4 seconds. AdjustMessage = textBuff; AdjustMessageTimeout = pPlatform->GetAppTime() + 4.0f; } void OculusWorldDemoApp::SetAdjustMessageTimeout(float timeout) { AdjustMessageTimeout = pPlatform->GetAppTime() + timeout; } // ***** View Control Adjustments void OculusWorldDemoApp::AdjustFov(float dt) { float esd = SConfig.GetEyeToScreenDistance() + 0.01f * dt; SConfig.SetEyeToScreenDistance(esd); SetAdjustMessage("ESD:%6.3f FOV: %6.3f", esd, SConfig.GetYFOVDegrees()); } void OculusWorldDemoApp::AdjustAspect(float dt) { float rawAspect = SConfig.GetAspect() / SConfig.GetAspectMultiplier(); float newAspect = SConfig.GetAspect() + 0.01f * dt; SConfig.SetAspectMultiplier(newAspect / rawAspect); SetAdjustMessage("Aspect: %6.3f", newAspect); } void OculusWorldDemoApp::AdjustDistortion(float dt, int kIndex, const char* label) { SConfig.SetDistortionK(kIndex, SConfig.GetDistortionK(kIndex) + 0.03f * dt); SetAdjustMessage("%s: %6.4f", label, SConfig.GetDistortionK(kIndex)); } void OculusWorldDemoApp::AdjustIPD(float dt) { SConfig.SetIPD(SConfig.GetIPD() + 0.025f * dt); SetAdjustMessage("EyeDistance: %6.4f", SConfig.GetIPD()); } void OculusWorldDemoApp::AdjustEyeHeight(float dt) { float dist = 0.5f * dt; ThePlayer.EyeHeight += dist; ThePlayer.EyePos.y += dist; SetAdjustMessage("EyeHeight: %4.2f", ThePlayer.EyeHeight); } void OculusWorldDemoApp::AdjustMotionPrediction(float dt) { float motionPred = SFusion.GetPredictionDelta() + 0.01f * dt; if (motionPred < 0.0f) { motionPred = 0.0f; } SFusion.SetPrediction(motionPred); SetAdjustMessage("MotionPrediction: %6.3fs", motionPred); } // Loads the scene data void OculusWorldDemoApp::PopulateScene(const char *fileName) { XmlHandler xmlHandler; if(!xmlHandler.ReadFile(fileName, pRender, &MainScene, &CollisionModels, &GroundCollisionModels)) { SetAdjustMessage("---------------------------------\nFILE LOAD FAILED\n---------------------------------"); SetAdjustMessageTimeout(10.0f); } MainScene.SetAmbient(Vector4f(1.0f, 1.0f, 1.0f, 1.0f)); // Distortion debug grid (brought up by 'G' key). Ptr gridModel = *Model::CreateGrid(Vector3f(0,0,0), Vector3f(1.0f/10, 0,0), Vector3f(0,1.0f/10,0), 10, 10, 5, Color(0, 255, 0, 255), Color(255, 50, 50, 255) ); GridScene.World.Add(gridModel); // Yaw angle marker and lines (brought up by ';' key). float shifty = -0.5f; Ptr yawMarkGreenModel = *Model::CreateBox(Color(0, 255, 0, 255), Vector3f(0.0f, shifty, -2.0f), Vector3f(0.05f, 0.05f, 0.05f)); YawMarkGreenScene.World.Add(yawMarkGreenModel); Ptr yawMarkRedModel = *Model::CreateBox(Color(255, 0, 0, 255), Vector3f(0.0f, shifty, -2.0f), Vector3f(0.05f, 0.05f, 0.05f)); YawMarkRedScene.World.Add(yawMarkRedModel); Ptr yawLinesModel = *new Model(Prim_Lines); float r = 2.0f; float theta0 = Math::PiOver2; float theta1 = 0.0f; Color c = Color(255, 200, 200, 255); for (int i = 0; i < 35; i++) { theta1 = theta0 + Math::Pi / 18.0f; yawLinesModel->AddLine(yawLinesModel->AddVertex(Vector3f(r*cos(theta0),shifty,-r*sin(theta0)),c), yawLinesModel->AddVertex(Vector3f(r*cos(theta1),shifty,-r*sin(theta1)),c)); theta0 = theta1; yawLinesModel->AddLine(yawLinesModel->AddVertex(Vector3f(r*cos(theta0),shifty,-r*sin(theta0)),c), yawLinesModel->AddVertex(Vector3f(r*cos(theta0),shifty+0.1f,-r*sin(theta0)),c)); theta0 = theta1; } theta1 = theta0 + Math::Pi / 18.0f; yawLinesModel->AddLine(yawLinesModel->AddVertex(Vector3f(r*cos(theta0),shifty,-r*sin(theta0)),c), yawLinesModel->AddVertex(Vector3f(r*cos(theta1),shifty,-r*sin(theta1)),c)); yawLinesModel->AddLine(yawLinesModel->AddVertex(Vector3f(0.0f,shifty+0.1f,-r),c), yawLinesModel->AddVertex(Vector3f(r*sin(0.02f),shifty,-r*cos(0.02f)),c)); yawLinesModel->AddLine(yawLinesModel->AddVertex(Vector3f(0.0f,shifty+0.1f,-r),c), yawLinesModel->AddVertex(Vector3f(r*sin(-0.02f),shifty,-r*cos(-0.02f)),c)); yawLinesModel->SetPosition(Vector3f(0.0f,0.0f,0.0f)); YawLinesScene.World.Add(yawLinesModel); } void OculusWorldDemoApp::PopulatePreloadScene() { // Load-screen screen shot image String fileName = MainFilePath; fileName.StripExtension(); Ptr imageFile = *new SysFile(fileName + "_LoadScreen.tga"); Ptr imageTex; if (imageFile->IsValid()) imageTex = *LoadTextureTga(pRender, imageFile); // Image is rendered as a single quad. if (imageTex) { imageTex->SetSampleMode(Sample_Anisotropic|Sample_Repeat); Ptr m = *new Model(Prim_Triangles); m->AddVertex(-0.5f, 0.5f, 0.0f, Color(255,255,255,255), 0.0f, 0.0f); m->AddVertex( 0.5f, 0.5f, 0.0f, Color(255,255,255,255), 1.0f, 0.0f); m->AddVertex( 0.5f, -0.5f, 0.0f, Color(255,255,255,255), 1.0f, 1.0f); m->AddVertex(-0.5f, -0.5f, 0.0f, Color(255,255,255,255), 0.0f, 1.0f); m->AddTriangle(2,1,0); m->AddTriangle(0,3,2); Ptr fill = *new ShaderFill(*pRender->CreateShaderSet()); fill->GetShaders()->SetShader(pRender->LoadBuiltinShader(Shader_Vertex, VShader_MVP)); fill->GetShaders()->SetShader(pRender->LoadBuiltinShader(Shader_Fragment, FShader_Texture)); fill->SetTexture(0, imageTex); m->Fill = fill; LoadingScene.World.Add(m); } } void OculusWorldDemoApp::ClearScene() { MainScene.Clear(); GridScene.Clear(); YawMarkGreenScene.Clear(); YawMarkRedScene.Clear(); YawLinesScene.Clear(); } void OculusWorldDemoApp::PopulateLODFileNames() { //OVR::String mainFilePath = MainFilePath; LODFilePaths.PushBack(MainFilePath); int LODIndex = 1; SPInt pos = strcspn(MainFilePath.ToCStr(), "."); SPInt len = strlen(MainFilePath.ToCStr()); SPInt diff = len - pos; if (diff == 0) return; while(true) { char pathWithoutExt[250]; char buffer[250]; for(SPInt i = 0; i < pos; ++i) { pathWithoutExt[i] = MainFilePath[(int)i]; } pathWithoutExt[pos] = '\0'; OVR_sprintf(buffer, sizeof(buffer), "%s%i.xml", pathWithoutExt, LODIndex); FILE* fp = 0; #if defined(_MSC_VER) && (_MSC_VER >= 1400 ) errno_t err = fopen_s(&fp, buffer, "rb"); if(!fp || err) { #else fp = fopen(buffer, "rb"); if(!fp) { #endif break; } fclose(fp); OVR::String result = buffer; LODFilePaths.PushBack(result); LODIndex++; } } void OculusWorldDemoApp::DropLOD() { if(CurrentLODFileIndex < (int)(LODFilePaths.GetSize() - 1)) { ClearScene(); CurrentLODFileIndex++; PopulateScene(LODFilePaths[CurrentLODFileIndex].ToCStr()); } } void OculusWorldDemoApp::RaiseLOD() { if(CurrentLODFileIndex > 0) { ClearScene(); CurrentLODFileIndex--; PopulateScene(LODFilePaths[CurrentLODFileIndex].ToCStr()); } } //----------------------------------------------------------------------------- void OculusWorldDemoApp::CycleDisplay() { int screenCount = pPlatform->GetDisplayCount(); // If Windowed, switch to the HMD screen first in Full-Screen Mode. // If already Full-Screen, cycle to next screen until we reach FirstScreenInCycle. if (pRender->IsFullscreen()) { // Right now, we always need to restore window before going to next screen. pPlatform->SetFullscreen(RenderParams, Display_Window); Screen++; if (Screen == screenCount) Screen = 0; RenderParams.Display = pPlatform->GetDisplay(Screen); if (Screen != FirstScreenInCycle) { pRender->SetParams(RenderParams); pPlatform->SetFullscreen(RenderParams, Display_Fullscreen); } } else { // Try to find HMD Screen, making it the first screen in full-screen Cycle. FirstScreenInCycle = 0; if (pHMD) { DisplayId HMD (SConfig.GetHMDInfo().DisplayDeviceName, SConfig.GetHMDInfo().DisplayId); for (int i = 0; i< screenCount; i++) { if (pPlatform->GetDisplay(i) == HMD) { FirstScreenInCycle = i; break; } } } // Switch full-screen on the HMD. Screen = FirstScreenInCycle; RenderParams.Display = pPlatform->GetDisplay(Screen); pRender->SetParams(RenderParams); pPlatform->SetFullscreen(RenderParams, Display_Fullscreen); } } void OculusWorldDemoApp::GamepadStateChanged(const GamepadState& pad) { ThePlayer.GamepadMove = Vector3f(pad.LX * pad.LX * (pad.LX > 0 ? 1 : -1), 0, pad.LY * pad.LY * (pad.LY > 0 ? -1 : 1)); ThePlayer.GamepadRotate = Vector3f(2 * pad.RX, -2 * pad.RY, 0); } //------------------------------------------------------------------------------------- OVR_PLATFORM_APP(OculusWorldDemoApp);