| // 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 <cmath> |
| |
| #include "base/logging.h" |
| #include "base/time/time.h" |
| #include "third_party/gvr-android-sdk/src/ndk/include/vr/gvr/capi/include/gvr.h" |
| #include "third_party/gvr-android-sdk/src/ndk/include/vr/gvr/capi/include/gvr_controller.h" |
| |
| 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. Also, minimum time distance needed to call two timestamps |
| // not equal. |
| constexpr float kDelta = 1.0e-7f; |
| |
| constexpr float kCutoffHz = 10.0f; |
| constexpr float kRC = static_cast<float>(1.0 / (2.0 * M_PI * kCutoffHz)); |
| constexpr float kNanoSecondsPerSecond = 1.0e9f; |
| |
| class Vector { |
| public: |
| static 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); |
| } |
| |
| static inline void SetZero(gvr::Vec2f* v) { |
| v->x = 0; |
| v->y = 0; |
| } |
| |
| static inline gvr::Vec2f Subtract(gvr::Vec2f v1, gvr::Vec2f v2) { |
| gvr::Vec2f result; |
| result.x = v1.x - v2.x; |
| result.y = v1.y - v2.y; |
| return result; |
| } |
| |
| static inline gvr::Vec2f Add(gvr::Vec2f v1, gvr::Vec2f v2) { |
| gvr::Vec2f result; |
| result.x = v1.x + v2.x; |
| result.y = v1.y + v2.y; |
| return result; |
| } |
| |
| static inline bool Equal(const gvr::Vec2f v1, const gvr::Vec2f v2) { |
| return (std::abs(v1.x - v2.x) < kDelta) && (std::abs(v1.y - v2.y) < kDelta); |
| } |
| |
| static inline gvr::Vec2f ScalarMult(gvr::Vec2f v, float scalar) { |
| gvr::Vec2f vect_prod; |
| vect_prod.x = v.x * scalar; |
| vect_prod.y = v.y * scalar; |
| return vect_prod; |
| } |
| |
| }; // Vector |
| |
| } // 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(gvr::ControllerButton button) { |
| return controller_state_->GetButtonDown(button); |
| } |
| |
| bool VrController::IsButtonUp(gvr::ControllerButton 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::UpdateTouchInfo() { |
| CHECK(touch_info_ != nullptr) << "touch_info_ not initialized properly."; |
| gvr::Vec2f position; |
| position.x = TouchPosX(); |
| position.y = TouchPosY(); |
| touch_info_->touch_up = IsTouchUp(); |
| touch_info_->touch_down = IsTouchDown(); |
| touch_info_->is_touching = IsTouching(); |
| touch_info_->touch_point.position = position; |
| Vector::ClampTouchpadPosition(&touch_info_->touch_point.position); |
| touch_info_->touch_point.timestamp = |
| gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos; |
| if (controller_state_->GetLastTouchTimestamp() == last_touch_timestamp_) { |
| // Fill the touch_info |
| float duration = |
| (gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos - |
| last_timestamp_nanos_) / |
| kNanoSecondsPerSecond; |
| |
| position.x += overall_velocity_.x * duration; |
| position.y += overall_velocity_.y * duration; |
| touch_info_->touch_point.position.x = position.x; |
| touch_info_->touch_point.position.y = position.y; |
| } |
| last_touch_timestamp_ = controller_state_->GetLastTouchTimestamp(); |
| last_timestamp_nanos_ = |
| gvr::GvrApi::GetTimePointNow().monotonic_system_time_nanos; |
| } |
| |
| void VrController::Initialize(gvr_context* gvr_context) { |
| CHECK(gvr_context != nullptr) << "invalid gvr_context"; |
| controller_api_.reset(new gvr::ControllerApi); |
| controller_state_.reset(new gvr::ControllerState); |
| 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(); |
| } |
| |
| std::vector<std::unique_ptr<WebGestureEvent>> VrController::DetectGestures() { |
| std::vector<std::unique_ptr<WebGestureEvent>> gesture_list; |
| std::unique_ptr<WebGestureEvent> gesture(new WebGestureEvent()); |
| |
| if (controller_state_->GetConnectionState() != gvr::kControllerConnected) { |
| gesture_list.push_back(std::move(gesture)); |
| return gesture_list; |
| } |
| UpdateTouchInfo(); |
| UpdateGestureFromTouchInfo(gesture.get()); |
| |
| if (gesture->type == WebInputEvent::Undefined && |
| IsButtonDown(gvr::kControllerButtonClick)) { |
| gesture->type = WebInputEvent::GestureTapDown; |
| gesture->data.tapDown.width = 0; |
| gesture->data.tapDown.height = 0; |
| } |
| gesture->sourceDevice = blink::WebGestureDeviceTouchpad; |
| gesture_list.push_back(std::move(gesture)); |
| |
| if (gesture_list.back()->type == WebInputEvent::GestureScrollEnd) { |
| std::unique_ptr<WebGestureEvent> fling(new WebGestureEvent()); |
| fling->timeStampSeconds = gesture_list.back()->timeStampSeconds; |
| fling->sourceDevice = blink::WebGestureDeviceTouchpad; |
| fling->type = WebInputEvent::GestureFlingStart; |
| if (IsHorizontalGesture()) { |
| fling->data.flingStart.velocityX = |
| overall_velocity_.x * kDisplacementScaleFactor; |
| } else { |
| fling->data.flingStart.velocityY = |
| overall_velocity_.y * kDisplacementScaleFactor; |
| } |
| gesture_list.push_back(std::move(fling)); |
| Reset(); |
| } |
| |
| return gesture_list; |
| } |
| |
| void VrController::UpdateGestureFromTouchInfo(WebGestureEvent* gesture) { |
| gesture->timeStampSeconds = |
| (base::TimeTicks::Now() - base::TimeTicks()).InSecondsF(); |
| switch (state_) { |
| // User has not put finger on touch pad. |
| case WAITING: |
| HandleWaitingState(gesture); |
| break; |
| // User has not started a gesture (by moving out of slop). |
| case TOUCHING: |
| HandleDetectingState(gesture); |
| break; |
| // User is scrolling on touchpad |
| case SCROLLING: |
| HandleScrollingState(gesture); |
| break; |
| default: |
| LOG(ERROR) << "Wrong gesture detector state: " << state_; |
| break; |
| } |
| } |
| |
| void VrController::HandleWaitingState(WebGestureEvent* gesture) { |
| // 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; |
| |
| gesture->type = WebInputEvent::GestureFlingCancel; |
| gesture->data.flingCancel.preventBoosting = false; |
| } |
| } |
| |
| void VrController::HandleDetectingState(WebGestureEvent* gesture) { |
| // 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; |
| gesture->type = WebInputEvent::GestureScrollBegin; |
| UpdateGesture(gesture); |
| gesture->data.scrollBegin.deltaXHint = |
| displacement_.x * kDisplacementScaleFactor; |
| gesture->data.scrollBegin.deltaYHint = |
| displacement_.y * kDisplacementScaleFactor; |
| } |
| } |
| |
| void VrController::HandleScrollingState(WebGestureEvent* gesture) { |
| // Update current touch point. |
| bool touch_position_changed = UpdateCurrentTouchpoint(); |
| if (touch_info_->touch_up || !(touch_info_->is_touching)) { |
| // Gesture ends. |
| gesture->type = WebInputEvent::GestureScrollEnd; |
| UpdateGesture(gesture); |
| } else if (touch_position_changed) { |
| // User continues scrolling and there is a change in touch position. |
| gesture->type = WebInputEvent::GestureScrollUpdate; |
| UpdateGesture(gesture); |
| if (IsHorizontalGesture()) { |
| gesture->data.scrollUpdate.deltaX = |
| displacement_.x * kDisplacementScaleFactor; |
| } else { |
| gesture->data.scrollUpdate.deltaY = |
| displacement_.y * kDisplacementScaleFactor; |
| } |
| } |
| } |
| |
| bool VrController::IsHorizontalGesture() { |
| return std::abs(overall_velocity_.x) > std::abs(overall_velocity_.y); |
| } |
| |
| 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); |
| Vector::SetZero(&overall_velocity_); |
| } |
| |
| void VrController::UpdateGesture(WebGestureEvent* gesture) { |
| if (!gesture) |
| LOG(ERROR) << "The gesture pointer is not initiated properly."; |
| displacement_ = |
| Vector::Subtract(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 (!Vector::Equal(cur_touch_point_->position, |
| touch_info_->touch_point.position)) { |
| prev_touch_point_.swap(cur_touch_point_); |
| *cur_touch_point_ = touch_info_->touch_point; |
| UpdateOverallVelocity(); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| void VrController::UpdateOverallVelocity() { |
| float duration = |
| (cur_touch_point_->timestamp - prev_touch_point_->timestamp) / |
| kNanoSecondsPerSecond; |
| |
| // If the timestamp does not change, do not update velocity. |
| if (duration < kDelta) |
| return; |
| |
| gvr::Vec2f displacement = |
| Vector::Subtract(cur_touch_point_->position, prev_touch_point_->position); |
| |
| gvr::Vec2f velocity = Vector::ScalarMult(displacement, 1 / duration); |
| |
| float weight = duration / (kRC + duration); |
| |
| overall_velocity_ = |
| Vector::Add(Vector::ScalarMult(overall_velocity_, 1 - weight), |
| Vector::ScalarMult(velocity, weight)); |
| } |
| |
| } // namespace vr_shell |