diff options
-rw-r--r-- | compat/LooseQuadtree-impl.h | 137 | ||||
-rw-r--r-- | compat/LooseQuadtree.cpp | 140 | ||||
-rw-r--r-- | compat/LooseQuadtree.h | 6 | ||||
-rw-r--r-- | serialize/world-impl.hpp | 12 | ||||
-rw-r--r-- | serialize/world-reader.cpp | 4 | ||||
-rw-r--r-- | serialize/world-writer.cpp | 4 | ||||
-rw-r--r-- | src/CMakeLists.txt | 2 | ||||
-rw-r--r-- | src/chunk-render.cpp | 107 | ||||
-rw-r--r-- | src/chunk.cpp | 183 | ||||
-rw-r--r-- | src/chunk.hpp | 22 | ||||
-rw-r--r-- | src/pass-mode.hpp | 5 | ||||
-rw-r--r-- | src/scenery.hpp | 2 | ||||
-rw-r--r-- | src/tile-atlas.cpp | 1 | ||||
-rw-r--r-- | src/tile-atlas.hpp | 1 | ||||
-rw-r--r-- | src/tile.cpp | 34 | ||||
-rw-r--r-- | src/tile.hpp | 17 | ||||
-rw-r--r-- | test/app.hpp | 5 | ||||
-rw-r--r-- | test/bbox.cpp | 28 | ||||
-rw-r--r-- | test/main.cpp | 1 | ||||
-rw-r--r-- | test/serializer.cpp | 2 |
20 files changed, 394 insertions, 319 deletions
diff --git a/compat/LooseQuadtree-impl.h b/compat/LooseQuadtree-impl.h index 3ad64033..4d37bcd0 100644 --- a/compat/LooseQuadtree-impl.h +++ b/compat/LooseQuadtree-impl.h @@ -113,138 +113,6 @@ struct BlocksAllocatorAdaptor { }; -BlocksAllocator::BlocksAllocator() {} - - -BlocksAllocator::~BlocksAllocator() { - for (auto& size_block_pair : size_to_blocks_) { - BlocksHead& blocks_head = size_block_pair.second; - for (auto& address_empty_pair : blocks_head.address_to_empty_slot_number) { - assert(address_empty_pair.second == blocks_head.slots_in_a_block_); - Block* block = address_empty_pair.first; - delete block; - } - } -} - - -void* BlocksAllocator::Allocate(std::size_t object_size) { -#ifdef LQT_USE_OWN_ALLOCATOR - if (object_size < sizeof(void*)) object_size = sizeof(void*); - assert(object_size <= kMaxAllowedAlloc); - auto size_blocks_it = size_to_blocks_.find(object_size); - if (size_blocks_it == size_to_blocks_.end()) { - size_blocks_it = size_to_blocks_.emplace(object_size, object_size).first; - } - BlocksHead& blocks_head = size_blocks_it->second; - assert(blocks_head.slots_in_a_block_ == kBlockSize / object_size); - if (blocks_head.first_empty_slot == nullptr) { - Block* new_block = new Block(); - std::size_t empties = blocks_head.slots_in_a_block_; - blocks_head.address_to_empty_slot_number.emplace(new_block, empties); - blocks_head.first_empty_slot = reinterpret_cast<void*>(new_block); - void* current_slot = reinterpret_cast<void*>(new_block); - empties--; - while (empties > 0) { - void* next_slot = - reinterpret_cast<void*>(reinterpret_cast<char*>(current_slot) - + object_size); - *reinterpret_cast<void**>(current_slot) = next_slot; - current_slot = next_slot; - empties--; - } - *reinterpret_cast<void**>(current_slot) = nullptr; - } - assert(blocks_head.first_empty_slot != nullptr); - void* slot = blocks_head.first_empty_slot; - blocks_head.first_empty_slot = *reinterpret_cast<void**>(slot); - auto address_empties_it = - blocks_head.address_to_empty_slot_number.upper_bound(reinterpret_cast<Block*>(slot)); - assert(address_empties_it != blocks_head.address_to_empty_slot_number.begin()); - address_empties_it--; - assert(address_empties_it->first <= reinterpret_cast<Block*>(slot) && - (std::size_t)(reinterpret_cast<char*>(slot) - - reinterpret_cast<char*>(address_empties_it->first)) < kBlockSize); - assert((std::size_t)(reinterpret_cast<char*>(slot) - - reinterpret_cast<char*>(address_empties_it->first)) % object_size == 0); - assert(address_empties_it->second > 0 && - address_empties_it->second <= blocks_head.slots_in_a_block_); - address_empties_it->second--; - return slot; -#else - return reinterpret_cast<void*>(new char[object_size]); -#endif -} - - -void BlocksAllocator::Deallocate(void* p, std::size_t object_size) { -#ifdef LQT_USE_OWN_ALLOCATOR - if (object_size < sizeof(void*)) object_size = sizeof(void*); - assert(object_size <= kMaxAllowedAlloc); - auto size_blocks_it = size_to_blocks_.find(object_size); - assert(size_blocks_it != size_to_blocks_.end()); - BlocksHead& blocks_head = size_blocks_it->second; - assert(blocks_head.slots_in_a_block_ == kBlockSize / object_size); - auto address_empties_it = - blocks_head.address_to_empty_slot_number.upper_bound(reinterpret_cast<Block*>(p)); - assert(address_empties_it != blocks_head.address_to_empty_slot_number.begin()); - address_empties_it--; - assert(address_empties_it->first <= reinterpret_cast<Block*>(p) && - (std::size_t)(reinterpret_cast<char*>(p) - - reinterpret_cast<char*>(address_empties_it->first)) < kBlockSize); - assert((std::size_t)(reinterpret_cast<char*>(p) - - reinterpret_cast<char*>(address_empties_it->first)) % object_size == 0); - assert(address_empties_it->second < blocks_head.slots_in_a_block_); - void* slot = p; - *reinterpret_cast<void**>(slot) = blocks_head.first_empty_slot; - blocks_head.first_empty_slot = slot; - address_empties_it->second++; - assert(address_empties_it->second > 0 && - address_empties_it->second <= blocks_head.slots_in_a_block_); -#else - (void)object_size; - delete[] reinterpret_cast<char*>(p); -#endif -} - - -void BlocksAllocator::ReleaseFreeBlocks() { - for (auto& size_block_pair : size_to_blocks_) { - BlocksHead& blocks_head = size_block_pair.second; - void** current = &blocks_head.first_empty_slot; - while (*current != nullptr) { - auto address_empties_it = - blocks_head.address_to_empty_slot_number.upper_bound(reinterpret_cast<Block*>(*current)); - assert(address_empties_it != blocks_head.address_to_empty_slot_number.begin()); - address_empties_it--; - assert(address_empties_it->first <= reinterpret_cast<Block*>(*current) && - (std::size_t)(reinterpret_cast<char*>(*current) - - reinterpret_cast<char*>(address_empties_it->first)) < kBlockSize); - assert((std::size_t)(reinterpret_cast<char*>(*current) - - reinterpret_cast<char*>(address_empties_it->first)) % size_block_pair.first == 0); - assert(address_empties_it->second > 0 && - address_empties_it->second <= blocks_head.slots_in_a_block_); - if (address_empties_it->second >= blocks_head.slots_in_a_block_) { - *current = **(void***)current; - } - else { - current = *(void***)current; - } - } - auto address_empties_it = blocks_head.address_to_empty_slot_number.begin(); - while (address_empties_it != blocks_head.address_to_empty_slot_number.end()) { - if (address_empties_it->second >= blocks_head.slots_in_a_block_) { - auto prev_address_empties_it = address_empties_it; - address_empties_it++; - blocks_head.address_to_empty_slot_number.erase(prev_address_empties_it); - } - else { - address_empties_it++; - } - } - } -} - template <typename T, typename... Args> T* BlocksAllocator::New(Args&&... args) { @@ -494,9 +362,6 @@ private: - - - template <typename NumberT> bool BoundingBox<NumberT>::Intersects(const BoundingBox<Number>& other) const { return !( @@ -520,7 +385,7 @@ bool BoundingBox<NumberT>::Contains(Number x, Number y) const { template <typename NumberT, typename ObjectT> - detail::ForwardTreeTraversal<NumberT, ObjectT>::ForwardTreeTraversal() : +detail::ForwardTreeTraversal<NumberT, ObjectT>::ForwardTreeTraversal() : position_(BoundingBox<Number>(0, 0, 0, 0), nullptr), depth_(0) { } diff --git a/compat/LooseQuadtree.cpp b/compat/LooseQuadtree.cpp new file mode 100644 index 00000000..7017b0ac --- /dev/null +++ b/compat/LooseQuadtree.cpp @@ -0,0 +1,140 @@ +#include "LooseQuadtree-impl.h" +#include "compat/assert.hpp" +#undef assert +#define assert fm_assert + +namespace loose_quadtree::detail { + +BlocksAllocator::BlocksAllocator() {} + + +BlocksAllocator::~BlocksAllocator() { + for (auto& size_block_pair : size_to_blocks_) { + BlocksHead& blocks_head = size_block_pair.second; + for (auto& address_empty_pair : blocks_head.address_to_empty_slot_number) { + assert(address_empty_pair.second == blocks_head.slots_in_a_block_); + Block* block = address_empty_pair.first; + delete block; + } + } +} + + +void* BlocksAllocator::Allocate(std::size_t object_size) { +#ifdef LQT_USE_OWN_ALLOCATOR + if (object_size < sizeof(void*)) object_size = sizeof(void*); + assert(object_size <= kMaxAllowedAlloc); + auto size_blocks_it = size_to_blocks_.find(object_size); + if (size_blocks_it == size_to_blocks_.end()) { + size_blocks_it = size_to_blocks_.emplace(object_size, object_size).first; + } + BlocksHead& blocks_head = size_blocks_it->second; + assert(blocks_head.slots_in_a_block_ == kBlockSize / object_size); + if (blocks_head.first_empty_slot == nullptr) { + Block* new_block = new Block(); + std::size_t empties = blocks_head.slots_in_a_block_; + blocks_head.address_to_empty_slot_number.emplace(new_block, empties); + blocks_head.first_empty_slot = reinterpret_cast<void*>(new_block); + void* current_slot = reinterpret_cast<void*>(new_block); + empties--; + while (empties > 0) { + void* next_slot = + reinterpret_cast<void*>(reinterpret_cast<char*>(current_slot) + + object_size); + *reinterpret_cast<void**>(current_slot) = next_slot; + current_slot = next_slot; + empties--; + } + *reinterpret_cast<void**>(current_slot) = nullptr; + } + assert(blocks_head.first_empty_slot != nullptr); + void* slot = blocks_head.first_empty_slot; + blocks_head.first_empty_slot = *reinterpret_cast<void**>(slot); + auto address_empties_it = + blocks_head.address_to_empty_slot_number.upper_bound(reinterpret_cast<Block*>(slot)); + assert(address_empties_it != blocks_head.address_to_empty_slot_number.begin()); + address_empties_it--; + assert(address_empties_it->first <= reinterpret_cast<Block*>(slot) && + (std::size_t)(reinterpret_cast<char*>(slot) - + reinterpret_cast<char*>(address_empties_it->first)) < kBlockSize); + assert((std::size_t)(reinterpret_cast<char*>(slot) - + reinterpret_cast<char*>(address_empties_it->first)) % object_size == 0); + assert(address_empties_it->second > 0 && + address_empties_it->second <= blocks_head.slots_in_a_block_); + address_empties_it->second--; + return slot; +#else + return reinterpret_cast<void*>(new char[object_size]); +#endif +} + + +void BlocksAllocator::Deallocate(void* p, std::size_t object_size) { +#ifdef LQT_USE_OWN_ALLOCATOR + if (object_size < sizeof(void*)) object_size = sizeof(void*); + assert(object_size <= kMaxAllowedAlloc); + auto size_blocks_it = size_to_blocks_.find(object_size); + assert(size_blocks_it != size_to_blocks_.end()); + BlocksHead& blocks_head = size_blocks_it->second; + assert(blocks_head.slots_in_a_block_ == kBlockSize / object_size); + auto address_empties_it = + blocks_head.address_to_empty_slot_number.upper_bound(reinterpret_cast<Block*>(p)); + assert(address_empties_it != blocks_head.address_to_empty_slot_number.begin()); + address_empties_it--; + assert(address_empties_it->first <= reinterpret_cast<Block*>(p) && + (std::size_t)(reinterpret_cast<char*>(p) - + reinterpret_cast<char*>(address_empties_it->first)) < kBlockSize); + assert((std::size_t)(reinterpret_cast<char*>(p) - + reinterpret_cast<char*>(address_empties_it->first)) % object_size == 0); + assert(address_empties_it->second < blocks_head.slots_in_a_block_); + void* slot = p; + *reinterpret_cast<void**>(slot) = blocks_head.first_empty_slot; + blocks_head.first_empty_slot = slot; + address_empties_it->second++; + assert(address_empties_it->second > 0 && + address_empties_it->second <= blocks_head.slots_in_a_block_); +#else + (void)object_size; + delete[] reinterpret_cast<char*>(p); +#endif +} + + +void BlocksAllocator::ReleaseFreeBlocks() { + for (auto& size_block_pair : size_to_blocks_) { + BlocksHead& blocks_head = size_block_pair.second; + void** current = &blocks_head.first_empty_slot; + while (*current != nullptr) { + auto address_empties_it = + blocks_head.address_to_empty_slot_number.upper_bound(reinterpret_cast<Block*>(*current)); + assert(address_empties_it != blocks_head.address_to_empty_slot_number.begin()); + address_empties_it--; + assert(address_empties_it->first <= reinterpret_cast<Block*>(*current) && + (std::size_t)(reinterpret_cast<char*>(*current) - + reinterpret_cast<char*>(address_empties_it->first)) < kBlockSize); + assert((std::size_t)(reinterpret_cast<char*>(*current) - + reinterpret_cast<char*>(address_empties_it->first)) % size_block_pair.first == 0); + assert(address_empties_it->second > 0 && + address_empties_it->second <= blocks_head.slots_in_a_block_); + if (address_empties_it->second >= blocks_head.slots_in_a_block_) { + *current = **(void***)current; + } + else { + current = *(void***)current; + } + } + auto address_empties_it = blocks_head.address_to_empty_slot_number.begin(); + while (address_empties_it != blocks_head.address_to_empty_slot_number.end()) { + if (address_empties_it->second >= blocks_head.slots_in_a_block_) { + auto prev_address_empties_it = address_empties_it; + address_empties_it++; + blocks_head.address_to_empty_slot_number.erase(prev_address_empties_it); + } + else { + address_empties_it++; + } + } + } +} + +} // namespace loose_quadtree::detail diff --git a/compat/LooseQuadtree.h b/compat/LooseQuadtree.h index 3f52457a..a8544346 100644 --- a/compat/LooseQuadtree.h +++ b/compat/LooseQuadtree.h @@ -38,11 +38,13 @@ template <typename NumberT> struct BoundingBox { using Number = NumberT; - BoundingBox(Number _left, Number _top, Number _width, Number _height) : + constexpr BoundingBox(Number _left, Number _top, Number _width, Number _height) : left(_left), top(_top), width(_width), height(_height) {} bool Intersects(const BoundingBox<Number>& other) const ; bool Contains(const BoundingBox<Number>& other) const; bool Contains(Number x, Number y) const; + BoundingBox& operator=(const BoundingBox&) noexcept = default; + BoundingBox(const BoundingBox&) noexcept = default; Number left; Number top; @@ -107,6 +109,4 @@ private: Impl impl_; }; - - } //loose_quadtree diff --git a/serialize/world-impl.hpp b/serialize/world-impl.hpp index 99db4cc0..e74ebda2 100644 --- a/serialize/world-impl.hpp +++ b/serialize/world-impl.hpp @@ -54,12 +54,12 @@ constexpr inline atlasid scenery_id_max = int_max<atlasid> & ~scenery_id_flag_ma } // namespace enum : tilemeta { - meta_ground = 1 << (pass_bits + 0), - meta_wall_n = 1 << (pass_bits + 1), - meta_wall_w = 1 << (pass_bits + 2), - meta_short_atlasid = 1 << (pass_bits + 3), - meta_short_variant_ = 1 << (pass_bits + 4), - meta_scenery = 1 << (pass_bits + 5), + meta_ground = 1 << 2, + meta_wall_n = 1 << 3, + meta_wall_w = 1 << 4, + meta_short_atlasid = 1 << 5, + meta_short_variant_ = 1 << 6, + meta_scenery = 1 << 7, }; } // namespace floormat::Serialize diff --git a/serialize/world-reader.cpp b/serialize/world-reader.cpp index 98704599..f5459203 100644 --- a/serialize/world-reader.cpp +++ b/serialize/world-reader.cpp @@ -149,7 +149,7 @@ void reader_state::read_chunks(reader_t& s) return { atlas, v }; }; - t.passability() = pass_mode(flags & pass_mask); + //t.passability() = pass_mode(flags & pass_mask); if (flags & meta_ground) t.ground() = make_atlas(); if (flags & meta_wall_n) @@ -178,6 +178,7 @@ void reader_state::read_chunks(reader_t& s) t.scenery() = sc; } +#if 0 switch (auto x = pass_mode(flags & pass_mask)) { case pass_mode::shoot_through: @@ -188,6 +189,7 @@ void reader_state::read_chunks(reader_t& s) default: [[unlikely]] fm_throw("bad pass mode '{}' for tile {}"_cf, i, pass_mode_(x)); } +#endif } } } diff --git a/serialize/world-writer.cpp b/serialize/world-writer.cpp index 0ca84e7b..21fba2b3 100644 --- a/serialize/world-writer.cpp +++ b/serialize/world-writer.cpp @@ -319,8 +319,8 @@ void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) if (flags != 0 && ashortp(img_g) && ashortp(img_n) && ashortp(img_w)) flags |= meta_short_atlasid; - fm_debug_assert((pass_mode_(x.passability) & pass_mask) == pass_mode_(x.passability)); - flags |= pass_mode_(x.passability); + //fm_debug_assert((pass_mode_(x.passability) & pass_mask) == pass_mode_(x.passability)); + //flags |= pass_mode_(x.passability); s << flags; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d30bf88d..b7ef8bd6 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,5 +1,5 @@ set(self floormat) -file(GLOB sources "*.cpp" "../shaders/*.cpp" CONFIGURE_ARGS) +file(GLOB sources *.cpp ../shaders/*.cpp ../compat/*.cpp CONFIGURE_ARGS) add_library(${self} OBJECT "${sources}") target_link_libraries( ${self} PUBLIC diff --git a/src/chunk-render.cpp b/src/chunk-render.cpp new file mode 100644 index 00000000..37440af9 --- /dev/null +++ b/src/chunk-render.cpp @@ -0,0 +1,107 @@ +#include "chunk.hpp" +#include "tile-atlas.hpp" +#include "shaders/tile.hpp" +#include <algorithm> +#include <Magnum/GL/Buffer.h> +#include <Corrade/Containers/ArrayViewStl.h> + +namespace floormat { + +static auto make_index_array(std::size_t offset) +{ + std::array<std::array<UnsignedShort, 6>, TILE_COUNT> array; // NOLINT(cppcoreguidelines-pro-type-member-init) + for (std::size_t i = 0; i < TILE_COUNT; i++) + array[i] = tile_atlas::indices(i + offset); + return array; +} + +struct vertex { + Vector3 position; + Vector2 texcoords; + float depth = -1; +}; + +auto chunk::ensure_ground_mesh() noexcept -> ground_mesh_tuple +{ + if (!_ground_modified) + return { ground_mesh, ground_indexes }; + _ground_modified = false; + + for (std::size_t i = 0; i < TILE_COUNT; i++) + ground_indexes[i] = std::uint8_t(i); + std::sort(ground_indexes.begin(), ground_indexes.end(), [this](std::uint8_t a, std::uint8_t b) { + return _ground_atlases[a].get() < _ground_atlases[b].get(); + }); + + std::array<std::array<vertex, 4>, TILE_COUNT> vertexes; + for (std::size_t k = 0; k < TILE_COUNT; k++) + { + const std::uint8_t i = ground_indexes[k]; + if (auto atlas = _ground_atlases[i]; !atlas) + vertexes[k] = {}; + else + { + const local_coords pos{i}; + const auto quad = atlas->floor_quad(Vector3(pos.x, pos.y, 0) * TILE_SIZE, TILE_SIZE2); + const auto texcoords = atlas->texcoords_for_id(_ground_variants[i]); + const float depth = tile_shader::depth_value(pos); + auto& v = vertexes[k]; + for (std::size_t j = 0; j < 4; j++) + v[j] = { quad[j], texcoords[j], depth }; + } + } + const auto indexes = make_index_array(0); + + GL::Mesh mesh{GL::MeshPrimitive::Triangles}; + mesh.addVertexBuffer(GL::Buffer{vertexes}, 0, tile_shader::Position{}, tile_shader::TextureCoordinates{}, tile_shader::Depth{}) + .setIndexBuffer(GL::Buffer{indexes}, 0, GL::MeshIndexType::UnsignedShort) + .setCount(6 * TILE_COUNT); + ground_mesh = Utility::move(mesh); + return { ground_mesh, ground_indexes }; +} + +auto chunk::ensure_wall_mesh() noexcept -> wall_mesh_tuple +{ + if (!_walls_modified) + return { wall_mesh, wall_indexes }; + _walls_modified = false; + + for (std::size_t i = 0; i < TILE_COUNT*2; i++) + wall_indexes[i] = std::uint16_t(i); + + std::sort(wall_indexes.begin(), wall_indexes.end(), [this](std::uint16_t a, std::uint16_t b) { + return _wall_atlases[a] < _wall_atlases[b]; + }); + + std::array<std::array<vertex, 4>, TILE_COUNT*2> vertexes; + for (std::size_t k = 0; k < TILE_COUNT*2; k++) + { + const std::uint16_t i = wall_indexes[k]; + if (const auto& atlas = _wall_atlases[i]; !atlas) + vertexes[k] = {}; + else + { + const auto& variant = _wall_variants[i]; + const local_coords pos{i / 2u}; + const auto center = Vector3(pos.x, pos.y, 0) * TILE_SIZE; + const auto quad = i & 1 ? atlas->wall_quad_W(center, TILE_SIZE) : atlas->wall_quad_N(center, TILE_SIZE); + const float depth = tile_shader::depth_value(pos); + const auto texcoords = atlas->texcoords_for_id(variant); + auto& v = vertexes[k]; + for (std::size_t j = 0; j < 4; j++) + v[j] = { quad[j], texcoords[j], depth, }; + } + } + + using index_t = std::array<std::array<UnsignedShort, 6>, TILE_COUNT>; + const index_t indexes[2] = { make_index_array(0), make_index_array(TILE_COUNT) }; + + GL::Mesh mesh{GL::MeshPrimitive::Triangles}; + mesh.addVertexBuffer(GL::Buffer{vertexes}, 0, tile_shader::Position{}, tile_shader::TextureCoordinates{}, tile_shader::Depth{}) + .setIndexBuffer(GL::Buffer{indexes}, 0, GL::MeshIndexType::UnsignedShort) + .setCount(6 * TILE_COUNT); + wall_mesh = Utility::move(mesh); + return { wall_mesh, wall_indexes }; +} + +} // namespace floormat diff --git a/src/chunk.cpp b/src/chunk.cpp index 55afe1b9..e66b193a 100644 --- a/src/chunk.cpp +++ b/src/chunk.cpp @@ -1,9 +1,6 @@ #include "chunk.hpp" -#include "tile-atlas.hpp" -#include "shaders/tile.hpp" -#include <algorithm> -#include <Corrade/Containers/ArrayViewStl.h> -#include <Magnum/GL/Buffer.h> +#include "compat/LooseQuadtree-impl.h" +#include "src/tile-atlas.hpp" namespace floormat { @@ -27,133 +24,97 @@ bool chunk::empty(bool force) const noexcept tile_atlas* chunk::ground_atlas_at(std::size_t i) const noexcept { return _ground_atlases[i].get(); } tile_atlas* chunk::wall_atlas_at(std::size_t i) const noexcept { return _wall_atlases[i].get(); } -static auto make_index_array(std::size_t offset) -{ - std::array<std::array<UnsignedShort, 6>, TILE_COUNT> array; // NOLINT(cppcoreguidelines-pro-type-member-init) - for (std::size_t i = 0; i < TILE_COUNT; i++) - array[i] = tile_atlas::indices(i + offset); - return array; -} +tile_ref chunk::operator[](std::size_t idx) noexcept { return { *this, std::uint8_t(idx) }; } +tile_proto chunk::operator[](std::size_t idx) const noexcept { return tile_proto(tile_ref { *const_cast<chunk*>(this), std::uint8_t(idx) }); } +tile_ref chunk::operator[](local_coords xy) noexcept { return operator[](xy.to_index()); } +tile_proto chunk::operator[](local_coords xy) const noexcept { return operator[](xy.to_index()); } + +auto chunk::begin() noexcept -> iterator { return iterator { *this, 0 }; } +auto chunk::end() noexcept -> iterator { return iterator { *this, TILE_COUNT }; } +auto chunk::cbegin() const noexcept -> const_iterator { return const_iterator { *this, 0 }; } +auto chunk::cend() const noexcept -> const_iterator { return const_iterator { *this, TILE_COUNT }; } +auto chunk::begin() const noexcept -> const_iterator { return cbegin(); } +auto chunk::end() const noexcept -> const_iterator { return cend(); } -struct vertex { - Vector3 position; - Vector2 texcoords; - float depth = -1; -}; +void chunk::mark_ground_modified() noexcept { _ground_modified = true; _pass_modified = true; } +void chunk::mark_walls_modified() noexcept { _walls_modified = true; _pass_modified = true; } +void chunk::mark_scenery_modified() noexcept { _scenery_modified = true; _pass_modified = true; } -auto chunk::ensure_ground_mesh() noexcept -> ground_mesh_tuple +void chunk::mark_modified() noexcept { - if (!_ground_modified) - return { ground_mesh, ground_indexes }; - _ground_modified = false; + mark_ground_modified(); + mark_walls_modified(); + mark_scenery_modified(); +} - for (std::size_t i = 0; i < TILE_COUNT; i++) - ground_indexes[i] = std::uint8_t(i); - std::sort(ground_indexes.begin(), ground_indexes.end(), [this](std::uint8_t a, std::uint8_t b) { - return _ground_atlases[a].get() < _ground_atlases[b].get(); - }); +static constexpr auto tile_size2us = Vector2us(iTILE_SIZE2); - std::array<std::array<vertex, 4>, TILE_COUNT> vertexes; - for (std::size_t k = 0; k < TILE_COUNT; k++) - { - const std::uint8_t i = ground_indexes[k]; - if (auto atlas = _ground_atlases[i]; !atlas) - vertexes[k] = {}; - else - { - const local_coords pos{i}; - const auto quad = atlas->floor_quad(Vector3(pos.x, pos.y, 0) * TILE_SIZE, TILE_SIZE2); - const auto texcoords = atlas->texcoords_for_id(_ground_variants[i]); - const float depth = tile_shader::depth_value(pos); - auto& v = vertexes[k]; - for (std::size_t j = 0; j < 4; j++) - v[j] = { quad[j], texcoords[j], depth }; - } - } - const auto indexes = make_index_array(0); - - GL::Mesh mesh{GL::MeshPrimitive::Triangles}; - mesh.addVertexBuffer(GL::Buffer{vertexes}, 0, tile_shader::Position{}, tile_shader::TextureCoordinates{}, tile_shader::Depth{}) - .setIndexBuffer(GL::Buffer{indexes}, 0, GL::MeshIndexType::UnsignedShort) - .setCount(6 * TILE_COUNT); - ground_mesh = Utility::move(mesh); - return { ground_mesh, ground_indexes }; +static constexpr Vector2s tile_start(std::size_t k) +{ + const auto i = std::uint8_t(k); + const local_coords coord{i}; + constexpr auto tile_size2s = Vector2s(tile_size2us), half = tile_size2s/2; + return tile_size2s * Vector2s(coord.x, coord.y) - half; } -auto chunk::ensure_wall_mesh() noexcept -> wall_mesh_tuple +auto chunk::ensure_passability() noexcept -> lqt& { - if (!_walls_modified) - return { wall_mesh, wall_indexes }; - _walls_modified = false; + auto& qt = *_static_lqt; + + if (!_pass_modified) + return qt; + _pass_modified = false; - for (std::size_t i = 0; i < TILE_COUNT*2; i++) - wall_indexes[i] = std::uint16_t(i); + qt.Clear(); + _lqt_bboxes.clear(); + _lqt_bboxes.reserve(32); - std::sort(wall_indexes.begin(), wall_indexes.end(), [this](std::uint16_t a, std::uint16_t b) { - return _wall_atlases[a] < _wall_atlases[b]; - }); + constexpr auto whole_tile = [](std::size_t k, pass_mode p) constexpr -> bbox { + auto start = tile_start(k); + return { start[0], start[1], tile_size2us[0], tile_size2us[1], p }; + }; - std::array<std::array<vertex, 4>, TILE_COUNT*2> vertexes; - for (std::size_t k = 0; k < TILE_COUNT*2; k++) + constexpr auto wall_north = [](std::size_t k, pass_mode p) constexpr -> bbox { + auto start = tile_start(k) - Vector2s(0, 1); + return { start[0], start[1], tile_size2us[0], 2, p }; + }; + + constexpr auto wall_west = [](std::size_t k, pass_mode p) constexpr -> bbox { + auto start = tile_start(k) - Vector2s(1, 0); + return { start[0], start[1], 2, tile_size2us[1], p }; + }; + + for (std::size_t i = 0; i < TILE_COUNT; i++) { - const std::uint16_t i = wall_indexes[k]; - if (const auto& atlas = _wall_atlases[i]; !atlas) - vertexes[k] = {}; - else - { - const auto& variant = _wall_variants[i]; - const local_coords pos{i / 2u}; - const auto center = Vector3(pos.x, pos.y, 0) * TILE_SIZE; - const auto quad = i & 1 ? atlas->wall_quad_W(center, TILE_SIZE) : atlas->wall_quad_N(center, TILE_SIZE); - const float depth = tile_shader::depth_value(pos); - const auto texcoords = atlas->texcoords_for_id(variant); - auto& v = vertexes[k]; - for (std::size_t j = 0; j < 4; j++) - v[j] = { quad[j], texcoords[j], depth, }; - } + const auto tile = const_cast<chunk&>(*this)[i]; + if (auto s = tile.scenery()) + if (auto p = s.frame.passability; p != pass_mode::pass) + _lqt_bboxes.push_back(whole_tile(i, p)); + if (auto atlas = tile.ground_atlas()) + if (auto p = atlas->pass_mode(pass_mode::pass); p != pass_mode::pass) + _lqt_bboxes.push_back(whole_tile(i, p)); + if (auto atlas = tile.wall_north_atlas()) + if (auto p = atlas->pass_mode(pass_mode::blocked); p != pass_mode::pass) + _lqt_bboxes.push_back(wall_north(i, p)); + if (auto atlas = tile.wall_west_atlas()) + if (auto p = atlas->pass_mode(pass_mode::blocked); p != pass_mode::pass) + _lqt_bboxes.push_back(wall_west(i, p)); } - using index_t = std::array<std::array<UnsignedShort, 6>, TILE_COUNT>; - const index_t indexes[2] = { make_index_array(0), make_index_array(TILE_COUNT) }; + for (auto& bbox : _lqt_bboxes) + qt.Insert(&bbox); - GL::Mesh mesh{GL::MeshPrimitive::Triangles}; - mesh.addVertexBuffer(GL::Buffer{vertexes}, 0, tile_shader::Position{}, tile_shader::TextureCoordinates{}, tile_shader::Depth{}) - .setIndexBuffer(GL::Buffer{indexes}, 0, GL::MeshIndexType::UnsignedShort) - .setCount(6 * TILE_COUNT); - wall_mesh = Utility::move(mesh); - return { wall_mesh, wall_indexes }; + return qt; } -fm_noinline -chunk::chunk() noexcept // NOLINT(modernize-use-equals-default) +void chunk::bb_extractor::ExtractBoundingBox(const chunk::bbox* x, BB* bbox) { - //fm_debug("chunk ctor"); + *bbox = { x->left, x->top, std::int16_t(x->width), std::int16_t(x->height) }; } -tile_ref chunk::operator[](std::size_t idx) noexcept { return { *this, std::uint8_t(idx) }; } -tile_proto chunk::operator[](std::size_t idx) const noexcept { return tile_proto(tile_ref { *const_cast<chunk*>(this), std::uint8_t(idx) }); } -tile_ref chunk::operator[](local_coords xy) noexcept { return operator[](xy.to_index()); } -tile_proto chunk::operator[](local_coords xy) const noexcept { return operator[](xy.to_index()); } - -auto chunk::begin() noexcept -> iterator { return iterator { *this, 0 }; } -auto chunk::end() noexcept -> iterator { return iterator { *this, TILE_COUNT }; } -auto chunk::cbegin() const noexcept -> const_iterator { return const_iterator { *this, 0 }; } -auto chunk::cend() const noexcept -> const_iterator { return const_iterator { *this, TILE_COUNT }; } -auto chunk::begin() const noexcept -> const_iterator { return cbegin(); } -auto chunk::end() const noexcept -> const_iterator { return cend(); } - +chunk::chunk() noexcept : _static_lqt { std::make_unique<lqt>() } {} +chunk::~chunk() noexcept = default; chunk::chunk(chunk&&) noexcept = default; chunk& chunk::operator=(chunk&&) noexcept = default; -void chunk::mark_ground_modified() noexcept { _ground_modified = true; } -void chunk::mark_walls_modified() noexcept { _walls_modified = true; } -void chunk::mark_scenery_modified() noexcept { _scenery_modified = true; } - -void chunk::mark_modified() noexcept -{ - mark_ground_modified(); - mark_walls_modified(); - mark_scenery_modified(); -} - } // namespace floormat diff --git a/src/chunk.hpp b/src/chunk.hpp index 9acd5f12..d66dfdc8 100644 --- a/src/chunk.hpp +++ b/src/chunk.hpp @@ -4,8 +4,12 @@ #include "scenery.hpp" #include <type_traits> #include <array> -#include <bitset> +#include <vector> +#include <memory> #include <Magnum/GL/Mesh.h> +#include "compat/LooseQuadtree.h" + +namespace loose_quadtree { template<typename NumberT, typename ObjectT, typename BoundingBoxExtractorT> class LooseQuadtree; } namespace floormat { @@ -14,7 +18,6 @@ struct anim_atlas; struct chunk final { friend struct tile_ref; - friend struct pass_mode_ref; tile_ref operator[](std::size_t idx) noexcept; tile_proto operator[](std::size_t idx) const noexcept; @@ -34,6 +37,7 @@ struct chunk final bool empty(bool force = false) const noexcept; chunk() noexcept; + ~chunk() noexcept; chunk(const chunk&) = delete; chunk& operator=(const chunk&) = delete; chunk(chunk&&) noexcept; @@ -59,6 +63,12 @@ struct chunk final wall_mesh_tuple ensure_wall_mesh() noexcept; tile_atlas* wall_atlas_at(std::size_t i) const noexcept; + struct bbox final { std::int16_t left, top; std::uint16_t width, height; enum pass_mode pass_mode; }; + using BB = loose_quadtree::BoundingBox<std::int16_t>; + struct bb_extractor { static void ExtractBoundingBox(const bbox* object, BB* bbox); }; + using lqt = loose_quadtree::LooseQuadtree<std::int16_t, bbox, bb_extractor>; + lqt& ensure_passability() noexcept; + private: std::array<std::shared_ptr<tile_atlas>, TILE_COUNT> _ground_atlases; std::array<std::uint8_t, TILE_COUNT> ground_indexes = {}; @@ -68,12 +78,16 @@ private: std::array<variant_t, TILE_COUNT*2> _wall_variants = {}; std::array<std::shared_ptr<anim_atlas>, TILE_COUNT> _scenery_atlases; std::array<scenery, TILE_COUNT> _scenery_variants = {}; - std::bitset<TILE_COUNT*2> _passability = {}; + + std::unique_ptr<lqt> _static_lqt; + std::vector<bbox> _lqt_bboxes; + GL::Mesh ground_mesh{NoCreate}, wall_mesh{NoCreate}; mutable bool _maybe_empty : 1 = true, _ground_modified : 1 = true, _walls_modified : 1 = true, - _scenery_modified : 1 = true; + _scenery_modified : 1 = true, + _pass_modified : 1 = true; }; } // namespace floormat diff --git a/src/pass-mode.hpp b/src/pass-mode.hpp index a60a8188..112aafcc 100644 --- a/src/pass-mode.hpp +++ b/src/pass-mode.hpp @@ -3,8 +3,7 @@ namespace floormat { -enum class pass_mode : std::uint8_t { shoot_through, pass, blocked, see_through }; -constexpr inline std::uint8_t pass_mode_COUNT = std::uint8_t(pass_mode::see_through) + 1; -static_assert(pass_mode_COUNT == 4); +enum class pass_mode : std::uint8_t { blocked, see_through, shoot_through, pass, }; +constexpr inline std::uint8_t pass_mode_COUNT = 4; } // namespace floormat diff --git a/src/scenery.hpp b/src/scenery.hpp index 5cfe3827..04fcf984 100644 --- a/src/scenery.hpp +++ b/src/scenery.hpp @@ -36,7 +36,7 @@ struct scenery final frame_t frame = 0; rotation r : 3 = rotation::N; scenery_type type : 3 = scenery_type::none; - pass_mode passability : 2 = pass_mode{0}; + pass_mode passability : 2 = pass_mode::pass; std::uint8_t active : 1 = false; std::uint8_t closing : 1 = false; std::uint8_t interactive : 1 = false; diff --git a/src/tile-atlas.cpp b/src/tile-atlas.cpp index 6f9e832f..b13b9ea8 100644 --- a/src/tile-atlas.cpp +++ b/src/tile-atlas.cpp @@ -57,6 +57,7 @@ auto tile_atlas::make_texcoords_array(Vector2ui pixel_size, Vector2ub tile_count std::size_t tile_atlas::num_tiles() const { return Vector2ui{dims_}.product(); } Optional<enum pass_mode> tile_atlas::pass_mode() const { return passability; } +enum pass_mode tile_atlas::pass_mode(enum pass_mode p) const { return passability ? *passability : p; } void tile_atlas::set_pass_mode(enum pass_mode p) { diff --git a/src/tile-atlas.hpp b/src/tile-atlas.hpp index 61f528ef..e4ac8412 100644 --- a/src/tile-atlas.hpp +++ b/src/tile-atlas.hpp @@ -28,6 +28,7 @@ struct tile_atlas final GL::Texture2D& texture() { return tex_; } StringView name() const { return name_; } Optional<enum pass_mode> pass_mode() const; + enum pass_mode pass_mode(enum pass_mode p) const; void set_pass_mode(enum pass_mode p); private: diff --git a/src/tile.cpp b/src/tile.cpp index 419e59a3..34d2a6e1 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -3,33 +3,6 @@ namespace floormat { -pass_mode_ref::pass_mode_ref(chunk& c, std::uint8_t i) noexcept : _chunk{&c}, i{i} -{ -} - -pass_mode_ref& pass_mode_ref::operator=(pass_mode val) noexcept -{ - auto x = std::underlying_type_t<pass_mode>(val) & pass_mode_COUNT; - auto& bitset = _chunk->_passability; - bitset[i*2 + 0] = x & 1; - bitset[i*2 + 1] = x >> 1 & 1; - return *this; -} - -pass_mode_ref& pass_mode_ref::operator=(const pass_mode_ref& x) noexcept -{ - return operator=(pass_mode(x)); // NOLINT(misc-unconventional-assign-operator) -} - -pass_mode_ref::operator pass_mode() const noexcept -{ - auto& bitset = _chunk->_passability; - std::uint8_t ret = 0; - ret |= (std::uint8_t)bitset[i*2 + 1]; - ret |= (std::uint8_t)bitset[i*2 + 0] << 1; - return pass_mode(ret); -} - bool operator==(const tile_proto& a, const tile_proto& b) noexcept { return a.ground() == b.ground() && a.wall_north() == b.wall_north() && @@ -64,15 +37,11 @@ tile_image_proto tile_ref::wall_north() const noexcept { return { _chunk->_wall_ tile_image_proto tile_ref::wall_west() const noexcept { return { _chunk->_wall_atlases[i*2+1], _chunk->_wall_variants[i*2+1] }; } scenery_proto tile_ref::scenery() const noexcept { return { _chunk->_scenery_atlases[i], _chunk->_scenery_variants[i] }; } -pass_mode_ref tile_ref::passability() noexcept { return { *_chunk, i }; } -pass_mode tile_ref::passability() const noexcept { return pass_mode_ref { *const_cast<struct chunk*>(_chunk), i }; } - tile_ref::operator tile_proto() const noexcept { return { _chunk->_ground_atlases[i], _chunk->_wall_atlases[i*2+0], _chunk->_wall_atlases[i*2+1], _chunk->_scenery_atlases[i], _chunk->_ground_variants[i], _chunk->_wall_variants[i*2+0], _chunk->_wall_variants[i*2+1], _chunk->_scenery_variants[i], - passability(), }; } @@ -84,8 +53,7 @@ bool operator==(const tile_ref& a, const tile_ref& b) noexcept return a.ground() == b.ground() && a.wall_north() == b.wall_north() && a.wall_west() == b.wall_west() && - a.scenery() == b.scenery() && - a.passability() == b.passability(); + a.scenery() == b.scenery(); } } // namespace floormat diff --git a/src/tile.hpp b/src/tile.hpp index 543b70b6..7a6604dd 100644 --- a/src/tile.hpp +++ b/src/tile.hpp @@ -1,32 +1,18 @@ #pragma once #include "tile-image.hpp" #include "scenery.hpp" -#include "pass-mode.hpp" namespace floormat { struct chunk; struct anim_atlas; -struct pass_mode_ref final -{ - pass_mode_ref(chunk& c, std::uint8_t i) noexcept; - pass_mode_ref& operator=(pass_mode x) noexcept; - pass_mode_ref& operator=(const pass_mode_ref& x) noexcept; - operator pass_mode() const noexcept; - -private: - chunk* _chunk; - std::uint8_t i; -}; - struct tile_proto final { std::shared_ptr<tile_atlas> ground_atlas, wall_north_atlas, wall_west_atlas; std::shared_ptr<anim_atlas> scenery_atlas; variant_t ground_variant = 0, wall_north_variant = 0, wall_west_variant = 0; struct scenery scenery_frame; - pass_mode passability = pass_mode{0}; tile_image_proto ground() const noexcept; tile_image_proto wall_north() const noexcept; @@ -60,9 +46,6 @@ struct tile_ref final std::shared_ptr<const tile_atlas> wall_west_atlas() const noexcept; std::shared_ptr<const anim_atlas> scenery_atlas() const noexcept; - pass_mode_ref passability() noexcept; - pass_mode passability() const noexcept; - explicit operator tile_proto() const noexcept; struct chunk& chunk() noexcept { return *_chunk; } diff --git a/test/app.hpp b/test/app.hpp index 17ca2b6e..46ea3fc0 100644 --- a/test/app.hpp +++ b/test/app.hpp @@ -13,12 +13,16 @@ #endif namespace floormat { + +struct chunk; + struct test_app final : private FM_APPLICATION { using Application = FM_APPLICATION; explicit test_app(const Arguments& arguments); ~test_app(); int exec() override; + static chunk make_test_chunk(); static void test_json(); static void test_tile_iter(); static void test_const_math(); @@ -26,5 +30,6 @@ struct test_app final : private FM_APPLICATION static void test_entity(); static void test_quadtree(); static void test_loader(); + static void test_bbox(); }; } // namespace floormat diff --git a/test/bbox.cpp b/test/bbox.cpp new file mode 100644 index 00000000..c045d858 --- /dev/null +++ b/test/bbox.cpp @@ -0,0 +1,28 @@ +#include "app.hpp" +#include "src/chunk.hpp" +#include "compat/LooseQuadtree-impl.h" +#include <Magnum/Math/Vector2.h> + +namespace floormat { + +void test_app::test_bbox() +{ + auto c = make_test_chunk(); + auto& qt = c.ensure_passability(); + fm_assert(qt.GetSize() >= 2); + + using namespace loose_quadtree; + using bbox = BoundingBox<std::int16_t>; + constexpr auto pos1 = Vector2s(iTILE_SIZE2 * (TILE_MAX_DIM/2) - Vector2i(0, iTILE_SIZE[1]/2)), + size = Vector2s(iTILE_SIZE2); + constexpr auto b1 = bbox{pos1[0], pos1[1], size[0], size[1]}; + constexpr auto pos2 = Vector2s(iTILE_SIZE2 * (Vector2i(TILE_MAX_DIM/2) - Vector2i(-1, -1)) - Vector2i(0, iTILE_SIZE[1]/2)); + auto q1 = qt.QueryIntersectsRegion(b1); + fm_assert(!q1.EndOfQuery()); + do q1.Next(); while (!q1.EndOfQuery()); + constexpr auto b2 = bbox{pos2[0], pos2[1], size[0], size[1]}; + auto q2 = qt.QueryIntersectsRegion(b2); + fm_assert(q2.EndOfQuery()); +} + +} // namespace floormat diff --git a/test/main.cpp b/test/main.cpp index ec09f55f..305c4926 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -25,6 +25,7 @@ int test_app::exec() test_serializer(); test_entity(); test_loader(); + test_bbox(); test_quadtree(); return 0; } diff --git a/test/serializer.cpp b/test/serializer.cpp index 9f382602..69b45575 100644 --- a/test/serializer.cpp +++ b/test/serializer.cpp @@ -9,7 +9,7 @@ namespace floormat { namespace Path = Corrade::Utility::Path; -static chunk make_test_chunk() +chunk test_app::make_test_chunk() { auto metal1 = loader.tile_atlas("metal1", {2, 2}, pass_mode::pass), metal2 = loader.tile_atlas("metal2", {2, 2}, pass_mode::blocked), |