Browse Source

Improve hyperoctree class

master
C. J. Howard 2 years ago
parent
commit
ce4456f1ba
8 changed files with 585 additions and 370 deletions
  1. +24
    -1
      src/game/system/terrain.cpp
  2. +2
    -2
      src/game/system/terrain.hpp
  3. +461
    -328
      src/geom/hyperoctree.hpp
  4. +11
    -11
      src/geom/mesh-accelerator.cpp
  5. +8
    -6
      src/geom/mesh-accelerator.hpp
  6. +27
    -6
      src/geom/octree.hpp
  7. +27
    -6
      src/geom/quadtree.hpp
  8. +25
    -10
      src/math/compile.hpp

+ 24
- 1
src/game/system/terrain.cpp View File

@ -29,6 +29,7 @@
#include "math/quaternion.hpp" #include "math/quaternion.hpp"
#include "render/vertex-attribute.hpp" #include "render/vertex-attribute.hpp"
#include "utility/fundamental-types.hpp" #include "utility/fundamental-types.hpp"
#include "math/compile.hpp"
#include <functional> #include <functional>
#include <iostream> #include <iostream>
@ -56,7 +57,29 @@ terrain::terrain(entity::registry& registry):
for (std::size_t i = 0; i <= quadtree_type::max_depth; ++i) for (std::size_t i = 0; i <= quadtree_type::max_depth; ++i)
quadtree_node_size[i] = 0.0f; quadtree_node_size[i] = 0.0f;
std::cout << "quadtree cap: " << quadtree.max_size() << std::endl;
geom::quadtree64<geom::hyperoctree_order::dfs_pre> q;
q.insert(q.node(4, 0));
//q.insert(q.node(8, geom::morton::encode<std::uint64_t>(4, 4)));
// std::cout << "q size: " << q.size() << std::endl;
// q.erase(q.node(1, 0));
// std::cout << "q size: " << q.size() << std::endl;
std::cout << "q maxd : " << (std::size_t)q.max_depth << std::endl;
std::cout << "q res : " << (std::size_t)q.resolution << std::endl;
std::cout << "q cap : " << q.max_size() << std::endl;
std::cout << "set cap: " << std::set<std::uint64_t>().max_size() << std::endl;
std::cout << "q size: " << q.size() << std::endl;
for (auto it = q.begin(); it != q.end(); ++it)
{
const auto& node = *it;
auto [depth, location] = q.split(node);
std::cout << "depth: " << (std::size_t)depth << "; location: " << (std::size_t)location << std::endl;
}
registry.on_construct<component::terrain>().connect<&terrain::on_terrain_construct>(this); registry.on_construct<component::terrain>().connect<&terrain::on_terrain_construct>(this);
registry.on_update<component::terrain>().connect<&terrain::on_terrain_update>(this); registry.on_update<component::terrain>().connect<&terrain::on_terrain_update>(this);

+ 2
- 2
src/game/system/terrain.hpp View File

@ -82,8 +82,8 @@ public:
void set_scene_collection(scene::collection* collection); void set_scene_collection(scene::collection* collection);
private: private:
typedef geom::quadtree16 quadtree_type;
typedef quadtree_type::node_type quadtree_node_type;
typedef geom::unordered_quadtree16 quadtree_type;
typedef typename quadtree_type::node_type quadtree_node_type;
struct patch struct patch
{ {

+ 461
- 328
src/geom/hyperoctree.hpp View File

@ -21,423 +21,556 @@
#define ANTKEEPER_GEOM_HYPEROCTREE_HPP #define ANTKEEPER_GEOM_HYPEROCTREE_HPP
#include "math/compile.hpp" #include "math/compile.hpp"
#include <algorithm>
#include <array>
#include <bit> #include <bit>
#include <cstdint>
#include <limits>
#include <concepts>
#include <cstddef>
#include <set>
#include <type_traits> #include <type_traits>
#include <unordered_set> #include <unordered_set>
#include <stack>
namespace geom { namespace geom {
/// Orders in which hyperoctree nodes can be stored and iterated.
enum class hyperoctree_order
{
/// Hyperoctree nodes are unordered, potentially resulting in faster insertions through the internal use of `std::unordered_set` rather than `std::set`.
unordered,
/// Hyperoctree nodes are stored and iterated in depth-first preorder.
dfs_pre,
/// Hyperoctree nodes are stored and iterated in breadth-first order.
bfs
};
/// @private
template <std::unsigned_integral T>
using hyperoctree_dfs_pre_compare = std::less<T>;
/// @private
template <std::unsigned_integral T, std::size_t DepthBits>
struct hyperoctree_bfs_compare
{
constexpr bool operator()(const T& lhs, const T& rhs) const noexcept
{
return std::rotr(lhs, DepthBits) < std::rotr(rhs, DepthBits);
}
};
/// @private
template <hyperoctree_order Order, std::unsigned_integral T, std::size_t DepthBits>
struct hyperoctree_container {};
/// @private
template <std::unsigned_integral T, std::size_t DepthBits>
struct hyperoctree_container<hyperoctree_order::unordered, T, DepthBits>
{
typedef std::unordered_set<T> type;
};
/// @private
template <std::unsigned_integral T, std::size_t DepthBits>
struct hyperoctree_container<hyperoctree_order::dfs_pre, T, DepthBits>
{
typedef std::set<T, hyperoctree_dfs_pre_compare<T>> type;
};
/// @private
template <std::unsigned_integral T, std::size_t DepthBits>
struct hyperoctree_container<hyperoctree_order::bfs, T, DepthBits>
{
typedef std::set<T, hyperoctree_bfs_compare<T, DepthBits>> type;
};
/** /**
* Hashed linear hyperoctree. * Hashed linear hyperoctree.
* *
* @tparam T Integer node type.
* @tparam T Unsigned integral node identifier type.
* @tparam N Number of dimensions. * @tparam N Number of dimensions.
* @tparam D Max depth.
*
* Max depth can likely be determined by a generalized formula. 2D and 3D cases are given below:
*
* 2D:
* 8 bit ( 1 byte) = max depth 1 ( 4 loc bits, 1 depth bits, 1 divider bit) = 6 bits
* 16 bit ( 2 byte) = max depth 5 ( 12 loc bits, 3 depth bits, 1 divider bit) = 16 bits
* 32 bit ( 4 byte) = max depth 12 ( 26 loc bits, 4 depth bits, 1 divider bit) = 31 bits
* 64 bit ( 8 byte) = max depth 28 ( 58 loc bits, 5 depth bits, 1 divider bit) = 64 bits
* 128 bit (16 byte) = max depth 59 (120 loc bits, 6 depth bits, 1 divider bit) = 127 bits
* 256 bit (32 byte) = max depth 123 (248 loc bits, 7 depth bits, 1 divider bit) = 256 bits
* @see https://oeis.org/A173009
*
* 3D:
* 8 bit ( 1 byte) = max depth 1 ( 6 loc bits, 1 depth bits, 1 divider bit) = 8 bits
* 16 bit ( 2 byte) = max depth 3 ( 12 loc bits, 2 depth bits, 1 divider bit) = 15 bits
* 32 bit ( 4 byte) = max depth 8 ( 27 loc bits, 4 depth bits, 1 divider bit) = 32 bits
* 64 bit ( 8 byte) = max depth 18 ( 57 loc bits, 5 depth bits, 1 divider bit) = 63 bits
* 128 bit (16 byte) = max depth 39 (120 loc bits, 6 depth bits, 1 divider bit) = 127 bits
* 256 bit (32 byte) = max depth 81 (243 loc bits, 7 depth bits, 1 divider bit) = 251 bits
* @see https://oeis.org/A178420
* @tparam Order Order in which nodes are stored and iterated.
* *
* @see http://codervil.blogspot.com/2015/10/octree-node-identifiers.html * @see http://codervil.blogspot.com/2015/10/octree-node-identifiers.html
* @see https://geidav.wordpress.com/2014/08/18/advanced-octrees-2-node-representations/ * @see https://geidav.wordpress.com/2014/08/18/advanced-octrees-2-node-representations/
*/ */
template <class T, std::size_t N, std::size_t D>
template <std::unsigned_integral T, std::size_t N, hyperoctree_order Order = hyperoctree_order::dfs_pre>
class hyperoctree class hyperoctree
{ {
private:
/**
* Finds the maximum hyperoctree depth level from the size of the node type `T` and number of dimensions `N`.
*
* @return Maximum hyperoctree depth level.
*
* @note There is likely a more elegant formula for this. Information about the 2D and 3D cases is given below:
*
* 2D:
* 8 bit ( 1 byte) = max depth 1 ( 4 loc bits, 1 depth bits, 1 divider bit) = 6 bits
* 16 bit ( 2 byte) = max depth 5 ( 12 loc bits, 3 depth bits, 1 divider bit) = 16 bits
* 32 bit ( 4 byte) = max depth 12 ( 26 loc bits, 4 depth bits, 1 divider bit) = 31 bits
* 64 bit ( 8 byte) = max depth 28 ( 58 loc bits, 5 depth bits, 1 divider bit) = 64 bits
* 128 bit (16 byte) = max depth 59 (120 loc bits, 6 depth bits, 1 divider bit) = 127 bits
* 256 bit (32 byte) = max depth 123 (248 loc bits, 7 depth bits, 1 divider bit) = 256 bits
*
* @see https://oeis.org/A173009
*
* 3D:
* 8 bit ( 1 byte) = max depth 1 ( 6 loc bits, 1 depth bits, 1 divider bit) = 8 bits
* 16 bit ( 2 byte) = max depth 3 ( 12 loc bits, 2 depth bits, 1 divider bit) = 15 bits
* 32 bit ( 4 byte) = max depth 8 ( 27 loc bits, 4 depth bits, 1 divider bit) = 32 bits
* 64 bit ( 8 byte) = max depth 18 ( 57 loc bits, 5 depth bits, 1 divider bit) = 63 bits
* 128 bit (16 byte) = max depth 39 (120 loc bits, 6 depth bits, 1 divider bit) = 127 bits
* 256 bit (32 byte) = max depth 81 (243 loc bits, 7 depth bits, 1 divider bit) = 251 bits
*
* @see https://oeis.org/A178420
*/
static consteval std::size_t find_max_depth() noexcept
{
std::size_t max_depth = 0;
for (std::size_t i = 1; i <= sizeof(T) * 8; ++i)
{
const std::size_t location_bits = sizeof(T) * 8 - i;
max_depth = location_bits / N - 1;
const std::size_t depth_bits = static_cast<std::size_t>(std::bit_width(max_depth));
if (depth_bits + location_bits < sizeof(T) * 8)
break;
}
return static_cast<T>(max_depth);
}
public: public:
/// Integral node type.
/// Node identifier type.
typedef T node_type; typedef T node_type;
/// Ensure the node type is integral
static_assert(std::is_integral<T>::value, "Node type must be integral.");
/// Number of dimensions.
static constexpr std::size_t dimensions = N;
/// Maximum node depth.
static constexpr std::size_t max_depth = D;
/// Node storage and traversal order.
static constexpr hyperoctree_order order = Order;
/// Number of bits required to encode the depth of a node.
static constexpr T depth_bits = math::compile::ceil_log2(max_depth + 1);
/// Number of bits required to encode the location of a node.
static constexpr T location_bits = (max_depth + 1) * N;
/// Maximum node depth level.
static constexpr node_type max_depth = find_max_depth();
/// Number of bits in the node type. /// Number of bits in the node type.
static constexpr T node_bits = sizeof(node_type) * 8;
static constexpr node_type node_bits = sizeof(node_type) * 8;
/// Number of bits required to encode the depth of a node.
static constexpr node_type depth_bits = std::bit_width(max_depth);
/// Number of bits required to encode the Morton location code of a node.
static constexpr node_type location_bits = (max_depth + 1) * N;
// Ensure the node type has enough bits
static_assert(depth_bits + location_bits + 1 <= node_bits, "Size of hyperoctree node type is insufficient to encode the maximum depth");
/// Number of bits separating the depth and Morton location code in a node identifier.
static constexpr node_type divider_bits = node_bits - (depth_bits + location_bits);
/// Number of children per node. /// Number of children per node.
static constexpr T children_per_node = (N) ? (2 << (N - 1)) : 1;
static constexpr node_type children_per_node = math::compile::exp2<node_type>(N);
/// Number of siblings per node. /// Number of siblings per node.
static constexpr T siblings_per_node = children_per_node - 1;
static constexpr node_type siblings_per_node = children_per_node - 1;
/// Root node which is always guaranteed to exist.
/// Resolution in each dimension.
static constexpr node_type resolution = math::compile::exp2<node_type>(max_depth + 1);
/// Number of nodes in a full hyperoctree.
static constexpr std::size_t max_node_count = (math::compile::pow<std::size_t>(resolution, N) - 1) / siblings_per_node;
/// Node identifier of the persistent root node.
static constexpr node_type root = 0; static constexpr node_type root = 0;
/// Node container type.
typedef typename hyperoctree_container<order, node_type, depth_bits>::type container_type;
/// Iterator type.
typedef typename container_type::iterator iterator;
/// Constant iterator type.
typedef typename container_type::const_iterator const_iterator;
/// Reverse iterator type.
typedef std::conditional<order != hyperoctree_order::unordered, std::reverse_iterator<iterator>, iterator>::type reverse_iterator;
/// Constant reverse iterator type.
typedef std::conditional<order != hyperoctree_order::unordered, std::reverse_iterator<const_iterator>, const_iterator>::type const_reverse_iterator;
/// @name Nodes
/// @{
/** /**
* Accesses nodes in their internal hashmap order.
* Extracts the depth of a node from its identifier.
*
* @param node Node identifier.
*
* @return Depth of the node.
*/ */
struct unordered_iterator
{
inline unordered_iterator(const unordered_iterator& other): set_iterator(other.set_iterator) {};
inline unordered_iterator& operator=(const unordered_iterator& other) { this->set_iterator = other.set_iterator; return *this; };
inline unordered_iterator& operator++() { ++(this->set_iterator); return *this; };
inline unordered_iterator& operator--() { --(this->set_iterator); return *this; };
inline bool operator==(const unordered_iterator& other) const { return this->set_iterator == other.set_iterator; };
inline bool operator!=(const unordered_iterator& other) const { return this->set_iterator != other.set_iterator; };
inline node_type operator*() const { return *this->set_iterator; };
private:
friend class hyperoctree;
inline explicit unordered_iterator(const typename std::unordered_set<node_type>::const_iterator& it): set_iterator(it) {};
typename std::unordered_set<node_type>::const_iterator set_iterator;
};
static constexpr inline node_type depth(node_type node) noexcept
{
constexpr node_type mask = math::compile::exp2<node_type>(depth_bits) - 1;
return node & mask;
}
/** /**
* Accesses nodes in z-order.
* Extracts the Morton location code of a node from its identifier.
* *
* @TODO Can this be implemented without a stack?
* @param node Node identifier.
*
* @return Morton location code of the node.
*/ */
struct iterator
{
inline iterator(const iterator& other): hyperoctree(other.hyperoctree), stack(other.stack) {};
inline iterator& operator=(const iterator& other) { this->hyperoctree = other.hyperoctree; this->stack = other.stack; return *this; };
iterator& operator++();
inline bool operator==(const iterator& other) const { return **this == *other; };
inline bool operator!=(const iterator& other) const { return **this != *other; };
inline node_type operator*() const { return stack.top(); };
private:
friend class hyperoctree;
inline explicit iterator(const hyperoctree* hyperoctree, node_type node): hyperoctree(hyperoctree), stack({node}) {};
const hyperoctree* hyperoctree;
std::stack<node_type> stack;
};
static constexpr inline node_type location(node_type node) noexcept
{
return node >> ((node_bits - 1) - depth(node) * N);
}
/** /**
* Returns the depth of a node.
* Extracts the depth and Morton location code of a node from its identifier.
* *
* @param node Node.
* @return Depth of the node.
* @param node Node identifier.
*
* @return Array containing the depth of the node, followed by the Morton location code of the node.
*/ */
static T depth(node_type node);
static constexpr std::array<node_type, 2> split(node_type node) noexcept
{
const node_type depth = hyperoctree::depth(node);
const node_type location = node >> ((node_bits - 1) - depth * N);
return {depth, location};
}
/** /**
* Returns the Morton code location of a node.
* Constructs an identifier for a node at the given depth and location.
*
* @param depth Depth level.
* @param location Morton location code.
*
* @return Identifier of a node at the given depth and location.
* *
* @param node Node.
* @return Morton code location of the node.
* @warning If @p depth exceeds `max_depth`, the returned node identifier is not valid.
*/ */
static T location(node_type node);
static constexpr inline node_type node(node_type depth, node_type location) noexcept
{
return (location << ((node_bits - 1) - depth * N)) | depth;
}
/** /**
* Returns the node at the given depth and location.
* Constructs an identifier for the ancestor of a node at a given depth.
*
* @param node Node identifier.
* @param depth Absolute depth of an ancestor node.
*
* @return Identifier of the ancestor of the node at the given depth.
* *
* @param depth Node depth.
* @param location Node Morton code location.
* @warning If @p depth exceeds the depth of @p node, the returned node identifier is not valid.
*/ */
static node_type node(T depth, T location);
static constexpr inline node_type ancestor(node_type node, node_type depth) noexcept
{
const node_type mask = (~node_type(0)) << ((node_bits - 1) - depth * N);
return (node & mask) | depth;
}
/** /**
* Returns the ancestor of a node at the specified depth.
* Constructs an identifier for the parent of a node.
* *
* @param node Node whose ancestor will be located.
* @param depth Absolute depth of the ancestors.
* @return Ancestral node.
* @param node Node identifier.
*
* @return Identifier of the parent node.
*/ */
static node_type ancestor(node_type node, T depth);
static constexpr inline node_type parent(node_type node) noexcept
{
return ancestor(node, depth(node) - 1);
}
/** /**
* Returns the parent of a node.
* Constructs an identifier for the nth sibling of a node.
* *
* @param node Node.
* @return Parent node.
* @param node Node identifier.
* @param n Offset to a sibling, automatically wrapped to `[0, siblings_per_node]`.
*
* @return Identifier of the nth sibling node.
*/ */
static node_type parent(node_type node);
static constexpr node_type sibling(node_type node, node_type n) noexcept
{
constexpr node_type mask = (1 << N) - 1;
const auto [depth, location] = split(node);
const node_type sibling_location = (location & (~mask)) | ((location + n) & mask);
return hyperoctree::node(depth, sibling_location);
}
/** /**
* Returns the nth sibling of a node.
* Constructs an identifier for the nth child of a node.
* *
* @param node Node.
* @param n Offset to next sibling. (Automatically wraps to `[0, siblings_per_node]`)
* @return Next sibling node.
* @param node Node identifier.
* @param n Offset to a sibling of the first child node, automatically wrapped to `[0, siblings_per_node]`.
*
* @return Identifier of the nth child node.
*/ */
static node_type sibling(node_type node, T n);
static constexpr inline node_type child(node_type node, T n) noexcept
{
return sibling(node + 1, n);
}
/** /**
* Returns the nth child of a node.
* Constructs an identifier for first common ancestor of two nodes
*
* @param a Identifier of the first node.
* @param b Identifier of the second node.
* *
* @param node Parent node.
* @param n Offset to the nth sibling of the first child node. (Automatically wraps to 0..children_per_node-1)
* @return nth child node.
* @return Identifier of the first common ancestor of the two nodes.
*/ */
static node_type child(node_type node, T n);
static constexpr node_type common_ancestor(node_type a, node_type b) noexcept
{
const node_type bits = std::min<node_type>(depth(a), depth(b)) * N;
const node_type marker = (node_type(1) << (node_bits - 1)) >> bits;
const node_type depth = node_type(std::countl_zero((a ^ b) | marker) / N);
return ancestor(a, depth);
}
/// @}
/// Constructs a hyperoctree with a single root node.
hyperoctree():
nodes({root})
{}
/// @name Iterators
/// @{
/** /**
* Calculates the first common ancestor of two nodes.
* Returns an iterator to the first node, in the traversal order specified by hyperoctree::order.
* *
* @param a First node.
* @param b Second node.
* @return First common ancestor of the two nodes.
* @note Node identifiers cannot be modified through iterators.
*/ */
static node_type common_ancestor(node_type a, node_type b);
/// Creates an hyperoctree with a single root node.
hyperoctree();
/// Returns a z-order iterator to the root node.
iterator begin() const;
/// Returns a z-order iterator indicating the end of a traversal.
iterator end() const;
/// Returns an iterator to the specified node.
iterator find(node_type node) const;
/// Returns an unordered iterator indicating the beginning of a traversal.
unordered_iterator unordered_begin() const;
/// Returns an unordered iterator indicating the end of a traversal.
unordered_iterator unordered_end() const;
/// @{
inline iterator begin() noexcept
{
return nodes.begin();
}
inline const_iterator begin() const noexcept
{
return nodes.begin();
}
inline const_iterator cbegin() const noexcept
{
return nodes.cbegin();
}
/// @}
/** /**
* Inserts a node and its siblings into the hyperoctree, creating its ancestors as necessary. Note: The root node is persistent and cannot be inserted.
* Returns an iterator to the node following the last node, in the traversal order specified by hyperoctree::order.
* *
* @param node Node to insert.
* @note Node identifiers cannot be modified through iterators.
*/ */
void insert(node_type node);
/// @{
inline iterator end() noexcept
{
return nodes.end();
}
inline const_iterator end() const noexcept
{
return nodes.end();
}
inline const_iterator cend() const noexcept
{
return nodes.cend();
}
/// @}
/** /**
* Erases a node along with its siblings and descendants. Note: The root node is persistent and cannot be erased.
* Returns a reverse iterator to the first node of the revered hyperoctree, in the traversal order specified by hyperoctree::order.
* *
* @param node Node to erase.
* @note Node identifiers cannot be modified through iterators.
* @note If the hyperoctree is unordered, reverse iteration and forward iteration will be identical.
*/ */
void erase(node_type node);
/// @{
inline reverse_iterator rbegin() noexcept
{
if constexpr (order != hyperoctree_order::unordered)
return nodes.rbegin();
else
return nodes.begin();
}
inline const_reverse_iterator rbegin() const noexcept
{
if constexpr (order != hyperoctree_order::unordered)
return nodes.rbegin();
else
return nodes.begin();
}
inline const_reverse_iterator crbegin() const noexcept
{
if constexpr (order != hyperoctree_order::unordered)
return nodes.crbegin();
else
return nodes.cbegin();
}
/// @}
/** /**
* Erases all nodes except the root.
* Returns a reverse iterator to the node following the last node of the reverse hyperoctree, in the traversal order specified by hyperoctree::order.
*
* @note Node identifiers cannot be modified through iterators.
* @note If the hyperoctree is unordered, reverse iteration and forward iteration will be identical.
*/ */
void clear();
/// Returns `true` if the node is contained within the hyperoctree, and `false` otherwise.
bool contains(node_type node) const;
/// Returns `true` if the node has no children, and `false` otherwise.
bool is_leaf(node_type node) const;
/// Returns the number of nodes in the hyperoctree.
std::size_t size() const;
/// Returns the total number of nodes the hyperoctree is capable of containing.
static consteval std::size_t max_size() noexcept
/// @{
inline reverse_iterator rend() noexcept
{ {
return (math::compile::pow<std::size_t>(children_per_node, max_depth + 1) - 1) / (children_per_node - 1);
if constexpr (order != hyperoctree_order::unordered)
return nodes.rend();
else
return nodes.end();
} }
private:
std::unordered_set<node_type> nodes;
};
template <class T, std::size_t N, std::size_t D>
typename hyperoctree<T, N, D>::iterator& hyperoctree<T, N, D>::iterator::operator++()
{
// Get next node from top of stack
node_type node = stack.top();
stack.pop();
inline const_reverse_iterator rend() const noexcept
{
if constexpr (order != hyperoctree_order::unordered)
return nodes.rend();
else
return nodes.end();
}
inline const_reverse_iterator crend() const noexcept
{
if constexpr (order != hyperoctree_order::unordered)
return nodes.crend();
else
return nodes.cend();
}
/// @}
/// @}
// If the node has children
if (!hyperoctree->is_leaf(node))
/// @name Capacity
/// @{
/**
* Checks if the hyperoctree has no nodes.
*
* @return `true` if the hyperoctree is empty, `false` otherwise.
*
* @note This function should always return `false`, as the root node is persistent.
*/
inline bool empty() const noexcept
{ {
// Push first child onto the stack
for (T i = 0; i < children_per_node; ++i)
stack.push(child(node, siblings_per_node - i));
return nodes.empty();
} }
if (stack.empty())
stack.push(std::numeric_limits<T>::max());
/**
* Checks if the hyperoctree is full.
*
* @return `true` if the hyperoctree is full, `false` otherwise.
*/
inline bool full() const noexcept
{
return size() == max_size();
}
return *this;
}
template <class T, std::size_t N, std::size_t D>
inline T hyperoctree<T, N, D>::depth(node_type node)
{
// Extract depth using a bit mask
constexpr T mask = math::compile::pow<node_type>(2, depth_bits) - 1;
return node & mask;
}
template <class T, std::size_t N, std::size_t D>
inline T hyperoctree<T, N, D>::location(node_type node)
{
return node >> ((node_bits - 1) - depth(node) * N);
}
template <class T, std::size_t N, std::size_t D>
inline typename hyperoctree<T, N, D>::node_type hyperoctree<T, N, D>::node(T depth, T location)
{
return (location << ((node_bits - 1) - depth * N)) | depth;
}
template <class T, std::size_t N, std::size_t D>
inline typename hyperoctree<T, N, D>::node_type hyperoctree<T, N, D>::ancestor(node_type node, T depth)
{
const T mask = std::numeric_limits<T>::max() << ((node_bits - 1) - depth * N);
return (node & mask) | depth;
}
template <class T, std::size_t N, std::size_t D>
inline typename hyperoctree<T, N, D>::node_type hyperoctree<T, N, D>::parent(node_type node)
{
return ancestor(node, depth(node) - 1);
}
template <class T, std::size_t N, std::size_t D>
inline typename hyperoctree<T, N, D>::node_type hyperoctree<T, N, D>::sibling(node_type node, T n)
{
constexpr T mask = (1 << N) - 1;
/**
* Returns the number of nodes in the hyperoctree.
*
* @return Number of nodes in the hyperoctree.
*
* @note Hyperoctree size will always be greater than or equal to one, as the root node is persistent.
*/
inline std::size_t size() const noexcept
{
return nodes.size();
}
T depth = hyperoctree::depth(node);
T location = node >> ((node_bits - 1) - depth * N);
/// Returns the total number of nodes the hyperoctree is capable of containing.
static consteval std::size_t max_size() noexcept
{
return max_node_count;
}
/// @}
return hyperoctree::node(depth, (location & (~mask)) | ((location + n) & mask));
}
template <class T, std::size_t N, std::size_t D>
inline typename hyperoctree<T, N, D>::node_type hyperoctree<T, N, D>::child(node_type node, T n)
{
return sibling(node + 1, n);
}
template <class T, std::size_t N, std::size_t D>
inline typename hyperoctree<T, N, D>::node_type hyperoctree<T, N, D>::common_ancestor(node_type a, node_type b)
{
T bits = std::min<T>(depth(a), depth(b)) * N;
T marker = (T(1) << (node_bits - 1)) >> bits;
T depth = T(std::countl_zero((a ^ b) | marker) / N);
return ancestor(a, depth);
}
template <class T, std::size_t N, std::size_t D>
inline hyperoctree<T, N, D>::hyperoctree():
nodes({0})
{}
template <class T, std::size_t N, std::size_t D>
void hyperoctree<T, N, D>::insert(node_type node)
{
if (contains(node))
return;
/// @name Modifiers
/// @{
/**
* Erases all nodes except the root node, which is persistent.
*/
inline void clear()
{
nodes = {root};
}
// Insert node
nodes.emplace(node);
// Insert siblings
for (T i = 1; i < children_per_node; ++i)
nodes.emplace(sibling(node, i));
// Insert parent as necessary
node_type parent = hyperoctree::parent(node);
if (!contains(parent))
insert(parent);
}
template <class T, std::size_t N, std::size_t D>
void hyperoctree<T, N, D>::erase(node_type node)
{
// Don't erase the root!
if (node == root)
return;
/**
* Inserts a node and its siblings into the hyperoctree, creating ancestors as necessary.
*
* @param node Node to insert.
*
* @note The root node is persistent and does not need to be inserted.
*/
void insert(node_type node)
{
if (contains(node))
return;
// Insert node
nodes.emplace(node);
// Insert node siblings
for (node_type i = 1; i < children_per_node; ++i)
nodes.emplace(sibling(node, i));
// Insert node ancestors
insert(parent(node));
}
for (T i = 0; i < children_per_node; ++i)
/**
* Erases a node, along with its descendants, siblings, and descendants of siblings.
*
* @param node Identifier of the node to erase.
*
* @note The root node is persistent and cannot be erased.
*/
void erase(node_type node)
{ {
// Erase node
if (node == root || !contains(node))
return;
// Erase node and its descendants
nodes.erase(node); nodes.erase(node);
// Erase descendants
if (!is_leaf(node))
erase(child(node, 0));
// Erase node siblings
for (node_type i = 0; i < siblings_per_node; ++i)
{ {
for (T j = 0; j < children_per_node; ++j)
erase(child(node, j));
node = sibling(node, 1);
// Erase sibling and its descendants
nodes.erase(node);
erase(child(node, 0));
} }
// Go to next sibling
if (i < siblings_per_node)
node = sibling(node, i);
} }
}
template <class T, std::size_t N, std::size_t D>
void hyperoctree<T, N, D>::clear()
{
nodes = {0};
}
template <class T, std::size_t N, std::size_t D>
inline bool hyperoctree<T, N, D>::contains(node_type node) const
{
return nodes.count(node);
}
template <class T, std::size_t N, std::size_t D>
inline bool hyperoctree<T, N, D>::is_leaf(node_type node) const
{
return !contains(child(node, 0));
}
template <class T, std::size_t N, std::size_t D>
inline std::size_t hyperoctree<T, N, D>::size() const
{
return nodes.size();
}
template <class T, std::size_t N, std::size_t D>
typename hyperoctree<T, N, D>::iterator hyperoctree<T, N, D>::begin() const
{
return iterator(this, hyperoctree::root);
}
template <class T, std::size_t N, std::size_t D>
typename hyperoctree<T, N, D>::iterator hyperoctree<T, N, D>::end() const
{
return iterator(this, std::numeric_limits<T>::max());
}
template <class T, std::size_t N, std::size_t D>
typename hyperoctree<T, N, D>::iterator hyperoctree<T, N, D>::find(node_type node) const
{
return contains(node) ? iterator(node) : end();
}
template <class T, std::size_t N, std::size_t D>
typename hyperoctree<T, N, D>::unordered_iterator hyperoctree<T, N, D>::unordered_begin() const
{
return unordered_iterator(nodes.begin());
}
/// @}
/// @name Lookup
/// @{
/**
* Checks if a node is contained within the hyperoctree.
*
* @param node Identifier of the node to check for.
*
* @return `true` if the hyperoctree contains the node, `false` otherwise.
*/
inline bool contains(node_type node) const
{
return nodes.contains(node);
}
/**
* Checks if a node has no children.
*
* @param node Node identififer.
*
* @return `true` if the node has no children, and `false` otherwise.
*/
inline bool is_leaf(node_type node) const
{
return !contains(child(node, 0));
}
/// @}
private:
container_type nodes;
};
template <class T, std::size_t N, std::size_t D>
typename hyperoctree<T, N, D>::unordered_iterator hyperoctree<T, N, D>::unordered_end() const
{
return unordered_iterator(nodes.end());
}
/// Hyperoctree with unordered node storage and traversal.
template <std::unsigned_integral T, std::size_t N>
using unordered_hyperoctree = hyperoctree<T, N, hyperoctree_order::unordered>;
} // namespace geom } // namespace geom

+ 11
- 11
src/geom/mesh-accelerator.cpp View File

@ -39,7 +39,7 @@ void mesh_accelerator::build(const mesh& mesh)
center_offset = mesh_dimensions * 0.5f - (bounds.min_point + bounds.max_point) * 0.5f; center_offset = mesh_dimensions * 0.5f - (bounds.min_point + bounds.max_point) * 0.5f;
// Calculate node dimensions at each octree depth // Calculate node dimensions at each octree depth
for (auto i = 0; i <= octree32::max_depth; ++i)
for (auto i = 0; i <= octree_type::max_depth; ++i)
{ {
node_dimensions[i] = mesh_dimensions * static_cast<float>((1.0f / std::pow(2, i))); node_dimensions[i] = mesh_dimensions * static_cast<float>((1.0f / std::pow(2, i)));
} }
@ -67,9 +67,9 @@ void mesh_accelerator::build(const mesh& mesh)
// 1. Find max depth node of aabb min // 1. Find max depth node of aabb min
// 2. Find max depth node of aabb max // 2. Find max depth node of aabb max
// 3. Find common ancestor of the two nodes--that's the containing node. // 3. Find common ancestor of the two nodes--that's the containing node.
octree32::node_type min_node = find_node(min_point);
octree32::node_type max_node = find_node(max_point);
octree32::node_type containing_node = octree32::common_ancestor(min_node, max_node);
typename octree_type::node_type min_node = find_node(min_point);
typename octree_type::node_type max_node = find_node(max_point);
typename octree_type::node_type containing_node = octree_type::common_ancestor(min_node, max_node);
// Insert containing node into octree // Insert containing node into octree
octree.insert(containing_node); octree.insert(containing_node);
@ -92,7 +92,7 @@ std::optional mesh_accelerator::query_neares
return std::nullopt; return std::nullopt;
} }
void mesh_accelerator::query_nearest_recursive(float& nearest_t, geom::mesh::face*& nearest_face, octree32::node_type node, const ray<float>& ray) const
void mesh_accelerator::query_nearest_recursive(float& nearest_t, geom::mesh::face*& nearest_face, typename octree_type::node_type node, const ray<float>& ray) const
{ {
// Get node bounds // Get node bounds
const aabb<float> node_bounds = get_node_bounds(node); const aabb<float> node_bounds = get_node_bounds(node);
@ -138,22 +138,22 @@ void mesh_accelerator::query_nearest_recursive(float& nearest_t, geom::mesh::fac
} }
} }
aabb<float> mesh_accelerator::get_node_bounds(octree32::node_type node) const
aabb<float> mesh_accelerator::get_node_bounds(typename octree_type::node_type node) const
{ {
// Decode Morton location of node // Decode Morton location of node
std::uint32_t x, y, z; std::uint32_t x, y, z;
morton::decode(octree32::location(node), x, y, z);
morton::decode(octree_type::location(node), x, y, z);
float3 node_location = float3{static_cast<float>(x), static_cast<float>(y), static_cast<float>(z)}; float3 node_location = float3{static_cast<float>(x), static_cast<float>(y), static_cast<float>(z)};
// Get node dimensions at node depth // Get node dimensions at node depth
const float3& dimensions = node_dimensions[octree32::depth(node)];
const float3& dimensions = node_dimensions[octree_type::depth(node)];
// Calculate AABB // Calculate AABB
float3 min_point = (node_location * dimensions) - center_offset; float3 min_point = (node_location * dimensions) - center_offset;
return aabb<float>{min_point, min_point + dimensions}; return aabb<float>{min_point, min_point + dimensions};
} }
octree32::node_type mesh_accelerator::find_node(const float3& point) const
typename mesh_accelerator::octree_type::node_type mesh_accelerator::find_node(const float3& point) const
{ {
// Transform point to octree space // Transform point to octree space
float3 transformed_point = (point + center_offset); float3 transformed_point = (point + center_offset);
@ -165,7 +165,7 @@ octree32::node_type mesh_accelerator::find_node(const float3& point) const
transformed_point.z() = std::max<float>(0.0f, std::min<float>(node_dimensions[0].z() - epsilon, transformed_point.z())); transformed_point.z() = std::max<float>(0.0f, std::min<float>(node_dimensions[0].z() - epsilon, transformed_point.z()));
// Transform point to max-depth node space // Transform point to max-depth node space
transformed_point = transformed_point / node_dimensions[octree32::max_depth];
transformed_point = transformed_point / node_dimensions[octree_type::max_depth];
// Encode transformed point as a Morton location code // Encode transformed point as a Morton location code
std::uint32_t location = morton::encode( std::uint32_t location = morton::encode(
@ -174,7 +174,7 @@ octree32::node_type mesh_accelerator::find_node(const float3& point) const
static_cast<std::uint32_t>(transformed_point.z())); static_cast<std::uint32_t>(transformed_point.z()));
// Return max depth node at the determined location // Return max depth node at the determined location
return octree32::node(octree32::max_depth, location);
return octree_type::node(octree_type::max_depth, location);
} }
} // namespace geom } // namespace geom

+ 8
- 6
src/geom/mesh-accelerator.hpp View File

@ -59,17 +59,19 @@ public:
std::optional<ray_query_result> query_nearest(const ray<float>& ray) const; std::optional<ray_query_result> query_nearest(const ray<float>& ray) const;
private: private:
aabb<float> get_node_bounds(octree32::node_type node) const;
typedef unordered_octree32 octree_type;
aabb<float> get_node_bounds(typename octree_type::node_type node) const;
void query_nearest_recursive(float& nearest_t, geom::mesh::face*& nearest_face, octree32::node_type node, const ray<float>& ray) const;
void query_nearest_recursive(float& nearest_t, geom::mesh::face*& nearest_face, typename octree_type::node_type node, const ray<float>& ray) const;
/// Returns the max-depth node in which the point is located /// Returns the max-depth node in which the point is located
octree32::node_type find_node(const float3& point) const;
typename octree_type::node_type find_node(const float3& point) const;
octree32 octree;
float3 node_dimensions[octree32::max_depth + 1];
octree_type octree;
float3 node_dimensions[octree_type::max_depth + 1];
float3 center_offset; float3 center_offset;
std::unordered_map<octree32::node_type, std::list<mesh::face*>> face_map;
std::unordered_map<typename octree_type::node_type, std::list<mesh::face*>> face_map;
}; };
} // namespace geom } // namespace geom

+ 27
- 6
src/geom/octree.hpp View File

@ -21,24 +21,45 @@
#define ANTKEEPER_GEOM_OCTREE_HPP #define ANTKEEPER_GEOM_OCTREE_HPP
#include "geom/hyperoctree.hpp" #include "geom/hyperoctree.hpp"
#include <cstdint>
namespace geom { namespace geom {
/// An octree, or 3-dimensional hyperoctree. /// An octree, or 3-dimensional hyperoctree.
template <class T, std::size_t D>
using octree = hyperoctree<T, 3, D>;
template <std::unsigned_integral T, hyperoctree_order Order>
using octree = hyperoctree<T, 3, Order>;
/// Octree with an 8-bit node type (2 depth levels). /// Octree with an 8-bit node type (2 depth levels).
typedef octree<std::uint8_t, 1> octree8;
template <hyperoctree_order Order>
using octree8 = octree<std::uint8_t, Order>;
/// Octree with a 16-bit node type (4 depth levels). /// Octree with a 16-bit node type (4 depth levels).
typedef octree<std::uint16_t, 3> octree16;
template <hyperoctree_order Order>
using octree16 = octree<std::uint16_t, Order>;
/// Octree with a 32-bit node type (9 depth levels). /// Octree with a 32-bit node type (9 depth levels).
typedef octree<std::uint32_t, 8> octree32;
template <hyperoctree_order Order>
using octree32 = octree<std::uint32_t, Order>;
/// Octree with a 64-bit node type (19 depth levels). /// Octree with a 64-bit node type (19 depth levels).
typedef octree<std::uint64_t, 18> octree64;
template <hyperoctree_order Order>
using octree64 = octree<std::uint64_t, Order>;
/// Octree with unordered node storage and traversal.
template <std::unsigned_integral T>
using unordered_octree = octree<T, hyperoctree_order::unordered>;
/// Unordered octree with an 8-bit node type (2 depth levels).
typedef unordered_octree<std::uint8_t> unordered_octree8;
/// Unordered octree with a 16-bit node type (4 depth levels).
typedef unordered_octree<std::uint16_t> unordered_octree16;
/// Unordered octree with a 32-bit node type (9 depth levels).
typedef unordered_octree<std::uint32_t> unordered_octree32;
/// Unordered octree with a 64-bit node type (19 depth levels).
typedef unordered_octree<std::uint64_t>unordered_octree64;
} // namespace geom } // namespace geom

+ 27
- 6
src/geom/quadtree.hpp View File

@ -21,24 +21,45 @@
#define ANTKEEPER_GEOM_QUADTREE_HPP #define ANTKEEPER_GEOM_QUADTREE_HPP
#include "geom/hyperoctree.hpp" #include "geom/hyperoctree.hpp"
#include <cstdint>
namespace geom { namespace geom {
/// A quadtree, or 2-dimensional hyperoctree. /// A quadtree, or 2-dimensional hyperoctree.
template <class T, std::size_t D>
using quadtree = hyperoctree<T, 2, D>;
template <std::unsigned_integral T, hyperoctree_order Order>
using quadtree = hyperoctree<T, 2, Order>;
/// Quadtree with an 8-bit node type (2 depth levels). /// Quadtree with an 8-bit node type (2 depth levels).
typedef quadtree<std::uint8_t, 1> quadtree8;
template <hyperoctree_order Order>
using quadtree8 = quadtree<std::uint8_t, Order>;
/// Quadtree with a 16-bit node type (6 depth levels). /// Quadtree with a 16-bit node type (6 depth levels).
typedef quadtree<std::uint16_t, 5> quadtree16;
template <hyperoctree_order Order>
using quadtree16 = quadtree<std::uint16_t, Order>;
/// Quadtree with a 32-bit node type (13 depth levels). /// Quadtree with a 32-bit node type (13 depth levels).
typedef quadtree<std::uint32_t, 12> quadtree32;
template <hyperoctree_order Order>
using quadtree32 = quadtree<std::uint32_t, Order>;
/// Quadtree with a 64-bit node type (29 depth levels). /// Quadtree with a 64-bit node type (29 depth levels).
typedef quadtree<std::uint64_t, 28> quadtree64;
template <hyperoctree_order Order>
using quadtree64 = quadtree<std::uint64_t, Order>;
/// Quadtree with unordered node storage and traversal.
template <std::unsigned_integral T>
using unordered_quadtree = quadtree<T, hyperoctree_order::unordered>;
/// Unordered quadtree with an 8-bit node type (2 depth levels).
typedef unordered_quadtree<std::uint8_t> unordered_quadtree8;
/// Unordered quadtree with a 16-bit node type (6 depth levels).
typedef unordered_quadtree<std::uint16_t> unordered_quadtree16;
/// Unordered quadtree with a 32-bit node type (13 depth levels).
typedef unordered_quadtree<std::uint32_t> unordered_quadtree32;
/// Unordered quadtree with a 64-bit node type (29 depth levels).
typedef unordered_quadtree<std::uint64_t>unordered_quadtree64;
} // namespace geom } // namespace geom

+ 25
- 10
src/math/compile.hpp View File

@ -27,31 +27,46 @@ namespace math {
/// Compile-time mathematical functions. /// Compile-time mathematical functions.
namespace compile { namespace compile {
/** /**
* Compile-time `pow` for unsigned integrals.
* Compile-time `ceil(log2(x))` for unsigned integrals.
* *
* @param x Base value.
* @param e Integral exponent.
* @param x Input value.
* *
* @return `x^e`.
* @return `ceil(log2(x))`.
*/ */
template <std::unsigned_integral T> template <std::unsigned_integral T>
consteval T pow(T x, T e) noexcept
consteval T ceil_log2(T x) noexcept
{ {
return (e == 0) ? T(1) : (x * pow<T>(x, e - 1));
return (x <= T(1)) ? T(0) : ceil_log2((x + T(1)) / T(2)) + T(1);
} }
/** /**
* Compile-time `ceil(log2(x))` for unsigned integrals.
* Compile-time `exp2` for unsigned integrals.
* *
* @param x Input value. * @param x Input value.
* *
* @return `ceil(log2(x))`.
* @return `exp2(x)`.
*/ */
template <std::unsigned_integral T> template <std::unsigned_integral T>
consteval T ceil_log2(T x) noexcept
consteval T exp2(T x) noexcept
{ {
return (x <= T(1)) ? T(0) : ceil_log2((x + T(1)) / T(2)) + T(1);
return (x) ? T(2) << (x - 1) : T(1);
}
/**
* Compile-time `pow` for unsigned integrals.
*
* @param x Base value.
* @param e Integral exponent.
*
* @return `x^e`.
*/
template <std::unsigned_integral T>
consteval T pow(T x, T e) noexcept
{
return (e == 0) ? T(1) : (x * pow<T>(x, e - 1));
} }
} // namespace compile } // namespace compile

Loading…
Cancel
Save