Porytiles
Loading...
Searching...
No Matches
anim_code_generator.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <format>
5#include <map>
6#include <ranges>
7#include <sstream>
8
11
12namespace {
13
14using namespace porytiles;
15
16[[nodiscard]] std::string generate_incbin_statements(
17 const std::string &tileset_name,
18 const std::filesystem::path &tileset_path_from_project_root,
19 const DynamicCasedName &anim_name,
20 const AnimParams &params)
21{
22 std::ostringstream out;
23
24 const std::string pascal_anim_name = anim_name.to_pascal_case();
25
26 // Generate INCBIN for each unique frame in frame_names (position in vector = FrameN index)
27 for (const auto &frame_name : params.frame_names()) {
28 const std::string frame_file = (tileset_path_from_project_root / "porytiles_bin" / "anim" /
29 anim_name.to_snake_case() / std::format("{}.4bpp", frame_name.to_snake_case()))
30 .string();
31
32 const auto statement = std::format(
33 "const u16 gTilesetAnims_{}{}_{}_Frame{}[] = INCBIN_U16(\"{}\");\n",
35 tileset_name,
36 pascal_anim_name,
37 frame_name.to_pascal_case(),
38 frame_file);
39
40 out << statement;
41 }
42
43 return out.str();
44}
45
46[[nodiscard]] std::string
47generate_frame_array(const std::string &tileset_name, const DynamicCasedName &anim_name, const AnimParams &params)
48{
49 std::ostringstream out;
50
51 const std::string pascal_anim_name = anim_name.to_pascal_case();
52 const std::string array_name =
53 std::format("gTilesetAnims_{}{}_{}", anim::porytiles_managed_prefix, tileset_name, pascal_anim_name);
54
55 out << std::format("const u16 *const {}[] = {{\n", array_name);
56
57 // Generate pointer array following frame_order
58 for (std::size_t i = 0; i < params.frame_order().size(); ++i) {
59 out << std::format(
60 " gTilesetAnims_{}{}_{}_Frame{}",
62 tileset_name,
63 pascal_anim_name,
64 params.frame_order()[i].to_pascal_case());
65 if (i < params.frame_order().size() - 1) {
66 out << ",";
67 }
68 out << "\n";
69 }
70
71 out << "};\n";
72
73 return out.str();
74}
75
76[[nodiscard]] std::string generate_queue_function(
77 const std::string &tileset_name, const DynamicCasedName &anim_name, const AnimParams &params, bool is_primary)
78{
79 std::ostringstream out;
80
81 const std::string pascal_anim_name = anim_name.to_pascal_case();
82 const std::string array_name =
83 std::format("gTilesetAnims_{}{}_{}", anim::porytiles_managed_prefix, tileset_name, pascal_anim_name);
84 const std::string func_name =
85 std::format("QueueAnimTiles_{}{}_{}", anim::porytiles_managed_prefix, tileset_name, pascal_anim_name);
86
87 const std::string tile_offset_expr =
88 is_primary ? std::format("TILE_OFFSET_4BPP({})", params.tile_offset())
89 : std::format("TILE_OFFSET_4BPP(NUM_TILES_IN_PRIMARY + {})", params.tile_offset());
90
91 out << std::format("static void {}(u16 timer)\n", func_name);
92 out << "{\n";
93 out << std::format(" u16 i = timer % ARRAY_COUNT({});\n", array_name);
94 out << std::format(
95 " AppendTilesetAnimToBuffer({}[i], (u16 *)(BG_VRAM + {}), {} * TILE_SIZE_4BPP);\n",
96 array_name,
97 tile_offset_expr,
98 params.tile_count());
99 out << "}\n";
100
101 return out.str();
102}
103
104[[nodiscard]] std::string
105generate_driver_function(const std::string &tileset_name, const std::map<DynamicCasedName, AnimParams> &animations)
106{
107 std::ostringstream out;
108
109 const std::string func_name = std::format("TilesetAnim_{}{}", anim::porytiles_managed_prefix, tileset_name);
110
111 out << std::format("static void {}(u16 timer)\n", func_name);
112 out << "{\n";
113
114 // Group animations by their frame_factor
115 std::map<std::size_t, std::vector<std::pair<DynamicCasedName, AnimParams>>> by_frame_factor;
116 for (const auto &[anim_name, params] : animations) {
117 by_frame_factor[params.frame_factor()].emplace_back(anim_name, params);
118 }
119
120 for (const auto &[frame_factor, anims] : by_frame_factor) {
121 // Group by frame_offset within each frame_factor
122 std::map<std::size_t, std::vector<std::pair<DynamicCasedName, AnimParams>>> by_offset;
123 for (const auto &[anim_name, params] : anims) {
124 by_offset[params.frame_offset()].emplace_back(anim_name, params);
125 }
126
127 for (const auto &[frame_offset, offset_anims] : by_offset) {
128 out << std::format(" if (timer % {} == {})\n", frame_factor, frame_offset);
129 out << " {\n";
130
131 for (const auto &anim_name : offset_anims | std::views::keys) {
132 out << std::format(
133 " QueueAnimTiles_{}{}_{}(timer / {});\n",
135 tileset_name,
136 anim_name.to_pascal_case(),
137 frame_factor);
138 }
139
140 out << " }\n";
141 }
142 }
143
144 out << "}\n";
145
146 return out.str();
147}
148
149[[nodiscard]] std::string generate_init_function(
150 const std::string &tileset_name, const std::map<DynamicCasedName, AnimParams> &animations, bool is_primary)
151{
152 std::ostringstream out;
153
154 const std::string init_func_name =
155 std::format("InitTilesetAnim_{}{}", anim::porytiles_managed_prefix, tileset_name);
156 const std::string driver_func_name = std::format("TilesetAnim_{}{}", anim::porytiles_managed_prefix, tileset_name);
157
158 // Find the maximum counter_max value across all animations
159 std::size_t max_counter_max = anim::default_counter_max;
160 for (const auto &params : animations | std::views::values) {
161 max_counter_max = std::max(max_counter_max, params.counter_max());
162 }
163
164 const std::string counter_var = is_primary ? "sPrimaryTilesetAnimCounter" : "sSecondaryTilesetAnimCounter";
165 const std::string counter_max_var =
166 is_primary ? "sPrimaryTilesetAnimCounterMax" : "sSecondaryTilesetAnimCounterMax";
167 const std::string callback_var = is_primary ? "sPrimaryTilesetAnimCallback" : "sSecondaryTilesetAnimCallback";
168
169 out << std::format("void {}(void)\n", init_func_name);
170 out << "{\n";
171 out << std::format(" {} = 0;\n", counter_var);
172 out << std::format(" {} = {};\n", counter_max_var, max_counter_max);
173 out << std::format(" {} = {};\n", callback_var, driver_func_name);
174 out << "}\n";
175
176 return out.str();
177}
178
179} // anonymous namespace
180
181namespace porytiles {
182
184 const std::string &tileset_name,
185 const std::filesystem::path &tileset_path_from_project_root,
186 const std::map<DynamicCasedName, AnimParams> &animations,
187 bool is_primary) const
188{
189 if (animations.empty()) {
190 return FormattableError{"No animations to generate code for."};
191 }
192
193 std::ostringstream out;
194
195 const std::string tileset_name_no_prefix = tileset_name.substr(std::size("gTileset_") - 1);
196 const std::string pascal_tileset_name = DynamicCasedName{tileset_name_no_prefix}.to_pascal_case();
197
198 // Generate header guard
199 const std::string guard_name = std::format("GUARD_GENERATED_ANIM_CODE_{}_H", pascal_tileset_name);
200 out << std::format("#ifndef {}\n", guard_name);
201 out << std::format("#define {}\n\n", guard_name);
202
203 // Generate INCBIN macro if not defined
204 out << "// Ensure INCBIN_U16 is available\n";
205 out << "#ifndef INCBIN_U16\n";
206 out << "#include \"gba/defines.h\"\n";
207 out << "#endif\n\n";
208
209 out << "/*\n";
210 out << " * This file is auto-generated by Porytiles.\n";
211 out << " * DO NOT EDIT THIS FILE MANUALLY (unless you know what you are doing).\n";
212 out << " */\n\n";
213
214 // Generate INCBIN statements for all animations
215 out << "// ============================================\n";
216 out << "// Frame Data (INCBIN statements)\n";
217 out << "// ============================================\n\n";
218
219 for (const auto &[anim_name, params] : animations) {
220 out << generate_incbin_statements(pascal_tileset_name, tileset_path_from_project_root, anim_name, params);
221 out << "\n";
222 }
223
224 // Generate frame pointer arrays
225 out << "// ============================================\n";
226 out << "// Frame Pointer Arrays\n";
227 out << "// ============================================\n\n";
228
229 for (const auto &[anim_name, params] : animations) {
230 out << generate_frame_array(pascal_tileset_name, anim_name, params);
231 out << "\n";
232 }
233
234 // Generate Queue functions
235 out << "// ============================================\n";
236 out << "// Queue Functions\n";
237 out << "// ============================================\n\n";
238
239 for (const auto &[anim_name, params] : animations) {
240 out << generate_queue_function(pascal_tileset_name, anim_name, params, is_primary);
241 out << "\n";
242 }
243
244 // Generate driver function
245 out << "// ============================================\n";
246 out << "// Driver Function\n";
247 out << "// ============================================\n\n";
248
249 out << generate_driver_function(pascal_tileset_name, animations);
250 out << "\n";
251
252 // Generate init function
253 out << "// ============================================\n";
254 out << "// Init Function\n";
255 out << "// ============================================\n\n";
256
257 out << generate_init_function(pascal_tileset_name, animations, is_primary);
258
259 // Close header guard
260 out << std::format("\n#endif // {}\n", guard_name);
261
262 return out.str();
263}
264
265} // namespace porytiles
ChainableResult< std::string > generate(const std::string &tileset_name, const std::filesystem::path &tileset_path_from_project_root, const std::map< DynamicCasedName, AnimParams > &animations, bool is_primary) const
Generates the complete generated_anim_code.h content.
Configuration parameters for a single tileset animation.
const std::vector< DynamicCasedName > & frame_order() const
Returns the playback sequence.
std::size_t frame_offset() const
Returns the frame offset (remainder value for timer modulo check).
std::size_t tile_offset() const
Returns the VRAM tile offset for this animation.
std::size_t frame_factor() const
Returns the frame factor (modulus divisor for timer).
std::size_t counter_max() const
Returns the animation counter maximum value.
std::size_t tile_count() const
Returns the number of tiles per animation frame.
A result type that maintains a chainable sequence of errors for debugging and error reporting.
A smart string wrapper that preserves word structure for lossless case format conversion.
std::string to_snake_case() const
Outputs all words flattened and joined with underscores.
std::string to_pascal_case() const
Outputs all words flattened and joined in PascalCase (each word capitalized, no separators).
General-purpose error implementation with formatted message support.
Definition error.hpp:63
constexpr std::size_t default_counter_max
constexpr std::string porytiles_managed_prefix
Definition animation.hpp:22