WIP AABBIndirect: Documentation, polishing.

This commit is contained in:
Vojtech Bubnik 2020-05-21 11:04:53 +02:00
parent eeb9590d28
commit 2b8f655020
2 changed files with 139 additions and 77 deletions

View file

@ -1,4 +1,7 @@
// 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 <alecjacobson@gmail.com>
// while the implicit balanced tree representation and memory optimizations are Vojtech's.
#ifndef slic3r_AABBTreeIndirect_hpp_
#define slic3r_AABBTreeIndirect_hpp_
@ -11,22 +14,37 @@
extern "C"
{
// Ray-Triangle Intersection Test Routines by Tomas Moller, May 2000
#include <igl/raytri.c>
}
// Definition of the ray intersection hit structure.
#include <igl/Hit.h>
// Find intersection parameters of a ray with axis aligned bounding box.
#include <igl/ray_box_intersect.h>
namespace Slic3r {
namespace AABBTreeIndirect {
// AABB tree for raycasting and closest triangle search.
// 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<int ANumDimensions, typename ACoordType>
class Tree
{
public:
static constexpr int NumDimensions = ANumDimensions;
using CoordType = ACoordType;
using Vec3crd = Eigen::Matrix<CoordType, NumDimensions, 1, Eigen::DontAlign>;
using VectorType = Eigen::Matrix<CoordType, NumDimensions, 1, Eigen::DontAlign>;
using BoundingBox = Eigen::AlignedBox<CoordType, NumDimensions>;
// Following could be static constexpr size_t, but that would not link in C++11
enum : size_t {
@ -36,10 +54,13 @@ public:
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.
// 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; }
@ -57,11 +78,13 @@ public:
// SourceNode shall implement
// size_t SourceNode::idx() const
// - index to the outside triangle.
// const Vec3crd& SourceNode::centroid() const
// - centroid of this node, for splitting the triangles into left / right bounding box.
// - 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.
// - 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<typename SourceNode>
void build(std::vector<SourceNode> &&input)
{
@ -69,8 +92,7 @@ public:
clear();
else {
// Allocate enough memory for a full binary tree.
//FIXME fianlize the tree size formula.
m_nodes.assign(next_highest_power_of_2(input.size() * 2 + 1), Node());
m_nodes.assign(next_highest_power_of_2(input.size()) * 2 - 1, Node());
build_recursive(input, 0, 0, input.size() - 1);
}
input.clear();
@ -80,6 +102,12 @@ public:
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<typename SourceNode>
void build(const std::vector<SourceNode> &input)
{
@ -111,7 +139,7 @@ private:
// 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 a node into the tree.
// 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);
@ -178,53 +206,10 @@ private:
}
}
// The balanced tree storage.
std::vector<Node> m_nodes;
};
template<typename VertexType, typename IndexedFaceType>
inline Tree<3, typename VertexType::Scalar>
build_aabb_tree(const std::vector<VertexType> &vertices, const std::vector<IndexedFaceType> &faces)
{
using TreeType = Tree<3, typename VertexType::Scalar>;
using CoordType = typename TreeType::CoordType;
using Vec3crd = typename TreeType::Vec3crd;
using BoundingBox = typename TreeType::BoundingBox;
static constexpr CoordType eps = CoordType(1e-4);
struct InputType {
size_t idx() const { return m_idx; }
const BoundingBox& bbox() const { return m_bbox; }
const Vec3crd& centroid() const { return m_centroid; }
size_t m_idx;
BoundingBox m_bbox;
Vec3crd m_centroid;
};
std::vector<InputType> input;
input.reserve(faces.size());
Vec3crd 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;
}
namespace detail {
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
struct RayIntersector {
@ -253,14 +238,9 @@ namespace detail {
Scalar min_t,
igl::Hit &hit)
{
const auto &nodes = ray_intersector.tree.nodes();
if (node_idx >= nodes.size())
return false;
const auto &node = nodes[node_idx];
if (! node.is_valid())
return false;
const auto &node = ray_intersector.tree.node(node_idx);
assert(node.is_valid());
{
Scalar t_start, t_end;
if (! igl::ray_box_intersect(ray_intersector.origin, ray_intersector.dir, node.bbox.template cast<Scalar>(), Scalar(0), min_t, t_start, t_end))
@ -268,7 +248,7 @@ namespace detail {
}
if (node.is_leaf()) {
using Vector = Eigen::Matrix<Scalar, 3, 1>;
using Vector = Eigen::Matrix<double, 3, 1>;
Vector origin_d = ray_intersector.origin.template cast<double>();
Vector dir_d = ray_intersector.dir .template cast<double>();
auto face = ray_intersector.faces[node.idx];
@ -306,12 +286,10 @@ namespace detail {
template<typename RayIntersectorType>
static inline void intersect_ray_recursive_all_hits(RayIntersectorType &ray_intersector, size_t node_idx)
{
using Vector = typename RayIntersectorType::VectorType;
using Scalar = typename Vector::Scalar;
using Scalar = typename RayIntersectorType::VectorType::Scalar;
const auto &node = ray_intersector.tree.node(node_idx);
if (! node.is_valid())
return;
assert(node.is_valid());
{
Scalar t_start, t_end;
@ -321,7 +299,7 @@ namespace detail {
}
if (node.is_leaf()) {
using Vector = Eigen::Matrix<Scalar, 3, 1>;
using Vector = Eigen::Matrix<double, 3, 1>;
Vector origin_d = ray_intersector.origin.template cast<double>();
Vector dir_d = ray_intersector.dir .template cast<double>();
auto face = ray_intersector.faces[node.idx];
@ -342,6 +320,7 @@ namespace detail {
intersect_ray_recursive_all_hits(ray_intersector, right);
}
// Nothing to do with COVID-19 social distancing.
template<typename AVertexType, typename AIndexedFaceType, typename ATreeType, typename AVectorType>
struct IndexedTriangleSetDistancer {
using VertexType = AVertexType;
@ -407,7 +386,7 @@ namespace detail {
};
template<typename IndexedTriangleSetDistancerType, typename Scalar>
static inline Scalar squared_distance_recursive(
static inline Scalar squared_distance_to_indexed_triangle_set_recursive(
IndexedTriangleSetDistancerType &distancer,
size_t node_idx,
Scalar low_sqr_d,
@ -420,6 +399,7 @@ namespace detail {
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;
@ -455,7 +435,7 @@ namespace detail {
{
size_t i_left;
Vector c_left = c;
Scalar sqr_d_left = squared_distance_recursive(distancer, left_node_idx, low_sqr_d, up_sqr_d, i_left, c_left);
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;
};
@ -463,7 +443,7 @@ namespace detail {
{
size_t i_right;
Vector c_right = c;
Scalar sqr_d_right = squared_distance_recursive(distancer, right_node_idx, low_sqr_d, up_sqr_d, i_right, c_right);
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;
};
@ -494,13 +474,73 @@ namespace detail {
} // 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<typename VertexType, typename IndexedFaceType>
inline Tree<3, typename VertexType::Scalar> build_aabb_tree_over_indexed_triangle_set(
// Indexed triangle set - 3D vertices.
const std::vector<VertexType> &vertices,
// Indexed triangle set - triangular faces, references to vertices.
const std::vector<IndexedFaceType> &faces)
{
using TreeType = Tree<3, typename VertexType::Scalar>;
using CoordType = typename TreeType::CoordType;
using VectorType = typename TreeType::VectorType;
using BoundingBox = typename TreeType::BoundingBox;
static constexpr CoordType eps = CoordType(1e-4);
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<InputType> input;
input.reserve(faces.size());
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<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
inline bool intersect_ray_first_hit(
// Indexed triangle set - 3D vertices.
const std::vector<VertexType> &vertices,
// Indexed triangle set - triangular faces, references to vertices.
const std::vector<IndexedFaceType> &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;
@ -512,13 +552,24 @@ inline bool intersect_ray_first_hit(
ray_intersector, size_t(0), std::numeric_limits<Scalar>::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<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
inline bool intersect_ray_all_hits(
// Indexed triangle set - 3D vertices.
const std::vector<VertexType> &vertices,
// Indexed triangle set - triangular faces, references to vertices.
const std::vector<IndexedFaceType> &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<igl::Hit> &hits)
{
auto ray_intersector = detail::RayIntersectorHits<VertexType, IndexedFaceType, TreeType, VectorType> {
@ -534,20 +585,31 @@ inline bool intersect_ray_all_hits(
return ! hits.empty();
}
// Closest point to triangle test will be performed with the accuracy of VectorType::Scalar.
// 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<typename VertexType, typename IndexedFaceType, typename TreeType, typename VectorType>
inline typename VectorType::Scalar squared_distance(
inline typename VectorType::Scalar squared_distance_to_indexed_triangle_set(
// Indexed triangle set - 3D vertices.
const std::vector<VertexType> &vertices,
// Indexed triangle set - triangular faces, references to vertices.
const std::vector<IndexedFaceType> &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<VectorType> &hit_point_out)
{
using Scalar = typename VectorType::Scalar;
auto distancer = detail::IndexedTriangleSetDistancer<VertexType, IndexedFaceType, TreeType, VectorType>
{ vertices, faces, tree, point };
return detail::squared_distance_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits<Scalar>::infinity(), hit_idx_out, hit_point_out);
return tree.empty() ? Scalar(-1) :
detail::squared_distance_to_indexed_triangle_set_recursive(distancer, size_t(0), Scalar(0), std::numeric_limits<Scalar>::infinity(), hit_idx_out, hit_point_out);
}
} // namespace AABBTreeIndirect

View file

@ -11,7 +11,7 @@ TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndi
TriangleMesh tmesh = make_cube(1., 1., 1.);
tmesh.repair();
auto tree = AABBTreeIndirect::build_aabb_tree(tmesh.its.vertices, tmesh.its.indices);
auto tree = AABBTreeIndirect::build_aabb_tree_over_indexed_triangle_set(tmesh.its.vertices, tmesh.its.indices);
REQUIRE(! tree.empty());
igl::Hit hit;
@ -39,7 +39,7 @@ TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndi
size_t hit_idx;
Vec3d closest_point;
double squared_distance = AABBTreeIndirect::squared_distance(
double squared_distance = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
tmesh.its.vertices, tmesh.its.indices,
tree,
Vec3d(0.3, 0.5, -5.),
@ -49,7 +49,7 @@ TEST_CASE("Building a tree over a box, ray caster and closest query", "[AABBIndi
REQUIRE(closest_point.y() == Approx(0.5));
REQUIRE(closest_point.z() == Approx(0.));
squared_distance = AABBTreeIndirect::squared_distance(
squared_distance = AABBTreeIndirect::squared_distance_to_indexed_triangle_set(
tmesh.its.vertices, tmesh.its.indices,
tree,
Vec3d(0.3, 0.5, 5.),