overte/plugins/oculus/src/OculusHelpers.cpp
2019-01-25 11:44:14 -08:00

298 lines
10 KiB
C++

//
// Created by Bradley Austin Davis on 2015/08/08
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "OculusHelpers.h"
#include <atomic>
#include <Windows.h>
#include <QtCore/QFile>
#include <QtCore/QDir>
#include <QtCore/QProcessEnvironment>
#define OVRPL_DISABLED
#include <OVR_Platform.h>
#include <controllers/Input.h>
#include <controllers/Pose.h>
#include <shared/GlobalAppProperties.h>
#include <NumericalConstants.h>
Q_LOGGING_CATEGORY(displayplugins, "hifi.plugins.display")
Q_LOGGING_CATEGORY(oculusLog, "hifi.plugins.display.oculus")
using namespace hifi;
static wchar_t* REQUIRED_OCULUS_DLL = L"LibOVRRT64_1.dll";
static wchar_t FOUND_PATH[MAX_PATH];
bool ovr::available() {
static std::once_flag once;
static bool result{ false };
std::call_once(once, [&] {
static const QString DEBUG_FLAG("HIFI_DEBUG_OPENVR");
static bool enableDebugOpenVR = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG);
if (enableDebugOpenVR) {
return;
}
ovrDetectResult detect = ovr_Detect(0);
if (!detect.IsOculusServiceRunning || !detect.IsOculusHMDConnected) {
return;
}
DWORD searchResult = SearchPathW(NULL, REQUIRED_OCULUS_DLL, NULL, MAX_PATH, FOUND_PATH, NULL);
if (searchResult <= 0) {
return;
}
result = true;
});
return result;
}
class ovrImpl {
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
std::mutex mutex;
ovrSession session{ nullptr };
size_t renderCount{ 0 };
private:
void setupSession(bool render) {
if (session) {
return;
}
ovrInitParams initParams{ ovrInit_RequestVersion | ovrInit_FocusAware, OVR_MINOR_VERSION, nullptr, 0, 0 };
if (render) {
initParams.Flags |= ovrInit_MixedRendering;
} else {
initParams.Flags |= ovrInit_Invisible;
}
if (!OVR_SUCCESS(ovr_Initialize(&initParams))) {
qCWarning(oculusLog) << "Failed to initialze Oculus SDK" << ovr::getError();
return;
}
#ifdef OCULUS_APP_ID
static std::once_flag once;
std::call_once(once, []() {
if (qApp->property(hifi::properties::OCULUS_STORE).toBool()) {
if (ovr_PlatformInitializeWindows(OCULUS_APP_ID) != ovrPlatformInitialize_Success) {
qCWarning(oculusLog) << "Unable to initialize the platform for entitlement check - fail the check" << ovr::getError();
return;
} else {
qCDebug(oculusLog) << "Performing Oculus Platform entitlement check";
ovr_Entitlement_GetIsViewerEntitled();
}
}
});
#endif
ovrGraphicsLuid luid;
if (!OVR_SUCCESS(ovr_Create(&session, &luid))) {
qCWarning(oculusLog) << "Failed to acquire Oculus session" << ovr::getError();
return;
} else {
ovrResult setFloorLevelOrigin = ovr_SetTrackingOriginType(session, ovrTrackingOrigin::ovrTrackingOrigin_FloorLevel);
if (!OVR_SUCCESS(setFloorLevelOrigin)) {
qCWarning(oculusLog) << "Failed to set the Oculus tracking origin to floor level" << ovr::getError();
}
}
}
void releaseSession() {
if (!session) {
return;
}
ovr_Destroy(session);
session = nullptr;
ovr_Shutdown();
}
public:
void withSession(const std::function<void(ovrSession)>& f) {
Lock lock(mutex);
if (!session) {
setupSession(false);
}
f(session);
}
ovrSession acquireRenderSession() {
Lock lock(mutex);
if (renderCount++ == 0) {
releaseSession();
setupSession(true);
}
return session;
}
void releaseRenderSession(ovrSession session) {
Lock lock(mutex);
if (--renderCount == 0) {
releaseSession();
}
}
} _ovr;
ovrSession ovr::acquireRenderSession() {
return _ovr.acquireRenderSession();
}
void ovr::releaseRenderSession(ovrSession session) {
_ovr.releaseRenderSession(session);
}
void ovr::withSession(const std::function<void(ovrSession)>& f) {
_ovr.withSession(f);
}
ovrSessionStatus ovr::getStatus() {
ovrResult result;
return ovr::getStatus(result);
}
ovrSessionStatus ovr::getStatus(ovrResult& result) {
ovrSessionStatus status{};
withSession([&](ovrSession session) {
result = ovr_GetSessionStatus(session, &status);
if (!OVR_SUCCESS(result)) {
qCWarning(oculusLog) << "Failed to get session status" << ovr::getError();
}
});
return status;
}
ovrTrackingState ovr::getTrackingState(double absTime, ovrBool latencyMarker) {
ovrTrackingState result{};
withSession([&](ovrSession session) { result = ovr_GetTrackingState(session, absTime, latencyMarker); });
return result;
}
QString ovr::getError() {
static ovrErrorInfo error;
ovr_GetLastErrorInfo(&error);
return QString(error.ErrorString);
}
controller::Pose hifi::ovr::toControllerPose(ovrHandType hand, const ovrPoseStatef& handPose) {
// When the sensor-to-world rotation is identity the coordinate axes look like this:
//
// user
// forward
// -z
// |
// y| user
// y o----x right
// o-----x user
// | up
// |
// z
//
// Rift
// From ABOVE the hand canonical axes looks like this:
//
// | | | | y | | | |
// | | | | | | | | |
// | | | | |
// |left | / x---- + \ |right|
// | _/ z \_ |
// | | | |
// | | | |
//
// So when the user is in Rift space facing the -zAxis with hands outstretched and palms down
// the rotation to align the Touch axes with those of the hands is:
//
// touchToHand = halfTurnAboutY * quaterTurnAboutX
// Due to how the Touch controllers fit into the palm there is an offset that is different for each hand.
// You can think of this offset as the inverse of the measured rotation when the hands are posed, such that
// the combination (measurement * offset) is identity at this orientation.
//
// Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down)
//
// An approximate offset for the Touch can be obtained by inspection:
//
// Qoffset = glm::inverse(glm::angleAxis(sign * PI/2.0f, zAxis) * glm::angleAxis(PI/4.0f, xAxis))
//
// So the full equation is:
//
// Q = combinedMeasurement * touchToHand
//
// Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
//
// Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
static const glm::quat touchToHand = yFlip * quarterX;
static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand;
static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand;
static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
static const glm::vec3 CONTROLLER_OFFSET =
glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f);
static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
auto translationOffset = (hand == ovrHand_Left ? leftTranslationOffset : rightTranslationOffset);
auto rotationOffset = (hand == ovrHand_Left ? leftRotationOffset : rightRotationOffset);
glm::quat rotation = toGlm(handPose.ThePose.Orientation);
controller::Pose pose;
pose.translation = toGlm(handPose.ThePose.Position);
pose.translation += rotation * translationOffset;
pose.rotation = rotation * rotationOffset;
pose.angularVelocity = rotation * toGlm(handPose.AngularVelocity);
pose.velocity = toGlm(handPose.LinearVelocity);
pose.valid = true;
return pose;
}
controller::Pose hifi::ovr::toControllerPose(ovrHandType hand,
const ovrPoseStatef& handPose,
const ovrPoseStatef& lastHandPose) {
static const glm::quat yFlip = glm::angleAxis(PI, Vectors::UNIT_Y);
static const glm::quat quarterX = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_X);
static const glm::quat touchToHand = yFlip * quarterX;
static const glm::quat leftQuarterZ = glm::angleAxis(-PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat rightQuarterZ = glm::angleAxis(PI_OVER_TWO, Vectors::UNIT_Z);
static const glm::quat leftRotationOffset = glm::inverse(leftQuarterZ) * touchToHand;
static const glm::quat rightRotationOffset = glm::inverse(rightQuarterZ) * touchToHand;
static const float CONTROLLER_LENGTH_OFFSET = 0.0762f; // three inches
static const glm::vec3 CONTROLLER_OFFSET =
glm::vec3(CONTROLLER_LENGTH_OFFSET / 2.0f, -CONTROLLER_LENGTH_OFFSET / 2.0f, CONTROLLER_LENGTH_OFFSET * 1.5f);
static const glm::vec3 leftTranslationOffset = glm::vec3(-1.0f, 1.0f, 1.0f) * CONTROLLER_OFFSET;
static const glm::vec3 rightTranslationOffset = CONTROLLER_OFFSET;
auto translationOffset = (hand == ovrHand_Left ? leftTranslationOffset : rightTranslationOffset);
auto rotationOffset = (hand == ovrHand_Left ? leftRotationOffset : rightRotationOffset);
glm::quat rotation = toGlm(handPose.ThePose.Orientation);
controller::Pose pose;
pose.translation = toGlm(lastHandPose.ThePose.Position);
pose.translation += rotation * translationOffset;
pose.rotation = rotation * rotationOffset;
pose.angularVelocity = toGlm(lastHandPose.AngularVelocity);
pose.velocity = toGlm(lastHandPose.LinearVelocity);
pose.valid = true;
return pose;
}