Chromium Code Reviews| Index: chrome/browser/android/vr_shell/vr_controller.cc |
| diff --git a/chrome/browser/android/vr_shell/vr_controller.cc b/chrome/browser/android/vr_shell/vr_controller.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..b2775fed868f7d04c9df3e10987098c67b778794 |
| --- /dev/null |
| +++ b/chrome/browser/android/vr_shell/vr_controller.cc |
| @@ -0,0 +1,374 @@ |
| +// 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_shell/vr_controller.h" |
| + |
| +#include <android/log.h> |
| + |
| +#include <cmath> |
| + |
| +#include "base/logging.h" |
| +#include "third_party/WebKit/public/web/WebInputEvent.h" |
| + |
| +using blink::WebInputEvent; |
| + |
| +namespace vr_shell { |
| + |
| +namespace { |
| +constexpr float kDisplacementScaleFactor = 800.0f; |
| + |
| +// A slop represents a small rectangular region around the first touch point of |
| +// a gesture. |
| +// If the user does not move outside of the slop, no gesture is detected. |
| +// Gestures start to be detected when the user moves outside of the slop. |
| +// Vertical distance from the border to the center of slop. |
| +constexpr float kSlopVertical = 0.165f; |
| + |
| +// Horizontal distance from the border to the center of slop. |
| +constexpr float kSlopHorizontal = 0.125f; |
| +constexpr float kDelta = 1.0e-7f; |
| + |
| +constexpr float kMinZoomAngle = 0.25f; |
| + |
| +inline void ClampTouchpadPosition(gvr_vec2f* position) { |
| + position->x = std::min(std::max(0.0f, position->x), 1.0f); |
| + position->y = std::min(std::max(0.0f, position->y), 1.0f); |
| +} |
| + |
| +inline void VectSetZero(gvr_vec2f* v) { |
|
mthiesse
2016/09/22 16:20:10
replace gvr_ with gvr:: types throughout.
asimjour
2016/09/22 22:55:15
Done.
|
| + v->x = 0; |
| + v->y = 0; |
| +} |
| + |
| +inline gvr_vec2f VectSubtract(gvr_vec2f v1, gvr_vec2f v2) { |
| + gvr_vec2f result; |
| + result.x = v1.x - v2.x; |
| + result.y = v1.y - v2.y; |
| + return result; |
| +} |
| + |
| +inline bool VectEqual(const gvr_vec2f v1, const gvr_vec2f v2) { |
| + return (std::abs(v1.x - v2.x) < kDelta) && (std::abs(v1.y - v2.y) < kDelta); |
| +} |
| + |
| +} // namespace |
| + |
| +VrController::VrController(gvr_context_* vr_context) { |
|
mthiesse
2016/09/22 16:20:11
gvr_context*
asimjour
2016/09/22 22:55:15
Done.
|
| + Reset(); |
| +} |
| + |
| +VrController::~VrController() {} |
| + |
| +void VrController::OnResume() { |
| + if (controller_api_) |
| + controller_api_->Resume(); |
| +} |
| + |
| +void VrController::OnPause() { |
| + if (controller_api_) |
| + controller_api_->Pause(); |
| +} |
| + |
| +bool VrController::IsTouching() { |
| + return controller_state_.IsTouching(); |
| +} |
| + |
| +float VrController::TouchPosX() { |
| + return controller_state_.GetTouchPos().x; |
| +} |
| + |
| +float VrController::TouchPosY() { |
| + return controller_state_.GetTouchPos().y; |
| +} |
| + |
| +const gvr_quatf VrController::Orientation() { |
| + return controller_state_.GetOrientation(); |
| +} |
| + |
| +bool VrController::IsTouchDown() { |
| + return controller_state_.GetTouchDown(); |
| +} |
| + |
| +bool VrController::IsTouchUp() { |
| + return controller_state_.GetTouchUp(); |
| +} |
| + |
| +bool VrController::ButtonDown(const int32_t button) { |
| + return controller_state_.GetButtonDown(button); |
| +} |
| + |
| +bool VrController::ButtonUp(const int32_t button) { |
| + return controller_state_.GetButtonUp(button); |
| +} |
| + |
| +bool VrController::IsConnected() { |
| + return controller_state_.GetConnectionState() == gvr::kControllerConnected; |
| +} |
| + |
| +void VrController::UpdateState() { |
| + const int32_t old_status = controller_state_.GetApiStatus(); |
| + const int32_t old_connection_state = controller_state_.GetConnectionState(); |
| + // 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()); |
| + } |
| + return; |
| +} |
| + |
| +void VrController::Update(bool touch_up, |
| + bool touch_down, |
| + bool is_touching, |
| + const gvr_vec2f position, |
| + int64_t timestamp) { |
| + CHECK(touch_info_ != nullptr) << "touch_info_ not initialized properly."; |
| + touch_info_->touch_up = touch_up; |
| + touch_info_->touch_down = touch_down; |
| + touch_info_->is_touching = is_touching; |
| + touch_info_->touch_point.position = position; |
| + ClampTouchpadPosition(&touch_info_->touch_point.position); |
| + touch_info_->touch_point.timestamp = timestamp; |
| + |
| + UpdateGestureFromTouchInfo(); |
| +} |
| + |
| +void VrController::Initialize(gvr_context_* gvr_context) { |
|
mthiesse
2016/09/22 16:20:11
gvr_context*
asimjour
2016/09/22 22:55:15
Done.
|
| + CHECK(gvr_context != nullptr) << "invalid gvr_context"; |
| + controller_api_.reset(new gvr::ControllerApi); |
| + int32_t options = gvr::ControllerApi::DefaultOptions(); |
| + |
| + // Enable non-default options, if you need them: |
| + // options |= GVR_CONTROLLER_ENABLE_GYRO; |
| + CHECK(controller_api_->Init(options, gvr_context)); |
| + controller_api_->Resume(); |
| +} |
| + |
| +VrGesture VrController::DetectGesture() { |
| + if (controller_state_.GetConnectionState() == gvr::kControllerConnected) { |
| + gvr_vec2f position; |
| + position.x = TouchPosX(); |
| + position.y = TouchPosY(); |
| + Update(IsTouchUp(), IsTouchDown(), IsTouching(), position, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos); |
| + if (GetGestureListSize() > 0 && |
| + GetGesturePtr(0)->type == kGestureTypeScroll) { |
| + switch (GetGesturePtr(0)->details.scroll.state) { |
| + case WebInputEvent::GestureScrollBegin: |
| + return VrGesture( |
| + kGestureScroll, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos, |
| + GetGesturePtr(0)->displacement.x * kDisplacementScaleFactor, |
| + GetGesturePtr(0)->displacement.y * kDisplacementScaleFactor, |
| + WebInputEvent::GestureScrollBegin, Orientation()); |
| + case WebInputEvent::GestureScrollUpdate: |
| + return VrGesture( |
| + kGestureScroll, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos, 0, 0, |
| + WebInputEvent::GestureScrollUpdate, Orientation()); |
| + case WebInputEvent::GestureScrollEnd: |
| + return VrGesture( |
| + kGestureScroll, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos, |
| + GetGesturePtr(0)->displacement.x * kDisplacementScaleFactor, |
| + GetGesturePtr(0)->displacement.y * kDisplacementScaleFactor, |
| + WebInputEvent::GestureScrollEnd, Orientation()); |
| + } |
| + } |
| + |
| + if (ButtonDown(gvr::kControllerButtonClick)) { |
| + return VrGesture( |
| + kGestureButtonsChange, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos, 1, 0, |
| + Orientation()); |
| + } |
| + float dqx = 0.0f; |
| + |
| + dqx = last_qx_ - Orientation().qx; |
| + |
| + // don't accept rapid moves |
| + if (dqx < -1.0f || dqx > 1.0f) |
| + dqx = 0.0f; |
| + last_qx_ = Orientation().qx; |
| + |
| + if (ButtonDown(gvr::kControllerButtonApp)) |
| + zoom_in_progress_ = true; |
| + if (ButtonUp(gvr::kControllerButtonApp)) { |
| + zoom_in_progress_ = false; |
| + if (pinch_started_) { |
| + pinch_started_ = false; |
| + } |
| + } |
| + if (zoom_in_progress_) { |
| + if (dqx != 0.0f) { |
| + // dz == 1 means no zoom. dz < 1 means zoom-out and dz > 1 means |
| + // zoom-in. |
| + // dqx * 2 + 1 results to dz \in [0,2] |
| + return VrGesture( |
| + kGestureZoom, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos, |
| + dqx * 2 + 1, Orientation()); |
| + } |
| + if (Orientation().qz < kMinZoomAngle && |
| + Orientation().qz > -1 * kMinZoomAngle && pinch_started_) { |
| + pinch_started_ = false; |
| + } |
| + } |
| + return VrGesture(kGestureAngularMove, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos, |
| + gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos, |
| + Orientation()); |
| + } |
| + return VrGesture(); |
| +} |
| + |
| +void VrController::UpdateGestureFromTouchInfo() { |
| + // Clear the gesture list. |
| + gesture_list_.clear(); |
| + |
| + switch (state_) { |
| + // user has not put finger on touch pad. |
| + case WAITING: |
| + HandleWaitingState(); |
| + break; |
| + // user has not started a gesture (by moving out of slop). |
| + case TOUCHING: |
| + HandleDetectingState(); |
| + break; |
| + // user is scrolling on touchpad |
| + case SCROLLING: |
| + HandleScrollingState(); |
| + break; |
| + default: |
| + LOG(ERROR) << "Wrong gesture detector state: " << state_; |
| + break; |
| + } |
| +} |
| + |
| +const VrGesture* VrController::GetGesturePtr(const size_t index) { |
| + CHECK(index < gesture_list_.size()) << "The gesture index exceeds the" |
| + "size of gesture list."; |
| + return const_cast<VrGesture*>(&gesture_list_[index]); |
| +} |
| + |
| +void VrController::Update(const gvr_controller_state* controller_state) { |
| + // Update touch information. |
| + CHECK(touch_info_ != nullptr) << "touch_info_ not initialized properly."; |
| + touch_info_->touch_up = gvr_controller_state_get_touch_up(controller_state); |
| + touch_info_->touch_down = |
| + gvr_controller_state_get_touch_down(controller_state); |
| + touch_info_->is_touching = gvr_controller_state_is_touching(controller_state); |
| + touch_info_->touch_point.position.x = |
| + gvr_controller_state_get_touch_pos(controller_state).x; |
| + touch_info_->touch_point.position.y = |
| + gvr_controller_state_get_touch_pos(controller_state).y; |
| + ClampTouchpadPosition(&(touch_info_->touch_point.position)); |
| + touch_info_->touch_point.timestamp = |
| + gvr_controller_state_get_last_touch_timestamp(controller_state); |
| + |
| + UpdateGestureFromTouchInfo(); |
| +} |
| + |
| +void VrController::HandleWaitingState() { |
| + // User puts finger on touch pad (or when the touch down for current gesture |
| + // is missed, initiate gesture from current touch point). |
| + if (touch_info_->touch_down || touch_info_->is_touching) { |
| + // update initial touchpoint |
| + *init_touch_point_ = touch_info_->touch_point; |
| + // update current touchpoint |
| + *cur_touch_point_ = touch_info_->touch_point; |
| + state_ = TOUCHING; |
| + } |
| +} |
| + |
| +void VrController::HandleDetectingState() { |
| + // User lifts up finger from touch pad. |
| + if (touch_info_->touch_up || !(touch_info_->is_touching)) { |
| + Reset(); |
| + return; |
| + } |
| + |
| + // Touch position is changed and the touch point moves outside of slop. |
| + if (UpdateCurrentTouchpoint() && touch_info_->is_touching && |
| + !InSlop(touch_info_->touch_point.position)) { |
| + state_ = SCROLLING; |
| + VrGesture gesture; |
| + gesture.type = kGestureTypeScroll; |
| + gesture.details.scroll.state = WebInputEvent::GestureScrollBegin; |
| + UpdateGesture(&gesture); |
| + gesture_list_.push_back(gesture); |
| + } |
| +} |
| + |
| +void VrController::HandleScrollingState() { |
| + // Update current touch point. |
| + bool touch_position_changed = UpdateCurrentTouchpoint(); |
| + if (touch_info_->touch_up || !(touch_info_->is_touching)) { // gesture ends |
| + VrGesture scroll_end; |
| + scroll_end.type = kGestureTypeScroll; |
| + scroll_end.details.scroll.state = WebInputEvent::GestureScrollEnd; |
| + UpdateGesture(&scroll_end); |
| + gesture_list_.push_back(scroll_end); |
| + |
| + Reset(); |
| + } else if (touch_position_changed) { // User continues scrolling and there is |
| + // a change in touch position. |
| + VrGesture scroll_update; |
| + scroll_update.type = kGestureTypeScroll; |
| + scroll_update.details.scroll.state = WebInputEvent::GestureScrollUpdate; |
| + UpdateGesture(&scroll_update); |
| + gesture_list_.push_back(scroll_update); |
| + } |
| +} |
| + |
| +bool VrController::InSlop(const gvr_vec2f touch_position) { |
| + return (std::abs(touch_position.x - init_touch_point_->position.x) < |
| + kSlopHorizontal) && |
| + (std::abs(touch_position.y - init_touch_point_->position.y) < |
| + kSlopVertical); |
| +} |
| + |
| +void VrController::Reset() { |
| + // Reset state. |
| + state_ = WAITING; |
| + |
| + // Reset the pointers. |
| + prev_touch_point_.reset(new TouchPoint); |
| + cur_touch_point_.reset(new TouchPoint); |
| + init_touch_point_.reset(new TouchPoint); |
| + touch_info_.reset(new TouchInfo); |
| + VectSetZero(&overall_velocity_); |
| +} |
| + |
| +void VrController::UpdateGesture(VrGesture* gesture) { |
| + if (!gesture) |
| + LOG(ERROR) << "The gesture pointer is not initiated properly."; |
| + gesture->velocity = overall_velocity_; |
| + gesture->displacement = |
| + VectSubtract(cur_touch_point_->position, prev_touch_point_->position); |
| +} |
| + |
| +bool VrController::UpdateCurrentTouchpoint() { |
| + if (touch_info_->is_touching || touch_info_->touch_up) { |
| + // Update the touch point when the touch position has changed. |
| + if (!VectEqual(cur_touch_point_->position, |
| + touch_info_->touch_point.position)) { |
| + prev_touch_point_.swap(cur_touch_point_); |
| + *cur_touch_point_ = touch_info_->touch_point; |
| + |
| + return true; |
| + } |
| + } |
| + return false; |
| +} |
| + |
| +} // namespace vr_shell |