nuclear@0: /************************************************************************************ nuclear@0: nuclear@0: Filename : CAPI_HSWDisplay.cpp nuclear@0: Content : Implements Health and Safety Warning system. nuclear@0: Created : July 3, 2014 nuclear@0: Authors : Paul Pedriana nuclear@0: nuclear@0: Copyright : Copyright 2014 Oculus VR, LLC All Rights reserved. nuclear@0: nuclear@0: Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License"); nuclear@0: you may not use the Oculus VR Rift SDK except in compliance with the License, nuclear@0: which is provided at the time of installation or download, or which nuclear@0: otherwise accompanies this software in either electronic or hard copy form. nuclear@0: nuclear@0: You may obtain a copy of the License at nuclear@0: nuclear@0: http://www.oculusvr.com/licenses/LICENSE-3.2 nuclear@0: nuclear@0: Unless required by applicable law or agreed to in writing, the Oculus VR SDK nuclear@0: distributed under the License is distributed on an "AS IS" BASIS, nuclear@0: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. nuclear@0: See the License for the specific language governing permissions and nuclear@0: limitations under the License. nuclear@0: nuclear@0: ************************************************************************************/ nuclear@0: nuclear@0: #include "CAPI_HSWDisplay.h" nuclear@0: #include "CAPI_HMDState.h" nuclear@0: #include "../Kernel/OVR_Log.h" nuclear@0: #include "../Kernel/OVR_String.h" nuclear@0: #include "Textures/healthAndSafety.tga.h" // TGA file as a C array declaration. nuclear@0: #include nuclear@0: nuclear@0: //------------------------------------------------------------------------------------- nuclear@0: // ***** HSWDISPLAY_DEBUGGING nuclear@0: // nuclear@0: // Defined as 0 or 1. Enables debugging features of this module. nuclear@0: nuclear@0: #if !defined(HSWDISPLAY_DEBUGGING) nuclear@0: #if defined(AUTHOR_PPEDRIANA) nuclear@0: #define HSWDISPLAY_DEBUGGING 1 nuclear@0: #else nuclear@0: #define HSWDISPLAY_DEBUGGING 0 nuclear@0: #endif nuclear@0: #endif nuclear@0: nuclear@0: #if HSWDISPLAY_DEBUGGING nuclear@0: OVR_DISABLE_ALL_MSVC_WARNINGS() nuclear@0: #include nuclear@0: #include nuclear@0: OVR_RESTORE_ALL_MSVC_WARNINGS() nuclear@0: #endif nuclear@0: nuclear@0: OVR_DISABLE_MSVC_WARNING(4996) // "This function or variable may be unsafe..." nuclear@0: nuclear@0: nuclear@0: //------------------------------------------------------------------------------------- nuclear@0: // ***** HSWDISPLAY_DEFAULT_ENABLED nuclear@0: // nuclear@0: // Defined as 0 or 1. 1 is default. If 0 then by default HSWDisplay is disabled. nuclear@0: // Developers can set it to 0 to disable HSW display. nuclear@0: // nuclear@0: #if !defined(HSWDISPLAY_DEFAULT_ENABLED) nuclear@0: #define HSWDISPLAY_DEFAULT_ENABLED 1 nuclear@0: #endif nuclear@0: nuclear@0: nuclear@0: nuclear@0: //------------------------------------------------------------------------------------- nuclear@0: // ***** Experimental C API functions nuclear@0: // nuclear@0: nuclear@0: extern "C" nuclear@0: { nuclear@0: OVR_EXPORT void ovrhmd_EnableHSWDisplaySDKRender(ovrHmd hmd, ovrBool enabled) nuclear@0: { nuclear@0: OVR::CAPI::HMDState* pHMDState = (OVR::CAPI::HMDState*)hmd->Handle; nuclear@0: nuclear@0: if (pHMDState) nuclear@0: { nuclear@0: OVR::CAPI::HSWDisplay* pHSWDisplay = pHMDState->pHSWDisplay; nuclear@0: nuclear@0: if(pHSWDisplay) nuclear@0: pHSWDisplay->EnableRender((enabled == 0) ? false : true); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nuclear@0: nuclear@0: nuclear@0: //------------------------------------------------------------------------------------- nuclear@0: // ***** HSWDisplay implementation nuclear@0: // nuclear@0: nuclear@0: namespace OVR { namespace CAPI { nuclear@0: nuclear@0: nuclear@0: static const time_t HSWDisplayTimeNever = (time_t)0; // Constant which denotes the time of "never", as in the display has never been shown yet. nuclear@0: nuclear@0: #define HSWDISPLAY_POLL_INTERVAL 0.400 // Seconds between polling for whether the display should be shown. nuclear@0: #define OVR_KEY_HSWDISPLAYLASTDISPLAYEDTIME "HASWLastDisplayedTime" nuclear@0: nuclear@0: nuclear@0: #if defined(OVR_BUILD_DEBUG) nuclear@0: #define HSWDISPLAY_FIRST_DISMISSAL_TIME 4 // Earliest time in seconds until the user can dismiss the display. nuclear@0: #define HSWDISPLAY_REGULAR_DISMISSAL_TIME 2 nuclear@0: #else nuclear@0: #define HSWDISPLAY_FIRST_DISMISSAL_TIME 15 nuclear@0: #define HSWDISPLAY_REGULAR_DISMISSAL_TIME 6 nuclear@0: #endif nuclear@0: nuclear@0: nuclear@0: HSWDisplay::HSWDisplay(ovrRenderAPIType renderAPIType, ovrHmd hmd, const HMDRenderState& hmdRenderState) nuclear@0: : Enabled(HSWDISPLAY_DEFAULT_ENABLED ? true : false), nuclear@0: Displayed(false), nuclear@0: SDKRendered(false), nuclear@0: DismissRequested(false), nuclear@0: RenderEnabled(true), nuclear@0: UnloadGraphicsRequested(false), nuclear@0: StartTime(0.0), nuclear@0: DismissibleTime(0.0), nuclear@0: LastPollTime(0.0), nuclear@0: HMD(hmd), nuclear@0: HMDMounted(false), nuclear@0: HMDNewlyMounted(false), nuclear@0: RenderAPIType(renderAPIType), nuclear@0: RenderState(hmdRenderState), nuclear@0: LastProfileName(), nuclear@0: LastHSWTime(0) nuclear@0: { nuclear@0: } nuclear@0: nuclear@0: nuclear@0: HSWDisplay::~HSWDisplay() nuclear@0: { nuclear@0: // To consider: assert that we are already shut down. nuclear@0: HSWDisplay::Shutdown(); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: void HSWDisplay::Enable(bool enable) nuclear@0: { nuclear@0: Enabled = enable; nuclear@0: nuclear@0: if(!enable && Displayed) // If it's visible but should not be... nuclear@0: Dismiss(); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: void HSWDisplay::EnableRender(bool enable) nuclear@0: { nuclear@0: RenderEnabled = enable; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: void HSWDisplay::Display() nuclear@0: { nuclear@0: HSWDISPLAY_LOG(("[HSWDisplay] Display()")); nuclear@0: nuclear@0: DisplayInternal(); nuclear@0: nuclear@0: HMDNewlyMounted = false; nuclear@0: Displayed = true; nuclear@0: SDKRendered = RenderEnabled; nuclear@0: StartTime = ovr_GetTimeInSeconds(); nuclear@0: nuclear@0: const time_t lastDisplayedTime = HSWDisplay::GetCurrentProfileLastHSWTime(); nuclear@0: DismissibleTime = StartTime + ((lastDisplayedTime == HSWDisplayTimeNever) ? HSWDISPLAY_FIRST_DISMISSAL_TIME : HSWDISPLAY_REGULAR_DISMISSAL_TIME); nuclear@0: nuclear@0: SetCurrentProfileLastHSWTime(time(NULL)); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: bool HSWDisplay::IsDisplayViewable() const nuclear@0: { nuclear@0: // This function is called IsDisplayViewable, but currently it refers only to whether the nuclear@0: // HMD is mounted on the user's head. nuclear@0: nuclear@0: return HMDMounted; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: bool HSWDisplay::Dismiss() nuclear@0: { nuclear@0: #if HSWDISPLAY_DEBUGGING && defined(OVR_OS_WIN32) nuclear@0: if(GetKeyState(VK_SCROLL) & 0x0001) // If the scroll lock key is toggled on... nuclear@0: return false; // Make it so that the display doesn't dismiss, so we can debug this. nuclear@0: #endif nuclear@0: nuclear@0: // If dismissal is not requested yet, mark it as such. nuclear@0: bool newlyRequested = false; nuclear@0: nuclear@0: if(!DismissRequested) nuclear@0: { nuclear@0: DismissRequested = true; nuclear@0: newlyRequested = true; nuclear@0: } nuclear@0: nuclear@0: // If displayed and time has elapsed, do the dismissal. nuclear@0: OVR_ASSERT(DismissibleTime <= (ovr_GetTimeInSeconds() + HSWDISPLAY_FIRST_DISMISSAL_TIME)); // Make sure the dismissal time is sane. nuclear@0: if (Displayed && (ovr_GetTimeInSeconds() >= DismissibleTime)) nuclear@0: { nuclear@0: DismissInternal(); nuclear@0: Displayed = false; nuclear@0: DismissRequested = false; nuclear@0: SDKRendered = false; nuclear@0: return true; nuclear@0: } nuclear@0: nuclear@0: if(newlyRequested) nuclear@0: { HSWDISPLAY_LOG(("[HSWDisplay] Dismiss(): Not permitted yet. Queued for timeout in %.1f seconds.", DismissibleTime - ovr_GetTimeInSeconds())); } nuclear@0: nuclear@0: return false; // Cannot dismiss yet. nuclear@0: } nuclear@0: nuclear@0: nuclear@0: bool HSWDisplay::TickState(ovrHSWDisplayState *hswDisplayState, bool graphicsContext) nuclear@0: { nuclear@0: bool newlyDisplayed = false; nuclear@0: const double currentTime = ovr_GetTimeInSeconds(); nuclear@0: nuclear@0: // See if we need to be currently displayed. By design we automatically display but don't automatically dismiss. nuclear@0: if (Displayed) nuclear@0: { nuclear@0: if (DismissRequested) // If dismiss was previously requested, see if it can be executed. nuclear@0: Dismiss(); nuclear@0: nuclear@0: if (Displayed) // If not already dismissed above... nuclear@0: { nuclear@0: // We currently have the debug behavior that we permit dismiss very soon after launch. nuclear@0: #if defined(OVR_BUILD_DEBUG) nuclear@0: if(currentTime >= (StartTime + 2)) nuclear@0: { nuclear@0: DismissibleTime = StartTime; nuclear@0: //Dismiss(); nuclear@0: } nuclear@0: #endif nuclear@0: } nuclear@0: nuclear@0: if (Displayed) // If not already dismissed above... nuclear@0: { nuclear@0: const ovrTrackingState ts = ((OVR::CAPI::HMDState*)HMD->Handle)->PredictedTrackingState(currentTime); nuclear@0: nuclear@0: if (ts.StatusFlags & ovrStatus_OrientationTracked) // If the Accelerometer data is valid... nuclear@0: { nuclear@0: const OVR::Vector3f v(ts.HeadPose.LinearAcceleration.x, ts.HeadPose.LinearAcceleration.y, ts.HeadPose.LinearAcceleration.z); nuclear@0: nuclear@0: const float minTapMagnitude = 350.0f; // Empirically determined by some testing. nuclear@0: nuclear@0: if (v.LengthSq() > minTapMagnitude) nuclear@0: Dismiss(); // This will do nothing if the display is not present. nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: else if (Enabled && (currentTime >= (LastPollTime + HSWDISPLAY_POLL_INTERVAL))) nuclear@0: { nuclear@0: LastPollTime = currentTime; nuclear@0: nuclear@0: // We need to display if any of the following are true: nuclear@0: // - The application is just started in Event Mode while the HMD is mounted (warning display would be viewable) and this app was not spawned from a launcher. nuclear@0: // - The current user has never seen the display yet while the HMD is mounted (warning display would be viewable). nuclear@0: // - The HMD is newly mounted (or the warning display is otherwise newly viewable). nuclear@0: // - The warning display hasn't shown in 24 hours (need to verify this as a requirement). nuclear@0: // Event Mode refers to when the app is being run in a public demo event such as a trade show. nuclear@0: nuclear@0: OVR::CAPI::HMDState* pHMDState = (OVR::CAPI::HMDState*)HMD->Handle; nuclear@0: nuclear@0: if(pHMDState) nuclear@0: { nuclear@0: const time_t lastDisplayedTime = HSWDisplay::GetCurrentProfileLastHSWTime(); nuclear@0: nuclear@0: // We currently unilaterally set HMDMounted to true because we don't yet have the ability to detect this. To do: Implement this when possible. nuclear@0: const bool previouslyMounted = HMDMounted; nuclear@0: HMDMounted = true; nuclear@0: HMDNewlyMounted = (!previouslyMounted && HMDMounted); // We set this back to false in the Display function or if the HMD is unmounted before then. nuclear@0: nuclear@0: if((lastDisplayedTime == HSWDisplayTimeNever) || HMDNewlyMounted) nuclear@0: { nuclear@0: if(IsDisplayViewable()) // If the HMD is mounted and otherwise being viewed by the user... nuclear@0: { nuclear@0: Display(); nuclear@0: newlyDisplayed = true; nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: else if(graphicsContext && UnloadGraphicsRequested) nuclear@0: { nuclear@0: UnloadGraphics(); nuclear@0: UnloadGraphicsRequested = false; nuclear@0: } nuclear@0: nuclear@0: if(hswDisplayState) nuclear@0: GetState(hswDisplayState); nuclear@0: nuclear@0: return newlyDisplayed; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: void HSWDisplay::GetState(ovrHSWDisplayState *hswDisplayState) const nuclear@0: { nuclear@0: // Return the state to the caller. nuclear@0: OVR_ASSERT(hswDisplayState != NULL); nuclear@0: if(hswDisplayState) nuclear@0: { nuclear@0: hswDisplayState->Displayed = Displayed; nuclear@0: hswDisplayState->StartTime = StartTime; nuclear@0: hswDisplayState->DismissibleTime = DismissibleTime; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nuclear@0: void HSWDisplay::Render(ovrEyeType eye, const ovrTexture* eyeTexture) nuclear@0: { nuclear@0: SDKRendered = true; nuclear@0: RenderInternal(eye, eyeTexture); nuclear@0: } nuclear@0: nuclear@0: // Persist the HSW settings on the server, since it needs to be synchronized across all applications. nuclear@0: // Note that the profile manager singleton cannot be used for this task because it overwrites the global nuclear@0: // settings for which the rift config tool is supposed to be authoritative. That also would step on the nuclear@0: // settings generated by other rift apps. The server settings, however, are synchronized for all apps nuclear@0: // and so are appropriate for this task. nuclear@0: static String getHSWTimeKey(const char* userName) nuclear@0: { nuclear@0: String keyName = "server:"; nuclear@0: keyName += OVR_KEY_HSWDISPLAYLASTDISPLAYEDTIME; nuclear@0: keyName += ":"; nuclear@0: if (userName) nuclear@0: { nuclear@0: keyName += userName; nuclear@0: } nuclear@0: return keyName; nuclear@0: } nuclear@0: nuclear@0: // Returns HSWDisplayTimeNever (0) if there is no profile or this is the first time we are seeing this profile. nuclear@0: time_t HSWDisplay::GetCurrentProfileLastHSWTime() const nuclear@0: { nuclear@0: // We store the timeout value in HMDState's pProfile. nuclear@0: HMDState* pHMDState = (HMDState*)HMD->Handle; nuclear@0: nuclear@0: if (pHMDState) nuclear@0: { nuclear@0: const char* profileName = pHMDState->pProfile ? pHMDState->pProfile->GetValue(OVR_KEY_USER) : NULL; nuclear@0: nuclear@0: if (profileName) nuclear@0: { nuclear@0: if (LastProfileName == profileName) nuclear@0: { nuclear@0: return LastHSWTime; nuclear@0: } nuclear@0: nuclear@0: LastProfileName = profileName; nuclear@0: String timeKey = getHSWTimeKey(profileName); nuclear@0: int lastTime = pHMDState->getIntValue(timeKey.ToCStr(), (int)HSWDisplayTimeNever); nuclear@0: nuclear@0: LastHSWTime = lastTime; nuclear@0: return lastTime; nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: return HSWDisplayTimeNever; nuclear@0: } nuclear@0: nuclear@0: void HSWDisplay::SetCurrentProfileLastHSWTime(time_t t) nuclear@0: { nuclear@0: // The timeout value is stored in HMDState's pProfile. nuclear@0: HMDState* pHMDState = (HMDState*)HMD->Handle; nuclear@0: nuclear@0: if (pHMDState) nuclear@0: { nuclear@0: const char* profileName = pHMDState->pProfile ? pHMDState->pProfile->GetValue(OVR_KEY_USER) : NULL; nuclear@0: nuclear@0: if (profileName) nuclear@0: { nuclear@0: LastProfileName = profileName; nuclear@0: LastHSWTime = (int)t; nuclear@0: nuclear@0: String timeKey = getHSWTimeKey(profileName); nuclear@0: pHMDState->setIntValue(timeKey.ToCStr(), (int)t); nuclear@0: } nuclear@0: } nuclear@0: } nuclear@0: nuclear@0: nuclear@0: // Generates an appropriate stereo ortho projection matrix. nuclear@0: void HSWDisplay::GetOrthoProjection(const HMDRenderState& RenderState, Matrix4f OrthoProjection[2]) nuclear@0: { nuclear@0: Matrix4f perspectiveProjection[2]; nuclear@0: perspectiveProjection[0] = ovrMatrix4f_Projection(RenderState.EyeRenderDesc[0].Fov, 0.01f, 10000.f, true); nuclear@0: perspectiveProjection[1] = ovrMatrix4f_Projection(RenderState.EyeRenderDesc[1].Fov, 0.01f, 10000.f, true); nuclear@0: nuclear@0: const float orthoDistance = HSWDISPLAY_DISTANCE; // This is meters from the camera (viewer) that we place the ortho plane. nuclear@0: const Vector2f orthoScale0 = Vector2f(1.f) / Vector2f(RenderState.EyeRenderDesc[0].PixelsPerTanAngleAtCenter); nuclear@0: const Vector2f orthoScale1 = Vector2f(1.f) / Vector2f(RenderState.EyeRenderDesc[1].PixelsPerTanAngleAtCenter); nuclear@0: nuclear@0: OrthoProjection[0] = ovrMatrix4f_OrthoSubProjection(perspectiveProjection[0], orthoScale0, orthoDistance, RenderState.EyeRenderDesc[0].HmdToEyeViewOffset.x); nuclear@0: OrthoProjection[1] = ovrMatrix4f_OrthoSubProjection(perspectiveProjection[1], orthoScale1, orthoDistance, RenderState.EyeRenderDesc[1].HmdToEyeViewOffset.x); nuclear@0: } nuclear@0: nuclear@0: nuclear@0: const uint8_t* HSWDisplay::GetDefaultTexture(size_t& TextureSize) nuclear@0: { nuclear@0: TextureSize = sizeof(healthAndSafety_tga); nuclear@0: return healthAndSafety_tga; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: nuclear@0: }} // namespace OVR::CAPI nuclear@0: nuclear@0: nuclear@0: nuclear@0: nuclear@0: //------------------------------------------------------------------------------------- nuclear@0: // ***** HSWDisplay factory nuclear@0: // nuclear@0: nuclear@0: #if defined (OVR_OS_WIN32) nuclear@0: #define OVR_D3D_VERSION 9 nuclear@0: #include "D3D9/CAPI_D3D9_HSWDisplay.h" nuclear@0: #undef OVR_D3D_VERSION nuclear@0: nuclear@0: #define OVR_D3D_VERSION 10 nuclear@0: #include "D3D1X/CAPI_D3D10_HSWDisplay.h" nuclear@0: #undef OVR_D3D_VERSION nuclear@0: nuclear@0: #define OVR_D3D_VERSION 11 nuclear@0: #include "D3D1X/CAPI_D3D11_HSWDisplay.h" nuclear@0: #undef OVR_D3D_VERSION nuclear@0: #endif nuclear@0: nuclear@0: #include "GL/CAPI_GL_HSWDisplay.h" nuclear@0: nuclear@0: nuclear@0: OVR::CAPI::HSWDisplay* OVR::CAPI::HSWDisplay::Factory(ovrRenderAPIType apiType, ovrHmd hmd, const OVR::CAPI::HMDRenderState& renderState) nuclear@0: { nuclear@0: OVR::CAPI::HSWDisplay* pHSWDisplay = NULL; nuclear@0: nuclear@0: switch (apiType) nuclear@0: { nuclear@0: case ovrRenderAPI_None: nuclear@0: pHSWDisplay = new OVR::CAPI::HSWDisplay(apiType, hmd, renderState); nuclear@0: break; nuclear@0: nuclear@0: case ovrRenderAPI_OpenGL: nuclear@0: pHSWDisplay = new OVR::CAPI::GL::HSWDisplay(apiType, hmd, renderState); nuclear@0: break; nuclear@0: nuclear@0: #if defined(OVR_OS_WIN32) nuclear@0: case ovrRenderAPI_D3D9: nuclear@0: pHSWDisplay = new OVR::CAPI::D3D9::HSWDisplay(apiType, hmd, renderState); nuclear@0: break; nuclear@0: nuclear@0: case ovrRenderAPI_D3D10: nuclear@0: pHSWDisplay = new OVR::CAPI::D3D10::HSWDisplay(apiType, hmd, renderState); nuclear@0: break; nuclear@0: nuclear@0: case ovrRenderAPI_D3D11: nuclear@0: pHSWDisplay = new OVR::CAPI::D3D11::HSWDisplay(apiType, hmd, renderState); nuclear@0: break; nuclear@0: #else nuclear@0: case ovrRenderAPI_D3D9: nuclear@0: case ovrRenderAPI_D3D10: nuclear@0: case ovrRenderAPI_D3D11: // Fall through nuclear@0: #endif nuclear@0: nuclear@0: // Handle unsupported cases. nuclear@0: case ovrRenderAPI_Android_GLES: nuclear@0: case ovrRenderAPI_Count: // This is not actually a type. nuclear@0: default: nuclear@0: break; nuclear@0: } nuclear@0: nuclear@0: return pHSWDisplay; nuclear@0: } nuclear@0: nuclear@0: nuclear@0: nuclear@0: nuclear@0: