Skip to content

Commit

Permalink
[octree] Add (face) neighbor-method
Browse files Browse the repository at this point in the history
and save the index of the cube in the parents array in `Cube::m_child_in_parent`
  • Loading branch information
movabo committed Jul 1, 2021
1 parent 8e5165e commit c45d3bf
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 13 deletions.
30 changes: 25 additions & 5 deletions include/inexor/vulkan-renderer/world/cube.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
#include <glm/geometric.hpp>
#include <glm/gtx/vector_angle.hpp>
#include <glm/vec3.hpp>
#include <spdlog/spdlog.h>

#include <array>
#include <cstdint>
#include <functional>
#include <memory>
#include <optional>
#include <utility>
#include <vector>

// forward declaration
Expand Down Expand Up @@ -42,6 +46,8 @@ class Cube : public std::enable_shared_from_this<Cube> {
static constexpr std::size_t EDGES{12};
/// Cube Type.
enum class Type { EMPTY = 0b00u, SOLID = 0b01u, NORMAL = 0b10u, OCTANT = 0b11u };
enum class NeighborAxis { X = 2, Y = 1, Z = 0 };
enum class NeighborDirection { POSITIVE, NEGATIVE };

/// IDs of the children and edges which will be swapped to receive the rotation.
/// To achieve a 90 degree rotation the 0th index have to be swapped with the 1st and the 1st with the 2nd, etc.
Expand All @@ -58,13 +64,16 @@ class Cube : public std::enable_shared_from_this<Cube> {
};

private:
Type m_type{Type::SOLID};
Type m_type{Type::EMPTY};
float m_size{32};
glm::vec3 m_position{0.0f, 0.0f, 0.0f};

/// Root cube is empty.
std::weak_ptr<Cube> m_parent{};

/// Index of this in m_parent.m_children; undefined behavior if root.
std::uint8_t m_index_in_parent{};

/// Indentations, should only be used if it is a geometry cube.
std::array<Indentation, Cube::EDGES> m_indentations;
std::array<std::shared_ptr<Cube>, Cube::SUB_CUBES> m_children;
Expand All @@ -86,12 +95,12 @@ class Cube : public std::enable_shared_from_this<Cube> {
void rotate(const RotationAxis::Type &axis);

public:
/// Create a solid cube.
/// Create an empty cube.
Cube() = default;
/// Create a solid cube.
/// Create an empty cube.
Cube(float size, const glm::vec3 &position);
/// Create a solid cube.
Cube(std::weak_ptr<Cube> parent, float size, const glm::vec3 &position);
/// Create an empty cube.
Cube(std::weak_ptr<Cube> parent, std::uint8_t index, float size, const glm::vec3 &position);
/// Use clone() to create an independent copy of a cube.
Cube(const Cube &rhs) = delete;
Cube(Cube &&rhs) noexcept;
Expand Down Expand Up @@ -161,6 +170,17 @@ class Cube : public std::enable_shared_from_this<Cube> {
/// Recursive way to collect all the caches.
/// @param update_invalid If true it will update invalid polygon caches.
[[nodiscard]] std::vector<PolygonCache> polygons(bool update_invalid = false) const;

/// Get the (face) neighbor of this cube by using a similar implementation to Samets "OT_GTEQ_FACE_NEIGHBOR(P,I)".
/// @brief Get the (face) neighbor of this cube.
/// @param axis The axis on which to get the neighboring cube
/// @param direction Whether to get the cube which is above or below this cube on the selected axis
/// @returns Same-sized neighbor if existent, else larger neighbor if exists, otherwise (i.e. when no neighbor
/// exists) nullptr.
/// @see Samet, H. (1989) [Neighbor finding in Images Represented by Octrees.]
/// (https://web.archive.org/web/20190712063957/http://www.cs.umd.edu/~hjs/pubs/SameCVGIP89.pdf)
/// Computer Vision, Graphics, and Image Processing. 46 (3), 367-386.
[[nodiscard]] std::shared_ptr<Cube> neighbor(NeighborAxis axis, NeighborDirection direction);
};

} // namespace inexor::vulkan_renderer::world
80 changes: 72 additions & 8 deletions src/vulkan-renderer/world/cube.cpp
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
#include "inexor/vulkan-renderer/world/cube.hpp"
#include "inexor/vulkan-renderer/world/indentation.hpp"

#include <spdlog/spdlog.h>

#include <algorithm>
#include <functional>
#include <utility>

void swap(inexor::vulkan_renderer::world::Cube &lhs, inexor::vulkan_renderer::world::Cube &rhs) noexcept {
std::swap(lhs.m_type, rhs.m_type);
std::swap(lhs.m_size, rhs.m_size);
std::swap(lhs.m_position, rhs.m_position);
std::swap(lhs.m_parent, rhs.m_parent);
std::swap(lhs.m_index_in_parent, rhs.m_index_in_parent);
std::swap(lhs.m_indentations, rhs.m_indentations);
std::swap(lhs.m_children, rhs.m_children);
std::swap(lhs.m_polygon_cache, rhs.m_polygon_cache);
Expand Down Expand Up @@ -176,8 +171,10 @@ void Cube::rotate<3>(const RotationAxis::Type &axis) {

Cube::Cube(const float size, const glm::vec3 &position) : m_size(size), m_position(position) {}

Cube::Cube(std::weak_ptr<Cube> parent, const float size, const glm::vec3 &position) : Cube(size, position) {
Cube::Cube(std::weak_ptr<Cube> parent, const std::uint8_t index, const float size, const glm::vec3 &position)
: Cube(size, position) {
m_parent = std::move(parent);
m_index_in_parent = index;
}

Cube::Cube(Cube &&rhs) noexcept : Cube() {
Expand All @@ -202,6 +199,7 @@ std::shared_ptr<const Cube> Cube::operator[](std::size_t idx) const {
std::shared_ptr<Cube> Cube::clone() const {
std::shared_ptr<Cube> clone = std::make_shared<Cube>(this->m_size, this->m_position);
clone->m_type = this->m_type;
clone->m_index_in_parent = this->m_index_in_parent;

if (clone->m_type == Type::NORMAL) {
clone->m_indentations = this->m_indentations;
Expand Down Expand Up @@ -259,8 +257,9 @@ void Cube::set_type(const Type new_type) {
break;
case Type::OCTANT:
const float half_size = m_size / 2;
std::uint8_t index = 0;
auto create_cube = [&](const glm::vec3 &offset) {
return std::make_shared<Cube>(weak_from_this(), half_size, m_position + offset);
return std::make_shared<Cube>(weak_from_this(), index++, half_size, m_position + offset);
};
// Look into octree documentation to find information about the order of subcubes in space.
// We can't use initializer list here because clang-tidy complains about it.
Expand Down Expand Up @@ -426,4 +425,69 @@ std::vector<PolygonCache> Cube::polygons(const bool update_invalid) const {
collect(*this);
return polygons;
}
std::shared_ptr<Cube> Cube::neighbor(const NeighborAxis axis, const NeighborDirection direction) {
if (is_root()) {
return nullptr;
}

// Each axis only requires information and manipulation of one (relevant) bit to find the neighbor.
const auto relevant_index_bit = static_cast<std::uint8_t>(axis); // bit index of the axis we are working on

auto get_bit = [&relevant_index_bit](const std::uint8_t cube_index) {
return ((cube_index >> relevant_index_bit) & 1u) != 0;
};
auto toggle_bit = [&relevant_index_bit](const std::uint8_t cube_index) {
return cube_index ^ (1u << relevant_index_bit);
};

auto parent = m_parent.lock();
const std::uint8_t index = m_index_in_parent;
const bool home_bit = get_bit(index);

// The relevant bit denotes whether `m_parent` and `this` share a face on the upper side of the relevant axis.
// If they share one and also the user wants to go in the positive direction, then the neighbor is not a sibling.
// (Same for opposite, i.e. share face on lower side and going negative direction.)
if (home_bit && direction == NeighborDirection::NEGATIVE || !home_bit && direction == NeighborDirection::POSITIVE) {
// The demanded neighbor is a sibling! Return the neighboring sibling.
return parent->m_children[toggle_bit(index)];
}
if (parent->is_root()) {
return nullptr;
}
// the neighbor is further away than a sibling

// Keep the history of indices because we just need to mirror indices (i.e. toggle the relevant bit)
// to find the desired neighboring cube.
std::vector<std::uint8_t> history;
history.push_back(index);

// Find the first cube where the bit (of `get_bit`) is different to `this_bit`.
// That cubes parent is the first mutual parent of the desired neighbor and `this`.
std::uint8_t p_index = parent->m_index_in_parent;
history.push_back(p_index);
while (get_bit(p_index) == home_bit) {
parent = parent->m_parent.lock();
if (parent->is_root()) {
return nullptr;
}
p_index = parent->m_index_in_parent;
history.push_back(p_index);
}

// get the first mutual parent of neighbor and `this`.
std::shared_ptr<Cube> child = parent->m_parent.lock();

// Now mirror the path we took by just flipping the relevant bit of each index in the history.
while (!history.empty()) {
if (child->m_type != Type::OCTANT) {
// The neighbor is larger but still a neighbor!
return std::shared_ptr<Cube>(child);
}
child = child->m_children[toggle_bit(history.back())];
history.pop_back();
}

// We found a same-sized neighbor!
return std::shared_ptr<Cube>(child);
}
} // namespace inexor::vulkan_renderer::world
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ set(INEXOR_UNIT_TEST_SOURCE_FILES
unit_tests_main.cpp

world/cube_collision.cpp
world/cube.cpp
)

add_executable(inexor-vulkan-renderer-tests ${INEXOR_UNIT_TEST_SOURCE_FILES})
Expand Down
30 changes: 30 additions & 0 deletions tests/world/cube.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#include <inexor/vulkan-renderer/world/cube.hpp>

#include <gtest/gtest.h>

namespace {
using namespace inexor::vulkan_renderer::world;

TEST(Cube, neighbor) {
std::shared_ptr<Cube> root = std::make_shared<Cube>(2.0f, glm::vec3{0, -1, -1});
root->set_type(Cube::Type::OCTANT);

for (const auto &child : root->children()) {
child->set_type(Cube::Type::OCTANT);

for (const auto &grandchild : child->children()) {
grandchild->set_type(Cube::Type::OCTANT);
}
}

EXPECT_EQ(root->children()[1]->neighbor(Cube::NeighborAxis::Y, Cube::NeighborDirection::POSITIVE),
root->children()[3]);
EXPECT_NE(root->children()[1]->neighbor(Cube::NeighborAxis::Y, Cube::NeighborDirection::POSITIVE),
root->children()[0]);
EXPECT_EQ(root->children()[1]->children()[2]->neighbor(Cube::NeighborAxis::Y, Cube::NeighborDirection::POSITIVE),
root->children()[3]->children()[0]);
EXPECT_EQ(root->children()[1]->children()[2]->neighbor(Cube::NeighborAxis::Z, Cube::NeighborDirection::NEGATIVE),
root->children()[0]->children()[3]);
}

} // namespace

0 comments on commit c45d3bf

Please sign in to comment.