blob: 5900c575d1f545dc95afc8a348b4c7542ccbd3af [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 "chrome/browser/android/vr/vr_controller.h"
#include <algorithm>
#include <utility>
#include "base/logging.h"
#include "base/numerics/math_constants.h"
#include "base/numerics/ranges.h"
#include "chrome/browser/vr/input_event.h"
#include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr.h"
#include "third_party/gvr-android-sdk/src/libraries/headers/vr/gvr/capi/include/gvr_controller.h"
namespace vr {
namespace {
constexpr float kNanoSecondsPerSecond = 1.0e9f;
// Distance from the center of the controller to start rendering the laser.
constexpr float kLaserStartDisplacement = 0.045;
constexpr float kFadeDistanceFromFace = 0.34f;
constexpr float kDeltaAlpha = 3.0f;
void ClampTouchpadPosition(gfx::PointF* position) {
position->set_x(base::ClampToRange(position->x(), 0.0f, 1.0f));
position->set_y(base::ClampToRange(position->y(), 0.0f, 1.0f));
}
float DeltaTimeSeconds(int64_t last_timestamp_nanos) {
return (gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos -
last_timestamp_nanos) /
kNanoSecondsPerSecond;
}
gvr::ControllerButton PlatformToGvrButton(PlatformController::ButtonType type) {
switch (type) {
case PlatformController::kButtonHome:
return gvr::kControllerButtonHome;
case PlatformController::kButtonMenu:
return gvr::kControllerButtonApp;
case PlatformController::kButtonSelect:
return gvr::kControllerButtonClick;
default:
return gvr::kControllerButtonNone;
}
}
} // namespace
VrController::VrController(gvr::GvrApi* gvr_api)
: gvr_api_(gvr_api), previous_button_states_{0} {
DVLOG(1) << __FUNCTION__ << "=" << this;
controller_api_ = std::make_unique<gvr::ControllerApi>();
controller_state_ = std::make_unique<gvr::ControllerState>();
int32_t options = gvr::ControllerApi::DefaultOptions();
options |= GVR_CONTROLLER_ENABLE_ARM_MODEL;
// Enable non-default options - WebVR needs gyro and linear acceleration, and
// since VrShell implements GvrGamepadDataProvider we need this always.
options |= GVR_CONTROLLER_ENABLE_GYRO;
options |= GVR_CONTROLLER_ENABLE_ACCEL;
CHECK(controller_api_->Init(options, gvr_api_->cobj()));
controller_api_->Resume();
handedness_ = gvr_api_->GetUserPrefs().GetControllerHandedness();
gesture_detector_ = std::make_unique<GestureDetector>();
last_timestamp_nanos_ =
gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos;
}
VrController::~VrController() {
DVLOG(1) << __FUNCTION__ << "=" << this;
}
void VrController::OnResume() {
if (controller_api_) {
controller_api_->Resume();
handedness_ = gvr_api_->GetUserPrefs().GetControllerHandedness();
}
}
void VrController::OnPause() {
if (controller_api_)
controller_api_->Pause();
}
device::GvrGamepadData VrController::GetGamepadData() {
device::GvrGamepadData pad = {};
pad.connected = IsConnected();
pad.timestamp = controller_state_->GetLastOrientationTimestamp();
if (pad.connected) {
pad.touch_pos = {GetPositionInTrackpad().x(), GetPositionInTrackpad().y()};
pad.orientation = Orientation();
// Use orientation to rotate acceleration/gyro into seated space.
gfx::Transform pose_mat(Orientation());
const gvr::Vec3f& accel = controller_state_->GetAccel();
const gvr::Vec3f& gyro = controller_state_->GetGyro();
pad.accel = gfx::Vector3dF(accel.x, accel.y, accel.z);
pose_mat.TransformVector(&pad.accel);
pad.gyro = gfx::Vector3dF(gyro.x, gyro.y, gyro.z);
pose_mat.TransformVector(&pad.gyro);
pad.is_touching = controller_state_->IsTouching();
pad.controller_button_pressed =
controller_state_->GetButtonState(GVR_CONTROLLER_BUTTON_CLICK);
pad.right_handed = handedness_ == GVR_CONTROLLER_RIGHT_HANDED;
}
return pad;
}
bool VrController::IsButtonDown(ButtonType type) const {
return controller_state_->GetButtonState(PlatformToGvrButton(type));
}
bool VrController::IsTouchingTrackpad() const {
return controller_state_->IsTouching();
}
gfx::PointF VrController::GetPositionInTrackpad() const {
gfx::PointF position{controller_state_->GetTouchPos().x,
controller_state_->GetTouchPos().y};
ClampTouchpadPosition(&position);
return position;
}
base::TimeTicks VrController::GetLastOrientationTimestamp() const {
// TODO(crbug/866040): Use controller_state_->GetLastTouchTimestamp() when
// GVR is upgraded.
return last_orientation_timestamp_;
}
base::TimeTicks VrController::GetLastTouchTimestamp() const {
// TODO(crbug/866040): Use controller_state_->GetLastTouchTimestamp() when
// GVR is upgraded.
return last_touch_timestamp_;
}
base::TimeTicks VrController::GetLastButtonTimestamp() const {
// TODO(crbug/866040): Use controller_state_->GetLastButtonTimestamp() when
// GVR is upgraded.
return last_button_timestamp_;
}
ControllerModel::Handedness VrController::GetHandedness() const {
return handedness_ == GVR_CONTROLLER_RIGHT_HANDED
? ControllerModel::kRightHanded
: ControllerModel::kLeftHanded;
}
bool VrController::GetRecentered() const {
return controller_state_->GetRecentered();
}
int VrController::GetBatteryLevel() const {
return static_cast<int>(controller_state_->GetBatteryLevel());
}
gfx::Quaternion VrController::Orientation() const {
const gvr::Quatf& orientation = controller_state_->GetOrientation();
return gfx::Quaternion(orientation.qx, orientation.qy, orientation.qz,
orientation.qw);
}
gfx::Point3F VrController::Position() const {
const gvr::Vec3f& position = controller_state_->GetPosition();
return gfx::Point3F(position.x + head_offset_.x(),
position.y + head_offset_.y(),
position.z + head_offset_.z());
}
void VrController::GetTransform(gfx::Transform* out) const {
*out = gfx::Transform(Orientation());
const gfx::Point3F& position = Position();
out->matrix().postTranslate(position.x(), position.y(), position.z());
}
void VrController::GetRelativePointerTransform(gfx::Transform* out) const {
*out = gfx::Transform();
out->RotateAboutXAxis(-kErgoAngleOffset * 180.0f / base::kPiFloat);
out->Translate3d(0, 0, -kLaserStartDisplacement);
}
void VrController::GetPointerTransform(gfx::Transform* out) const {
gfx::Transform controller;
GetTransform(&controller);
GetRelativePointerTransform(out);
out->ConcatTransform(controller);
}
float VrController::GetOpacity() const {
return alpha_value_;
}
gfx::Point3F VrController::GetPointerStart() const {
gfx::Transform pointer_transform;
GetPointerTransform(&pointer_transform);
gfx::Point3F pointer_position;
pointer_transform.TransformPoint(&pointer_position);
return pointer_position;
}
bool VrController::TouchDownHappened() {
return controller_state_->GetTouchDown();
}
bool VrController::TouchUpHappened() {
return controller_state_->GetTouchUp();
}
bool VrController::ButtonDownHappened(ButtonType button) const {
// Workaround for GVR sometimes not reporting GetButtonDown when it should.
auto gvr_button = PlatformToGvrButton(button);
bool detected_down = !previous_button_states_[static_cast<int>(gvr_button)] &&
ButtonState(gvr_button);
return controller_state_->GetButtonDown(gvr_button) || detected_down;
}
bool VrController::ButtonUpHappened(ButtonType button) const {
// Workaround for GVR sometimes not reporting GetButtonUp when it should.
auto gvr_button = PlatformToGvrButton(button);
bool detected_up = previous_button_states_[static_cast<int>(gvr_button)] &&
!ButtonState(gvr_button);
return controller_state_->GetButtonUp(gvr_button) || detected_up;
}
bool VrController::ButtonState(gvr::ControllerButton button) const {
return controller_state_->GetButtonState(button);
}
bool VrController::IsConnected() {
return controller_state_->GetConnectionState() == gvr::kControllerConnected;
}
void VrController::UpdateState(const gfx::Transform& head_pose) {
gfx::Transform inv_pose;
if (head_pose.GetInverse(&inv_pose)) {
auto current_head_offset = gfx::Point3F();
inv_pose.TransformPoint(&current_head_offset);
head_offset_ = current_head_offset;
}
gvr::Mat4f gvr_head_pose;
TransformToGvrMat(head_pose, &gvr_head_pose);
controller_api_->ApplyArmModel(handedness_, gvr::kArmModelBehaviorFollowGaze,
gvr_head_pose);
const int32_t old_status = controller_state_->GetApiStatus();
const int32_t old_connection_state = controller_state_->GetConnectionState();
// Due to DON flow skipping weirdness, it's possible for the controller to be
// briefly disconnected. We don't want to miss a button up/down transition
// during that time, so only update previous button states if the controller
// is actually connected.
if (IsConnected()) {
for (int button = 0; button < GVR_CONTROLLER_BUTTON_COUNT; ++button) {
previous_button_states_[button] =
ButtonState(static_cast<gvr_controller_button>(button));
}
}
// Read current controller state.
controller_state_->Update(*controller_api_);
// Print new API status and connection state, if they changed.
if (controller_state_->GetApiStatus() != old_status ||
controller_state_->GetConnectionState() != old_connection_state) {
VLOG(1) << "Controller Connection status: "
<< gvr_controller_connection_state_to_string(
controller_state_->GetConnectionState());
}
UpdateAlpha();
UpdateTimestamps();
last_timestamp_nanos_ =
gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos;
}
InputEventList VrController::DetectGestures() {
if (controller_state_->GetConnectionState() != gvr::kControllerConnected) {
return {};
}
return gesture_detector_->DetectGestures(*this, base::TimeTicks::Now());
}
void VrController::UpdateTimestamps() {
// controller_state_->GetLast*Timestamp() returns timestamps in a
// different timebase from base::TimeTicks::Now(), so we can't use the
// timestamps in any meaningful way in the rest of Chrome.
// TODO(crbug/866040): Use controller_state_->GetLast*Timestamp() after
// we upgrade GVR.
base::TimeTicks now = base::TimeTicks::Now();
last_orientation_timestamp_ = now;
if (IsTouchingTrackpad())
last_touch_timestamp_ = now;
for (int button = PlatformController::kButtonTypeFirst;
button < PlatformController::kButtonTypeNumber; ++button) {
if (IsButtonDown(static_cast<PlatformController::ButtonType>(button))) {
last_button_timestamp_ = now;
break;
}
}
}
void VrController::UpdateAlpha() {
float distance_to_face = (Position() - gfx::Point3F()).Length();
float alpha_change = kDeltaAlpha * DeltaTimeSeconds(last_timestamp_nanos_);
alpha_value_ = base::ClampToRange(distance_to_face < kFadeDistanceFromFace
? alpha_value_ - alpha_change
: alpha_value_ + alpha_change,
0.0f, 1.0f);
}
} // namespace vr