blob: e27e817e52828c91ed1b47d881acdf7d8166e4ae [file]
// Copyright 2018 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 "chrome/browser/android/vr/gvr_input_delegate.h"
#include <utility>
#include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/android/vr/gl_browser_interface.h"
#include "chrome/browser/android/vr/vr_controller.h"
#include "chrome/browser/vr/input_event.h"
#include "chrome/browser/vr/model/controller_model.h"
#include "chrome/browser/vr/pose_util.h"
#include "chrome/browser/vr/render_info.h"
#include "device/vr/android/gvr/gvr_delegate.h"
namespace {
constexpr gfx::Vector3dF kForwardVector = {0.0f, 0.0f, -1.0f};
// TODO(https://crbug.com/957806): Copies of this function live in device/vr and
// device/gamepad, which will be consolidated. Is there a way to also remove
// this duplicate even though this is in under chrome/browser instead of device?
void CopyToUString(const base::string16& src,
base::char16* dest,
size_t dest_length) {
const size_t copy_char_count = std::min(src.size(), dest_length - 1);
src.copy(dest, copy_char_count);
std::fill(dest + copy_char_count, dest + dest_length, 0);
}
device::Gamepad CreateGamepad(const device::GvrGamepadData& data) {
device::Gamepad gamepad;
// Unless the controller state is updated on a different thread,
// data.connected should always be true when this function is called by
// GvrInputDelegate::GetInputSourceState.
gamepad.connected = data.connected;
gamepad.timestamp = data.timestamp;
// TODO(https://crbug.com/942201): Get correct ID string once WebXR spec issue
// #550 (https://github.com/immersive-web/webxr/issues/550) is resolved.
CopyToUString(base::UTF8ToUTF16("daydream-controller"), gamepad.id,
base::size(gamepad.id));
gamepad.hand = data.right_handed ? device::GamepadHand::kRight
: device::GamepadHand::kLeft;
bool pressed = data.controller_button_pressed;
bool touched = data.is_touching;
double value = pressed ? 1.0 : 0.0;
gamepad.buttons[gamepad.buttons_length++] =
device::GamepadButton(pressed, touched, value);
if (touched) {
// data.touch_pos values are clamped to [0.0, 1.0], so normalize them to
// [-1.0, 1.0]
gamepad.axes[0] = (data.touch_pos.x() * 2.0) - 1.0;
gamepad.axes[1] = (data.touch_pos.y() * 2.0) - 1.0;
} else {
gamepad.axes[0] = 0.0;
gamepad.axes[1] = 0.0;
}
gamepad.axes_length = 2;
return gamepad;
}
} // namespace
namespace vr {
GvrInputDelegate::GvrInputDelegate(gvr::GvrApi* gvr_api,
GlBrowserInterface* browser)
: controller_(std::make_unique<VrController>(gvr_api)),
gvr_api_(gvr_api),
browser_(browser) {}
GvrInputDelegate::~GvrInputDelegate() = default;
gfx::Transform GvrInputDelegate::GetHeadPose() {
gfx::Transform head_pose;
device::GvrDelegate::GetGvrPoseWithNeckModel(gvr_api_, &head_pose);
return head_pose;
}
void GvrInputDelegate::OnTriggerEvent(bool pressed) {
NOTREACHED();
}
void GvrInputDelegate::UpdateController(const gfx::Transform& head_pose,
base::TimeTicks current_time,
bool is_webxr_frame) {
controller_->UpdateState(head_pose);
device::GvrGamepadData controller_data = controller_->GetGamepadData();
if (!is_webxr_frame)
controller_data.connected = false;
browser_->UpdateGamepadData(controller_data);
}
ControllerModel GvrInputDelegate::GetControllerModel(
const gfx::Transform& head_pose) {
gfx::Vector3dF head_direction = GetForwardVector(head_pose);
gfx::Vector3dF controller_direction;
gfx::Quaternion controller_quat;
if (!controller_->IsConnected()) {
// No controller detected, set up a gaze cursor that tracks the forward
// direction.
controller_direction = kForwardVector;
controller_quat = gfx::Quaternion(kForwardVector, head_direction);
} else {
controller_direction = {0.0f, -sin(kErgoAngleOffset),
-cos(kErgoAngleOffset)};
controller_quat = controller_->Orientation();
}
gfx::Transform(controller_quat).TransformVector(&controller_direction);
ControllerModel controller_model;
controller_->GetTransform(&controller_model.transform);
controller_model.touchpad_button_state = ControllerModel::ButtonState::kUp;
DCHECK(!(controller_->ButtonUpHappened(PlatformController::kButtonSelect) &&
controller_->ButtonDownHappened(PlatformController::kButtonSelect)))
<< "Cannot handle a button down and up event within one frame.";
if (controller_->ButtonState(gvr::kControllerButtonClick)) {
controller_model.touchpad_button_state =
ControllerModel::ButtonState::kDown;
}
controller_model.app_button_state =
controller_->ButtonState(gvr::kControllerButtonApp)
? ControllerModel::ButtonState::kDown
: ControllerModel::ButtonState::kUp;
controller_model.home_button_state =
controller_->ButtonState(gvr::kControllerButtonHome)
? ControllerModel::ButtonState::kDown
: ControllerModel::ButtonState::kUp;
controller_model.opacity = controller_->GetOpacity();
controller_model.laser_direction = controller_direction;
controller_model.laser_origin = controller_->GetPointerStart();
controller_model.handedness = controller_->GetHandedness();
controller_model.recentered = controller_->GetRecentered();
controller_model.touching_touchpad = controller_->IsTouchingTrackpad();
controller_model.touchpad_touch_position =
controller_->GetPositionInTrackpad();
controller_model.last_orientation_timestamp =
controller_->GetLastOrientationTimestamp();
controller_model.last_button_timestamp =
controller_->GetLastButtonTimestamp();
controller_model.battery_level = controller_->GetBatteryLevel();
return controller_model;
}
InputEventList GvrInputDelegate::GetGestures(base::TimeTicks current_time) {
if (!controller_->IsConnected())
return {};
return gesture_detector_.DetectGestures(*controller_, current_time);
}
device::mojom::XRInputSourceStatePtr GvrInputDelegate::GetInputSourceState() {
device::mojom::XRInputSourceStatePtr state =
device::mojom::XRInputSourceState::New();
state->description = device::mojom::XRInputSourceDescription::New();
// Only one controller is supported, so the source id can be static.
state->source_id = 1;
// It's a handheld pointing device.
state->description->target_ray_mode =
device::mojom::XRTargetRayMode::POINTING;
// Controller uses an arm model.
state->description->emulated_position = true;
if (controller_->IsConnected()) {
// Set the primary button state.
bool select_button_down =
controller_->IsButtonDown(PlatformController::kButtonSelect);
state->primary_input_pressed = select_button_down;
state->primary_input_clicked =
was_select_button_down_ && !select_button_down;
was_select_button_down_ = select_button_down;
// Set handedness.
state->description->handedness =
controller_->GetHandedness() ==
ControllerModel::Handedness::kRightHanded
? device::mojom::XRHandedness::RIGHT
: device::mojom::XRHandedness::LEFT;
// Get the grip transform
gfx::Transform grip;
controller_->GetTransform(&grip);
state->grip = grip;
// Set the pointer offset from the grip transform.
gfx::Transform pointer;
controller_->GetRelativePointerTransform(&pointer);
state->description->pointer_offset = pointer;
// This Gamepad data is used to expose touchpad position to WebXR.
state->gamepad = CreateGamepad(controller_->GetGamepadData());
}
return state;
}
void GvrInputDelegate::OnResume() {
controller_->OnResume();
}
void GvrInputDelegate::OnPause() {
controller_->OnPause();
}
} // namespace vr