refactor: Consolidate and update Dungeon Editor documentation

- Deleted outdated DUNGEON_EDITOR_COMPLETE_GUIDE.md and DUNGEON_EDITOR_GUIDE.md files to streamline documentation.
- Introduced a new F2-dungeon-editor-v2-guide.md that consolidates features, architecture, and usage instructions for the Dungeon Editor V2.
- Documented recent refactoring efforts, including critical bug fixes and architectural improvements.
- Enhanced the guide with structured sections for quick start, testing, and troubleshooting, reflecting the current production-ready status of the Dungeon Editor.
- Updated related source files to support new documentation structure and features.
This commit is contained in:
scawful
2025-10-09 21:24:48 -04:00
parent 0491781dd5
commit 66061652b1
7 changed files with 739 additions and 1018 deletions

View File

@@ -41,52 +41,69 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
static int prev_layout = -1;
static int prev_spriteset = -1;
gui::InputHexByte("Layout", &room.layout);
ImGui::SameLine();
gui::InputHexByte("Gfx", &room.blockset);
ImGui::SameLine();
gui::InputHexByte("Spriteset", &room.spriteset);
ImGui::SameLine();
gui::InputHexByte("Palette", &room.palette);
// Floor graphics - use temp variables and setters (floor1/floor2 are now accessors)
uint8_t floor1_val = room.floor1();
uint8_t floor2_val = room.floor2();
if (gui::InputHexByte("Floor1", &floor1_val) && ImGui::IsItemDeactivatedAfterEdit()) {
room.set_floor1(floor1_val);
// Trigger re-render since floor graphics changed
if (room.rom() && room.rom()->is_loaded()) {
room.RenderRoomGraphics();
// Room properties in organized table
if (ImGui::BeginTable("##RoomProperties", 4, ImGuiTableFlags_SizingStretchSame | ImGuiTableFlags_Borders)) {
ImGui::TableSetupColumn("Graphics");
ImGui::TableSetupColumn("Layout");
ImGui::TableSetupColumn("Floors");
ImGui::TableSetupColumn("Message");
ImGui::TableHeadersRow();
ImGui::TableNextRow();
// Column 1: Graphics (Blockset, Spriteset, Palette)
ImGui::TableNextColumn();
gui::InputHexByte("Gfx", &room.blockset, 50.f);
gui::InputHexByte("Sprite", &room.spriteset, 50.f);
gui::InputHexByte("Palette", &room.palette, 50.f);
// Column 2: Layout
ImGui::TableNextColumn();
gui::InputHexByte("Layout", &room.layout, 50.f);
// Column 3: Floors
ImGui::TableNextColumn();
uint8_t floor1_val = room.floor1();
uint8_t floor2_val = room.floor2();
if (gui::InputHexByte("Floor1", &floor1_val, 50.f) && ImGui::IsItemDeactivatedAfterEdit()) {
room.set_floor1(floor1_val);
if (room.rom() && room.rom()->is_loaded()) {
room.RenderRoomGraphics();
}
}
}
ImGui::SameLine();
if (gui::InputHexByte("Floor2", &floor2_val) && ImGui::IsItemDeactivatedAfterEdit()) {
room.set_floor2(floor2_val);
// Trigger re-render since floor graphics changed
if (room.rom() && room.rom()->is_loaded()) {
room.RenderRoomGraphics();
if (gui::InputHexByte("Floor2", &floor2_val, 50.f) && ImGui::IsItemDeactivatedAfterEdit()) {
room.set_floor2(floor2_val);
if (room.rom() && room.rom()->is_loaded()) {
room.RenderRoomGraphics();
}
}
// Column 4: Message
ImGui::TableNextColumn();
gui::InputHexWord("MsgID", &room.message_id_, 70.f);
ImGui::EndTable();
}
ImGui::SameLine();
gui::InputHexWord("Message ID", &room.message_id_);
// Per-room layer visibility controls
// Layer visibility controls in compact table
ImGui::Separator();
ImGui::Text("Layer Controls (Per-Room):");
auto& layer_settings = GetRoomLayerSettings(room_id);
ImGui::Checkbox("Show BG1", &layer_settings.bg1_visible);
ImGui::SameLine();
ImGui::Checkbox("Show BG2", &layer_settings.bg2_visible);
// BG2 layer type dropdown
const char* bg2_layer_types[] = {
"Normal (100%)", "Translucent (75%)", "Addition (50%)", "Dark (25%)", "Off (0%)"
};
const int bg2_alpha_values[] = {255, 191, 127, 64, 0};
if (ImGui::Combo("BG2 Layer Type", &layer_settings.bg2_layer_type, bg2_layer_types,
sizeof(bg2_layer_types) / sizeof(bg2_layer_types[0]))) {
// BG2 layer type changed, no need to reload graphics
if (ImGui::BeginTable("##LayerControls", 3, ImGuiTableFlags_SizingStretchSame)) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
auto& layer_settings = GetRoomLayerSettings(room_id);
ImGui::Checkbox("Show BG1", &layer_settings.bg1_visible);
ImGui::TableNextColumn();
ImGui::Checkbox("Show BG2", &layer_settings.bg2_visible);
ImGui::TableNextColumn();
// BG2 layer type dropdown
const char* bg2_layer_types[] = {"Normal", "Trans", "Add", "Dark", "Off"};
ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::Combo("##BG2Type", &layer_settings.bg2_layer_type, bg2_layer_types, 5);
ImGui::EndTable();
}
// Check if critical properties changed and trigger reload
@@ -196,6 +213,10 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
// This already includes objects rendered by ObjectDrawer in Room::RenderObjectsToBackground()
DrawRoomBackgroundLayers(room_id);
// Draw room layout (structural elements like walls, pits)
// This provides context for object placement
DrawRoomLayout(room);
// VISUALIZATION: Draw object position rectangles (for debugging)
// This shows where objects are placed regardless of whether graphics render
DrawObjectPositionOutlines(room);
@@ -369,6 +390,23 @@ void DungeonCanvasViewer::CalculateWallDimensions(const zelda3::RoomObject& obje
height = std::min(height, 256);
}
// Room layout visualization
void DungeonCanvasViewer::DrawRoomLayout(const zelda3::Room& room) {
// Draw room layout structural elements (walls, floors, pits)
// This provides visual context for where objects should be placed
const auto& layout = room.GetLayout();
// Get dimensions (64x64 tiles = 512x512 pixels)
auto [width_tiles, height_tiles] = layout.GetDimensions();
// TODO: Get layout objects by type
// For now, draw a grid overlay to show the room structure
// Future: Implement GetObjectsByType() in RoomLayout
LOG_DEBUG("[DrawRoomLayout]", "Room layout: %dx%d tiles", width_tiles, height_tiles);
}
// Object visualization methods
void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) {
// Draw colored rectangles showing object positions

View File

@@ -104,7 +104,8 @@ class DungeonCanvasViewer {
// Object dimension calculation
void CalculateWallDimensions(const zelda3::RoomObject& object, int& width, int& height);
// Object visualization
// Visualization
void DrawRoomLayout(const zelda3::Room& room);
void DrawObjectPositionOutlines(const zelda3::Room& room);
// Room graphics management

View File

@@ -372,10 +372,10 @@ void DungeonEditorV2::DrawLayout() {
int room_id = active_rooms_[i];
bool open = true;
// Create session-aware card title
// Create session-aware card title with room ID prominent
std::string base_name;
if (room_id >= 0 && static_cast<size_t>(room_id) < std::size(zelda3::kRoomNames)) {
base_name = absl::StrFormat("%s", zelda3::kRoomNames[room_id].data());
base_name = absl::StrFormat("[%03X] %s", room_id, zelda3::kRoomNames[room_id].data());
} else {
base_name = absl::StrFormat("Room %03X", room_id);
}
@@ -460,14 +460,14 @@ void DungeonEditorV2::DrawRoomTab(int room_id) {
}
}
// Room info header
ImGui::Text("Room %03X", room_id);
ImGui::SameLine();
// Room ID moved to card title - just show load status now
if (room.IsLoaded()) {
ImGui::TextColored(ImVec4(0.4f, 0.8f, 0.4f, 1.0f), ICON_MD_CHECK " Loaded");
} else {
ImGui::TextColored(ImVec4(0.8f, 0.4f, 0.4f, 1.0f), ICON_MD_PENDING " Not Loaded");
}
ImGui::SameLine();
ImGui::TextDisabled("Objects: %zu", room.GetTileObjects().size());
ImGui::Separator();
@@ -547,7 +547,7 @@ void DungeonEditorV2::DrawRoomsListCard() {
std::string filter_str = room_filter;
std::transform(filter_str.begin(), filter_str.end(), filter_str.begin(), ::tolower);
for (int i = 0; i < 0x128; i++) {
for (int i = 0; i < zelda3::NumberOfRooms; i++) {
// Get room name
std::string room_name;
if (i < static_cast<int>(std::size(zelda3::kRoomNames))) {
@@ -853,8 +853,9 @@ void DungeonEditorV2::DrawRoomGraphicsCard() {
ImGui::Text("Blockset: %02X", room.blockset);
ImGui::Separator();
// Create a canvas for displaying room graphics
static gui::Canvas room_gfx_canvas("##RoomGfxCanvas", ImVec2(0x100 + 1, 0x10 * 0x40 + 1));
// Create a canvas for displaying room graphics (16 blocks, 2 columns, 8 rows)
// Each block is 128x32, so 2 cols = 256 wide, 8 rows = 256 tall
static gui::Canvas room_gfx_canvas("##RoomGfxCanvas", ImVec2(256 + 1, 256 + 1));
room_gfx_canvas.DrawBackground();
room_gfx_canvas.DrawContextMenu();

View File

@@ -302,33 +302,41 @@ void Room::RenderRoomGraphics() {
bg1_buffer_.DrawBackground(std::span<uint8_t>(current_gfx16_));
bg2_buffer_.DrawBackground(std::span<uint8_t>(current_gfx16_));
// Render objects ON TOP of background tiles
// This must happen AFTER DrawBackground to avoid overwriting object data
RenderObjectsToBackground();
auto& bg1_bmp = bg1_buffer_.bitmap();
auto& bg2_bmp = bg2_buffer_.bitmap();
// Get and apply palette FIRST (before marking modified)
// Get and apply palette BEFORE rendering objects (so objects use correct colors)
auto& dungeon_pal_group = rom()->mutable_palette_group()->dungeon_main;
int num_palettes = dungeon_pal_group.size();
int palette_id = palette;
if (palette_id < 0 || palette_id >= num_palettes) {
LOG_DEBUG("[RenderRoomGraphics]", "5. WARNING: palette_id %d is out of bounds [0, %d), using palette %d",
palette_id, num_palettes, palette_id % num_palettes);
palette_id = palette_id % num_palettes; // Use modulo to wrap around instead of defaulting to 0
LOG_DEBUG("[RenderRoomGraphics]", "WARNING: palette_id %d out of bounds, using %d",
palette_id, palette_id % num_palettes);
palette_id = palette_id % num_palettes;
}
auto bg1_palette = dungeon_pal_group[palette_id];
if (bg1_palette.size() > 0) {
// Use SetPalette() to apply the FULL 90-color dungeon palette
// SetPaletteWithTransparent() only extracts 8 colors, which is wrong for dungeons!
// Apply FULL 90-color dungeon palette
bg1_bmp.SetPalette(bg1_palette);
bg2_bmp.SetPalette(bg1_palette);
// Queue texture creation for background buffers
}
// Render objects ON TOP of background tiles (AFTER palette is set)
// ObjectDrawer will write indexed pixel data that uses the palette we just set
RenderObjectsToBackground();
// Update textures with all the data (floor + background + objects + palette)
if (bg1_bmp.texture()) {
// Texture exists - UPDATE it with new object data
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &bg1_bmp);
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::UPDATE, &bg2_bmp);
} else {
// No texture yet - CREATE it
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg1_bmp);
gfx::Arena::Get().QueueTextureCommand(