diff options
40 files changed, 969 insertions, 686 deletions
diff --git a/draw/anim.cpp b/draw/anim.cpp index 44be4129..df67585b 100644 --- a/draw/anim.cpp +++ b/draw/anim.cpp @@ -25,24 +25,20 @@ std::array<UnsignedShort, 6> anim_mesh::make_index_array() }}; } -void anim_mesh::add_clickable(tile_shader& shader, const Vector2i& win_size, - chunk_coords c, std::uint8_t i, const std::shared_ptr<anim_atlas>& atlas, scenery& s, - std::vector<clickable>& list) +void anim_mesh::add_clickable(tile_shader& shader, const Vector2i& win_size, const std::shared_ptr<entity>& s, std::vector<clickable>& list) { - const local_coords xy{i}; - const auto& a = *atlas; - const auto& g = a.group(s.r); - const auto& f = a.frame(s.r, s.frame); - const auto world_pos = TILE_SIZE20 * Vector3(xy) + Vector3(g.offset) + Vector3(Vector2(s.offset), 0); + const auto& a = *s->atlas; + const auto& g = a.group(s->r); + const auto& f = a.frame(s->r, s->frame); + const auto world_pos = TILE_SIZE20 * Vector3(s->coord.local()) + Vector3(g.offset) + Vector3(Vector2(s->offset), 0); const Vector2i offset((Vector2(shader.camera_offset()) + Vector2(win_size)*.5f) + shader.project(world_pos) - Vector2(f.ground)); if (offset < win_size && offset + Vector2i(f.size) >= Vector2i()) { clickable item = { { f.offset, f.offset + f.size }, { offset, offset + Vector2i(f.size) }, - a.bitmask(), tile_shader::depth_value(xy, tile_shader::scenery_depth_offset), + a.bitmask(), s, s->ordinal(), a.info().pixel_size[0], - c, xy, !g.mirror_from.isEmpty(), }; list.push_back(item); @@ -52,12 +48,14 @@ void anim_mesh::add_clickable(tile_shader& shader, const Vector2i& win_size, void anim_mesh::draw(tile_shader& shader, chunk& c) { constexpr auto quad_index_count = 6; - auto [mesh_, ids, size] = c.ensure_scenery_mesh(); + auto [mesh_] = c.ensure_scenery_mesh(); + const auto& es = c.entities(); GL::MeshView mesh{mesh_}; + [[maybe_unused]] std::size_t draw_count = 0; anim_atlas* bound = nullptr; - [[maybe_unused]] std::size_t draw_count = 0; - fm_debug_assert(std::size_t(mesh_.count()) == size*quad_index_count); + const auto size = es.size(); + const auto max_index = std::uint32_t(size*quad_index_count - 1); const auto do_draw = [&](std::size_t from, std::size_t to, anim_atlas* atlas, std::uint32_t max_index) { if (atlas != bound) @@ -69,48 +67,40 @@ void anim_mesh::draw(tile_shader& shader, chunk& c) draw_count++; }; - struct last_ { anim_atlas* atlas = nullptr; std::size_t run_from = 0; }; - Optional<last_> last; - const auto max_index = std::uint32_t(size*quad_index_count - 1); + fm_debug_assert(std::size_t(mesh_.count()) <= size*quad_index_count); + + struct last_ { + anim_atlas* atlas = nullptr; std::size_t run_from = 0; + operator bool() const { return atlas; } + last_& operator=(std::nullptr_t) { atlas = nullptr; return *this; } + } last; + std::size_t i = 0; - auto last_id = 0_uz; for (auto k = 0_uz; k < size; k++) { - auto id = ids[k]; - auto [atlas, s] = c[id].scenery(); - for (auto i = last_id+1; i < id; i++) - if (auto [atlas, s] = c[i].scenery(); - atlas && atlas->info().fps > 0) + const auto& e = *es[k]; + auto& atlas = *e.atlas; + if (atlas.info().fps > 0) + { + if (last) + do_draw(last.run_from, i, last.atlas, max_index); + draw(shader, atlas, e.r, e.frame, e.coord.local(), e.offset, tile_shader::scenery_depth_offset); + last = nullptr; + } + else + { + if (last && &atlas != last.atlas) { - if (last) - { - do_draw(last->run_from, k, last->atlas, max_index); - last = NullOpt; - } - bound = nullptr; - draw(shader, *atlas, s.r, s.frame, local_coords{i}, s.offset, tile_shader::scenery_depth_offset); + do_draw(last.run_from, i, last.atlas, max_index); + last = nullptr; } - last_id = id; - if (last && atlas && &*atlas != last->atlas) - { - do_draw(last->run_from, k, last->atlas, max_index); - last = NullOpt; + if (!last) + last = { &atlas, i }; + i++; } - if (!last) - last = { InPlaceInit, &*atlas, k }; - } - if (size > 0) - { - if (last) - do_draw(last->run_from, size, last->atlas, max_index); - for (std::size_t i = ids[size-1]+1; i < TILE_COUNT; i++) - if (auto [atlas, s] = c[i].scenery(); atlas && atlas->info().fps > 0) - draw(shader, *atlas, s.r, s.frame, local_coords{i}, s.offset, tile_shader::scenery_depth_offset); } - else - for (auto i = 0_uz; i < TILE_COUNT; i++) - if (auto [atlas, s] = c[i].scenery(); atlas) - draw(shader, *atlas, s.r, s.frame, local_coords{i}, s.offset, tile_shader::scenery_depth_offset); + if (last) + do_draw(last.run_from, size, last.atlas, max_index); //#define FM_DEBUG_DRAW_COUNT #ifdef FM_DEBUG_DRAW_COUNT diff --git a/draw/anim.hpp b/draw/anim.hpp index 4a3e4c40..b7d0a0cd 100644 --- a/draw/anim.hpp +++ b/draw/anim.hpp @@ -1,5 +1,6 @@ #pragma once #include "local-coords.hpp" +#include "rotation.hpp" #include <array> #include <Corrade/Containers/ArrayViewStl.h> #include <Magnum/Magnum.h> @@ -7,7 +8,6 @@ #include <Magnum/Math/Vector3.h> #include <Magnum/GL/Mesh.h> #include <Magnum/GL/Buffer.h> -#include "src/scenery.hpp" #include "main/clickable.hpp" //namespace floormat::Serialize { struct anim_frame; } @@ -18,7 +18,7 @@ struct tile_shader; struct anim_atlas; struct chunk; struct clickable; -struct scenery; +struct entity; struct anim_mesh final { @@ -27,9 +27,7 @@ struct anim_mesh final void draw(tile_shader& shader, chunk& c); void draw(tile_shader& shader, anim_atlas& atlas, rotation r, std::size_t frame, const Vector3& pos, float depth); void draw(tile_shader& shader, anim_atlas& atlas, rotation r, std::size_t frame, local_coords xy, Vector2b offset, float depth_offset); - static void add_clickable(tile_shader& shader, const Vector2i& win_size, - chunk_coords c, std::uint8_t i, const std::shared_ptr<anim_atlas>& atlas, scenery& s, - std::vector<clickable>& list); + static void add_clickable(tile_shader& shader, const Vector2i& win_size, const std::shared_ptr<entity>& s, std::vector<clickable>& list); private: struct vertex_data final { diff --git a/editor/app.cpp b/editor/app.cpp index 80599ed1..aa8608c5 100644 --- a/editor/app.cpp +++ b/editor/app.cpp @@ -33,7 +33,7 @@ app::app(fm_settings&& opts) : maybe_initialize_chunk_(coord, w[coord]); reset_camera_offset(); inspectors.reserve(16); - _character = std::make_unique<character_wip>(); + _character = w.make_entity<character>(global_coords{}); } app::~app() diff --git a/editor/app.hpp b/editor/app.hpp index 56660845..9e6b1312 100644 --- a/editor/app.hpp +++ b/editor/app.hpp @@ -23,7 +23,7 @@ struct tile_atlas; struct tile_editor; struct fm_settings; struct anim_atlas; -struct character_wip; +struct character; struct cursor_state final { Optional<Vector2i> pixel; @@ -44,8 +44,7 @@ enum class popup_target_type : unsigned char { }; struct popup_target final { - chunk_coords c; - local_coords pos; + std::shared_ptr<entity> e; popup_target_type target = popup_target_type::none; bool operator==(const popup_target&) const; }; @@ -150,7 +149,7 @@ private: key_set keys; std::array<int, key_set::COUNT> key_modifiers = {}; std::vector<popup_target> inspectors; - std::unique_ptr<character_wip> _character; + std::shared_ptr<character> _character; cursor_state cursor; popup_target _popup_target; diff --git a/editor/character.cpp b/editor/character.cpp deleted file mode 100644 index 845f8345..00000000 --- a/editor/character.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "character.hpp" -#include "src/anim-atlas.hpp" -#include "loader/loader.hpp" -#include "src/world.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 inv_tile_size = 1 / TILE_SIZE2; -constexpr Vector2b bbox_size(12); - -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_wip::character_wip() : - walk_anim{loader.anim_atlas("npc-walk", loader.ANIM_PATH)} -{ -} - -character_wip::~character_wip() = default; - -int character_wip::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_wip::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_wip::tick(world& w, float dt, bool L, bool R, bool U, bool D) -{ - auto [lr, ud, rot] = arrows_to_dir(L, R, U, D); - - if (!lr & !ud) - { - delta = 0; - return; - } - - int nframes = allocate_frame_time(dt); - - if (!nframes) - return; - - const auto vec = move_vec(lr, ud); - r = rot; - - for (int i = 0; i < nframes; i++) - { - auto pos_ = pos; - Vector2 offset_ = offset; - offset_ += vec; - auto pos_1 = Vector2i(offset_ * inv_tile_size); - pos_ += pos_1; - offset_ = Vector2(std::fmod(offset_[0], TILE_SIZE2[0]), std::fmod(offset_[1], TILE_SIZE2[1])); - constexpr auto half_tile = TILE_SIZE2/2; - if (auto off = offset_[0]; std::fabs(off) > half_tile[0]) - { - pos_ += Vector2i(offset_[0] < 0 ? -1 : 1, 0); - offset_[0] = std::copysign(TILE_SIZE[0] - std::fabs(offset_[0]), -off); - } - if (auto off = offset_[1]; std::fabs(off) > half_tile[1]) - { - pos_ += Vector2i(0, offset_[1] < 0 ? -1 : 1); - offset_[1] = std::copysign(TILE_SIZE[1] - std::fabs(offset_[1]), -off); - } - auto [c, t] = w[pos_]; - const auto& r = c.rtree(); - auto center = Vector2(pos_.local()) * TILE_SIZE2 + offset_; - auto half_bbox = Vector2(bbox_size)*.5f; - auto min = center - half_bbox; - auto max = center + half_bbox; - bool is_blocked = false; - r->Search(min.data(), max.data(), [&](const std::uint64_t data, const auto&) { - auto cdata = std::bit_cast<collision_data>(data); - is_blocked |= (pass_mode)cdata.pass != pass_mode::pass; - return !is_blocked; - }); - if (is_blocked) - break; - pos = pos_; - offset = offset_; - ++frame %= walk_anim->info().nframes; - } - //Debug{} << "pos" << Vector2i(pos.local()); -} - -} // namespace floormat diff --git a/editor/character.hpp b/editor/character.hpp deleted file mode 100644 index 7172415c..00000000 --- a/editor/character.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once -#include "src/global-coords.hpp" -#include "src/rotation.hpp" -#include <memory> - -namespace floormat { - -struct anim_atlas; -struct world; - -struct character_wip final -{ - character_wip(); - ~character_wip(); - void tick(world& w, float dt, bool L, bool R, bool U, bool D); - - std::shared_ptr<anim_atlas> walk_anim; - global_coords pos; - Vector2 offset; - std::uint16_t delta = 0, frame = 0; - rotation r = rotation::NE; - -private: - int allocate_frame_time(float dt); - static Vector2 move_vec(int left_right, int top_bottom); -}; - -} // namespace floormat diff --git a/editor/draw.cpp b/editor/draw.cpp index 7ce5c7bf..dd286edf 100644 --- a/editor/draw.cpp +++ b/editor/draw.cpp @@ -54,7 +54,7 @@ void app::draw_cursor() shader.set_tint({1, 1, 1, 0.75f}); auto [_f, _w, anim_mesh] = M->meshes(); const auto pos = cursor.tile->to_signed3()*iTILE_SIZE; - anim_mesh.draw(shader, *sel.atlas, sel.frame.r, sel.frame.frame, Vector3(pos), 1); + anim_mesh.draw(shader, *sel.atlas, sel.r, sel.frame, Vector3(pos), 1); } } @@ -153,9 +153,9 @@ void app::draw_character() auto& c = *_character; const auto [minx, maxx, miny, maxy] = M->get_draw_bounds(); - const with_shifted_camera_offset o{shader, c.pos.chunk(), {minx, miny}, {maxx, maxy}}; + const with_shifted_camera_offset o{shader, c.coord.chunk(), {minx, miny}, {maxx, maxy}}; if (floormat_main::check_chunk_visible(shader.camera_offset(), sz)) - mesh.draw(shader, *c.walk_anim, c.r, c.frame, c.pos.local(), Vector2b(c.offset), tile_shader::character_depth_offset); + mesh.draw(shader, *c.atlas, c.r, c.frame, c.coord.local(), Vector2b(c.offset), tile_shader::character_depth_offset); GL::Renderer::setDepthMask(true); GL::Renderer::disable(GL::Renderer::Feature::DepthTest); @@ -163,7 +163,7 @@ void app::draw_character() void app::draw() { - draw_character(); + //draw_character(); if (_render_bboxes) draw_collision_boxes(); if (_editor.current_tile_editor() || _editor.current_scenery_editor()) diff --git a/editor/imgui-editors.cpp b/editor/imgui-editors.cpp index cdc99c69..77658680 100644 --- a/editor/imgui-editors.cpp +++ b/editor/imgui-editors.cpp @@ -69,7 +69,7 @@ void app::draw_editor_scenery_pane(scenery_editor& ed) } if (ImGui::TableSetColumnIndex(2)) { - switch (scenery.proto.frame.type) + switch (scenery.proto.sc_type) { case scenery_type::none: text("none"); break; case scenery_type::generic: text("generic"); break; diff --git a/editor/imgui-inspect.cpp b/editor/imgui-inspect.cpp index b673c9d5..0d1ab6cf 100644 --- a/editor/imgui-inspect.cpp +++ b/editor/imgui-inspect.cpp @@ -29,15 +29,11 @@ void app::draw_inspector() for (auto i = inspectors.size()-1; i != -1_uz; i--) { - auto [ch, pos, target] = inspectors[i]; - auto [c, t] = w[{ch, pos}]; - auto s = t.scenery(); - - if (!s) - { - inspectors.erase(inspectors.begin() + (int)i); - continue; - } + auto [e, target] = inspectors[i]; + auto& s = *e; + chunk_coords ch = e->coord.chunk(); + local_coords pos = e->coord.local(); + auto& c = w[ch]; char buf[128]; snformat(buf, "i-{}-{}x{}-{}x{}"_cf, (int)target, ch.x, ch.y, (int)pos.x, (int)pos.y); @@ -48,7 +44,13 @@ void app::draw_inspector() snformat(buf, "{} ({}x{} -> {}x{})"_cf, name, ch.x, ch.y, (int)pos.x, (int)pos.y); bool is_open = true; if (auto b2 = begin_window(buf, &is_open)) - c.with_scenery_update(s.index(), [&] { return entities::inspect_type(s); }); + { + if (s.type == entity_type::scenery) + { + auto& s2 = static_cast<scenery&>(s); + c.with_scenery_update(s, [&] { return entities::inspect_type(s2); }); + } + } if (!is_open) inspectors.erase(inspectors.begin() + (int)i); } diff --git a/editor/imgui.cpp b/editor/imgui.cpp index 753475d3..4ce19403 100644 --- a/editor/imgui.cpp +++ b/editor/imgui.cpp @@ -138,19 +138,14 @@ bool app::check_inspector_exists(popup_target p) void app::do_popup_menu() { fm_assert(_popup_target.target != popup_target_type::none); - auto& w = M->world(); - auto b0 = push_id(SCENERY_POPUP_NAME); - - const auto [ch, pos, type] = _popup_target; - auto [c, t] = w[{ch, pos}]; + const auto [sc, target] = _popup_target; //if (_popup_target.target != popup_target_type::scenery) {...} - auto sc = t.scenery(); if (_pending_popup) { _pending_popup = false; - fm_assert(type != popup_target_type::none); + fm_assert(target != popup_target_type::none); //if (type != popup_target_type::scenery) {...} if (sc) ImGui::OpenPopup(SCENERY_POPUP_NAME.data()); @@ -158,11 +153,13 @@ void app::do_popup_menu() if (auto b1 = begin_popup(SCENERY_POPUP_NAME)) { - if (ImGui::MenuItem("Activate", nullptr, false, sc.can_activate())) - sc.activate(); - if (auto next_rot = sc.atlas->next_rotation_from(sc.frame.r); - ImGui::MenuItem("Rotate", nullptr, false, next_rot != sc.frame.r)) - sc.rotate(next_rot); + auto iter = sc->iter(); + auto& c = sc->chunk(); + if (ImGui::MenuItem("Activate", nullptr, false, sc->can_activate(iter, c))) + sc->activate(iter, c); + if (auto next_rot = sc->atlas->next_rotation_from(sc->r); + ImGui::MenuItem("Rotate", nullptr, false, next_rot != sc->r)) + sc->rotate(iter, c, next_rot); ImGui::Separator(); diff --git a/editor/inspect-types.cpp b/editor/inspect-types.cpp index 110ebabf..9b14c132 100644 --- a/editor/inspect-types.cpp +++ b/editor/inspect-types.cpp @@ -20,53 +20,52 @@ static Corrade::Containers::String my_str; namespace floormat::entities { template<> -struct entity_accessors<scenery_ref> { +struct entity_accessors<scenery> { static constexpr auto accessors() { - using entity = Entity<scenery_ref>; - using frame_t = scenery::frame_t; + using entity = Entity<scenery>; return std::tuple{ entity::type<StringView>::field{"name"_s, - [](const scenery_ref& x) { return loader.strip_prefix(x.atlas->name()); }, - [](scenery_ref&, StringView) {}, + [](const scenery& x) { return loader.strip_prefix(x.atlas->name()); }, + [](scenery&, StringView) {}, constantly(field_status::readonly), }, entity::type<rotation>::field{"rotation"_s, - [](const scenery_ref& x) { return x.frame.r; }, - [](scenery_ref& x, rotation r) { x.rotate(r); }, + [](const scenery& x) { return x.r; }, + [](scenery& x, rotation r) { x.rotate(x.iter(), x.chunk(), r); }, }, - entity::type<scenery::frame_t>::field{"frame"_s, - [](const scenery_ref& x) { return x.frame.frame; }, - [](scenery_ref& x, frame_t value) { x.frame.frame = value; }, - [](const scenery_ref& x) { return constraints::range<frame_t>{0, !x.atlas ? frame_t(0) : frame_t(x.atlas->info().nframes-1)}; } + entity::type<std::uint16_t>::field{"frame"_s, + [](const scenery& x) { return x.frame; }, + [](scenery& x, std::uint16_t value) { x.frame = value; }, + [](const scenery& x) { return constraints::range<std::uint16_t>{0, !x.atlas ? std::uint16_t(0) : std::uint16_t(x.atlas->info().nframes-1)}; } }, entity::type<Vector2b>::field{"offset"_s, - [](const scenery_ref& x) { return x.frame.offset; }, - [](scenery_ref& x, Vector2b value) { x.frame.offset = value; }, + [](const scenery& x) { return x.offset; }, + [](scenery& x, Vector2b value) { x.offset = value; }, constantly(constraints::range{Vector2b(iTILE_SIZE2/-2), Vector2b(iTILE_SIZE2/2)}) }, entity::type<pass_mode>::field{"pass-mode"_s, - [](const scenery_ref& x) { return x.frame.passability; }, - [](scenery_ref& x, pass_mode value) { x.chunk().with_scenery_update(x.index(), [&] { x.frame.passability = value; }); }, + [](const scenery& x) { return x.pass; }, + [](scenery& x, pass_mode value) { x.chunk().with_scenery_update(x, [&] { x.pass = value; }); }, }, entity::type<Vector2b>::field{"bbox-offset"_s, - [](const scenery_ref& x) { return x.frame.bbox_offset; }, - [](scenery_ref& x, Vector2b value) { x.chunk().with_scenery_update(x.index(), [&] { x.frame.bbox_offset = value; }); }, - [](const scenery_ref& x) { return x.frame.passability == pass_mode::pass ? field_status::readonly : field_status::enabled; }, + [](const scenery& x) { return x.bbox_offset; }, + [](scenery& x, Vector2b value) { x.chunk().with_scenery_update(x, [&] { x.bbox_offset = value; }); }, + [](const scenery& x) { return x.pass == pass_mode::pass ? field_status::readonly : field_status::enabled; }, }, entity::type<Vector2ub>::field{"bbox-size"_s, - [](const scenery_ref& x) { return x.frame.bbox_size; }, - [](scenery_ref& x, Vector2ub value) { x.chunk().with_scenery_update(x.index(), [&] { x.frame.bbox_size = value; }); }, - [](const scenery_ref& x) { return x.frame.passability == pass_mode::pass ? field_status::readonly : field_status::enabled; }, + [](const scenery& x) { return x.bbox_size; }, + [](scenery& x, Vector2ub value) { x.chunk().with_scenery_update(x, [&] { x.bbox_size = value; }); }, + [](const scenery& x) { return x.pass == pass_mode::pass ? field_status::readonly : field_status::enabled; }, }, entity::type<bool>::field{"interactive"_s, - [](const scenery_ref& x) { return x.frame.interactive; }, - [](scenery_ref& x, bool value) { x.frame.interactive = value; } + [](const scenery& x) { return x.interactive; }, + [](scenery& x, bool value) { x.interactive = value; } }, #ifdef TEST_STR entity::type<String>::field{"string"_s, - [](const scenery_ref&) { return my_str; }, - [](scenery_ref&, String value) { my_str = std::move(value); }, + [](const scenery&) { return my_str; }, + [](scenery&, String value) { my_str = std::move(value); }, constantly(constraints::max_length{8}), }, #endif @@ -75,8 +74,8 @@ struct entity_accessors<scenery_ref> { }; template<typename T, typename = void> struct has_anim_atlas : std::false_type {}; -template<> struct has_anim_atlas<scenery_ref> : std::true_type { - static const anim_atlas& get_atlas(const scenery_ref& x) { +template<> struct has_anim_atlas<scenery> : std::true_type { + static const anim_atlas& get_atlas(const scenery& x) { fm_assert(x.atlas); return *x.atlas; } @@ -135,12 +134,12 @@ struct enum_values<rotation, U> : std::false_type { }; template<> -bool inspect_type<scenery_ref>(scenery_ref& x) +bool inspect_type<scenery>(scenery& x) { bool ret = false; visit_tuple([&](const auto& field) { using type = typename std::decay_t<decltype(field)>::FieldType; - using enum_type = enum_values<type, scenery_ref>; + using enum_type = enum_values<type, scenery>; if constexpr(enum_type::value) { constexpr auto list = enum_type::get(); @@ -151,7 +150,7 @@ bool inspect_type<scenery_ref>(scenery_ref& x) const auto& list = enum_type::get(x); ret |= inspect_field<type>(&x, field.erased(), list); } - }, entity_metadata<scenery_ref>::accessors); + }, entity_metadata<scenery>::accessors); return ret; } diff --git a/editor/scenery-editor.cpp b/editor/scenery-editor.cpp index 1d0635a6..9e9aa47b 100644 --- a/editor/scenery-editor.cpp +++ b/editor/scenery-editor.cpp @@ -22,7 +22,7 @@ scenery_editor::scenery_editor() noexcept void scenery_editor::set_rotation(rotation_ r) { - auto& s = _selected.proto.frame; + auto& s = _selected.proto; s.bbox_offset = rotate_point(s.bbox_offset, s.r, r); s.bbox_size = rotate_size(s.bbox_size, s.r, r); s.r = r; @@ -30,24 +30,24 @@ void scenery_editor::set_rotation(rotation_ r) rotation_ scenery_editor::rotation() const { - return _selected.proto.frame.r; + return _selected.proto.r; } void scenery_editor::next_rotation() { - set_rotation(_selected.proto.atlas->next_rotation_from(_selected.proto.frame.r)); + set_rotation(_selected.proto.atlas->next_rotation_from(_selected.proto.r)); } void scenery_editor::prev_rotation() { - set_rotation(_selected.proto.atlas->prev_rotation_from(_selected.proto.frame.r)); + set_rotation(_selected.proto.atlas->prev_rotation_from(_selected.proto.r)); } void scenery_editor::select_tile(const scenery_& s) { - const auto r = s.proto.atlas && s.proto.atlas->check_rotation(_selected.proto.frame.r) - ? _selected.proto.frame.r - : s.proto.frame.r; + const auto r = s.proto.atlas && s.proto.atlas->check_rotation(_selected.proto.r) + ? _selected.proto.r + : s.proto.r; _selected = s; set_rotation(r); } @@ -80,7 +80,8 @@ bool scenery_editor::is_anything_selected() const void scenery_editor::place_tile(world& w, global_coords pos, const scenery_& s) { auto [c, t] = w[pos]; - t.scenery() = s.proto; + // todo check collision at pos + auto sc = w.make_entity<scenery>(pos, s.proto); c.mark_scenery_modified(); } diff --git a/editor/update.cpp b/editor/update.cpp index e9f178a1..24fd3086 100644 --- a/editor/update.cpp +++ b/editor/update.cpp @@ -69,28 +69,12 @@ void app::do_mouse_up_down(std::uint8_t button, bool is_down, int mods) if (button == mouse_button_left) { if (auto* cl = find_clickable_scenery(*cursor.pixel)) - { - auto [c, t] = w[{cl->chunk, cl->pos}]; - if (auto s = t.scenery()) - return (void)s.activate(); - } + return (void)cl->e->activate(cl->e->iter(), cl->e->chunk()); } // TODO it should open on mouseup if still on the same item as on mousedown else if (button == mouse_button_right) - { if (auto* cl = find_clickable_scenery(*cursor.pixel)) - { - auto [c, t] = w[{cl->chunk, cl->pos}]; - if (auto s = t.scenery()) - { - _popup_target = { - .c = cl->chunk, .pos = cl->pos, - .target = popup_target_type::scenery, - }; - do_open_popup(); - } - } - } + _popup_target = { .e = cl->e, .target = popup_target_type::scenery, }; break; case editor_mode::floor: case editor_mode::walls: @@ -120,18 +104,14 @@ void app::do_rotate(bool backward) { if (ed->is_anything_selected()) backward ? ed->prev_rotation() : ed->next_rotation(); - else if (cursor.tile) + else if (auto* cl = find_clickable_scenery(*cursor.pixel)) { - auto [c, t] = M->world()[*cursor.tile]; - if (auto sc = t.scenery()) + auto& e = *cl->e; + auto r = backward ? e.atlas->prev_rotation_from(e.r) : e.atlas->next_rotation_from(e.r); + if (r != e.r) { - auto [atlas, s] = sc; - auto r = backward ? atlas->prev_rotation_from(s.r) : atlas->next_rotation_from(s.r); - if (r != s.r) - { - sc.rotate(r); - c.mark_scenery_modified(); - } + e.rotate(e.iter(), e.chunk(), r); + e.chunk().mark_scenery_modified(); } } } @@ -204,16 +184,24 @@ void app::update_world(float dt) minx--; miny--; maxx++; maxy++; for (std::int16_t y = miny; y <= maxy; y++) for (std::int16_t x = minx; x <= maxx; x++) - for (auto& c = world[chunk_coords{x, y}]; auto [x, k, pt] : c) - if (auto sc = x.scenery(); sc && sc.can_activate()) - c.with_scenery_update(sc.index(), [&] { return sc.update(dt); }); + { + auto& c = world[chunk_coords{x, y}]; + const auto& es = c.entities(); + const auto size = es.size(); + for (auto i = size-1; i != (std::size_t)-1; i--) + { + auto iter = es.cbegin() + std::ptrdiff_t(i); + auto& e = *es[i]; + c.with_scenery_update(e, [&] { return e.update(iter, c, dt); }); + } + } } -void app::update_character(float dt) +void app::update_character([[maybe_unused]] float dt) { - _character->tick(M->world(), dt, keys[key_left], keys[key_right], keys[key_up], keys[key_down]); + _character->set_keys(keys[key_left], keys[key_right], keys[key_up], keys[key_down]); } void app::set_cursor() @@ -221,16 +209,9 @@ void app::set_cursor() if (!cursor.in_imgui) { if (auto* cl = find_clickable_scenery(cursor.pixel)) - { - auto& w = M->world(); - auto [c, t] = w[{cl->chunk, cl->pos}]; - if (auto sc = t.scenery()) - { - M->set_cursor(std::uint32_t(Cursor::Hand)); - return; - } - } - M->set_cursor(std::uint32_t(Cursor::Arrow)); + M->set_cursor(std::uint32_t(Cursor::Hand)); + else + M->set_cursor(std::uint32_t(Cursor::Arrow)); } else set_cursor_from_imgui(); diff --git a/main/clickable.hpp b/main/clickable.hpp index 3ca8ccb6..c7502f33 100644 --- a/main/clickable.hpp +++ b/main/clickable.hpp @@ -1,18 +1,19 @@ #pragma once #include "src/global-coords.hpp" +#include <memory> #include <Corrade/Containers/BitArrayView.h> #include <Magnum/Math/Range.h> namespace floormat { +struct entity; + struct clickable final { Math::Range2D<unsigned> src; Math::Range2D<int> dest; BitArrayView bitmask; - float depth = 0; - std::uint32_t stride; - chunk_coords chunk; - local_coords pos; + std::shared_ptr<entity> e; + std::uint32_t depth, stride; bool mirrored; }; diff --git a/main/draw.cpp b/main/draw.cpp index 57623eef..84d90f65 100644 --- a/main/draw.cpp +++ b/main/draw.cpp @@ -136,12 +136,8 @@ void main_impl::draw_world() noexcept if (check_chunk_visible(_shader.camera_offset(), sz)) { _anim_mesh.draw(_shader, c); - for (auto i = 0_uz; i < TILE_COUNT; i++) - { - const local_coords xy{i}; - if (auto [atlas, s] = c[xy].scenery(); atlas) - _anim_mesh.add_clickable(_shader, window_size(), pos, std::uint8_t(i), atlas, s, _clickable_scenery); - } + for (const auto& e : c.entities()) + _anim_mesh.add_clickable(_shader, window_size(), e, _clickable_scenery); } } diff --git a/serialize/scenery.cpp b/serialize/scenery.cpp index 4eeb95f7..3cd084cd 100644 --- a/serialize/scenery.cpp +++ b/serialize/scenery.cpp @@ -80,20 +80,19 @@ void adl_serializer<rotation>::from_json(const json& j, rotation& val) val = foo_from_string(j, rotation_map, "rotation"); } -void adl_serializer<scenery_proto>::to_json(json& j, const scenery_proto& val) +void adl_serializer<scenery_proto>::to_json(json& j, const scenery_proto& f) { - fm_assert(val.atlas); - constexpr scenery default_scenery; - const scenery& f = val.frame; + fm_assert(f.atlas); + const scenery_proto default_scenery; if (f.type != default_scenery.type) j["type"] = f.type; - j["atlas-name"] = val.atlas->name(); + j["atlas-name"] = f.atlas->name(); if (f.frame != default_scenery.frame) j["frame"] = f.frame; if (f.r != default_scenery.r) j["rotation"] = f.r; - if (f.passability != default_scenery.passability) - j["pass-mode"] = f.passability; + if (f.pass != default_scenery.pass) + j["pass-mode"] = f.pass; if (f.active != default_scenery.active) j["active"] = f.active; if (f.interactive != default_scenery.interactive) @@ -106,7 +105,7 @@ void adl_serializer<scenery_proto>::to_json(json& j, const scenery_proto& val) j["bbox-size"] = Vector2ui(f.bbox_size); } -void adl_serializer<scenery_proto>::from_json(const json& j, scenery_proto& val) +void adl_serializer<scenery_proto>::from_json(const json& j, scenery_proto& f) { const auto get = [&](const StringView& name, auto& value) { @@ -117,14 +116,13 @@ void adl_serializer<scenery_proto>::from_json(const json& j, scenery_proto& val) StringView atlas_name = j["atlas-name"]; fm_soft_assert(!atlas_name.isEmpty()); - val.atlas = loader.anim_atlas(atlas_name, loader_::SCENERY_PATH); - auto& f = val.frame; f = {}; + f.atlas = loader.anim_atlas(atlas_name, loader_::SCENERY_PATH); auto type = scenery_type::generic; get("type", type); auto frame = f.frame; get("frame", frame); - auto r = val.atlas->first_rotation(); get("rotation", r); - pass_mode pass = f.passability; get("pass-mode", pass); + auto r = f.atlas->first_rotation(); get("rotation", r); + pass_mode pass = f.pass; get("pass-mode", pass); bool active = f.active; get("active", active); bool interactive = f.interactive; get("interactive", interactive); auto offset = Vector2i(f.offset); get("offset", offset); @@ -139,13 +137,30 @@ void adl_serializer<scenery_proto>::from_json(const json& j, scenery_proto& val) default: fm_throw("unhandled scenery type '{}'"_cf, (unsigned)type); case scenery_type::generic: - f = { scenery::generic, *val.atlas, r, frame, pass, - active, interactive, - Vector2b(offset), Vector2b(bbox_offset), Vector2ub(bbox_size) }; + f.type = entity_type::scenery; + f.sc_type = scenery_type::generic; + f.r = r; + f.frame = frame; + f.pass = pass; + f.active = active; + f.interactive = interactive; + f.offset = Vector2b(offset); + f.bbox_offset = Vector2b(bbox_offset); + f.bbox_size = Vector2ub(bbox_size); break; case scenery_type::door: - f = { scenery::door, *val.atlas, r, false, - Vector2b(offset), Vector2b(bbox_offset), Vector2ub(bbox_size), }; + fm_assert(f.atlas->info().fps > 0 && f.atlas->info().nframes > 0); + f.type = entity_type::scenery; + f.sc_type = scenery_type::door; + f.r = r; + f.frame = std::uint16_t(f.atlas->group(r).frames.size()-1); + f.pass = pass_mode::blocked; + f.interactive = true; + f.closing = false; + f.offset = Vector2b(offset); + f.bbox_offset = Vector2b(bbox_offset); + f.bbox_size = Vector2ub(bbox_size); + break; } } diff --git a/serialize/world-impl.hpp b/serialize/world-impl.hpp index 5a43654e..c8282141 100644 --- a/serialize/world-impl.hpp +++ b/serialize/world-impl.hpp @@ -4,6 +4,8 @@ #pragma once #include "src/tile.hpp" +#include "src/pass-mode.hpp" +#include "src/rotation.hpp" #include <bit> #include <cstddef> #include <cstdint> diff --git a/serialize/world-reader.cpp b/serialize/world-reader.cpp index 3060579f..83a77ffb 100644 --- a/serialize/world-reader.cpp +++ b/serialize/world-reader.cpp @@ -2,6 +2,7 @@ #include "world-impl.hpp" #include "binary-reader.inl" #include "src/world.hpp" +#include "src/scenery.hpp" #include "loader/loader.hpp" #include "loader/scenery.hpp" #include "src/tile-atlas.hpp" @@ -52,10 +53,10 @@ void reader_state::read_atlases(reader_t& s) } template<typename T> -bool read_scenery_flags(binary_reader<T>& s, scenery& sc) +bool read_scenery_flags(binary_reader<T>& s, scenery_proto& sc) { std::uint8_t flags; flags << s; - sc.passability = pass_mode(flags & pass_mask); + sc.pass = pass_mode(flags & pass_mask); sc.active = !!(flags & 1 << 2); sc.closing = !!(flags & 1 << 3); sc.interactive = !!(flags & 1 << 4); @@ -79,20 +80,19 @@ void reader_state::read_sceneries(reader_t& s) std::uint8_t num; num << s; fm_soft_assert(num > 0); auto str = s.read_asciiz_string<atlas_name_max>(); - const auto sc_ = loader.scenery(str); + auto sc = loader.scenery(str); for (auto n = 0_uz; n < num; n++) { atlasid id; id << s; fm_soft_assert(id < sz); fm_soft_assert(!sceneries[id]); - scenery_proto sc = sc_; - bool short_frame = read_scenery_flags(s, sc.frame); + bool short_frame = read_scenery_flags(s, sc); fm_debug_assert(sc.atlas != nullptr); if (short_frame) - sc.frame.frame = s.read<std::uint8_t>(); + sc.frame = s.read<std::uint8_t>(); else - sc.frame.frame << s; - fm_soft_assert(sc.frame.frame < sc.atlas->info().nframes); + sc.frame << s; + fm_soft_assert(sc.frame < sc.atlas->info().nframes); sceneries[id] = sc; } i += num; @@ -165,39 +165,42 @@ void reader_state::read_chunks(reader_t& s) id &= ~scenery_id_flag_mask; auto sc = lookup_scenery(id); (void)sc.atlas->group(r); - sc.frame.r = r; + sc.r = r; if (!exact) { - if (read_scenery_flags(s, sc.frame)) - sc.frame.frame = s.read<std::uint8_t>(); + if (read_scenery_flags(s, sc)) + sc.frame = s.read<std::uint8_t>(); else - sc.frame.frame << s; + sc.frame << s; if (PROTO >= 5) [[likely]] { - sc.frame.offset[0] << s; - sc.frame.offset[1] << s; + sc.offset[0] << s; + sc.offset[1] << s; } if (PROTO >= 6) [[likely]] { - sc.frame.bbox_size[0] << s; - sc.frame.bbox_size[1] << s; + sc.bbox_size[0] << s; + sc.bbox_size[1] << s; } if (PROTO >= 7) [[likely]] { - sc.frame.bbox_offset[0] << s; - sc.frame.bbox_offset[1] << s; + sc.bbox_offset[0] << s; + sc.bbox_offset[1] << s; } - if (sc.frame.active) + if (sc.active) { if (PROTO >= 4) [[likely]] - sc.frame.delta << s; + sc.delta << s; else - sc.frame.delta = (std::uint16_t)Math::clamp(int(s.read<float>() * 65535), 0, 65535); + sc.delta = (std::uint16_t)Math::clamp(int(s.read<float>() * 65535), 0, 65535); } } - t.scenery() = sc; + + auto e = _world->make_entity<scenery>({ coord, local_coords{i} }, sc); + chunk.add_entity_unsorted(e); } } + chunk.sort_entities(); } } diff --git a/serialize/world-writer.cpp b/serialize/world-writer.cpp index 2a58a51b..413a9566 100644 --- a/serialize/world-writer.cpp +++ b/serialize/world-writer.cpp @@ -115,7 +115,6 @@ void writer_state::load_scenery_1(const serialized_scenery& s) scenery_map[ptr] = { { &s, null_atlas } }; else { - fm_assert(s.proto.frame.delta == 0); auto& vec = scenery_map[ptr]; for (const auto& x : vec) if (s.proto.frame == x.s->proto.frame) @@ -140,8 +139,8 @@ scenery_pair writer_state::intern_scenery(scenery_proto s, bool create) interned_scenery *ret = nullptr, *ret2 = nullptr; for (interned_scenery& x : vec) { - fm_debug_assert(s.frame.type == x.s->proto.frame.type); - s.frame.r = x.s->proto.frame.r; + fm_debug_assert(s.type == x.s->proto.type); + s.r = x.s->proto.r; if (x.s->proto.frame == s.frame) { if (x.index != null_atlas) @@ -178,10 +177,10 @@ scenery_pair writer_state::maybe_intern_scenery(const scenery_proto& s, bool cre } template<typename T> -void write_scenery_flags(binary_writer<T>& s, const scenery& proto) +void write_scenery_flags(binary_writer<T>& s, const scenery_proto& proto) { std::uint8_t flags = 0; - flags |= pass_mode_(proto.passability) & pass_mask; + flags |= pass_mode_(proto.pass) & pass_mask; flags |= (1 << 2) * proto.active; flags |= (1 << 3) * proto.closing; flags |= (1 << 4) * proto.interactive; @@ -272,12 +271,11 @@ void writer_state::serialize_scenery() s.write_asciiz_string(sc->name); } s << idx; - const auto& fr = sc->proto.frame; - write_scenery_flags(s, sc->proto.frame); - if (sc->proto.frame.frame <= 0xff) - s << (std::uint8_t)fr.frame; + write_scenery_flags(s, sc->proto); + if (sc->proto.frame <= 0xff) + s << (std::uint8_t)sc->proto.frame; else - s << fr.frame; + s << sc->proto.frame; } scenery_buf.resize(s.bytes_written()); @@ -296,27 +294,29 @@ void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) { const tile_proto x = c[i]; const auto ground = x.ground(), wall_north = x.wall_north(), wall_west = x.wall_west(); - const auto scenery = x.scenery_frame; + //const auto scenery = x.scenery_frame; fm_debug_assert(s.bytes_written() + tile_size <= chunkbuf_size); auto img_g = maybe_intern_atlas(ground); auto img_n = maybe_intern_atlas(wall_north); auto img_w = maybe_intern_atlas(wall_west); - auto [sc, img_s, sc_exact] = maybe_intern_scenery(x.scenery(), true); + //auto [sc, img_s, sc_exact] = maybe_intern_scenery(x.scenery(), true); +#if 0 if (sc_exact && sc) { sc_exact = scenery.offset == sc->proto.frame.offset && scenery.bbox_size == sc->proto.frame.bbox_size && scenery.bbox_offset == sc->proto.frame.bbox_offset; } +#endif tilemeta flags = {}; flags |= meta_ground * (img_g != null_atlas); flags |= meta_wall_n * (img_n != null_atlas); flags |= meta_wall_w * (img_w != null_atlas); - flags |= meta_scenery * (img_s != null_atlas); + //flags |= meta_scenery * (img_s != null_atlas); using uchar = std::uint8_t; @@ -353,6 +353,7 @@ void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) write(img_n, wall_north.variant); if (img_w != null_atlas) write(img_w, wall_west.variant); +#if 0 if (img_s != null_atlas) { atlasid id = img_s; @@ -381,6 +382,7 @@ void writer_state::serialize_chunk(const chunk& c, chunk_coords coord) s << scenery.delta; } } +#endif } const auto nbytes = s.bytes_written(); @@ -403,9 +405,11 @@ ArrayView<const char> writer_state::serialize_world() { load_scenery(); +#if 0 for (const auto& [_, c] : _world->chunks()) for (auto [x, _k, _pt] : c) maybe_intern_scenery(x.scenery(), false); +#endif for (const auto& [pos, c] : _world->chunks()) { 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); }; diff --git a/test/app.hpp b/test/app.hpp index 894401c8..2152e2e5 100644 --- a/test/app.hpp +++ b/test/app.hpp @@ -1,4 +1,5 @@ #pragma once +#include "src/world.hpp" #include <Magnum/Magnum.h> #ifdef __APPLE__ @@ -22,13 +23,15 @@ struct test_app final : private FM_APPLICATION explicit test_app(const Arguments& arguments); ~test_app(); int exec() override; - static chunk make_test_chunk(); + chunk& make_test_chunk(chunk_coords ch); static void test_json(); static void test_tile_iter(); static void test_const_math(); - static void test_serializer(); + void test_serializer(); static void test_entity(); static void test_loader(); static void test_bitmask(); + + world w; }; } // namespace floormat diff --git a/test/entity.cpp b/test/entity.cpp index a7810863..c1489723 100644 --- a/test/entity.cpp +++ b/test/entity.cpp @@ -96,6 +96,7 @@ constexpr bool test_visitor() } void test_fun2() { + using entity = Entity<TestAccessors>; static constexpr auto read_fn = [](const TestAccessors& x) constexpr { return x.bar(); }; static constexpr auto write_fn = [](TestAccessors& x, int value) constexpr { x.set_bar(value); }; constexpr auto read_bar = fu2::function_view<int(const TestAccessors&) const>{read_fn}; @@ -156,6 +157,7 @@ void test_type_name() [[maybe_unused]] constexpr void test_null_writer() { + using entity = Entity<TestAccessors>; constexpr auto foo = entity::type<int>::field{"foo"_s, &TestAccessors::foo, nullptr}; static_assert(foo.writer == nullptr); static_assert(!foo.can_write); @@ -164,6 +166,7 @@ void test_type_name() void test_predicate() { + using entity = Entity<TestAccessors>; constexpr TestAccessors x{0, 0, 0}; constexpr auto foo = entity::type<int>::field{"foo"_s, &TestAccessors::foo, &TestAccessors::foo, [](const TestAccessors&) { return field_status::hidden; }}; @@ -199,6 +202,7 @@ constexpr bool test_names() constexpr void test_constraints() { + using entity = Entity<TestAccessors>; constexpr auto x = TestAccessors{}; constexpr auto foo = entity::type<int>::field { "foo"_s, &TestAccessors::foo, &TestAccessors::foo, @@ -226,6 +230,7 @@ constexpr void test_constraints() void test_erased_constraints() { + using entity = Entity<TestAccessors>; static constexpr auto foo = entity::type<int>::field{ "foo"_s, &TestAccessors::foo, &TestAccessors::foo, constantly(constraints::max_length{42}), diff --git a/test/main.cpp b/test/main.cpp index 0d7ddf84..05ecd7fe 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -24,10 +24,10 @@ int test_app::exec() test_json(); test_tile_iter(); test_const_math(); - test_serializer(); test_entity(); test_loader(); test_bitmask(); + test_serializer(); return 0; } diff --git a/test/serializer.cpp b/test/serializer.cpp index 71b1a3f1..c74f4ddf 100644 --- a/test/serializer.cpp +++ b/test/serializer.cpp @@ -9,40 +9,50 @@ namespace floormat { namespace Path = Corrade::Utility::Path; -chunk test_app::make_test_chunk() +chunk& test_app::make_test_chunk(chunk_coords ch) { + chunk& c = w[ch]; + c.mark_modified(); auto metal1 = loader.tile_atlas("metal1", {2, 2}, pass_mode::pass), metal2 = loader.tile_atlas("metal2", {2, 2}, pass_mode::blocked), tiles = loader.tile_atlas("tiles", {8, 5}, pass_mode::pass); constexpr auto N = TILE_MAX_DIM; - chunk c; for (auto [x, k, pt] : c) x.ground() = { tiles, variant_t(k % tiles->num_tiles()) }; auto door = loader.scenery("door1"), table = loader.scenery("table1"), control_panel = loader.scenery("control panel (wall) 1"); - control_panel.frame.r = rotation::W; + control_panel.r = rotation::W; constexpr auto K = N/2; c[{K, K }].wall_north() = { metal1, 0 }; c[{K, K }].wall_west() = { metal2, 0 }; c[{K, K+1}].wall_north() = { metal1, 0 }; c[{K+1, K }].wall_west() = { metal2, 0 }; - c[{K+3, K+1}].scenery() = door; - c[{ 3, 4 }].scenery() = table; - c[{K, K+1}].scenery() = control_panel; - c[{K, K+1}].scenery().frame.bbox_size = {99, 88}; - c.mark_modified(); + w.make_entity<scenery>({ch, {K+3, K+1}}, door); + w.make_entity<scenery>({ch, {3, 4}}, table); + w.make_entity<scenery>({ch, {K, K+1}}, control_panel); return c; } static bool chunks_equal(const chunk& a, const chunk& b) { + if (a.entities().size() != b.entities().size()) + return false; + for (auto i = 0_uz; i < TILE_COUNT; i++) { const auto &a1 = a[i], &b1 = b[i]; if (a1 != b1) return false; } + + for (auto i = 0_uz; i < a.entities().size(); i++) + { + auto &ae = *a.entities()[i], &be = *b.entities()[i]; + if (entity_proto(ae) != entity_proto(be)) + return false; + } + return true; } @@ -51,13 +61,12 @@ void test_app::test_serializer() constexpr auto filename = "../test/test-serializer1.dat"; if (Path::exists(filename)) Path::remove(filename); - world w; const chunk_coords coord{1, 1}; - w[coord] = make_test_chunk(); + auto& c = make_test_chunk(coord); w.serialize(filename); auto w2 = world::deserialize(filename); - auto &c1 = w[coord], &c2 = w2[coord]; - fm_assert(chunks_equal(c1, c2)); + auto& c2 = w2[coord]; + fm_assert(chunks_equal(c, c2)); } } // namespace floormat diff --git a/userconfig-sthalik@Windows-Clang.cmake b/userconfig-sthalik@Windows-Clang.cmake index 7c75c204..2413704a 100644 --- a/userconfig-sthalik@Windows-Clang.cmake +++ b/userconfig-sthalik@Windows-Clang.cmake @@ -89,6 +89,7 @@ function(fm-userconfig-src) -Wno-exit-time-destructors -Wno-implicit-int-float-conversion -Wno-shadow-field-in-constructor + -Wno-shadow-field -Wno-shadow -Wno-ctad-maybe-unsupported -Wno-documentation-unknown-command |