6#include "absl/strings/str_format.h"
12#include "imgui/imgui.h"
27uint64_t
HashBytes(uint64_t hash,
const std::vector<uint8_t>& bytes) {
28 for (
const uint8_t b : bytes) {
89 uint64_t hash = kFnvOffset;
90 auto groups = [&](
const auto& table) {
91 for (
const auto& row : table)
92 for (
const auto id : row)
93 hash = (hash ^
id) * kFnvPrime;
114 std::map<int, uint64_t> revisions;
116 for (
auto it = contexts.begin(); it != contexts.end();) {
117 auto* room = it->second.room;
118 if (!room || room->rom() !=
rom_ || room->game_data() !=
game_data_ ||
119 !room->IsLoaded() || room->id() != it->first) {
120 it = contexts.erase(it);
123 std::copy_n(room->blocks().begin(), 16, blocks[it->first].begin());
124 revisions[it->first] = room->composite_source_revision();
125 if (it->second.current)
146 const int preferred =
150 const auto selected =
165 uint64_t hash = kFnvOffset;
173 for (
const auto sheet : found->second) {
174 hash = (hash ^ sheet) * kFnvPrime;
183 ids.insert(target.sheet_id);
186 ids.insert(slot.sheet_id);
190 for (
const auto& [
id, pixels] : sheets) {
191 hash = (hash ^ id) * kFnvPrime;
192 hash = HashBytes(hash, pixels);
206 const int tile = (selection.y / 8) * 16 + selection.
x / 8;
207 return std::clamp(tile, 0, 63);
219 const int depth = indexed ? 8 : 32;
220 std::vector<SDL_Color> colors(image.
colors.begin(), image.
colors.end());
233 QueueTexture(bitmap);
237 const std::vector<SDL_Rect>& rects,
238 float max_scale,
bool fit_height) {
240 ImGui::TextDisabled(
"%s", tr(
"Rendering..."));
243 const auto available = ImGui::GetContentRegionAvail();
245 std::min(max_scale, std::max(1.0f, available.x) / bitmap.
width());
247 scale = std::min(scale, std::max(1.0f, available.y) / bitmap.
height());
249 scale = std::max(0.5f, scale);
251 const ImVec2 size(bitmap.
width() * scale, bitmap.
height() * scale);
252 const ImVec2 origin = ImGui::GetCursorScreenPos();
253 ImGui::Image((ImTextureID)(intptr_t)bitmap.
texture(), size);
254 auto* draw_list = ImGui::GetWindowDrawList();
256 for (
const auto& r : rects) {
258 ImVec2(origin.x + r.x * scale, origin.y + r.y * scale),
259 ImVec2(origin.x + (r.x + r.w) * scale, origin.y + (r.y + r.h) * scale),
268 ImGui::TextDisabled(
"%s", tr(
"No source preview available."));
273 ImGui::TextUnformatted(tr(
"Source preview"));
275 ImGui::Checkbox(tr(
"Live"), &
live_);
283 HOVER_HINT(
"Render unsaved source pixels now");
285 if (!source.palette_error.empty()) {
289 ImGui::TextWrapped(
"%s", source.palette_error.c_str());
302 if (
live_ && source.preview_revision) {
303 const auto current = source.preview_revision();
317 const uint64_t hash =
323 const double now = ImGui::GetTime();
325 !ImGui::IsMouseDown(ImGuiMouseButton_Left) ||
327 const auto rendered = source.render_preview();
329 : std::string(rendered.status().message());
331 if (rendered->empty())
339 if (rendered.ok() && source.preview_revision) {
340 const auto current = source.preview_revision();
356 uint64_t hash = kFnvOffset;
357 auto add = [&](uint64_t value) {
358 hash = (hash ^ value) * kFnvPrime;
366 for (
const auto& [
id, context] : rooms) {
368 add(
reinterpret_cast<uintptr_t
>(context.room));
369 add(context.entrance_slot);
370 if (context.room && context.room->rom() ==
rom_ &&
372 add(context.room->composite_source_revision());
373 for (
const auto sheet : context.room->blocks()) {
390 return absl::FailedPreconditionError(
391 "Reopen Edit Graphics from a live source to find shared uses.");
400 return absl::FailedPreconditionError(
401 "Graphics source changed; reopen Edit Graphics from the source.");
404 auto validated = source;
409 shared_targets_.push_back(validated.targets.at(source.selected_target));
415 return absl::OkStatus();
419 const bool expanded = ImGui::CollapsingHeader(
420 tr(
"Shared uses in loaded rooms"), ImGuiTreeNodeFlags_NoAutoOpenOnLog);
431 tr(
"Source or loaded rooms changed. Find shared uses again.");
433 if (ImGui::RadioButton(tr(
"Current tile##SharedScope"),
440 if (ImGui::RadioButton(tr(
"Source pattern##SharedScope"),
447 if (ImGui::Button(tr(
"Find shared uses"))) {
450 status.ok() ? std::string() : std::string(status.message());
454 "%s", tr(
"Loaded dungeon rooms only. Doors, layout, sprites, unloaded "
455 "rooms and untraced drawing are not covered. Matches are "
456 "possible uses, not a prediction of visible changes."));
461 size_t matches = 0, unknown = 0, failed = 0;
463 matches += room.objects.size();
464 unknown += room.unknown_objects;
465 failed += !room.error.empty();
467 ImGui::Text(tr(
"%zu objects / %zu loaded rooms"), matches,
469 ImGui::Text(tr(
"%zu unknown objects; %zu rooms unavailable"), unknown,
471 ImGui::TextWrapped(
"%s", tr(
"Pixels = same sheet and tile. Palette = shared "
472 "source color addresses; unmapped colors are "
474 if (ImGui::TreeNodeEx(tr(
"Source tiles and palettes"),
475 ImGuiTreeNodeFlags_NoAutoOpenOnLog)) {
477 ImGui::Text(
"Sheet $%02X / tile $%02X", target.sheet_id,
479 if (target.writable_palette) {
480 const auto& mapping = *target.writable_palette;
481 ImGui::Text(
"Row %u, %s half", mapping.row,
482 mapping.half_offset == 8 ?
"right" :
"left");
483 for (
size_t i = 1; i < mapping.colors.size(); ++i) {
484 if (
const auto& address = mapping.colors[i])
485 ImGui::Text(
"%zu: %s / %d / %d", i,
487 address->palette_index, address->color_index);
490 ImGui::TextWrapped(
"%s", target.palette_address_limitation.c_str());
496 ImGui::TextWrapped(
"%s", tr(
"No traced matches in these loaded rooms. "
497 "This does not establish exclusive use."));
499 ImGui::BeginChild(
"##SharedUseResults", ImVec2(0, 8 * ImGui::GetFontSize()),
502 if (!room.error.empty())
503 ImGui::TextWrapped(
"$%03X: %s", room.room_id, room.error.c_str());
504 for (
const auto&
object : room.objects) {
505 const auto label = absl::StrFormat(
506 "$%03X / object %03X #%zu: %s%s%s", room.room_id,
object.object_id,
507 object.object_index,
object.shares_pixels ?
"Pixels" :
"",
508 object.shares_pixels &&
object.shares_palette ?
" + " :
"",
509 object.shares_palette ?
"Palette" :
"");
510 if (ImGui::Selectable(
514 const auto live = rooms.find(room.room_id);
517 ? absl::FailedPreconditionError(
"Room is no longer loaded")
519 {},
false, live->second.room);
521 status.ok() ? std::string() : std::string(status.message());
530 if (ImGui::IsItemHovered()) {
531 ImGui::BeginTooltip();
532 for (
const auto& reference :
object.references)
533 ImGui::Text(
"Sheet $%02X / tile $%02X / row %d / slot %d",
534 reference.sheet, reference.tile_word & 0x3F,
535 (reference.tile_word >> 10) & 7, reference.slot);
553 ImGui::TextDisabled(
"%s", tr(
"Load a ROM to preview sheet usage."));
558 const auto palette_revision =
567 const bool source_changed =
569 if (source_changed) {
583 if (std::find(slots.begin() + 4, slots.end(), sheet) != slots.end()) {
589 std::stable_partition(
593 room_gfx_.begin(), room_gfx_.end(), [ss](const auto& room) {
594 return room.second.spriteset + zelda3::kDungeonSpritesetBase ==
605 ImGui::Text(tr(
"Sheet 0x%02X"), sheet);
607 ImGui::Checkbox(tr(
"Live"), &live_);
609 "Re-render while you draw (at most 10 times a second, and when the "
610 "stroke ends). Unsaved edits only; the ROM is not written.");
614 last_render_time_ = -1.0;
615 source_dirty_ =
true;
616 source_render_time_ = -1.0;
622 const uint64_t hash =
623 live_ ? HashWatchedSheets(mode_ == Mode::kRoom)
625 if (hash != last_hash_) {
629 const double now = ImGui::GetTime();
630 const bool stroke_active = ImGui::IsMouseDown(ImGuiMouseButton_Left);
631 const bool render_now =
632 dirty_ && (last_render_time_ < 0.0 || !stroke_active ||
633 now - last_render_time_ >= kMinRenderInterval);
635 last_render_time_ = now;
639 if (ImGui::BeginTabBar(
"##UsagePreviewTabs")) {
640 if (state_->source_context && state_->source_context->render_preview &&
641 state_->source_context->owner == game_data_) {
642 if (ImGui::BeginTabItem(
643 tr(
"Source"),
nullptr,
644 source_changed ? ImGuiTabItemFlags_SetSelected : 0)) {
645 DrawSourcePreview(
false);
649 if (ImGui::BeginTabItem(
tr(
"Room"))) {
650 if (mode_ != Mode::kRoom) {
654 DrawRoomContext(render_now);
657 if (ImGui::BeginTabItem(
tr(
"Sprite"))) {
658 if (mode_ != Mode::kSprite) {
659 mode_ = Mode::kSprite;
662 DrawSpriteContext(render_now, ImGui::GetIO().DeltaTime);
665 if (ImGui::BeginTabItem(
tr(
"Tile16"))) {
666 if (mode_ != Mode::kTile16) {
667 mode_ = Mode::kTile16;
670 DrawTile16Context(render_now);
675 if (!status_.empty()) {
680void UsagePreviewView::DrawRoomContext(
bool render_now) {
681 if (rooms_for_sheet_.empty()) {
682 ImGui::TextDisabled(
"%s", tr(
"No dungeon room loads this sheet."));
685 room_choice_ = std::clamp(room_choice_, 0,
686 static_cast<int>(rooms_for_sheet_.size()) - 1);
687 const int room_id = rooms_for_sheet_[room_choice_];
688 ImGui::SetNextItemWidth(160.0f);
689 if (ImGui::BeginCombo(tr(
"Room"),
690 absl::StrFormat(
"0x%03X", room_id).c_str())) {
691 for (
int i = 0; i < static_cast<int>(rooms_for_sheet_.size()); ++i) {
692 const bool selected = i == room_choice_;
693 if (ImGui::Selectable(
694 absl::StrFormat(
"0x%03X", rooms_for_sheet_[i]).c_str(),
703 ImGui::TextDisabled(tr(
"%zu rooms"), rooms_for_sheet_.size());
704 if (ImGui::Checkbox(tr(
"Objects only"), &objects_only_)) {
707 HOVER_HINT(
"Hide the floor and layout layers; show object layers only.");
709 ImGui::Checkbox(tr(
"Outline objects using this sheet"), &outline_objects_);
711 if (render_now || room_preview_.room_id() != room_id) {
712 const auto live = live_rooms_.find(room_id);
713 auto* room = live == live_rooms_.end() ? nullptr : live->second.room;
714 const auto status = room_preview_.Render(rom_, game_data_, room_id,
715 Snapshot(), objects_only_, room);
717 live_room_revisions_[room_id] = room->composite_source_revision();
718 status_ = status.ok() ? std::string() : std::string(status.message());
720 object_rects_.clear();
724 room_preview_.ObjectRectsUsingSheet(state_->current_sheet_id);
725 QueueTexture(room_preview_.mutable_bitmap());
727 const auto live = live_rooms_.find(room_id);
728 const auto* context = live == live_rooms_.end() ? nullptr : &live->second;
729 const auto blocks = room_preview_.blocks();
730 const bool loads_sheet = std::find(blocks.begin(), blocks.end(),
731 state_->current_sheet_id) != blocks.end();
732 ImGui::TextDisabled(
"%s%s", usage_preview::RoomContextLabel(context).c_str(),
733 loads_sheet ?
"" :
" · Sheet not loaded");
734 if (ImGui::IsItemHovered()) {
735 ImGui::BeginTooltip();
736 ImGui::TextUnformatted(
737 context ? context->provenance.c_str()
738 :
"ROM room header / default entrance (not live room edits)");
739 ImGui::TextUnformatted(
740 "Session sheet pixels and graphics-group assignments");
741 ImGui::Text(
"%zu objects with traced references; %zu unknown/unattributed",
742 object_rects_.size(), room_preview_.unknown_objects());
743 ImGui::Text(
"BG sheets: %02X %02X %02X %02X %02X %02X %02X %02X", blocks[0],
744 blocks[1], blocks[2], blocks[3], blocks[4], blocks[5],
745 blocks[6], blocks[7]);
746 ImGui::TextUnformatted(
747 "Object traces only; doors, layout and sprites are unscanned.");
748 ImGui::TextUnformatted(
749 "Loaded sheets and traced references do not prove a visible pixel "
751 ImGui::TextUnformatted(
752 "Coverage can be partial. Unknown includes covered, custom or untraced "
755 for (
const auto& use : room_preview_.tile_references()) {
756 if (use.sheet != state_->current_sheet_id)
759 ImGui::TextDisabled(
"Additional traced references omitted here.");
762 ImGui::Text(
"Object %03X: slot %d, tile %03X, palette row %d, H:%d V:%d",
763 use.object_id, use.slot, use.tile_word & 0x3FF,
764 (use.tile_word >> 10) & 7, (use.tile_word >> 14) & 1,
765 (use.tile_word >> 15) & 1);
769 DrawBitmapFit(room_preview_.mutable_bitmap(),
770 outline_objects_ ? object_rects_ : std::vector<SDL_Rect>{},
774void UsagePreviewView::DrawSpriteContext(
bool render_now,
float delta) {
775 if (spritesets_for_sheet_.empty()) {
776 ImGui::TextDisabled(
"%s",
777 tr(
"No spriteset loads this sheet in slots 4-7."));
780 spriteset_choice_ = std::clamp(
781 spriteset_choice_, 0,
static_cast<int>(spritesets_for_sheet_.size()) - 1);
782 const int spriteset = spritesets_for_sheet_[spriteset_choice_];
783 ImGui::SetNextItemWidth(160.0f);
784 if (ImGui::BeginCombo(tr(
"Spriteset"),
785 absl::StrFormat(
"0x%02X", spriteset).c_str())) {
786 for (
int i = 0; i < static_cast<int>(spritesets_for_sheet_.size()); ++i) {
787 if (ImGui::Selectable(
788 absl::StrFormat(
"0x%02X", spritesets_for_sheet_[i]).c_str(),
789 i == spriteset_choice_)) {
790 spriteset_choice_ = i;
791 configured_spriteset_ = -1;
797 const bool spriteset_changed = configured_spriteset_ != spriteset;
798 if (spriteset_changed || sprite_palette_revision_ != palette_revision_) {
801 int palette_room = -1;
802 for (
const auto& [room_id, info] : room_gfx_) {
804 palette_room = room_id;
808 const auto status = sprite_preview_.Configure(rom_, game_data_, spriteset,
809 palette_room, hack_name_);
810 status_ = status.ok() ? std::string() : std::string(status.message());
811 if (spriteset_changed) {
812 if (zsm_.has_value()) {
813 sprite_preview_.SetZsm(*zsm_, zsm_animation_);
815 sprite_preview_.SetVanillaSprite(
816 static_cast<uint8_t
>(vanilla_sprite_id_));
819 sprite_palette_revision_ = palette_revision_;
820 configured_spriteset_ = spriteset;
824 ImGui::SetNextItemWidth(120.0f);
825 uint8_t sprite_id =
static_cast<uint8_t
>(vanilla_sprite_id_);
826 constexpr uint8_t kStep = 1;
827 if (ImGui::InputScalar(tr(
"Vanilla sprite"), ImGuiDataType_U8, &sprite_id,
828 &kStep,
nullptr,
"%02X",
829 ImGuiInputTextFlags_CharsHexadecimal)) {
830 vanilla_sprite_id_ = sprite_id;
832 zsm_loaded_path_.clear();
833 sprite_preview_.SetVanillaSprite(
static_cast<uint8_t
>(vanilla_sprite_id_));
838 static_cast<uint16_t
>(vanilla_sprite_id_)));
840 ImGui::SetNextItemWidth(-80.0f);
841 ImGui::InputTextWithHint(
"##ZsmPath", tr(
".zsm path"), zsm_path_,
844 if (ImGui::Button(tr(
"Load"))) {
846 const auto status = zsm.
Load(zsm_path_);
848 zsm_ = std::move(zsm);
849 zsm_loaded_path_ = zsm_path_;
851 sprite_preview_.SetZsm(*zsm_, zsm_animation_);
854 status_ = std::string(status.message());
858 if (zsm_.has_value() && !zsm_->animations.empty()) {
859 zsm_animation_ = std::clamp(zsm_animation_, 0,
860 static_cast<int>(zsm_->animations.size()) - 1);
861 ImGui::SetNextItemWidth(200.0f);
862 if (ImGui::BeginCombo(
864 zsm_->animations[zsm_animation_].frame_name.c_str())) {
865 for (
int i = 0; i < static_cast<int>(zsm_->animations.size()); ++i) {
866 if (ImGui::Selectable(zsm_->animations[i].frame_name.c_str(),
867 i == zsm_animation_)) {
869 sprite_preview_.SetZsm(*zsm_, zsm_animation_);
876 playing_ = !playing_;
881 sprite_preview_.SetFrame(sprite_preview_.frame() - 1);
887 sprite_preview_.SetFrame(sprite_preview_.frame() + 1);
891 ImGui::Text(tr(
"Frame %d / %d"), sprite_preview_.frame(),
892 sprite_preview_.frame_count());
896 if (playing_ && sprite_preview_.Advance(delta)) {
900 ImGui::PushTextWrapPos(0.0f);
902 "%s", tr(
"Vanilla sprites have one catalog pose; load a .zsm to "
903 "play its animations."));
904 ImGui::PopTextWrapPos();
907 if (render_now || !sprite_bitmap_.is_active()) {
908 ShowImage(sprite_bitmap_, sprite_preview_.RenderFrame(Snapshot()));
910 const auto& slots = sprite_preview_.slots();
912 tr(
"OAM slots: %02X %02X %02X %02X | %02X %02X %02X %02X"), slots[0],
913 slots[1], slots[2], slots[3], slots[4], slots[5], slots[6], slots[7]);
914 DrawBitmapFit(sprite_bitmap_, {}, 4.0f);
917void UsagePreviewView::DrawTile16Context(
bool render_now) {
918 const uint16_t sheet = state_->current_sheet_id;
919 auto* live = live_overworld_provider_ ? live_overworld_provider_() :
nullptr;
920 if (live && (!live->is_loaded() || live->rom() != rom_))
922 std::vector<int> choices;
924 for (
const auto& map : live->overworld_maps()) {
925 if (!map.is_built() || map.game_data() != game_data_)
927 for (
int slot = 0; slot < 8; ++slot) {
928 if (map.static_graphics(slot) == sheet) {
929 choices.push_back(map.index());
934 const int current = live->current_map_id();
935 const auto preferred = std::find(choices.begin(), choices.end(), current);
936 if (preferred != choices.end())
937 std::rotate(choices.begin(), preferred, preferred + 1);
939 if (!areas_.has_value() && choices.empty()) {
941 "%s", tr(
"Open an overworld area or load ROM area assignments."));
942 if (ImGui::Button(tr(
"Load overworld areas"))) {
944 last_sheet_ = 0xFFFF;
948 if (!areas_ && !choices.empty() &&
949 ImGui::SmallButton(tr(
"Load other ROM areas"))) {
953 for (
const auto& [area, info] : *areas_) {
954 if (std::find(info.static_graphics.begin(),
955 info.static_graphics.begin() + 8,
956 sheet) != info.static_graphics.begin() + 8 &&
957 std::find(choices.begin(), choices.end(), area) == choices.end()) {
958 choices.push_back(area);
962 if (choices != areas_for_sheet_) {
965 area_choice_ < static_cast<int>(areas_for_sheet_.size())
966 ? areas_for_sheet_[area_choice_]
968 areas_for_sheet_ = std::move(choices);
969 const auto selected =
970 std::find(areas_for_sheet_.begin(), areas_for_sheet_.end(), previous);
971 area_choice_ = selected == areas_for_sheet_.end()
973 :
static_cast<int>(selected - areas_for_sheet_.begin());
976 if (areas_for_sheet_.empty()) {
977 ImGui::TextDisabled(
"%s", tr(
"No overworld area loads this sheet."));
980 area_choice_ = std::clamp(area_choice_, 0,
981 static_cast<int>(areas_for_sheet_.size()) - 1);
982 const int area = areas_for_sheet_[area_choice_];
983 ImGui::SetNextItemWidth(160.0f);
984 if (ImGui::BeginCombo(tr(
"Area"), absl::StrFormat(
"0x%02X", area).c_str())) {
985 for (
int i = 0; i < static_cast<int>(areas_for_sheet_.size()); ++i) {
986 if (ImGui::Selectable(
987 absl::StrFormat(
"0x%02X", areas_for_sheet_[i]).c_str(),
988 i == area_choice_)) {
995 ImGui::Checkbox(tr(
"Follow selection"), &follow_selection_);
996 HOVER_HINT(
"Use the 8x8 tile at the pixel editor's selection origin.");
997 if (!follow_selection_ || !state_->selection.is_active) {
999 ImGui::SetNextItemWidth(100.0f);
1000 if (ImGui::InputInt(tr(
"8x8 tile"), &tile_index_)) {
1001 tile_index_ = std::clamp(tile_index_, 0, 63);
1005 const int tile_index = SelectedTileIndex();
1007 const auto* live_map = live ? live->overworld_map(area) :
nullptr;
1009 (!live_map->is_built() || live_map->game_data() != game_data_))
1012 uint64_t hash = kFnvOffset;
1013 auto add = [&](uint64_t value) {
1014 hash = (hash ^ value) * kFnvPrime;
1017 for (
const auto& tile : live->tiles16())
1018 for (
const auto& quadrant : tile.tiles_info)
1020 for (
const auto id : live_map->tileset_key()) {
1022 add(game_data_->sheet_store.Revision(
id));
1024 for (
const auto& color : live_map->current_palette())
1026 if (!tile16_uses_live_source_ || tile16_preview_.area() != area ||
1027 live_tile16_hash_ != hash) {
1028 const auto status = tile16_preview_.ConfigureFromLive(
1029 *live_map, live->tiles16(), game_data_);
1030 status_ = status.ok() ? std::string() : std::string(status.message());
1033 tile16_uses_live_source_ =
true;
1034 live_tile16_hash_ = hash;
1037 ImGui::TextDisabled(
"%s", tr(
"Live overworld: unsaved Tile16 definitions"));
1039 if (tile16_uses_live_source_ || tile16_preview_.area() != area) {
1040 const auto status = tile16_preview_.Configure(rom_, game_data_, area);
1041 status_ = status.ok() ? std::string() : std::string(status.message());
1044 tile16_uses_live_source_ =
false;
1047 ImGui::TextDisabled(
"%s",
1048 tr(
"ROM definitions: open this area for live edits"));
1050 const auto tile16s = tile16_preview_.Tile16sUsingTile(sheet, tile_index);
1051 if (tile16s != tile16s_) {
1055 ImGui::Text(tr(
"8x8 tile 0x%02X: %zu tile16s"), tile_index, tile16s_.size());
1056 if (tile16s_.empty()) {
1059 if (render_now || !tile16_bitmap_.is_active()) {
1060 tile16_highlights_.clear();
1061 ShowImage(tile16_bitmap_,
1062 tile16_preview_.Render(Snapshot(), tile16s_, sheet, tile_index,
1063 &tile16_highlights_));
1065 DrawBitmapFit(tile16_bitmap_, tile16_highlights_, 4.0f);
The Rom class is used to load, save, and modify Rom data. This is a generic SNES ROM container and do...
std::optional< GraphicsEditContext > source_context
std::set< uint16_t > modified_sheets
uint16_t current_sheet_id
uint64_t source_context_revision
const GraphicsEditTarget * SourceTarget() const
gfx::Bitmap source_bitmap_
usage_preview::SheetPixels Snapshot() const
static constexpr double kMinRenderInterval
uint64_t palette_revision_
void DrawBitmapFit(gfx::Bitmap &bitmap, const std::vector< SDL_Rect > &rects, float max_scale, bool fit_height=false)
bool shared_whole_pattern_
bool tile16_uses_live_source_
std::optional< uint64_t > source_model_revision_
usage_preview::RoomUsagePreview shared_room_preview_
GraphicsEditorState * state_
usage_preview::LiveRoomProvider live_rooms_provider_
uint64_t source_context_revision_
std::optional< std::map< int, zelda3::OverworldAreaGfxInfo > > areas_
std::vector< GraphicsEditTarget > shared_targets_
void ShowImage(gfx::Bitmap &bitmap, const usage_preview::PreviewImage &image)
zelda3::GameData * game_data_
absl::Status ScanSharedUses(const usage_preview::LiveRoomContexts &rooms)
void DrawSourcePreview(bool show_controls=true)
std::string shared_scan_status_
double source_render_time_
uint64_t HashWatchedSheets(bool include_room_context) const
uint64_t shared_scan_hash_
std::vector< int > spritesets_for_sheet_
uint64_t rendered_source_revision_
std::string source_status_
std::vector< int > rooms_for_sheet_
uint64_t SharedUsesHash(const usage_preview::LiveRoomContexts &rooms) const
std::map< int, uint64_t > live_room_revisions_
void RefreshRoomContexts()
uint64_t source_palette_revision_
int configured_spriteset_
uint64_t live_tile16_hash_
std::map< int, zelda3::RoomGfxInfo > room_gfx_
std::map< int, std::array< uint8_t, 16 > > room_blocks_
int SelectedTileIndex() const
void SetGameData(zelda3::GameData *game_data)
size_t shared_preview_object_
usage_preview::RoomUsagePreview room_preview_
std::map< int, std::array< uint8_t, 16 > > fallback_room_blocks_
std::vector< int > areas_for_sheet_
std::vector< usage_preview::LoadedRoomSourceUses > shared_uses_
usage_preview::Tile16UsagePreview tile16_preview_
std::vector< int > tile16s_
std::vector< SDL_Rect > object_rects_
usage_preview::LiveRoomContexts live_rooms_
absl::Status Render(Rom *rom, zelda3::GameData *game_data, int room_id, const SheetPixels &sheets, bool objects_only, zelda3::Room *live_room=nullptr)
gfx::Bitmap & mutable_bitmap()
void Reset()
Clears the loaded room so the next Render() reloads it from the ROM.
std::vector< SDL_Rect > ObjectRectsForObject(size_t index) const
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Represents a bitmap image optimized for SNES ROM hacking.
void Create(int width, int height, int depth, std::span< uint8_t > data)
Create a bitmap with the given dimensions and data.
TextureHandle texture() const
BitmapMetadata & metadata()
void set_data(const std::vector< uint8_t > &data)
void SetPalette(const SnesPalette &palette)
Set the palette for the bitmap using SNES palette format.
uint64_t ColorRevision(const zelda3::GameData *game_data) const
static PaletteManager & Get()
Get the singleton instance.
static CustomObjectManager & Get()
uint64_t Revision(uint16_t sheet) const
#define ICON_MD_PLAY_ARROW
#define ICON_MD_SKIP_NEXT
#define ICON_MD_SKIP_PREVIOUS
#define HOVER_HINT(string)
constexpr uint64_t kFnvOffset
void QueueTexture(gfx::Bitmap &bitmap)
constexpr uint64_t kFnvPrime
uint64_t HashBytes(uint64_t hash, const std::vector< uint8_t > &bytes)
SheetPixels SnapshotSessionSheets(const std::set< uint16_t > &ids, const zelda3::GameData *game_data)
std::vector< LoadedRoomSourceUses > FindLoadedRoomSourceUses(Rom *rom, zelda3::GameData *game_data, const LiveRoomContexts &rooms, const std::vector< GraphicsEditTarget > &targets)
std::map< uint16_t, std::vector< uint8_t > > SheetPixels
Sheet id -> 4096 8bpp pixels (the GameData::graphics_buffer layout).
std::map< int, LiveRoomContext > LiveRoomContexts
std::vector< int > RoomsUsingSheet(const std::map< int, std::array< uint8_t, 16 > > &room_blocks, uint16_t sheet, bool include_sprites)
std::map< int, std::array< uint8_t, 16 > > CollectRoomSheetBlocks(Rom *rom, zelda3::GameData *game_data, const std::map< int, zelda3::RoomGfxInfo > &rooms)
Editors are the view controllers for the application.
absl::Status UpdateGraphicsContextPalettes(GraphicsEditContext &context, GraphicsEditContext refreshed)
uint16_t TileInfoToWord(TileInfo tile_info)
void AutoRegisterLastItem(const std::string &widget_type, const std::string &explicit_label, const std::string &description)
Automatically register the last ImGui item.
const char * tr(const char *source)
std::map< int, OverworldAreaGfxInfo > CollectOverworldAreaGfx(Rom &rom, GameData *game_data)
constexpr uint16_t kDungeonSpritesetBase
std::map< int, RoomGfxInfo > CollectRoomGfx(const Rom &rom)
const char * PaletteAddressGroupName(PaletteAddressGroup group)
constexpr bool IsUnderworldSpriteset(int spriteset)
Dungeon rooms use spritesets 0x40-0x8F (room header value + 0x40).
std::array< uint8_t, 8 > SpriteSheetSlots(const std::array< uint8_t, 4 > &spriteset_values, bool underworld)
The eight sheets for a spriteset's four values.
const char * ResolveSpriteName(uint16_t id)
#define RETURN_IF_ERROR(expr)
Which graphics sheets back sprite OAM tiles 0x000-0x1FF.
zelda3::ResolvedGraphicsContext resolved
const zelda3::GameData * owner
std::array< SDL_Color, 256 > colors
std::vector< uint8_t > pixels
absl::Status Load(const std::string &filename)
Load a ZSM file from disk.
std::array< std::array< uint8_t, 4 >, kNumSpritesets > spriteset_ids
std::array< std::array< uint8_t, 4 >, kNumRoomBlocksets > room_blockset_ids
std::array< std::array< uint8_t, 8 >, kNumMainBlocksets > main_blockset_ids
GraphicsSheetStore sheet_store