yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
sprite_authoring.h
Go to the documentation of this file.
1#ifndef YAZE_APP_EDITOR_SPRITE_SPRITE_AUTHORING_H_
2#define YAZE_APP_EDITOR_SPRITE_SPRITE_AUTHORING_H_
3
4#include <algorithm>
5#include <cmath>
6#include <sstream>
7
8#include "absl/status/statusor.h"
9
12#include "core/sprite_asset.h"
14
16inline absl::StatusOr<gfx::PaletteGroup> BindPaletteRows(
17 const project::SpriteAssetBinding& binding,
18 const gfx::PaletteGroup& defaults, const gfx::PaletteGroup& global,
19 const gfx::PaletteGroup& aux1, const gfx::PaletteGroup& aux2,
20 const gfx::PaletteGroup& aux3) {
21 gfx::PaletteGroup result;
22 for (size_t i = 0; i < 8; ++i) {
23 const auto& row = binding.palette_rows[i];
24 const gfx::PaletteGroup* group = nullptr;
25 int index = row.index;
26 if (row.group == "auto") {
27 group = &defaults;
28 index = i;
29 } else if (row.group == "global_sprites")
30 group = &global;
31 else if (row.group == "sprites_aux1")
32 group = &aux1;
33 else if (row.group == "sprites_aux2")
34 group = &aux2;
35 else if (row.group == "sprites_aux3")
36 group = &aux3;
37 if (!group || index < 0 || static_cast<size_t>(index) >= group->size())
38 return absl::OutOfRangeError(
39 "Sprite palette binding unavailable in loaded ROM");
40 result.AddPalette(group->palette(index));
41 }
42 return result;
43}
44
45inline absl::Status ValidateDrawAdapter(const zsprite::ZSprite& sprite,
46 const std::string& adapter) {
47 if (adapter == "literal_v1")
48 return absl::OkStatus();
49 if (adapter != "oracle_maple_v1")
50 return absl::InvalidArgumentError("Unsupported draw adapter");
51 for (const auto& frame : sprite.editor.Frames)
52 for (const auto& tile : frame.Tiles)
53 if (tile.x != 128 || !tile.size)
54 return absl::FailedPreconditionError(
55 "Maple's current driver requires X offset 0 and 16x16 tiles");
56 return absl::OkStatus();
57}
58
59// ZSpriteMaker GetASM subtracts these canvas coordinates from stored bytes.
60constexpr int kOriginX = 128;
61constexpr int kOriginY = 112;
62constexpr size_t kMaxFrames = 256; // Animation endpoints are bytes.
63
64inline int OffsetX(const zsprite::OamTile& tile) {
65 return int(tile.x) - kOriginX;
66}
67inline int OffsetY(const zsprite::OamTile& tile) {
68 return int(tile.y) - kOriginY;
69}
70
72 zsprite::Frame frame;
73 // Registry entries have paint order; ZSM renders index zero last.
74 for (auto it = layout.tiles.rbegin(); it != layout.tiles.rend(); ++it) {
75 const auto& tile = *it;
76 frame.Tiles.emplace_back(tile.x_offset + kOriginX, tile.y_offset + kOriginY,
77 tile.flip_x, tile.flip_y, tile.tile_id,
78 tile.palette, tile.size_16x16, 3);
79 }
80 return frame;
81}
82
83inline bool AppendFrame(zsprite::ZSprite& sprite, int copy_from = -1) {
84 auto& frames = sprite.editor.Frames;
85 if (frames.size() >= kMaxFrames || copy_from < -1 ||
86 copy_from >= static_cast<int>(frames.size()))
87 return false;
88 // Copy before vector growth to avoid a reference into reallocated storage.
89 auto frame = copy_from < 0 ? zsprite::Frame{} : frames[copy_from];
90 frames.push_back(std::move(frame));
91 return true;
92}
93
94inline bool DeleteFrame(zsprite::ZSprite& sprite, int index) {
95 auto& frames = sprite.editor.Frames;
96 if (frames.size() <= 1 || index < 0 ||
97 index >= static_cast<int>(frames.size()))
98 return false;
99 frames.erase(frames.begin() + index);
100 const int last = std::min(255, static_cast<int>(frames.size()) - 1);
101 for (auto& animation : sprite.animations) {
102 auto remap = [=](int endpoint) {
103 return std::clamp(endpoint - (endpoint > index ? 1 : 0), 0, last);
104 };
105 animation.frame_start = remap(animation.frame_start);
106 animation.frame_end =
107 std::max<int>(animation.frame_start, remap(animation.frame_end));
108 }
109 return true;
110}
111
112// The inherited draw loop uses byte indices. Export only table data; dispatch,
113// bank placement, OAM allocation and behavior remain owned by the ASM family.
114inline absl::StatusOr<std::string> ExportDrawTables(
115 const zsprite::ZSprite& sprite) {
116 if (sprite.editor.Frames.empty() || sprite.editor.Frames.size() > kMaxFrames)
117 return absl::InvalidArgumentError("Draw export needs 1 to 256 frames");
118 std::ostringstream starts, counts, xs, ys, chars, properties, sizes;
119 size_t total = 0;
120 for (size_t i = 0; i < sprite.editor.Frames.size(); ++i) {
121 const auto& frame = sprite.editor.Frames[i];
122 if (frame.Tiles.empty() || total + frame.Tiles.size() > 128)
123 return absl::InvalidArgumentError(
124 "Draw export needs nonempty frames and at most 128 total tiles (word "
125 "offset indexing)");
126 if (i) {
127 starts << ", ";
128 counts << ", ";
129 }
130 starts << total;
131 counts << frame.Tiles.size() - 1;
132 for (const auto& tile : frame.Tiles) {
133 if (tile.id > 511 || tile.palette > 7 || tile.priority > 3 || tile.z != 0)
134 return absl::InvalidArgumentError(
135 "Draw export requires tile 0..511, palette 0..7, priority 0..3 and "
136 "Z=0");
137 if (total++) {
138 xs << ", ";
139 ys << ", ";
140 chars << ", ";
141 properties << ", ";
142 sizes << ", ";
143 }
144 xs << OffsetX(tile);
145 ys << OffsetY(tile);
146 chars << (tile.id & 255);
147 properties << ((tile.mirror_y ? 128 : 0) | (tile.mirror_x ? 64 : 0) |
148 (tile.priority << 4) | (tile.palette << 1) |
149 (tile.id >> 8));
150 sizes << (tile.size ? 2 : 0);
151 }
152 }
153 return std::string(
154 "; Yaze draw-table candidate. Review driver, bank and OAM "
155 "allocation.\n") +
156 ".start_index\n db " + starts.str() + "\n.nbr_of_tiles\n db " +
157 counts.str() + "\n.x_offsets\n dw " + xs.str() +
158 "\n.y_offsets\n dw " + ys.str() + "\n.chr\n db " + chars.str() +
159 "\n.properties\n db " + properties.str() + "\n.sizes\n db " +
160 sizes.str() + "\n";
161}
162
163// Keep elapsed remainder and advance multiple frames when rendering is slow.
164// Invalid imported ranges are bounded for playback without rewriting the file.
165inline bool Advance(const zsprite::AnimationGroup& animation,
166 size_t frame_count, float delta, int& frame,
167 float& remainder) {
168 if (!frame_count || !std::isfinite(delta) || delta < 0)
169 return false;
170 const int last = std::min<size_t>(255, frame_count - 1);
171 const int start = std::min<int>(animation.frame_start, last);
172 const int end = std::clamp<int>(animation.frame_end, start, last);
173 const int before = frame;
174 frame = std::clamp(frame, start, end);
175 const double duration = std::max<int>(1, animation.frame_speed) / 60.0;
176 const double elapsed =
177 delta +
178 (std::isfinite(remainder) && remainder > 0 ? double(remainder) : 0.0);
179 const double steps = std::floor(elapsed / duration);
180 const int length = end - start + 1;
181 frame = start +
182 (frame - start + static_cast<int>(std::fmod(steps, length))) % length;
183 remainder = static_cast<float>(std::fmod(elapsed, duration));
184 return frame != before;
185}
186} // namespace yaze::editor::sprite_authoring
187#endif
absl::StatusOr< std::string > ExportDrawTables(const zsprite::ZSprite &sprite)
int OffsetX(const zsprite::OamTile &tile)
absl::Status ValidateDrawAdapter(const zsprite::ZSprite &sprite, const std::string &adapter)
bool Advance(const zsprite::AnimationGroup &animation, size_t frame_count, float delta, int &frame, float &remainder)
absl::StatusOr< gfx::PaletteGroup > BindPaletteRows(const project::SpriteAssetBinding &binding, const gfx::PaletteGroup &defaults, const gfx::PaletteGroup &global, const gfx::PaletteGroup &aux1, const gfx::PaletteGroup &aux2, const gfx::PaletteGroup &aux3)
int OffsetY(const zsprite::OamTile &tile)
bool AppendFrame(zsprite::ZSprite &sprite, int copy_from=-1)
zsprite::Frame CopyVanillaLayout(const zelda3::SpriteOamLayout &layout)
bool DeleteFrame(zsprite::ZSprite &sprite, int index)
std::vector< OamTile > Tiles
Definition zsprite.h:112
std::vector< Frame > Frames
Definition zsprite.h:125
std::vector< AnimationGroup > animations
Definition zsprite.h:445
Represents a group of palettes.
auto palette(int i) const
void AddPalette(SnesPalette pal)
std::array< SpritePaletteBinding, 8 > palette_rows
Complete OAM layout for a vanilla sprite.
std::vector< SpriteOamEntry > tiles