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.
This commit is contained in:
360
docs/dungeon-editor-comprehensive-guide.md
Normal file
360
docs/dungeon-editor-comprehensive-guide.md
Normal file
@@ -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<int, int> RoomToCanvasCoordinates(int room_x, int room_y) const {
|
||||||
|
return {room_x * 16, room_y * 16};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert canvas coordinates to room coordinates
|
||||||
|
std::pair<int, int> 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.
|
||||||
@@ -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.
|
|
||||||
@@ -10,12 +10,11 @@
|
|||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
#include "app/gui/input.h"
|
#include "app/gui/input.h"
|
||||||
#include "app/rom.h"
|
#include "app/rom.h"
|
||||||
#include "app/zelda3/dungeon/room.h"
|
|
||||||
#include "app/zelda3/dungeon/dungeon_editor_system.h"
|
#include "app/zelda3/dungeon/dungeon_editor_system.h"
|
||||||
#include "app/zelda3/dungeon/dungeon_object_editor.h"
|
#include "app/zelda3/dungeon/dungeon_object_editor.h"
|
||||||
#include "app/zelda3/dungeon/object_renderer.h"
|
#include "app/zelda3/dungeon/object_renderer.h"
|
||||||
|
#include "app/zelda3/dungeon/room.h"
|
||||||
#include "imgui/imgui.h"
|
#include "imgui/imgui.h"
|
||||||
#include "imgui_memory_editor.h"
|
|
||||||
#include "util/hex.h"
|
#include "util/hex.h"
|
||||||
|
|
||||||
namespace yaze::editor {
|
namespace yaze::editor {
|
||||||
@@ -45,7 +44,8 @@ constexpr ImGuiTableFlags kDungeonObjectTableFlags =
|
|||||||
|
|
||||||
void DungeonEditor::Initialize() {
|
void DungeonEditor::Initialize() {
|
||||||
if (rom_ && !dungeon_editor_system_) {
|
if (rom_ && !dungeon_editor_system_) {
|
||||||
dungeon_editor_system_ = std::make_unique<zelda3::DungeonEditorSystem>(rom_);
|
dungeon_editor_system_ =
|
||||||
|
std::make_unique<zelda3::DungeonEditorSystem>(rom_);
|
||||||
object_editor_ = std::make_shared<zelda3::DungeonObjectEditor>(rom_);
|
object_editor_ = std::make_shared<zelda3::DungeonObjectEditor>(rom_);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,8 +258,9 @@ void DungeonEditor::DrawToolset() {
|
|||||||
if (BeginTable("DWToolset", 16, ImGuiTableFlags_SizingFixedFit,
|
if (BeginTable("DWToolset", 16, ImGuiTableFlags_SizingFixedFit,
|
||||||
ImVec2(0, 0))) {
|
ImVec2(0, 0))) {
|
||||||
static std::array<const char *, 16> tool_names = {
|
static std::array<const char *, 16> tool_names = {
|
||||||
"Undo", "Redo", "Separator", "Any", "BG1", "BG2", "BG3",
|
"Undo", "Redo", "Separator", "Any", "BG1", "BG2",
|
||||||
"Separator", "Object", "Sprite", "Item", "Entrance", "Door", "Chest", "Block", "Palette"};
|
"BG3", "Separator", "Object", "Sprite", "Item", "Entrance",
|
||||||
|
"Door", "Chest", "Block", "Palette"};
|
||||||
std::ranges::for_each(tool_names,
|
std::ranges::for_each(tool_names,
|
||||||
[](const char *name) { TableSetupColumn(name); });
|
[](const char *name) { TableSetupColumn(name); });
|
||||||
|
|
||||||
@@ -663,9 +664,9 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
|||||||
|
|
||||||
// Render regular room objects
|
// Render regular room objects
|
||||||
for (const auto &object : rooms_[room_id].GetTileObjects()) {
|
for (const auto &object : rooms_[room_id].GetTileObjects()) {
|
||||||
// Convert room coordinates to canvas coordinates
|
// Convert room coordinates to canvas coordinates using helper function
|
||||||
int canvas_x = object.x_ * 16;
|
auto [canvas_x, canvas_y] =
|
||||||
int canvas_y = object.y_ * 16;
|
RoomToCanvasCoordinates(object.x_, object.y_);
|
||||||
|
|
||||||
if (show_objects) {
|
if (show_objects) {
|
||||||
// Draw object outline - use size_ to determine dimensions
|
// Draw object outline - use size_ to determine dimensions
|
||||||
@@ -682,7 +683,8 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
|
|||||||
outline_height = (height_bits + 1) * 16;
|
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);
|
outline_height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,9 +711,8 @@ void DungeonEditor::RenderObjectInCanvas(const zelda3::RoomObject &object,
|
|||||||
mutable_object.set_rom(rom_);
|
mutable_object.set_rom(rom_);
|
||||||
mutable_object.EnsureTilesLoaded();
|
mutable_object.EnsureTilesLoaded();
|
||||||
|
|
||||||
// Check if tiles were loaded successfully using the new method
|
// Check if tiles were loaded successfully
|
||||||
auto tiles_result = mutable_object.GetTiles();
|
if (mutable_object.tiles().empty()) {
|
||||||
if (!tiles_result.ok() || tiles_result->empty()) {
|
|
||||||
return; // Skip objects without tiles
|
return; // Skip objects without tiles
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,14 +723,20 @@ void DungeonEditor::RenderObjectInCanvas(const zelda3::RoomObject &object,
|
|||||||
(palette_hash << 6) + (palette_hash >> 2);
|
(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
|
// Check cache first
|
||||||
for (auto &cached : object_render_cache_) {
|
for (auto &cached : object_render_cache_) {
|
||||||
if (cached.object_id == object.id_ && cached.object_x == object.x_ &&
|
if (cached.object_id == object.id_ && cached.object_x == object.x_ &&
|
||||||
cached.object_y == object.y_ && cached.object_size == object.size_ &&
|
cached.object_y == object.y_ && cached.object_size == object.size_ &&
|
||||||
cached.palette_hash == palette_hash && cached.is_valid) {
|
cached.palette_hash == palette_hash && cached.is_valid) {
|
||||||
// Use cached bitmap
|
// Use cached bitmap - canvas handles scrolling internally
|
||||||
int canvas_x = object.x_ * 16;
|
|
||||||
int canvas_y = object.y_ * 16;
|
|
||||||
canvas_.DrawBitmap(cached.rendered_bitmap, canvas_x, canvas_y, 1.0f, 255);
|
canvas_.DrawBitmap(cached.rendered_bitmap, canvas_x, canvas_y, 1.0f, 255);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -749,12 +756,8 @@ void DungeonEditor::RenderObjectInCanvas(const zelda3::RoomObject &object,
|
|||||||
// Render the bitmap to a texture so it can be drawn
|
// Render the bitmap to a texture so it can be drawn
|
||||||
core::Renderer::Get().RenderBitmap(&object_bitmap);
|
core::Renderer::Get().RenderBitmap(&object_bitmap);
|
||||||
|
|
||||||
// Convert room coordinates to canvas coordinates
|
// Draw the object bitmap to the canvas
|
||||||
// Room coordinates are in 16x16 tile units, canvas coordinates are in pixels
|
// Canvas will handle scrolling and coordinate transformation
|
||||||
int canvas_x = object.x_ * 16;
|
|
||||||
int canvas_y = object.y_ * 16;
|
|
||||||
|
|
||||||
// Draw the object bitmap to the canvas immediately
|
|
||||||
canvas_.DrawBitmap(object_bitmap, canvas_x, canvas_y, 1.0f, 255);
|
canvas_.DrawBitmap(object_bitmap, canvas_x, canvas_y, 1.0f, 255);
|
||||||
|
|
||||||
// Cache the rendered bitmap (create a copy for caching)
|
// 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
|
// This provides a visual representation of the room's structure
|
||||||
|
|
||||||
for (const auto &layout_obj : layout.GetObjects()) {
|
for (const auto &layout_obj : layout.GetObjects()) {
|
||||||
int canvas_x = layout_obj.x() * 16;
|
// Convert room coordinates to canvas coordinates using helper function
|
||||||
int canvas_y = layout_obj.y() * 16;
|
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
|
// Choose color based on object type
|
||||||
gfx::SnesColor color;
|
gfx::SnesColor color;
|
||||||
@@ -895,7 +904,8 @@ void DungeonEditor::DrawObjectRenderer() {
|
|||||||
selected_object = i;
|
selected_object = i;
|
||||||
|
|
||||||
// Create a test object and render it
|
// 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.set_rom(rom_);
|
||||||
test_object.EnsureTilesLoaded();
|
test_object.EnsureTilesLoaded();
|
||||||
|
|
||||||
@@ -907,6 +917,8 @@ void DungeonEditor::DrawObjectRenderer() {
|
|||||||
auto result = object_renderer_.GetObjectPreview(test_object, palette);
|
auto result = object_renderer_.GetObjectPreview(test_object, palette);
|
||||||
if (result.ok()) {
|
if (result.ok()) {
|
||||||
object_loaded_ = true;
|
object_loaded_ = true;
|
||||||
|
preview_object_ = test_object; // Store for rendering
|
||||||
|
preview_palette_ = palette;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
@@ -922,6 +934,24 @@ void DungeonEditor::DrawObjectRenderer() {
|
|||||||
object_canvas_.DrawContextMenu();
|
object_canvas_.DrawContextMenu();
|
||||||
object_canvas_.DrawTileSelector(32);
|
object_canvas_.DrawTileSelector(32);
|
||||||
object_canvas_.DrawGrid(32.0f);
|
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();
|
object_canvas_.DrawOverlay();
|
||||||
|
|
||||||
EndChild();
|
EndChild();
|
||||||
@@ -930,7 +960,10 @@ void DungeonEditor::DrawObjectRenderer() {
|
|||||||
|
|
||||||
if (object_loaded_) {
|
if (object_loaded_) {
|
||||||
ImGui::Begin("Object Preview", &object_loaded_, 0);
|
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<int>(preview_object_.layer_));
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1165,7 +1198,8 @@ void DungeonEditor::DrawUsageGrid() {
|
|||||||
Text("Floor1: %#02x", room.floor1);
|
Text("Floor1: %#02x", room.floor1);
|
||||||
Text("Floor2: %#02x", room.floor2);
|
Text("Floor2: %#02x", room.floor2);
|
||||||
Text("Message ID: %#04x", room.message_id_);
|
Text("Message ID: %#04x", room.message_id_);
|
||||||
Text("Size: %#016llx", room_sizes_[row * squaresWide + col]);
|
Text("Size: %#016llx", static_cast<unsigned long long>(
|
||||||
|
room_sizes_[row * squaresWide + col]));
|
||||||
Text("Size Pointer: %#016llx",
|
Text("Size Pointer: %#016llx",
|
||||||
room_size_pointers_[row * squaresWide + col]);
|
room_size_pointers_[row * squaresWide + col]);
|
||||||
ImGui::EndTooltip();
|
ImGui::EndTooltip();
|
||||||
@@ -1189,9 +1223,8 @@ void DungeonEditor::DrawObjectEditor() {
|
|||||||
|
|
||||||
// Display current editing mode
|
// Display current editing mode
|
||||||
auto mode = object_editor_->GetMode();
|
auto mode = object_editor_->GetMode();
|
||||||
const char* mode_names[] = {
|
const char *mode_names[] = {"Select", "Insert", "Delete",
|
||||||
"Select", "Insert", "Delete", "Edit", "Layer", "Preview"
|
"Edit", "Layer", "Preview"};
|
||||||
};
|
|
||||||
ImGui::Text("Mode: %s", mode_names[static_cast<int>(mode)]);
|
ImGui::Text("Mode: %s", mode_names[static_cast<int>(mode)]);
|
||||||
|
|
||||||
// Mode selection
|
// Mode selection
|
||||||
@@ -1244,8 +1277,10 @@ void DungeonEditor::DrawObjectEditor() {
|
|||||||
|
|
||||||
// Undo/Redo status
|
// Undo/Redo status
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("Undo: %s", object_editor_->CanUndo() ? "Available" : "Not Available");
|
ImGui::Text("Undo: %s",
|
||||||
ImGui::Text("Redo: %s", object_editor_->CanRedo() ? "Available" : "Not Available");
|
object_editor_->CanUndo() ? "Available" : "Not Available");
|
||||||
|
ImGui::Text("Redo: %s",
|
||||||
|
object_editor_->CanRedo() ? "Available" : "Not Available");
|
||||||
}
|
}
|
||||||
|
|
||||||
void DungeonEditor::DrawSpriteEditor() {
|
void DungeonEditor::DrawSpriteEditor() {
|
||||||
@@ -1265,13 +1300,13 @@ void DungeonEditor::DrawSpriteEditor() {
|
|||||||
auto sprites = sprites_result.value();
|
auto sprites = sprites_result.value();
|
||||||
ImGui::Text("Sprites in room %d: %zu", current_room, sprites.size());
|
ImGui::Text("Sprites in room %d: %zu", current_room, sprites.size());
|
||||||
|
|
||||||
for (const auto& sprite : sprites) {
|
for (const auto &sprite : sprites) {
|
||||||
ImGui::Text("ID: %d, Type: %d, Position: (%d, %d)",
|
ImGui::Text("ID: %d, Type: %d, Position: (%d, %d)", sprite.sprite_id,
|
||||||
sprite.sprite_id, static_cast<int>(sprite.type),
|
static_cast<int>(sprite.type), sprite.x, sprite.y);
|
||||||
sprite.x, sprite.y);
|
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// Sprite placement controls
|
||||||
@@ -1319,13 +1354,14 @@ void DungeonEditor::DrawItemEditor() {
|
|||||||
auto items = items_result.value();
|
auto items = items_result.value();
|
||||||
ImGui::Text("Items in room %d: %zu", current_room, items.size());
|
ImGui::Text("Items in room %d: %zu", current_room, items.size());
|
||||||
|
|
||||||
for (const auto& item : items) {
|
for (const auto &item : items) {
|
||||||
ImGui::Text("ID: %d, Type: %d, Position: (%d, %d), Hidden: %s",
|
ImGui::Text("ID: %d, Type: %d, Position: (%d, %d), Hidden: %s",
|
||||||
item.item_id, static_cast<int>(item.type),
|
item.item_id, static_cast<int>(item.type), item.x, item.y,
|
||||||
item.x, item.y, item.is_hidden ? "Yes" : "No");
|
item.is_hidden ? "Yes" : "No");
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// Item placement controls
|
||||||
@@ -1368,19 +1404,22 @@ void DungeonEditor::DrawEntranceEditor() {
|
|||||||
|
|
||||||
// Display current room entrances
|
// Display current room entrances
|
||||||
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
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()) {
|
if (entrances_result.ok()) {
|
||||||
auto entrances = entrances_result.value();
|
auto entrances = entrances_result.value();
|
||||||
ImGui::Text("Entrances in room %d: %zu", current_room, entrances.size());
|
ImGui::Text("Entrances in room %d: %zu", current_room, entrances.size());
|
||||||
|
|
||||||
for (const auto& entrance : entrances) {
|
for (const auto &entrance : entrances) {
|
||||||
ImGui::Text("ID: %d, Type: %d, Target Room: %d, Target Position: (%d, %d)",
|
ImGui::Text(
|
||||||
|
"ID: %d, Type: %d, Target Room: %d, Target Position: (%d, %d)",
|
||||||
entrance.entrance_id, static_cast<int>(entrance.type),
|
entrance.entrance_id, static_cast<int>(entrance.type),
|
||||||
entrance.target_room_id, entrance.target_x, entrance.target_y);
|
entrance.target_room_id, entrance.target_x, entrance.target_y);
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// Entrance creation controls
|
||||||
@@ -1399,9 +1438,8 @@ void DungeonEditor::DrawEntranceEditor() {
|
|||||||
ImGui::InputInt("Target Y", &target_y);
|
ImGui::InputInt("Target Y", &target_y);
|
||||||
|
|
||||||
if (ImGui::Button("Connect Rooms")) {
|
if (ImGui::Button("Connect Rooms")) {
|
||||||
auto status = dungeon_editor_system_->ConnectRooms(current_room, target_room_id,
|
auto status = dungeon_editor_system_->ConnectRooms(
|
||||||
source_x, source_y,
|
current_room, target_room_id, source_x, source_y, target_x, target_y);
|
||||||
target_x, target_y);
|
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
ImGui::Text("Error connecting rooms: %s", status.message().data());
|
ImGui::Text("Error connecting rooms: %s", status.message().data());
|
||||||
}
|
}
|
||||||
@@ -1425,12 +1463,14 @@ void DungeonEditor::DrawDoorEditor() {
|
|||||||
auto doors = doors_result.value();
|
auto doors = doors_result.value();
|
||||||
ImGui::Text("Doors in room %d: %zu", current_room, doors.size());
|
ImGui::Text("Doors in room %d: %zu", current_room, doors.size());
|
||||||
|
|
||||||
for (const auto& door : doors) {
|
for (const auto &door : doors) {
|
||||||
ImGui::Text("ID: %d, Position: (%d, %d), Direction: %d, Target Room: %d",
|
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);
|
door.door_id, door.x, door.y, door.direction,
|
||||||
|
door.target_room_id);
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// Door creation controls
|
||||||
@@ -1493,14 +1533,15 @@ void DungeonEditor::DrawChestEditor() {
|
|||||||
auto chests = chests_result.value();
|
auto chests = chests_result.value();
|
||||||
ImGui::Text("Chests in room %d: %zu", current_room, chests.size());
|
ImGui::Text("Chests in room %d: %zu", current_room, chests.size());
|
||||||
|
|
||||||
for (const auto& chest : chests) {
|
for (const auto &chest : chests) {
|
||||||
ImGui::Text("ID: %d, Position: (%d, %d), Big: %s, Item: %d, Quantity: %d",
|
ImGui::Text("ID: %d, Position: (%d, %d), Big: %s, Item: %d, Quantity: %d",
|
||||||
chest.chest_id, chest.x, chest.y,
|
chest.chest_id, chest.x, chest.y,
|
||||||
chest.is_big_chest ? "Yes" : "No",
|
chest.is_big_chest ? "Yes" : "No", chest.item_id,
|
||||||
chest.item_id, chest.item_quantity);
|
chest.item_quantity);
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
// Chest creation controls
|
||||||
@@ -1542,7 +1583,6 @@ void DungeonEditor::DrawIntegratedEditingPanels() {
|
|||||||
|
|
||||||
// Create a tabbed interface for different editing modes
|
// Create a tabbed interface for different editing modes
|
||||||
if (ImGui::BeginTabBar("##EditingPanels")) {
|
if (ImGui::BeginTabBar("##EditingPanels")) {
|
||||||
|
|
||||||
// Object Editor Tab
|
// Object Editor Tab
|
||||||
if (ImGui::BeginTabItem("Objects")) {
|
if (ImGui::BeginTabItem("Objects")) {
|
||||||
DrawCompactObjectEditor();
|
DrawCompactObjectEditor();
|
||||||
@@ -1595,17 +1635,19 @@ void DungeonEditor::DrawCompactObjectEditor() {
|
|||||||
|
|
||||||
// Display current editing mode
|
// Display current editing mode
|
||||||
auto mode = object_editor_->GetMode();
|
auto mode = object_editor_->GetMode();
|
||||||
const char* mode_names[] = {
|
const char *mode_names[] = {"Select", "Insert", "Delete",
|
||||||
"Select", "Insert", "Delete", "Edit", "Layer", "Preview"
|
"Edit", "Layer", "Preview"};
|
||||||
};
|
|
||||||
ImGui::Text("Mode: %s", mode_names[static_cast<int>(mode)]);
|
ImGui::Text("Mode: %s", mode_names[static_cast<int>(mode)]);
|
||||||
|
|
||||||
// Compact mode selection
|
// 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();
|
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();
|
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
|
// Layer and object type selection
|
||||||
int current_layer = object_editor_->GetCurrentLayer();
|
int current_layer = object_editor_->GetCurrentLayer();
|
||||||
@@ -1665,10 +1707,9 @@ void DungeonEditor::DrawCompactSpriteEditor() {
|
|||||||
// Show first few sprites in compact format
|
// Show first few sprites in compact format
|
||||||
int display_count = std::min(3, static_cast<int>(sprites.size()));
|
int display_count = std::min(3, static_cast<int>(sprites.size()));
|
||||||
for (int i = 0; i < display_count; ++i) {
|
for (int i = 0; i < display_count; ++i) {
|
||||||
const auto& sprite = sprites[i];
|
const auto &sprite = sprites[i];
|
||||||
ImGui::Text("ID:%d Type:%d (%d,%d)",
|
ImGui::Text("ID:%d Type:%d (%d,%d)", sprite.sprite_id,
|
||||||
sprite.sprite_id, static_cast<int>(sprite.type),
|
static_cast<int>(sprite.type), sprite.x, sprite.y);
|
||||||
sprite.x, sprite.y);
|
|
||||||
}
|
}
|
||||||
if (sprites.size() > 3) {
|
if (sprites.size() > 3) {
|
||||||
ImGui::Text("... and %zu more", sprites.size() - 3);
|
ImGui::Text("... and %zu more", sprites.size() - 3);
|
||||||
@@ -1719,10 +1760,9 @@ void DungeonEditor::DrawCompactItemEditor() {
|
|||||||
// Show first few items in compact format
|
// Show first few items in compact format
|
||||||
int display_count = std::min(3, static_cast<int>(items.size()));
|
int display_count = std::min(3, static_cast<int>(items.size()));
|
||||||
for (int i = 0; i < display_count; ++i) {
|
for (int i = 0; i < display_count; ++i) {
|
||||||
const auto& item = items[i];
|
const auto &item = items[i];
|
||||||
ImGui::Text("ID:%d Type:%d (%d,%d)",
|
ImGui::Text("ID:%d Type:%d (%d,%d)", item.item_id,
|
||||||
item.item_id, static_cast<int>(item.type),
|
static_cast<int>(item.type), item.x, item.y);
|
||||||
item.x, item.y);
|
|
||||||
}
|
}
|
||||||
if (items.size() > 3) {
|
if (items.size() > 3) {
|
||||||
ImGui::Text("... and %zu more", items.size() - 3);
|
ImGui::Text("... and %zu more", items.size() - 3);
|
||||||
@@ -1765,16 +1805,17 @@ void DungeonEditor::DrawCompactEntranceEditor() {
|
|||||||
|
|
||||||
// Display current room entrances
|
// Display current room entrances
|
||||||
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
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()) {
|
if (entrances_result.ok()) {
|
||||||
auto entrances = entrances_result.value();
|
auto entrances = entrances_result.value();
|
||||||
ImGui::Text("Entrances: %zu", entrances.size());
|
ImGui::Text("Entrances: %zu", entrances.size());
|
||||||
|
|
||||||
for (const auto& entrance : entrances) {
|
for (const auto &entrance : entrances) {
|
||||||
ImGui::Text("ID:%d -> Room:%d (%d,%d)",
|
ImGui::Text("ID:%d -> Room:%d (%d,%d)", entrance.entrance_id,
|
||||||
entrance.entrance_id, entrance.target_room_id,
|
entrance.target_room_id, entrance.target_x,
|
||||||
entrance.target_x, entrance.target_y);
|
entrance.target_y);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("Error loading entrances");
|
ImGui::Text("Error loading entrances");
|
||||||
@@ -1797,9 +1838,8 @@ void DungeonEditor::DrawCompactEntranceEditor() {
|
|||||||
ImGui::InputInt("Target Y", &target_y);
|
ImGui::InputInt("Target Y", &target_y);
|
||||||
|
|
||||||
if (ImGui::Button("Connect")) {
|
if (ImGui::Button("Connect")) {
|
||||||
auto status = dungeon_editor_system_->ConnectRooms(current_room, target_room_id,
|
auto status = dungeon_editor_system_->ConnectRooms(
|
||||||
source_x, source_y,
|
current_room, target_room_id, source_x, source_y, target_x, target_y);
|
||||||
target_x, target_y);
|
|
||||||
if (!status.ok()) {
|
if (!status.ok()) {
|
||||||
ImGui::Text("Error connecting rooms");
|
ImGui::Text("Error connecting rooms");
|
||||||
}
|
}
|
||||||
@@ -1818,9 +1858,9 @@ void DungeonEditor::DrawCompactDoorEditor() {
|
|||||||
auto doors = doors_result.value();
|
auto doors = doors_result.value();
|
||||||
ImGui::Text("Doors: %zu", doors.size());
|
ImGui::Text("Doors: %zu", doors.size());
|
||||||
|
|
||||||
for (const auto& door : doors) {
|
for (const auto &door : doors) {
|
||||||
ImGui::Text("ID:%d (%d,%d) -> Room:%d",
|
ImGui::Text("ID:%d (%d,%d) -> Room:%d", door.door_id, door.x, door.y,
|
||||||
door.door_id, door.x, door.y, door.target_room_id);
|
door.target_room_id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("Error loading doors");
|
ImGui::Text("Error loading doors");
|
||||||
@@ -1872,9 +1912,9 @@ void DungeonEditor::DrawCompactChestEditor() {
|
|||||||
auto chests = chests_result.value();
|
auto chests = chests_result.value();
|
||||||
ImGui::Text("Chests: %zu", chests.size());
|
ImGui::Text("Chests: %zu", chests.size());
|
||||||
|
|
||||||
for (const auto& chest : chests) {
|
for (const auto &chest : chests) {
|
||||||
ImGui::Text("ID:%d (%d,%d) Item:%d",
|
ImGui::Text("ID:%d (%d,%d) Item:%d", chest.chest_id, chest.x, chest.y,
|
||||||
chest.chest_id, chest.x, chest.y, chest.item_id);
|
chest.item_id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ImGui::Text("Error loading chests");
|
ImGui::Text("Error loading chests");
|
||||||
@@ -1915,7 +1955,8 @@ void DungeonEditor::DrawCompactPropertiesEditor() {
|
|||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
auto current_room = dungeon_editor_system_->GetCurrentRoom();
|
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()) {
|
if (properties_result.ok()) {
|
||||||
auto properties = properties_result.value();
|
auto properties = properties_result.value();
|
||||||
@@ -1952,7 +1993,8 @@ void DungeonEditor::DrawCompactPropertiesEditor() {
|
|||||||
new_properties.is_save_room = is_save_room;
|
new_properties.is_save_room = is_save_room;
|
||||||
new_properties.music_id = music_id;
|
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()) {
|
if (!status.ok()) {
|
||||||
ImGui::Text("Error saving properties");
|
ImGui::Text("Error saving properties");
|
||||||
}
|
}
|
||||||
@@ -1975,4 +2017,27 @@ void DungeonEditor::DrawCompactPropertiesEditor() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Coordinate conversion helper functions
|
||||||
|
std::pair<int, int> 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<int, int> 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
|
} // namespace yaze::editor
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ constexpr ImGuiTableFlags kDungeonTableFlags =
|
|||||||
class DungeonEditor : public Editor {
|
class DungeonEditor : public Editor {
|
||||||
public:
|
public:
|
||||||
explicit DungeonEditor(Rom* rom = nullptr)
|
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;
|
type_ = EditorType::kDungeon;
|
||||||
// Initialize the new dungeon editor system
|
// Initialize the new dungeon editor system
|
||||||
if (rom) {
|
if (rom) {
|
||||||
@@ -112,6 +112,11 @@ class DungeonEditor : public Editor {
|
|||||||
void RenderLayoutObjects(const zelda3::RoomLayout& layout,
|
void RenderLayoutObjects(const zelda3::RoomLayout& layout,
|
||||||
const gfx::SnesPalette& palette);
|
const gfx::SnesPalette& palette);
|
||||||
|
|
||||||
|
// Coordinate conversion helpers
|
||||||
|
std::pair<int, int> RoomToCanvasCoordinates(int room_x, int room_y) const;
|
||||||
|
std::pair<int, int> 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
|
// Object rendering cache to avoid re-rendering the same objects
|
||||||
struct ObjectRenderCache {
|
struct ObjectRenderCache {
|
||||||
int object_id;
|
int object_id;
|
||||||
@@ -124,6 +129,10 @@ class DungeonEditor : public Editor {
|
|||||||
std::vector<ObjectRenderCache> object_render_cache_;
|
std::vector<ObjectRenderCache> object_render_cache_;
|
||||||
uint64_t last_palette_hash_ = 0;
|
uint64_t last_palette_hash_ = 0;
|
||||||
|
|
||||||
|
// Object preview system
|
||||||
|
zelda3::RoomObject preview_object_;
|
||||||
|
gfx::SnesPalette preview_palette_;
|
||||||
|
|
||||||
void CalculateUsageStats();
|
void CalculateUsageStats();
|
||||||
void DrawUsageStats();
|
void DrawUsageStats();
|
||||||
void DrawUsageGrid();
|
void DrawUsageGrid();
|
||||||
|
|||||||
Reference in New Issue
Block a user