| // 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 |