From dd4ee56968b3ab10763363dfef76b06c8548a66f Mon Sep 17 00:00:00 2001 From: Stanislaw Halik Date: Thu, 8 Feb 2024 08:36:19 +0100 Subject: work on the atlas loaders --- doc/saves/quicksave - Copy (50).dat | Bin 0 -> 7450 bytes loader/anim-info.hpp | 15 ++++ loader/anim.cpp | 126 ++++++++++++++++++++++++++++++++++ loader/impl.cpp | 1 + loader/impl.hpp | 17 ++--- loader/json.cpp | 13 ++++ loader/loader.cpp | 7 +- loader/loader.hpp | 24 ++++--- test/dijkstra.cpp | 15 ++-- test/json.cpp | 7 +- test/loader.cpp | 20 +++--- test/path-search.cpp | 11 +-- test/raycast.cpp | 6 ++ test/save/quicksave - Copy (0034).dat | Bin 0 -> 3014 bytes test/save/quicksave - Copy (0035).dat | Bin 0 -> 7450 bytes 15 files changed, 212 insertions(+), 50 deletions(-) create mode 100644 doc/saves/quicksave - Copy (50).dat create mode 100644 loader/anim-info.hpp create mode 100644 loader/anim.cpp create mode 100644 test/save/quicksave - Copy (0034).dat create mode 100644 test/save/quicksave - Copy (0035).dat diff --git a/doc/saves/quicksave - Copy (50).dat b/doc/saves/quicksave - Copy (50).dat new file mode 100644 index 00000000..e548dc75 Binary files /dev/null and b/doc/saves/quicksave - Copy (50).dat differ diff --git a/loader/anim-info.hpp b/loader/anim-info.hpp new file mode 100644 index 00000000..97ba5954 --- /dev/null +++ b/loader/anim-info.hpp @@ -0,0 +1,15 @@ +#pragma once +#include +#include + +namespace floormat { + +class anim_atlas; + +struct anim_info +{ + String name; + std::shared_ptr atlas; +}; + +} // namespace floormat diff --git a/loader/anim.cpp b/loader/anim.cpp new file mode 100644 index 00000000..25b5e292 --- /dev/null +++ b/loader/anim.cpp @@ -0,0 +1,126 @@ +#include "impl.hpp" +#include "loader/anim-info.hpp" +#include "compat/exception.hpp" +#include "src/anim-atlas.hpp" +#include +#include +#include +#include + +namespace floormat { + +std::shared_ptr +loader_::get_anim_atlas(StringView path) noexcept(false) +{ + auto anim_info = deserialize_anim_def(path + ".json"); + + for (anim_group& group : anim_info.groups) + { + if (!group.mirror_from.isEmpty()) + { + auto it = std::find_if(anim_info.groups.cbegin(), anim_info.groups.cend(), + [&](const anim_group& x) { return x.name == group.mirror_from; }); + if (it == anim_info.groups.cend()) + fm_throw("can't find group '{}' to mirror from '{}'"_cf, group.mirror_from, group.name); + group.frames = array(arrayView(it->frames)); + for (anim_frame& f : group.frames) + f.ground = Vector2i((Int)f.size[0] - f.ground[0], f.ground[1]); + } + } + + auto tex = texture(""_s, path); + + fm_soft_assert(!anim_info.object_name.isEmpty()); + fm_soft_assert(anim_info.pixel_size.product() > 0); + fm_soft_assert(!anim_info.groups.isEmpty()); + fm_soft_assert(anim_info.nframes > 0); + fm_soft_assert(anim_info.nframes == 1 || anim_info.fps > 0); + const auto size = tex.pixels().size(); + const auto width = size[1], height = size[0]; + fm_soft_assert(anim_info.pixel_size[0] == width && anim_info.pixel_size[1] == height); + + auto atlas = std::make_shared(path, tex, std::move(anim_info)); + return atlas; +} + +} // namespace floormat + +namespace floormat::loader_detail { + +ArrayView loader_impl::anim_atlas_list() +{ + if (anim_atlases.empty()) + get_anim_atlas_list(); + fm_assert(!anim_atlases.empty()); + return { anim_atlases.data(), anim_atlases.size() }; +} + +std::shared_ptr loader_impl::anim_atlas(StringView name, StringView dir, loader_policy policy) noexcept(false) +{ + if (name == INVALID) return make_invalid_anim_atlas().atlas; // todo! hack + fm_soft_assert(check_atlas_name(name)); + fm_soft_assert(!dir || dir[dir.size()-1] == '/'); + char buf[FILENAME_MAX]; + auto path = make_atlas_path(buf, dir, name); + + if (auto it = anim_atlas_map.find(path); it != anim_atlas_map.end()) + return it->second; + else + { + auto atlas = get_anim_atlas(path); + return anim_atlas_map[atlas->name()] = atlas; + } +} + +void loader_impl::get_anim_atlas_list() +{ + anim_atlases.clear(); + using f = Path::ListFlag; + constexpr auto flags = f::SkipDirectories | f::SkipDotAndDotDot | f::SkipSpecial | f::SortAscending; + if (const auto list = Path::list(ANIM_PATH, flags); list) + { + anim_atlases.reserve(list->size()); + constexpr auto suffix = ".json"_s; + for (StringView str : *list) + if (str.hasSuffix(suffix)) + anim_atlases.emplace_back(str.exceptSuffix(suffix.size())); + } + anim_atlases.shrink_to_fit(); + fm_assert(!anim_atlases.empty()); +} + +const anim_info& loader_impl::make_invalid_anim_atlas() +{ + if (invalid_anim_atlas) [[likely]] + return *invalid_anim_atlas; + + constexpr auto size = Vector2ui{16}; + + auto frame = anim_frame { + .ground = Vector2i(size/2), + .offset = {}, + .size = size, + }; + auto groups = Array{ValueInit, 1}; + groups[0] = anim_group { + .name = "n"_s, + .frames = array({ frame }), + }; + auto def = anim_def { + .object_name = INVALID, + .anim_name = INVALID, + .groups = Utility::move(groups), + .pixel_size = size, + .scale = anim_scale::fixed{size.x(), true}, + .nframes = 1, + }; + auto atlas = std::make_shared(INVALID, make_error_texture(size), std::move(def)); + auto info = anim_info { + .name = INVALID, + .atlas = atlas, + }; + invalid_anim_atlas = Pointer{ InPlace, std::move(info) }; + return *invalid_anim_atlas; +} + +} // namespace floormat::loader_detail diff --git a/loader/impl.cpp b/loader/impl.cpp index f2f3c669..7da85f3f 100644 --- a/loader/impl.cpp +++ b/loader/impl.cpp @@ -2,6 +2,7 @@ #include "compat/assert.hpp" #include "loader/scenery.hpp" #include "loader/wall-info.hpp" +#include "loader/anim-info.hpp" #include "src/ground-atlas.hpp" #include #include diff --git a/loader/impl.hpp b/loader/impl.hpp index b376503b..5c8a542a 100644 --- a/loader/impl.hpp +++ b/loader/impl.hpp @@ -10,12 +10,6 @@ #include #include -namespace floormat { -struct anim_def; -struct wall_info; -struct ground_info; -} - namespace floormat::loader_detail { struct loader_impl final : loader_ @@ -53,9 +47,9 @@ struct loader_impl final : loader_ std::shared_ptr wall_atlas(StringView name, loader_policy policy) override; ArrayView wall_atlas_list() override; void get_wall_atlas_list(); - const wall_info& make_invalid_wall_atlas(); + const wall_info& make_invalid_wall_atlas() override; - // >-----> tile >-----> + // >-----> ground >-----> tsl::robin_map ground_atlas_map; std::vector ground_atlas_array; std::vector missing_ground_atlases; @@ -63,15 +57,16 @@ struct loader_impl final : loader_ std::shared_ptr ground_atlas(StringView filename, loader_policy policy) noexcept(false) override; ArrayView ground_atlas_list() noexcept(false) override; void get_ground_atlas_list(); - const ground_info& make_invalid_ground_atlas(); + const ground_info& make_invalid_ground_atlas() override; // >-----> anim >-----> tsl::robin_map> anim_atlas_map; std::vector anim_atlases; + Pointer invalid_anim_atlas; ArrayView anim_atlas_list() override; - std::shared_ptr anim_atlas(StringView name, StringView dir) noexcept(false) override; - static anim_def deserialize_anim(StringView filename); + std::shared_ptr anim_atlas(StringView name, StringView dir, loader_policy policy) noexcept(false) override; void get_anim_atlas_list(); + const anim_info& make_invalid_anim_atlas() override; // >-----> scenery >-----> std::vector sceneries_array; diff --git a/loader/json.cpp b/loader/json.cpp index 756f8151..5c7eecb5 100644 --- a/loader/json.cpp +++ b/loader/json.cpp @@ -6,6 +6,7 @@ #include "serialize/anim.hpp" #include "serialize/scenery.hpp" #include "loader/scenery.hpp" +#include "loader/anim-info.hpp" #include #include @@ -24,14 +25,26 @@ void loader_impl::get_scenery_list() { sceneries_array.clear(); sceneries_array = json_helper::from_json>(Path::join(SCENERY_PATH, "scenery.json")); + + if constexpr(false) + { + auto proto = scenery_proto{}; + proto.atlas = make_invalid_anim_atlas().atlas; + proto.bbox_size = Vector2ub{20}; + proto.subtype = generic_scenery_proto{false, true}; + sceneries_array.push_back({ .name = INVALID, .proto = proto }); + } + sceneries_map.clear(); sceneries_map.reserve(sceneries_array.size() * 2); + for (const serialized_scenery& s : sceneries_array) { if (sceneries_map.contains(s.name)) fm_abort("duplicate scenery name '%s'", s.name.data()); sceneries_map[s.name] = &s; } + fm_assert(!sceneries_map.empty()); } diff --git a/loader/loader.cpp b/loader/loader.cpp index af7b053f..531d731a 100644 --- a/loader/loader.cpp +++ b/loader/loader.cpp @@ -1,6 +1,7 @@ #include "impl.hpp" #include "ground-info.hpp" #include "wall-info.hpp" +#include "anim-info.hpp" #include "scenery.hpp" namespace floormat::loader_detail { @@ -19,8 +20,10 @@ void loader_impl::destroy() anim_atlas_map.clear(); anim_atlases.clear(); + invalid_anim_atlas = nullptr; sceneries_map.clear(); sceneries_array.clear(); + vobj_atlas_map.clear(); vobjs.clear(); } @@ -45,8 +48,6 @@ loader_::~loader_() noexcept = default; StringView loader_::strip_prefix(StringView name) { - if (name.hasPrefix(IMAGE_PATH_)) - return name.exceptPrefix(IMAGE_PATH_.size()); if (name.hasPrefix(ANIM_PATH)) return name.exceptPrefix(ANIM_PATH.size()); if (name.hasPrefix(SCENERY_PATH)) @@ -60,8 +61,6 @@ StringView loader_::strip_prefix(StringView name) return name; } -const StringView loader_::INVALID = ""_s; // todo use it -const StringView loader_::IMAGE_PATH_ = "images/"_s; const StringView loader_::ANIM_PATH = "anim/"_s; const StringView loader_::SCENERY_PATH = "scenery/"_s; const StringView loader_::TEMP_PATH = "../../../"_s; diff --git a/loader/loader.hpp b/loader/loader.hpp index a9b1d262..96ab57ed 100644 --- a/loader/loader.hpp +++ b/loader/loader.hpp @@ -2,7 +2,7 @@ #include "compat/defs.hpp" #include "src/pass-mode.hpp" #include "loader/policy.hpp" -#include +#include // NOLINT(*-deprecated-headers) #include #include @@ -17,12 +17,14 @@ namespace floormat { struct anim_def; class anim_atlas; +struct anim_info; struct scenery_proto; struct vobj_info; class ground_atlas; struct ground_info; struct wall_info; class wall_atlas; +struct scenery_proto; struct vobj_info final { @@ -36,12 +38,12 @@ struct loader_ virtual Trade::ImageData2D texture(StringView prefix, StringView filename) noexcept(false) = 0; virtual std::shared_ptr ground_atlas(StringView filename, loader_policy policy = loader_policy::DEFAULT) noexcept(false) = 0; virtual ArrayView anim_atlas_list() = 0; - virtual std::shared_ptr anim_atlas(StringView name, StringView dir = ANIM_PATH) noexcept(false) = 0; + virtual std::shared_ptr anim_atlas(StringView name, StringView dir = ANIM_PATH, loader_policy policy = loader_policy::DEFAULT) noexcept(false) = 0; virtual std::shared_ptr wall_atlas(StringView name, loader_policy policy = loader_policy::DEFAULT) noexcept(false) = 0; virtual ArrayView wall_atlas_list() = 0; virtual void destroy() = 0; static loader_& default_loader() noexcept; - virtual ArrayView ground_atlas_list() noexcept(false) = 0; + virtual ArrayView ground_atlas_list() noexcept(false) = 0; // todo maybe try returning virtual ArrayView sceneries() = 0; virtual const scenery_proto& scenery(StringView name) noexcept(false) = 0; virtual StringView startup_directory() noexcept = 0; @@ -51,16 +53,22 @@ struct loader_ static StringView make_atlas_path(char(&buf)[FILENAME_MAX], StringView dir, StringView name); [[nodiscard]] static bool check_atlas_name(StringView name) noexcept; - /** \deprecated{internal use only}*/ [[nodiscard]] std::shared_ptr get_ground_atlas(StringView name, Vector2ub size, pass_mode pass) noexcept(false); - /** \deprecated{internal use only}*/ [[nodiscard]] std::shared_ptr get_wall_atlas(StringView name) noexcept(false); - /** \deprecated{internal use only}*/ [[nodiscard]] std::shared_ptr get_anim_atlas(StringView path) noexcept(false); + virtual const wall_info& make_invalid_wall_atlas() = 0; + virtual const ground_info& make_invalid_ground_atlas() = 0; + virtual const anim_info& make_invalid_anim_atlas() = 0; + + /** \deprecated{internal use only}*/ [[nodiscard]] + std::shared_ptr get_ground_atlas(StringView name, Vector2ub size, pass_mode pass) noexcept(false); + /** \deprecated{internal use only}*/ [[nodiscard]] + std::shared_ptr get_wall_atlas(StringView name) noexcept(false); + /** \deprecated{internal use only}*/ [[nodiscard]] + std::shared_ptr get_anim_atlas(StringView path) noexcept(false); virtual ~loader_() noexcept; fm_DECLARE_DELETED_COPY_ASSIGNMENT(loader_); fm_DECLARE_DELETED_MOVE_ASSIGNMENT(loader_); - static const StringView INVALID; - static const StringView IMAGE_PATH_; + static constexpr StringView INVALID = ""_s; static const StringView ANIM_PATH; static const StringView SCENERY_PATH; static const StringView TEMP_PATH; diff --git a/test/dijkstra.cpp b/test/dijkstra.cpp index d5412559..8a702a52 100644 --- a/test/dijkstra.cpp +++ b/test/dijkstra.cpp @@ -1,6 +1,7 @@ #include "app.hpp" #include "src/path-search.hpp" #include "loader/loader.hpp" +#include "loader/wall-info.hpp" #include namespace floormat { @@ -19,7 +20,7 @@ void test_app::test_dijkstra() constexpr auto wpos = global_coords{wch, wt}; auto& ch = w[wch]; - auto metal2 = wall_image_proto{loader.wall_atlas("empty", loader_policy::warn), 0}; + auto wall = wall_image_proto{loader.make_invalid_wall_atlas().atlas, 0}; for (int16_t j = wcy - 1; j <= wcy + 1; j++) for (int16_t i = wcx - 1; i <= wcx + 1; i++) @@ -27,15 +28,15 @@ void test_app::test_dijkstra() auto &c = w[chunk_coords_{i, j, 0}]; for (int k : { 3, 4, 5, 6, 11, 12, 13, 14, 15, }) { - c[{ k, k }].wall_north() = metal2; - c[{ k, k }].wall_west() = metal2; + c[{ k, k }].wall_north() = wall; + c[{ k, k }].wall_west() = wall; } } - ch[{ wtx, wty }].wall_west() = metal2; - ch[{ wtx, wty }].wall_north() = metal2; - ch[{ wtx+1, wty }].wall_west() = metal2; - ch[{ wtx, wty +1}].wall_north() = metal2; + ch[{ wtx, wty }].wall_west() = wall; + ch[{ wtx, wty }].wall_north() = wall; + ch[{ wtx+1, wty }].wall_west() = wall; + ch[{ wtx, wty +1}].wall_north() = wall; for (int16_t j = wcy - 1; j <= wcy + 1; j++) for (int16_t i = wcx - 1; i <= wcx + 1; i++) diff --git a/test/json.cpp b/test/json.cpp index d9be8ff3..70d8a80c 100644 --- a/test/json.cpp +++ b/test/json.cpp @@ -1,6 +1,7 @@ #include "app.hpp" #include "serialize/tile.hpp" #include "serialize/ground-atlas.hpp" +#include "serialize/wall-atlas.hpp" #include "serialize/magnum-vector.hpp" #include "serialize/json-helper.hpp" #include "compat/assert.hpp" @@ -10,6 +11,8 @@ #include "src/chunk.hpp" #include "src/world.hpp" #include "loader/loader.hpp" +#include "loader/wall-info.hpp" +#include #include #include @@ -19,10 +22,6 @@ void test_app::test_json() // NOLINT(readability-convert-member-functions-to-sta { fm_assert(Path::exists(Path::join(loader.TEMP_PATH, "CMakeCache.txt"))); const auto output_dir = Path::join(loader.TEMP_PATH, "test/."_s); - { - auto atlas = loader.ground_atlas("metal1"); - json_helper::to_json(atlas, Path::join(output_dir, "atlas.json")); - } { Magnum::Math::Vector<2, int> v2i_1{1, 2}; Vector2i v2i_2{2, 3}; diff --git a/test/loader.cpp b/test/loader.cpp index 2716d238..3b04459a 100644 --- a/test/loader.cpp +++ b/test/loader.cpp @@ -1,8 +1,8 @@ #include "app.hpp" #include "compat/assert.hpp" #include "loader/loader.hpp" -#include "src/ground-atlas.hpp" #include "loader/wall-info.hpp" +#include "src/ground-atlas.hpp" namespace floormat { @@ -37,6 +37,14 @@ constexpr const char* anim_atlases[] = { void test_app::test_loader() { + fm_assert(loader.make_invalid_ground_atlas().atlas); + fm_assert(&loader.make_invalid_ground_atlas().atlas == &loader.make_invalid_ground_atlas().atlas); + fm_assert(loader.make_invalid_ground_atlas().name == loader.INVALID); + + fm_assert(loader.make_invalid_wall_atlas().atlas); + fm_assert(&loader.make_invalid_wall_atlas().atlas == &loader.make_invalid_wall_atlas().atlas); + fm_assert(loader.make_invalid_wall_atlas().name == loader.INVALID); + for (const auto& str : anim_atlases) (void)loader.get_anim_atlas(str); for (const auto& x : ground_atlases) @@ -51,16 +59,6 @@ void test_app::test_loader() loader.sceneries(); for (StringView name : loader.anim_atlas_list()) loader.anim_atlas(name); - - { auto walls = loader.wall_atlas_list(); - fm_assert(!walls.isEmpty()); - fm_assert(loader.wall_atlas("test1"_s)); - fm_assert(loader.wall_atlas(loader.INVALID, loader_policy::ignore)); - fm_assert(loader.wall_atlas("test1"_s) == loader.wall_atlas("test1"_s)); - fm_assert(loader.wall_atlas("test1"_s) != loader.wall_atlas(loader.INVALID, loader_policy::ignore)); - } - for (const auto& info : loader.wall_atlas_list()) - fm_assert(loader.wall_atlas(info.name)); } } // namespace floormat diff --git a/test/path-search.cpp b/test/path-search.cpp index 1345f091..5a1dc825 100644 --- a/test/path-search.cpp +++ b/test/path-search.cpp @@ -2,6 +2,7 @@ #include "compat/assert.hpp" #include "compat/function2.hpp" #include "loader/loader.hpp" +#include "loader/wall-info.hpp" #include "src/world.hpp" #include "src/scenery.hpp" #include "src/path-search.hpp" @@ -178,7 +179,7 @@ void test_bbox() return neighbor_tiles(w, { ch, pos }, {}, (object_id)-1, path_search::never_continue()); }; - const auto metal2 = loader.wall_atlas("empty", loader_policy::warn); + const auto wall = loader.make_invalid_wall_atlas().atlas; const auto table = loader.scenery("table1"); { @@ -190,7 +191,7 @@ void test_bbox() auto w = world(); [[maybe_unused]] auto& c12 = w[coord2]; [[maybe_unused]] auto& c11 = w[coord1]; - c12[{0, 0}].wall_north() = {metal2, 0}; + c12[{0, 0}].wall_north() = { wall, 0}; fm_assert( !is_passable_1(c12, bbox({}, N)) ); fm_assert( is_passable_1(c12, bbox({}, E)) ); @@ -211,8 +212,8 @@ void test_bbox() auto w = world(); auto& c = w[ch]; - c[{8, 7}].wall_north() = {metal2,0}; - c[{8, 9}].wall_north() = {metal2,0}; + c[{8, 7}].wall_north() = { wall,0}; + c[{8, 9}].wall_north() = { wall,0}; fm_assert( is_passable_1(c, bbox({8, 6}, N)) ); fm_assert( !is_passable_1(c, bbox({8, 6}, S)) ); fm_assert( !is_passable_1(c, bbox({8, 7}, N)) ); @@ -224,7 +225,7 @@ void test_bbox() fm_assert(neighbors(w, ch, {8, 8}).size == 3); - c[{8, 8}].wall_north() = {metal2,0}; + c[{8, 8}].wall_north() = { wall,0}; c.mark_passability_modified(); fm_assert( is_passable_1(c, bbox({8, 8}, C)) ); fm_assert( !is_passable_1(c, bbox({8, 7}, S)) ); diff --git a/test/raycast.cpp b/test/raycast.cpp index 23b03926..cb76333f 100644 --- a/test/raycast.cpp +++ b/test/raycast.cpp @@ -3,6 +3,7 @@ #include "src/raycast-diag.hpp" #include "src/world.hpp" #include "loader/loader.hpp" +#include "loader/wall-info.hpp" #include namespace floormat { @@ -12,8 +13,13 @@ namespace { world make_world() { constexpr auto var = (variant_t)-1; +#if 1 + auto wall1_ = loader.make_invalid_wall_atlas().atlas; + auto wall2_ = loader.make_invalid_wall_atlas().atlas; +#else auto wall1_ = loader.wall_atlas("test1"_s); auto wall2_ = loader.wall_atlas("concrete1"_s); +#endif auto wall1 = wall_image_proto{wall1_, var}; auto wall2 = wall_image_proto{wall2_, var}; diff --git a/test/save/quicksave - Copy (0034).dat b/test/save/quicksave - Copy (0034).dat new file mode 100644 index 00000000..20d38b8a Binary files /dev/null and b/test/save/quicksave - Copy (0034).dat differ diff --git a/test/save/quicksave - Copy (0035).dat b/test/save/quicksave - Copy (0035).dat new file mode 100644 index 00000000..e548dc75 Binary files /dev/null and b/test/save/quicksave - Copy (0035).dat differ -- cgit v1.2.3