Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[octree] Get neighboring cubes #385

Merged
merged 1 commit into from
Jul 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 };
movabo marked this conversation as resolved.
Show resolved Hide resolved
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);
movabo marked this conversation as resolved.
Show resolved Hide resolved
};

} // 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);
movabo marked this conversation as resolved.
Show resolved Hide resolved
};
// 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