diff --git a/docs/dungeon-editor-design-plan.md b/docs/dungeon-editor-design-plan.md new file mode 100644 index 00000000..2dff54b1 --- /dev/null +++ b/docs/dungeon-editor-design-plan.md @@ -0,0 +1,430 @@ +# Dungeon Editor Design Plan & Future Development Guide + +## Overview +This document provides a comprehensive design plan for the Yaze Dungeon Editor, including current architecture, identified issues, and a roadmap for future developers to continue development effectively. + +## Current Architecture + +### Main Components + +#### 1. **DungeonEditor** (Main Controller) +- **File**: `src/app/editor/dungeon/dungeon_editor.h/cc` +- **Purpose**: Main UI controller and coordinator +- **Responsibilities**: + - Managing the 3-column UI layout + - Coordinating between different editor components + - Handling ROM loading and initialization + - Managing editor state and undo/redo + +#### 2. **DungeonRoomSelector** (Room/Entrance Selection) +- **File**: `src/app/editor/dungeon/dungeon_room_selector.h/cc` +- **Purpose**: Handles room and entrance selection UI +- **Responsibilities**: + - Room list display and selection + - Entrance configuration and selection + - Room properties editing + +#### 3. **DungeonCanvasViewer** (Main Canvas) +- **File**: `src/app/editor/dungeon/dungeon_canvas_viewer.h/cc` +- **Purpose**: Main canvas rendering and interaction +- **Responsibilities**: + - Room graphics rendering + - Object rendering and positioning + - Canvas interaction (pan, zoom, select) + - Coordinate system management + +#### 4. **DungeonObjectSelector** (Object Management) +- **File**: `src/app/editor/dungeon/dungeon_object_selector.h/cc` +- **Purpose**: Object selection, preview, and editing +- **Responsibilities**: + - Object preview rendering + - Object editing controls + - Graphics sheet display + - Integrated editing panels + +### Core Systems + +#### 1. **ObjectRenderer** +- **File**: `src/app/zelda3/dungeon/object_renderer.h/cc` +- **Purpose**: Renders dungeon objects to bitmaps +- **Features**: Caching, performance monitoring, memory management + +#### 2. **DungeonEditorSystem** +- **File**: `src/app/zelda3/dungeon/dungeon_editor_system.h/cc` +- **Purpose**: High-level dungeon editing operations +- **Features**: Undo/redo, room management, object operations + +#### 3. **DungeonObjectEditor** +- **File**: `src/app/zelda3/dungeon/dungeon_object_editor.h/cc` +- **Purpose**: Interactive object editing +- **Features**: Object placement, editing modes, validation + +## Current Issues & Fixes Applied + +### 1. **Crash Prevention** ✅ FIXED +- **Issue**: Multiple crashes when ROM not loaded or invalid data accessed +- **Fixes Applied**: + - Added comprehensive null checks in `DrawSpriteTile` + - Added ROM validation in `LoadAnimatedGraphics` and `CopyRoomGraphicsToBuffer` + - Added bounds checking for all array/vector accesses + - Added graceful error handling with early returns + +### 2. **UI Simplification** ✅ FIXED +- **Issue**: 4-column layout was too crowded +- **Fixes Applied**: + - Reduced to 3-column layout: Room/Entrance Selector | Canvas | Object Selector/Editor + - Fixed column widths for better space utilization + - Separated logical components into dedicated classes + +### 3. **Coordinate System Issues** ✅ PARTIALLY FIXED +- **Issue**: Object previews and coordinates were incorrect +- **Fixes Applied**: + - Fixed object preview centering in canvas + - Added coordinate conversion helper functions + - Improved bounds checking for object rendering + +## Remaining Issues + +### 1. **Object Preview Not Showing** +- **Current Status**: Objects may not render in preview due to: + - ROM data not properly loaded + - Palette issues + - Object parsing failures +- **Recommended Fix**: Add debug logging and fallback rendering + +### 2. **Graphics Loading Issues** +- **Current Status**: Room graphics may not load properly +- **Recommended Fix**: Implement proper error handling and user feedback + +### 3. **Memory Management** +- **Current Status**: Potential memory leaks in graphics caching +- **Recommended Fix**: Implement proper cleanup and memory monitoring + +## Future Development Roadmap + +### Phase 1: Stability & Bug Fixes (Priority: High) + +#### 1.1 Object Preview System +```cpp +// In DungeonObjectSelector::DrawObjectRenderer() +void DungeonObjectSelector::DrawObjectRenderer() { + // Add debug information + if (ImGui::Button("Debug Object Preview")) { + // Log object data, ROM state, palette info + LogObjectPreviewDebugInfo(); + } + + // Add fallback rendering + if (!object_loaded_) { + ImGui::Text("No object preview available"); + if (ImGui::Button("Force Load Preview")) { + ForceLoadObjectPreview(); + } + } +} +``` + +#### 1.2 Error Handling & User Feedback +```cpp +// Add to all major operations +absl::Status LoadRoomGraphics(int room_id) { + auto result = DoLoadRoomGraphics(room_id); + if (!result.ok()) { + ShowErrorMessage("Failed to load room graphics", result.message()); + return result; + } + ShowSuccessMessage("Room graphics loaded successfully"); + return absl::OkStatus(); +} +``` + +#### 1.3 Memory Management +```cpp +// Add memory monitoring +class DungeonEditor { +private: + void MonitorMemoryUsage() { + auto stats = object_renderer_.GetPerformanceStats(); + if (stats.memory_usage > kMaxMemoryUsage) { + ClearObjectCache(); + ShowWarningMessage("Memory usage high, cleared cache"); + } + } +}; +``` + +### Phase 2: Feature Enhancement (Priority: Medium) + +#### 2.1 Advanced Object Editing +- Multi-object selection +- Object grouping +- Copy/paste operations +- Object templates + +#### 2.2 Enhanced Canvas Features +- Zoom controls +- Grid snapping +- Layer management +- Real-time preview + +#### 2.3 Room Management +- Room duplication +- Room templates +- Bulk operations +- Room validation + +### Phase 3: Advanced Features (Priority: Low) + +#### 3.1 Scripting Support +- Lua scripting for custom operations +- Automation tools +- Batch processing + +#### 3.2 Plugin System +- Modular architecture +- Third-party plugins +- Custom object types + +## Implementation Guidelines + +### 1. **Error Handling Pattern** +```cpp +absl::Status DoOperation() { + // Validate inputs + if (!IsValidInput()) { + return absl::InvalidArgumentError("Invalid input"); + } + + // Perform operation with error checking + auto result = PerformOperation(); + if (!result.ok()) { + LogError("Operation failed", result.status()); + return result.status(); + } + + return absl::OkStatus(); +} +``` + +### 2. **UI Component Pattern** +```cpp +class ComponentName { +public: + void Draw() { + if (!IsValid()) { + ImGui::Text("Component not ready"); + return; + } + + // Draw component UI + DrawMainUI(); + + // Handle interactions + HandleInteractions(); + } + +private: + bool IsValid() const { + return rom_ != nullptr && rom_->is_loaded(); + } +}; +``` + +### 3. **Memory Management Pattern** +```cpp +class GraphicsManager { +public: + void ClearCache() { + object_cache_.clear(); + texture_cache_.clear(); + memory_pool_.Reset(); + } + + size_t GetMemoryUsage() const { + return object_cache_.size() * sizeof(CachedObject) + + texture_cache_.size() * sizeof(Texture); + } + +private: + std::vector object_cache_; + std::unordered_map texture_cache_; + MemoryPool memory_pool_; +}; +``` + +## Testing Strategy + +### 1. **Unit Tests** +- Test each component in isolation +- Mock ROM data for consistent testing +- Test error conditions and edge cases + +### 2. **Integration Tests** +- Test component interactions +- Test with real ROM data +- Test performance under load + +### 3. **UI Tests** +- Test user interactions +- Test layout responsiveness +- Test accessibility + +## Performance Considerations + +### 1. **Rendering Optimization** +- Implement object culling +- Use texture atlases +- Implement level-of-detail (LOD) + +### 2. **Memory Optimization** +- Implement smart caching +- Use memory pools +- Monitor memory usage + +### 3. **UI Responsiveness** +- Use async operations for heavy tasks +- Implement progress indicators +- Minimize UI blocking operations + +## Debugging Tools + +### 1. **Debug Console** +```cpp +class DebugConsole { +public: + void LogObjectState(const RoomObject& obj) { + ImGui::Text("Object ID: %d", obj.id_); + ImGui::Text("Position: (%d, %d)", obj.x_, obj.y_); + ImGui::Text("Size: %d", obj.size_); + ImGui::Text("Layer: %d", static_cast(obj.layer_)); + ImGui::Text("Tiles Loaded: %s", obj.tiles().empty() ? "No" : "Yes"); + } +}; +``` + +### 2. **Performance Monitor** +```cpp +class PerformanceMonitor { +public: + void DrawPerformanceStats() { + auto stats = GetStats(); + ImGui::Text("Render Time: %.2f ms", stats.render_time.count()); + ImGui::Text("Memory Usage: %.2f MB", stats.memory_usage / 1024.0 / 1024.0); + ImGui::Text("Cache Hit Rate: %.2f%%", stats.cache_hit_rate * 100); + } +}; +``` + +## Current Implementation Status + +### ✅ Completed (Phase 1) +- **UI Component Separation**: Successfully separated DungeonEditor into 3 main components: + - `DungeonRoomSelector`: Room and entrance selection UI + - `DungeonCanvasViewer`: Main canvas rendering and interaction + - `DungeonObjectSelector`: Object management and editing panels +- **3-Column Layout**: Implemented clean 3-column layout as requested +- **Debug Elements Popup**: Moved debug controls into modal popup for cleaner UI +- **Crash Prevention**: Added comprehensive null checks and bounds validation +- **Component Architecture**: Established proper separation of concerns + +### 🔄 In Progress (Phase 2) +- **Component Integration**: Currently integrating UI components into main DungeonEditor +- **Method Implementation**: Adding missing SetRom, SetRooms, SetCurrentPaletteGroup methods +- **Data Flow**: Establishing proper data flow between components + +### ⏳ Next Steps (Phase 3) +1. **Complete Method Implementation**: Add all missing methods to UI components +2. **Test Integration**: Verify all components work together correctly +3. **Error Handling**: Add proper error handling and user feedback +4. **Performance Optimization**: Implement caching and memory management + +## Implementation Notes for Future Developers + +### Component Integration Pattern +```cpp +// Main DungeonEditor coordinates components +class DungeonEditor { +private: + DungeonRoomSelector room_selector_; + DungeonCanvasViewer canvas_viewer_; + DungeonObjectSelector object_selector_; + +public: + void Load() { + // Initialize components with data + room_selector_.set_rom(rom_); + room_selector_.set_rooms(&rooms_); + room_selector_.set_entrances(&entrances_); + + canvas_viewer_.SetRom(rom_); + canvas_viewer_.SetRooms(rooms_); + canvas_viewer_.SetCurrentPaletteGroup(current_palette_group_); + + object_selector_.SetRom(rom_); + object_selector_.SetCurrentPaletteGroup(current_palette_group_); + } + + void UpdateDungeonRoomView() { + // 3-column layout + if (BeginTable("#DungeonEditTable", 3, kDungeonTableFlags)) { + TableNextColumn(); + room_selector_.Draw(); // Column 1: Room/Entrance Selector + + TableNextColumn(); + canvas_viewer_.Draw(current_room_id_); // Column 2: Canvas + + TableNextColumn(); + object_selector_.Draw(); // Column 3: Object Selector/Editor + + EndTable(); + } + } +}; +``` + +### Required Methods for UI Components +Each UI component needs these methods for proper integration: + +```cpp +// DungeonRoomSelector +void SetRom(Rom* rom); +void SetRooms(std::array* rooms); +void SetEntrances(std::array* entrances); +void Draw(); + +// DungeonCanvasViewer +void SetRom(Rom* rom); +void SetRooms(std::array& rooms); +void SetCurrentPaletteGroup(const gfx::PaletteGroup& palette_group); +void SetCurrentPaletteId(uint64_t palette_id); +void Draw(int room_id); + +// DungeonObjectSelector +void SetRom(Rom* rom); +void SetCurrentPaletteGroup(const gfx::PaletteGroup& palette_group); +void SetCurrentPaletteId(uint64_t palette_id); +void Draw(); +``` + +## Conclusion + +The Dungeon Editor has a solid foundation with proper separation of concerns and crash prevention measures in place. The main areas for improvement are: + +1. **Component Integration**: Complete the integration of UI components (IN PROGRESS) +2. **Object Preview System**: Needs debugging and fallback mechanisms +3. **Error Handling**: Needs better user feedback and recovery +4. **Memory Management**: Needs monitoring and cleanup +5. **UI Polish**: Needs better visual feedback and responsiveness + +Following this design plan will ensure the Dungeon Editor becomes a robust, user-friendly tool for Zelda 3 ROM editing. + +## Quick Start for New Developers + +1. **Read the Architecture**: Understand the component separation +2. **Study the Crash Fixes**: Learn the error handling patterns +3. **Start with Debugging**: Add logging to understand current issues +4. **Implement Incrementally**: Make small, testable changes +5. **Test Thoroughly**: Always test with real ROM data +6. **Document Changes**: Update this document as you make improvements + +The codebase is well-structured and ready for continued development. Focus on stability first, then features. diff --git a/src/app/editor/dungeon/dungeon_canvas_viewer.cc b/src/app/editor/dungeon/dungeon_canvas_viewer.cc index 855e5059..8b7fac44 100644 --- a/src/app/editor/dungeon/dungeon_canvas_viewer.cc +++ b/src/app/editor/dungeon/dungeon_canvas_viewer.cc @@ -65,6 +65,10 @@ void DungeonCanvasViewer::DrawDungeonTabView() { Separator(); } +void DungeonCanvasViewer::Draw(int room_id) { + DrawDungeonCanvas(room_id); +} + void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) { // Validate room_id and ROM if (room_id < 0 || room_id >= 128) { diff --git a/src/app/editor/dungeon/dungeon_canvas_viewer.h b/src/app/editor/dungeon/dungeon_canvas_viewer.h index 16c095a4..0113f087 100644 --- a/src/app/editor/dungeon/dungeon_canvas_viewer.h +++ b/src/app/editor/dungeon/dungeon_canvas_viewer.h @@ -20,22 +20,23 @@ class DungeonCanvasViewer { void DrawDungeonTabView(); void DrawDungeonCanvas(int room_id); + void Draw(int room_id); - void set_rom(Rom* rom) { + void SetRom(Rom* rom) { rom_ = rom; object_renderer_.SetROM(rom); } Rom* rom() const { return rom_; } // Room data access - void set_rooms(std::array* rooms) { rooms_ = rooms; } + void SetRooms(std::array* rooms) { rooms_ = rooms; } void set_active_rooms(const ImVector& rooms) { active_rooms_ = rooms; } void set_current_active_room_tab(int tab) { current_active_room_tab_ = tab; } // Palette access void set_current_palette_group_id(uint64_t id) { current_palette_group_id_ = id; } - void set_current_palette_id(uint64_t id) { current_palette_id_ = id; } - void set_current_palette_group(const gfx::PaletteGroup& group) { current_palette_group_ = group; } + void SetCurrentPaletteId(uint64_t id) { current_palette_id_ = id; } + void SetCurrentPaletteGroup(const gfx::PaletteGroup& group) { current_palette_group_ = group; } // Canvas access gui::Canvas& canvas() { return canvas_; } diff --git a/src/app/editor/dungeon/dungeon_editor.cc b/src/app/editor/dungeon/dungeon_editor.cc index 1d72ee9b..8be4454b 100644 --- a/src/app/editor/dungeon/dungeon_editor.cc +++ b/src/app/editor/dungeon/dungeon_editor.cc @@ -100,6 +100,21 @@ absl::Status DungeonEditor::Load() { } } + // Initialize the new UI components with loaded data + room_selector_.set_rom(rom_); + room_selector_.set_rooms(&rooms_); + room_selector_.set_entrances(&entrances_); + room_selector_.set_active_rooms(active_rooms_); + + canvas_viewer_.SetRom(rom_); + canvas_viewer_.SetRooms(&rooms_); + canvas_viewer_.SetCurrentPaletteGroup(current_palette_group_); + canvas_viewer_.SetCurrentPaletteId(current_palette_id_); + + object_selector_.SetRom(rom_); + object_selector_.SetCurrentPaletteGroup(current_palette_group_); + object_selector_.SetCurrentPaletteId(current_palette_id_); + is_loaded_ = true; return absl::OkStatus(); } @@ -230,33 +245,21 @@ absl::Status DungeonEditor::UpdateDungeonRoomView() { TableHeadersRow(); TableNextRow(); - // Column 1: Room and Entrance Selector + // Column 1: Room and Entrance Selector (using new component) TableNextColumn(); - if (ImGui::BeginTabBar("##DungeonRoomTabBar")) { - TAB_ITEM("Rooms"); - DrawRoomSelector(); - END_TAB_ITEM(); - TAB_ITEM("Entrances"); - DrawEntranceSelector(); - END_TAB_ITEM(); - ImGui::EndTabBar(); - } + room_selector_.Draw(); - // Column 2: Main Canvas + // Column 2: Main Canvas (using new component) TableNextColumn(); - DrawDungeonTabView(); - - // Column 3: Object Selector and Editor - TableNextColumn(); - if (ImGui::BeginTabBar("##ObjectEditorTabBar")) { - TAB_ITEM("Graphics"); - DrawTileSelector(); - END_TAB_ITEM(); - TAB_ITEM("Editor"); - DrawIntegratedEditingPanels(); - END_TAB_ITEM(); - ImGui::EndTabBar(); + int current_room = current_room_id_; + if (!active_rooms_.empty() && current_active_room_tab_ < active_rooms_.Size) { + current_room = active_rooms_[current_active_room_tab_]; } + canvas_viewer_.Draw(current_room); + + // Column 3: Object Selector and Editor (using new component) + TableNextColumn(); + object_selector_.Draw(); ImGui::EndTable(); } diff --git a/src/app/editor/dungeon/dungeon_editor.h b/src/app/editor/dungeon/dungeon_editor.h index 7faa0d0b..227eda5b 100644 --- a/src/app/editor/dungeon/dungeon_editor.h +++ b/src/app/editor/dungeon/dungeon_editor.h @@ -45,7 +45,8 @@ constexpr ImGuiTableFlags kDungeonTableFlags = class DungeonEditor : public Editor { public: explicit DungeonEditor(Rom* rom = nullptr) - : rom_(rom), object_renderer_(rom), preview_object_(0, 0, 0, 0, 0) { + : rom_(rom), object_renderer_(rom), preview_object_(0, 0, 0, 0, 0), + room_selector_(rom), canvas_viewer_(rom), object_selector_(rom) { type_ = EditorType::kDungeon; // Initialize the new dungeon editor system if (rom) { @@ -67,7 +68,13 @@ class DungeonEditor : public Editor { void add_room(int i) { active_rooms_.push_back(i); } - void set_rom(Rom* rom) { rom_ = rom; } + void set_rom(Rom* rom) { + rom_ = rom; + // Update the new UI components with the new ROM + room_selector_.set_rom(rom_); + canvas_viewer_.SetRom(rom_); + object_selector_.SetRom(rom_); + } Rom* rom() const { return rom_; } private: @@ -212,6 +219,11 @@ class DungeonEditor : public Editor { std::array entrances_ = {}; zelda3::ObjectRenderer object_renderer_; + // New UI components + DungeonRoomSelector room_selector_; + DungeonCanvasViewer canvas_viewer_; + DungeonObjectSelector object_selector_; + absl::flat_hash_map spriteset_usage_; absl::flat_hash_map blockset_usage_; absl::flat_hash_map palette_usage_; diff --git a/src/app/editor/dungeon/dungeon_object_selector.cc b/src/app/editor/dungeon/dungeon_object_selector.cc index 3670baef..3bd81209 100644 --- a/src/app/editor/dungeon/dungeon_object_selector.cc +++ b/src/app/editor/dungeon/dungeon_object_selector.cc @@ -127,6 +127,20 @@ void DungeonObjectSelector::DrawObjectRenderer() { } } +void DungeonObjectSelector::Draw() { + if (ImGui::BeginTabBar("##ObjectEditorTabBar")) { + if (ImGui::BeginTabItem("Graphics")) { + DrawRoomGraphics(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Editor")) { + DrawIntegratedEditingPanels(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } +} + void DungeonObjectSelector::DrawRoomGraphics() { const auto height = 0x40; room_gfx_canvas_.DrawBackground(); diff --git a/src/app/editor/dungeon/dungeon_object_selector.h b/src/app/editor/dungeon/dungeon_object_selector.h index a67ecc9b..565dcbed 100644 --- a/src/app/editor/dungeon/dungeon_object_selector.h +++ b/src/app/editor/dungeon/dungeon_object_selector.h @@ -22,11 +22,16 @@ class DungeonObjectSelector { void DrawTileSelector(); void DrawObjectRenderer(); void DrawIntegratedEditingPanels(); + void Draw(); void set_rom(Rom* rom) { rom_ = rom; object_renderer_.SetROM(rom); } + void SetRom(Rom* rom) { + rom_ = rom; + object_renderer_.SetROM(rom); + } Rom* rom() const { return rom_; } // Editor system access @@ -43,6 +48,8 @@ class DungeonObjectSelector { // Palette access void set_current_palette_group_id(uint64_t id) { current_palette_group_id_ = id; } + void SetCurrentPaletteGroup(const gfx::PaletteGroup& palette_group) { current_palette_group_ = palette_group; } + void SetCurrentPaletteId(uint64_t palette_id) { current_palette_id_ = palette_id; } private: void DrawRoomGraphics(); @@ -69,6 +76,8 @@ class DungeonObjectSelector { // Palette data uint64_t current_palette_group_id_ = 0; + uint64_t current_palette_id_ = 0; + gfx::PaletteGroup current_palette_group_; // Object preview system zelda3::RoomObject preview_object_{0, 0, 0, 0, 0}; diff --git a/src/app/editor/dungeon/dungeon_room_selector.cc b/src/app/editor/dungeon/dungeon_room_selector.cc index 9578a339..c6d2c2c7 100644 --- a/src/app/editor/dungeon/dungeon_room_selector.cc +++ b/src/app/editor/dungeon/dungeon_room_selector.cc @@ -10,9 +10,28 @@ namespace yaze::editor { using ImGui::BeginChild; +using ImGui::BeginTabBar; +using ImGui::BeginTabItem; using ImGui::EndChild; +using ImGui::EndTabBar; +using ImGui::EndTabItem; using ImGui::Separator; using ImGui::SameLine; +using ImGui::Text; + +void DungeonRoomSelector::Draw() { + if (ImGui::BeginTabBar("##DungeonRoomTabBar")) { + if (ImGui::BeginTabItem("Rooms")) { + DrawRoomSelector(); + ImGui::EndTabItem(); + } + if (ImGui::BeginTabItem("Entrances")) { + DrawEntranceSelector(); + ImGui::EndTabItem(); + } + ImGui::EndTabBar(); + } +} void DungeonRoomSelector::DrawRoomSelector() { if (!rom_ || !rom_->is_loaded()) { diff --git a/src/app/editor/dungeon/dungeon_room_selector.h b/src/app/editor/dungeon/dungeon_room_selector.h index 4af32e71..4ed5893d 100644 --- a/src/app/editor/dungeon/dungeon_room_selector.h +++ b/src/app/editor/dungeon/dungeon_room_selector.h @@ -17,6 +17,7 @@ class DungeonRoomSelector { public: explicit DungeonRoomSelector(Rom* rom = nullptr) : rom_(rom) {} + void Draw(); void DrawRoomSelector(); void DrawEntranceSelector();