23 if (room_id < 0 || room_id >= 0x128) {
24 ImGui::Text(
"Invalid room ID: %d", room_id);
29 ImGui::Text(
"ROM not loaded");
41 constexpr int kRoomPixelWidth = 512;
42 constexpr int kRoomPixelHeight = 512;
43 constexpr int kDungeonTileSize = 8;
50 static int debug_frame_count = 0;
51 if (debug_frame_count++ % 60 == 0) {
52 LOG_DEBUG(
"[DungeonCanvas]",
"Canvas config: size=(%.0f,%.0f) scale=%.2f grid=%.0f",
54 LOG_DEBUG(
"[DungeonCanvas]",
"Canvas viewport: p0=(%.0f,%.0f) p1=(%.0f,%.0f)",
61 auto& room = (*rooms_)[room_id];
64 static int prev_blockset = -1;
65 static int prev_palette = -1;
66 static int prev_layout = -1;
67 static int prev_spriteset = -1;
70 if (ImGui::BeginTable(
"##RoomProperties", 4, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Borders)) {
71 ImGui::TableSetupColumn(
"Graphics");
72 ImGui::TableSetupColumn(
"Layout");
73 ImGui::TableSetupColumn(
"Floors");
74 ImGui::TableSetupColumn(
"Message");
75 ImGui::TableHeadersRow();
77 ImGui::TableNextRow();
80 ImGui::TableNextColumn();
86 ImGui::TableNextColumn();
90 ImGui::TableNextColumn();
91 uint8_t floor1_val = room.floor1();
92 uint8_t floor2_val = room.floor2();
93 if (
gui::InputHexByte(
"Floor1", &floor1_val, 50.f) && ImGui::IsItemDeactivatedAfterEdit()) {
94 room.set_floor1(floor1_val);
95 if (room.rom() && room.rom()->is_loaded()) {
96 room.RenderRoomGraphics();
99 if (
gui::InputHexByte(
"Floor2", &floor2_val, 50.f) && ImGui::IsItemDeactivatedAfterEdit()) {
100 room.set_floor2(floor2_val);
101 if (room.rom() && room.rom()->is_loaded()) {
102 room.RenderRoomGraphics();
107 ImGui::TableNextColumn();
115 if (ImGui::BeginTable(
"##AdvancedProperties", 3, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Borders)) {
116 ImGui::TableSetupColumn(
"Effect");
117 ImGui::TableSetupColumn(
"Tag 1");
118 ImGui::TableSetupColumn(
"Tag 2");
119 ImGui::TableHeadersRow();
121 ImGui::TableNextRow();
124 ImGui::TableNextColumn();
125 const char* effect_names[] = {
"Nothing",
"One",
"Moving Floor",
"Moving Water",
"Four",
"Red Flashes",
"Torch Show Floor",
"Ganon Room"};
126 int effect_val =
static_cast<int>(room.effect());
127 if (ImGui::Combo(
"##Effect", &effect_val, effect_names, 8)) {
132 ImGui::TableNextColumn();
133 const char* tag_names[] = {
"Nothing",
"NW Kill",
"NE Kill",
"SW Kill",
"SE Kill",
"W Kill",
"E Kill",
"N Kill",
"S Kill",
134 "Clear Quad",
"Clear Room",
"NW Push",
"NE Push",
"SW Push",
"SE Push",
"W Push",
"E Push",
135 "N Push",
"S Push",
"Push Block",
"Pull Lever",
"Clear Level",
"Switch Hold",
"Switch Toggle"};
136 int tag1_val =
static_cast<int>(room.tag1());
137 if (ImGui::Combo(
"##Tag1", &tag1_val, tag_names, 24)) {
142 ImGui::TableNextColumn();
143 int tag2_val =
static_cast<int>(room.tag2());
144 if (ImGui::Combo(
"##Tag2", &tag2_val, tag_names, 24)) {
153 if (ImGui::BeginTable(
"##LayerControls", 4, ImGuiTableFlags_SizingStretchSame)) {
154 ImGui::TableNextRow();
156 ImGui::TableNextColumn();
158 ImGui::Checkbox(
"BG1", &layer_settings.bg1_visible);
160 ImGui::TableNextColumn();
161 ImGui::Checkbox(
"BG2", &layer_settings.bg2_visible);
163 ImGui::TableNextColumn();
165 const char* bg2_types[] = {
"Norm",
"Trans",
"Add",
"Dark",
"Off"};
166 ImGui::SetNextItemWidth(-FLT_MIN);
167 ImGui::Combo(
"##BG2Type", &layer_settings.bg2_layer_type, bg2_types, 5);
169 ImGui::TableNextColumn();
171 const char* merge_types[] = {
"Off",
"Parallax",
"Dark",
"On top",
"Translucent",
"Addition",
"Normal",
"Transparent",
"Dark room"};
172 int merge_val = room.layer_merging().ID;
173 if (ImGui::Combo(
"##Merge", &merge_val, merge_types, 9)) {
174 room.SetLayerMerging(zelda3::kLayerMergeTypeList[merge_val]);
181 if (prev_blockset != room.blockset || prev_palette != room.palette ||
182 prev_layout != room.layout || prev_spriteset != room.spriteset) {
185 if (room.rom() && room.rom()->is_loaded()) {
188 room.LoadRoomGraphics(room.blockset);
189 room.RenderRoomGraphics();
192 prev_blockset = room.blockset;
193 prev_palette = room.palette;
194 prev_layout = room.layout;
195 prev_spriteset = room.spriteset;
206 if (debug_frame_count % 60 == 1) {
207 LOG_DEBUG(
"[DungeonCanvas]",
"After DrawBackground: canvas_sz=(%.0f,%.0f) canvas_p0=(%.0f,%.0f) canvas_p1=(%.0f,%.0f)",
217 auto& room = (*rooms_)[room_id];
243 settings.bg1_visible = !settings.bg1_visible;
252 settings.bg2_visible = !settings.bg2_visible;
261 room.RenderRoomGraphics();
289 object_bounds_menu.
callback = [
this]() {
294 object_bounds_menu.
subitems.push_back({
295 "Type 1 (0x00-0xFF)",
300 object_bounds_menu.
subitems.push_back({
301 "Type 2 (0x100-0x1FF)",
306 object_bounds_menu.
subitems.push_back({
307 "Type 3 (0xF00-0xFFF)",
317 object_bounds_menu.
subitems.push_back(sep);
320 object_bounds_menu.
subitems.push_back({
326 object_bounds_menu.
subitems.push_back({
332 object_bounds_menu.
subitems.push_back({
339 debug_menu.
subitems.push_back(object_bounds_menu);
354 room.LoadRoomGraphics(room.blockset);
355 room.RenderRoomGraphics();
363 LOG_DEBUG(
"DungeonDebug",
"=== Room %03X Debug ===", room_id);
364 LOG_DEBUG(
"DungeonDebug",
"Blockset: %d, Palette: %d, Layout: %d",
365 room.blockset, room.palette, room.layout);
366 LOG_DEBUG(
"DungeonDebug",
"Objects: %zu, Sprites: %zu",
367 room.GetTileObjects().size(), room.GetSprites().size());
368 LOG_DEBUG(
"DungeonDebug",
"BG1: %dx%d, BG2: %dx%d",
369 room.bg1_buffer().bitmap().width(), room.bg1_buffer().bitmap().height(),
370 room.bg2_buffer().bitmap().width(), room.bg2_buffer().bitmap().height());
381 auto& room = (*rooms_)[room_id];
383 ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_FirstUseEver);
385 ImGui::Text(
"Room: 0x%03X (%d)", room_id, room_id);
387 ImGui::Text(
"Graphics");
388 ImGui::Text(
" Blockset: 0x%02X", room.blockset);
389 ImGui::Text(
" Palette: 0x%02X", room.palette);
390 ImGui::Text(
" Layout: 0x%02X", room.layout);
391 ImGui::Text(
" Spriteset: 0x%02X", room.spriteset);
393 ImGui::Text(
"Content");
394 ImGui::Text(
" Objects: %zu", room.GetTileObjects().size());
395 ImGui::Text(
" Sprites: %zu", room.GetSprites().size());
397 ImGui::Text(
"Buffers");
398 auto& bg1 = room.bg1_buffer().bitmap();
399 auto& bg2 = room.bg2_buffer().bitmap();
400 ImGui::Text(
" BG1: %dx%d %s", bg1.width(), bg1.height(),
401 bg1.texture() ?
"(has texture)" :
"(NO TEXTURE)");
402 ImGui::Text(
" BG2: %dx%d %s", bg2.width(), bg2.height(),
403 bg2.texture() ?
"(has texture)" :
"(NO TEXTURE)");
405 ImGui::Text(
"Layers");
407 ImGui::Checkbox(
"BG1 Visible", &layer_settings.bg1_visible);
408 ImGui::Checkbox(
"BG2 Visible", &layer_settings.bg2_visible);
409 ImGui::SliderInt(
"BG2 Type", &layer_settings.bg2_layer_type, 0, 4);
412 ImGui::Text(
"Layout Override");
413 static bool enable_override =
false;
414 ImGui::Checkbox(
"Enable Override", &enable_override);
415 if (enable_override) {
423 ImGui::Text(
"Object Outline Filters");
424 ImGui::Text(
"By Type:");
428 ImGui::Text(
"By Layer:");
439 ImGui::SetNextWindowSize(ImVec2(250, 0), ImGuiCond_FirstUseEver);
441 auto& room = (*rooms_)[room_id];
442 auto& bg1 = room.bg1_buffer().bitmap();
443 auto& bg2 = room.bg2_buffer().bitmap();
445 ImGui::Text(
"BG1 Bitmap");
446 ImGui::Text(
" Size: %dx%d", bg1.width(), bg1.height());
447 ImGui::Text(
" Active: %s", bg1.is_active() ?
"YES" :
"NO");
448 ImGui::Text(
" Texture: 0x%p", bg1.texture());
449 ImGui::Text(
" Modified: %s", bg1.modified() ?
"YES" :
"NO");
452 ImGui::Text(
" Preview:");
453 ImGui::Image((ImTextureID)(intptr_t)bg1.texture(), ImVec2(128, 128));
457 ImGui::Text(
"BG2 Bitmap");
458 ImGui::Text(
" Size: %dx%d", bg2.width(), bg2.height());
459 ImGui::Text(
" Active: %s", bg2.is_active() ?
"YES" :
"NO");
460 ImGui::Text(
" Texture: 0x%p", bg2.texture());
461 ImGui::Text(
" Modified: %s", bg2.modified() ?
"YES" :
"NO");
464 ImGui::Text(
" Preview:");
465 ImGui::Image((ImTextureID)(intptr_t)bg2.texture(), ImVec2(128, 128));
473 ImGui::SetNextWindowSize(ImVec2(200, 0), ImGuiCond_FirstUseEver);
474 if (ImGui::Begin(
"Layer Info", &
show_layer_info_, ImGuiWindowFlags_NoCollapse)) {
479 ImGui::Text(
"Layer Visibility:");
480 ImGui::Text(
" BG1: %s", layer_settings.bg1_visible ?
"VISIBLE" :
"hidden");
481 ImGui::Text(
" BG2: %s", layer_settings.bg2_visible ?
"VISIBLE" :
"hidden");
482 ImGui::Text(
"BG2 Type: %d", layer_settings.bg2_layer_type);
483 const char* bg2_type_names[] = {
"Normal",
"Translucent",
"Addition",
"Dark",
"Off"};
484 ImGui::Text(
" (%s)", bg2_type_names[std::min(layer_settings.bg2_layer_type, 4)]);
490 auto& room = (*rooms_)[room_id];
496 auto& bg1_bitmap = room.bg1_buffer().bitmap();
497 bool needs_render = !bg1_bitmap.is_active() || bg1_bitmap.width() == 0;
500 static int last_rendered_room = -1;
501 static bool has_rendered =
false;
502 if (needs_render && (last_rendered_room != room_id || !has_rendered)) {
503 printf(
"[DungeonCanvasViewer] Loading and rendering graphics for room %d\n", room_id);
505 last_rendered_room = room_id;
510 if (room.GetTileObjects().empty()) {
540 auto& room = (*rooms_)[room_id];
556 auto& room = (*rooms_)[room_id];
557 std::string layer_info = absl::StrFormat(
558 "Room %03X - Objects: %zu, Sprites: %zu\n"
559 "Layers are game concept: Objects exist on different levels\n"
560 "connected by stair objects for player navigation",
561 room_id, room.GetTileObjects().size(), room.GetSprites().size());
580 for (
const auto& sprite : room.
GetSprites()) {
588 if (sprite.layer() == 0) {
589 sprite_color = ImVec4(0.2f, 0.8f, 0.2f, 0.8f);
591 sprite_color = ImVec4(0.2f, 0.2f, 0.8f, 0.8f);
597 canvas_.
DrawRect(canvas_x, canvas_y, 8, 8, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
600 std::string sprite_text;
601 if (sprite.id() >= 0) {
603 std::string full_name = zelda3::kSpriteDefaultNames[sprite.id()];
604 auto space_pos = full_name.find(
' ');
605 if (space_pos != std::string::npos && space_pos < full_name.length() - 1) {
606 std::string sprite_name = full_name.substr(space_pos + 1);
608 if (sprite_name.length() > 8) {
609 sprite_name = sprite_name.substr(0, 8) +
"...";
611 sprite_text = absl::StrFormat(
"%02X\n%s", sprite.id(), sprite_name.c_str());
613 sprite_text = absl::StrFormat(
"%02X", sprite.id());
616 sprite_text = absl::StrFormat(
"%02X", sprite.id());
709 for (
const auto& obj : objects) {
711 bool show_this_type =
true;
712 if (obj.id_ < 0x100) {
714 }
else if (obj.id_ >= 0x100 && obj.id_ < 0x200) {
716 }
else if (obj.id_ >= 0xF00) {
722 bool show_this_layer =
true;
723 if (obj.GetLayerValue() == 0) {
725 }
else if (obj.GetLayerValue() == 1) {
727 }
else if (obj.GetLayerValue() == 2) {
733 if (!show_this_type || !show_this_layer) {
746 int size_h = (obj.size() & 0x0F);
747 int size_v = (obj.size() >> 4) & 0x0F;
750 width = (size_h + 1) * 8;
751 height = (size_v + 1) * 8;
755 width = std::min(width, 512);
756 height = std::min(height, 512);
759 ImVec4 outline_color;
760 if (obj.GetLayerValue() == 0) {
761 outline_color = ImVec4(1.0f, 0.0f, 0.0f, 0.5f);
762 }
else if (obj.GetLayerValue() == 1) {
763 outline_color = ImVec4(0.0f, 1.0f, 0.0f, 0.5f);
765 outline_color = ImVec4(0.0f, 0.0f, 1.0f, 0.5f);
772 std::string label = absl::StrFormat(
"%02X", obj.id_);
833 auto& room = (*rooms_)[room_id];
837 auto& bg1_bitmap = room.bg1_buffer().bitmap();
838 auto& bg2_bitmap = room.bg2_buffer().bitmap();
841 if (layer_settings.bg1_visible && bg1_bitmap.is_active() && bg1_bitmap.width() > 0 && bg1_bitmap.height() > 0) {
842 if (!bg1_bitmap.texture()) {
853 if (bg1_bitmap.texture()) {
856 LOG_DEBUG(
"DungeonCanvasViewer",
"Drawing BG1 bitmap to canvas with texture %p, scale=%.2f", bg1_bitmap.texture(), scale);
859 LOG_DEBUG(
"DungeonCanvasViewer",
"ERROR: BG1 bitmap has no texture!");
864 if (layer_settings.bg2_visible && bg2_bitmap.is_active() && bg2_bitmap.width() > 0 && bg2_bitmap.height() > 0) {
865 if (!bg2_bitmap.texture()) {
876 if (bg2_bitmap.texture()) {
878 const int bg2_alpha_values[] = {255, 191, 127, 64, 0};
879 int alpha_value = bg2_alpha_values[std::min(layer_settings.bg2_layer_type, 4)];
882 LOG_DEBUG(
"DungeonCanvasViewer",
"Drawing BG2 bitmap to canvas with texture %p, alpha=%d, scale=%.2f", bg2_bitmap.texture(), alpha_value, scale);
885 LOG_DEBUG(
"DungeonCanvasViewer",
"ERROR: BG2 bitmap has no texture!");
890 if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0) {
891 LOG_DEBUG(
"DungeonCanvasViewer",
"BG1 bitmap: %dx%d, active=%d, visible=%d, texture=%p",
892 bg1_bitmap.width(), bg1_bitmap.height(), bg1_bitmap.is_active(), layer_settings.bg1_visible, bg1_bitmap.texture());
895 auto& bg1_data = bg1_bitmap.mutable_data();
896 int non_zero_pixels = 0;
897 for (
size_t i = 0; i < bg1_data.size(); i += 100) {
898 if (bg1_data[i] != 0) non_zero_pixels++;
900 LOG_DEBUG(
"DungeonCanvasViewer",
"BG1 bitmap data: %zu pixels, ~%d non-zero samples",
901 bg1_data.size(), non_zero_pixels);
903 if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0) {
904 LOG_DEBUG(
"DungeonCanvasViewer",
"BG2 bitmap: %dx%d, active=%d, visible=%d, layer_type=%d, texture=%p",
905 bg2_bitmap.width(), bg2_bitmap.height(), bg2_bitmap.is_active(), layer_settings.bg2_visible, layer_settings.bg2_layer_type, bg2_bitmap.texture());
908 auto& bg2_data = bg2_bitmap.mutable_data();
909 int non_zero_pixels = 0;
910 for (
size_t i = 0; i < bg2_data.size(); i += 100) {
911 if (bg2_data[i] != 0) non_zero_pixels++;
913 LOG_DEBUG(
"DungeonCanvasViewer",
"BG2 bitmap data: %zu pixels, ~%d non-zero samples",
914 bg2_data.size(), non_zero_pixels);
918 LOG_DEBUG(
"DungeonCanvasViewer",
"Canvas pos: (%.1f, %.1f), Canvas size: (%.1f, %.1f)",
920 LOG_DEBUG(
"DungeonCanvasViewer",
"BG1 bitmap size: %dx%d, BG2 bitmap size: %dx%d",
921 bg1_bitmap.width(), bg1_bitmap.height(), bg2_bitmap.width(), bg2_bitmap.height());