yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
sprite_editor_assets.cc
Go to the documentation of this file.
2
3#include <cstring>
4#include <filesystem>
5
12#include "core/project.h"
16
17namespace yaze::editor {
26
28 behavior_candidate_.clear();
29 behavior_status_ = absl::OkStatus();
30 if (const auto* binding = current_sprite_binding()) {
31 std::copy(binding->sheets.begin(), binding->sheets.end(), current_sheets_);
32 gfx_buffer_loaded_ = false;
34 source_checked_ = false;
35 palette_binding_status_ = absl::OkStatus();
36 graphics_binding_status_ = absl::OkStatus();
37 graphics_source_edit_status_ = absl::OkStatus();
39 }
40}
41
42absl::Status SpriteEditor::CreateSpriteAssetFromVanilla(int sprite_id) {
43 if (sprite_id < 0 || sprite_id > 255)
44 return absl::OutOfRangeError("Sprite ID must be 0..255");
47 sprite_id, project() ? project()->hack_manifest.hack_name() : "");
48 if (!layout)
49 layout = zelda3::SpriteOamRegistry::GetLayout(sprite_id);
51 std::copy_n(vanilla_sheets_, 8, current_sheets_);
52 std::copy_n(vanilla_sheets_, 8,
53 custom_sprite_bindings_.back().sheets.begin());
54 auto& copy = custom_sprites_.back();
55 copy.sprName = std::string(zelda3::kSpriteDefaultNames[sprite_id]) +
56 (layout ? " (static pose copy)" : " (empty template)");
57 copy.property_sprid.Text = std::to_string(sprite_id);
58 if (layout)
59 copy.editor.Frames[0] = sprite_authoring::CopyVanillaLayout(*layout);
60 custom_sprite_bindings_.back().catalog_key =
61 "vanilla." + std::to_string(sprite_id);
62 gfx_buffer_loaded_ = false;
66 visible.push_back("sprite.custom_editor");
68 visible);
69 }
70 return absl::OkStatus();
71}
72
74 int slot, const project::SpriteGraphicsSource& source) {
76 return absl::FailedPreconditionError("Select a sprite asset");
77 if (slot < 0 || slot >= 8)
78 return absl::OutOfRangeError("Sprite graphics slot must be 0..7");
79 auto resolved_source = source;
80 if (resolved_source.kind == "bin" && project()) {
81 resolved_source.path = project()->GetAbsolutePath(resolved_source.path);
82 }
83 auto candidate = *current_sprite_binding();
84 candidate.graphics_sources[slot] = resolved_source;
85 if (candidate.zsm_path.empty())
86 candidate.zsm_path = "unsaved.zsm";
87 auto validated =
89 if (!validated.ok())
90 return validated.status();
91 const auto directory =
92 project() && !project()->filepath.empty()
93 ? std::filesystem::path(project()->filepath).parent_path()
94 : std::filesystem::path{};
96 resolved_source, current_sheets_[slot], game_data(), rom_, directory);
97 // Sheet bindings can be authored before a ROM is loaded.
98 if (!pixels.ok() && source.kind != "sheet")
99 return pixels.status();
102 std::move(resolved_source);
106 return absl::OkStatus();
107}
108
110 const auto* binding = current_sprite_binding();
111 if (!binding || slot < 0 || slot >= 8 ||
112 binding->graphics_sources[slot].kind != "bin")
113 return absl::FailedPreconditionError("Select a bound BIN graphics slot");
114 auto* manager = static_cast<EditorManager*>(dependencies_.custom_data);
115 if (!manager)
116 return absl::FailedPreconditionError("Editor manager unavailable");
117 auto path = std::filesystem::path(binding->graphics_sources[slot].path);
118 if (path.is_relative()) {
119 if (!project() || project()->filepath.empty())
120 return absl::FailedPreconditionError(
121 "Save the project to resolve this BIN path");
122 path = std::filesystem::path(project()->filepath).parent_path() / path;
123 }
127 int palette = 0;
128 std::optional<zsprite::Frame> captured_frame;
129 std::string captured_label;
130 if (const auto* sprite = current_custom_sprite();
131 sprite && current_frame_ >= 0 &&
132 current_frame_ < static_cast<int>(sprite->editor.Frames.size())) {
133 const auto& frame = sprite->editor.Frames[current_frame_];
134 captured_frame = frame;
135 captured_label = sprite->sprName + " / frame " +
136 std::to_string(current_frame_) + " / slot " +
137 std::to_string(slot);
138 if (selected_tile_index_ >= 0 &&
139 selected_tile_index_ < static_cast<int>(frame.Tiles.size()))
140 palette = frame.Tiles[selected_tile_index_].palette;
141 else
142 for (const auto& tile : frame.Tiles)
143 if (zelda3::SpriteSlotForTile(tile.id) == slot) {
144 palette = tile.palette;
145 break;
146 }
147 }
148 if (palette >= static_cast<int>(sprite_palettes_.size()))
149 return absl::FailedPreconditionError("Selected OAM palette unavailable");
150 RETURN_IF_ERROR(manager->EnsureEditorAssetsLoaded(EditorType::kGraphics));
151 auto* editors = manager->GetCurrentEditorSet();
152 if (!editors || !editors->GetGraphicsEditor())
153 return absl::FailedPreconditionError("Graphics editor unavailable");
154 const auto& source = binding->graphics_sources[slot];
155 RETURN_IF_ERROR(editors->GetGraphicsEditor()->ImportScratchBin(
156 path.lexically_normal().string(), source.bpp, source.offset,
157 sprite_palettes_.palette_ref(palette)));
158 if (captured_frame) {
159 editors->GetGraphicsEditor()->SetScratchPreview(
160 std::move(captured_label),
161 [frame = std::move(*captured_frame), buffer = sprite_gfx_buffer_,
162 palettes = sprite_palettes_, slot,
163 palette](const GraphicsScratchAsset& asset)
164 -> absl::StatusOr<usage_preview::PreviewImage> {
165 if (asset.pixels.size() != 4096 || buffer.size() < 8 * 4096)
166 return absl::InvalidArgumentError(
167 "Sprite scratch slot has invalid dimensions");
168 auto updated = buffer;
169 std::copy(asset.pixels.begin(), asset.pixels.end(),
170 updated.begin() + slot * 4096);
171 auto colors = palettes;
173 for (const auto color : asset.palette)
174 row.AddColor(gfx::SnesColor(color));
175 colors.palette_ref(palette) = std::move(row);
176 return sprite_graphics_context::RenderFrameBuffer(frame, updated,
177 colors);
178 });
179 } else {
180 editors->GetGraphicsEditor()->SetScratchPreview({}, {});
181 }
182 manager->SwitchToEditor(EditorType::kGraphics, true);
183 return absl::OkStatus();
184}
185
187 const auto* binding = current_sprite_binding();
188 if (!binding || !ImGui::CollapsingHeader("Graphics sources"))
189 return;
190 ImGui::TextWrapped(
191 "Each slot holds 64 tiles (128 x 32 pixels). BIN and raw ROM ranges "
192 "are preview sources. Edit a BIN through Graphics > Scratch, export it, "
193 "then reload here. Raw ROM offsets are headerless PC byte offsets.");
194 ImGui::SliderInt("Graphics slot", &graphics_source_slot_, 0, 7);
197 const auto& source = binding->graphics_sources[graphics_source_slot_];
198 graphics_source_kind_ = source.kind == "bin" ? 1
199 : source.kind == "raw_rom" ? 2
200 : 0;
201 graphics_source_bpp_ = source.bpp;
202 graphics_source_offset_ = source.offset;
203 std::strncpy(graphics_source_path_, source.path.c_str(),
204 sizeof(graphics_source_path_) - 1);
208 }
209 constexpr const char* kinds[] = {"ROM sheet", "BIN file", "Raw ROM range"};
210 ImGui::Combo("Source", &graphics_source_kind_, kinds, 3);
211 if (graphics_source_kind_ != 0) {
212 ImGui::SliderInt("Bits per pixel", &graphics_source_bpp_, 2, 4);
213 ImGui::InputScalar("Byte offset (hex)", ImGuiDataType_U32,
214 &graphics_source_offset_, nullptr, nullptr, "%08X",
215 ImGuiInputTextFlags_CharsHexadecimal);
216 if (graphics_source_kind_ == 1)
217 ImGui::InputText("BIN path", graphics_source_path_,
218 sizeof(graphics_source_path_));
219 }
220 if (ImGui::Button("Apply graphics source")) {
222 if (graphics_source_kind_ != 0) {
223 source.kind = graphics_source_kind_ == 1 ? "bin" : "raw_rom";
226 source.bpp = graphics_source_bpp_;
227 }
230 }
231 ImGui::SameLine();
232 if (ImGui::Button("Reload graphics sources")) {
233 gfx_buffer_loaded_ = false;
235 }
236 if (binding->graphics_sources[graphics_source_slot_].kind == "bin" &&
237 ImGui::Button("Edit bound BIN in Graphics Scratch"))
240 ImGui::TextWrapped("%s", graphics_source_edit_status_.ToString().c_str());
241 for (int slot = 0; slot < 8; ++slot) {
242 const auto& source = binding->graphics_sources[slot];
243 if (source.kind != "sheet")
244 ImGui::TextWrapped("Slot %d: %s, %dbpp, offset $%08X %s", slot,
245 source.kind.c_str(), source.bpp, source.offset,
246 source.path.c_str());
247 }
248}
249
251 const std::array<uint8_t, 8>& sheets,
252 const std::array<project::SpritePaletteBinding, 8>& rows) {
254 return absl::FailedPreconditionError("Select a sprite asset");
255 auto updated = *current_sprite_binding();
256 updated.sheets = sheets;
257 updated.palette_rows = rows;
258 auto validation = updated;
259 if (validation.zsm_path.empty())
260 validation.zsm_path = "unsaved.zsm";
261 auto parsed =
263 if (!parsed.ok())
264 return parsed.status();
270 return absl::OkStatus();
271}
272
273absl::Status SpriteEditor::OpenSpriteAsset(const std::string& path) {
275 const auto absolute =
276 std::filesystem::absolute(path).lexically_normal().string();
277 auto found = std::find(custom_sprite_paths_.begin(),
278 custom_sprite_paths_.end(), absolute);
279 if (found != custom_sprite_paths_.end()) {
281 std::distance(custom_sprite_paths_.begin(), found);
283 custom_sprites_[current_custom_sprite_index_].editor.Frames.empty() ? -1
284 : 0;
286 custom_sprites_[current_custom_sprite_index_].animations.empty() ? -1
287 : 0;
289 animation_playing_ = false;
292 return absl::OkStatus();
293 }
294 LoadZsmFile(absolute);
295 return status_;
296}
297
298absl::Status SpriteEditor::SaveSpriteAsset(const std::string& path) {
300 return absl::FailedPreconditionError("Select a sprite asset");
301 const auto absolute =
302 std::filesystem::absolute(path).lexically_normal().string();
303 SaveZsmFile(absolute);
304 return status_;
305}
306
308 absl::Status result;
309 if (project()) {
310 for (const auto& asset : project()->sprite_assets) {
311 auto loaded = OpenSpriteAsset(project()->GetAbsolutePath(asset.zsm_path));
312 if (!loaded.ok() && result.ok())
313 result = loaded;
314 }
315 }
316 // An unavailable asset is a panel-local error, not a ROM/editor load failure.
317 status_ = absl::OkStatus();
318 return result;
319}
320
322 source_checked_ = true;
323 const auto* asset = current_sprite_binding();
324 if (!asset || asset->source_path.empty()) {
325 source_check_status_ = absl::NotFoundError("Asset has no imported source");
327 }
328 const auto root =
329 project() ? project()->GetAbsolutePath(project()->sprite_source_root)
330 : "";
331 auto source = zelda3::ReadSpriteSource(
332 root,
333 {"draw", asset->source_path, asset->source_label, asset->draw_adapter});
334 if (!source.ok())
335 source_check_status_ = source.status();
336 else if (core::ComputeSourceArtifactSha256(source->content) !=
337 asset->source_sha256)
338 source_check_status_ = absl::FailedPreconditionError(
339 "Draw source changed since import. Edited asset preserved; review "
340 "before export.");
341 else
342 source_check_status_ = absl::OkStatus();
344}
345
346absl::StatusOr<std::string> SpriteEditor::ExportCurrentSpriteDraw() {
348 return absl::FailedPreconditionError("Select a sprite asset");
350 *current_custom_sprite(), current_sprite_binding()->draw_adapter));
351 if (!current_sprite_binding()->source_path.empty())
354}
355
358 if (!palette_binding_status_.ok())
359 ImGui::TextWrapped("%s", palette_binding_status_.ToString().c_str());
360 if (!graphics_binding_status_.ok())
361 ImGui::TextWrapped("%s", graphics_binding_status_.ToString().c_str());
362 const auto* current = current_sprite_binding();
363 if (!current || !ImGui::CollapsingHeader("Asset bindings"))
364 return;
365 ImGui::TextWrapped(
366 "Save the ZSM, then save the project to retain these bindings.");
367 if (!current->catalog_key.empty())
368 ImGui::TextWrapped("Catalog: %s", current->catalog_key.c_str());
369 if (!current->source_path.empty()) {
370 ImGui::TextWrapped("Source: %s : %s", current->source_path.c_str(),
371 current->source_label.c_str());
372 ImGui::TextWrapped("Adapter: %s", current->draw_adapter.c_str());
373 if (ImGui::Button("Check source hash"))
375 if (source_checked_)
376 ImGui::TextWrapped("%s", source_check_status_.ok()
377 ? "Source matched at last check"
378 : source_check_status_.ToString().c_str());
379 }
380 constexpr const char* groups[] = {"auto", "global_sprites", "sprites_aux1",
381 "sprites_aux2", "sprites_aux3"};
382 auto rows = current->palette_rows;
383 bool changed = false;
384 for (int i = 0; i < 8; ++i) {
385 ImGui::PushID(i);
386 ImGui::Text("OAM palette %d", i);
387 int group = 0;
388 for (int j = 0; j < 5; ++j)
389 if (rows[i].group == groups[j])
390 group = j;
391 if (ImGui::Combo("Group", &group, groups, 5)) {
392 rows[i].group = groups[group];
393 changed = true;
394 }
395 if (rows[i].group != "auto" &&
396 ImGui::InputInt("Palette index", &rows[i].index)) {
397 rows[i].index = std::clamp(rows[i].index, 0, 255);
398 changed = true;
399 }
400 ImGui::PopID();
401 }
402 if (changed) {
403 (void)SetSpriteGraphics(current->sheets, rows);
405 }
406}
407} // namespace yaze::editor
The EditorManager controls the main editor window and manages the various editor classes.
zelda3::GameData * game_data() const
Definition editor.h:369
EditorDependencies dependencies_
Definition editor.h:382
absl::Status OpenSpriteAsset(const std::string &path)
std::vector< project::SpriteAssetBinding > custom_sprite_bindings_
const zsprite::ZSprite * current_custom_sprite() const
void RefreshSpritePreviewGraphics(bool use_asset_binding)
absl::StatusOr< std::string > ExportCurrentSpriteDraw()
absl::Status OpenBoundBinInScratch(int slot)
absl::Status graphics_source_edit_status_
gfx::PaletteGroup sprite_palettes_
void LoadZsmFile(const std::string &path)
absl::Status CreateSpriteAssetFromVanilla(int sprite_id)
absl::Status palette_binding_status_
absl::Status SaveSpriteAsset(const std::string &path)
std::vector< zsprite::ZSprite > custom_sprites_
std::vector< uint8_t > sprite_gfx_buffer_
project::YazeProject * project() const
absl::Status SetSpriteGraphicsSource(int slot, const project::SpriteGraphicsSource &source)
const project::SpriteAssetBinding * current_sprite_binding() const
void SaveZsmFile(const std::string &path)
absl::Status graphics_binding_status_
std::vector< std::string > custom_sprite_paths_
absl::Status SetSpriteGraphics(const std::array< uint8_t, 8 > &sheets, const std::array< project::SpritePaletteBinding, 8 > &rows)
void SetVisibleWindows(size_t session_id, const std::vector< std::string > &window_ids)
Set which panels should be visible for a session.
std::vector< std::string > GetVisibleWindowIds(size_t session_id) const
Get list of currently visible panel IDs for a session.
SNES Color container.
Definition snes_color.h:110
Represents a palette of colors for the Super Nintendo Entertainment System (SNES).
void AddColor(const SnesColor &color)
static const SpriteOamLayout * GetLayout(uint8_t sprite_id)
Get the OAM layout for a sprite ID.
static const SpriteOamLayout * GetPreviewOverride(uint8_t sprite_id, std::string_view hack_name)
std::string ComputeSourceArtifactSha256(std::string_view content)
absl::StatusOr< std::string > ExportDrawTables(const zsprite::ZSprite &sprite)
absl::Status ValidateDrawAdapter(const zsprite::ZSprite &sprite, const std::string &adapter)
zsprite::Frame CopyVanillaLayout(const zelda3::SpriteOamLayout &layout)
absl::StatusOr< usage_preview::PreviewImage > RenderFrameBuffer(const zsprite::Frame &frame, std::span< const uint8_t > buffer, const gfx::PaletteGroup &palettes)
absl::StatusOr< std::vector< uint8_t > > Resolve(const project::SpriteGraphicsSource &source, uint16_t sheet, const zelda3::GameData *data, const Rom *rom, const std::filesystem::path &project_directory)
Editors are the view controllers for the application.
nlohmann::json SpriteAssetToJson(const SpriteAssetBinding &asset)
absl::StatusOr< SpriteAssetBinding > SpriteAssetFromJson(const nlohmann::json &value)
absl::StatusOr< SpriteSourceDocument > ReadSpriteSource(const std::string &source_root, const SpriteSourceBinding &binding)
const std::string kSpriteDefaultNames[256]
Definition sprite.cc:16
constexpr int SpriteSlotForTile(int tile)
Slot (0-7) holding 8x8 OAM tile tile (name-table bit included).
#define RETURN_IF_ERROR(expr)
Definition snes.cc:22
WorkspaceWindowManager * window_manager
Definition editor.h:182
const SnesPalette & palette_ref(int i) const
std::vector< SpriteAssetBinding > sprite_assets
Definition project.h:336
std::string GetAbsolutePath(const std::string &relative_path) const
Definition project.cc:2675