diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2023-03-14 07:33:47 +0100 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2023-03-14 07:33:47 +0100 |
commit | dc5e66b5a29fd7de8ddf59852ceefd982289b7c3 (patch) | |
tree | 18bf0a274f1595d6d2d6cfb32a3b3825d843e315 /src | |
parent | 29bdd5f2170b9d46a8b3b0973c4c0845d6a2b61e (diff) |
a
Diffstat (limited to 'src')
-rw-r--r-- | src/character.cpp | 121 | ||||
-rw-r--r-- | src/character.hpp | 32 | ||||
-rw-r--r-- | src/chunk-collision.cpp | 43 | ||||
-rw-r--r-- | src/chunk-render.cpp | 88 | ||||
-rw-r--r-- | src/chunk.cpp | 64 | ||||
-rw-r--r-- | src/chunk.hpp | 37 | ||||
-rw-r--r-- | src/chunk.inl | 17 | ||||
-rw-r--r-- | src/entity.cpp | 199 | ||||
-rw-r--r-- | src/entity.hpp | 80 | ||||
-rw-r--r-- | src/pass-mode.hpp | 1 | ||||
-rw-r--r-- | src/scenery.cpp | 134 | ||||
-rw-r--r-- | src/scenery.hpp | 104 | ||||
-rw-r--r-- | src/tile.cpp | 15 | ||||
-rw-r--r-- | src/tile.hpp | 8 | ||||
-rw-r--r-- | src/world.cpp | 29 | ||||
-rw-r--r-- | src/world.hpp | 26 |
16 files changed, 720 insertions, 278 deletions
diff --git a/src/character.cpp b/src/character.cpp new file mode 100644 index 00000000..18472954 --- /dev/null +++ b/src/character.cpp @@ -0,0 +1,121 @@ +#include "character.hpp" +#include "src/anim-atlas.hpp" +#include "loader/loader.hpp" +#include "src/world.hpp" +#include "src/entity.hpp" +#include "src/RTree.hpp" +#include <cmath> + +namespace floormat { + +namespace { + +template <typename T> +constexpr T sgn(T val) { return T(T(0) < val) - T(val < T(0)); } + +constexpr int tile_size_1 = iTILE_SIZE2.sum()/2, + framerate = 96, move_speed = tile_size_1 * 2; +constexpr float frame_time = 1.f/framerate; + +constexpr auto arrows_to_dir(bool L, bool R, bool U, bool D) +{ + if (L == R) + L = R = false; + if (U == D) + U = D = false; + + using enum rotation; + struct { + int lr = 0, ud = 0; + rotation r = N; + } dir; + + if (L && U) + dir = { -1, 0, W }; + else if (L && D) + dir = { 0, 1, S }; + else if (R && U) + dir = { 0, -1, N }; + else if (R && D) + dir = { 1, 0, E }; + else if (L) + dir = { -1, 1, SW }; + else if (D) + dir = { 1, 1, SE }; + else if (R) + dir = { 1, -1, NE }; + else if (U) + dir = { -1, -1, NW }; + + return dir; +} + +} // namespace + +character::character(std::uint64_t id, struct world& w, entity_type type) : entity{id, w, type} +{ + atlas = loader.anim_atlas("npc-walk", loader.ANIM_PATH); + bbox_size = {12, 12}; +} + +character::~character() = default; + +int character::allocate_frame_time(float dt) +{ + int d = int(delta) + int(65535u * dt); + constexpr int framerate_ = 65535/framerate; + static_assert(framerate_ > 0); + auto ret = d / framerate_; + delta = (std::uint16_t)std::clamp(d - ret*65535, 0, 65535); + return ret; +} + +Vector2 character::move_vec(int left_right, int top_bottom) +{ + constexpr auto c = move_speed * frame_time; + return c * Vector2(sgn(left_right), sgn(top_bottom)).normalized(); +} + +void character::set_keys(bool L, bool R, bool U, bool D) +{ + b_L = L; + b_R = R; + b_U = U; + b_D = D; +} + +bool character::update(It it, struct chunk& c, float dt) +{ + auto [lr, ud, rot] = arrows_to_dir(b_L, b_R, b_U, b_D); + + if (!lr & !ud) + { + delta = 0; + return false; + } + + int nframes = allocate_frame_time(dt); + + if (!nframes) + return false; + + const auto vec = move_vec(lr, ud); + r = rot; + c.ensure_passability(); + + for (int i = 0; i < nframes; i++) + { + constexpr auto frac = Vector2(32767); + constexpr auto inv_frac = Vector2(1.f/32767); + auto offset_ = vec + Vector2(offset_frac) * inv_frac; + offset_frac = Vector2s(Vector2(std::fmod(offset_[0], 1.f), std::fmod(offset_[1], 1.f)) * frac); + auto off_i = Vector2i(offset_); + if (can_move_to(off_i, c)) + entity::move(it, off_i, c); + ++frame %= atlas->info().nframes; + } + //Debug{} << "pos" << Vector2i(pos.local()); + return true; +} + +} // namespace floormat diff --git a/src/character.hpp b/src/character.hpp new file mode 100644 index 00000000..d69b98dc --- /dev/null +++ b/src/character.hpp @@ -0,0 +1,32 @@ +#pragma once +#include "src/global-coords.hpp" +#include "src/rotation.hpp" +#include "src/entity.hpp" +#include <memory> + +namespace floormat { + +struct anim_atlas; +struct world; + +struct character final : entity +{ + ~character() override; + void set_keys(bool L, bool R, bool U, bool D); + bool update(It it, struct chunk& c, float dt) override; + +private: + int allocate_frame_time(float dt); + static Vector2 move_vec(int left_right, int top_bottom); + character(std::uint64_t id, struct world& w, entity_type type); + + friend struct world; + + Vector2s offset_frac; + + bool b_L : 1 = false, b_R : 1 = false, b_U : 1 = false, b_D : 1 = false; +}; + +template<> struct entity_type_<struct character> : std::integral_constant<entity_type, entity_type::character> {}; + +} // namespace floormat diff --git a/src/chunk-collision.cpp b/src/chunk-collision.cpp index 3cf772ef..032c9d7d 100644 --- a/src/chunk-collision.cpp +++ b/src/chunk-collision.cpp @@ -18,10 +18,9 @@ constexpr Vector2 tile_start(std::size_t k) return TILE_SIZE2 * Vector2(coord) - half_tile; } -constexpr Pair<Vector2i, Vector2i> scenery_tile(std::size_t k, const scenery& sc) +Pair<Vector2i, Vector2i> scenery_tile(const entity& sc) { - const local_coords coord{k}; - auto center = iTILE_SIZE2 * Vector2i(coord) + Vector2i(sc.offset) + Vector2i(sc.bbox_offset); + auto center = iTILE_SIZE2 * Vector2i(sc.coord.local()) + Vector2i(sc.offset) + Vector2i(sc.bbox_offset); auto start = center - Vector2i(sc.bbox_size); auto size = Vector2i(sc.bbox_size)*2; return { start, start + size, }; @@ -60,15 +59,13 @@ void chunk::ensure_passability() noexcept _rtree.RemoveAll(); - for (auto i = 0_uz; i < TILE_COUNT; i++) + for (const std::shared_ptr<entity>& s : entities()) { - if (auto s = operator[](i).scenery()) - { - bbox box; - if (_bbox_for_scenery(i, box)) - _add_bbox(box); - } + bbox box; + if (_bbox_for_scenery(*s, box)) + _add_bbox(box); } + for (auto i = 0_uz; i < TILE_COUNT; i++) { if (const auto* atlas = ground_atlas_at(i)) @@ -99,24 +96,26 @@ void chunk::ensure_passability() noexcept } } -bool chunk::_bbox_for_scenery(std::size_t i, bbox& value) noexcept +bool chunk::_bbox_for_scenery(const entity& s, bbox& value) noexcept { - fm_debug_assert(i < TILE_COUNT); - auto [atlas, s] = operator[](i).scenery(); - auto [start, end] = scenery_tile(i, s); - auto id = make_id(collision_type::scenery, s.passability, i); + auto [start, end] = scenery_tile(s); + auto id = make_id(collision_type::scenery, s.pass, s.id); value = { .id = id, .start = start, .end = end }; - return atlas && s.passability != pass_mode::pass && Vector2i(s.bbox_size).product() > 0; + return s.atlas && s.pass != pass_mode::pass; } void chunk::_remove_bbox(const bbox& x) { + if (_scenery_modified) + return; auto start = Vector2(x.start), end = Vector2(x.end); _rtree.Remove(start.data(), end.data(), x.id); } void chunk::_add_bbox(const bbox& x) { + if (_scenery_modified) + return; auto start = Vector2(x.start), end = Vector2(x.end); _rtree.Insert(start.data(), end.data(), x.id); } @@ -145,4 +144,16 @@ void chunk::_replace_bbox(const bbox& x0, const bbox& x1, bool b0, bool b1) CORRADE_ASSUME(false); } +bool chunk::can_place_entity(const entity_proto& proto, local_coords pos) +{ + (void)ensure_scenery_mesh(); + + const auto center = Vector2(pos)*TILE_SIZE2 + Vector2(proto.offset) + Vector2(proto.bbox_offset), + half_bbox = Vector2(proto.bbox_size)*.5f, + min = center - half_bbox, max = center + half_bbox; + bool ret = true; + _rtree.Search(min.data(), max.data(), [&](auto, const auto&) { return ret = false; }); + return ret; +} + } // namespace floormat diff --git a/src/chunk-render.cpp b/src/chunk-render.cpp index bc22ef9b..e370239f 100644 --- a/src/chunk-render.cpp +++ b/src/chunk-render.cpp @@ -1,7 +1,8 @@ #include "chunk.hpp" #include "tile-atlas.hpp" -#include "anim-atlas.hpp" #include "shaders/tile.hpp" +#include "entity.hpp" +#include "anim-atlas.hpp" #include <algorithm> #include <Corrade/Containers/ArrayViewStl.h> #include <Magnum/GL/Buffer.h> @@ -17,12 +18,6 @@ static auto make_index_array(std::size_t max) return array; } -struct vertex { - Vector3 position; - Vector2 texcoords; - float depth = -1; -}; - auto chunk::ensure_ground_mesh() noexcept -> ground_mesh_tuple { if (!_ground_modified) @@ -111,48 +106,47 @@ auto chunk::ensure_wall_mesh() noexcept -> wall_mesh_tuple auto chunk::ensure_scenery_mesh() noexcept -> scenery_mesh_tuple { - if (!_scenery_modified) - return { scenery_mesh, scenery_indexes, std::size_t(scenery_mesh.count()/6) }; - _scenery_modified = false; - - std::size_t count = 0; - for (auto i = 0_uz; i < TILE_COUNT; i++) - if (const auto& atlas = _scenery_atlases[i]; atlas && atlas->info().fps == 0) - scenery_indexes[count++] = std::uint8_t(i); - -#if 0 - std::sort(scenery_indexes.begin(), scenery_indexes.begin() + count, - [this](std::uint8_t a, std::uint8_t b) { - return _scenery_atlases[a] < _scenery_atlases[b]; - }); -#endif - std::array<std::array<vertex, 4>, TILE_COUNT> vertexes; - for (auto k = 0_uz; k < count; k++) + if (_scenery_modified) { - const std::uint8_t i = scenery_indexes[k]; - const local_coords pos{i}; - const auto& atlas = _scenery_atlases[i]; - const auto& fr = _scenery_variants[i]; - const auto coord = Vector3(pos) * TILE_SIZE + Vector3(Vector2(fr.offset), 0); - const auto quad = atlas->frame_quad(coord, fr.r, fr.frame); - const auto& group = atlas->group(fr.r); - const auto texcoords = atlas->texcoords_for_frame(fr.r, fr.frame, !group.mirror_from.isEmpty()); - const float depth = tile_shader::depth_value(pos, tile_shader::scenery_depth_offset); - auto& v = vertexes[k]; - for (auto j = 0_uz; j < 4; j++) - v[j] = { quad[j], texcoords[j], depth }; + _scenery_modified = false; + const auto count = fm_begin( + std::size_t ret = 0; + for (const auto& e : _entities) + ret += e->atlas->info().fps == 0; + return ret; + ); + + scenery_indexes.clear(); + scenery_indexes.reserve(count); + scenery_vertexes.clear(); + scenery_vertexes.reserve(count); + + for (const auto& e : _entities) + { + const auto i = scenery_indexes.size(); + scenery_indexes.emplace_back(); + scenery_indexes.back() = tile_atlas::indices(i); + const auto& atlas = e->atlas; + const auto& fr = *e; + const auto pos = e->coord.local(); + const auto coord = Vector3(pos) * TILE_SIZE + Vector3(Vector2(fr.offset), 0); + const auto quad = atlas->frame_quad(coord, fr.r, fr.frame); + const auto& group = atlas->group(fr.r); + const auto texcoords = atlas->texcoords_for_frame(fr.r, fr.frame, !group.mirror_from.isEmpty()); + const float depth = tile_shader::depth_value(pos, tile_shader::scenery_depth_offset); + scenery_vertexes.emplace_back(); + auto& v = scenery_vertexes.back(); + for (auto j = 0_uz; j < 4; j++) + v[j] = { quad[j], texcoords[j], depth }; + } + + GL::Mesh mesh{GL::MeshPrimitive::Triangles}; + mesh.addVertexBuffer(GL::Buffer{scenery_vertexes}, 0, tile_shader::Position{}, tile_shader::TextureCoordinates{}, tile_shader::Depth{}) + .setIndexBuffer(GL::Buffer{scenery_indexes}, 0, GL::MeshIndexType::UnsignedShort) + .setCount(std::int32_t(6 * count)); + scenery_mesh = Utility::move(mesh); } - - const auto indexes = make_index_array(count); - const auto vertex_view = ArrayView{vertexes.data(), count}; - const auto vert_index_view = ArrayView{indexes.data(), count}; - - GL::Mesh mesh{GL::MeshPrimitive::Triangles}; - mesh.addVertexBuffer(GL::Buffer{vertex_view}, 0, tile_shader::Position{}, tile_shader::TextureCoordinates{}, tile_shader::Depth{}) - .setIndexBuffer(GL::Buffer{vert_index_view}, 0, GL::MeshIndexType::UnsignedShort) - .setCount(std::int32_t(6 * count)); - scenery_mesh = Utility::move(mesh); - return { scenery_mesh, scenery_indexes, count }; + return { scenery_mesh, }; } } // namespace floormat diff --git a/src/chunk.cpp b/src/chunk.cpp index 6cc7ee78..6edced05 100644 --- a/src/chunk.cpp +++ b/src/chunk.cpp @@ -1,5 +1,7 @@ #include "chunk.hpp" #include "src/tile-atlas.hpp" +#include "anim-atlas.hpp" +#include <algorithm> #include <Magnum/GL/Context.h> namespace floormat { @@ -22,17 +24,16 @@ bool chunk::empty(bool force) const noexcept if (!force && !_maybe_empty) return false; for (auto i = 0_uz; i < TILE_COUNT; i++) - if (_ground_atlases[i] || _wall_atlases[i*2 + 0] || _wall_atlases[i*2 + 1] || _scenery_atlases[i]) + if (_ground_atlases[i] || _wall_atlases[i*2 + 0] || _wall_atlases[i*2 + 1] || !_entities.empty()) return _maybe_empty = false; + if (!_entities.empty()) + return false; return true; } 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(); } -std::shared_ptr<anim_atlas>& chunk::scenery_atlas_at(std::size_t i) noexcept { return _scenery_atlases[i]; } -scenery& chunk::scenery_at(std::size_t i) noexcept { return _scenery_variants[i]; } - 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()); } @@ -89,11 +90,64 @@ void chunk::mark_modified() noexcept } chunk::chunk() noexcept = default; -chunk::~chunk() noexcept = default; +chunk::~chunk() noexcept +{ + _teardown = true; + _entities.clear(); + _rtree.RemoveAll(); +} chunk::chunk(chunk&&) noexcept = default; chunk& chunk::operator=(chunk&&) noexcept = default; bool chunk::bbox::operator==(const bbox& other) const noexcept = default; +void chunk::add_entity_unsorted(const std::shared_ptr<entity>& e) +{ + if (e->atlas->info().fps == 0) + mark_scenery_modified(false); + if (bbox bb; _bbox_for_scenery(*e, bb)) + _add_bbox(bb); + _entities.push_back(e); +} + +void chunk::sort_entities() +{ + mark_scenery_modified(false); + + std::sort(_entities.begin(), _entities.end(), [](const auto& a, const auto& b) { + return a->ordinal() < b->ordinal(); + }); +} + +void chunk::add_entity(const std::shared_ptr<entity>& e) +{ + if (e->atlas->info().fps == 0) + mark_scenery_modified(false); + if (bbox bb; _bbox_for_scenery(*e, bb)) + _add_bbox(bb); + + _entities.reserve(_entities.size() + 1); + auto it = std::lower_bound(_entities.cbegin(), _entities.cend(), e, [ord = e->ordinal()](const auto& a, const auto&) { + return a->ordinal() < ord; + }); + _entities.insert(it, e); +} + +void chunk::remove_entity(entity_const_iterator it) +{ + const auto& e = *it; + if (e->atlas->info().fps == 0) + mark_scenery_modified(false); + if (bbox bb; _bbox_for_scenery(*e, bb)) + _remove_bbox(bb); + + _entities.erase(it); +} + +const std::vector<std::shared_ptr<entity>>& chunk::entities() const +{ + return _entities; +} + } // namespace floormat diff --git a/src/chunk.hpp b/src/chunk.hpp index a2fdf53f..a5eba19e 100644 --- a/src/chunk.hpp +++ b/src/chunk.hpp @@ -11,6 +11,7 @@ namespace floormat { struct anim_atlas; +struct entity; enum class collision : std::uint8_t { view, shoot, move, @@ -29,6 +30,7 @@ struct collision_data final { struct chunk final { friend struct tile_ref; + friend struct entity; tile_ref operator[](std::size_t idx) noexcept; tile_proto operator[](std::size_t idx) const noexcept; @@ -76,8 +78,12 @@ struct chunk final struct scenery_mesh_tuple final { GL::Mesh& mesh; - const ArrayView<const std::uint8_t> ids; - const std::size_t size; + }; + + struct vertex { + Vector3 position; + Vector2 texcoords; + float depth = -1; }; ground_mesh_tuple ensure_ground_mesh() noexcept; @@ -87,8 +93,6 @@ struct chunk final tile_atlas* wall_atlas_at(std::size_t i) const noexcept; scenery_mesh_tuple ensure_scenery_mesh() noexcept; - std::shared_ptr<anim_atlas>& scenery_atlas_at(std::size_t i) noexcept; - scenery& scenery_at(std::size_t i) noexcept; void ensure_passability() noexcept; @@ -99,7 +103,18 @@ struct chunk final template<typename F> requires requires(F fun) { fun(); } - void with_scenery_update(std::size_t idx, F&& fun); + void with_scenery_update(entity& e, F&& fun); + + using entity_vector = std::vector<std::shared_ptr<entity>>; + using entity_const_iterator = typename entity_vector::const_iterator; + + [[nodiscard]] bool can_place_entity(const entity_proto& proto, local_coords pos); + + void add_entity(const std::shared_ptr<entity>& e); + void add_entity_unsorted(const std::shared_ptr<entity>& e); + void sort_entities(); + void remove_entity(entity_const_iterator it); + const std::vector<std::shared_ptr<entity>>& entities() const; private: std::array<std::shared_ptr<tile_atlas>, TILE_COUNT> _ground_atlases; @@ -108,9 +123,10 @@ private: std::array<std::shared_ptr<tile_atlas>, TILE_COUNT*2> _wall_atlases; std::array<std::uint16_t, TILE_COUNT*2> wall_indexes = {}; std::array<variant_t, TILE_COUNT*2> _wall_variants = {}; - std::array<std::shared_ptr<anim_atlas>, TILE_COUNT> _scenery_atlases; - std::array<std::uint8_t, TILE_COUNT> scenery_indexes = {}; - std::array<scenery, TILE_COUNT> _scenery_variants = {}; + std::vector<std::shared_ptr<entity>> _entities; + + std::vector<std::array<UnsignedShort, 6>> scenery_indexes; + std::vector<std::array<vertex, 4>> scenery_vertexes; GL::Mesh ground_mesh{NoCreate}, wall_mesh{NoCreate}, scenery_mesh{NoCreate}; @@ -120,7 +136,8 @@ private: _ground_modified : 1 = true, _walls_modified : 1 = true, _scenery_modified : 1 = true, - _pass_modified : 1 = true; + _pass_modified : 1 = true, + _teardown : 1 = false; struct bbox final // NOLINT(cppcoreguidelines-pro-type-member-init) { @@ -129,7 +146,7 @@ private: bool operator==(const bbox& other) const noexcept; }; - bool _bbox_for_scenery(std::size_t i, bbox& value) noexcept; + bool _bbox_for_scenery(const entity& s, bbox& value) noexcept; void _remove_bbox(const bbox& x); void _add_bbox(const bbox& x); void _replace_bbox(const bbox& x0, const bbox& x, bool b0, bool b); diff --git a/src/chunk.inl b/src/chunk.inl index 23013099..3d40c30e 100644 --- a/src/chunk.inl +++ b/src/chunk.inl @@ -1,16 +1,20 @@ #pragma once #include "chunk.hpp" +#include "anim-atlas.hpp" namespace floormat { template<typename F> requires requires(F fun) { fun(); } -void chunk::with_scenery_update(std::size_t idx, F&& fun) +void chunk::with_scenery_update(entity& s, F&& fun) { static_assert(std::is_convertible_v<decltype(fun()), bool> || std::is_same_v<void, decltype(fun())>); - const auto& s = scenery_at(idx), s0 = s; - bbox bb0; bool b0 = _bbox_for_scenery(idx, bb0); + // todo handle coord & offset fields + + auto ch = s.coord.chunk(); + entity_proto s0 = s; + bbox bb0; bool b0 = _bbox_for_scenery(s, bb0); bool modified = true; if constexpr(!std::is_same_v<void, std::decay_t<decltype(fun())>>) @@ -20,10 +24,13 @@ void chunk::with_scenery_update(std::size_t idx, F&& fun) if (!modified) return; + if (s.coord.chunk() != ch) // todo + return; + if (bbox bb; !is_passability_modified()) - if (bool b = _bbox_for_scenery(idx, bb); b != b0 || bb != bb0) + if (bool b = _bbox_for_scenery(s, bb); b != b0 || bb != bb0) _replace_bbox(bb0, bb, b0, b); - if (!is_scenery_modified() && scenery::is_mesh_modified(s0, s)) + if (!is_scenery_modified() && s.atlas->info().fps == 0 && s != s0) mark_scenery_modified(false); } diff --git a/src/entity.cpp b/src/entity.cpp new file mode 100644 index 00000000..07289291 --- /dev/null +++ b/src/entity.cpp @@ -0,0 +1,199 @@ +#include "entity.hpp" +#include "world.hpp" +#include "rotation.inl" +#include "chunk.inl" +#include "RTree.hpp" +#include <algorithm> + +namespace floormat { + +bool entity_proto::operator==(const entity_proto&) const = default; +entity_proto& entity_proto::operator=(const entity_proto&) = default; +entity_proto::~entity_proto() noexcept = default; +entity_proto::entity_proto() = default; +entity_proto::entity_proto(const entity_proto&) = default; + +entity::entity(std::uint64_t id, struct world& w, entity_type type) noexcept : + id{id}, w{w}, type{type} +{ +} + +entity::entity(std::uint64_t id, struct world& w, entity_type type, const entity_proto& proto) noexcept : + id{id}, w{w}, atlas{proto.atlas}, + offset{proto.offset}, bbox_offset{proto.bbox_offset}, + bbox_size{proto.bbox_size}, delta{proto.delta}, + frame{proto.frame}, type{type}, r{proto.r}, pass{proto.pass} +{ + fm_assert(type == proto.type); +} + +entity::~entity() noexcept +{ + fm_debug_assert(id); + if (w.is_teardown()) [[unlikely]] + return; + auto& c = chunk(); + if (!c._teardown) [[likely]] + { + if (chunk::bbox bb; c._bbox_for_scenery(*this, bb)) + c._remove_bbox(bb); + auto it = std::find_if(c._entities.cbegin(), c._entities.cend(), [id = id](const auto& x) { return x->id == id; }); + fm_debug_assert(it != c._entities.cend()); + c._entities.erase(it); + } + w.do_kill_entity(id); +} + +std::uint32_t entity_proto::ordinal(local_coords local) const +{ + return entity::ordinal(local, offset); +} + +std::uint32_t entity::ordinal() const +{ + return ordinal(coord.local(), offset); +} + +std::uint32_t entity::ordinal(local_coords xy, Vector2b offset) +{ + constexpr auto x_size = (std::uint32_t)TILE_MAX_DIM * (std::uint32_t)iTILE_SIZE[0]; + auto vec = Vector2ui(xy) * Vector2ui(iTILE_SIZE2) + Vector2ui(offset); + return vec[1] * x_size + vec[0]; +} + +struct chunk& entity::chunk() const +{ + return w[coord.chunk()]; +} + +auto entity::iter() const -> It +{ + auto& c = chunk(); + auto it = std::find_if(c._entities.cbegin(), c._entities.cend(), [id = id](const auto& x) { return x->id == id; }); + fm_assert(it != c._entities.cend()); + return it; +} + +bool entity::operator==(const entity_proto& o) const +{ + return atlas.get() == o.atlas.get() && + type == o.type && frame == o.frame && r == o.r && pass == o.pass && + offset == o.offset && bbox_offset == o.bbox_offset && + bbox_size == o.bbox_size && delta == o.delta; +} + +void entity::rotate(It, struct chunk&, rotation new_r) +{ + w[coord.chunk()].with_scenery_update(*this, [&]() { + bbox_offset = rotate_point(bbox_offset, r, new_r); + bbox_size = rotate_size(bbox_size, r, new_r); + r = new_r; + }); +} + +Pair<global_coords, Vector2b> entity::normalize_coords(global_coords coord, Vector2b cur_offset, Vector2i new_offset) +{ + auto tmp = Vector2i(cur_offset) + new_offset; + auto new_off = tmp % iTILE_SIZE2; + auto tile = tmp / iTILE_SIZE2; + return { coord + tile, Vector2b(new_off) }; +} + +bool entity::can_move_to(Vector2i delta, struct chunk& c) +{ + auto [coord_, offset_] = normalize_coords(coord, offset, delta); + auto& c_ = coord.chunk() == coord_.chunk() ? c : w[coord_.chunk()]; + + const auto center = Vector2(coord_.local())*TILE_SIZE2 + Vector2(offset_) + Vector2(bbox_offset), + half_bbox = Vector2(bbox_size)*.5f, + min = center - half_bbox, max = center + half_bbox; + + bool ret = true; + c_.rtree()->Search(min.data(), max.data(), [&](std::uint64_t data, const auto&) { + auto id2 = std::bit_cast<collision_data>(data).data; + if (id2 != id) + return ret = false; + else + return true; + }); + return ret; +} + +void entity::move(It it, Vector2i delta, struct chunk& c) +{ + auto e_ = *it; + auto& e = *e_; + auto& w = e.w; + auto& coord = e.coord; + auto& offset = e.offset; + + auto& es = c._entities; + auto [coord_, offset_] = normalize_coords(coord, offset, delta); + + if (coord_ == coord && offset_ == offset) + return; + + if (e.atlas->info().fps == 0) + c.mark_scenery_modified(false); + + if (coord_ != coord) + Debug{} << "coord" << Vector2i(coord_.chunk()) << Vector2i(coord_.local()); + + bool same_chunk = coord_.chunk() == coord.chunk(); + chunk::bbox bb0, bb1; + bool b0 = c._bbox_for_scenery(e, bb0); + coord = coord_; offset = offset_; + bool b1 = c._bbox_for_scenery(e, bb1); + + if (same_chunk) + { + c._replace_bbox(bb0, bb1, b0, b1); + auto it_ = std::lower_bound(es.cbegin(), es.cend(), e_, [ord = e.ordinal()](const auto& a, const auto&) { return a->ordinal() < ord; }); + if (it_ != it) + { + auto pos0 = std::distance(es.cbegin(), it), pos1 = std::distance(es.cbegin(), it_); + if (pos1 > pos0) + { + Debug{} << "decr"; + pos1--; + } + else + Debug{} << "no decr"; + es.erase(it); + [[maybe_unused]] auto size = es.size(); + es.insert(es.cbegin() + pos1, std::move(e_)); + } + } + else + { + auto& c2 = w[coord.chunk()]; + if (e.atlas->info().fps == 0) + c2.mark_scenery_modified(false); + c._remove_bbox(bb0); + c2._add_bbox(bb1); + c.remove_entity(it); + auto it_ = std::lower_bound(c2._entities.cbegin(), c2._entities.cend(), e_, + [ord = e.ordinal()](const auto& a, const auto&) { return a->ordinal() < ord; }); + c2._entities.insert(it_, std::move(e_)); + } +} + +entity::operator entity_proto() const +{ + entity_proto x; + x.atlas = atlas; + x.offset = offset; + x.bbox_offset = bbox_offset; + x.bbox_size = bbox_size; + x.delta = delta; + x.frame = frame; + x.type = type; + x.r = r; + x.pass = pass; + return x; +} + +bool entity::can_activate(It, struct chunk&) const { return false; } +bool entity::activate(It, struct chunk&) { return false; } + +} // namespace floormat diff --git a/src/entity.hpp b/src/entity.hpp new file mode 100644 index 00000000..18c588bd --- /dev/null +++ b/src/entity.hpp @@ -0,0 +1,80 @@ +#pragma once +#include "compat/defs.hpp" +#include "src/global-coords.hpp" +#include "src/rotation.hpp" +#include "src/pass-mode.hpp" +#include <memory> +#include <vector> + +namespace floormat { + +template<typename T> struct entity_type_; +struct anim_atlas; +struct world; + +enum class entity_type : std::uint8_t { + none, character, scenery, +}; + +struct entity_proto +{ + std::shared_ptr<anim_atlas> atlas; + Vector2b offset, bbox_offset; + Vector2ub bbox_size = Vector2ub(iTILE_SIZE2); + std::uint16_t delta = 0, frame = 0; + entity_type type = entity_type::none; + rotation r : rotation_BITS = rotation::N; + pass_mode pass : pass_mode_BITS = pass_mode::see_through; + + std::uint32_t ordinal(local_coords coord) const; + entity_proto& operator=(const entity_proto&); + entity_proto(); + entity_proto(const entity_proto&); + + bool operator==(const entity_proto&) const; + virtual ~entity_proto() noexcept; +}; + +struct entity +{ + fm_DECLARE_DELETED_COPY_ASSIGNMENT(entity); + using It = typename std::vector<std::shared_ptr<entity>>::const_iterator; + + const std::uint64_t id = 0; + world& w; + std::shared_ptr<anim_atlas> atlas; + global_coords coord; + Vector2b offset, bbox_offset; + Vector2ub bbox_size; + std::uint16_t delta = 0, frame = 0; + const entity_type type; + rotation r : rotation_BITS = rotation::N; + pass_mode pass : pass_mode_BITS = pass_mode::see_through; + + virtual ~entity() noexcept; + + std::uint32_t ordinal() const; + static std::uint32_t ordinal(local_coords xy, Vector2b offset); + struct chunk& chunk() const; + It iter() const; + + virtual bool operator==(const entity_proto& e0) const; + operator entity_proto() const; + + virtual bool can_activate(It it, struct chunk& c) const; + virtual bool activate(It it, struct chunk& c); + virtual bool update(It it, struct chunk& c, float dt) = 0; + virtual void rotate(It it, struct chunk& c, rotation r); + + static Pair<global_coords, Vector2b> normalize_coords(global_coords coord, Vector2b cur_offset, Vector2i delta); + [[nodiscard]] virtual bool can_move_to(Vector2i delta, struct chunk& c); + static void move(It it, Vector2i delta, struct chunk& c); + + friend struct world; + +protected: + entity(std::uint64_t id, struct world& w, entity_type type) noexcept; + entity(std::uint64_t id, struct world& w, entity_type type, const entity_proto& proto) noexcept; +}; + +} // namespace floormat diff --git a/src/pass-mode.hpp b/src/pass-mode.hpp index 112aafcc..accefaae 100644 --- a/src/pass-mode.hpp +++ b/src/pass-mode.hpp @@ -5,5 +5,6 @@ namespace floormat { enum class pass_mode : std::uint8_t { blocked, see_through, shoot_through, pass, }; constexpr inline std::uint8_t pass_mode_COUNT = 4; +constexpr inline std::uint8_t pass_mode_BITS = 2; } // namespace floormat diff --git a/src/scenery.cpp b/src/scenery.cpp index e2ebdb49..cf58a8de 100644 --- a/src/scenery.cpp +++ b/src/scenery.cpp @@ -2,91 +2,30 @@ #include "anim-atlas.hpp" #include "chunk.hpp" #include "compat/assert.hpp" -#include "rotation.inl" -#include "chunk.inl" +#include "world.hpp" #include <algorithm> namespace floormat { -scenery_proto::scenery_proto() noexcept = default; -scenery_proto::scenery_proto(const std::shared_ptr<anim_atlas>& atlas, const scenery& frame) noexcept : - atlas{atlas}, frame{frame} -{} +scenery_proto::scenery_proto() = default; -scenery_proto& scenery_proto::operator=(const scenery_proto&) noexcept = default; -scenery_proto::scenery_proto(const scenery_proto&) noexcept = default; -scenery_proto::operator bool() const noexcept { return atlas != nullptr; } +scenery_proto& scenery_proto::operator=(const scenery_proto&) = default; +scenery_proto::scenery_proto(const scenery_proto&) = default; +scenery_proto::~scenery_proto() noexcept = default; +scenery_proto::operator bool() const { return atlas != nullptr; } -scenery_ref::scenery_ref(struct chunk& c, std::size_t i) noexcept : - atlas{c.scenery_atlas_at(i)}, frame{c.scenery_at(i)}, - c{&c}, idx{std::uint8_t(i)} -{} -scenery_ref::scenery_ref(const scenery_ref&) noexcept = default; -scenery_ref::scenery_ref(scenery_ref&&) noexcept = default; -struct chunk& scenery_ref::chunk() noexcept { return *c; } -std::uint8_t scenery_ref::index() const noexcept { return idx; } - -scenery_ref& scenery_ref::operator=(const scenery_proto& proto) noexcept -{ - atlas = proto.atlas; - frame = proto.frame; - return *this; -} - -scenery_ref::operator scenery_proto() const noexcept { return { atlas, frame }; } -scenery_ref::operator bool() const noexcept { return atlas != nullptr; } - -scenery::scenery(generic_tag_t, const anim_atlas& atlas, rotation r, frame_t frame, - pass_mode passability, bool active, bool interactive, - Vector2b offset, Vector2b bbox_offset, Vector2ub bbox_size) : - frame{frame}, - offset{offset}, - bbox_offset{rotate_point(bbox_offset, atlas.first_rotation(), r)}, - bbox_size{rotate_size(bbox_size, atlas.first_rotation(), r)}, - r{r}, type{scenery_type::generic}, - passability{passability}, - active{active}, interactive{interactive} -{ - fm_assert(r < rotation_COUNT); - fm_assert(frame < atlas.group(r).frames.size()); -} - -scenery::scenery(door_tag_t, const anim_atlas& atlas, rotation r, bool is_open, - Vector2b offset, Vector2b bbox_offset, Vector2ub bbox_size) : - frame{frame_t(is_open ? 0 : atlas.group(r).frames.size()-1)}, - offset{offset}, - bbox_offset{rotate_point(bbox_offset, atlas.first_rotation(), r)}, - bbox_size{rotate_size(bbox_size, atlas.first_rotation(), r)}, - r{r}, type{scenery_type::door}, - passability{is_open ? pass_mode::pass : pass_mode::blocked}, - interactive{true} -{ - fm_assert(r < rotation_COUNT); - fm_assert(atlas.group(r).frames.size() >= 2); -} - -void scenery_ref::rotate(rotation new_r) -{ - c->with_scenery_update(idx, [&]() { - auto& s = frame; - s.bbox_offset = rotate_point(s.bbox_offset, s.r, new_r); - s.bbox_size = rotate_size(s.bbox_size, s.r, new_r); - s.r = new_r; - }); -} - -bool scenery_ref::can_activate() const noexcept +bool scenery::can_activate(It, struct chunk&) const { - return atlas && frame.interactive; + return atlas && interactive; } -bool scenery_ref::update(float dt) +bool scenery::update(It, struct chunk&, float dt) { - auto& s = frame; + auto& s = *this; if (!s.active) return false; - switch (s.type) + switch (s.sc_type) { default: case scenery_type::none: @@ -109,25 +48,25 @@ bool scenery_ref::update(float dt) const int fr = s.frame + dir*n; s.active = fr > 0 && fr < nframes-1; if (fr <= 0) - s.passability = pass_mode::pass; + s.pass = pass_mode::pass; else if (fr >= nframes-1) - s.passability = pass_mode::blocked; + s.pass = pass_mode::blocked; else - s.passability = pass_mode::see_through; - s.frame = (scenery::frame_t)std::clamp(fr, 0, nframes-1); + s.pass = pass_mode::see_through; + s.frame = (std::uint16_t)std::clamp(fr, 0, nframes-1); if (!s.active) s.delta = s.closing = 0; return true; } } -bool scenery_ref::activate() +bool scenery::activate(It, struct chunk&) { - auto& s = frame; - if (!*this || s.active) + auto& s = *this; + if (s.active) return false; - switch (s.type) + switch (s.sc_type) { default: case scenery_type::none: @@ -143,22 +82,29 @@ bool scenery_ref::activate() return false; } -bool scenery::is_mesh_modified(const scenery& s0, const scenery& s) +bool scenery::operator==(const entity_proto& e0) const { - if (s.interactive != s0.interactive || s.type != s0.type) - return true; - if (!s.interactive) - return s.r != s0.r || s.offset != s0.offset || s0.frame != s.frame || s.type != s0.type; - return false; + if (!entity::operator==(e0)) + return false; + + const auto& s0 = static_cast<const scenery_proto&>(e0); + return sc_type == s0.sc_type && active == s0.active && + closing == s0.closing && interactive == s0.interactive; } -#ifdef __GNUG__ -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wfloat-equal" -#endif -bool scenery::operator==(const scenery&) const noexcept = default; -#ifdef __GNUG__ -#pragma GCC diagnostic pop -#endif +scenery::scenery(std::uint64_t id, struct world& w, entity_type type, const scenery_proto& proto) : + entity{id, w, type}, sc_type{proto.sc_type}, active{proto.active}, + closing{proto.closing}, interactive{proto.interactive} +{ + fm_assert(type == proto.type); + atlas = proto.atlas; + offset = proto.offset; + bbox_offset = proto.bbox_offset; + bbox_size = proto.bbox_size; + delta = proto.delta; + frame = proto.frame; + r = proto.r; + pass = proto.pass; +} } // namespace floormat diff --git a/src/scenery.hpp b/src/scenery.hpp index 9d9ad6e6..6f8afadd 100644 --- a/src/scenery.hpp +++ b/src/scenery.hpp @@ -2,6 +2,7 @@ #include "pass-mode.hpp" #include "tile-defs.hpp" #include "rotation.hpp" +#include "entity.hpp" #include <cstdint> #include <memory> #include <type_traits> @@ -12,104 +13,43 @@ namespace floormat { struct chunk; struct anim_atlas; +struct world; enum class scenery_type : std::uint8_t { none, generic, door, }; -struct scenery final +struct scenery_proto : entity_proto { - struct none_tag_t final {}; - struct generic_tag_t final {}; - struct door_tag_t final {}; - - static constexpr auto none = none_tag_t{}; - static constexpr auto generic = generic_tag_t{}; - static constexpr auto door = door_tag_t{}; - - using frame_t = std::uint16_t; - - std::uint16_t delta = 0; - frame_t frame = 0; - Vector2b offset, bbox_offset; - Vector2ub bbox_size{usTILE_SIZE2/2}; - rotation r : 3 = rotation::N; - scenery_type type : 3 = scenery_type::none; - pass_mode passability : 2 = pass_mode::shoot_through; + scenery_type sc_type : 3 = scenery_type::none; std::uint8_t active : 1 = false; std::uint8_t closing : 1 = false; std::uint8_t interactive : 1 = false; - constexpr scenery() noexcept; - constexpr scenery(none_tag_t) noexcept; - scenery(generic_tag_t, const anim_atlas& atlas, rotation r, frame_t frame, - pass_mode passability, bool active, bool interactive, - Vector2b offset, Vector2b bbox_offset, Vector2ub bbox_size); - scenery(door_tag_t, const anim_atlas& atlas, rotation r, bool is_open, - Vector2b offset, Vector2b bbox_offset, Vector2ub bbox_size); - - static bool is_mesh_modified(const scenery& s1, const scenery& s2); - - bool operator==(const scenery&) const noexcept; + scenery_proto(); + scenery_proto(const scenery_proto&); + ~scenery_proto() noexcept override; + scenery_proto& operator=(const scenery_proto&); + operator bool() const; }; -constexpr scenery::scenery() noexcept : scenery{scenery::none_tag_t{}} {} -constexpr scenery::scenery(none_tag_t) noexcept {} - -struct scenery_proto final +struct scenery final : entity { - std::shared_ptr<anim_atlas> atlas; - scenery frame; - - scenery_proto() noexcept; - scenery_proto(const std::shared_ptr<anim_atlas>& atlas, const scenery& frame) noexcept; - scenery_proto& operator=(const scenery_proto&) noexcept; - scenery_proto(const scenery_proto&) noexcept; - - template<typename... Ts> - scenery_proto(scenery::generic_tag_t, const std::shared_ptr<anim_atlas>& atlas, Ts&&... args) : - atlas{atlas}, frame{scenery::generic, *atlas, std::forward<Ts>(args)...} - {} - - template<typename... Ts> - scenery_proto(scenery::door_tag_t, const std::shared_ptr<anim_atlas>& atlas, Ts&&... args) : - atlas{atlas}, frame{scenery::door, *atlas, std::forward<Ts>(args)...} - {} - - operator bool() const noexcept; -}; - -struct scenery_ref final { - scenery_ref(struct chunk& c, std::size_t i) noexcept; - scenery_ref(const scenery_ref&) noexcept; - scenery_ref(scenery_ref&&) noexcept; - scenery_ref& operator=(const scenery_ref&) = delete; - scenery_ref& operator=(const scenery_proto& proto) noexcept; - - operator scenery_proto() const noexcept; - operator bool() const noexcept; - - struct chunk& chunk() noexcept; - std::uint8_t index() const noexcept; - - template<std::size_t N> std::tuple_element_t<N, scenery_ref>& get() & { if constexpr(N == 0) return atlas; else return frame; } - template<std::size_t N> std::tuple_element_t<N, scenery_ref>& get() && { if constexpr(N == 0) return atlas; else return frame; } - - std::shared_ptr<anim_atlas>& atlas; - scenery& frame; + scenery_type sc_type : 3 = scenery_type::none; + std::uint8_t active : 1 = false; + std::uint8_t closing : 1 = false; + std::uint8_t interactive : 1 = false; - bool can_activate() const noexcept; - bool activate(); - bool update(float dt); - void rotate(rotation r); + bool can_activate(It it, struct chunk& c) const override; + bool activate(It it, struct chunk& c) override; + bool update(It it, struct chunk& c, float dt) override; + bool operator==(const entity_proto& p) const override; private: - struct chunk* c; - std::uint8_t idx; + friend struct world; + scenery(std::uint64_t id, struct world& w, entity_type type, const scenery_proto& proto); }; -} // namespace floormat +template<> struct entity_type_<scenery> : std::integral_constant<entity_type, entity_type::scenery> {}; -template<> struct std::tuple_size<floormat::scenery_ref> final : std::integral_constant<std::size_t, 2> {}; -template<> struct std::tuple_element<0, floormat::scenery_ref> final { using type = std::shared_ptr<floormat::anim_atlas>; }; -template<> struct std::tuple_element<1, floormat::scenery_ref> final { using type = floormat::scenery; }; +} // namespace floormat diff --git a/src/tile.cpp b/src/tile.cpp index 25b413a9..75ef71e7 100644 --- a/src/tile.cpp +++ b/src/tile.cpp @@ -6,42 +6,36 @@ namespace floormat { bool operator==(const tile_proto& a, const tile_proto& b) noexcept { return a.ground() == b.ground() && a.wall_north() == b.wall_north() && - a.wall_west() == b.wall_west() && - a.scenery() == b.scenery(); + a.wall_west() == b.wall_west(); }; tile_image_proto tile_proto::ground() const noexcept { return { ground_atlas, ground_variant }; } tile_image_proto tile_proto::wall_north() const noexcept { return { wall_north_atlas, wall_north_variant }; } tile_image_proto tile_proto::wall_west() const noexcept { return { wall_west_atlas, wall_west_variant }; } -scenery_proto tile_proto::scenery() const noexcept { return { scenery_atlas, scenery_frame }; } tile_ref::tile_ref(struct chunk& c, std::uint8_t i) noexcept : _chunk{&c}, i{i} {} std::shared_ptr<tile_atlas> tile_ref::ground_atlas() noexcept { return _chunk->_ground_atlases[i]; } std::shared_ptr<tile_atlas> tile_ref::wall_north_atlas() noexcept { return _chunk->_wall_atlases[i*2+0]; } std::shared_ptr<tile_atlas> tile_ref::wall_west_atlas() noexcept { return _chunk->_wall_atlases[i*2+1]; } -std::shared_ptr<anim_atlas> tile_ref::scenery_atlas() noexcept { return _chunk->_scenery_atlases[i]; } std::shared_ptr<const tile_atlas> tile_ref::ground_atlas() const noexcept { return _chunk->_ground_atlases[i]; } std::shared_ptr<const tile_atlas> tile_ref::wall_north_atlas() const noexcept { return _chunk->_wall_atlases[i*2+0]; } std::shared_ptr<const tile_atlas> tile_ref::wall_west_atlas() const noexcept { return _chunk->_wall_atlases[i*2+1]; } -std::shared_ptr<const anim_atlas> tile_ref::scenery_atlas() const noexcept { return _chunk->_scenery_atlases[i]; } tile_image_ref tile_ref::ground() noexcept { return {_chunk->_ground_atlases[i], _chunk->_ground_variants[i] }; } tile_image_ref tile_ref::wall_north() noexcept { return {_chunk->_wall_atlases[i*2+0], _chunk->_wall_variants[i*2+0] }; } tile_image_ref tile_ref::wall_west() noexcept { return {_chunk->_wall_atlases[i*2+1], _chunk->_wall_variants[i*2+1] }; } -scenery_ref tile_ref::scenery() noexcept { return { *_chunk, i }; } tile_image_proto tile_ref::ground() const noexcept { return { _chunk->_ground_atlases[i], _chunk->_ground_variants[i] }; } tile_image_proto tile_ref::wall_north() const noexcept { return { _chunk->_wall_atlases[i*2+0], _chunk->_wall_variants[i*2+0] }; } 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] }; } 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], + _chunk->_ground_atlases[i], _chunk->_wall_atlases[i*2+0], _chunk->_wall_atlases[i*2+1], + _chunk->_ground_variants[i], _chunk->_wall_variants[i*2+0], _chunk->_wall_variants[i*2+1], }; } @@ -52,8 +46,7 @@ bool operator==(const tile_ref& a, const tile_ref& b) noexcept else return a.ground() == b.ground() && a.wall_north() == b.wall_north() && - a.wall_west() == b.wall_west() && - a.scenery() == b.scenery(); + a.wall_west() == b.wall_west(); } } // namespace floormat diff --git a/src/tile.hpp b/src/tile.hpp index 7a6604dd..5eed7633 100644 --- a/src/tile.hpp +++ b/src/tile.hpp @@ -1,6 +1,5 @@ #pragma once #include "tile-image.hpp" -#include "scenery.hpp" namespace floormat { @@ -10,14 +9,11 @@ struct anim_atlas; 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; tile_image_proto ground() const noexcept; tile_image_proto wall_north() const noexcept; tile_image_proto wall_west() const noexcept; - scenery_proto scenery() const noexcept; friend bool operator==(const tile_proto& a, const tile_proto& b) noexcept; }; @@ -29,22 +25,18 @@ struct tile_ref final tile_image_ref ground() noexcept; tile_image_ref wall_north() noexcept; tile_image_ref wall_west() noexcept; - scenery_ref scenery() noexcept; tile_image_proto ground() const noexcept; tile_image_proto wall_north() const noexcept; tile_image_proto wall_west() const noexcept; - scenery_proto scenery() const noexcept; std::shared_ptr<tile_atlas> ground_atlas() noexcept; std::shared_ptr<tile_atlas> wall_north_atlas() noexcept; std::shared_ptr<tile_atlas> wall_west_atlas() noexcept; - std::shared_ptr<anim_atlas> scenery_atlas() noexcept; std::shared_ptr<const tile_atlas> ground_atlas() const noexcept; std::shared_ptr<const tile_atlas> wall_north_atlas() const noexcept; std::shared_ptr<const tile_atlas> wall_west_atlas() const noexcept; - std::shared_ptr<const anim_atlas> scenery_atlas() const noexcept; explicit operator tile_proto() const noexcept; diff --git a/src/world.cpp b/src/world.cpp index 1fcee1d8..ea767422 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -1,5 +1,6 @@ #include "world.hpp" #include "chunk.hpp" +#include "entity.hpp" namespace floormat { @@ -7,6 +8,14 @@ world::world() : world{initial_capacity} { } +world::~world() noexcept +{ + _teardown = true; + _last_chunk = {}; + _chunks.clear(); + _entities.clear(); +} + world::world(std::size_t capacity) : _chunks{capacity, hasher} { _chunks.max_load_factor(max_load_factor); @@ -69,4 +78,24 @@ void world::collect(bool force) fm_debug("world: collected %zu/%zu chunks", len, len0); } +static constexpr std::uint64_t min_id = 1u << 16; +std::uint64_t world::entity_counter = min_id; + +void world::do_make_entity(const std::shared_ptr<entity>& e, global_coords pos) +{ + fm_debug_assert(e->id > min_id && &e->w == this); + fm_assert(Vector2ui(e->bbox_size).product() > 0); + fm_assert(e->type != entity_type::none); + e->coord = pos; + _entities[e->id] = e; + operator[](pos.chunk()).add_entity(e); +} + +void world::do_kill_entity(std::uint64_t id) +{ + fm_debug_assert(id > min_id); + auto cnt = _entities.erase(id); + fm_debug_assert(cnt > 0); +} + } // namespace floormat diff --git a/src/world.hpp b/src/world.hpp index 36703fd3..6781ee3c 100644 --- a/src/world.hpp +++ b/src/world.hpp @@ -9,6 +9,9 @@ namespace floormat { +struct entity; +template<typename T> struct entity_type_; + struct world final { private: @@ -24,13 +27,24 @@ private: return int_hash((std::size_t)c.y << 16 | (std::size_t)c.x); }; std::unordered_map<chunk_coords, chunk, decltype(hasher)> _chunks; + std::unordered_map<std::uint64_t, std::weak_ptr<entity>> _entities; std::size_t _last_collection = 0; std::size_t _collect_every = 64; + bool _teardown : 1 = false; + + static std::uint64_t entity_counter; + explicit world(std::size_t capacity); + void do_make_entity(const std::shared_ptr<entity>& e, global_coords pos); + void do_kill_entity(std::uint64_t id); + + friend struct entity; + public: explicit world(); + ~world() noexcept; struct pair final { chunk& c; tile_ref t; }; // NOLINT @@ -52,6 +66,18 @@ public: void set_collect_threshold(std::size_t value) { _collect_every = value; } std::size_t collect_threshold() const noexcept { return _collect_every; } + template<typename T, typename... Xs> + requires requires { T{std::uint64_t(), std::declval<world&>(), entity_type(), std::declval<Xs>()...}; } + std::shared_ptr<T> make_entity(global_coords pos, Xs&&... xs) + { + static_assert(std::is_base_of_v<entity, T>); + auto ret = std::shared_ptr<T>(new T{++entity_counter, *this, entity_type_<T>::value, std::forward<Xs>(xs)...}); + do_make_entity(std::static_pointer_cast<entity>(ret), pos); + return ret; + } + + bool is_teardown() const { return _teardown; } + fm_DECLARE_DEPRECATED_COPY_ASSIGNMENT(world); fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT_(world); }; |