feat: Enhance DungeonCanvasViewer with Object Outline Filters and Context Menu Improvements

- Added a sub-menu for toggling visibility of object outlines based on type and layer, allowing for more granular control over displayed objects in the dungeon canvas.
- Implemented checkboxes for filtering object outlines by type (Type 1, Type 2, Type 3) and layer (Layer 0, Layer 1, Layer 2) in the debug menu.
- Updated the drawing logic to respect the new filtering options, ensuring only the selected objects are rendered on the canvas.
- Improved the visibility of object ID labels by making them smaller and less obtrusive, enhancing the overall clarity of the canvas display.
This commit is contained in:
scawful
2025-10-10 01:11:39 -04:00
parent 9f0b503ada
commit 6f3c9ba81b
6 changed files with 148 additions and 36 deletions

View File

@@ -283,13 +283,60 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
}
});
// Show object bounds
debug_menu.subitems.push_back({
ICON_MD_CROP_SQUARE " Show Object Bounds",
// Show object bounds with sub-menu for categories
gui::Canvas::ContextMenuItem object_bounds_menu;
object_bounds_menu.label = ICON_MD_CROP_SQUARE " Show Object Bounds";
object_bounds_menu.callback = [this]() {
show_object_bounds_ = !show_object_bounds_;
};
// Sub-menu for filtering by type
object_bounds_menu.subitems.push_back({
"Type 1 (0x00-0xFF)",
[this]() {
show_object_bounds_ = !show_object_bounds_;
object_outline_toggles_.show_type1_objects = !object_outline_toggles_.show_type1_objects;
}
});
object_bounds_menu.subitems.push_back({
"Type 2 (0x100-0x1FF)",
[this]() {
object_outline_toggles_.show_type2_objects = !object_outline_toggles_.show_type2_objects;
}
});
object_bounds_menu.subitems.push_back({
"Type 3 (0xF00-0xFFF)",
[this]() {
object_outline_toggles_.show_type3_objects = !object_outline_toggles_.show_type3_objects;
}
});
// Separator
gui::Canvas::ContextMenuItem sep;
sep.label = "---";
sep.enabled_condition = []() { return false; };
object_bounds_menu.subitems.push_back(sep);
// Sub-menu for filtering by layer
object_bounds_menu.subitems.push_back({
"Layer 0 (BG1)",
[this]() {
object_outline_toggles_.show_layer0_objects = !object_outline_toggles_.show_layer0_objects;
}
});
object_bounds_menu.subitems.push_back({
"Layer 1 (BG2)",
[this]() {
object_outline_toggles_.show_layer1_objects = !object_outline_toggles_.show_layer1_objects;
}
});
object_bounds_menu.subitems.push_back({
"Layer 2 (BG3)",
[this]() {
object_outline_toggles_.show_layer2_objects = !object_outline_toggles_.show_layer2_objects;
}
});
debug_menu.subitems.push_back(object_bounds_menu);
// Show layer info
debug_menu.subitems.push_back({
@@ -360,6 +407,19 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
ImGui::Checkbox("BG1 Visible", &layer_settings.bg1_visible);
ImGui::Checkbox("BG2 Visible", &layer_settings.bg2_visible);
ImGui::SliderInt("BG2 Type", &layer_settings.bg2_layer_type, 0, 4);
if (show_object_bounds_) {
ImGui::Separator();
ImGui::Text("Object Outline Filters");
ImGui::Text("By Type:");
ImGui::Checkbox("Type 1", &object_outline_toggles_.show_type1_objects);
ImGui::Checkbox("Type 2", &object_outline_toggles_.show_type2_objects);
ImGui::Checkbox("Type 3", &object_outline_toggles_.show_type3_objects);
ImGui::Text("By Layer:");
ImGui::Checkbox("Layer 0", &object_outline_toggles_.show_layer0_objects);
ImGui::Checkbox("Layer 1", &object_outline_toggles_.show_layer1_objects);
ImGui::Checkbox("Layer 2", &object_outline_toggles_.show_layer2_objects);
}
}
ImGui::End();
}
@@ -694,6 +754,33 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) {
const auto& objects = room.GetTileObjects();
for (const auto& obj : objects) {
// Filter by object type (default to true if unknown type)
bool show_this_type = true; // Default to showing
if (obj.id_ < 0x100) {
show_this_type = object_outline_toggles_.show_type1_objects;
} else if (obj.id_ >= 0x100 && obj.id_ < 0x200) {
show_this_type = object_outline_toggles_.show_type2_objects;
} else if (obj.id_ >= 0xF00) {
show_this_type = object_outline_toggles_.show_type3_objects;
}
// else: unknown type, use default (true)
// Filter by layer (default to true if unknown layer)
bool show_this_layer = true; // Default to showing
if (obj.GetLayerValue() == 0) {
show_this_layer = object_outline_toggles_.show_layer0_objects;
} else if (obj.GetLayerValue() == 1) {
show_this_layer = object_outline_toggles_.show_layer1_objects;
} else if (obj.GetLayerValue() == 2) {
show_this_layer = object_outline_toggles_.show_layer2_objects;
}
// else: unknown layer, use default (true)
// Skip if filtered out
if (!show_this_type || !show_this_layer) {
continue;
}
// Convert object position (tile coordinates) to canvas pixel coordinates (UNSCALED)
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(obj.x(), obj.y());
@@ -718,24 +805,19 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) {
// Color-code by layer
ImVec4 outline_color;
if (obj.GetLayerValue() == 0) {
outline_color = ImVec4(1.0f, 0.0f, 0.0f, 0.7f); // Red for layer 0
outline_color = ImVec4(1.0f, 0.0f, 0.0f, 0.5f); // Red for layer 0
} else if (obj.GetLayerValue() == 1) {
outline_color = ImVec4(0.0f, 1.0f, 0.0f, 0.7f); // Green for layer 1
outline_color = ImVec4(0.0f, 1.0f, 0.0f, 0.5f); // Green for layer 1
} else {
outline_color = ImVec4(0.0f, 0.0f, 1.0f, 0.7f); // Blue for layer 2
outline_color = ImVec4(0.0f, 0.0f, 1.0f, 0.5f); // Blue for layer 2
}
// Draw outline rectangle
canvas_.DrawRect(canvas_x, canvas_y, width, height, outline_color);
// Draw object ID label
std::string label = absl::StrFormat("0x%02X", obj.id_);
canvas_.DrawText(label, canvas_x + 2, canvas_y + 2);
}
// Log object count
if (!objects.empty()) {
LOG_DEBUG("[DrawObjectPositionOutlines]", "Drew %zu object outlines", objects.size());
// Draw object ID label (smaller, less obtrusive)
std::string label = absl::StrFormat("%02X", obj.id_);
canvas_.DrawText(label, canvas_x + 1, canvas_y + 1);
}
}

View File

@@ -160,6 +160,17 @@ class DungeonCanvasViewer {
bool show_texture_debug_ = false;
bool show_object_bounds_ = false;
bool show_layer_info_ = false;
// Object outline category toggles
struct ObjectOutlineToggles {
bool show_type1_objects = true; // Standard objects (0x00-0xFF)
bool show_type2_objects = true; // Extended objects (0x100-0x1FF)
bool show_type3_objects = true; // Special objects (0xF00-0xFFF)
bool show_layer0_objects = true; // Layer 0 (BG1)
bool show_layer1_objects = true; // Layer 1 (BG2)
bool show_layer2_objects = true; // Layer 2 (BG3)
};
ObjectOutlineToggles object_outline_toggles_;
};
} // namespace editor

View File

@@ -323,9 +323,14 @@ absl::StatusOr<PaletteGroup> CreatePaletteGroupFromLargePalette(
PaletteGroup palette_group;
for (int i = 0; i < palette.size(); i += num_colors) {
SnesPalette new_palette;
if (i + num_colors < palette.size()) {
if (i + num_colors <= palette.size()) {
for (int j = 0; j < num_colors; j++) {
new_palette.AddColor(palette[i + j]);
auto color = palette[i + j];
// Ensure first color of each sub-palette (index 0) is transparent
if (j == 0) {
color.set_transparent(true);
}
new_palette.AddColor(color);
}
}
palette_group.AddPalette(new_palette);

View File

@@ -554,6 +554,15 @@ void Canvas::DrawContextMenu() {
modals_->Render();
}
// CRITICAL: Render custom context menu items AFTER enhanced menu
// Don't return early - we need to show custom items too!
if (!context_menu_items_.empty() && ImGui::BeginPopupContextItem(context_id_.c_str())) {
for (const auto& item : context_menu_items_) {
DrawContextMenuItem(item);
}
ImGui::EndPopup();
}
return;
}

View File

@@ -314,8 +314,11 @@ void Room::RenderRoomGraphics() {
int num_palettes = dungeon_pal_group.size();
int palette_id = palette;
LOG_DEBUG("[RenderRoomGraphics]", "Room palette byte: %d, num_palettes available: %d",
palette_id, num_palettes);
if (palette_id < 0 || palette_id >= num_palettes) {
LOG_DEBUG("[RenderRoomGraphics]", "WARNING: palette_id %d out of bounds, using %d",
LOG_DEBUG("[RenderRoomGraphics]", "WARNING: palette_id %d out of bounds, clamping to %d",
palette_id, palette_id % num_palettes);
palette_id = palette_id % num_palettes;
}
@@ -323,9 +326,22 @@ void Room::RenderRoomGraphics() {
auto bg1_palette = dungeon_pal_group[palette_id];
if (bg1_palette.size() > 0) {
LOG_DEBUG("[RenderRoomGraphics]", "Applying palette_id=%d with %d colors to bitmaps",
palette_id, (int)bg1_palette.size());
// Debug: Log first few palette colors
for (int i = 0; i < std::min(16, (int)bg1_palette.size()); i++) {
auto rgb = bg1_palette[i].rgb();
LOG_DEBUG("[RenderRoomGraphics]", " Palette[%d]: R=%.0f G=%.0f B=%.0f A=%.0f %s",
i, rgb.x, rgb.y, rgb.z, rgb.w,
bg1_palette[i].is_transparent() ? "(TRANSPARENT)" : "");
}
// Apply FULL 90-color dungeon palette
bg1_bmp.SetPalette(bg1_palette);
bg2_bmp.SetPalette(bg1_palette);
} else {
LOG_DEBUG("[RenderRoomGraphics]", "ERROR: Palette is empty!");
}
// Render objects ON TOP of background tiles (AFTER palette is set)

View File

@@ -162,29 +162,18 @@ absl::Status RoomLayout::ParseLayoutData(const std::vector<uint8_t>& data) {
uint8_t tile_id = data[index];
// Determine object type based on tile ID
// NOTE: Layout format needs research - using simplified heuristics
RoomLayoutObject::Type type = RoomLayoutObject::Type::kUnknown;
if (tile_id == 0) {
// Empty space - skip
continue;
} else if (tile_id >= 0x01 && tile_id <= 0x20) {
// Wall tiles
type = RoomLayoutObject::Type::kWall;
} else if (tile_id >= 0x21 && tile_id <= 0x40) {
// Floor tiles
type = RoomLayoutObject::Type::kFloor;
} else if (tile_id >= 0x41 && tile_id <= 0x60) {
// Ceiling tiles
type = RoomLayoutObject::Type::kCeiling;
} else if (tile_id >= 0x61 && tile_id <= 0x80) {
// Water tiles
type = RoomLayoutObject::Type::kWater;
} else if (tile_id >= 0x81 && tile_id <= 0xA0) {
// Stairs
type = RoomLayoutObject::Type::kStairs;
} else if (tile_id >= 0xA1 && tile_id <= 0xC0) {
// Doors
type = RoomLayoutObject::Type::kDoor;
}
// Just mark everything as unknown for now
// The room graphics bitmap handles the actual visual appearance
// Layout objects are just for structural information
type = RoomLayoutObject::Type::kUnknown;
// Create layout object
objects_.emplace_back(tile_id, x, y, type, 0);