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
|
// Show object bounds with sub-menu for categories
|
||||||
debug_menu.subitems.push_back({
|
gui::Canvas::ContextMenuItem object_bounds_menu;
|
||||||
ICON_MD_CROP_SQUARE " Show Object Bounds",
|
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]() {
|
[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
|
// Show layer info
|
||||||
debug_menu.subitems.push_back({
|
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("BG1 Visible", &layer_settings.bg1_visible);
|
||||||
ImGui::Checkbox("BG2 Visible", &layer_settings.bg2_visible);
|
ImGui::Checkbox("BG2 Visible", &layer_settings.bg2_visible);
|
||||||
ImGui::SliderInt("BG2 Type", &layer_settings.bg2_layer_type, 0, 4);
|
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();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
@@ -694,6 +754,33 @@ void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) {
|
|||||||
const auto& objects = room.GetTileObjects();
|
const auto& objects = room.GetTileObjects();
|
||||||
|
|
||||||
for (const auto& obj : objects) {
|
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)
|
// Convert object position (tile coordinates) to canvas pixel coordinates (UNSCALED)
|
||||||
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(obj.x(), obj.y());
|
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
|
// Color-code by layer
|
||||||
ImVec4 outline_color;
|
ImVec4 outline_color;
|
||||||
if (obj.GetLayerValue() == 0) {
|
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) {
|
} 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 {
|
} 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
|
// Draw outline rectangle
|
||||||
canvas_.DrawRect(canvas_x, canvas_y, width, height, outline_color);
|
canvas_.DrawRect(canvas_x, canvas_y, width, height, outline_color);
|
||||||
|
|
||||||
// Draw object ID label
|
// Draw object ID label (smaller, less obtrusive)
|
||||||
std::string label = absl::StrFormat("0x%02X", obj.id_);
|
std::string label = absl::StrFormat("%02X", obj.id_);
|
||||||
canvas_.DrawText(label, canvas_x + 2, canvas_y + 2);
|
canvas_.DrawText(label, canvas_x + 1, canvas_y + 1);
|
||||||
}
|
|
||||||
|
|
||||||
// Log object count
|
|
||||||
if (!objects.empty()) {
|
|
||||||
LOG_DEBUG("[DrawObjectPositionOutlines]", "Drew %zu object outlines", objects.size());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -160,6 +160,17 @@ class DungeonCanvasViewer {
|
|||||||
bool show_texture_debug_ = false;
|
bool show_texture_debug_ = false;
|
||||||
bool show_object_bounds_ = false;
|
bool show_object_bounds_ = false;
|
||||||
bool show_layer_info_ = 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
|
} // namespace editor
|
||||||
|
|||||||
@@ -323,9 +323,14 @@ absl::StatusOr<PaletteGroup> CreatePaletteGroupFromLargePalette(
|
|||||||
PaletteGroup palette_group;
|
PaletteGroup palette_group;
|
||||||
for (int i = 0; i < palette.size(); i += num_colors) {
|
for (int i = 0; i < palette.size(); i += num_colors) {
|
||||||
SnesPalette new_palette;
|
SnesPalette new_palette;
|
||||||
if (i + num_colors < palette.size()) {
|
if (i + num_colors <= palette.size()) {
|
||||||
for (int j = 0; j < num_colors; j++) {
|
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);
|
palette_group.AddPalette(new_palette);
|
||||||
|
|||||||
@@ -554,6 +554,15 @@ void Canvas::DrawContextMenu() {
|
|||||||
modals_->Render();
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -314,8 +314,11 @@ void Room::RenderRoomGraphics() {
|
|||||||
int num_palettes = dungeon_pal_group.size();
|
int num_palettes = dungeon_pal_group.size();
|
||||||
int palette_id = palette;
|
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) {
|
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);
|
||||||
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];
|
auto bg1_palette = dungeon_pal_group[palette_id];
|
||||||
|
|
||||||
if (bg1_palette.size() > 0) {
|
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
|
// Apply FULL 90-color dungeon palette
|
||||||
bg1_bmp.SetPalette(bg1_palette);
|
bg1_bmp.SetPalette(bg1_palette);
|
||||||
bg2_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)
|
// 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];
|
uint8_t tile_id = data[index];
|
||||||
|
|
||||||
// Determine object type based on tile ID
|
// Determine object type based on tile ID
|
||||||
|
// NOTE: Layout format needs research - using simplified heuristics
|
||||||
RoomLayoutObject::Type type = RoomLayoutObject::Type::kUnknown;
|
RoomLayoutObject::Type type = RoomLayoutObject::Type::kUnknown;
|
||||||
|
|
||||||
if (tile_id == 0) {
|
if (tile_id == 0) {
|
||||||
// Empty space - skip
|
// Empty space - skip
|
||||||
continue;
|
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
|
// Create layout object
|
||||||
objects_.emplace_back(tile_id, x, y, type, 0);
|
objects_.emplace_back(tile_id, x, y, type, 0);
|
||||||
|
|||||||
Reference in New Issue
Block a user