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..ed69f4f4ad9a20894816e1fd547a10a5ad31b917 |
--- /dev/null |
+++ b/chrome/browser/android/vr_shell/vr_controller.cc |
@@ -0,0 +1,380 @@ |
+// 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; |
+ |
+// Minimum distance needed in at least one direction to call two vectors |
+// not equal. |
+constexpr float kDelta = 1.0e-7f; |
+ |
+// Minimum angle needed to start zoom gesture. |
+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) { |
+ 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); |
+} |
tdresser
2016/09/23 17:45:17
Can these be methods on the vector class?
asimjour
2016/09/23 19:56:31
Done.
|
+ |
+} // namespace |
+ |
+VrController::VrController(gvr_context* vr_context) { |
+ Initialize(vr_context); |
+ 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::IsButtonDown(const int32_t button) { |
+ return controller_state_.GetButtonDown(button); |
+} |
+ |
+bool VrController::IsButtonUp(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()); |
+ } |
+} |
+ |
+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) { |
+ 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, |
+ GetGesturePtr(0)->displacement.x * kDisplacementScaleFactor, |
+ GetGesturePtr(0)->displacement.y * kDisplacementScaleFactor, |
+ 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 (IsButtonDown(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 (IsButtonDown(gvr::kControllerButtonApp)) |
+ zoom_in_progress_ = true; |
+ if (IsButtonUp(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 |