Eliminate heap allocated `Data *m_data`

This required changing return types to `Node *` in many cases. All unit tests have been updated to account for the new interface.
This commit is contained in:
JacksonCampolattaro 2023-03-23 09:58:20 +01:00
parent 6e901604cb
commit 4f6d249c1f
10 changed files with 386 additions and 400 deletions

View File

@ -35,9 +35,11 @@ namespace CGAL {
\tparam PointRange_ must be a model of range whose value type is the key type of `PointMap`
\tparam PointMap must be a model of `ReadablePropertyMap` whose value type is `GeomTraits::Point_3`
*/
template <typename GeomTraits, typename PointRange,
typename PointMap = Identity_property_map
<typename std::iterator_traits<typename PointRange::iterator>::value_type> >
template <
typename GeomTraits,
typename PointRange,
typename PointMap = Identity_property_map<typename std::iterator_traits<typename PointRange::iterator>::value_type>
>
#ifdef DOXYGEN_RUNNING
class Octree;
#else

View File

@ -40,7 +40,8 @@
#include <vector>
#include <math.h>
namespace CGAL {
namespace CGAL
{
/*!
\ingroup PkgOrthtreeClasses
@ -123,7 +124,7 @@ public:
#ifdef DOXYGEN_RUNNING
typedef unspecified_type Node_range;
#else
typedef boost::iterator_range<Traversal_iterator<Node> > Node_range;
typedef boost::iterator_range<Traversal_iterator<const Node>> Node_range;
#endif
/// \cond SKIP_IN_MANUAL
@ -131,7 +132,7 @@ public:
/*!
* \brief A function that determines the next node in a traversal given the current one.
*/
typedef std::function<Node(Node)> Node_traversal_method_const;
typedef std::function<const Node *(const Node *)> Node_traversal_method_const;
/// \endcond
@ -196,7 +197,7 @@ public:
: m_traits(traits)
, m_range(point_range)
, m_point_map(point_map)
, m_root(Node(), 0)
, m_root() // todo: can this be default-constructed?
{
Array bbox_min;
Array bbox_max;
@ -260,8 +261,7 @@ public:
, m_point_map(other.m_point_map)
, m_root(other.m_root.deep_copy())
, m_bbox_min(other.m_bbox_min)
, m_side_per_depth(other.m_side_per_depth)
{ }
, m_side_per_depth(other.m_side_per_depth) {}
// move constructor
Orthtree(Orthtree&& other)
@ -270,31 +270,16 @@ public:
, m_point_map(other.m_point_map)
, m_root(other.m_root)
, m_bbox_min(other.m_bbox_min)
, m_side_per_depth(other.m_side_per_depth)
{
other.m_root = Node(Node(), 0);
, m_side_per_depth(other.m_side_per_depth) {
other.m_root = Node{};
}
// Non-necessary but just to be clear on the rule of 5:
// assignment operators deleted (PointRange is a ref)
Orthtree& operator=(const Orthtree& other) = delete;
Orthtree& operator=(Orthtree&& other) = delete;
// Destructor
~Orthtree()
{
std::queue<Node> nodes;
nodes.push(m_root);
while (!nodes.empty())
{
Node node = nodes.front();
nodes.pop();
if (!node.is_leaf())
for (std::size_t i = 0; i < Degree::value; ++ i)
nodes.push (node[i]);
node.free();
}
}
// move constructor
/// \endcond
@ -320,52 +305,39 @@ public:
void refine(const Split_predicate& split_predicate) {
// If the tree has already been refined, reset it
if (!m_root.is_leaf()){
std::queue<Node> nodes;
for (std::size_t i = 0; i < Degree::value; ++ i)
nodes.push (m_root[i]);
while (!nodes.empty())
{
Node node = nodes.front();
nodes.pop();
if (!node.is_leaf())
for (std::size_t i = 0; i < Degree::value; ++ i)
nodes.push (node[i]);
node.free();
}
if (!m_root.is_leaf())
m_root.unsplit();
}
// Reset the side length map, too
m_side_per_depth.resize(1);
// Initialize a queue of nodes that need to be refined
std::queue<Node> todo;
todo.push(m_root);
std::queue<Node *> todo;
todo.push(&m_root);
// Process items in the queue until it's consumed fully
while (!todo.empty()) {
// Get the next element
Node current = todo.front();
auto current = todo.front();
todo.pop();
// Check if this node needs to be processed
if (split_predicate(current)) {
if (split_predicate(*current)) {
// Check if we've reached a new max depth
if (current.depth() == depth()) {
if (current->depth() == depth()) {
// Update the side length map
m_side_per_depth.push_back(*(m_side_per_depth.end() - 1) / 2);
}
// Split the node, redistributing its points to its children
split(current);
split((*current));
// Process each of its children
for (int i = 0; i < Degree::value; ++i)
todo.push(current[i]);
todo.push(&(*current)[i]);
}
}
@ -401,52 +373,52 @@ public:
void grade() {
// Collect all the leaf nodes
std::queue<Node> leaf_nodes;
for (Node leaf : traverse(Orthtrees::Leaves_traversal())) {
std::queue<Node *> leaf_nodes;
for (const Node &leaf : traverse(Orthtrees::Leaves_traversal())) {
// TODO: I'd like to find a better (safer) way of doing this
leaf_nodes.push(leaf);
leaf_nodes.push(const_cast<Node *>(&leaf));
}
// Iterate over the nodes
while (!leaf_nodes.empty()) {
// Get the next node
Node node = leaf_nodes.front();
Node *node = leaf_nodes.front();
leaf_nodes.pop();
// Skip this node if it isn't a leaf anymore
if (!node.is_leaf())
if (!node->is_leaf())
continue;
// Iterate over each of the neighbors
for (int direction = 0; direction < 6; ++direction) {
// Get the neighbor
Node neighbor = node.adjacent_node(direction);
auto *neighbor = node->adjacent_node(direction);
// If it doesn't exist, skip it
if (neighbor.is_null())
if (!neighbor)
continue;
// Skip if this neighbor is a direct sibling (it's guaranteed to be the same depth)
// TODO: This check might be redundant, if it doesn't affect performance maybe I could remove it
if (neighbor.parent() == node.parent())
if (neighbor->parent() == node->parent())
continue;
// If it's already been split, skip it
if (!neighbor.is_leaf())
if (!neighbor->is_leaf())
continue;
// Check if the neighbor breaks our grading rule
// TODO: could the rule be parametrized?
if ((node.depth() - neighbor.depth()) > 1) {
if ((node->depth() - neighbor->depth()) > 1) {
// Split the neighbor
split(neighbor);
split(*neighbor);
// Add newly created children to the queue
for (int i = 0; i < Degree::value; ++i) {
leaf_nodes.push(neighbor[i]);
leaf_nodes.push(&(*neighbor)[i]);
}
}
}
@ -459,9 +431,22 @@ public:
/// @{
/*!
\brief returns the root node.
\brief provides read-only access to the root node, and by
extension the rest of the tree.
\return a const reference to the root node of the tree.
*/
Node root() const { return m_root; }
const Node &root() const { return m_root; }
/*!
\brief provides read-write access to the root node, and by
extension the rest of the tree.
todo: why wasn't this provided previously?
\return a reference to the root node of the tree.
*/
Node &root() { return m_root; }
/*!
\brief Convenience function to access the child nodes of the root
@ -472,9 +457,9 @@ public:
\sa `Node::operator[]()`
\param index the index of the child node.
\return the accessed node.
\return a reference to the node.
*/
Node operator[](std::size_t index) const { return m_root[index]; }
const Node &operator[](std::size_t index) const { return m_root[index]; }
/*!
\brief returns the deepest level reached by a leaf node in this tree (root being level 0).
@ -497,13 +482,13 @@ public:
template<typename Traversal>
Node_range traverse(const Traversal &traversal = Traversal()) const {
Node first = traversal.first(m_root);
const Node *first = traversal.first(&m_root);
Node_traversal_method_const next
= [&](const Node& n) -> Node { return traversal.next(n); };
= [&](const Node* n) -> const Node * { return traversal.next(n); };
return boost::make_iterator_range(Traversal_iterator<Node>(first, next),
Traversal_iterator<Node>());
return boost::make_iterator_range(Traversal_iterator<const Node>(first, next),
Traversal_iterator<const Node>());
}
/*!
@ -547,19 +532,19 @@ public:
\param point query point.
\return the node which contains the point.
*/
Node locate(const Point &point) const {
const Node& locate(const Point &point) const {
// Make sure the point is enclosed by the orthtree
CGAL_precondition (CGAL::do_intersect(point, bbox(m_root)));
// Start at the root node
auto node_for_point = m_root;
auto *node_for_point = &m_root;
// Descend the tree until reaching a leaf node
while (!node_for_point.is_leaf()) {
while (!node_for_point->is_leaf()) {
// Find the point to split around
Point center = barycenter(node_for_point);
Point center = barycenter(*node_for_point);
// Find the index of the correct sub-node
typename Node::Local_coordinates index;
@ -568,11 +553,11 @@ public:
index[dimension ++] = (get<0>(r) < get<1>(r));
// Find the correct sub-node of the current node
node_for_point = node_for_point[index.to_ulong()];
node_for_point = &(*node_for_point)[index.to_ulong()];
}
// Return the result
return node_for_point;
return *node_for_point;
}
/*!
@ -582,9 +567,9 @@ public:
`query`.
\tparam OutputIterator a model of `OutputIterator` that accept `Point_d` objects.
\param query a query point.
\param k the number of neighbors.
\param output the output iterator.
\param query query point.
\param k number of neighbors.
\param output output iterator.
*/
template <typename OutputIterator>
OutputIterator nearest_neighbors(const Point& query,
@ -703,7 +688,8 @@ private: // functions :
// Split the point collection around the center point on this dimension
Range_iterator split_point = std::partition
(begin, end,
[&](const Range_type &a) -> bool {
[&](const Range_type& a) -> bool
{
// This should be done with cartesian iterator but it seems
// complicated to do efficiently
return (get(m_point_map, a)[int(dimension)] < center[int(dimension)]);
@ -755,14 +741,15 @@ private: // functions :
Point point;
FT distance;
};
struct Node_index_with_distance {
struct Node_index_with_distance
{
typename Node::Local_coordinates index;
FT distance;
Node_index_with_distance(const typename Node::Local_coordinates& index,
const FT& distance)
: index(index), distance(distance)
{ }
: index(index), distance(distance) {}
};
void nearest_k_neighbors_recursive(Sphere &search_bounds, const Node& node,
@ -820,7 +807,7 @@ private: // functions :
// Fill the list with child nodes
for (int index = 0; index < Degree::value; ++index) {
Node child_node = node[index];
auto &child_node = node[index];
// Add a child to the list, with its distance
children_with_distances.emplace_back(typename Node::Local_coordinates(index),
@ -834,7 +821,7 @@ private: // functions :
// Loop over the children
for (auto child_with_distance : children_with_distances) {
Node child_node = node[child_with_distance.index.to_ulong()];
auto &child_node = node[child_with_distance.index.to_ulong()];
// Check whether the bounding box of the child intersects with the search bounds
if (do_intersect(child_node, search_bounds)) {
@ -853,9 +840,9 @@ private: // functions :
// Check if the current node intersects with the query
if (CGAL::do_intersect(query, bbox(node))) {
// if this node is a leaf, than it's considered an intersecting node
// if this node is a leaf, then it's considered an intersecting node
if (node.is_leaf()) {
*output++ = node;
*output++ = &node;
return output;
}
@ -907,8 +894,7 @@ private: // functions :
public:
/// \cond SKIP_IN_MANUAL
void dump_to_polylines (std::ostream& os) const
{
void dump_to_polylines(std::ostream& os) const {
for (const Node& n: traverse<Orthtrees::Preorder_traversal>())
if (n.is_leaf())
{
@ -917,8 +903,7 @@ public:
}
}
void dump_box_to_polylines (const Bbox_2& box, std::ostream& os) const
{
void dump_box_to_polylines(const Bbox_2& box, std::ostream& os) const {
// dump in 3D for visualisation
os << "5 "
<< box.xmin() << " " << box.ymin() << " 0 "
@ -927,8 +912,8 @@ public:
<< box.xmax() << " " << box.ymin() << " 0 "
<< box.xmin() << " " << box.ymin() << " 0" << std::endl;
}
void dump_box_to_polylines (const Bbox_3& box, std::ostream& os) const
{
void dump_box_to_polylines(const Bbox_3& box, std::ostream& os) const {
// Back face
os << "5 "
<< box.xmin() << " " << box.ymin() << " " << box.zmin() << " "
@ -960,12 +945,12 @@ public:
<< box.xmax() << " " << box.ymax() << " " << box.zmax() << std::endl;
}
friend std::ostream& operator<< (std::ostream& os, const Self& orthtree)
{
friend std::ostream& operator<<(std::ostream& os, const Self& orthtree) {
// Create a range of nodes
auto nodes = orthtree.traverse(Orthtrees::Preorder_traversal());
// Iterate over the range
for (auto &n : nodes) {
for (auto& n: nodes)
{
// Show the depth
for (int i = 0; i < n.depth(); ++i)
os << ". ";

View File

@ -27,42 +27,20 @@
namespace CGAL {
/// \cond SKIP_IN_MANUAL
namespace Orthtrees
{
namespace Orthtrees {
// Non-documented, or testing purpose only
struct Node_access
{
struct Node_access {
template <typename Node, typename LC>
static Node create_node (Node parent, LC local_coordinates)
{
static Node create_node(Node* parent, LC local_coordinates) {
return Node(parent, local_coordinates);
}
template <typename Node>
static typename Node::Point_range& points(Node node) { return node.points(); }
static typename Node::Point_range& points(Node& node) { return node.points(); }
template <typename Node>
static void split(Node node) { return node.split(); }
template <typename Node>
static void free(Node node)
{
typedef Dimension_tag<(2 << (Node::Dimension::value - 1))> Degree;
std::queue<Node> nodes;
nodes.push(node);
while (!nodes.empty())
{
Node node = nodes.front();
nodes.pop();
if (!node.is_leaf()){
for (std::size_t i = 0; i < Degree::value; ++ i){
nodes.push (node[i]);
}
}
node.free();
}
}
static void split(Node& node) { return node.split(); }
};
@ -80,8 +58,7 @@ struct Node_access
\cgalModels `ConstRange`
*/
template <typename Traits, typename PointRange, typename PointMap>
class Orthtree<Traits, PointRange, PointMap>::Node
{
class Orthtree<Traits, PointRange, PointMap>::Node {
public:
@ -144,20 +121,25 @@ private:
typedef boost::iterator_range<iterator> Point_range;
/// \endcond
// make Node trivially copiabled
struct Data
{
Point_range points;
Self parent;
std::uint8_t depth;
Global_coordinates global_coordinates;
std::unique_ptr<Children> children;
// make Node trivially copiable
// struct Data
// {
// Point_range points;
// Self parent;
// std::uint8_t depth;
// Global_coordinates global_coordinates;
// std::unique_ptr<Children> children;
//
// Data (Self parent)
// : parent (parent), depth (0) { }
// };
// Data* m_data;
Data (Self parent)
: parent (parent), depth (0) { }
};
Data* m_data;
Point_range m_points;
Self* m_parent; // todo: use optional<reference_wrapper<Self>> instead of Self *
std::uint8_t m_depth;
Global_coordinates m_global_coordinates;
std::shared_ptr<Children> m_children;
/// \cond SKIP_IN_MANUAL
@ -173,8 +155,9 @@ private:
* \brief Access to the content held by this node
* \return a reference to the collection of point indices
*/
Point_range &points() { return m_data->points; }
const Point_range &points() const { return m_data->points; }
Point_range& points() { return m_points; }
const Point_range& points() const { return m_points; }
/// \name Construction
/// @{
@ -194,42 +177,38 @@ private:
\param parent the node containing this one
\param index this node's relationship to its parent
*/
explicit Node(Self parent, Local_coordinates local_coordinates)
: m_data (new Data(parent)) {
explicit Node(Self* parent, Local_coordinates local_coordinates)
: m_parent(parent) {
if (!parent.is_null()) {
m_data->depth = parent.m_data->depth + 1;
if (parent != nullptr) {
m_depth = parent->m_depth + 1;
for (int i = 0; i < Dimension::value; i++)
m_data->global_coordinates[i] = (2 * parent.m_data->global_coordinates[i]) + local_coordinates[i];
m_global_coordinates[i] = (2 * parent->m_global_coordinates[i]) + local_coordinates[i];
} else {
m_depth = 0;
}
else
for (int i = 0; i < Dimension::value; i++)
m_data->global_coordinates[i] = 0;
m_global_coordinates[i] = 0;
}
}
void free() { delete m_data; }
Node deep_copy(Self parent = Node()) const
{
if (is_null())
return Node();
Node deep_copy(Self parent = Node()) const {
Node out;
out.m_data = new Data(parent);
out.m_data->points = m_data->points;
out.m_data->depth = m_data->depth;
out.m_data->global_coordinates = m_data->global_coordinates;
std::unique_ptr<Children> children;
if (!is_leaf())
{
out.m_data->children = std::make_unique<Children>();
out.m_parent = m_parent;
out.m_points = m_points;
out.m_depth = m_depth;
out.m_global_coordinates = m_global_coordinates;
if (!is_leaf()) {
out.m_children = std::make_shared<Children>();
for (int index = 0; index < Degree::value; index++)
(*out.m_data->children)[index] = (*this)[index].deep_copy(out);
(*out.m_children)[index] = (*m_children)[index].deep_copy(out);
}
return out;
}
@ -251,10 +230,10 @@ private:
CGAL_precondition (is_leaf());
m_data->children = std::make_unique<Children>();
m_children = std::make_shared<Children>();
for (int index = 0; index < Degree::value; index++) {
(*m_data->children)[index] = std::move(Self(*this, {Local_coordinates(index)}));
(*m_children)[index] = std::move(Self(this, {Local_coordinates(index)}));
}
}
@ -267,7 +246,7 @@ private:
*/
void unsplit() {
m_data->children.reset();
m_children.reset();
}
/// @}
@ -279,10 +258,12 @@ public:
/// \cond SKIP_IN_MANUAL
// Default creates null node
Node() : m_data(nullptr) { }
// todo: default node is no longer null, but still isn't guaranteed to be valid
Node() = default;
// Comparison operator
bool operator< (const Node& other) const { return m_data < other.m_data; }
// todo: where is this used
//bool operator<(const Node& other) const { return m_data < other.m_data; }
/// \endcond
/// \name Type & Location
@ -291,36 +272,30 @@ public:
/*!
\brief returns `true` if the node is null, `false` otherwise.
*/
bool is_null() const { return (m_data == nullptr); }
//bool is_null() const { return (m_data == nullptr); }
/*!
\brief returns `true` if the node has no parent, `false` otherwise.
\pre `!is_null()`
*/
bool is_root() const
{
CGAL_precondition(!is_null());
return m_data->parent.is_null();
bool is_root() const {
return m_parent == nullptr;
}
/*!
\brief returns `true` if the node has no children, `false` otherwise.
\pre `!is_null()`
*/
bool is_leaf() const
{
CGAL_precondition(!is_null());
return (!m_data->children);
bool is_leaf() const {
return (!m_children);
}
/*!
\brief returns this node's depth.
\pre `!is_null()`
*/
std::uint8_t depth() const
{
CGAL_precondition (!is_null());
return m_data->depth;
std::uint8_t depth() const {
return m_depth;
}
/*!
@ -329,9 +304,6 @@ public:
*/
Local_coordinates local_coordinates() const {
CGAL_precondition (!is_null());
// TODO: There must be a better way of doing this!
Local_coordinates result;
for (std::size_t i = 0; i < Dimension::value; ++i)
@ -344,10 +316,8 @@ public:
\brief returns this node's global coordinates.
\pre `!is_null()`
*/
Global_coordinates global_coordinates() const
{
CGAL_precondition (!is_null());
return m_data->global_coordinates;
Global_coordinates global_coordinates() const {
return m_global_coordinates;
}
@ -356,18 +326,16 @@ public:
/*!
\brief returns this node's parent.
\pre `!is_null()`
\pre `!is_root()`
*/
Self parent() const
{
CGAL_precondition (!is_null());
return m_data->parent;
const Self* parent() const {
CGAL_precondition (!is_root());
return m_parent;
}
/*!
\brief returns the nth child of this node.
\pre `!is_null()`
\pre `!is_leaf()`
\pre `0 <= index && index < Degree::value`
@ -418,13 +386,29 @@ public:
The operator can be chained. For example, `n[5][2][3]` returns the
third child of the second child of the fifth child of a node `n`.
*/
Self operator[](std::size_t index) const {
Self& operator[](std::size_t index) {
CGAL_precondition (!is_null());
CGAL_precondition (!is_leaf());
CGAL_precondition (index < Degree::value);
return (*m_data->children)[index];
return (*m_children)[index];
}
/*!
\brief returns the nth child of this node.
\pre `!is_leaf()`
\pre `index < Degree::value`
The operator can be chained. For example, `n[5][2][3]` returns the
third child of the second child of the fifth child of a node `n`.
*/
const Self& operator[](std::size_t index) const {
CGAL_precondition (!is_leaf());
CGAL_precondition (index < Degree::value);
return (*m_children)[index];
}
/*!
@ -478,9 +462,7 @@ public:
\return the adjacent node if it exists, a null node otherwise.
*/
Self adjacent_node (Local_coordinates direction) const
{
CGAL_precondition(!is_null());
const Self* adjacent_node(Local_coordinates direction) const {
// Direction: LEFT RIGHT DOWN UP BACK FRONT
// direction: 000 001 010 011 100 101
@ -489,8 +471,7 @@ public:
CGAL_precondition(direction.to_ulong() < Dimension::value * 2);
// The root node has no adjacent nodes!
if (is_root())
return Self();
if (is_root()) return nullptr;
// The least significant bit indicates the sign (which side of the node)
bool sign = direction[0];
@ -506,24 +487,22 @@ public:
// Check if this child has the opposite sign along the direction's axis
if (local_coordinates()[dimension] != sign) {
// This means the adjacent node is a direct sibling, the offset can be applied easily!
return parent()[local_coordinates().to_ulong() + offset];
return &(*parent())[local_coordinates().to_ulong() + offset];
}
// Find the parent's neighbor in that direction if it exists
Self adjacent_node_of_parent = parent().adjacent_node(direction);
// Find the parent's neighbor in that direction, if it exists
const Self* adjacent_node_of_parent = parent()->adjacent_node(direction);
// If the parent has no neighbor, then this node doesn't have one
if (adjacent_node_of_parent.is_null())
return Node();
if (!adjacent_node_of_parent) return nullptr;
// If the parent's adjacent node has no children, then it's this node's adjacent node
if (adjacent_node_of_parent.is_leaf())
if (adjacent_node_of_parent->is_leaf())
return adjacent_node_of_parent;
// Return the nearest node of the parent by subtracting the offset instead of adding
return adjacent_node_of_parent[local_coordinates().to_ulong() - offset];
return &(*adjacent_node_of_parent)[local_coordinates().to_ulong() - offset];
}
@ -531,7 +510,21 @@ public:
\brief equivalent to `adjacent_node()`, with an adjacency direction
rather than a bitset.
*/
Self adjacent_node(Adjacency adjacency) const {
const Self* adjacent_node(Adjacency adjacency) const {
return adjacent_node(std::bitset<Dimension::value>(static_cast<int>(adjacency)));
}
/*!
* \brief equivalent to adjacent_node, except non-const
*/
Self* adjacent_node(std::bitset<Dimension::value> direction) {
return const_cast<Self*>(const_cast<const Self*>(this)->adjacent_node(direction));
}
/*!
* \brief equivalent to adjacent_node, with a Direction rather than a bitset and non-const
*/
Self* adjacent_node(Adjacency adjacency) {
return adjacent_node(std::bitset<Dimension::value>(static_cast<int>(adjacency)));
}
@ -544,27 +537,27 @@ public:
\brief checks whether the node is empty of points or not.
*/
bool empty() const {
return m_data->points.empty();
return m_points.empty();
}
/*!
\brief returns the number of points of this node.
*/
std::size_t size() const {
return std::size_t(std::distance(m_data->points.begin(), m_data->points.end()));
return std::size_t(std::distance(m_points.begin(), m_points.end()));
}
/*!
\brief returns the iterator at the start of the collection of
points held by this node.
*/
const_iterator begin() const { return m_data->points.begin(); }
const_iterator begin() const { return m_points.begin(); }
/*!
\brief returns the iterator at the end of the collection of
points held by this node.
*/
const_iterator end() const { return m_data->points.end(); }
const_iterator end() const { return m_points.end(); }
/// @}
@ -575,18 +568,23 @@ public:
/*!
* \brief compares the topology of this node to another node.
*
* \todo
* \todo This seems out of date, the implementation I see compares for direct equality
*
* \param rhs node to compare with
* \return whether the nodes have different topology.
*/
bool operator==(const Self& rhs) const {
return m_data == rhs.m_data;
// todo: This is a trivial implementation, maybe it can be set to =default in c++17?
return rhs.m_parent == m_parent &&
rhs.m_children == m_children &&
rhs.m_points == m_points &&
rhs.m_depth == m_depth &&
rhs.m_global_coordinates == m_global_coordinates;
}
static bool is_topology_equal (const Self& a, const Self& b)
{
CGAL_assertion (!a.is_null() && !b.is_null());
// todo: this does what the documentation for operator== claims to do!
static bool is_topology_equal(const Self& a, const Self& b) {
// If one node is a leaf, and the other isn't, they're not the same
if (a.is_leaf() != b.is_leaf())
@ -607,8 +605,7 @@ public:
return (a.global_coordinates() == b.global_coordinates());
}
friend std::ostream& operator<< (std::ostream& os, const Self& node)
{
friend std::ostream& operator<<(std::ostream& os, const Self& node) {
return internal::print_orthtree_node(os, node);
}
/// \endcond

View File

@ -44,7 +44,7 @@ public:
*
* \todo
*/
typedef std::function<Value(Value)> Traversal_function;
typedef std::function<Value *(Value *)> Traversal_function;
/// @}
@ -58,7 +58,7 @@ public:
*
* \todo
*/
Traversal_iterator() : m_value(), m_next() {}
Traversal_iterator() : m_value(nullptr), m_next() {}
/*!
* \brief
@ -68,7 +68,7 @@ public:
* \param first
* \param next
*/
Traversal_iterator(Value first, const Traversal_function &next) : m_value(first), m_next(next) {}
Traversal_iterator(Value *first, const Traversal_function &next) : m_value(first), m_next(next) {}
/// @}
@ -84,12 +84,12 @@ private:
}
Value &dereference() const {
return const_cast<Value&>(m_value);
return *m_value;
}
private:
Value m_value;
Value *m_value;
Traversal_function m_next;
};
}

View File

@ -33,86 +33,85 @@ namespace Orthtrees {
/// \cond SKIP_IN_MANUAL
template <typename Node>
Node next_sibling(Node n) {
const Node* next_sibling(const Node* n) {
// Passing null returns the first node
if (n.is_null())
return Node();
if (nullptr == n)
return nullptr;
// If this node has no parent, it has no siblings
if (n.parent().is_null())
return Node();
if (n->is_root())
return nullptr;
// Find out which child this is
std::size_t index = n.local_coordinates().to_ulong();
std::size_t index = n->local_coordinates().to_ulong();
constexpr static int degree = Node::Degree::value;
// Return null if this is the last child
if (int(index) == degree - 1)
return Node();
return nullptr;
// Otherwise, return the next child
return n.parent()[index + 1];
return &((*n->parent())[index + 1]);
}
template <typename Node>
Node next_sibling_up(Node n) {
const Node* next_sibling_up(const Node* n) {
if (n.is_null())
return Node();
if (!n || n->is_root()) return nullptr;
Node up = n.parent();
auto up = n->parent();
while (nullptr != up) {
while (!up.is_null()) {
if (!next_sibling(up).is_null())
if (nullptr != next_sibling(up))
return next_sibling(up);
up = up.parent();
if (up->is_root()) return nullptr;
up = up->parent();
}
return Node();
return nullptr;
}
template <typename Node>
Node deepest_first_child(Node n) {
const Node* deepest_first_child(const Node* n) {
if (n.is_null())
return Node();
if (!n)
return nullptr;
// Find the deepest child on the left
Node first = n;
while (!first.is_leaf())
first = first[0];
auto first = n;
while (!first->is_leaf())
first = &(*first)[0];
return first;
}
template <typename Node>
Node first_child_at_depth(Node n, std::size_t depth) {
const Node& first_child_at_depth(const Node* n, std::size_t depth) {
if (n.is_null())
return Node();
if (!n)
return nullptr;
std::stack<Node> todo;
std::stack<const Node*> todo;
todo.push(n);
if (n.depth() == depth)
if (n->depth() == depth)
return n;
while (!todo.empty())
{
Node node = todo.top();
while (!todo.empty()) {
const Node* node = todo.top();
todo.pop();
if (node.depth() == depth)
if (node->depth() == depth)
return node;
if (!node.is_leaf())
if (!node->is_leaf())
for (int i = 0; i < Node::Degree::value; ++i)
todo.push(node[std::size_t(Node::Degree::value - 1 - i)]);
todo.push(&((*node)[std::size_t(Node::Degree::value - 1 - i)]));
}
return Node();
return nullptr;
}
/// \endcond
@ -128,25 +127,30 @@ Node first_child_at_depth(Node n, std::size_t depth) {
struct Preorder_traversal {
template <typename Node>
Node first(Node root) const {
const Node* first(const Node* root) const {
return root;
}
template <typename Node>
Node next(Node n) const {
const Node* next(const Node* n) const {
if (n.is_leaf()) {
if (n->is_leaf()) {
Node next = next_sibling(n);
auto next = next_sibling(n);
if (nullptr == next) {
if (next.is_null())
return next_sibling_up(n);
}
return next;
} else {
// Return the first child of this node
return &(*n)[0];
}
else // Return the first child of this node
return n[0];
}
};
@ -161,18 +165,18 @@ struct Preorder_traversal {
struct Postorder_traversal {
template <typename Node>
Node first(Node root) const {
const Node* first(const Node* root) const {
return deepest_first_child(root);
}
template <typename Node>
Node next(Node n) const {
const Node* next(const Node* n) const {
Node next = deepest_first_child(next_sibling(n));
auto next = deepest_first_child(next_sibling(n));
if (next.is_null())
next = n.parent();
if (!next)
next = n->parent();
return next;
}
@ -189,17 +193,17 @@ struct Postorder_traversal {
struct Leaves_traversal {
template <typename Node>
Node first(Node root) const {
const Node* first(const Node* root) const {
return deepest_first_child(root);
}
template <typename Node>
Node next(Node n) const {
const Node* next(const Node* n) const {
Node next = deepest_first_child(next_sibling(n));
auto next = deepest_first_child(next_sibling(n));
if (next.is_null())
if (!next)
next = deepest_first_child(next_sibling_up(n));
return next;
@ -229,26 +233,25 @@ public:
Level_traversal(std::size_t depth) : depth(depth) {}
template <typename Node>
Node first(Node root) const {
const Node* first(const Node* root) const {
return first_child_at_depth(root, depth);
}
template <typename Node>
Node next(Node n) const {
const Node* next(const Node* n) const {
// fixme: leftover from debugging?
std::cerr << depth << " ";
Node next = next_sibling(n);
const Node* next = next_sibling(n);
if (next.is_null())
{
Node up = n;
do
{
if (!next) {
const Node* up = n;
do {
up = next_sibling_up(up);
if (up.is_null())
return Node();
if (!up)
return nullptr;
next = first_child_at_depth(up, depth);
}
while (next.is_null());
} while (!next);
}
return next;

View File

@ -42,33 +42,36 @@ int main(void) {
std::cout << octree << std::endl;
// Root node should have no siblings
assert(octree.root().adjacent_node(0).is_null());
assert(octree.root().adjacent_node(1).is_null());
assert(octree.root().adjacent_node(2).is_null());
assert(octree.root().adjacent_node(3).is_null());
assert(octree.root().adjacent_node(4).is_null());
assert(octree.root().adjacent_node(5).is_null());
assert(octree.root().adjacent_node(0) == nullptr);
assert(octree.root().adjacent_node(1) == nullptr);
assert(octree.root().adjacent_node(2) == nullptr);
assert(octree.root().adjacent_node(3) == nullptr);
assert(octree.root().adjacent_node(4) == nullptr);
assert(octree.root().adjacent_node(5) == nullptr);
// Left Top Front node should have siblings to the Right, Down, and Back
auto left_top_back = octree.root()[Traits::LEFT_TOP_BACK];
assert(octree.root()[Traits::RIGHT_TOP_BACK] == left_top_back.adjacent_node(Traits::RIGHT));
assert(octree.root()[Traits::LEFT_BOTTOM_BACK] == left_top_back.adjacent_node(Traits::DOWN));
assert(octree.root()[Traits::LEFT_TOP_FRONT] == left_top_back.adjacent_node(Traits::FRONT));
assert(left_top_back.adjacent_node(Traits::LEFT).is_null());
assert(left_top_back.adjacent_node(Traits::UP).is_null());
assert(left_top_back.adjacent_node(Traits::BACK).is_null());
assert(&octree.root()[Traits::RIGHT_TOP_BACK] == left_top_back.adjacent_node(Traits::RIGHT));
assert(&octree.root()[Traits::LEFT_BOTTOM_BACK] == left_top_back.adjacent_node(Traits::DOWN));
assert(&octree.root()[Traits::LEFT_TOP_FRONT] == left_top_back.adjacent_node(Traits::FRONT));
assert(left_top_back.adjacent_node(Traits::LEFT) == nullptr);
assert(left_top_back.adjacent_node(Traits::UP) == nullptr);
assert(left_top_back.adjacent_node(Traits::BACK) == nullptr);
std::cout << octree.root()[Traits::LEFT_BOTTOM_BACK] << std::endl;
auto right_top_back_of_left_bottom_back = octree.root()[Traits::LEFT_BOTTOM_BACK][Traits::RIGHT_TOP_BACK];
assert(octree.root()[Traits::LEFT_BOTTOM_BACK][Traits::LEFT_TOP_BACK] == right_top_back_of_left_bottom_back.adjacent_node(Traits::LEFT));
assert(octree.root()[Traits::RIGHT_BOTTOM_BACK] == right_top_back_of_left_bottom_back.adjacent_node(Traits::RIGHT));
assert(!right_top_back_of_left_bottom_back.adjacent_node(Traits::RIGHT).is_null());
assert(!right_top_back_of_left_bottom_back.adjacent_node(Traits::UP).is_null());
assert(!right_top_back_of_left_bottom_back.adjacent_node(Traits::DOWN).is_null());
assert(right_top_back_of_left_bottom_back.adjacent_node(Traits::BACK).is_null());
assert(!right_top_back_of_left_bottom_back.adjacent_node(Traits::FRONT).is_null());
assert(&octree.root()[Traits::LEFT_BOTTOM_BACK][Traits::LEFT_TOP_BACK] ==
right_top_back_of_left_bottom_back.adjacent_node(Traits::LEFT));
assert(&octree.root()[Traits::RIGHT_BOTTOM_BACK] == right_top_back_of_left_bottom_back.adjacent_node(Traits::RIGHT));
assert(right_top_back_of_left_bottom_back.adjacent_node(Traits::RIGHT) != nullptr);
assert(right_top_back_of_left_bottom_back.adjacent_node(Traits::UP) != nullptr);
assert(right_top_back_of_left_bottom_back.adjacent_node(Traits::DOWN) != nullptr);
assert(right_top_back_of_left_bottom_back.adjacent_node(Traits::FRONT) != nullptr);
// A node at the back of the tree should have no neighbor to its back
assert(right_top_back_of_left_bottom_back.adjacent_node(Traits::BACK) == nullptr);
return 0;
}

View File

@ -27,10 +27,10 @@ std::size_t count_jumps(Octree &octree) {
auto adjacent_node = node.adjacent_node(direction);
if (adjacent_node.is_null())
if (adjacent_node == nullptr)
continue;
if ((node.depth() - adjacent_node.depth()) > 1)
if ((node.depth() - adjacent_node->depth()) > 1)
jumps++;
}
}

View File

@ -46,14 +46,14 @@ int main(void) {
auto query = Point{1, 1, 1};
// Get a list of nodes intersected
std::vector<Octree::Node> nodes{};
std::vector<const Octree::Node *> nodes{};
octree.intersected_nodes(query, std::back_inserter(nodes));
// A point should only intersect one node
assert(1 == nodes.size());
// That node should be the node leaf that contains the point
assert(octree.locate(Point(1, 1, 1)) == nodes[0]);
assert(octree.locate(Point(1, 1, 1)) == *nodes[0]);
}
// Intersection with a sphere
@ -63,15 +63,15 @@ int main(void) {
auto query = Kernel::Sphere_3(Point{1, 0.5, 1}, 1.0);
// Get a list of nodes intersected
std::vector<Octree::Node> nodes{};
std::vector<const Octree::Node *> nodes{};
octree.intersected_nodes(query, std::back_inserter(nodes));
// Check the results
assert(4 == nodes.size());
assert(octree[Octree::Traits::RIGHT_TOP_BACK] == nodes[0]);
assert(octree[Octree::Traits::RIGHT_BOTTOM_FRONT] == nodes[1]);
assert(octree[Octree::Traits::LEFT_TOP_FRONT] == nodes[2]);
assert(octree[Octree::Traits::RIGHT_TOP_FRONT] == nodes[3]);
assert(octree[Octree::Traits::RIGHT_TOP_BACK] == *nodes[0]);
assert(octree[Octree::Traits::RIGHT_BOTTOM_FRONT] == *nodes[1]);
assert(octree[Octree::Traits::LEFT_TOP_FRONT] == *nodes[2]);
assert(octree[Octree::Traits::RIGHT_TOP_FRONT] == *nodes[3]);
}
// Intersection with a ray
@ -81,19 +81,19 @@ int main(void) {
auto query = Kernel::Ray_3(Point{1, 1, 1}, Point{0, 0, 0});
// Get a list of nodes intersected
std::vector<Octree::Node> nodes{};
std::vector<const Octree::Node *> nodes{};
octree.intersected_nodes(query, std::back_inserter(nodes));
// Check the results
assert(8 == nodes.size());
assert(octree[Octree::Traits::LEFT_BOTTOM_BACK] == nodes[0]);
assert(octree[Octree::Traits::RIGHT_BOTTOM_BACK][Octree::Traits::LEFT_TOP_FRONT] == nodes[1]);
assert(octree[Octree::Traits::LEFT_TOP_BACK] == nodes[2]);
assert(octree[Octree::Traits::RIGHT_TOP_BACK] == nodes[3]);
assert(octree[Octree::Traits::LEFT_BOTTOM_FRONT] == nodes[4]);
assert(octree[Octree::Traits::RIGHT_BOTTOM_FRONT] == nodes[5]);
assert(octree[Octree::Traits::LEFT_TOP_FRONT] == nodes[6]);
assert(octree[Octree::Traits::RIGHT_TOP_FRONT] == nodes[7]);
assert(octree[Octree::Traits::LEFT_BOTTOM_BACK] == *nodes[0]);
assert(octree[Octree::Traits::RIGHT_BOTTOM_BACK][Octree::Traits::LEFT_TOP_FRONT] == *nodes[1]);
assert(octree[Octree::Traits::LEFT_TOP_BACK] == *nodes[2]);
assert(octree[Octree::Traits::RIGHT_TOP_BACK] == *nodes[3]);
assert(octree[Octree::Traits::LEFT_BOTTOM_FRONT] == *nodes[4]);
assert(octree[Octree::Traits::RIGHT_BOTTOM_FRONT] == *nodes[5]);
assert(octree[Octree::Traits::LEFT_TOP_FRONT] == *nodes[6]);
assert(octree[Octree::Traits::RIGHT_TOP_FRONT] == *nodes[7]);
}
return EXIT_SUCCESS;

View File

@ -72,7 +72,6 @@ void naive_vs_octree(std::size_t dataset_size) {
octree.refine(10, 20);
auto octree_start_time = high_resolution_clock::now();
{
// TODO: Write a nearest-neighbor implementation and use it here
std::vector<Point> k_neighbors;
octree.nearest_neighbors(random_point, 1, std::back_inserter(k_neighbors));
octree_nearest = *k_neighbors.begin();
@ -143,6 +142,7 @@ void kdtree_vs_octree(std::size_t dataset_size, std::size_t K) {
int main(void) {
naive_vs_octree(21);
naive_vs_octree(500);
naive_vs_octree(1000);
naive_vs_octree(10000);

View File

@ -25,12 +25,11 @@ void test_1_point() {
octree.refine(10, 1);
// Check that the topology matches
Node single_node = CGAL::Orthtrees::Node_access::create_node(Node(), 0);
Node single_node = CGAL::Orthtrees::Node_access::create_node(static_cast<Node *>(nullptr), 0);
CGAL::Orthtrees::Node_access::points(single_node)
= CGAL::Orthtrees::Node_access::points(octree.root());
assert(Node::is_topology_equal(single_node, octree.root()));
assert(0 == octree.depth());
CGAL::Orthtrees::Node_access::free(single_node);
}
void test_2_points() {
@ -45,12 +44,10 @@ void test_2_points() {
octree.refine(10, 1);
// The octree should have been split once
Node other = CGAL::Orthtrees::Node_access::create_node(Node(), 0);
Node other = CGAL::Orthtrees::Node_access::create_node(static_cast<Node *>(nullptr), 0);
CGAL::Orthtrees::Node_access::split(other);
assert(Node::is_topology_equal(other, octree.root()));
assert(1 == octree.depth());
CGAL::Orthtrees::Node_access::free(other);
}
void test_4_points() {
@ -66,13 +63,12 @@ void test_4_points() {
octree.refine(10, 1);
// The octree should have been split once on the first level, and twice on the second
Node other = CGAL::Orthtrees::Node_access::create_node(Node(), 0);
Node other = CGAL::Orthtrees::Node_access::create_node(static_cast<Node *>(nullptr), 0);
CGAL::Orthtrees::Node_access::split(other);
CGAL::Orthtrees::Node_access::split(other[3]);
CGAL::Orthtrees::Node_access::split(other[7]);
assert(Node::is_topology_equal(other, octree.root()));
assert(2 == octree.depth());
CGAL::Orthtrees::Node_access::free(other);
}
int main(void) {