53 if (room_id < 0 || room_id >= 0x128) {
54 ImGui::Text(
"Invalid room ID: %d", room_id);
59 ImGui::Text(
"ROM not loaded");
72 float pixel_x = target_x * 8 * scale;
73 float pixel_y = target_y * 8 * scale;
76 ImVec2 view_size = ImGui::GetWindowSize();
77 float scroll_x = pixel_x - (view_size.x * 0.5f);
78 float scroll_y = pixel_y - (view_size.y * 0.5f);
86 ImGui::SetScrollX(scroll_x);
87 ImGui::SetScrollY(scroll_y);
100 constexpr int kRoomPixelWidth = 512;
101 constexpr int kRoomPixelHeight = 512;
102 constexpr int kDungeonTileSize = 8;
106 frame_opts.
canvas_size = ImVec2(kRoomPixelWidth, kRoomPixelHeight);
117 static int debug_frame_count = 0;
118 if (debug_frame_count++ % 60 == 0) {
120 "Canvas config: size=(%.0f,%.0f) scale=%.2f grid=%.0f",
124 "[DungeonCanvas]",
"Canvas viewport: p0=(%.0f,%.0f) p1=(%.0f,%.0f)",
131 auto& room = (*rooms_)[room_id];
137 if (room.rom() && room.rom()->is_loaded()) {
141 room.LoadRoomGraphics(room.blockset);
142 room.RenderRoomGraphics();
152 auto draw_navigation = [&]() {
158 const int col = room_id % kRoomMatrixCols;
159 const int row = room_id / kRoomMatrixCols;
161 auto room_if_valid = [](
int candidate) -> std::optional<int> {
168 room_if_valid(row > 0 ? room_id - kRoomMatrixCols : -1);
169 const auto south = room_if_valid(
170 row < kRoomMatrixRows - 1 ? room_id + kRoomMatrixCols : -1);
171 const auto west = room_if_valid(col > 0 ? room_id - 1 : -1);
173 room_if_valid(col < kRoomMatrixCols - 1 ? room_id + 1 : -1);
176 auto make_tooltip = [](
const std::optional<int>& target,
177 const char* direction) -> std::string {
178 if (!target.has_value())
181 return absl::StrFormat(
"%s: [%03X] %s", direction, *target, label);
184 auto nav_button = [&](
const char* id, ImGuiDir dir,
185 const std::optional<int>& target,
186 const std::string& tooltip) {
187 const bool enabled = target.has_value();
189 ImGui::BeginDisabled();
190 if (ImGui::ArrowButton(
id, dir) && enabled) {
199 ImGui::EndDisabled();
200 if (enabled && ImGui::IsItemHovered() && !tooltip.empty())
201 ImGui::SetTooltip(
"%s", tooltip.c_str());
208 float button_width = ImGui::GetFrameHeight();
209 float spacing = ImGui::GetStyle().ItemSpacing.x;
213 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + button_width + spacing);
214 nav_button(
"RoomNavNorth", ImGuiDir_Up, north,
215 make_tooltip(north,
"North"));
218 nav_button(
"RoomNavWest", ImGuiDir_Left, west,
219 make_tooltip(west,
"West"));
221 ImGui::Dummy(ImVec2(button_width, 0));
223 nav_button(
"RoomNavEast", ImGuiDir_Right, east,
224 make_tooltip(east,
"East"));
227 ImGui::SetCursorPosX(ImGui::GetCursorPosX() + button_width + spacing);
228 nav_button(
"RoomNavSouth", ImGuiDir_Down, south,
229 make_tooltip(south,
"South"));
240 layer_mgr.ApplyLayerMerging(room.layer_merging());
242 uint8_t blockset_val = room.blockset;
243 uint8_t spriteset_val = room.spriteset;
244 uint8_t palette_val = room.palette;
245 uint8_t floor1_val = room.floor1();
246 uint8_t floor2_val = room.floor2();
247 int effect_val =
static_cast<int>(room.effect());
248 int tag1_val =
static_cast<int>(room.tag1());
249 int tag2_val =
static_cast<int>(room.tag2());
250 uint8_t layout_val = room.layout;
253 const char* effect_names[] = {
260 "Light Torch to See",
265 const char* tag_names[] = {
267 "NW Kill Enemy to Open",
268 "NE Kill Enemy to Open",
269 "SW Kill Enemy to Open",
270 "SE Kill Enemy to Open",
271 "W Kill Enemy to Open",
272 "E Kill Enemy to Open",
273 "N Kill Enemy to Open",
274 "S Kill Enemy to Open",
275 "Clear Quadrant to Open",
276 "Clear Full Tile to Open",
277 "NW Push Block to Open",
278 "NE Push Block to Open",
279 "SW Push Block to Open",
280 "SE Push Block to Open",
281 "W Push Block to Open",
282 "E Push Block to Open",
283 "N Push Block to Open",
284 "S Push Block to Open",
285 "Push Block to Open",
286 "Pull Lever to Open",
287 "Collect Prize to Open",
288 "Hold Switch Open Door",
289 "Toggle Switch to Open",
298 "Push Switch Exploding Wall",
300 "Open Chest (Holes 0)",
303 "Defeat Boss for Prize",
304 "SE Kill Enemy Push Block",
305 "Trigger Switch Chest",
306 "Pull Lever Exploding Wall",
307 "NW Kill Enemy for Chest",
308 "NE Kill Enemy for Chest",
309 "SW Kill Enemy for Chest",
310 "SE Kill Enemy for Chest",
311 "W Kill Enemy for Chest",
312 "E Kill Enemy for Chest",
313 "N Kill Enemy for Chest",
314 "S Kill Enemy for Chest",
315 "Clear Quadrant for Chest",
316 "Clear Full Tile for Chest",
317 "Light Torches to Open",
325 "Open Chest for Holes 8",
326 "Push Block for Chest",
327 "Clear Room for Triforce",
328 "Light Torches for Chest",
332 constexpr int kNumTags = IM_ARRAYSIZE(tag_names);
334 const char* merge_types[] = {
"Off",
"Parallax",
"Dark",
335 "On top",
"Translucent",
"Addition",
336 "Normal",
"Transparent",
"Dark room"};
337 const char* blend_modes[] = {
"Normal",
"Trans",
"Add",
"Dark",
"Off"};
343 constexpr ImGuiTableFlags kPropsTableFlags =
344 ImGuiTableFlags_NoPadOuterX | ImGuiTableFlags_NoBordersInBody;
345 if (ImGui::BeginTable(
"##RoomPropsTable", 2, kPropsTableFlags)) {
346 ImGui::TableSetupColumn(
"NavCol", ImGuiTableColumnFlags_WidthFixed, 90);
347 ImGui::TableSetupColumn(
"PropsCol", ImGuiTableColumnFlags_WidthStretch);
350 ImGui::TableNextRow();
351 ImGui::TableNextColumn();
353 ImGui::TableNextColumn();
358 ImGui::SameLine(0, 2);
363 room.SetBlockset(blockset_val);
364 if (room.rom() && room.rom()->is_loaded())
365 room.RenderRoomGraphics();
367 if (ImGui::IsItemHovered())
368 ImGui::SetTooltip(
"Blockset (0-51)");
371 ImGui::SameLine(0, 2);
376 room.SetPalette(palette_val);
379 if (palette_val < game_data_->paletteset_ids.size() &&
382 if (
auto palette_id_res =
rom_->
ReadWord(0xDEC4B + palette_ptr);
383 palette_id_res.ok()) {
399 if (room.rom() && room.rom()->is_loaded())
400 room.RenderRoomGraphics();
402 if (ImGui::IsItemHovered())
403 ImGui::SetTooltip(
"Palette (0-47)");
406 ImGui::SameLine(0, 2);
411 room.layout = layout_val;
412 room.MarkLayoutDirty();
413 if (room.rom() && room.rom()->is_loaded())
414 room.RenderRoomGraphics();
416 if (ImGui::IsItemHovered())
417 ImGui::SetTooltip(
"Layout (0-7)");
420 ImGui::SameLine(0, 2);
425 room.SetSpriteset(spriteset_val);
426 if (room.rom() && room.rom()->is_loaded())
427 room.RenderRoomGraphics();
429 if (ImGui::IsItemHovered())
430 ImGui::SetTooltip(
"Spriteset (0-8F)");
433 ImGui::TableNextRow();
434 ImGui::TableNextColumn();
436 ImGui::TableNextColumn();
438 ImGui::SameLine(0, 2);
443 room.set_floor1(floor1_val);
444 if (room.rom() && room.rom()->is_loaded())
445 room.RenderRoomGraphics();
447 if (ImGui::IsItemHovered())
448 ImGui::SetTooltip(
"Floor 1 (0-F)");
451 ImGui::SameLine(0, 2);
456 room.set_floor2(floor2_val);
457 if (room.rom() && room.rom()->is_loaded())
458 room.RenderRoomGraphics();
460 if (ImGui::IsItemHovered())
461 ImGui::SetTooltip(
"Floor 2 (0-F)");
464 ImGui::SameLine(0, 2);
465 constexpr int kNumEffects = IM_ARRAYSIZE(effect_names);
468 if (effect_val >= kNumEffects)
469 effect_val = kNumEffects - 1;
470 ImGui::SetNextItemWidth(140);
471 if (ImGui::BeginCombo(
"##Effect", effect_names[effect_val])) {
472 for (
int i = 0; i < kNumEffects; i++) {
473 if (ImGui::Selectable(effect_names[i], effect_val == i)) {
475 if (room.rom() && room.rom()->is_loaded())
476 room.RenderRoomGraphics();
481 if (ImGui::IsItemHovered())
482 ImGui::SetTooltip(
"Effect");
485 ImGui::TableNextRow();
486 ImGui::TableNextColumn();
488 ImGui::TableNextColumn();
490 ImGui::SameLine(0, 2);
491 int tag1_idx = std::clamp(tag1_val, 0, kNumTags - 1);
492 ImGui::SetNextItemWidth(240);
493 if (ImGui::BeginCombo(
"##Tag1", tag_names[tag1_idx])) {
494 for (
int i = 0; i < kNumTags; i++) {
495 if (ImGui::Selectable(tag_names[i], tag1_idx == i)) {
497 if (room.rom() && room.rom()->is_loaded())
498 room.RenderRoomGraphics();
503 if (ImGui::IsItemHovered())
504 ImGui::SetTooltip(
"Tag 1");
507 ImGui::SameLine(0, 2);
508 int tag2_idx = std::clamp(tag2_val, 0, kNumTags - 1);
509 ImGui::SetNextItemWidth(240);
510 if (ImGui::BeginCombo(
"##Tag2", tag_names[tag2_idx])) {
511 for (
int i = 0; i < kNumTags; i++) {
512 if (ImGui::Selectable(tag_names[i], tag2_idx == i)) {
514 if (room.rom() && room.rom()->is_loaded())
515 room.RenderRoomGraphics();
520 if (ImGui::IsItemHovered())
521 ImGui::SetTooltip(
"Tag 2");
524 ImGui::TableNextRow();
525 ImGui::TableNextColumn();
527 ImGui::TableNextColumn();
536 auto mark_layers_dirty = [&]() {
538 auto& r = (*rooms_)[room_id];
539 r.bg1_buffer().bitmap().set_modified(
true);
540 r.bg2_buffer().bitmap().set_modified(
true);
541 r.object_bg1_buffer().bitmap().set_modified(
true);
542 r.object_bg2_buffer().bitmap().set_modified(
true);
543 r.MarkCompositeDirty();
547 if (ImGui::Checkbox(
"BG1##L", &bg1_layout)) {
551 if (ImGui::IsItemHovered()) {
553 "BG1 Layout: Main floor tiles (rendered on top of BG2)");
556 if (ImGui::Checkbox(
"O1##O", &bg1_objects)) {
560 if (ImGui::IsItemHovered()) {
562 "BG1 Objects: Walls, pots, interactive objects (topmost layer)");
565 if (ImGui::Checkbox(
"BG2##L2", &bg2_layout)) {
569 if (ImGui::IsItemHovered()) {
570 ImGui::SetTooltip(
"BG2 Layout: Background floor patterns (behind BG1)");
573 if (ImGui::Checkbox(
"O2##O2", &bg2_objects)) {
577 if (ImGui::IsItemHovered()) {
578 ImGui::SetTooltip(
"BG2 Objects: Background details (behind BG1)");
581 ImGui::SetNextItemWidth(60);
582 int bg2_blend =
static_cast<int>(
584 if (ImGui::Combo(
"##Bld", &bg2_blend, blend_modes,
585 IM_ARRAYSIZE(blend_modes))) {
590 if (ImGui::IsItemHovered()) {
592 "BG2 Blend Mode (color math effect):\n"
593 "- Normal: Opaque pixels\n"
594 "- Translucent: 50% alpha\n"
595 "- Addition: Additive blending\n"
596 "Does not change layer order (BG1 always on top)");
599 ImGui::SetNextItemWidth(70);
600 int merge_val = room.layer_merging().ID;
601 if (ImGui::Combo(
"##Mrg", &merge_val, merge_types,
602 IM_ARRAYSIZE(merge_types))) {
603 room.SetLayerMerging(zelda3::kLayerMergeTypeList[merge_val]);
604 layer_mgr.ApplyLayerMergingPreserveVisibility(room.layer_merging());
605 if (room.rom() && room.rom()->is_loaded())
606 room.RenderRoomGraphics();
608 if (ImGui::IsItemHovered())
609 ImGui::SetTooltip(
"Merge type");
612 ImGui::TableNextRow();
613 ImGui::TableNextColumn();
615 ImGui::TableNextColumn();
618 if (ImGui::RadioButton(
"All",
634 ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.4f, 0.8f, 1.0f, 1.0f));
635 if (ImGui::RadioButton(
"Mask", is_mask_mode))
638 ImGui::PopStyleColor();
639 if (ImGui::IsItemHovered()) {
641 "Mask Selection Mode\n"
642 "Only select BG2/Layer 1 overlay objects (platforms, statues, "
644 "These are the objects that create transparency holes in BG1");
650 if (layer_mgr.AreLayersMerged()) {
670 if (ImGui::IsItemHovered()) {
672 "Open Object Editor panel to select objects for placement");
682 if (ImGui::IsItemHovered()) {
684 "Open Sprite Editor panel to select sprites for placement");
694 if (ImGui::IsItemHovered()) {
695 ImGui::SetTooltip(
"Open Item Editor panel to select items for placement");
702 ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.3f, 0.6f, 0.9f, 1.0f));
709 ImGui::PopStyleColor();
711 if (ImGui::IsItemHovered()) {
712 ImGui::SetTooltip(door_mode ?
"Click to cancel door placement"
713 :
"Click to place doors");
726 auto& room = (*rooms_)[room_id];
730 place_menu.
label =
"Place Entity";
767 layer_menu.
label =
"Layer Visibility";
803 entity_menu.
label =
"Entity Visibility";
821 [&room]() { room.RenderRoomGraphics(); },
"Ctrl+R"));
825 grid_menu.
label =
"Grid Options";
833 grid_menu.
subitems.push_back(toggle_grid_item);
853 debug_menu.
label =
"Debug";
860 debug_menu.
subitems.push_back(room_info_item);
866 debug_menu.
subitems.push_back(texture_info_item);
873 debug_menu.
subitems.push_back(coord_overlay_item);
877 object_bounds_menu.
label =
"Show Object Bounds";
879 object_bounds_menu.
callback = [
this]() {
884 object_bounds_menu.
subitems.push_back(
889 object_bounds_menu.
subitems.push_back(
894 object_bounds_menu.
subitems.push_back(
906 object_bounds_menu.
subitems.push_back(sep);
909 object_bounds_menu.
subitems.push_back(
914 object_bounds_menu.
subitems.push_back(
919 object_bounds_menu.
subitems.push_back(
925 debug_menu.
subitems.push_back(object_bounds_menu);
931 debug_menu.
subitems.push_back(layer_info_item);
937 room.LoadRoomGraphics(room.blockset);
938 room.RenderRoomGraphics();
940 debug_menu.
subitems.push_back(force_reload_item);
945 LOG_DEBUG(
"DungeonDebug",
"=== Room %03X Debug ===", room_id);
946 LOG_DEBUG(
"DungeonDebug",
"Blockset: %d, Palette: %d, Layout: %d",
947 room.blockset, room.palette, room.layout);
948 LOG_DEBUG(
"DungeonDebug",
"Objects: %zu, Sprites: %zu",
949 room.GetTileObjects().size(), room.GetSprites().size());
950 LOG_DEBUG(
"DungeonDebug",
"BG1: %dx%d, BG2: %dx%d",
951 room.bg1_buffer().bitmap().width(),
952 room.bg1_buffer().bitmap().height(),
953 room.bg2_buffer().bitmap().width(),
954 room.bg2_buffer().bitmap().height());
956 debug_menu.
subitems.push_back(log_item);
965 const bool has_selection = !selected.empty();
966 const bool single_selection = selected.size() == 1;
967 const bool has_clipboard = interaction.HasClipboardData();
968 const bool placing_object = interaction.IsObjectLoaded();
970 if (single_selection &&
rooms_) {
971 auto& room = (*rooms_)[room_id];
972 const auto& objects = room.GetTileObjects();
973 if (selected[0] < objects.size()) {
974 const auto& obj = objects[selected[0]];
975 std::string name = GetObjectName(obj.id_);
977 absl::StrFormat(
"Object 0x%02X: %s", obj.id_, name.c_str())));
981 auto enabled_if = [](
bool enabled) {
990 interaction.HandleCopySelected();
991 interaction.HandleDeleteSelected();
999 [&interaction]() { interaction.HandleCopySelected(); },
"Ctrl+C");
1006 interaction.HandleCopySelected();
1007 interaction.HandlePasteObjects();
1015 [&interaction]() { interaction.HandleDeleteSelected(); },
"Del");
1021 [&interaction]() { interaction.HandlePasteObjects(); },
"Ctrl+V");
1027 [&interaction]() { interaction.CancelPlacement(); },
"Esc");
1033 layer_menu.
label =
"Send to Layer";
1039 [&interaction]() { interaction.SendSelectedToLayer(0); },
"1");
1041 layer_menu.
subitems.push_back(layer1_item);
1045 [&interaction]() { interaction.SendSelectedToLayer(1); },
"2");
1047 layer_menu.
subitems.push_back(layer2_item);
1051 [&interaction]() { interaction.SendSelectedToLayer(2); },
"3");
1053 layer_menu.
subitems.push_back(layer3_item);
1059 arrange_menu.
label =
"Arrange";
1065 [&interaction]() { interaction.SendSelectedToFront(); },
1068 arrange_menu.
subitems.push_back(bring_front_item);
1072 [&interaction]() { interaction.SendSelectedToBack(); },
"Ctrl+Shift+[");
1074 arrange_menu.
subitems.push_back(send_back_item);
1078 [&interaction]() { interaction.BringSelectedForward(); },
"Ctrl+]");
1080 arrange_menu.
subitems.push_back(bring_forward_item);
1084 [&interaction]() { interaction.SendSelectedBackward(); },
"Ctrl+[");
1086 arrange_menu.
subitems.push_back(send_backward_item);
1091 const auto& selected_entity = interaction.GetSelectedEntity();
1092 const bool has_entity_selection = interaction.HasEntitySelection();
1094 if (has_entity_selection &&
rooms_) {
1095 auto& room = (*rooms_)[room_id];
1098 std::string entity_info;
1099 switch (selected_entity.type) {
1101 const auto& doors = room.GetDoors();
1102 if (selected_entity.index < doors.size()) {
1103 const auto& door = doors[selected_entity.index];
1104 entity_info = absl::StrFormat(
1111 const auto& sprites = room.GetSprites();
1112 if (selected_entity.index < sprites.size()) {
1113 const auto& sprite = sprites[selected_entity.index];
1114 entity_info = absl::StrFormat(
1121 const auto& items = room.GetPotItems();
1122 if (selected_entity.index < items.size()) {
1123 const auto& item = items[selected_entity.index];
1133 if (!entity_info.empty()) {
1139 [
this, &room, selected_entity]() {
1140 switch (selected_entity.type) {
1141 case EntityType::Door: {
1142 auto& doors = room.GetDoors();
1143 if (selected_entity.index < doors.size()) {
1144 doors.erase(doors.begin() +
1145 static_cast<long>(selected_entity.index));
1150 auto& sprites = room.GetSprites();
1151 if (selected_entity.index < sprites.size()) {
1152 sprites.erase(sprites.begin() +
1153 static_cast<long>(selected_entity.index));
1158 auto& items = room.GetPotItems();
1159 if (selected_entity.index < items.size()) {
1160 items.erase(items.begin() +
1161 static_cast<long>(selected_entity.index));
1181 if (show_room_debug_info_ && rooms_ && rom_->is_loaded()) {
1182 auto& room = (*rooms_)[room_id];
1183 ImGui::SetNextWindowPos(
1184 ImVec2(canvas_.zero_point().x + 10, canvas_.zero_point().y + 10),
1185 ImGuiCond_FirstUseEver);
1186 ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_FirstUseEver);
1187 if (ImGui::Begin(
"Room Debug Info", &show_room_debug_info_,
1188 ImGuiWindowFlags_NoCollapse)) {
1189 ImGui::Text(
"Room: 0x%03X (%d)", room_id, room_id);
1191 ImGui::Text(
"Graphics");
1192 ImGui::Text(
" Blockset: 0x%02X", room.blockset);
1193 ImGui::Text(
" Palette: 0x%02X", room.palette);
1194 ImGui::Text(
" Layout: 0x%02X", room.layout);
1195 ImGui::Text(
" Spriteset: 0x%02X", room.spriteset);
1197 ImGui::Text(
"Content");
1198 ImGui::Text(
" Objects: %zu", room.GetTileObjects().size());
1199 ImGui::Text(
" Sprites: %zu", room.GetSprites().size());
1201 ImGui::Text(
"Buffers");
1202 auto& bg1 = room.bg1_buffer().bitmap();
1203 auto& bg2 = room.bg2_buffer().bitmap();
1204 ImGui::Text(
" BG1: %dx%d %s", bg1.width(), bg1.height(),
1205 bg1.texture() ?
"(has texture)" :
"(NO TEXTURE)");
1206 ImGui::Text(
" BG2: %dx%d %s", bg2.width(), bg2.height(),
1207 bg2.texture() ?
"(has texture)" :
"(NO TEXTURE)");
1209 ImGui::Text(
"Layers (4-way)");
1210 auto& layer_mgr = GetRoomLayerManager(room_id);
1215 if (ImGui::Checkbox(
"BG1 Layout", &bg1l))
1217 if (ImGui::Checkbox(
"BG1 Objects", &bg1o))
1219 if (ImGui::Checkbox(
"BG2 Layout", &bg2l))
1221 if (ImGui::Checkbox(
"BG2 Objects", &bg2o))
1223 int blend =
static_cast<int>(
1225 if (ImGui::SliderInt(
"BG2 Blend", &blend, 0, 4)) {
1233 ImGui::Text(
"Layout Override");
1234 static bool enable_override =
false;
1235 ImGui::Checkbox(
"Enable Override", &enable_override);
1236 if (enable_override) {
1237 ImGui::SliderInt(
"Layout ID", &layout_override_, 0, 7);
1239 layout_override_ = -1;
1242 if (show_object_bounds_) {
1244 ImGui::Text(
"Object Outline Filters");
1245 ImGui::Text(
"By Type:");
1246 ImGui::Checkbox(
"Type 1", &object_outline_toggles_.show_type1_objects);
1247 ImGui::Checkbox(
"Type 2", &object_outline_toggles_.show_type2_objects);
1248 ImGui::Checkbox(
"Type 3", &object_outline_toggles_.show_type3_objects);
1249 ImGui::Text(
"By Layer:");
1250 ImGui::Checkbox(
"Layer 0",
1251 &object_outline_toggles_.show_layer0_objects);
1252 ImGui::Checkbox(
"Layer 1",
1253 &object_outline_toggles_.show_layer1_objects);
1254 ImGui::Checkbox(
"Layer 2",
1255 &object_outline_toggles_.show_layer2_objects);
1261 if (show_texture_debug_ && rooms_ && rom_->is_loaded()) {
1262 ImGui::SetNextWindowPos(
1263 ImVec2(canvas_.zero_point().x + 320, canvas_.zero_point().y + 10),
1264 ImGuiCond_FirstUseEver);
1265 ImGui::SetNextWindowSize(ImVec2(250, 0), ImGuiCond_FirstUseEver);
1266 if (ImGui::Begin(
"Texture Debug", &show_texture_debug_,
1267 ImGuiWindowFlags_NoCollapse)) {
1268 auto& room = (*rooms_)[room_id];
1269 auto& bg1 = room.bg1_buffer().bitmap();
1270 auto& bg2 = room.bg2_buffer().bitmap();
1272 ImGui::Text(
"BG1 Bitmap");
1273 ImGui::Text(
" Size: %dx%d", bg1.width(), bg1.height());
1274 ImGui::Text(
" Active: %s", bg1.is_active() ?
"YES" :
"NO");
1275 ImGui::Text(
" Texture: 0x%p", bg1.texture());
1276 ImGui::Text(
" Modified: %s", bg1.modified() ?
"YES" :
"NO");
1278 if (bg1.texture()) {
1279 ImGui::Text(
" Preview:");
1280 ImGui::Image((ImTextureID)(intptr_t)bg1.texture(), ImVec2(128, 128));
1284 ImGui::Text(
"BG2 Bitmap");
1285 ImGui::Text(
" Size: %dx%d", bg2.width(), bg2.height());
1286 ImGui::Text(
" Active: %s", bg2.is_active() ?
"YES" :
"NO");
1287 ImGui::Text(
" Texture: 0x%p", bg2.texture());
1288 ImGui::Text(
" Modified: %s", bg2.modified() ?
"YES" :
"NO");
1290 if (bg2.texture()) {
1291 ImGui::Text(
" Preview:");
1292 ImGui::Image((ImTextureID)(intptr_t)bg2.texture(), ImVec2(128, 128));
1298 if (show_layer_info_) {
1299 ImGui::SetNextWindowPos(
1300 ImVec2(canvas_.zero_point().x + 580, canvas_.zero_point().y + 10),
1301 ImGuiCond_FirstUseEver);
1302 ImGui::SetNextWindowSize(ImVec2(220, 0), ImGuiCond_FirstUseEver);
1303 if (ImGui::Begin(
"Layer Info", &show_layer_info_,
1304 ImGuiWindowFlags_NoCollapse)) {
1305 ImGui::Text(
"Canvas Scale: %.2f", canvas_.global_scale());
1306 ImGui::Text(
"Canvas Size: %.0fx%.0f", canvas_.width(), canvas_.height());
1307 auto& layer_mgr = GetRoomLayerManager(room_id);
1309 ImGui::Text(
"Layer Visibility (4-way):");
1312 for (
int i = 0; i < 4; ++i) {
1314 bool visible = layer_mgr.IsLayerVisible(layer);
1315 auto blend = layer_mgr.GetLayerBlendMode(layer);
1316 ImGui::Text(
" %s: %s (%s)",
1318 visible ?
"VISIBLE" :
"hidden",
1319 zelda3::RoomLayerManager::GetBlendModeName(blend));
1323 ImGui::Text(
"Draw Order:");
1324 auto draw_order = layer_mgr.GetDrawOrder();
1325 for (
int i = 0; i < 4; ++i) {
1326 ImGui::Text(
" %d: %s", i + 1,
1329 ImGui::Text(
"BG2 On Top: %s", layer_mgr.IsBG2OnTop() ?
"YES" :
"NO");
1334 if (rooms_ && rom_->is_loaded()) {
1335 auto& room = (*rooms_)[room_id];
1338 object_interaction_.SetCurrentRoom(rooms_, room_id);
1341 auto& bg1_bitmap = room.bg1_buffer().bitmap();
1342 bool needs_render = !bg1_bitmap.is_active() || bg1_bitmap.width() == 0;
1345 static int last_rendered_room = -1;
1346 static bool has_rendered =
false;
1347 if (needs_render && (last_rendered_room != room_id || !has_rendered)) {
1349 "[DungeonCanvasViewer] Loading and rendering graphics for room %d\n",
1351 (void)LoadAndRenderRoomGraphics(room_id);
1352 last_rendered_room = room_id;
1353 has_rendered =
true;
1357 if (room.GetTileObjects().empty()) {
1362 if (room.GetSprites().empty()) {
1367 if (room.GetPotItems().empty()) {
1368 room.LoadPotItems();
1374 if (rom_ && rom_->is_loaded()) {
1381 DrawRoomBackgroundLayers(room_id);
1385 if (object_interaction_.IsMaskModeActive()) {
1386 DrawMaskHighlights(canvas_rt, room);
1391 RenderEntityOverlay(canvas_rt, room);
1394 if (object_interaction_enabled_) {
1395 object_interaction_.HandleCanvasMouseInput();
1396 object_interaction_.CheckForObjectSelection();
1397 object_interaction_.DrawSelectBox();
1399 .DrawSelectionHighlights();
1401 .DrawEntitySelectionHighlights();
1402 object_interaction_.DrawGhostPreview();
1408 if (rooms_ && rom_->is_loaded()) {
1409 auto& room = (*rooms_)[room_id];
1415 if (show_object_bounds_) {
1416 DrawObjectPositionOutlines(canvas_rt, room);
1421 if (show_coordinate_overlay_ && canvas_.IsMouseHovering()) {
1422 ImVec2 mouse_pos = ImGui::GetMousePos();
1423 ImVec2 canvas_pos = canvas_.zero_point();
1424 float scale = canvas_.global_scale();
1429 int canvas_x =
static_cast<int>((mouse_pos.x - canvas_pos.x) / scale);
1430 int canvas_y =
static_cast<int>((mouse_pos.y - canvas_pos.y) / scale);
1433 if (canvas_x >= 0 && canvas_x < kRoomPixelWidth && canvas_y >= 0 &&
1434 canvas_y < kRoomPixelHeight) {
1436 int tile_x = canvas_x / kDungeonTileSize;
1437 int tile_y = canvas_y / kDungeonTileSize;
1440 auto [camera_x, camera_y] =
1448 ImVec2 overlay_pos = ImVec2(mouse_pos.x + 15, mouse_pos.y + 15);
1451 std::string coord_text = absl::StrFormat(
1454 "Camera: ($%04X, $%04X)\n"
1456 tile_x, tile_y, canvas_x, canvas_y, camera_x, camera_y, sprite_x,
1460 ImVec2 text_size = ImGui::CalcTextSize(coord_text.c_str());
1461 ImVec2 box_min = ImVec2(overlay_pos.x - 4, overlay_pos.y - 2);
1462 ImVec2 box_max = ImVec2(overlay_pos.x + text_size.x + 8,
1463 overlay_pos.y + text_size.y + 4);
1465 ImDrawList* draw_list = ImGui::GetWindowDrawList();
1466 draw_list->AddRectFilled(box_min, box_max, IM_COL32(0, 0, 0, 200), 4.0f);
1467 draw_list->AddRect(box_min, box_max, IM_COL32(100, 100, 100, 255), 4.0f);
1468 draw_list->AddText(overlay_pos, IM_COL32(255, 255, 255, 255),
1469 coord_text.c_str());