blob: bd08cef9ad0fee586e9f4d358f2448c890dd0e62 [file] [log] [blame]
// Copyright 2017 The Fuchsia 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 "src/ui/scenic/lib/gfx/engine/hit_tester.h"
#include <sstream>
#include <stack>
#include "src/lib/fxl/logging.h"
#include "src/ui/scenic/lib/gfx/engine/hit_accumulator.h"
#include "src/ui/scenic/lib/gfx/resources/nodes/node.h"
#include "src/ui/scenic/lib/gfx/resources/nodes/traversal.h"
namespace scenic_impl {
namespace gfx {
namespace {
// TODO(45071): Re-enable when we no longer have known misbehaving clients.
// void LogDistanceCollisionWarning(const std::vector<std::vector<GlobalId>>& collisions) {
// if (!collisions.empty()) {
// std::ostringstream warning_message("Input-hittable nodes with ids ");
// for (const std::vector<GlobalId>& ids : collisions) {
// warning_message << "[ ";
// for (const GlobalId& id : ids) {
// warning_message << id << " ";
// }
// warning_message << "] ";
// }
// warning_message << "are at equal distance and overlapping. See "
// "https://fuchsia.dev/fuchsia-src/the-book/ui/view_bounds#collisions";
// FXL_LOG(WARNING) << warning_message.str();
// }
// }
// Checks if a node is hit by a ray. |local_ray| is the ray in the local space of the node.
Node::IntersectionInfo HitTestSingleNode(const Node* node, escher::ray4 local_ray,
Node::IntersectionInfo parent_intersection) {
// Bail if hit testing is suppressed or if the ray is clipped.
if (node->hit_test_behavior() == ::fuchsia::ui::gfx::HitTestBehavior::kSuppress ||
(node->clip_to_self() && node->ClipsRay(local_ray))) {
return Node::IntersectionInfo{.did_hit = false, .continue_with_children = false};
}
return node->GetIntersection(local_ray, parent_intersection);
}
struct HitTestNode {
// The node to perform the test on.
const Node* node;
// The ray in the local space of the parent.
escher::ray4 parent_ray;
// The intersection of the ray against the parent node.
Node::IntersectionInfo parent_intersection;
};
} // namespace
void HitTest(Node* root, const escher::ray4& ray, HitAccumulator<NodeHit>* accumulator) {
FXL_DCHECK(root);
FXL_DCHECK(accumulator);
CollisionAccumulator collision_reporter;
// Hit testing scene graph iteratively by depth first traversal.
std::stack<HitTestNode> stack;
stack.push(HitTestNode{
.node = root, .parent_ray = ray, .parent_intersection = Node::IntersectionInfo()});
while (!stack.empty()) {
HitTestNode current_node = stack.top();
stack.pop();
// Get local reference frame.
const glm::mat4 inverse_transform =
glm::inverse(static_cast<glm::mat4>(current_node.node->transform()));
const escher::ray4 local_ray = inverse_transform * current_node.parent_ray;
// Perform hit test.
const Node::IntersectionInfo local_intersection =
HitTestSingleNode(current_node.node, local_ray, current_node.parent_intersection);
if (local_intersection.did_hit) {
FXL_VLOG(2) << "\tHit: " << current_node.node->global_id();
NodeHit hit{.node = current_node.node, .distance = local_intersection.distance};
collision_reporter.Add(hit);
accumulator->Add(hit);
}
if (local_intersection.continue_with_children) {
// Add all children to the stack.
// Since each descendant is added to the stack and then processed in opposite order, the
// actual traversal order here ends up being back-to-front.
ForEachChildFrontToBack(
*current_node.node, [&stack, &local_ray, &local_intersection](Node* node) {
stack.push(
{.node = node, .parent_ray = local_ray, .parent_intersection = local_intersection});
});
}
}
// TODO(45071): Re-enable when we no longer have known misbehaving clients.
// Warn if there are objects at the same distance as that is a user error.
// LogDistanceCollisionWarning(collision_reporter.Report());
}
} // namespace gfx
} // namespace scenic_impl