yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
dungeon_canvas_debug.cc
Go to the documentation of this file.
2#include "util/i18n/tr.h"
3
4#include <algorithm>
5#include <cstdio>
6#include <string>
7
8#include "absl/strings/str_format.h"
13#include "app/gui/core/color.h"
16#include "dungeon_coordinates.h"
17#include "imgui/imgui.h"
19
20namespace yaze::editor {
21
22namespace {
23
24constexpr int kThievesTownEastAtticRoomId = 0x65;
25
26} // namespace
27
29 if (!rooms_ || !rom_ || !rom_->is_loaded()) {
30 return;
31 }
32
33 auto& room = (*rooms_)[room_id];
35 DrawRoomDebugWindow(room, room_id);
36 }
38 DrawTextureDebugWindow(room, room_id);
39 }
40 if (show_layer_info_) {
41 DrawLayerInfoWindow(room_id);
42 }
43}
44
46 ImGui::SetNextWindowPos(
47 ImVec2(canvas_.zero_point().x + 10, canvas_.zero_point().y + 10),
48 ImGuiCond_FirstUseEver);
49 ImGui::SetNextWindowSize(ImVec2(300, 0), ImGuiCond_FirstUseEver);
50 if (ImGui::Begin("Room Debug Info", &show_room_debug_info_,
51 ImGuiWindowFlags_NoCollapse)) {
52 ImGui::Text(tr("Room: 0x%03X (%d)"), room_id, room_id);
53 ImGui::Separator();
54 ImGui::Text(tr("Graphics"));
55 ImGui::Text(tr(" Blockset: 0x%02X"), room.blockset());
56 ImGui::Text(tr(" Palette: 0x%02X"), room.palette());
57 ImGui::Text(tr(" Layout: 0x%02X"), room.layout_id());
58 ImGui::Text(tr(" Spriteset: 0x%02X"), room.spriteset());
59 ImGui::Separator();
60 ImGui::Text(tr("Content"));
61 ImGui::Text(tr(" Objects: %zu"), room.GetTileObjects().size());
62 ImGui::Text(tr(" Sprites: %zu"), room.GetSprites().size());
63 ImGui::Separator();
64 ImGui::Text(tr("Buffers"));
65 auto& bg1 = room.bg1_buffer().bitmap();
66 auto& bg2 = room.bg2_buffer().bitmap();
67 ImGui::Text(tr(" BG1: %dx%d %s"), bg1.width(), bg1.height(),
68 bg1.texture() ? "(has texture)" : "(NO TEXTURE)");
69 ImGui::Text(tr(" BG2: %dx%d %s"), bg2.width(), bg2.height(),
70 bg2.texture() ? "(has texture)" : "(NO TEXTURE)");
71 ImGui::Separator();
72 ImGui::Text(tr("Layers (4-way)"));
73 auto& layer_mgr = GetRoomLayerManager(room_id);
74 bool bg1l = layer_mgr.IsLayerVisible(zelda3::LayerType::BG1_Layout);
75 bool bg1o = layer_mgr.IsLayerVisible(zelda3::LayerType::BG1_Objects);
76 bool bg2l = layer_mgr.IsLayerVisible(zelda3::LayerType::BG2_Layout);
77 bool bg2o = layer_mgr.IsLayerVisible(zelda3::LayerType::BG2_Objects);
78 if (ImGui::Checkbox(tr("BG1 Layout"), &bg1l)) {
79 layer_mgr.SetLayerVisible(zelda3::LayerType::BG1_Layout, bg1l);
80 }
81 if (ImGui::Checkbox(tr("BG1 Objects"), &bg1o)) {
82 layer_mgr.SetLayerVisible(zelda3::LayerType::BG1_Objects, bg1o);
83 }
84 if (ImGui::Checkbox(tr("BG2 Layout"), &bg2l)) {
85 layer_mgr.SetLayerVisible(zelda3::LayerType::BG2_Layout, bg2l);
86 }
87 if (ImGui::Checkbox(tr("BG2 Objects"), &bg2o)) {
88 layer_mgr.SetLayerVisible(zelda3::LayerType::BG2_Objects, bg2o);
89 }
90 int blend = static_cast<int>(
91 layer_mgr.GetLayerBlendMode(zelda3::LayerType::BG2_Layout));
92 if (ImGui::SliderInt(tr("BG2 Blend"), &blend, 0, 4)) {
93 layer_mgr.SetLayerBlendMode(zelda3::LayerType::BG2_Layout,
94 static_cast<zelda3::LayerBlendMode>(blend));
95 layer_mgr.SetLayerBlendMode(zelda3::LayerType::BG2_Objects,
96 static_cast<zelda3::LayerBlendMode>(blend));
97 }
98
99 ImGui::Separator();
100 ImGui::Text(tr("Layout Override"));
101 static bool enable_override = false;
102 ImGui::Checkbox(tr("Enable Override"), &enable_override);
103 if (enable_override) {
104 ImGui::SliderInt(tr("Layout ID"), &layout_override_, 0, 7);
105 } else {
106 layout_override_ = -1;
107 }
108
109 ImGui::Separator();
110 ImGui::Text(tr("Preview State"));
111 if (auto* editor_state =
112 dynamic_cast<zelda3::EditorDungeonState*>(room.GetDungeonState());
113 editor_state != nullptr) {
114 bool preview_state_changed = false;
115
116 bool water_face_active = editor_state->IsWaterFaceActive(room_id);
117 if (ImGui::Checkbox(tr("Water Face Active"), &water_face_active)) {
118 editor_state->SetWaterFaceActive(room_id, water_face_active);
119 preview_state_changed = true;
120 }
121
122 bool dam_floodgate_open = editor_state->IsDamFloodgateOpen(room_id);
123 if (ImGui::Checkbox(tr("Dam Floodgate Open"), &dam_floodgate_open)) {
124 editor_state->SetDamFloodgateOpen(room_id, dam_floodgate_open);
125 preview_state_changed = true;
126 }
127
128 bool wall_moved = editor_state->IsWallMoved(room_id);
129 if (ImGui::Checkbox(tr("Moving Wall Shifted"), &wall_moved)) {
130 editor_state->SetWallMoved(room_id, wall_moved);
131 preview_state_changed = true;
132 }
133
134 bool floor_bombed = editor_state->IsFloorBombable(room_id);
135 if (ImGui::Checkbox(tr("Bombed Floor Open"), &floor_bombed)) {
136 editor_state->SetFloorBombable(room_id, floor_bombed);
137 preview_state_changed = true;
138 }
139
140 // FF1 in Blind's room reads room 0x065's persisted attic-floor flag,
141 // not the current room's flag.
142 bool blind_light_active =
143 editor_state->IsFloorBombable(kThievesTownEastAtticRoomId);
144 if (ImGui::Checkbox(tr("East Attic Open / Blind Light"),
145 &blind_light_active)) {
146 editor_state->SetFloorBombable(kThievesTownEastAtticRoomId,
147 blind_light_active);
148 preview_state_changed = true;
149 }
150 if (ImGui::IsItemHovered()) {
151 ImGui::SetTooltip(tr(
152 "Object FF1 uses the persisted bombed-floor flag for room 0x065, "
153 "even when previewed in Blind's room."));
154 }
155
156 bool rupee_floor_cleared = editor_state->IsRupeeFloorCleared(room_id);
157 if (ImGui::Checkbox(tr("Rupee Floor Cleared"), &rupee_floor_cleared)) {
158 editor_state->SetRupeeFloorCleared(room_id, rupee_floor_cleared);
159 preview_state_changed = true;
160 }
161
162 if (ImGui::Button(tr("Reset Preview State"))) {
163 editor_state->Reset();
164 preview_state_changed = true;
165 }
166
167 if (preview_state_changed) {
169 }
170 } else {
171 ImGui::TextDisabled(
172 tr("Preview state controls require EditorDungeonState."));
173 }
174
176 ImGui::Separator();
177 ImGui::Text(tr("Object Outline Filters"));
178 ImGui::Text(tr("By Type:"));
179 ImGui::Checkbox(tr("Type 1"),
181 ImGui::Checkbox(tr("Type 2"),
183 ImGui::Checkbox(tr("Type 3"),
185 ImGui::Text(tr("By Stored Placement:"));
186 ImGui::Checkbox(tr("Primary / Upper layer (BG1)"),
188 ImGui::Checkbox(tr("BG2 overlay / Lower layer (BG2)"),
190 ImGui::Checkbox(tr("BG1 overlay (room stream only)"),
192 }
193 }
194 ImGui::End();
195}
196
198 int room_id) {
199 ImGui::SetNextWindowPos(
200 ImVec2(canvas_.zero_point().x + 320, canvas_.zero_point().y + 10),
201 ImGuiCond_FirstUseEver);
202 ImGui::SetNextWindowSize(ImVec2(250, 0), ImGuiCond_FirstUseEver);
203 if (ImGui::Begin("Texture Debug", &show_texture_debug_,
204 ImGuiWindowFlags_NoCollapse)) {
205 auto& bg1 = room.bg1_buffer().bitmap();
206 auto& bg2 = room.bg2_buffer().bitmap();
207
208 auto ensure_bitmap_texture = [this](gfx::Bitmap& bitmap) {
209 if (!renderer_ || !bitmap.is_active() || bitmap.width() <= 0) {
210 return;
211 }
212 if (!bitmap.texture()) {
215 } else if (bitmap.modified()) {
218 }
219 };
220
221 ensure_bitmap_texture(bg1);
222 ensure_bitmap_texture(bg2);
223 if (renderer_) {
225 }
226
227 ImGui::Text(tr("BG1 Bitmap"));
228 ImGui::Text(tr(" Size: %dx%d"), bg1.width(), bg1.height());
229 ImGui::Text(tr(" Active: %s"), bg1.is_active() ? "YES" : "NO");
230 ImGui::Text(tr(" Texture: 0x%p"), bg1.texture());
231 ImGui::Text(tr(" Modified: %s"), bg1.modified() ? "YES" : "NO");
232
233 if (bg1.texture()) {
234 ImGui::Text(tr(" Preview:"));
235 ImGui::Image((ImTextureID)(intptr_t)bg1.texture(), ImVec2(128, 128));
236 }
237
238 ImGui::Separator();
239 ImGui::Text(tr("BG2 Bitmap"));
240 ImGui::Text(tr(" Size: %dx%d"), bg2.width(), bg2.height());
241 ImGui::Text(tr(" Active: %s"), bg2.is_active() ? "YES" : "NO");
242 ImGui::Text(tr(" Texture: 0x%p"), bg2.texture());
243 ImGui::Text(tr(" Modified: %s"), bg2.modified() ? "YES" : "NO");
244
245 if (bg2.texture()) {
246 ImGui::Text(tr(" Preview:"));
247 ImGui::Image((ImTextureID)(intptr_t)bg2.texture(), ImVec2(128, 128));
248 }
249 }
250 ImGui::End();
251}
252
254 ImGui::SetNextWindowPos(
255 ImVec2(canvas_.zero_point().x + 580, canvas_.zero_point().y + 10),
256 ImGuiCond_FirstUseEver);
257 ImGui::SetNextWindowSize(ImVec2(220, 0), ImGuiCond_FirstUseEver);
258 if (ImGui::Begin("Layer Info", &show_layer_info_,
259 ImGuiWindowFlags_NoCollapse)) {
260 ImGui::Text(tr("Canvas Scale: %.2f"), canvas_.global_scale());
261 ImGui::Text(tr("Canvas Size: %.0fx%.0f"), canvas_.width(),
262 canvas_.height());
263 auto& layer_mgr = GetRoomLayerManager(room_id);
264 ImGui::Separator();
265 ImGui::Text(tr("Layer Visibility (4-way):"));
266
267 for (int i = 0; i < 4; ++i) {
268 auto layer = static_cast<zelda3::LayerType>(i);
269 bool visible = layer_mgr.IsLayerVisible(layer);
270 auto blend = layer_mgr.GetLayerBlendMode(layer);
271 ImGui::Text(" %s: %s (%s)",
273 visible ? "VISIBLE" : "hidden",
275 }
276
277 ImGui::Separator();
278 ImGui::Text(tr("Draw Order:"));
279 auto draw_order = layer_mgr.GetDrawOrder();
280 for (int i = 0; i < 4; ++i) {
281 ImGui::Text(" %d: %s", i + 1,
283 }
284 ImGui::Text(tr("BG2 On Top: %s"), layer_mgr.IsBG2OnTop() ? "YES" : "NO");
285 }
286 ImGui::End();
287}
288
290 zelda3::Room& room,
291 int room_id) {
292 const DungeonCanvasTransform transform(rt.canvas_p0, rt.scrolling, rt.scale);
293 const ImVec2 room_origin = transform.room_origin_screen();
294 const float scale = transform.scale();
295
298 }
299
302 ImGui::GetWindowDrawList(), room_origin, scale,
306 }
307
310 ImGui::GetWindowDrawList(), room_origin, scale, room);
311 }
312
314 DungeonRenderingHelpers::DrawWaterFillOverlay(ImGui::GetWindowDrawList(),
315 room_origin, scale, room);
316 }
317
320 ImGui::GetWindowDrawList(), room_origin, scale, room);
321 }
322
325 ImGui::GetWindowDrawList(), room_origin, scale, room,
327 }
328
331 ImGui::GetWindowDrawList(), room_origin, scale, room,
333 }
334
337 ImGui::GetWindowDrawList(), room_origin, scale,
339 }
340
342 ImDrawList* draw_list = ImGui::GetWindowDrawList();
343 const ImVec2 canvas_pos = room_origin;
344 const ImVec4 info = gui::GetInfoColor();
345 const ImU32 fill_color =
346 ImGui::GetColorU32(ImVec4(info.x, info.y, info.z, 0.25f));
347 const ImU32 border_color =
348 ImGui::GetColorU32(ImVec4(info.x, info.y, info.z, 0.8f));
349 const ImU32 text_bg_color = ImGui::GetColorU32(ImVec4(0, 0, 0, 0.6f));
350
351 auto is_custom = [](int id) {
352 return id == 0x31 || id == 0x32;
353 };
354
355 for (const auto& obj : room.GetTileObjects()) {
356 if (!is_custom(static_cast<int>(obj.id_))) {
357 continue;
358 }
359
360 const float px = static_cast<float>(obj.x()) * 8.0f * scale;
361 const float py = static_cast<float>(obj.y()) * 8.0f * scale;
362 const float box_w = 16.0f * scale;
363 const float box_h = 16.0f * scale;
364 const ImVec2 p0(canvas_pos.x + px, canvas_pos.y + py);
365 const ImVec2 p1(p0.x + box_w, p0.y + box_h);
366
367 draw_list->AddRectFilled(p0, p1, fill_color, 2.0f);
368 draw_list->AddRect(p0, p1, border_color, 2.0f, 0, 1.5f);
369
370 char label[32];
371 std::snprintf(label, sizeof(label), "0x%02X s%d",
372 static_cast<int>(obj.id_),
373 static_cast<int>(obj.size_ & 0x1F));
374 const ImVec2 text_sz = ImGui::CalcTextSize(label);
375 const ImVec2 tp(p0.x + 1.0f, p0.y - text_sz.y - 1.0f);
376 draw_list->AddRectFilled(
377 tp, ImVec2(tp.x + text_sz.x + 2.0f, tp.y + text_sz.y), text_bg_color,
378 2.0f);
379 draw_list->AddText(tp, border_color, label);
380 }
381 }
382
384 const bool show_tracks =
386 const auto& tracks = minecart_track_panel_->GetTracks();
387 if (show_tracks && !tracks.empty()) {
388 ImDrawList* draw_list = ImGui::GetWindowDrawList();
389 const ImVec2 canvas_pos = room_origin;
390 const auto& theme = AgentUI::GetTheme();
391 const int active_track =
394 : -1;
395
396 for (const auto& track : tracks) {
398 static_cast<uint16_t>(track.start_x),
399 static_cast<uint16_t>(track.start_y));
400 if (local.room_id != room_id) {
401 continue;
402 }
403
404 ImVec4 marker_color = theme.selection_primary;
405 if (track.id == active_track) {
406 marker_color = theme.status_warning;
407 }
408
409 const float px = static_cast<float>(local.local_pixel_x) * scale;
410 const float py = static_cast<float>(local.local_pixel_y) * scale;
411 ImVec2 center(canvas_pos.x + px, canvas_pos.y + py);
412 const float radius = 6.0f * scale;
413
414 draw_list->AddCircleFilled(center, radius,
415 ImGui::GetColorU32(marker_color));
416 draw_list->AddCircle(center, radius + 2.0f,
417 ImGui::GetColorU32(ImVec4(0, 0, 0, 0.6f)), 0,
418 2.0f);
419
420 std::string label = absl::StrFormat("T%d", track.id);
421 draw_list->AddText(
422 ImVec2(center.x + 8.0f * scale, center.y - 6.0f * scale),
423 ImGui::GetColorU32(theme.text_primary), label.c_str());
424 }
425 }
426 }
427}
428
431 return;
432 }
433
434 // Plain locals (not structured bindings): the DrawCanvasHUD lambda below
435 // captures these, which is ill-formed on clang<16 / gcc<8 (C++17).
436 const DungeonCanvasTransform transform(
438 const std::pair<int, int> tile_coords = transform.ScreenToRoomTiles(
439 ImGui::GetMousePos(), dungeon_coords::kTileSize);
440 const int tile_x = tile_coords.first;
441 const int tile_y = tile_coords.second;
442 if (tile_x < 0 || tile_x >= 64 || tile_y < 0 || tile_y >= 64) {
443 return;
444 }
445
446 const int canvas_x = tile_x * 8;
447 const int canvas_y = tile_y * 8;
448 const std::pair<uint16_t, uint16_t> camera_coords =
449 dungeon_coords::TileToCameraCoords(room_id, tile_x, tile_y);
450 const uint16_t camera_x = camera_coords.first;
451 const uint16_t camera_y = camera_coords.second;
452 const int sprite_x = canvas_x / dungeon_coords::kSpriteTileSize;
453 const int sprite_y = canvas_y / dungeon_coords::kSpriteTileSize;
454
455 const ImVec2 mouse_pos = ImGui::GetMousePos();
456 const ImVec2 overlay_pos(mouse_pos.x + 15, mouse_pos.y + 15);
457
458 gui::DrawCanvasHUD("##CoordHUD", overlay_pos, ImVec2(0, 0), [&]() {
459 ImGui::Text(tr("Tile: (%d, %d)"), tile_x, tile_y);
460 ImGui::Text(tr("Pixel: (%d, %d)"), canvas_x, canvas_y);
461 ImGui::Text(tr("Camera: ($%04X, $%04X)"), camera_x, camera_y);
462 ImGui::Text(tr("Sprite: (%d, %d)"), sprite_x, sprite_y);
463 });
464}
465
466} // namespace yaze::editor
bool is_loaded() const
Definition rom.h:144
std::pair< int, int > ScreenToRoomTiles(ImVec2 screen, int tile_size) const
const DungeonRenderingHelpers::CollisionOverlayCache & GetCollisionOverlayCache(int room_id)
void DrawTextureDebugWindow(zelda3::Room &room, int room_id)
void DrawRoomDebugWindow(zelda3::Room &room, int room_id)
zelda3::RoomLayerManager & GetRoomLayerManager(int room_id)
DungeonRenderingHelpers::TrackCollisionConfig track_collision_config_
void DrawObjectPositionOutlines(const gui::CanvasRuntime &rt, const zelda3::Room &room)
MinecartTrackEditorPanel * minecart_track_panel_
void DrawRoomCanvasOverlays(const gui::CanvasRuntime &rt, zelda3::Room &room, int room_id)
static void DrawCustomCollisionOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const zelda3::Room &room)
static void DrawTrackGapOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const zelda3::Room &room, const CollisionOverlayCache &cache)
static void DrawTrackCollisionOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const CollisionOverlayCache &cache, const TrackCollisionConfig &config, bool direction_map_enabled, const std::vector< uint16_t > &track_tile_order, const std::vector< uint16_t > &switch_tile_order, bool show_legend)
static void DrawCameraQuadrantOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const zelda3::Room &room)
static void DrawTrackRouteOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const CollisionOverlayCache &cache)
static void DrawMinecartSpriteOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const zelda3::Room &room, const std::bitset< 256 > &minecart_sprite_ids, const TrackCollisionConfig &config)
static void DrawWaterFillOverlay(ImDrawList *draw_list, const ImVec2 &canvas_pos, float scale, const zelda3::Room &room)
const std::vector< MinecartTrack > & GetTracks()
void QueueTextureCommand(TextureCommandType type, Bitmap *bitmap)
Definition arena.cc:36
void ProcessTextureQueue(IRenderer *renderer)
Definition arena.cc:116
static Arena & Get()
Definition arena.cc:21
Represents a bitmap image optimized for SNES ROM hacking.
Definition bitmap.h:67
auto height() const
Definition canvas.h:401
auto global_scale() const
Definition canvas.h:397
auto width() const
Definition canvas.h:400
auto zero_point() const
Definition canvas.h:348
bool IsMouseHovering() const
Definition canvas.h:338
auto scrolling() const
Definition canvas.h:350
Editor implementation of DungeonState.
static const char * GetBlendModeName(LayerBlendMode mode)
Get blend mode name.
static const char * GetLayerName(LayerType layer)
Get human-readable name for layer type.
uint8_t blockset() const
Definition room.h:902
void ReloadGraphics(std::optional< uint8_t > entrance_blockset=std::nullopt)
Definition room.cc:839
uint8_t palette() const
Definition room.h:905
uint8_t spriteset() const
Definition room.h:904
const std::vector< zelda3::Sprite > & GetSprites() const
Definition room.h:253
auto & bg1_buffer()
Definition room.h:975
const std::vector< RoomObject > & GetTileObjects() const
Definition room.h:382
DungeonState * GetDungeonState()
Definition room.h:992
uint8_t layout_id() const
Definition room.h:912
auto & bg2_buffer()
Definition room.h:976
int id() const
Definition room.h:899
const AgentUITheme & GetTheme()
std::pair< uint16_t, uint16_t > TileToCameraCoords(int room_id, int tile_x, int tile_y)
Calculate camera coordinates from room and tile position.
CameraToLocalResult CameraToLocalCoords(uint16_t camera_x, uint16_t camera_y)
Editors are the view controllers for the application.
void DrawCanvasHUD(const char *label, const ImVec2 &pos, const ImVec2 &size, std::function< void()> draw_content)
Draw a stylized Heads-Up Display (HUD) for canvas status.
ImVec4 GetInfoColor()
Definition ui_helpers.cc:64
LayerBlendMode
Layer blend modes for compositing.
LayerType
Layer types for the 4-way visibility system.