diff --git a/src/game/system/terrain.cpp b/src/game/system/terrain.cpp index 7264c18..fc980f9 100644 --- a/src/game/system/terrain.cpp +++ b/src/game/system/terrain.cpp @@ -29,6 +29,7 @@ #include "math/quaternion.hpp" #include "render/vertex-attribute.hpp" #include "utility/fundamental-types.hpp" +#include "math/compile.hpp" #include #include @@ -56,7 +57,29 @@ terrain::terrain(entity::registry& registry): for (std::size_t i = 0; i <= quadtree_type::max_depth; ++i) quadtree_node_size[i] = 0.0f; - std::cout << "quadtree cap: " << quadtree.max_size() << std::endl; + + geom::quadtree64 q; + q.insert(q.node(4, 0)); + //q.insert(q.node(8, geom::morton::encode(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().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().connect<&terrain::on_terrain_construct>(this); registry.on_update().connect<&terrain::on_terrain_update>(this); diff --git a/src/game/system/terrain.hpp b/src/game/system/terrain.hpp index f54eaea..55b94d4 100644 --- a/src/game/system/terrain.hpp +++ b/src/game/system/terrain.hpp @@ -82,8 +82,8 @@ public: void set_scene_collection(scene::collection* collection); 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 { diff --git a/src/geom/hyperoctree.hpp b/src/geom/hyperoctree.hpp index 295ce15..cb8bb21 100644 --- a/src/geom/hyperoctree.hpp +++ b/src/geom/hyperoctree.hpp @@ -21,423 +21,556 @@ #define ANTKEEPER_GEOM_HYPEROCTREE_HPP #include "math/compile.hpp" +#include +#include #include -#include -#include +#include +#include +#include #include #include -#include 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 +using hyperoctree_dfs_pre_compare = std::less; + +/// @private +template +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 +struct hyperoctree_container {}; + +/// @private +template +struct hyperoctree_container +{ + typedef std::unordered_set type; +}; + +/// @private +template +struct hyperoctree_container +{ + typedef std::set> type; +}; + +/// @private +template +struct hyperoctree_container +{ + typedef std::set> type; +}; + /** * Hashed linear hyperoctree. * - * @tparam T Integer node type. + * @tparam T Unsigned integral node identifier type. * @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 https://geidav.wordpress.com/2014/08/18/advanced-octrees-2-node-representations/ */ -template +template 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::bit_width(max_depth)); + + if (depth_bits + location_bits < sizeof(T) * 8) + break; + } + return static_cast(max_depth); + } + public: - /// Integral node type. + /// Node identifier type. typedef T node_type; - /// Ensure the node type is integral - static_assert(std::is_integral::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. - 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. - static constexpr T children_per_node = (N) ? (2 << (N - 1)) : 1; + static constexpr node_type children_per_node = math::compile::exp2(N); /// 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(max_depth + 1); + + /// Number of nodes in a full hyperoctree. + static constexpr std::size_t max_node_count = (math::compile::pow(resolution, N) - 1) / siblings_per_node; + + /// Node identifier of the persistent root node. static constexpr node_type root = 0; + /// Node container type. + typedef typename hyperoctree_container::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, iterator>::type reverse_iterator; + + /// Constant reverse iterator type. + typedef std::conditional, 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::const_iterator& it): set_iterator(it) {}; - typename std::unordered_set::const_iterator set_iterator; - }; + static constexpr inline node_type depth(node_type node) noexcept + { + constexpr node_type mask = math::compile::exp2(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 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 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(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(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 nodes; -}; - -template -typename hyperoctree::iterator& hyperoctree::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::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 -inline T hyperoctree::depth(node_type node) -{ - // Extract depth using a bit mask - constexpr T mask = math::compile::pow(2, depth_bits) - 1; - return node & mask; -} - -template -inline T hyperoctree::location(node_type node) -{ - return node >> ((node_bits - 1) - depth(node) * N); -} - -template -inline typename hyperoctree::node_type hyperoctree::node(T depth, T location) -{ - return (location << ((node_bits - 1) - depth * N)) | depth; -} - -template -inline typename hyperoctree::node_type hyperoctree::ancestor(node_type node, T depth) -{ - const T mask = std::numeric_limits::max() << ((node_bits - 1) - depth * N); - return (node & mask) | depth; -} - -template -inline typename hyperoctree::node_type hyperoctree::parent(node_type node) -{ - return ancestor(node, depth(node) - 1); -} - -template -inline typename hyperoctree::node_type hyperoctree::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 -inline typename hyperoctree::node_type hyperoctree::child(node_type node, T n) -{ - return sibling(node + 1, n); -} - -template -inline typename hyperoctree::node_type hyperoctree::common_ancestor(node_type a, node_type b) -{ - T bits = std::min(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 -inline hyperoctree::hyperoctree(): - nodes({0}) -{} - -template -void hyperoctree::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 -void hyperoctree::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); - - // 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 -void hyperoctree::clear() -{ - nodes = {0}; -} - -template -inline bool hyperoctree::contains(node_type node) const -{ - return nodes.count(node); -} - -template -inline bool hyperoctree::is_leaf(node_type node) const -{ - return !contains(child(node, 0)); -} - -template -inline std::size_t hyperoctree::size() const -{ - return nodes.size(); -} - -template -typename hyperoctree::iterator hyperoctree::begin() const -{ - return iterator(this, hyperoctree::root); -} - -template -typename hyperoctree::iterator hyperoctree::end() const -{ - return iterator(this, std::numeric_limits::max()); -} - -template -typename hyperoctree::iterator hyperoctree::find(node_type node) const -{ - return contains(node) ? iterator(node) : end(); -} - -template -typename hyperoctree::unordered_iterator hyperoctree::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 -typename hyperoctree::unordered_iterator hyperoctree::unordered_end() const -{ - return unordered_iterator(nodes.end()); -} +/// Hyperoctree with unordered node storage and traversal. +template +using unordered_hyperoctree = hyperoctree; } // namespace geom diff --git a/src/geom/mesh-accelerator.cpp b/src/geom/mesh-accelerator.cpp index 588672e..de31d09 100644 --- a/src/geom/mesh-accelerator.cpp +++ b/src/geom/mesh-accelerator.cpp @@ -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; // 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((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 // 2. Find max depth node of aabb max // 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 octree.insert(containing_node); @@ -92,7 +92,7 @@ std::optional mesh_accelerator::query_neares return std::nullopt; } -void mesh_accelerator::query_nearest_recursive(float& nearest_t, geom::mesh::face*& nearest_face, octree32::node_type node, const ray& ray) const +void mesh_accelerator::query_nearest_recursive(float& nearest_t, geom::mesh::face*& nearest_face, typename octree_type::node_type node, const ray& ray) const { // Get node bounds const aabb node_bounds = get_node_bounds(node); @@ -138,22 +138,22 @@ void mesh_accelerator::query_nearest_recursive(float& nearest_t, geom::mesh::fac } } -aabb mesh_accelerator::get_node_bounds(octree32::node_type node) const +aabb mesh_accelerator::get_node_bounds(typename octree_type::node_type node) const { // Decode Morton location of node 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(x), static_cast(y), static_cast(z)}; // 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 float3 min_point = (node_location * dimensions) - center_offset; return aabb{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 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(0.0f, std::min(node_dimensions[0].z() - epsilon, transformed_point.z())); // 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 std::uint32_t location = morton::encode( @@ -174,7 +174,7 @@ octree32::node_type mesh_accelerator::find_node(const float3& point) const static_cast(transformed_point.z())); // 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 diff --git a/src/geom/mesh-accelerator.hpp b/src/geom/mesh-accelerator.hpp index a28cc14..e2c8b32 100644 --- a/src/geom/mesh-accelerator.hpp +++ b/src/geom/mesh-accelerator.hpp @@ -59,17 +59,19 @@ public: std::optional query_nearest(const ray& ray) const; private: - aabb get_node_bounds(octree32::node_type node) const; + typedef unordered_octree32 octree_type; + + aabb 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& ray) const; + void query_nearest_recursive(float& nearest_t, geom::mesh::face*& nearest_face, typename octree_type::node_type node, const ray& ray) const; /// 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; - std::unordered_map> face_map; + std::unordered_map> face_map; }; } // namespace geom diff --git a/src/geom/octree.hpp b/src/geom/octree.hpp index 18f32d7..041babb 100644 --- a/src/geom/octree.hpp +++ b/src/geom/octree.hpp @@ -21,24 +21,45 @@ #define ANTKEEPER_GEOM_OCTREE_HPP #include "geom/hyperoctree.hpp" +#include namespace geom { /// An octree, or 3-dimensional hyperoctree. -template -using octree = hyperoctree; +template +using octree = hyperoctree; /// Octree with an 8-bit node type (2 depth levels). -typedef octree octree8; +template +using octree8 = octree; /// Octree with a 16-bit node type (4 depth levels). -typedef octree octree16; +template +using octree16 = octree; /// Octree with a 32-bit node type (9 depth levels). -typedef octree octree32; +template +using octree32 = octree; /// Octree with a 64-bit node type (19 depth levels). -typedef octree octree64; +template +using octree64 = octree; + +/// Octree with unordered node storage and traversal. +template +using unordered_octree = octree; + +/// Unordered octree with an 8-bit node type (2 depth levels). +typedef unordered_octree unordered_octree8; + +/// Unordered octree with a 16-bit node type (4 depth levels). +typedef unordered_octree unordered_octree16; + +/// Unordered octree with a 32-bit node type (9 depth levels). +typedef unordered_octree unordered_octree32; + +/// Unordered octree with a 64-bit node type (19 depth levels). +typedef unordered_octreeunordered_octree64; } // namespace geom diff --git a/src/geom/quadtree.hpp b/src/geom/quadtree.hpp index 646e836..f23bade 100644 --- a/src/geom/quadtree.hpp +++ b/src/geom/quadtree.hpp @@ -21,24 +21,45 @@ #define ANTKEEPER_GEOM_QUADTREE_HPP #include "geom/hyperoctree.hpp" +#include namespace geom { /// A quadtree, or 2-dimensional hyperoctree. -template -using quadtree = hyperoctree; +template +using quadtree = hyperoctree; /// Quadtree with an 8-bit node type (2 depth levels). -typedef quadtree quadtree8; +template +using quadtree8 = quadtree; /// Quadtree with a 16-bit node type (6 depth levels). -typedef quadtree quadtree16; +template +using quadtree16 = quadtree; /// Quadtree with a 32-bit node type (13 depth levels). -typedef quadtree quadtree32; +template +using quadtree32 = quadtree; /// Quadtree with a 64-bit node type (29 depth levels). -typedef quadtree quadtree64; +template +using quadtree64 = quadtree; + +/// Quadtree with unordered node storage and traversal. +template +using unordered_quadtree = quadtree; + +/// Unordered quadtree with an 8-bit node type (2 depth levels). +typedef unordered_quadtree unordered_quadtree8; + +/// Unordered quadtree with a 16-bit node type (6 depth levels). +typedef unordered_quadtree unordered_quadtree16; + +/// Unordered quadtree with a 32-bit node type (13 depth levels). +typedef unordered_quadtree unordered_quadtree32; + +/// Unordered quadtree with a 64-bit node type (29 depth levels). +typedef unordered_quadtreeunordered_quadtree64; } // namespace geom diff --git a/src/math/compile.hpp b/src/math/compile.hpp index 8280668..be82d65 100644 --- a/src/math/compile.hpp +++ b/src/math/compile.hpp @@ -27,31 +27,46 @@ namespace math { /// Compile-time mathematical functions. 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 -consteval T pow(T x, T e) noexcept +consteval T ceil_log2(T x) noexcept { - return (e == 0) ? T(1) : (x * pow(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. * - * @return `ceil(log2(x))`. + * @return `exp2(x)`. */ template -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 +consteval T pow(T x, T e) noexcept +{ + return (e == 0) ? T(1) : (x * pow(x, e - 1)); } } // namespace compile