blob: 2be8ef54da237681ea1f1d0e6e21ee76c8c67bec [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "device/vr/openvr/open_vr_gamepad_data_fetcher.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "third_party/WebKit/public/platform/WebGamepads.h"
#include "third_party/openvr/openvr/headers/openvr.h"
#include "ui/gfx/transform.h"
#include "ui/gfx/transform_util.h"
namespace device {
using namespace blink;
OpenVRGamepadDataFetcher::OpenVRGamepadDataFetcher() : vr_system_(nullptr) {
base::CommandLine* cmd = base::CommandLine::ForCurrentProcess();
emulate_daydream_ = cmd->HasSwitch("emulate-daydream-webvr");
}
OpenVRGamepadDataFetcher::~OpenVRGamepadDataFetcher() {
if (vr_system_) {
// This is weird and won't stand up to scrutiny, but since this is on a
// separate thread and we need to do a post message to shut it down this
// should always die after the device provider, which means it SHOULD be
// safe to actually do the shutdown here.
vr::VR_Shutdown();
vr_system_ = nullptr;
}
}
void OpenVRGamepadDataFetcher::OnAddedToProvider() {
// Loading the SteamVR Runtime
vr::EVRInitError error = vr::VRInitError_None;
vr_system_ = vr::VR_Init(&error, vr::VRApplication_Scene);
if (error != vr::VRInitError_None) {
vr_system_ = nullptr;
return;
}
}
void SetGamepadButton(WebGamepad& pad,
vr::VRControllerState_t& controller_state,
uint64_t supported_buttons,
vr::EVRButtonId button_id) {
uint64_t button_mask = vr::ButtonMaskFromId(button_id);
if ((supported_buttons & button_mask) != 0) {
bool button_pressed = (controller_state.ulButtonPressed & button_mask) != 0;
bool button_touched = (controller_state.ulButtonTouched & button_mask) != 0;
pad.buttons[pad.buttonsLength].touched = button_touched;
pad.buttons[pad.buttonsLength].pressed = button_pressed;
pad.buttons[pad.buttonsLength].value = button_pressed ? 1.0 : 0.0;
pad.buttonsLength++;
}
}
void OpenVRGamepadDataFetcher::GetGamepadData(bool devices_changed_hint) {
if (!vr_system_)
return;
vr::TrackedDevicePose_t tracked_devices_poses[vr::k_unMaxTrackedDeviceCount];
vr_system_->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseSeated, 0.04f,
tracked_devices_poses,
vr::k_unMaxTrackedDeviceCount);
for (uint32_t i = 0; i < vr::k_unMaxTrackedDeviceCount; ++i) {
if (vr_system_->GetTrackedDeviceClass(i) !=
vr::TrackedDeviceClass_Controller)
continue;
PadState* state = GetPadState(i);
if (!state)
continue; // No available slot.
WebGamepad& pad = state->data;
vr::VRControllerState_t controller_state;
if (vr_system_->GetControllerState(i, &controller_state)) {
pad.timestamp = controller_state.unPacketNum;
pad.connected = true;
pad.pose.notNull = true;
pad.pose.hasOrientation = true;
pad.pose.hasPosition = !emulate_daydream_;
// TODO: vr::Prop_AttachedDeviceId_String
swprintf(pad.id, WebGamepad::idLengthCap, L"OpenVR Gamepad");
// Handedness check
vr::ETrackedControllerRole hand =
vr_system_->GetControllerRoleForTrackedDeviceIndex(i);
switch (hand) {
case vr::TrackedControllerRole_Invalid:
pad.hand = GamepadHandNone; break;
case vr::TrackedControllerRole_LeftHand:
pad.hand = GamepadHandLeft; break;
case vr::TrackedControllerRole_RightHand:
pad.hand = GamepadHandRight; break;
}
uint64_t supported_buttons = vr_system_->GetUint64TrackedDeviceProperty(
i, vr::Prop_SupportedButtons_Uint64);
pad.buttonsLength = 0;
pad.axesLength = 0;
for (int j = 0; j < vr::k_unControllerStateAxisCount; ++j) {
int32_t axis_type = vr_system_->GetInt32TrackedDeviceProperty(
i, static_cast<vr::TrackedDeviceProperty>(vr::Prop_Axis0Type_Int32 +
j));
switch (axis_type) {
case vr::k_eControllerAxis_Joystick:
if (emulate_daydream_)
break;
case vr::k_eControllerAxis_TrackPad:
pad.axes[pad.axesLength++] = controller_state.rAxis[j].x;
pad.axes[pad.axesLength++] = controller_state.rAxis[j].y;
SetGamepadButton(
pad, controller_state, supported_buttons,
static_cast<vr::EVRButtonId>(vr::k_EButton_Axis0 + j));
break;
case vr::k_eControllerAxis_Trigger:
if (emulate_daydream_)
break;
pad.buttons[pad.buttonsLength].value = controller_state.rAxis[j].x;
uint64_t button_mask = vr::ButtonMaskFromId(
static_cast<vr::EVRButtonId>(vr::k_EButton_Axis0 + j));
if ((supported_buttons & button_mask) != 0) {
pad.buttons[pad.buttonsLength].pressed =
(controller_state.ulButtonPressed & button_mask) != 0;
}
pad.buttonsLength++;
break;
}
}
if (!emulate_daydream_) {
SetGamepadButton(pad, controller_state, supported_buttons,
vr::k_EButton_A);
SetGamepadButton(pad, controller_state, supported_buttons,
vr::k_EButton_Grip);
}
if (!emulate_daydream_) {
SetGamepadButton(pad, controller_state, supported_buttons,
vr::k_EButton_ApplicationMenu);
// SetGamepadButton(pad, controller_state, supported_buttons,
// vr::k_EButton_System);
SetGamepadButton(pad, controller_state, supported_buttons,
vr::k_EButton_DPad_Left);
SetGamepadButton(pad, controller_state, supported_buttons,
vr::k_EButton_DPad_Up);
SetGamepadButton(pad, controller_state, supported_buttons,
vr::k_EButton_DPad_Right);
SetGamepadButton(pad, controller_state, supported_buttons,
vr::k_EButton_DPad_Down);
}
}
const vr::TrackedDevicePose_t& pose = tracked_devices_poses[i];
if (pose.bPoseIsValid) {
const vr::HmdMatrix34_t& mat = pose.mDeviceToAbsoluteTracking;
gfx::Transform transform(
mat.m[0][0], mat.m[0][1], mat.m[0][2], mat.m[0][3], mat.m[1][0],
mat.m[1][1], mat.m[1][2], mat.m[1][3], mat.m[2][0], mat.m[2][1],
mat.m[2][2], mat.m[2][3], 0, 0, 0, 1);
gfx::DecomposedTransform decomposed_transform;
gfx::DecomposeTransform(&decomposed_transform, transform);
pad.pose.orientation.notNull = true;
pad.pose.orientation.x = decomposed_transform.quaternion[0];
pad.pose.orientation.y = decomposed_transform.quaternion[1];
pad.pose.orientation.z = decomposed_transform.quaternion[2];
pad.pose.orientation.w = decomposed_transform.quaternion[3];
if (!emulate_daydream_) {
pad.pose.position.notNull = true;
pad.pose.position.x = decomposed_transform.translate[0];
pad.pose.position.y = decomposed_transform.translate[1];
pad.pose.position.z = decomposed_transform.translate[2];
} else {
pad.pose.position.notNull = false;
}
pad.pose.angularVelocity.notNull = true;
pad.pose.angularVelocity.x = pose.vAngularVelocity.v[0];
pad.pose.angularVelocity.y = pose.vAngularVelocity.v[1];
pad.pose.angularVelocity.z = pose.vAngularVelocity.v[2];
pad.pose.linearVelocity.notNull = true;
pad.pose.linearVelocity.x = pose.vVelocity.v[0];
pad.pose.linearVelocity.y = pose.vVelocity.v[1];
pad.pose.linearVelocity.z = pose.vVelocity.v[2];
} else {
pad.pose.orientation.notNull = false;
pad.pose.position.notNull = false;
pad.pose.angularVelocity.notNull = false;
pad.pose.linearVelocity.notNull = false;
}
pad.pose.linearAcceleration.notNull = false;
pad.pose.angularAcceleration.notNull = false;
if (emulate_daydream_)
break;
// TODO: I don't see anywhere to query this from OpenVR.
pad.hapticsLength = 1;
}
}
void OpenVRGamepadDataFetcher::PauseHint(bool paused) {
// TODO
}
void OpenVRGamepadDataFetcher::VibrateHaptic(int source_id, int haptic_index,
double intensity, double duration) {
if (intensity == 0.0)
duration = 0.0;
UpdateVibration(source_id, haptic_index, intensity, duration,
++vibration_index_);
}
void OpenVRGamepadDataFetcher::UpdateVibration(int source_id, int haptic_index,
double intensity, double duration, uint64_t vibration_index) {
// Prevent previously scheduled tasks from overriding newer inputs.
if (vibration_index != vibration_index_)
return;
// I've found you can only reliably ask OpenVR to vibrate an axis for <4ms or
// it won't respond to the command. It also prevents you from issuing new
// haptic requests for 5ms after one is issued. So for haptic responses longer
// than 4ms we do a loop of ~4ms on, 1ms off until the requested duration has
// passed. To the user this feels like a continuous vibration.
// In addition to that, OpenVR has no concept of intensity but we can simulate
// it by repeating shorter haptic pulses, so we'll scale the pulse duration
// by the intensity to accomplish the desired.
uint32_t microseconds = static_cast<uint32_t>(
(duration < 3.9 ? duration : 3.9) * 1000.0) * intensity;
vr_system_->TriggerHapticPulse(source_id, haptic_index, microseconds);
if (duration > 5.0) {
// Trigger another haptic pulse every 5ms till the requested duration has
// elapsed.
base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::Bind(&OpenVRGamepadDataFetcher::UpdateVibration,
base::Unretained(this),
source_id, haptic_index, intensity, duration-5.0, vibration_index_),
base::TimeDelta::FromMilliseconds(5));
}
}
} // namespace device