// AABB tree built upon external data set, referencing the external data by integer indices. // The AABB tree balancing and traversal (ray casting, closest triangle of an indexed triangle mesh) // were adapted from libigl AABB.{cpp,hpp} Copyright (C) 2015 Alec Jacobson // while the implicit balanced tree representation and memory optimizations are Vojtech's. #ifndef slic3r_AABBTreeIndirect_hpp_ #define slic3r_AABBTreeIndirect_hpp_ #include #include #include #include #include "Utils.hpp" // for next_highest_power_of_2() extern "C" { // Ray-Triangle Intersection Test Routines by Tomas Moller, May 2000 #include } // Definition of the ray intersection hit structure. #include namespace Slic3r { namespace AABBTreeIndirect { // Static balanced AABB tree for raycasting and closest triangle search. // The balanced tree is built over a single large std::vector of nodes, where the children of nodes // are addressed implicitely using a power of two indexing rule. // Memory for a full balanced tree is allocated, but not all nodes at the last level are used. // This may seem like a waste of memory, but one saves memory for the node links and there is zero // overhead of a memory allocator management (usually the memory allocator adds at least one pointer // before the memory returned). However, allocating memory in a single vector is very fast even // in multi-threaded environment and it is cache friendly. // // A balanced tree is built upon a vector of bounding boxes and their centroids, storing the reference // to the source entity (a 3D triangle, a 2D segment etc, a 3D or 2D point etc). // The source bounding boxes may have an epsilon applied to fight numeric rounding errors when // traversing the AABB tree. template class Tree { public: static constexpr int NumDimensions = ANumDimensions; using CoordType = ACoordType; using VectorType = Eigen::Matrix; using BoundingBox = Eigen::AlignedBox; // Following could be static constexpr size_t, but that would not link in C++11 enum : size_t { // Node is not used. npos = size_t(-1), // Inner node (not leaf). inner = size_t(-2) }; // Single node of the implicit balanced AABB tree. There are no links to the children nodes, // as these links are calculated implicitely using a power of two rule. struct Node { // Index of the external source entity, for which this AABB tree was built, npos for internal nodes. size_t idx = npos; // Bounding box around this entity, possibly with epsilons applied to fight numeric rounding errors // when traversing the AABB tree. BoundingBox bbox; bool is_valid() const { return this->idx != npos; } bool is_inner() const { return this->idx == inner; } bool is_leaf() const { return ! this->is_inner(); } template void set(const SourceNode &rhs) { this->idx = rhs.idx(); this->bbox = rhs.bbox(); } }; void clear() { m_nodes.clear(); } // SourceNode shall implement // size_t SourceNode::idx() const // - Index to the outside entity (triangle, edge, point etc). // const VectorType& SourceNode::centroid() const // - Centroid of this node. The centroid is used for balancing the tree. // const BoundingBox& SourceNode::bbox() const // - Bounding box of this node, likely expanded with epsilon to account for numeric rounding during tree traversal. // Union of bounding boxes at a single level of the AABB tree is used for deciding the longest axis aligned dimension // to split around. template void build(std::vector &&input) { if (input.empty()) clear(); else { // Allocate enough memory for a full binary tree. m_nodes.assign(next_highest_power_of_2(input.size()) * 2 - 1, Node()); build_recursive(input, 0, 0, input.size() - 1); } input.clear(); } const std::vector& nodes() const { return m_nodes; } const Node& node(size_t idx) const { return m_nodes[idx]; } bool empty() const { return m_nodes.empty(); } // Addressing the child nodes using the power of two rule. static size_t left_child_idx(size_t idx) { return idx * 2 + 1; } static size_t right_child_idx(size_t idx) { return left_child_idx(idx) + 1; } const Node& left_child(size_t idx) const { return m_nodes[left_child_idx(idx)]; } const Node& right_child(size_t idx) const { return m_nodes[right_child_idx(idx)]; } template void build(const std::vector &input) { std::vector copy(input); this->build(std::move(copy)); } private: // Build a balanced tree by splitting the input sequence by an axis aligned plane at a dimension. template void build_recursive(std::vector &input, size_t node, const size_t left, const size_t right) { assert(node < m_nodes.size()); assert(left <= right); if (left == right) { // Insert a node into the balanced tree. m_nodes[node].set(input[left]); return; } // Calculate bounding box of the input. BoundingBox bbox(input[left].bbox()); for (size_t i = left + 1; i <= right; ++ i) bbox.extend(input[i].bbox()); int dimension = -1; bbox.diagonal().maxCoeff(&dimension); // Partition the input to left / right pieces of the same length to produce a balanced tree. size_t center = (left + right) / 2; partition_input(input, size_t(dimension), left, right, center); // Insert an inner node into the tree. Inner node does not reference any input entity (triangle, line segment etc). m_nodes[node].idx = inner; m_nodes[node].bbox = bbox; build_recursive(input, node * 2 + 1, left, center); build_recursive(input, node * 2 + 2, center + 1, right); } // Partition the input m_nodes at "k" and "dimension" using the QuickSelect method: // https://en.wikipedia.org/wiki/Quickselect // Items left of the k'th item are lower than the k'th item in the "dimension", // items right of the k'th item are higher than the k'th item in the "dimension", template void partition_input(std::vector &input, const size_t dimension, size_t left, size_t right, const size_t k) const { while (left < right) { size_t center = (left + right) / 2; CoordType pivot; { // Bubble sort the input[left], input[center], input[right], so that a median of the three values // will end up in input[center]. CoordType left_value = input[left ].centroid()(dimension); CoordType center_value = input[center].centroid()(dimension); CoordType right_value = input[right ].centroid()(dimension); if (left_value > center_value) { std::swap(input[left], input[center]); std::swap(left_value, center_value); } if (left_value > right_value) { std::swap(input[left], input[right]); right_value = left_value; } if (center_value > right_value) { std::swap(input[center], input[right]); center_value = right_value; } pivot = center_value; } if (right <= left + 2) // The interval is already sorted. break; size_t i = left; size_t j = right - 1; std::swap(input[center], input[j]); // Partition the set based on the pivot. for (;;) { // Skip left points that are already at correct positions. // Search will certainly stop at position (right - 1), which stores the pivot. while (input[++ i].centroid()(dimension) < pivot) ; // Skip right points that are already at correct positions. while (input[-- j].centroid()(dimension) > pivot && i < j) ; if (i >= j) break; std::swap(input[i], input[j]); } // Restore pivot to the center of the sequence. std::swap(input[i], input[right - 1]); // Which side the kth element is in? if (k < i) right = i - 1; else if (k == i) // Sequence is partitioned, kth element is at its place. break; else left = i + 1; } } // The balanced tree storage. std::vector m_nodes; }; using Tree2f = Tree<2, float>; using Tree3f = Tree<3, float>; using Tree2d = Tree<2, double>; using Tree3d = Tree<3, double>; namespace detail { template struct RayIntersector { using VertexType = AVertexType; using IndexedFaceType = AIndexedFaceType; using TreeType = ATreeType; using VectorType = AVectorType; const std::vector &vertices; const std::vector &faces; const TreeType &tree; const VectorType origin; const VectorType dir; const VectorType invdir; }; template struct RayIntersectorHits : RayIntersector { std::vector hits; }; //FIXME implement SSE for float AABB trees with float ray queries. // SSE/SSE2 is supported by any Intel/AMD x64 processor. // SSE support requires 16 byte alignment of the AABB nodes, representing the bounding boxes with 4+4 floats, // storing the node index as the 4th element of the bounding box min value etc. // https://www.flipcode.com/archives/SSE_RayBox_Intersection_Test.shtml template inline bool ray_box_intersect_invdir( const Eigen::MatrixBase &origin, const Eigen::MatrixBase &inv_dir, Eigen::AlignedBox box, const Scalar &t0, const Scalar &t1) { // http://people.csail.mit.edu/amy/papers/box-jgt.pdf // "An Efficient and Robust Ray–Box Intersection Algorithm" if (inv_dir.x() < 0) std::swap(box.min().x(), box.max().x()); if (inv_dir.y() < 0) std::swap(box.min().y(), box.max().y()); Scalar tmin = (box.min().x() - origin.x()) * inv_dir.x(); Scalar tymax = (box.max().y() - origin.y()) * inv_dir.y(); if (tmin > tymax) return false; Scalar tmax = (box.max().x() - origin.x()) * inv_dir.x(); Scalar tymin = (box.min().y() - origin.y()) * inv_dir.y(); if (tymin > tmax) return false; if (tymin > tmin) tmin = tymin; if (tymax < tmax) tmax = tymax; if (inv_dir.z() < 0) std::swap(box.min().z(), box.max().z()); Scalar tzmin = (box.min().z() - origin.z()) * inv_dir.z(); if (tzmin > tmax) return false; Scalar tzmax = (box.max().z() - origin.z()) * inv_dir.z(); if (tmin > tzmax) return false; if (tzmin > tmin) tmin = tzmin; if (tzmax < tmax) tmax = tzmax; return tmin < t1 && tmax > t0; } template std::enable_if_t::value && std::is_same::value, bool> intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), &t, &u, &v); } template std::enable_if_t::value && !std::is_same::value, bool> intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { using Vector = Eigen::Matrix; Vector w0 = v0.template cast(); Vector w1 = v1.template cast(); Vector w2 = v2.template cast(); return intersect_triangle1(const_cast(origin.data()), const_cast(dir.data()), w0.data(), w1.data(), w2.data(), &t, &u, &v); } template std::enable_if_t::value && std::is_same::value, bool> intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { using Vector = Eigen::Matrix; Vector o = origin.template cast(); Vector d = dir.template cast(); return intersect_triangle1(o.data(), d.data(), const_cast(v0.data()), const_cast(v1.data()), const_cast(v2.data()), &t, &u, &v); } template std::enable_if_t::value && ! std::is_same::value, bool> intersect_triangle(const V &origin, const V &dir, const W &v0, const W &v1, const W v2, double &t, double &u, double &v) { using Vector = Eigen::Matrix; Vector o = origin.template cast(); Vector d = dir.template cast(); Vector w0 = v0.template cast(); Vector w1 = v1.template cast(); Vector w2 = v2.template cast(); return intersect_triangle1(o.data(), d.data(), w0.data(), w1.data(), w2.data(), &t, &u, &v); } template static inline bool intersect_ray_recursive_first_hit( RayIntersectorType &ray_intersector, size_t node_idx, Scalar min_t, igl::Hit &hit) { const auto &node = ray_intersector.tree.node(node_idx); assert(node.is_valid()); if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast(), Scalar(0), min_t)) return false; if (node.is_leaf()) { // shoot ray, record hit auto face = ray_intersector.faces[node.idx]; double t, u, v; if (intersect_triangle( ray_intersector.origin, ray_intersector.dir, ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)], t, u, v) && t > 0.) { hit = igl::Hit { int(node.idx), -1, float(u), float(v), float(t) }; return true; } else return false; } else { // Left / right child node index. size_t left = node_idx * 2 + 1; size_t right = left + 1; igl::Hit left_hit; igl::Hit right_hit; bool left_ret = intersect_ray_recursive_first_hit(ray_intersector, left, min_t, left_hit); if (left_ret && left_hit.t < min_t) { min_t = left_hit.t; hit = left_hit; } else left_ret = false; bool right_ret = intersect_ray_recursive_first_hit(ray_intersector, right, min_t, right_hit); if (right_ret && right_hit.t < min_t) hit = right_hit; else right_ret = false; return left_ret || right_ret; } } template static inline void intersect_ray_recursive_all_hits(RayIntersectorType &ray_intersector, size_t node_idx) { using Scalar = typename RayIntersectorType::VectorType::Scalar; const auto &node = ray_intersector.tree.node(node_idx); assert(node.is_valid()); if (! ray_box_intersect_invdir(ray_intersector.origin, ray_intersector.invdir, node.bbox.template cast(), Scalar(0), std::numeric_limits::infinity())) return; if (node.is_leaf()) { auto face = ray_intersector.faces[node.idx]; double t, u, v; if (intersect_triangle( ray_intersector.origin, ray_intersector.dir, ray_intersector.vertices[face(0)], ray_intersector.vertices[face(1)], ray_intersector.vertices[face(2)], t, u, v) && t > 0.) { ray_intersector.hits.emplace_back(igl::Hit{ int(node.idx), -1, float(u), float(v), float(t) }); } } else { // Left / right child node index. size_t left = node_idx * 2 + 1; size_t right = left + 1; intersect_ray_recursive_all_hits(ray_intersector, left); intersect_ray_recursive_all_hits(ray_intersector, right); } } // Nothing to do with COVID-19 social distancing. template struct IndexedTriangleSetDistancer { using VertexType = AVertexType; using IndexedFaceType = AIndexedFaceType; using TreeType = ATreeType; using VectorType = AVectorType; const std::vector &vertices; const std::vector &faces; const TreeType &tree; const VectorType origin; }; // Real-time collision detection, Ericson, Chapter 5 template static inline Vector closest_point_to_triangle(const Vector &p, const Vector &a, const Vector &b, const Vector &c) { using Scalar = typename Vector::Scalar; // Check if P in vertex region outside A Vector ab = b - a; Vector ac = c - a; Vector ap = p - a; Scalar d1 = ab.dot(ap); Scalar d2 = ac.dot(ap); if (d1 <= 0 && d2 <= 0) return a; // Check if P in vertex region outside B Vector bp = p - b; Scalar d3 = ab.dot(bp); Scalar d4 = ac.dot(bp); if (d3 >= 0 && d4 <= d3) return b; // Check if P in edge region of AB, if so return projection of P onto AB Scalar vc = d1*d4 - d3*d2; if (a != b && vc <= 0 && d1 >= 0 && d3 <= 0) { Scalar v = d1 / (d1 - d3); return a + v * ab; } // Check if P in vertex region outside C Vector cp = p - c; Scalar d5 = ab.dot(cp); Scalar d6 = ac.dot(cp); if (d6 >= 0 && d5 <= d6) return c; // Check if P in edge region of AC, if so return projection of P onto AC Scalar vb = d5*d2 - d1*d6; if (vb <= 0 && d2 >= 0 && d6 <= 0) { Scalar w = d2 / (d2 - d6); return a + w * ac; } // Check if P in edge region of BC, if so return projection of P onto BC Scalar va = d3*d6 - d5*d4; if (va <= 0 && (d4 - d3) >= 0 && (d5 - d6) >= 0) { Scalar w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); return b + w * (c - b); } // P inside face region. Compute Q through its barycentric coordinates (u,v,w) Scalar denom = Scalar(1.0) / (va + vb + vc); Scalar v = vb * denom; Scalar w = vc * denom; return a + ab * v + ac * w; // = u*a + v*b + w*c, u = va * denom = 1.0-v-w }; template static inline Scalar squared_distance_to_indexed_triangle_set_recursive( IndexedTriangleSetDistancerType &distancer, size_t node_idx, Scalar low_sqr_d, Scalar up_sqr_d, size_t &i, Eigen::PlainObjectBase &c) { using Vector = typename IndexedTriangleSetDistancerType::VectorType; if (low_sqr_d > up_sqr_d) return low_sqr_d; // Save the best achieved hit. auto set_min = [&i, &c, &up_sqr_d](const Scalar sqr_d_candidate, const size_t i_candidate, const Vector &c_candidate) { if (sqr_d_candidate < up_sqr_d) { i = i_candidate; c = c_candidate; up_sqr_d = sqr_d_candidate; } }; const auto &node = distancer.tree.node(node_idx); assert(node.is_valid()); if (node.is_leaf()) { const auto &triangle = distancer.faces[node.idx]; Vector c_candidate = closest_point_to_triangle( distancer.origin, distancer.vertices[triangle(0)].template cast(), distancer.vertices[triangle(1)].template cast(), distancer.vertices[triangle(2)].template cast()); set_min((c_candidate - distancer.origin).squaredNorm(), node.idx, c_candidate); } else { size_t left_node_idx = node_idx * 2 + 1; size_t right_node_idx = left_node_idx + 1; const auto &node_left = distancer.tree.node(left_node_idx); const auto &node_right = distancer.tree.node(right_node_idx); assert(node_left.is_valid()); assert(node_right.is_valid()); bool looked_left = false; bool looked_right = false; const auto &look_left = [&]() { size_t i_left; Vector c_left = c; Scalar sqr_d_left = squared_distance_to_indexed_triangle_set_recursive(distancer, left_node_idx, low_sqr_d, up_sqr_d, i_left, c_left); set_min(sqr_d_left, i_left, c_left); looked_left = true; }; const auto &look_right = [&]() { size_t i_right; Vector c_right = c; Scalar sqr_d_right = squared_distance_to_indexed_triangle_set_recursive(distancer, right_node_idx, low_sqr_d, up_sqr_d, i_right, c_right); set_min(sqr_d_right, i_right, c_right); looked_right = true; }; // must look left or right if in box using BBoxScalar = typename IndexedTriangleSetDistancerType::TreeType::BoundingBox::Scalar; if (node_left.bbox.contains(distancer.origin.template cast())) look_left(); if (node_right.bbox.contains(distancer.origin.template cast())) look_right(); // if haven't looked left and could be less than current min, then look Scalar left_up_sqr_d = node_left.bbox.squaredExteriorDistance(distancer.origin); Scalar right_up_sqr_d = node_right.bbox.squaredExteriorDistance(distancer.origin); if (left_up_sqr_d < right_up_sqr_d) { if (! looked_left && left_up_sqr_d < up_sqr_d) look_left(); if (! looked_right && right_up_sqr_d < up_sqr_d) look_right(); } else { if (! looked_right && right_up_sqr_d < up_sqr_d) look_right(); if (! looked_left && left_up_sqr_d < up_sqr_d) look_left(); } } return up_sqr_d; } } // namespace detail // Build a balanced AABB Tree over an indexed triangles set, balancing the tree // on centroids of the triangles. // Epsilon is applied to the bounding boxes of the AABB Tree to cope with numeric inaccuracies // during tree traversal. template inline Tree<3, typename VertexType::Scalar> build_aabb_tree_over_indexed_triangle_set( // Indexed triangle set - 3D vertices. const std::vector &vertices, // Indexed triangle set - triangular faces, references to vertices. const std::vector &faces, //FIXME do we want to apply an epsilon? const typename VertexType::Scalar eps = 0) { using TreeType = Tree<3, typename VertexType::Scalar>; // using CoordType = typename TreeType::CoordType; using VectorType = typename TreeType::VectorType; using BoundingBox = typename TreeType::BoundingBox; struct InputType { size_t idx() const { return m_idx; } const BoundingBox& bbox() const { return m_bbox; } const VectorType& centroid() const { return m_centroid; } size_t m_idx; BoundingBox m_bbox; VectorType m_centroid; }; std::vector input; input.reserve(faces.size()); const VectorType veps(eps, eps, eps); for (size_t i = 0; i < faces.size(); ++ i) { const IndexedFaceType &face = faces[i]; const VertexType &v1 = vertices[face(0)]; const VertexType &v2 = vertices[face(1)]; const VertexType &v3 = vertices[face(2)]; InputType n; n.m_idx = i; n.m_centroid = (1./3.) * (v1 + v2 + v3); n.m_bbox = BoundingBox(v1, v1); n.m_bbox.extend(v2); n.m_bbox.extend(v3); n.m_bbox.min() -= veps; n.m_bbox.max() += veps; input.emplace_back(n); } TreeType out; out.build(std::move(input)); return out; } // Find a first intersection of a ray with indexed triangle set. // Intersection test is calculated with the accuracy of VectorType::Scalar // even if the triangle mesh and the AABB Tree are built with floats. template inline bool intersect_ray_first_hit( // Indexed triangle set - 3D vertices. const std::vector &vertices, // Indexed triangle set - triangular faces, references to vertices. const std::vector &faces, // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. const TreeType &tree, // Origin of the ray. const VectorType &origin, // Direction of the ray. const VectorType &dir, // First intersection of the ray with the indexed triangle set. igl::Hit &hit) { using Scalar = typename VectorType::Scalar; auto ray_intersector = detail::RayIntersector { vertices, faces, tree, origin, dir, VectorType(dir.cwiseInverse()) }; return ! tree.empty() && detail::intersect_ray_recursive_first_hit( ray_intersector, size_t(0), std::numeric_limits::infinity(), hit); } // Find all intersections of a ray with indexed triangle set. // Intersection test is calculated with the accuracy of VectorType::Scalar // even if the triangle mesh and the AABB Tree are built with floats. // The output hits are sorted by the ray parameter. // If the ray intersects a shared edge of two triangles, hits for both triangles are returned. template inline bool intersect_ray_all_hits( // Indexed triangle set - 3D vertices. const std::vector &vertices, // Indexed triangle set - triangular faces, references to vertices. const std::vector &faces, // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. const TreeType &tree, // Origin of the ray. const VectorType &origin, // Direction of the ray. const VectorType &dir, // All intersections of the ray with the indexed triangle set, sorted by parameter t. std::vector &hits) { auto ray_intersector = detail::RayIntersectorHits { vertices, faces, tree, origin, dir, VectorType(dir.cwiseInverse()) }; if (! tree.empty()) { ray_intersector.hits.reserve(8); detail::intersect_ray_recursive_all_hits(ray_intersector, 0); std::swap(hits, ray_intersector.hits); std::sort(hits.begin(), hits.end(), [](const auto &l, const auto &r) { return l.t < r.t; }); } return ! hits.empty(); } // Finding a closest triangle, its closest point and squared distance to the closest point // on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree. // Closest point to triangle test will be performed with the accuracy of VectorType::Scalar // even if the triangle mesh and the AABB Tree are built with floats. // Returns squared distance to the closest point or -1 if the input is empty. template inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set( // Indexed triangle set - 3D vertices. const std::vector &vertices, // Indexed triangle set - triangular faces, references to vertices. const std::vector &faces, // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. const TreeType &tree, // Point to which the closest point on the indexed triangle set is searched for. const VectorType &point, // Index of the closest triangle in faces. size_t &hit_idx_out, // Position of the closest point on the indexed triangle set. Eigen::PlainObjectBase &hit_point_out) { using Scalar = typename VectorType::Scalar; auto distancer = detail::IndexedTriangleSetDistancer { vertices, faces, tree, point }; return tree.empty() ? Scalar(-1) : detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits::infinity(), hit_idx_out, hit_point_out); } // Decides if exists some triangle in defined radius on a 3D indexed triangle set using a pre-built AABBTreeIndirect::Tree. // Closest point to triangle test will be performed with the accuracy of VectorType::Scalar // even if the triangle mesh and the AABB Tree are built with floats. // Returns true if exists some triangle in defined radius, false otherwise. template inline bool is_any_triangle_in_radius( // Indexed triangle set - 3D vertices. const std::vector &vertices, // Indexed triangle set - triangular faces, references to vertices. const std::vector &faces, // AABBTreeIndirect::Tree over vertices & faces, bounding boxes built with the accuracy of vertices. const TreeType &tree, // Point to which the closest point on the indexed triangle set is searched for. const VectorType &point, // Maximum distance in which triangle is search for typename VectorType::Scalar &max_distance) { using Scalar = typename VectorType::Scalar; auto distancer = detail::IndexedTriangleSetDistancer { vertices, faces, tree, point }; size_t hit_idx; VectorType hit_point = VectorType::Ones() * (std::nan("")); if(tree.empty()) { return false; } detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), max_distance, hit_idx, hit_point); return hit_point.allFinite(); } } // namespace AABBTreeIndirect } // namespace Slic3r #endif /* slic3r_AABBTreeIndirect_hpp_ */