summaryrefslogtreecommitdiffhomepage
path: root/loader/atlas.cpp
blob: 2afd4d2eade68d40d04310b255ad117d8d030f37 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
#include "impl.hpp"
#include "compat/assert.hpp"
#include "compat/exception.hpp"
#include "src/emplacer.hpp"
#include "src/tile-atlas.hpp"
#include "src/anim-atlas.hpp"
#include <cstdio>
#include <algorithm>
#include <Corrade/Containers/ArrayViewStl.h>
#include <Corrade/Containers/Pair.h>
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/Containers/StringStlView.h>
#include <Corrade/Utility/Path.h>
#include <Magnum/Trade/ImageData.h>

namespace floormat {

StringView loader_::make_atlas_path(char(&buf)[FILENAME_MAX], StringView dir, StringView name)
{
    fm_soft_assert(!dir || dir[dir.size()-1] == '/');
    auto name_noext = Path::splitExtension(name).first();
    const auto dirsiz = dir.size(), namesiz = name_noext.size();
    fm_soft_assert(dirsiz + namesiz + 1 < FILENAME_MAX);
    std::memcpy(buf, dir.data(), dirsiz);
    std::memcpy(&buf[dirsiz], name_noext.data(), namesiz);
    buf[dirsiz + namesiz] = '\0';
    return StringView{buf};
}

bool loader_::check_atlas_name(StringView str) noexcept
{
    if (!str || str[0] == '.' || str[0] == '/')
        return false;
    if (str.findAny("\\\"'\n\r\t\a\033\0|$!%{}#^*?<>&;:^"_s) || str.find("/."_s))
        return false;

    return true;
}

} // namespace floormat

namespace floormat::loader_detail {

std::shared_ptr<tile_atlas> loader_impl::tile_atlas(StringView name, Vector2ub size, Optional<pass_mode> pass) noexcept(false)
{
    if (auto it = tile_atlas_map.find(name); it != tile_atlas_map.end())
    {
        if (pass)
            fm_assert(it->second->pass_mode() == pass);
        return it->second;
    }

    fm_soft_assert(check_atlas_name(name));

    char buf[FILENAME_MAX];
    auto path = make_atlas_path(buf, IMAGE_PATH, name);

    auto atlas = std::make_shared<struct tile_atlas>(path, name, texture(""_s, path), size, pass);
    tile_atlas_map[atlas->name()] = atlas;
    return atlas;
}

std::shared_ptr<struct tile_atlas> loader_impl::tile_atlas(StringView filename) noexcept(false)
{
    fm_assert(!tile_atlas_map.empty());
    auto it = tile_atlas_map.find(filename);
    if (it == tile_atlas_map.end())
        fm_throw("no such tile atlas '{}'"_cf, filename);
    return it->second;
}

ArrayView<const String> loader_impl::anim_atlas_list()
{
    if (anim_atlases.empty())
        get_anim_atlas_list();
    return anim_atlases;
}

std::shared_ptr<anim_atlas> loader_impl::anim_atlas(StringView name, StringView dir) noexcept(false)
{
    fm_soft_assert(dir && dir[dir.size()-1] == '/');
    char path_buf[FILENAME_MAX];
    name = Path::splitExtension(name).first();
    const auto dirsiz = dir.size(), namesiz = name.size();
    fm_soft_assert(dirsiz + namesiz + 1 < FILENAME_MAX);
    std::memcpy(path_buf, dir.data(), dirsiz);
    std::memcpy(&path_buf[dirsiz], name.data(), namesiz);
    path_buf[dirsiz + namesiz] = '\0';
    const StringView path = path_buf;

    if (auto it = anim_atlas_map.find(path); it != anim_atlas_map.end())
        return it->second;
    else
    {
        fm_soft_assert(check_atlas_name(name));
        auto anim_info = deserialize_anim(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 = 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.empty());
        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<struct anim_atlas>(path, tex, std::move(anim_info));
        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()*2);
        for (StringView str : *list)
            if (str.hasSuffix(".json"))
                anim_atlases.emplace_back(str.exceptSuffix(std::size(".json")-1));
    }
}

} // namespace floormat::loader_detail