blob: c9c325d7c01f9686907500ca1897d35218107f71 [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 <lib/syslog/cpp/macros.h>
#include <stack>
#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"
#include "src/ui/scenic/lib/gfx/resources/view.h"
namespace scenic_impl {
namespace gfx {
namespace {
std::optional<ViewHit> CreateViewHit(const NodeHit& hit) {
FX_DCHECK(hit.node);
ViewPtr view = hit.node->FindOwningView(); // hit.node is a raw ptr, use and drop.
if (!view) {
return std::nullopt;
}
return ViewHit{
.view_ref_koid = view->view_ref_koid(),
.distance = hit.distance,
};
}
// 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,
bool semantic_hit_test) {
// Bail if hit testing is suppressed, the ray is clipped or if we're doing a semantic test and the
// node is invisible to it.
if (node->hit_test_behavior() == ::fuchsia::ui::gfx::HitTestBehavior::kSuppress ||
(semantic_hit_test && !node->semantically_visible()) ||
(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 intersection of the ray against the parent node.
Node::IntersectionInfo parent_intersection;
};
} // namespace
void HitTest(Node* starting_node, const escher::ray4& world_space_ray,
HitAccumulator<NodeHit>* accumulator, bool semantic_hit_test) {
FX_DCHECK(starting_node);
FX_DCHECK(accumulator);
// Hit testing scene graph iteratively by depth first traversal.
std::stack<HitTestNode> stack;
stack.push(HitTestNode{.node = starting_node, .parent_intersection = Node::IntersectionInfo()});
while (!stack.empty()) {
HitTestNode current_node = stack.top();
stack.pop();
// Get local reference frame.
const glm::mat4 world_to_local_transform =
glm::inverse(current_node.node->GetGlobalTransform());
const escher::ray4 local_ray = world_to_local_transform * world_space_ray;
// Perform hit test.
const Node::IntersectionInfo local_intersection = HitTestSingleNode(
current_node.node, local_ray, current_node.parent_intersection, semantic_hit_test);
if (local_intersection.did_hit) {
FX_VLOGS(2) << "\tHit: " << current_node.node->global_id();
NodeHit hit{.node = current_node.node, .distance = local_intersection.distance};
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_intersection](Node* child) {
stack.push({.node = child, .parent_intersection = local_intersection});
});
}
}
}
void HitTest(Node* starting_node, const escher::ray4& world_space_ray,
HitAccumulator<ViewHit>* accumulator, bool semantic_hit_test) {
MappingAccumulator<NodeHit, ViewHit> transforming_accumulator(
accumulator, [](const NodeHit& hit) { return CreateViewHit(hit); });
HitTest(starting_node, world_space_ray, &transforming_accumulator, semantic_hit_test);
transforming_accumulator.EndLayer();
}
} // namespace gfx
} // namespace scenic_impl