Commit 2c5e66fe authored by Yash Malik's avatar Yash Malik Committed by Commit Bot

VR: Hittesting should work with a ray as input instead of point

This CL has no functional change. It modifies UiElement::Hittest to take a ray
as input in preperation for keyboard UI element support.

Bug: 780135
Cq-Include-Trybots: master.tryserver.chromium.android:android_optional_gpu_tests_rel;master.tryserver.chromium.linux:linux_optional_gpu_tests_rel;master.tryserver.chromium.mac:mac_optional_gpu_tests_rel;master.tryserver.chromium.win:win_optional_gpu_tests_rel
Change-Id: I2788ec50ad1b23aea48489e6c6e7034775fc83e6
Reviewed-on: https://chromium-review.googlesource.com/767507Reviewed-by: default avatarChristopher Grant <cjgrant@chromium.org>
Reviewed-by: default avatarIan Vollick <vollick@chromium.org>
Commit-Queue: Yash Malik <ymalik@chromium.org>
Cr-Commit-Position: refs/heads/master@{#516321}
parent e953738c
...@@ -98,7 +98,7 @@ void Button::HandleHoverEnter() { ...@@ -98,7 +98,7 @@ void Button::HandleHoverEnter() {
} }
void Button::HandleHoverMove(const gfx::PointF& position) { void Button::HandleHoverMove(const gfx::PointF& position) {
hovered_ = hit_plane_->HitTest(position); hovered_ = hit_plane_->LocalHitTest(position);
OnStateUpdated(); OnStateUpdated();
} }
......
...@@ -242,7 +242,7 @@ float UiElement::computed_opacity() const { ...@@ -242,7 +242,7 @@ float UiElement::computed_opacity() const {
return computed_opacity_; return computed_opacity_;
} }
bool UiElement::HitTest(const gfx::PointF& point) const { bool UiElement::LocalHitTest(const gfx::PointF& point) const {
if (point.x() < 0.0f || point.x() > 1.0f || point.y() < 0.0f || if (point.x() < 0.0f || point.x() > 1.0f || point.y() < 0.0f ||
point.y() > 1.0f) { point.y() > 1.0f) {
return false; return false;
...@@ -267,6 +267,33 @@ bool UiElement::HitTest(const gfx::PointF& point) const { ...@@ -267,6 +267,33 @@ bool UiElement::HitTest(const gfx::PointF& point) const {
return rrect.contains(point_rect); return rrect.contains(point_rect);
} }
void UiElement::HitTest(const HitTestRequest& request,
HitTestResult* result) const {
gfx::Vector3dF ray_vector = request.ray_target - request.ray_origin;
ray_vector.GetNormalized(&ray_vector);
result->type = HitTestResult::Type::kNone;
float distance_to_plane;
if (!GetRayDistance(request.ray_origin, ray_vector, &distance_to_plane)) {
return;
}
if (distance_to_plane < 0 ||
distance_to_plane > request.max_distance_to_plane) {
return;
}
result->type = HitTestResult::Type::kHitsPlane;
result->distance_to_plane = distance_to_plane;
result->hit_point =
request.ray_origin + gfx::ScaleVector3d(ray_vector, distance_to_plane);
gfx::PointF unit_xy_point = GetUnitRectangleCoordinates(result->hit_point);
result->local_hit_point.set_x(0.5f + unit_xy_point.x());
result->local_hit_point.set_y(0.5f - unit_xy_point.y());
if (LocalHitTest(result->local_hit_point)) {
result->type = HitTestResult::Type::kHits;
}
}
void UiElement::SetMode(ColorScheme::Mode mode) { void UiElement::SetMode(ColorScheme::Mode mode) {
for (auto& child : children_) { for (auto& child : children_) {
child->SetMode(mode); child->SetMode(mode);
......
...@@ -60,6 +60,34 @@ struct EventHandlers { ...@@ -60,6 +60,34 @@ struct EventHandlers {
base::Callback<void()> button_up; base::Callback<void()> button_up;
}; };
struct HitTestRequest {
gfx::Point3F ray_origin;
gfx::Point3F ray_target;
float max_distance_to_plane;
};
// The result of performing a hit test.
struct HitTestResult {
enum Type {
// The given ray does not pass through the element.
kNone = 0,
// The given ray does not pass through the element, but passes through the
// element's plane.
kHitsPlane,
// The given ray passes through the element.
kHits,
};
Type type;
// The fields below are not set if the result Type is kNone.
// The hit position in the element's local coordinate space.
gfx::PointF local_hit_point;
// The hit position relative to the world.
gfx::Point3F hit_point;
// The distance from the ray origin to the hit position.
float distance_to_plane;
};
class UiElement : public cc::AnimationTarget { class UiElement : public cc::AnimationTarget {
public: public:
UiElement(); UiElement();
...@@ -121,7 +149,11 @@ class UiElement : public cc::AnimationTarget { ...@@ -121,7 +149,11 @@ class UiElement : public cc::AnimationTarget {
// though will extend outside this range when outside of the element: // though will extend outside this range when outside of the element:
// [(0.0, 0.0), (1.0, 0.0) // [(0.0, 0.0), (1.0, 0.0)
// (1.0, 0.0), (1.0, 1.0)] // (1.0, 0.0), (1.0, 1.0)]
virtual bool HitTest(const gfx::PointF& point) const; virtual bool LocalHitTest(const gfx::PointF& point) const;
// Performs a hit test for the ray supplied in the request and populates the
// result. The ray is in the world coordinate space.
void HitTest(const HitTestRequest& request, HitTestResult* result) const;
int id() const { return id_; } int id() const { return id_; }
......
...@@ -160,11 +160,11 @@ TEST(UiElements, HitTest) { ...@@ -160,11 +160,11 @@ TEST(UiElements, HitTest) {
for (size_t i = 0; i < arraysize(test_cases); ++i) { for (size_t i = 0; i < arraysize(test_cases); ++i) {
SCOPED_TRACE(i); SCOPED_TRACE(i);
EXPECT_EQ(test_cases[i].expected_rect, EXPECT_EQ(test_cases[i].expected_rect,
rect.HitTest(test_cases[i].location)); rect.LocalHitTest(test_cases[i].location));
EXPECT_EQ(test_cases[i].expected_circle, EXPECT_EQ(test_cases[i].expected_circle,
circle.HitTest(test_cases[i].location)); circle.LocalHitTest(test_cases[i].location));
EXPECT_EQ(test_cases[i].expected_rounded_rect, EXPECT_EQ(test_cases[i].expected_rounded_rect,
rounded_rect.HitTest(test_cases[i].location)); rounded_rect.LocalHitTest(test_cases[i].location));
} }
} }
......
...@@ -55,7 +55,7 @@ void UrlBar::OnButtonUp(const gfx::PointF& position) { ...@@ -55,7 +55,7 @@ void UrlBar::OnButtonUp(const gfx::PointF& position) {
security_region_down_ = false; security_region_down_ = false;
} }
bool UrlBar::HitTest(const gfx::PointF& position) const { bool UrlBar::LocalHitTest(const gfx::PointF& position) const {
return texture_->HitsUrlBar(position) || texture_->HitsBackButton(position); return texture_->HitsUrlBar(position) || texture_->HitsBackButton(position);
} }
......
...@@ -33,7 +33,7 @@ class UrlBar : public TexturedElement { ...@@ -33,7 +33,7 @@ class UrlBar : public TexturedElement {
void OnMove(const gfx::PointF& position) override; void OnMove(const gfx::PointF& position) override;
void OnButtonDown(const gfx::PointF& position) override; void OnButtonDown(const gfx::PointF& position) override;
void OnButtonUp(const gfx::PointF& position) override; void OnButtonUp(const gfx::PointF& position) override;
bool HitTest(const gfx::PointF& point) const override; bool LocalHitTest(const gfx::PointF& point) const override;
void SetHistoryButtonsEnabled(bool can_go_back); void SetHistoryButtonsEnabled(bool can_go_back);
void SetToolbarState(const ToolbarState& state); void SetToolbarState(const ToolbarState& state);
......
...@@ -27,12 +27,6 @@ static constexpr gfx::Point3F kOrigin = {0.0f, 0.0f, 0.0f}; ...@@ -27,12 +27,6 @@ static constexpr gfx::Point3F kOrigin = {0.0f, 0.0f, 0.0f};
static constexpr float kControllerQuiescenceAngularThresholdDegrees = 3.5f; static constexpr float kControllerQuiescenceAngularThresholdDegrees = 3.5f;
static constexpr float kControllerQuiescenceTemporalThresholdSeconds = 1.2f; static constexpr float kControllerQuiescenceTemporalThresholdSeconds = 1.2f;
gfx::Point3F GetRayPoint(const gfx::Point3F& rayOrigin,
const gfx::Vector3dF& rayVector,
float scale) {
return rayOrigin + gfx::ScaleVector3d(rayVector, scale);
}
bool IsScrollEvent(const GestureList& list) { bool IsScrollEvent(const GestureList& list) {
if (list.empty()) { if (list.empty()) {
return false; return false;
...@@ -49,60 +43,27 @@ bool IsScrollEvent(const GestureList& list) { ...@@ -49,60 +43,27 @@ bool IsScrollEvent(const GestureList& list) {
return false; return false;
} }
bool GetTargetLocalPoint(const gfx::Vector3dF& eye_to_target,
const UiElement& element,
float max_distance_to_plane,
gfx::PointF* out_target_local_point,
gfx::Point3F* out_target_point,
float* out_distance_to_plane) {
if (!element.GetRayDistance(kOrigin, eye_to_target, out_distance_to_plane)) {
return false;
}
if (*out_distance_to_plane < 0 ||
*out_distance_to_plane >= max_distance_to_plane) {
return false;
}
*out_target_point =
GetRayPoint(kOrigin, eye_to_target, *out_distance_to_plane);
gfx::PointF unit_xy_point =
element.GetUnitRectangleCoordinates(*out_target_point);
out_target_local_point->set_x(0.5f + unit_xy_point.x());
out_target_local_point->set_y(0.5f - unit_xy_point.y());
return true;
}
void HitTestElements(UiElement* element, void HitTestElements(UiElement* element,
ReticleModel* reticle_model, ReticleModel* reticle_model,
gfx::Vector3dF* out_eye_to_target, HitTestRequest* request) {
float* out_closest_element_distance) {
for (auto& child : element->children()) { for (auto& child : element->children()) {
HitTestElements(child.get(), reticle_model, out_eye_to_target, HitTestElements(child.get(), reticle_model, request);
out_closest_element_distance);
} }
if (!element->IsHitTestable()) { if (!element->IsHitTestable()) {
return; return;
} }
gfx::PointF local_point; HitTestResult result;
gfx::Point3F plane_intersection_point; element->HitTest(*request, &result);
float distance_to_plane; if (result.type != HitTestResult::Type::kHits) {
if (!GetTargetLocalPoint(*out_eye_to_target, *element,
*out_closest_element_distance, &local_point,
&plane_intersection_point, &distance_to_plane)) {
return;
}
if (!element->HitTest(local_point)) {
return; return;
} }
*out_closest_element_distance = distance_to_plane;
reticle_model->target_point = plane_intersection_point;
reticle_model->target_element_id = element->id(); reticle_model->target_element_id = element->id();
reticle_model->target_local_point = local_point; reticle_model->target_local_point = result.local_hit_point;
reticle_model->target_point = result.hit_point;
request->max_distance_to_plane = result.distance_to_plane;
} }
} // namespace } // namespace
...@@ -116,10 +77,9 @@ void UiInputManager::HandleInput(base::TimeTicks current_time, ...@@ -116,10 +77,9 @@ void UiInputManager::HandleInput(base::TimeTicks current_time,
ReticleModel* reticle_model, ReticleModel* reticle_model,
GestureList* gesture_list) { GestureList* gesture_list) {
UpdateQuiescenceState(current_time, controller_model); UpdateQuiescenceState(current_time, controller_model);
gfx::Vector3dF eye_to_target;
reticle_model->target_element_id = 0; reticle_model->target_element_id = 0;
reticle_model->target_local_point = kInvalidTargetPoint; reticle_model->target_local_point = kInvalidTargetPoint;
GetVisualTargetElement(controller_model, reticle_model, &eye_to_target); GetVisualTargetElement(controller_model, reticle_model);
UiElement* target_element = nullptr; UiElement* target_element = nullptr;
// TODO(vollick): this should be replaced with a formal notion of input // TODO(vollick): this should be replaced with a formal notion of input
...@@ -127,12 +87,14 @@ void UiInputManager::HandleInput(base::TimeTicks current_time, ...@@ -127,12 +87,14 @@ void UiInputManager::HandleInput(base::TimeTicks current_time,
if (input_locked_element_id_) { if (input_locked_element_id_) {
target_element = scene_->GetUiElementById(input_locked_element_id_); target_element = scene_->GetUiElementById(input_locked_element_id_);
if (target_element) { if (target_element) {
gfx::Point3F plane_intersection_point; HitTestRequest request;
float distance_to_plane; request.ray_origin = kOrigin;
if (!GetTargetLocalPoint(eye_to_target, *target_element, request.ray_target = reticle_model->target_point;
2 * scene_->background_distance(), request.max_distance_to_plane = 2 * scene_->background_distance();
&reticle_model->target_local_point, HitTestResult result;
&plane_intersection_point, &distance_to_plane)) { target_element->HitTest(request, &result);
reticle_model->target_local_point = result.local_hit_point;
if (result.type == HitTestResult::Type::kNone) {
reticle_model->target_local_point = kInvalidTargetPoint; reticle_model->target_local_point = kInvalidTargetPoint;
} }
} }
...@@ -380,8 +342,7 @@ bool UiInputManager::SendButtonUp(UiElement* target, ...@@ -380,8 +342,7 @@ bool UiInputManager::SendButtonUp(UiElement* target,
void UiInputManager::GetVisualTargetElement( void UiInputManager::GetVisualTargetElement(
const ControllerModel& controller_model, const ControllerModel& controller_model,
ReticleModel* reticle_model, ReticleModel* reticle_model) const {
gfx::Vector3dF* out_eye_to_target) const {
// If we place the reticle based on elements intersecting the controller beam, // If we place the reticle based on elements intersecting the controller beam,
// we can end up with the reticle hiding behind elements, or jumping laterally // we can end up with the reticle hiding behind elements, or jumping laterally
// in the field of view. This is physically correct, but hard to use. For // in the field of view. This is physically correct, but hard to use. For
...@@ -398,18 +359,19 @@ void UiInputManager::GetVisualTargetElement( ...@@ -398,18 +359,19 @@ void UiInputManager::GetVisualTargetElement(
// simplicity. // simplicity.
float distance = scene_->background_distance(); float distance = scene_->background_distance();
reticle_model->target_point = reticle_model->target_point =
GetRayPoint(controller_model.laser_origin, controller_model.laser_origin +
controller_model.laser_direction, distance); gfx::ScaleVector3d(controller_model.laser_direction, distance);
*out_eye_to_target = reticle_model->target_point - kOrigin;
out_eye_to_target->GetNormalized(out_eye_to_target);
// Determine which UI element (if any) intersects the line between the eyes // Determine which UI element (if any) intersects the line between the eyes
// and the controller target position. // and the controller target position.
float closest_element_distance = float closest_element_distance =
(reticle_model->target_point - kOrigin).Length(); (reticle_model->target_point - kOrigin).Length();
HitTestElements(&scene_->root_element(), reticle_model, out_eye_to_target, HitTestRequest request;
&closest_element_distance); request.ray_origin = kOrigin;
request.ray_target = reticle_model->target_point;
request.max_distance_to_plane = closest_element_distance;
HitTestElements(&scene_->root_element(), reticle_model, &request);
} }
void UiInputManager::UpdateQuiescenceState( void UiInputManager::UpdateQuiescenceState(
......
...@@ -68,8 +68,7 @@ class UiInputManager { ...@@ -68,8 +68,7 @@ class UiInputManager {
const gfx::PointF& target_point, const gfx::PointF& target_point,
ButtonState button_state); ButtonState button_state);
void GetVisualTargetElement(const ControllerModel& controller_model, void GetVisualTargetElement(const ControllerModel& controller_model,
ReticleModel* reticle_model, ReticleModel* reticle_model) const;
gfx::Vector3dF* out_eye_to_target) const;
void UpdateQuiescenceState(base::TimeTicks current_time, void UpdateQuiescenceState(base::TimeTicks current_time,
const ControllerModel& controller_model); const ControllerModel& controller_model);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment