From 04909dc3d02e704b0b77a7c39e2df201912a7786 Mon Sep 17 00:00:00 2001 From: scawful Date: Wed, 24 Sep 2025 23:00:44 -0400 Subject: [PATCH] Add Comprehensive Guide for Yaze Dungeon Editor - Introduced a new documentation file detailing the Yaze Dungeon Editor, covering its architecture, core components, coordinate system, object rendering system, user interface, and integration with ZScream. - Included sections on usage examples, testing and validation strategies, and future enhancements to provide a complete overview of the editor's capabilities. - Removed the outdated dungeon object rendering refactor documentation to streamline resources and focus on the comprehensive guide. - Enhanced clarity and organization of the documentation to support both novice and expert users in effectively utilizing the dungeon editing tools. --- docs/dungeon-editor-comprehensive-guide.md | 360 ++++++++++++++++++++ docs/dungeon-object-rendering-refactor.md | 175 ---------- src/app/editor/dungeon/dungeon_editor.cc | 365 ++++++++++++--------- src/app/editor/dungeon/dungeon_editor.h | 11 +- 4 files changed, 585 insertions(+), 326 deletions(-) create mode 100644 docs/dungeon-editor-comprehensive-guide.md delete mode 100644 docs/dungeon-object-rendering-refactor.md diff --git a/docs/dungeon-editor-comprehensive-guide.md b/docs/dungeon-editor-comprehensive-guide.md new file mode 100644 index 00000000..1bb6d9ee --- /dev/null +++ b/docs/dungeon-editor-comprehensive-guide.md @@ -0,0 +1,360 @@ +# Yaze Dungeon Editor - Comprehensive Guide + +## Overview + +The Yaze Dungeon Editor is a comprehensive tool for editing Zelda 3: A Link to the Past dungeon rooms, objects, sprites, items, entrances, doors, and chests. It provides an integrated editing experience with real-time rendering, coordinate system management, and advanced features for dungeon modification. + +## Architecture + +### Core Components + +#### 1. DungeonEditorSystem +- **Purpose**: Central coordinator for all dungeon editing operations +- **Location**: `src/app/zelda3/dungeon/dungeon_editor_system.h/cc` +- **Features**: + - Room management (loading, saving, creating, deleting) + - Sprite management (enemies, NPCs, interactive objects) + - Item management (keys, hearts, rupees, etc.) + - Entrance/exit management (room connections) + - Door management (locked doors, key requirements) + - Chest management (treasure placement) + - Undo/redo system + - Event callbacks for real-time updates + +#### 2. DungeonObjectEditor +- **Purpose**: Specialized editor for room objects (walls, floors, decorations) +- **Location**: `src/app/zelda3/dungeon/dungeon_object_editor.h/cc` +- **Features**: + - Object placement and editing + - Layer management (BG1, BG2, BG3) + - Object size editing with scroll wheel + - Collision detection and validation + - Selection and multi-selection + - Grid snapping + - Real-time preview + +#### 3. ObjectRenderer +- **Purpose**: High-performance rendering system for dungeon objects +- **Location**: `src/app/zelda3/dungeon/object_renderer.h/cc` +- **Features**: + - Graphics cache for performance optimization + - Memory pool management + - Performance monitoring and statistics + - Object parsing from ROM data + - Palette support and color management + - Batch rendering for efficiency + +#### 4. DungeonEditor (UI Layer) +- **Purpose**: User interface and interaction handling +- **Location**: `src/app/editor/dungeon/dungeon_editor.h/cc` +- **Features**: + - Integrated tabbed interface + - Canvas-based room editing + - Coordinate system management + - Object preview system + - Real-time rendering + - Compact editing panels + +## Coordinate System + +### Room Coordinates vs Canvas Coordinates + +The dungeon editor uses a two-tier coordinate system: + +1. **Room Coordinates**: 16x16 tile units (as used in the ROM) +2. **Canvas Coordinates**: Pixel coordinates for rendering + +#### Conversion Functions + +```cpp +// Convert room coordinates to canvas coordinates +std::pair RoomToCanvasCoordinates(int room_x, int room_y) const { + return {room_x * 16, room_y * 16}; +} + +// Convert canvas coordinates to room coordinates +std::pair CanvasToRoomCoordinates(int canvas_x, int canvas_y) const { + return {canvas_x / 16, canvas_y / 16}; +} + +// Check if coordinates are within canvas bounds +bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const; +``` + +### Coordinate System Features + +- **Automatic Bounds Checking**: Objects outside visible canvas area are culled +- **Scrolling Support**: Canvas handles scrolling internally with proper coordinate transformation +- **Grid Alignment**: 16x16 pixel grid for precise object placement +- **Margin Support**: Configurable margins for partial object visibility + +## Object Rendering System + +### Object Types + +The system supports three main object subtypes based on ROM structure: + +1. **Subtype 1** (0x00-0xFF): Standard room objects (walls, floors, decorations) +2. **Subtype 2** (0x100-0x1FF): Interactive objects (doors, switches, chests) +3. **Subtype 3** (0x200+): Special objects (stairs, warps, bosses) + +### Rendering Pipeline + +1. **Object Loading**: Objects are loaded from ROM data using `LoadObjects()` +2. **Tile Parsing**: Object tiles are parsed using `ObjectParser` +3. **Graphics Caching**: Frequently used graphics are cached for performance +4. **Palette Application**: SNES palettes are applied to object graphics +5. **Canvas Rendering**: Objects are rendered to canvas with proper coordinate transformation + +### Performance Optimizations + +- **Graphics Cache**: Reduces redundant graphics sheet loading +- **Memory Pool**: Efficient memory allocation for rendering +- **Batch Rendering**: Multiple objects rendered in single pass +- **Bounds Culling**: Objects outside visible area are skipped +- **Cache Invalidation**: Smart cache management based on palette changes + +## User Interface + +### Integrated Editing Panels + +The dungeon editor features a consolidated interface with: + +#### Main Canvas +- **Room Visualization**: Real-time room rendering with background layers +- **Object Display**: Objects rendered with proper positioning and sizing +- **Interactive Editing**: Click-to-select, drag-to-move, scroll-to-resize +- **Grid Overlay**: Optional grid display for precise positioning +- **Coordinate Display**: Real-time coordinate information + +#### Compact Editing Panels + +1. **Object Editor** + - Mode selection (Select, Insert, Edit, Delete) + - Layer management (BG1, BG2, BG3) + - Object type selection + - Size editing with scroll wheel + - Configuration options (snap to grid, show grid) + +2. **Sprite Editor** + - Sprite placement and management + - Enemy and NPC configuration + - Layer assignment + - Quick sprite addition + +3. **Item Editor** + - Item placement (keys, hearts, rupees) + - Hidden item configuration + - Item type selection + - Room assignment + +4. **Entrance Editor** + - Room connection management + - Bidirectional connection support + - Position configuration + - Connection validation + +5. **Door Editor** + - Door placement and configuration + - Lock status management + - Key requirement setup + - Direction and target room assignment + +6. **Chest Editor** + - Treasure chest placement + - Item and quantity configuration + - Big chest support + - Opened status tracking + +7. **Properties Editor** + - Room metadata management + - Dungeon settings + - Music and ambient sound configuration + - Boss room and save room flags + +### Object Preview System + +- **Real-time Preview**: Objects are previewed in the canvas as they're selected +- **Centered Display**: Preview objects are centered in the canvas for optimal viewing +- **Palette Support**: Previews use current palette settings +- **Information Display**: Object properties are shown in preview window + +## Integration with ZScream + +The dungeon editor is designed to be compatible with ZScream C# patterns: + +### Room Loading +- Uses same room loading patterns as ZScream +- Compatible with ZScream room data structures +- Supports ZScream room naming conventions + +### Object Parsing +- Follows ZScream object parsing logic +- Compatible with ZScream object type definitions +- Supports ZScream size encoding + +### Coordinate System +- Matches ZScream coordinate conventions +- Uses same tile size calculations +- Compatible with ZScream positioning logic + +## Testing and Validation + +### Integration Tests + +The system includes comprehensive integration tests: + +1. **Basic Object Rendering**: Tests fundamental object rendering functionality +2. **Multi-Palette Rendering**: Tests rendering with different palettes +3. **Real Room Object Rendering**: Tests with actual ROM room data +4. **Disassembly Room Validation**: Tests specific rooms from disassembly +5. **Performance Testing**: Measures rendering performance and memory usage +6. **Cache Effectiveness**: Tests graphics cache performance +7. **Error Handling**: Tests error conditions and edge cases + +### Test Data + +Tests use real ROM data from `build/bin/zelda3.sfc`: +- **Room 0x0000**: Ganon's room (from disassembly) +- **Room 0x0002, 0x0012**: Sewer rooms (from disassembly) +- **Room 0x0020**: Agahnim's tower (from disassembly) +- **Additional rooms**: 0x0001, 0x0010, 0x0033, 0x005A + +### Performance Benchmarks + +- **Rendering Time**: < 500ms for 100 objects +- **Memory Usage**: < 100MB for large object sets +- **Cache Hit Rate**: Optimized for frequent object access +- **Coordinate Conversion**: O(1) coordinate transformation + +## Usage Examples + +### Basic Object Editing + +```cpp +// Load a room +auto room_result = dungeon_editor_system_->GetRoom(0x0000); + +// Add an object +auto status = object_editor_->InsertObject(5, 5, 0x10, 0x12, 0); +// Parameters: x, y, object_type, size, layer + +// Render objects +auto result = object_renderer_->RenderObjects(objects, palette); +``` + +### Coordinate Conversion + +```cpp +// Convert room coordinates to canvas coordinates +auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(room_x, room_y); + +// Check if coordinates are within bounds +if (IsWithinCanvasBounds(canvas_x, canvas_y)) { + // Render object at this position +} +``` + +### Object Preview + +```cpp +// Create preview object +auto preview_object = zelda3::RoomObject(id, 8, 8, 0x12, 0); +preview_object.set_rom(rom_); +preview_object.EnsureTilesLoaded(); + +// Render preview +auto result = object_renderer_->RenderObject(preview_object, palette); +``` + +## Configuration Options + +### Editor Configuration + +```cpp +struct EditorConfig { + bool snap_to_grid = true; + int grid_size = 16; + bool show_grid = true; + bool show_preview = true; + bool auto_save = false; + int auto_save_interval = 300; + bool validate_objects = true; + bool show_collision_bounds = false; +}; +``` + +### Performance Configuration + +```cpp +// Object renderer settings +object_renderer_->SetCacheSize(100); +object_renderer_->EnablePerformanceMonitoring(true); + +// Canvas settings +canvas_.SetCanvasSize(ImVec2(512, 512)); +canvas_.set_draggable(true); +``` + +## Troubleshooting + +### Common Issues + +1. **Objects Not Displaying** + - Check if ROM is loaded + - Verify object tiles are loaded with `EnsureTilesLoaded()` + - Check coordinate bounds with `IsWithinCanvasBounds()` + +2. **Coordinate Misalignment** + - Use coordinate conversion functions + - Check canvas scrolling settings + - Verify grid alignment + +3. **Performance Issues** + - Enable graphics caching + - Check memory usage with `GetMemoryUsage()` + - Monitor performance stats with `GetPerformanceStats()` + +4. **Preview Not Showing** + - Verify object is within canvas bounds + - Check palette is properly set + - Ensure object has valid tiles + +### Debug Information + +The system provides comprehensive debug information: +- Object count and statistics +- Cache hit/miss rates +- Memory usage tracking +- Performance metrics +- Coordinate system validation + +## Future Enhancements + +### Planned Features + +1. **Advanced Object Editing** + - Multi-object selection and manipulation + - Object grouping and layers + - Advanced collision detection + +2. **Enhanced Rendering** + - Real-time lighting effects + - Animation support + - Advanced shader effects + +3. **Improved UX** + - Keyboard shortcuts + - Context menus + - Undo/redo visualization + +4. **Integration Features** + - ZScream project import/export + - Collaborative editing + - Version control integration + +## Conclusion + +The Yaze Dungeon Editor provides a comprehensive, high-performance solution for editing Zelda 3 dungeon rooms. With its integrated interface, robust coordinate system, and advanced rendering capabilities, it offers both novice and expert users the tools needed to create and modify dungeon content effectively. + +The system's compatibility with ZScream patterns and comprehensive testing ensure reliability and consistency with existing tools, while its modern architecture provides a foundation for future enhancements and features. diff --git a/docs/dungeon-object-rendering-refactor.md b/docs/dungeon-object-rendering-refactor.md deleted file mode 100644 index 18436e8d..00000000 --- a/docs/dungeon-object-rendering-refactor.md +++ /dev/null @@ -1,175 +0,0 @@ -# Dungeon Object Rendering Refactor - -## Overview - -This document describes the comprehensive refactoring of the dungeon object rendering system in YAZE, replacing the SNES emulation approach with direct ROM parsing for better performance, reliability, and maintainability. - -## Problem Statement - -The original dungeon object rendering system had several issues: - -1. **SNES Emulation Complexity**: Used full SNES CPU emulation to render objects, which was slow and error-prone -2. **Poor Performance**: Emulating thousands of CPU instructions for simple object rendering -3. **Maintenance Issues**: Complex emulation code was difficult to debug and maintain -4. **Limited Testing**: Hard to test without real ROM files and complex emulation state -5. **Architectural Problems**: Tight coupling between rendering and emulation systems - -## Solution Architecture - -### New Components - -#### 1. ObjectParser (`src/app/zelda3/dungeon/object_parser.h/cc`) -- **Purpose**: Direct ROM parsing for object data without emulation -- **Features**: - - Parses all three object subtypes (0x00-0xFF, 0x100-0x1FF, 0x200+) - - Extracts tile data directly from ROM tables - - Provides object size and orientation information - - Handles object routine information - -#### 2. ObjectRenderer (`src/app/zelda3/dungeon/object_renderer.h/cc`) -- **Purpose**: High-performance object rendering using parsed data -- **Features**: - - Renders single objects or multiple objects - - Supports size and orientation variations - - Provides object previews for UI - - Direct bitmap generation without emulation - -#### 3. Enhanced RoomObject (`src/app/zelda3/dungeon/room_object.h/cc`) -- **Purpose**: Improved object representation with better tile loading -- **Features**: - - Uses ObjectParser for tile loading - - Fallback to legacy method for compatibility - - Better error handling and validation - -#### 4. Simplified Test Framework (`test/test_dungeon_objects.h/cc`) -- **Purpose**: Comprehensive testing without real ROM files -- **Features**: - - Extended MockRom class for testing - - Simplified test structure using test folder prefix - - Performance benchmarks - - Modular component testing - -## Implementation Details - -### Object Parsing - -The new system directly parses object data from ROM tables: - -```cpp -// Old approach: SNES emulation -snes_.cpu().ExecuteInstruction(opcode); // Thousands of instructions - -// New approach: Direct parsing -auto tiles = parser.ParseObject(object_id); // Direct ROM access -``` - -### Object Rendering - -Objects are now rendered directly to bitmaps: - -```cpp -// Create object with tiles -auto object = RoomObject(id, x, y, size, layer); -object.set_rom(rom); -object.EnsureTilesLoaded(); // Uses ObjectParser - -// Render to bitmap -auto bitmap = renderer.RenderObject(object, palette); -``` - -### Testing Strategy - -The new system includes comprehensive testing: - -1. **Unit Tests**: Individual component testing -2. **Integration Tests**: End-to-end pipeline testing -3. **Mock Data**: Testing without real ROM files -4. **Performance Tests**: Benchmarking improvements - -## Performance Improvements - -### Before (SNES Emulation) -- **Object Rendering**: ~1000+ CPU instructions per object -- **Memory Usage**: Full SNES memory state (128KB+) -- **Complexity**: O(n) where n = instruction count -- **Debugging**: Difficult due to emulation state - -### After (Direct Parsing) -- **Object Rendering**: ~10-20 ROM reads per object -- **Memory Usage**: Minimal (just parsed data) -- **Complexity**: O(1) for most operations -- **Debugging**: Simple, direct code paths - -## API Changes - -### DungeonEditor -```cpp -// Old -// zelda3::DungeonObjectRenderer object_renderer_; - -// New -zelda3::ObjectRenderer object_renderer_; -``` - -### RoomObject -```cpp -// New methods -void EnsureTilesLoaded(); // Uses ObjectParser -absl::Status LoadTilesWithParser(); // Direct parsing -``` - -## Testing - -### Running Tests -```bash -# Build and run all tests -cd build -make yaze_test -./yaze_test - -# Run specific test suites -./yaze_test --gtest_filter="*ObjectParser*" -./yaze_test --gtest_filter="*ObjectRenderer*" -./yaze_test --gtest_filter="*TestDungeonObjects*" -``` - -### Test Coverage -- **ObjectParser**: 100% method coverage -- **ObjectRenderer**: 100% method coverage -- **Integration Tests**: Complete pipeline coverage -- **Mock Data**: Realistic test scenarios - -## Migration Guide - -### For Developers - -1. **Replace Old Renderer**: Use `ObjectRenderer` instead of `DungeonObjectRenderer` -2. **Update Object Loading**: Use `EnsureTilesLoaded()` for automatic tile loading -3. **Add Error Handling**: Check return values from new methods -4. **Update Tests**: Use new mock framework for testing - -### For Users - -- **No Breaking Changes**: All existing functionality preserved -- **Better Performance**: Faster object rendering -- **More Reliable**: Fewer emulation-related bugs -- **Better UI**: Improved object previews - -## Future Improvements - -1. **Caching**: Add tile data caching for repeated objects -2. **Batch Rendering**: Render multiple objects in single operation -3. **GPU Acceleration**: Use GPU for large-scale rendering -4. **Real-time Preview**: Live object preview in editor -5. **Animation Support**: Animated object rendering - -## Conclusion - -The refactored dungeon object rendering system provides: - -- **10x Performance Improvement**: Direct parsing vs emulation -- **Better Maintainability**: Cleaner, more focused code -- **Comprehensive Testing**: Full test coverage with mocks -- **Future-Proof Architecture**: Extensible design for new features - -This refactoring establishes a solid foundation for future dungeon editing features while maintaining backward compatibility and improving overall system reliability. \ No newline at end of file diff --git a/src/app/editor/dungeon/dungeon_editor.cc b/src/app/editor/dungeon/dungeon_editor.cc index 535779b4..9c1b3a60 100644 --- a/src/app/editor/dungeon/dungeon_editor.cc +++ b/src/app/editor/dungeon/dungeon_editor.cc @@ -10,12 +10,11 @@ #include "app/gui/icons.h" #include "app/gui/input.h" #include "app/rom.h" -#include "app/zelda3/dungeon/room.h" #include "app/zelda3/dungeon/dungeon_editor_system.h" #include "app/zelda3/dungeon/dungeon_object_editor.h" #include "app/zelda3/dungeon/object_renderer.h" +#include "app/zelda3/dungeon/room.h" #include "imgui/imgui.h" -#include "imgui_memory_editor.h" #include "util/hex.h" namespace yaze::editor { @@ -45,7 +44,8 @@ constexpr ImGuiTableFlags kDungeonObjectTableFlags = void DungeonEditor::Initialize() { if (rom_ && !dungeon_editor_system_) { - dungeon_editor_system_ = std::make_unique(rom_); + dungeon_editor_system_ = + std::make_unique(rom_); object_editor_ = std::make_shared(rom_); } } @@ -91,7 +91,7 @@ absl::Status DungeonEditor::Load() { gfx::CreatePaletteGroupFromLargePalette(full_palette_)); CalculateUsageStats(); - + // Initialize the new editor system if (dungeon_editor_system_) { auto status = dungeon_editor_system_->Initialize(); @@ -99,7 +99,7 @@ absl::Status DungeonEditor::Load() { return status; } } - + is_loaded_ = true; return absl::OkStatus(); } @@ -115,7 +115,7 @@ absl::Status DungeonEditor::Update() { status_ = UpdateDungeonRoomView(); ImGui::EndTabItem(); } - + if (ImGui::BeginTabItem("Usage Statistics")) { if (is_loaded_) { DrawUsageStats(); @@ -258,8 +258,9 @@ void DungeonEditor::DrawToolset() { if (BeginTable("DWToolset", 16, ImGuiTableFlags_SizingFixedFit, ImVec2(0, 0))) { static std::array tool_names = { - "Undo", "Redo", "Separator", "Any", "BG1", "BG2", "BG3", - "Separator", "Object", "Sprite", "Item", "Entrance", "Door", "Chest", "Block", "Palette"}; + "Undo", "Redo", "Separator", "Any", "BG1", "BG2", + "BG3", "Separator", "Object", "Sprite", "Item", "Entrance", + "Door", "Chest", "Block", "Palette"}; std::ranges::for_each(tool_names, [](const char *name) { TableSetupColumn(name); }); @@ -663,9 +664,9 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) { // Render regular room objects for (const auto &object : rooms_[room_id].GetTileObjects()) { - // Convert room coordinates to canvas coordinates - int canvas_x = object.x_ * 16; - int canvas_y = object.y_ * 16; + // Convert room coordinates to canvas coordinates using helper function + auto [canvas_x, canvas_y] = + RoomToCanvasCoordinates(object.x_, object.y_); if (show_objects) { // Draw object outline - use size_ to determine dimensions @@ -682,7 +683,8 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) { outline_height = (height_bits + 1) * 16; } - canvas_.DrawOutline(object.x_, object.y_, outline_width, + // Use canvas coordinates for outline + canvas_.DrawOutline(canvas_x, canvas_y, outline_width, outline_height); } @@ -709,9 +711,8 @@ void DungeonEditor::RenderObjectInCanvas(const zelda3::RoomObject &object, mutable_object.set_rom(rom_); mutable_object.EnsureTilesLoaded(); - // Check if tiles were loaded successfully using the new method - auto tiles_result = mutable_object.GetTiles(); - if (!tiles_result.ok() || tiles_result->empty()) { + // Check if tiles were loaded successfully + if (mutable_object.tiles().empty()) { return; // Skip objects without tiles } @@ -722,14 +723,20 @@ void DungeonEditor::RenderObjectInCanvas(const zelda3::RoomObject &object, (palette_hash << 6) + (palette_hash >> 2); } + // Convert room coordinates to canvas coordinates using helper function + auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_); + + // Check if object is within canvas bounds (accounting for scrolling) + if (!IsWithinCanvasBounds(canvas_x, canvas_y, 32)) { + return; // Skip objects outside visible area + } + // Check cache first for (auto &cached : object_render_cache_) { if (cached.object_id == object.id_ && cached.object_x == object.x_ && cached.object_y == object.y_ && cached.object_size == object.size_ && cached.palette_hash == palette_hash && cached.is_valid) { - // Use cached bitmap - int canvas_x = object.x_ * 16; - int canvas_y = object.y_ * 16; + // Use cached bitmap - canvas handles scrolling internally canvas_.DrawBitmap(cached.rendered_bitmap, canvas_x, canvas_y, 1.0f, 255); return; } @@ -749,12 +756,8 @@ void DungeonEditor::RenderObjectInCanvas(const zelda3::RoomObject &object, // Render the bitmap to a texture so it can be drawn core::Renderer::Get().RenderBitmap(&object_bitmap); - // Convert room coordinates to canvas coordinates - // Room coordinates are in 16x16 tile units, canvas coordinates are in pixels - int canvas_x = object.x_ * 16; - int canvas_y = object.y_ * 16; - - // Draw the object bitmap to the canvas immediately + // Draw the object bitmap to the canvas + // Canvas will handle scrolling and coordinate transformation canvas_.DrawBitmap(object_bitmap, canvas_x, canvas_y, 1.0f, 255); // Cache the rendered bitmap (create a copy for caching) @@ -790,8 +793,14 @@ void DungeonEditor::RenderLayoutObjects(const zelda3::RoomLayout &layout, // This provides a visual representation of the room's structure for (const auto &layout_obj : layout.GetObjects()) { - int canvas_x = layout_obj.x() * 16; - int canvas_y = layout_obj.y() * 16; + // Convert room coordinates to canvas coordinates using helper function + auto [canvas_x, canvas_y] = + RoomToCanvasCoordinates(layout_obj.x(), layout_obj.y()); + + // Check if layout object is within canvas bounds + if (!IsWithinCanvasBounds(canvas_x, canvas_y, 16)) { + continue; // Skip objects outside visible area + } // Choose color based on object type gfx::SnesColor color; @@ -895,7 +904,8 @@ void DungeonEditor::DrawObjectRenderer() { selected_object = i; // Create a test object and render it - auto test_object = zelda3::RoomObject(i, 0, 0, 0x12, 0); + auto test_object = + zelda3::RoomObject(i, 8, 8, 0x12, 0); // Center in canvas test_object.set_rom(rom_); test_object.EnsureTilesLoaded(); @@ -907,6 +917,8 @@ void DungeonEditor::DrawObjectRenderer() { auto result = object_renderer_.GetObjectPreview(test_object, palette); if (result.ok()) { object_loaded_ = true; + preview_object_ = test_object; // Store for rendering + preview_palette_ = palette; } } i += 1; @@ -922,6 +934,24 @@ void DungeonEditor::DrawObjectRenderer() { object_canvas_.DrawContextMenu(); object_canvas_.DrawTileSelector(32); object_canvas_.DrawGrid(32.0f); + + // Render object preview if available + if (object_loaded_ && preview_object_.id_ >= 0) { + // Render preview object at center of canvas + int preview_x = 8 * 16; // Center horizontally (8 tiles * 16 pixels) + int preview_y = 8 * 16; // Center vertically + + auto preview_result = + object_renderer_.RenderObject(preview_object_, preview_palette_); + if (preview_result.ok()) { + auto preview_bitmap = std::move(preview_result.value()); + preview_bitmap.SetPalette(preview_palette_); + core::Renderer::Get().RenderBitmap(&preview_bitmap); + object_canvas_.DrawBitmap(preview_bitmap, preview_x, preview_y, 1.0f, + 255); + } + } + object_canvas_.DrawOverlay(); EndChild(); @@ -930,7 +960,10 @@ void DungeonEditor::DrawObjectRenderer() { if (object_loaded_) { ImGui::Begin("Object Preview", &object_loaded_, 0); - ImGui::Text("Object rendered successfully using improved renderer!"); + ImGui::Text("Object ID: 0x%02X", preview_object_.id_); + ImGui::Text("Position: (%d, %d)", preview_object_.x_, preview_object_.y_); + ImGui::Text("Size: 0x%02X", preview_object_.size_); + ImGui::Text("Layer: %d", static_cast(preview_object_.layer_)); ImGui::End(); } } @@ -1165,7 +1198,8 @@ void DungeonEditor::DrawUsageGrid() { Text("Floor1: %#02x", room.floor1); Text("Floor2: %#02x", room.floor2); Text("Message ID: %#04x", room.message_id_); - Text("Size: %#016llx", room_sizes_[row * squaresWide + col]); + Text("Size: %#016llx", static_cast( + room_sizes_[row * squaresWide + col])); Text("Size Pointer: %#016llx", room_size_pointers_[row * squaresWide + col]); ImGui::EndTooltip(); @@ -1189,9 +1223,8 @@ void DungeonEditor::DrawObjectEditor() { // Display current editing mode auto mode = object_editor_->GetMode(); - const char* mode_names[] = { - "Select", "Insert", "Delete", "Edit", "Layer", "Preview" - }; + const char *mode_names[] = {"Select", "Insert", "Delete", + "Edit", "Layer", "Preview"}; ImGui::Text("Mode: %s", mode_names[static_cast(mode)]); // Mode selection @@ -1236,7 +1269,7 @@ void DungeonEditor::DrawObjectEditor() { // Object count and selection info ImGui::Separator(); ImGui::Text("Objects: %zu", object_editor_->GetObjectCount()); - + auto selection = object_editor_->GetSelection(); if (!selection.selected_objects.empty()) { ImGui::Text("Selected: %zu objects", selection.selected_objects.size()); @@ -1244,8 +1277,10 @@ void DungeonEditor::DrawObjectEditor() { // Undo/Redo status ImGui::Separator(); - ImGui::Text("Undo: %s", object_editor_->CanUndo() ? "Available" : "Not Available"); - ImGui::Text("Redo: %s", object_editor_->CanRedo() ? "Available" : "Not Available"); + ImGui::Text("Undo: %s", + object_editor_->CanUndo() ? "Available" : "Not Available"); + ImGui::Text("Redo: %s", + object_editor_->CanRedo() ? "Available" : "Not Available"); } void DungeonEditor::DrawSpriteEditor() { @@ -1260,18 +1295,18 @@ void DungeonEditor::DrawSpriteEditor() { // Display current room sprites auto current_room = dungeon_editor_system_->GetCurrentRoom(); auto sprites_result = dungeon_editor_system_->GetSpritesByRoom(current_room); - + if (sprites_result.ok()) { auto sprites = sprites_result.value(); ImGui::Text("Sprites in room %d: %zu", current_room, sprites.size()); - - for (const auto& sprite : sprites) { - ImGui::Text("ID: %d, Type: %d, Position: (%d, %d)", - sprite.sprite_id, static_cast(sprite.type), - sprite.x, sprite.y); + + for (const auto &sprite : sprites) { + ImGui::Text("ID: %d, Type: %d, Position: (%d, %d)", sprite.sprite_id, + static_cast(sprite.type), sprite.x, sprite.y); } } else { - ImGui::Text("Error loading sprites: %s", sprites_result.status().message().data()); + ImGui::Text("Error loading sprites: %s", + sprites_result.status().message().data()); } // Sprite placement controls @@ -1294,7 +1329,7 @@ void DungeonEditor::DrawSpriteEditor() { sprite_data.x = new_sprite_x; sprite_data.y = new_sprite_y; sprite_data.layer = new_sprite_layer; - + auto status = dungeon_editor_system_->AddSprite(sprite_data); if (!status.ok()) { ImGui::Text("Error adding sprite: %s", status.message().data()); @@ -1314,18 +1349,19 @@ void DungeonEditor::DrawItemEditor() { // Display current room items auto current_room = dungeon_editor_system_->GetCurrentRoom(); auto items_result = dungeon_editor_system_->GetItemsByRoom(current_room); - + if (items_result.ok()) { auto items = items_result.value(); ImGui::Text("Items in room %d: %zu", current_room, items.size()); - - for (const auto& item : items) { - ImGui::Text("ID: %d, Type: %d, Position: (%d, %d), Hidden: %s", - item.item_id, static_cast(item.type), - item.x, item.y, item.is_hidden ? "Yes" : "No"); + + for (const auto &item : items) { + ImGui::Text("ID: %d, Type: %d, Position: (%d, %d), Hidden: %s", + item.item_id, static_cast(item.type), item.x, item.y, + item.is_hidden ? "Yes" : "No"); } } else { - ImGui::Text("Error loading items: %s", items_result.status().message().data()); + ImGui::Text("Error loading items: %s", + items_result.status().message().data()); } // Item placement controls @@ -1349,7 +1385,7 @@ void DungeonEditor::DrawItemEditor() { item_data.y = new_item_y; item_data.room_id = current_room; item_data.is_hidden = new_item_hidden; - + auto status = dungeon_editor_system_->AddItem(item_data); if (!status.ok()) { ImGui::Text("Error adding item: %s", status.message().data()); @@ -1368,19 +1404,22 @@ void DungeonEditor::DrawEntranceEditor() { // Display current room entrances auto current_room = dungeon_editor_system_->GetCurrentRoom(); - auto entrances_result = dungeon_editor_system_->GetEntrancesByRoom(current_room); - + auto entrances_result = + dungeon_editor_system_->GetEntrancesByRoom(current_room); + if (entrances_result.ok()) { auto entrances = entrances_result.value(); ImGui::Text("Entrances in room %d: %zu", current_room, entrances.size()); - - for (const auto& entrance : entrances) { - ImGui::Text("ID: %d, Type: %d, Target Room: %d, Target Position: (%d, %d)", - entrance.entrance_id, static_cast(entrance.type), - entrance.target_room_id, entrance.target_x, entrance.target_y); + + for (const auto &entrance : entrances) { + ImGui::Text( + "ID: %d, Type: %d, Target Room: %d, Target Position: (%d, %d)", + entrance.entrance_id, static_cast(entrance.type), + entrance.target_room_id, entrance.target_x, entrance.target_y); } } else { - ImGui::Text("Error loading entrances: %s", entrances_result.status().message().data()); + ImGui::Text("Error loading entrances: %s", + entrances_result.status().message().data()); } // Entrance creation controls @@ -1399,9 +1438,8 @@ void DungeonEditor::DrawEntranceEditor() { ImGui::InputInt("Target Y", &target_y); if (ImGui::Button("Connect Rooms")) { - auto status = dungeon_editor_system_->ConnectRooms(current_room, target_room_id, - source_x, source_y, - target_x, target_y); + auto status = dungeon_editor_system_->ConnectRooms( + current_room, target_room_id, source_x, source_y, target_x, target_y); if (!status.ok()) { ImGui::Text("Error connecting rooms: %s", status.message().data()); } @@ -1420,17 +1458,19 @@ void DungeonEditor::DrawDoorEditor() { // Display current room doors auto current_room = dungeon_editor_system_->GetCurrentRoom(); auto doors_result = dungeon_editor_system_->GetDoorsByRoom(current_room); - + if (doors_result.ok()) { auto doors = doors_result.value(); ImGui::Text("Doors in room %d: %zu", current_room, doors.size()); - - for (const auto& door : doors) { - ImGui::Text("ID: %d, Position: (%d, %d), Direction: %d, Target Room: %d", - door.door_id, door.x, door.y, door.direction, door.target_room_id); + + for (const auto &door : doors) { + ImGui::Text("ID: %d, Position: (%d, %d), Direction: %d, Target Room: %d", + door.door_id, door.x, door.y, door.direction, + door.target_room_id); } } else { - ImGui::Text("Error loading doors: %s", doors_result.status().message().data()); + ImGui::Text("Error loading doors: %s", + doors_result.status().message().data()); } // Door creation controls @@ -1468,7 +1508,7 @@ void DungeonEditor::DrawDoorEditor() { door_data.is_locked = door_locked; door_data.requires_key = door_requires_key; door_data.key_type = door_key_type; - + auto status = dungeon_editor_system_->AddDoor(door_data); if (!status.ok()) { ImGui::Text("Error adding door: %s", status.message().data()); @@ -1488,19 +1528,20 @@ void DungeonEditor::DrawChestEditor() { // Display current room chests auto current_room = dungeon_editor_system_->GetCurrentRoom(); auto chests_result = dungeon_editor_system_->GetChestsByRoom(current_room); - + if (chests_result.ok()) { auto chests = chests_result.value(); ImGui::Text("Chests in room %d: %zu", current_room, chests.size()); - - for (const auto& chest : chests) { - ImGui::Text("ID: %d, Position: (%d, %d), Big: %s, Item: %d, Quantity: %d", - chest.chest_id, chest.x, chest.y, - chest.is_big_chest ? "Yes" : "No", - chest.item_id, chest.item_quantity); + + for (const auto &chest : chests) { + ImGui::Text("ID: %d, Position: (%d, %d), Big: %s, Item: %d, Quantity: %d", + chest.chest_id, chest.x, chest.y, + chest.is_big_chest ? "Yes" : "No", chest.item_id, + chest.item_quantity); } } else { - ImGui::Text("Error loading chests: %s", chests_result.status().message().data()); + ImGui::Text("Error loading chests: %s", + chests_result.status().message().data()); } // Chest creation controls @@ -1526,7 +1567,7 @@ void DungeonEditor::DrawChestEditor() { chest_data.is_big_chest = chest_big; chest_data.item_id = chest_item_id; chest_data.item_quantity = chest_item_quantity; - + auto status = dungeon_editor_system_->AddChest(chest_data); if (!status.ok()) { ImGui::Text("Error adding chest: %s", status.message().data()); @@ -1542,49 +1583,48 @@ void DungeonEditor::DrawIntegratedEditingPanels() { // Create a tabbed interface for different editing modes if (ImGui::BeginTabBar("##EditingPanels")) { - // Object Editor Tab if (ImGui::BeginTabItem("Objects")) { DrawCompactObjectEditor(); ImGui::EndTabItem(); } - + // Sprite Editor Tab if (ImGui::BeginTabItem("Sprites")) { DrawCompactSpriteEditor(); ImGui::EndTabItem(); } - + // Item Editor Tab if (ImGui::BeginTabItem("Items")) { DrawCompactItemEditor(); ImGui::EndTabItem(); } - + // Entrance Editor Tab if (ImGui::BeginTabItem("Entrances")) { DrawCompactEntranceEditor(); ImGui::EndTabItem(); } - + // Door Editor Tab if (ImGui::BeginTabItem("Doors")) { DrawCompactDoorEditor(); ImGui::EndTabItem(); } - + // Chest Editor Tab if (ImGui::BeginTabItem("Chests")) { DrawCompactChestEditor(); ImGui::EndTabItem(); } - + // Properties Tab if (ImGui::BeginTabItem("Properties")) { DrawCompactPropertiesEditor(); ImGui::EndTabItem(); } - + ImGui::EndTabBar(); } } @@ -1595,17 +1635,19 @@ void DungeonEditor::DrawCompactObjectEditor() { // Display current editing mode auto mode = object_editor_->GetMode(); - const char* mode_names[] = { - "Select", "Insert", "Delete", "Edit", "Layer", "Preview" - }; + const char *mode_names[] = {"Select", "Insert", "Delete", + "Edit", "Layer", "Preview"}; ImGui::Text("Mode: %s", mode_names[static_cast(mode)]); // Compact mode selection - if (ImGui::Button("Select")) object_editor_->SetMode(zelda3::DungeonObjectEditor::Mode::kSelect); + if (ImGui::Button("Select")) + object_editor_->SetMode(zelda3::DungeonObjectEditor::Mode::kSelect); ImGui::SameLine(); - if (ImGui::Button("Insert")) object_editor_->SetMode(zelda3::DungeonObjectEditor::Mode::kInsert); + if (ImGui::Button("Insert")) + object_editor_->SetMode(zelda3::DungeonObjectEditor::Mode::kInsert); ImGui::SameLine(); - if (ImGui::Button("Edit")) object_editor_->SetMode(zelda3::DungeonObjectEditor::Mode::kEdit); + if (ImGui::Button("Edit")) + object_editor_->SetMode(zelda3::DungeonObjectEditor::Mode::kEdit); // Layer and object type selection int current_layer = object_editor_->GetCurrentLayer(); @@ -1633,7 +1675,7 @@ void DungeonEditor::DrawCompactObjectEditor() { // Object count and selection info ImGui::Separator(); ImGui::Text("Objects: %zu", object_editor_->GetObjectCount()); - + auto selection = object_editor_->GetSelection(); if (!selection.selected_objects.empty()) { ImGui::Text("Selected: %zu", selection.selected_objects.size()); @@ -1657,18 +1699,17 @@ void DungeonEditor::DrawCompactSpriteEditor() { // Display current room sprites auto current_room = dungeon_editor_system_->GetCurrentRoom(); auto sprites_result = dungeon_editor_system_->GetSpritesByRoom(current_room); - + if (sprites_result.ok()) { auto sprites = sprites_result.value(); ImGui::Text("Sprites in room %d: %zu", current_room, sprites.size()); - + // Show first few sprites in compact format int display_count = std::min(3, static_cast(sprites.size())); for (int i = 0; i < display_count; ++i) { - const auto& sprite = sprites[i]; - ImGui::Text("ID:%d Type:%d (%d,%d)", - sprite.sprite_id, static_cast(sprite.type), - sprite.x, sprite.y); + const auto &sprite = sprites[i]; + ImGui::Text("ID:%d Type:%d (%d,%d)", sprite.sprite_id, + static_cast(sprite.type), sprite.x, sprite.y); } if (sprites.size() > 3) { ImGui::Text("... and %zu more", sprites.size() - 3); @@ -1680,15 +1721,15 @@ void DungeonEditor::DrawCompactSpriteEditor() { // Quick sprite placement ImGui::Separator(); ImGui::Text("Quick Add Sprite"); - + static int new_sprite_id = 0; static int new_sprite_x = 0; static int new_sprite_y = 0; - + ImGui::InputInt("ID", &new_sprite_id); ImGui::InputInt("X", &new_sprite_x); ImGui::InputInt("Y", &new_sprite_y); - + if (ImGui::Button("Add Sprite")) { zelda3::DungeonEditorSystem::SpriteData sprite_data; sprite_data.sprite_id = new_sprite_id; @@ -1696,7 +1737,7 @@ void DungeonEditor::DrawCompactSpriteEditor() { sprite_data.x = new_sprite_x; sprite_data.y = new_sprite_y; sprite_data.layer = 0; - + auto status = dungeon_editor_system_->AddSprite(sprite_data); if (!status.ok()) { ImGui::Text("Error adding sprite"); @@ -1711,18 +1752,17 @@ void DungeonEditor::DrawCompactItemEditor() { // Display current room items auto current_room = dungeon_editor_system_->GetCurrentRoom(); auto items_result = dungeon_editor_system_->GetItemsByRoom(current_room); - + if (items_result.ok()) { auto items = items_result.value(); ImGui::Text("Items in room %d: %zu", current_room, items.size()); - + // Show first few items in compact format int display_count = std::min(3, static_cast(items.size())); for (int i = 0; i < display_count; ++i) { - const auto& item = items[i]; - ImGui::Text("ID:%d Type:%d (%d,%d)", - item.item_id, static_cast(item.type), - item.x, item.y); + const auto &item = items[i]; + ImGui::Text("ID:%d Type:%d (%d,%d)", item.item_id, + static_cast(item.type), item.x, item.y); } if (items.size() > 3) { ImGui::Text("... and %zu more", items.size() - 3); @@ -1734,15 +1774,15 @@ void DungeonEditor::DrawCompactItemEditor() { // Quick item placement ImGui::Separator(); ImGui::Text("Quick Add Item"); - + static int new_item_id = 0; static int new_item_x = 0; static int new_item_y = 0; - + ImGui::InputInt("ID", &new_item_id); ImGui::InputInt("X", &new_item_x); ImGui::InputInt("Y", &new_item_y); - + if (ImGui::Button("Add Item")) { zelda3::DungeonEditorSystem::ItemData item_data; item_data.item_id = new_item_id; @@ -1751,7 +1791,7 @@ void DungeonEditor::DrawCompactItemEditor() { item_data.y = new_item_y; item_data.room_id = current_room; item_data.is_hidden = false; - + auto status = dungeon_editor_system_->AddItem(item_data); if (!status.ok()) { ImGui::Text("Error adding item"); @@ -1765,16 +1805,17 @@ void DungeonEditor::DrawCompactEntranceEditor() { // Display current room entrances auto current_room = dungeon_editor_system_->GetCurrentRoom(); - auto entrances_result = dungeon_editor_system_->GetEntrancesByRoom(current_room); - + auto entrances_result = + dungeon_editor_system_->GetEntrancesByRoom(current_room); + if (entrances_result.ok()) { auto entrances = entrances_result.value(); ImGui::Text("Entrances: %zu", entrances.size()); - - for (const auto& entrance : entrances) { - ImGui::Text("ID:%d -> Room:%d (%d,%d)", - entrance.entrance_id, entrance.target_room_id, - entrance.target_x, entrance.target_y); + + for (const auto &entrance : entrances) { + ImGui::Text("ID:%d -> Room:%d (%d,%d)", entrance.entrance_id, + entrance.target_room_id, entrance.target_x, + entrance.target_y); } } else { ImGui::Text("Error loading entrances"); @@ -1783,23 +1824,22 @@ void DungeonEditor::DrawCompactEntranceEditor() { // Quick room connection ImGui::Separator(); ImGui::Text("Connect Rooms"); - + static int target_room_id = 0; static int source_x = 0; static int source_y = 0; static int target_x = 0; static int target_y = 0; - + ImGui::InputInt("Target Room", &target_room_id); ImGui::InputInt("Source X", &source_x); ImGui::InputInt("Source Y", &source_y); ImGui::InputInt("Target X", &target_x); ImGui::InputInt("Target Y", &target_y); - + if (ImGui::Button("Connect")) { - auto status = dungeon_editor_system_->ConnectRooms(current_room, target_room_id, - source_x, source_y, - target_x, target_y); + auto status = dungeon_editor_system_->ConnectRooms( + current_room, target_room_id, source_x, source_y, target_x, target_y); if (!status.ok()) { ImGui::Text("Error connecting rooms"); } @@ -1813,14 +1853,14 @@ void DungeonEditor::DrawCompactDoorEditor() { // Display current room doors auto current_room = dungeon_editor_system_->GetCurrentRoom(); auto doors_result = dungeon_editor_system_->GetDoorsByRoom(current_room); - + if (doors_result.ok()) { auto doors = doors_result.value(); ImGui::Text("Doors: %zu", doors.size()); - - for (const auto& door : doors) { - ImGui::Text("ID:%d (%d,%d) -> Room:%d", - door.door_id, door.x, door.y, door.target_room_id); + + for (const auto &door : doors) { + ImGui::Text("ID:%d (%d,%d) -> Room:%d", door.door_id, door.x, door.y, + door.target_room_id); } } else { ImGui::Text("Error loading doors"); @@ -1829,17 +1869,17 @@ void DungeonEditor::DrawCompactDoorEditor() { // Quick door creation ImGui::Separator(); ImGui::Text("Add Door"); - + static int door_x = 0; static int door_y = 0; static int door_direction = 0; static int door_target_room = 0; - + ImGui::InputInt("X", &door_x); ImGui::InputInt("Y", &door_y); ImGui::SliderInt("Dir", &door_direction, 0, 3); ImGui::InputInt("Target", &door_target_room); - + if (ImGui::Button("Add Door")) { zelda3::DungeonEditorSystem::DoorData door_data; door_data.room_id = current_room; @@ -1852,7 +1892,7 @@ void DungeonEditor::DrawCompactDoorEditor() { door_data.is_locked = false; door_data.requires_key = false; door_data.key_type = 0; - + auto status = dungeon_editor_system_->AddDoor(door_data); if (!status.ok()) { ImGui::Text("Error adding door"); @@ -1867,14 +1907,14 @@ void DungeonEditor::DrawCompactChestEditor() { // Display current room chests auto current_room = dungeon_editor_system_->GetCurrentRoom(); auto chests_result = dungeon_editor_system_->GetChestsByRoom(current_room); - + if (chests_result.ok()) { auto chests = chests_result.value(); ImGui::Text("Chests: %zu", chests.size()); - - for (const auto& chest : chests) { - ImGui::Text("ID:%d (%d,%d) Item:%d", - chest.chest_id, chest.x, chest.y, chest.item_id); + + for (const auto &chest : chests) { + ImGui::Text("ID:%d (%d,%d) Item:%d", chest.chest_id, chest.x, chest.y, + chest.item_id); } } else { ImGui::Text("Error loading chests"); @@ -1883,17 +1923,17 @@ void DungeonEditor::DrawCompactChestEditor() { // Quick chest creation ImGui::Separator(); ImGui::Text("Add Chest"); - + static int chest_x = 0; static int chest_y = 0; static int chest_item_id = 0; static bool chest_big = false; - + ImGui::InputInt("X", &chest_x); ImGui::InputInt("Y", &chest_y); ImGui::InputInt("Item ID", &chest_item_id); ImGui::Checkbox("Big", &chest_big); - + if (ImGui::Button("Add Chest")) { zelda3::DungeonEditorSystem::ChestData chest_data; chest_data.room_id = current_room; @@ -1902,7 +1942,7 @@ void DungeonEditor::DrawCompactChestEditor() { chest_data.is_big_chest = chest_big; chest_data.item_id = chest_item_id; chest_data.item_quantity = 1; - + auto status = dungeon_editor_system_->AddChest(chest_data); if (!status.ok()) { ImGui::Text("Error adding chest"); @@ -1915,11 +1955,12 @@ void DungeonEditor::DrawCompactPropertiesEditor() { ImGui::Separator(); auto current_room = dungeon_editor_system_->GetCurrentRoom(); - auto properties_result = dungeon_editor_system_->GetRoomProperties(current_room); - + auto properties_result = + dungeon_editor_system_->GetRoomProperties(current_room); + if (properties_result.ok()) { auto properties = properties_result.value(); - + static char room_name[128] = {0}; static int dungeon_id = 0; static int floor_level = 0; @@ -1951,8 +1992,9 @@ void DungeonEditor::DrawCompactPropertiesEditor() { new_properties.is_boss_room = is_boss_room; new_properties.is_save_room = is_save_room; new_properties.music_id = music_id; - - auto status = dungeon_editor_system_->SetRoomProperties(current_room, new_properties); + + auto status = dungeon_editor_system_->SetRoomProperties(current_room, + new_properties); if (!status.ok()) { ImGui::Text("Error saving properties"); } @@ -1964,7 +2006,7 @@ void DungeonEditor::DrawCompactPropertiesEditor() { // Dungeon settings summary ImGui::Separator(); ImGui::Text("Dungeon Settings"); - + auto dungeon_settings_result = dungeon_editor_system_->GetDungeonSettings(); if (dungeon_settings_result.ok()) { auto settings = dungeon_settings_result.value(); @@ -1975,4 +2017,27 @@ void DungeonEditor::DrawCompactPropertiesEditor() { } } +// Coordinate conversion helper functions +std::pair DungeonEditor::RoomToCanvasCoordinates(int room_x, + int room_y) const { + // Convert room coordinates (16x16 tile units) to canvas coordinates (pixels) + return {room_x * 16, room_y * 16}; +} + +std::pair DungeonEditor::CanvasToRoomCoordinates(int canvas_x, + int canvas_y) const { + // Convert canvas coordinates (pixels) to room coordinates (16x16 tile units) + return {canvas_x / 16, canvas_y / 16}; +} + +bool DungeonEditor::IsWithinCanvasBounds(int canvas_x, int canvas_y, + int margin) const { + // Check if coordinates are within canvas bounds with optional margin + auto canvas_width = canvas_.width(); + auto canvas_height = canvas_.height(); + return (canvas_x >= -margin && canvas_y >= -margin && + canvas_x <= canvas_width + margin && + canvas_y <= canvas_height + margin); +} + } // namespace yaze::editor diff --git a/src/app/editor/dungeon/dungeon_editor.h b/src/app/editor/dungeon/dungeon_editor.h index 513ff587..ac7d4a73 100644 --- a/src/app/editor/dungeon/dungeon_editor.h +++ b/src/app/editor/dungeon/dungeon_editor.h @@ -42,7 +42,7 @@ constexpr ImGuiTableFlags kDungeonTableFlags = class DungeonEditor : public Editor { public: explicit DungeonEditor(Rom* rom = nullptr) - : rom_(rom), object_renderer_(rom) { + : rom_(rom), object_renderer_(rom), preview_object_(0, 0, 0, 0, 0) { type_ = EditorType::kDungeon; // Initialize the new dungeon editor system if (rom) { @@ -111,6 +111,11 @@ class DungeonEditor : public Editor { int canvas_y); void RenderLayoutObjects(const zelda3::RoomLayout& layout, const gfx::SnesPalette& palette); + + // Coordinate conversion helpers + std::pair RoomToCanvasCoordinates(int room_x, int room_y) const; + std::pair CanvasToRoomCoordinates(int canvas_x, int canvas_y) const; + bool IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin = 32) const; // Object rendering cache to avoid re-rendering the same objects struct ObjectRenderCache { @@ -123,6 +128,10 @@ class DungeonEditor : public Editor { std::vector object_render_cache_; uint64_t last_palette_hash_ = 0; + + // Object preview system + zelda3::RoomObject preview_object_; + gfx::SnesPalette preview_palette_; void CalculateUsageStats(); void DrawUsageStats();