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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user