refactor: Consolidate Canvas Documentation and Update Structure
- Streamlined the canvas documentation by consolidating multiple guides into a single comprehensive overview. - Updated the canvas architecture section to reflect new features and interaction modes, enhancing clarity for users. - Improved API patterns and integration steps for editors, ensuring consistency across documentation. - Removed outdated content and added new sections on automation and debugging, aligning with recent code changes. - Adjusted file paths in the documentation to match the current project structure, ensuring accurate references.
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
# F2: Dungeon Editor v2 - Complete Guide
|
# F2: Dungeon Editor v2 - Complete Guide
|
||||||
|
|
||||||
**Version**: v0.4.0
|
|
||||||
**Last Updated**: October 10, 2025
|
**Last Updated**: October 10, 2025
|
||||||
**Status**: ✅ Production Ready
|
|
||||||
**Related**: [E2-development-guide.md](E2-development-guide.md), [E5-debugging-guide.md](E5-debugging-guide.md)
|
**Related**: [E2-development-guide.md](E2-development-guide.md), [E5-debugging-guide.md](E5-debugging-guide.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -21,54 +19,6 @@ The Dungeon Editor uses a modern card-based architecture (DungeonEditorV2) with
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Recent Refactoring (Oct 9-10, 2025)
|
|
||||||
|
|
||||||
### Critical Bugs Fixed ✅
|
|
||||||
|
|
||||||
#### Bug #1: Segfault on Startup
|
|
||||||
**Cause**: `ImGui::GetID()` called before ImGui context ready
|
|
||||||
**Fix**: Moved to `Update()` when ImGui is initialized
|
|
||||||
**File**: `dungeon_editor_v2.cc:160-163`
|
|
||||||
|
|
||||||
#### Bug #2: Floor Values Always Zero
|
|
||||||
**Cause**: `RenderRoomGraphics()` called before `LoadObjects()`
|
|
||||||
**Fix**: Correct loading sequence:
|
|
||||||
```cpp
|
|
||||||
// CORRECT ORDER:
|
|
||||||
if (room.blocks().empty()) {
|
|
||||||
room.LoadRoomGraphics(room.blockset); // 1. Load blocks from ROM
|
|
||||||
}
|
|
||||||
if (room.GetTileObjects().empty()) {
|
|
||||||
room.LoadObjects(); // 2. Load objects (SETS floor1_graphics_!)
|
|
||||||
}
|
|
||||||
if (needs_render || !bg1_bitmap.is_active()) {
|
|
||||||
room.RenderRoomGraphics(); // 3. Render with correct floor values
|
|
||||||
}
|
|
||||||
```
|
|
||||||
**Impact**: Floor graphics now load correctly (4, 8, etc. instead of 0)
|
|
||||||
|
|
||||||
#### Bug #3: Duplicate Floor Variables
|
|
||||||
**Cause**: `floor1` (public) vs `floor1_graphics_` (private) - two sources of truth
|
|
||||||
**Fix**: Removed public members, added accessors: `floor1()`, `set_floor1()`
|
|
||||||
**Impact**: UI floor edits now trigger immediate re-render
|
|
||||||
|
|
||||||
#### Bug #4: Wall Graphics Not Rendering
|
|
||||||
**Cause**: Textures created BEFORE objects drawn, never updated
|
|
||||||
**Fix**: Added UPDATE commands after `RenderObjectsToBackground()`
|
|
||||||
```cpp
|
|
||||||
// room.cc:327-344
|
|
||||||
RenderObjectsToBackground(); // Draw objects to bitmaps
|
|
||||||
|
|
||||||
// Update textures with new data
|
|
||||||
if (bg1_bmp.texture()) {
|
|
||||||
gfx::Arena::Get().QueueTextureCommand(UPDATE, &bg1_bmp);
|
|
||||||
gfx::Arena::Get().QueueTextureCommand(UPDATE, &bg2_bmp);
|
|
||||||
} else {
|
|
||||||
gfx::Arena::Get().QueueTextureCommand(CREATE, &bg1_bmp);
|
|
||||||
gfx::Arena::Get().QueueTextureCommand(CREATE, &bg2_bmp);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Architecture Improvements ✅
|
### Architecture Improvements ✅
|
||||||
1. **Room Buffers Decoupled** - No dependency on Arena graphics sheets
|
1. **Room Buffers Decoupled** - No dependency on Arena graphics sheets
|
||||||
2. **ObjectRenderer Removed** - Standardized on ObjectDrawer (~1000 lines deleted)
|
2. **ObjectRenderer Removed** - Standardized on ObjectDrawer (~1000 lines deleted)
|
||||||
@@ -112,6 +62,8 @@ Room (Data Layer)
|
|||||||
|
|
||||||
### Room Rendering Pipeline
|
### Room Rendering Pipeline
|
||||||
|
|
||||||
|
TODO: Update this to latest code.
|
||||||
|
|
||||||
```
|
```
|
||||||
1. LoadRoomGraphics(blockset)
|
1. LoadRoomGraphics(blockset)
|
||||||
└─> Reads blocks[] from ROM
|
└─> Reads blocks[] from ROM
|
||||||
@@ -146,9 +98,8 @@ Understanding ALTTP dungeon composition is critical:
|
|||||||
```
|
```
|
||||||
Room Composition:
|
Room Composition:
|
||||||
├─ Room Layout (BASE LAYER - immovable)
|
├─ Room Layout (BASE LAYER - immovable)
|
||||||
│ ├─ Walls (structural boundaries)
|
│ ├─ Walls (structural boundaries, 7 configurations of squares in 2x2 grid)
|
||||||
│ ├─ Floors (walkable areas)
|
│ ├─ Floors (walkable areas, repeated tile pattern set to BG1/BG2)
|
||||||
│ └─ Pits (holes/damage zones)
|
|
||||||
├─ Layer 0 Objects (floor decorations, some walls)
|
├─ Layer 0 Objects (floor decorations, some walls)
|
||||||
├─ Layer 1 Objects (chests, decorations)
|
├─ Layer 1 Objects (chests, decorations)
|
||||||
└─ Layer 2 Objects (stairs, transitions)
|
└─ Layer 2 Objects (stairs, transitions)
|
||||||
@@ -164,43 +115,7 @@ Doors: Positioned at room edges to connect rooms
|
|||||||
|
|
||||||
### High Priority (Must Do)
|
### High Priority (Must Do)
|
||||||
|
|
||||||
#### 1. Implement Room Layout Base Layer Rendering
|
#### 1. Door Rendering at Room Edges
|
||||||
**File**: `dungeon_canvas_viewer.cc:377-391` (stub exists)
|
|
||||||
|
|
||||||
**What**: Render the immovable room structure (walls, floors, pits)
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
```cpp
|
|
||||||
void DungeonCanvasViewer::DrawRoomLayout(const zelda3::Room& room) {
|
|
||||||
const auto& layout = room.GetLayout();
|
|
||||||
|
|
||||||
// TODO: Load layout if not loaded
|
|
||||||
// layout.LoadLayout(room_id);
|
|
||||||
|
|
||||||
// Get structural elements
|
|
||||||
auto walls = layout.GetObjectsByType(RoomLayoutObject::Type::kWall);
|
|
||||||
auto floors = layout.GetObjectsByType(RoomLayoutObject::Type::kFloor);
|
|
||||||
auto pits = layout.GetObjectsByType(RoomLayoutObject::Type::kPit);
|
|
||||||
|
|
||||||
// Draw walls (dark gray, semi-transparent)
|
|
||||||
for (const auto& wall : walls) {
|
|
||||||
auto [x, y] = RoomToCanvasCoordinates(wall.x(), wall.y());
|
|
||||||
canvas_.DrawRect(x, y, 8, 8, ImVec4(0.3f, 0.3f, 0.3f, 0.6f));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Draw pits (orange warning)
|
|
||||||
for (const auto& pit : pits) {
|
|
||||||
auto [x, y] = RoomToCanvasCoordinates(pit.x(), pit.y());
|
|
||||||
canvas_.DrawRect(x, y, 8, 8, ImVec4(1.0f, 0.5f, 0.0f, 0.7f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Reference**: `src/app/zelda3/dungeon/room_layout.h/cc` for LoadLayout() logic
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 2. Door Rendering at Room Edges
|
|
||||||
**What**: Render doors with proper patterns at room connections
|
**What**: Render doors with proper patterns at room connections
|
||||||
|
|
||||||
**Pattern Reference**: ZScream's door drawing patterns
|
**Pattern Reference**: ZScream's door drawing patterns
|
||||||
@@ -220,7 +135,7 @@ void DungeonCanvasViewer::DrawDoors(const zelda3::Room& room) {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 3. Object Name Labels from String Array
|
#### 2. Object Name Labels from String Array
|
||||||
**File**: `dungeon_canvas_viewer.cc:416` (DrawObjectPositionOutlines)
|
**File**: `dungeon_canvas_viewer.cc:416` (DrawObjectPositionOutlines)
|
||||||
|
|
||||||
**What**: Show real object names instead of just IDs
|
**What**: Show real object names instead of just IDs
|
||||||
@@ -275,19 +190,6 @@ if (ImGui::BeginPopup("SelectRoomToOpen")) {
|
|||||||
|
|
||||||
### Medium Priority (Should Do)
|
### Medium Priority (Should Do)
|
||||||
|
|
||||||
#### 5. Update current_room_id on Card Hover
|
|
||||||
**What**: Update DungeonEditorV2::current_room_id_ when hovering room cards
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
```cpp
|
|
||||||
// In dungeon_editor_v2.cc, after room_card->Begin():
|
|
||||||
if (ImGui::IsWindowHovered()) {
|
|
||||||
current_room_id_ = room_id;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 6. Fix InputHexByte +/- Button Events
|
#### 6. Fix InputHexByte +/- Button Events
|
||||||
**File**: `src/app/gui/input.cc` (likely)
|
**File**: `src/app/gui/input.cc` (likely)
|
||||||
|
|
||||||
@@ -298,36 +200,9 @@ if (ImGui::IsWindowHovered()) {
|
|||||||
- Verify event logic matches working examples
|
- Verify event logic matches working examples
|
||||||
- Keep existing event style if it works elsewhere
|
- Keep existing event style if it works elsewhere
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 7. Update Room Graphics Card
|
|
||||||
**File**: `dungeon_editor_v2.cc:856-920`
|
|
||||||
|
|
||||||
**What**: Show per-room graphics from `current_gfx16_` instead of Arena sheets
|
|
||||||
|
|
||||||
**Implementation**:
|
|
||||||
```cpp
|
|
||||||
// Instead of Arena sheets:
|
|
||||||
auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block];
|
|
||||||
|
|
||||||
// Use room's current_gfx16_:
|
|
||||||
const auto& gfx_buffer = room.get_gfx_buffer(); // Returns current_gfx16_
|
|
||||||
// Extract 128x128 block from gfx_buffer
|
|
||||||
// Display as 128x32 strips (16 blocks, 2 columns)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Lower Priority (Nice to Have)
|
### Lower Priority (Nice to Have)
|
||||||
|
|
||||||
#### 8. Selection System with Primitive Squares
|
|
||||||
**What**: Allow selecting objects even if graphics don't render
|
|
||||||
|
|
||||||
**Current**: Selection works on bitmaps
|
|
||||||
**Enhancement**: Selection works on position outlines
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
#### 9. Move Backend Logic to DungeonEditorSystem
|
#### 9. Move Backend Logic to DungeonEditorSystem
|
||||||
**What**: Separate UI (V2) from data operations (System)
|
**What**: Separate UI (V2) from data operations (System)
|
||||||
|
|
||||||
@@ -341,13 +216,6 @@ const auto& gfx_buffer = room.get_gfx_buffer(); // Returns current_gfx16_
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### 10. Extract ROM Addresses to Separate File
|
|
||||||
**File**: `room.h` lines 18-84 (66 lines of constants)
|
|
||||||
|
|
||||||
**Action**: Move to `dungeon_rom_addresses.h`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
### Build & Run
|
### Build & Run
|
||||||
@@ -363,16 +231,6 @@ cmake --build build_ai --target yaze -j12
|
|||||||
./build_ai/bin/yaze.app/Contents/MacOS/yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0x00"
|
./build_ai/bin/yaze.app/Contents/MacOS/yaze --rom_file=zelda3.sfc --editor=Dungeon --cards="Room 0x00"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Expected Visuals
|
|
||||||
- ✅ **Floor tiles**: Proper dungeon graphics with correct colors
|
|
||||||
- ✅ **Floor values**: Show 4, 8, etc. (not 0!)
|
|
||||||
- ✅ **Object outlines**: Colored rectangles by layer
|
|
||||||
- 🟥 Red = Layer 0 (walls, floors)
|
|
||||||
- 🟩 Green = Layer 1 (decorations, chests)
|
|
||||||
- 🟦 Blue = Layer 2 (stairs, transitions)
|
|
||||||
- ✅ **Object IDs**: Labels like "0x10", "0x20"
|
|
||||||
- ⏳ **Wall graphics**: Should render inside rectangles (needs verification)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Testing & Verification
|
## Testing & Verification
|
||||||
@@ -391,230 +249,6 @@ cmake --build build_ai --target yaze -j12
|
|||||||
./build_ai/bin/yaze.app/Contents/MacOS/yaze --rom_file=zelda3.sfc --editor=Dungeon 2>&1 | grep "Writing Tile16"
|
./build_ai/bin/yaze.app/Contents/MacOS/yaze --rom_file=zelda3.sfc --editor=Dungeon 2>&1 | grep "Writing Tile16"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Visual Checklist
|
|
||||||
- [ ] Floor tiles render with correct colors
|
|
||||||
- [ ] Object position outlines visible
|
|
||||||
- [ ] Room ID shows in card title as `[000] Ganon`
|
|
||||||
- [ ] Properties in clean table layout (4 columns)
|
|
||||||
- [ ] Layer controls compact (1 row)
|
|
||||||
- [ ] Can edit floor1/floor2 values
|
|
||||||
- [ ] Changes update canvas immediately
|
|
||||||
- [ ] Room graphics card height correct (257px, not 1025px)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Technical Reference
|
|
||||||
|
|
||||||
### Correct Loading Order
|
|
||||||
The loading sequence is **critical**:
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
1. LoadRoomGraphics(blockset) - Loads blocks[], current_gfx16_
|
|
||||||
2. LoadObjects() - Parses objects, SETS floor graphics
|
|
||||||
3. RenderRoomGraphics() - Uses floor graphics from step 2
|
|
||||||
```
|
|
||||||
|
|
||||||
**Why**: `LoadObjects()` sets `floor1_graphics_` and `floor2_graphics_` during parsing. If you render before loading objects, floor values are still 0!
|
|
||||||
|
|
||||||
### Floor Graphics Accessors
|
|
||||||
```cpp
|
|
||||||
// room.h:341-350
|
|
||||||
uint8_t floor1() const { return floor1_graphics_; }
|
|
||||||
uint8_t floor2() const { return floor2_graphics_; }
|
|
||||||
void set_floor1(uint8_t value) {
|
|
||||||
floor1_graphics_ = value;
|
|
||||||
// UI code triggers re-render when changed
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Object Visualization
|
|
||||||
```cpp
|
|
||||||
// dungeon_canvas_viewer.cc:394-425
|
|
||||||
void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) {
|
|
||||||
for (const auto& obj : room.GetTileObjects()) {
|
|
||||||
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(obj.x(), obj.y());
|
|
||||||
|
|
||||||
// Size from object.size_ field
|
|
||||||
int width = ((obj.size() & 0x0F) + 1) * 8;
|
|
||||||
int height = (((obj.size() >> 4) & 0x0F) + 1) * 8;
|
|
||||||
|
|
||||||
// Color by layer
|
|
||||||
ImVec4 color = (layer == 0) ? Red : (layer == 1) ? Green : Blue;
|
|
||||||
|
|
||||||
canvas_.DrawRect(canvas_x, canvas_y, width, height, color);
|
|
||||||
canvas_.DrawText(absl::StrFormat("0x%02X", obj.id_), canvas_x + 2, canvas_y + 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Texture Atlas (Future-Proof Stub)
|
|
||||||
```cpp
|
|
||||||
// src/app/gfx/texture_atlas.h
|
|
||||||
class TextureAtlas {
|
|
||||||
AtlasRegion* AllocateRegion(int source_id, int width, int height);
|
|
||||||
absl::Status PackBitmap(const Bitmap& src, const AtlasRegion& region);
|
|
||||||
absl::Status DrawRegion(int source_id, int dest_x, int dest_y);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Future usage:
|
|
||||||
TextureAtlas atlas(2048, 2048);
|
|
||||||
auto* region = atlas.AllocateRegion(room_id, 512, 512);
|
|
||||||
atlas.PackBitmap(room.bg1_buffer().bitmap(), *region);
|
|
||||||
atlas.DrawRegion(room_id, x, y);
|
|
||||||
```
|
|
||||||
|
|
||||||
When implemented:
|
|
||||||
- Single GPU texture for all rooms
|
|
||||||
- Fewer texture binds per frame
|
|
||||||
- Better performance with many rooms
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Files Modified (16 files)
|
|
||||||
|
|
||||||
### Dungeon Editor
|
|
||||||
```
|
|
||||||
✅ src/app/editor/dungeon/dungeon_editor_v2.cc
|
|
||||||
- Room ID in title `[003] Room Name`
|
|
||||||
- Object count in status bar
|
|
||||||
- Room graphics canvas 257x257 (fixed from 1025 tall)
|
|
||||||
- Loading order fix (CRITICAL)
|
|
||||||
|
|
||||||
✅ src/app/editor/dungeon/dungeon_canvas_viewer.h/cc
|
|
||||||
- Properties in table layout
|
|
||||||
- Compact layer controls
|
|
||||||
- DrawRoomLayout() stub
|
|
||||||
- DrawObjectPositionOutlines() working
|
|
||||||
- Removed ObjectRenderer
|
|
||||||
|
|
||||||
✅ src/app/editor/dungeon/dungeon_object_selector.h/cc
|
|
||||||
- Removed ObjectRenderer
|
|
||||||
- TODO for ObjectDrawer-based preview
|
|
||||||
```
|
|
||||||
|
|
||||||
### Room System
|
|
||||||
```
|
|
||||||
✅ src/app/zelda3/dungeon/room.h
|
|
||||||
- floor1()/floor2() accessors
|
|
||||||
- Removed LoadGraphicsSheetsIntoArena()
|
|
||||||
|
|
||||||
✅ src/app/zelda3/dungeon/room.cc
|
|
||||||
- Removed LoadGraphicsSheetsIntoArena() impl
|
|
||||||
- Added UPDATE texture commands
|
|
||||||
- Palette before objects (correct order)
|
|
||||||
- Debug logging
|
|
||||||
```
|
|
||||||
|
|
||||||
### Graphics Infrastructure
|
|
||||||
```
|
|
||||||
✅ src/app/gfx/texture_atlas.h - NEW
|
|
||||||
✅ src/app/gfx/texture_atlas.cc - NEW
|
|
||||||
✅ src/app/gfx/gfx_library.cmake - Added texture_atlas.cc
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
```
|
|
||||||
❌ test/unit/zelda3/dungeon_object_renderer_mock_test.cc - DELETED
|
|
||||||
❌ test/integration/zelda3/dungeon_object_renderer_integration_test.cc - DELETED
|
|
||||||
✅ test/CMakeLists.txt - Updated
|
|
||||||
✅ test/unit/zelda3/test_dungeon_objects.cc - ObjectDrawer
|
|
||||||
✅ test/integration/zelda3/dungeon_object_rendering_tests.cc - Simplified
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Statistics
|
|
||||||
|
|
||||||
```
|
|
||||||
Tasks Completed: 13/20 (65%)
|
|
||||||
Code Deleted: ~1600 lines (tests + obsolete methods)
|
|
||||||
Code Added: ~400 lines (fixes + features + atlas)
|
|
||||||
Net Change: -1200 lines
|
|
||||||
Files Modified: 16
|
|
||||||
Files Deleted: 2 (tests)
|
|
||||||
Files Created: 2 (atlas.h/cc)
|
|
||||||
Documentation: Consolidated 4 guides → 1
|
|
||||||
Build Status: ✅ Core libraries compile
|
|
||||||
User Verification: ✅ "it does render correct now"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Floor tiles blank/wrong?
|
|
||||||
**Check**: Debug output should show `floor1=4, floor2=8` (NOT 0)
|
|
||||||
**Fix**: Verify loading order in `dungeon_editor_v2.cc:442-460`
|
|
||||||
|
|
||||||
### Objects not visible?
|
|
||||||
**Check**: Object outlines should show colored rectangles
|
|
||||||
**If no outlines**: LoadObjects() failed
|
|
||||||
**If outlines but no graphics**: ObjectDrawer or tile data issue
|
|
||||||
|
|
||||||
### Wall graphics not rendering?
|
|
||||||
**Check**: Texture UPDATE commands in `room.cc:332-344`
|
|
||||||
**Debug**: Check ObjectDrawer logs for "Writing Tile16"
|
|
||||||
**Verify**: Objects drawn to bitmaps before texture update
|
|
||||||
|
|
||||||
### Performance issues?
|
|
||||||
**Cause**: Each room = ~2MB (2x 512x512 bitmaps)
|
|
||||||
**Solution**: Close unused room windows or implement texture pooling
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Session Summary
|
|
||||||
|
|
||||||
### Accomplished This Session
|
|
||||||
- ✅ Fixed 6 critical bugs (segfault, loading order, floor variables, property detection, wall rendering, ObjectRenderer confusion)
|
|
||||||
- ✅ Decoupled room buffers from Arena
|
|
||||||
- ✅ Deleted 1270 lines of redundant test code
|
|
||||||
- ✅ UI improvements (tables, titles, compact controls)
|
|
||||||
- ✅ Object position visualization
|
|
||||||
- ✅ Texture atlas infrastructure
|
|
||||||
- ✅ Documentation consolidated
|
|
||||||
|
|
||||||
### Statistics
|
|
||||||
- **Lines Deleted**: ~1600
|
|
||||||
- **Lines Added**: ~400
|
|
||||||
- **Net Change**: -1200 lines
|
|
||||||
- **Build Status**: ✅ Success
|
|
||||||
- **Test Status**: ✅ Core libraries pass
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Code Reference
|
|
||||||
|
|
||||||
### Property Table (NEW)
|
|
||||||
```cpp
|
|
||||||
// dungeon_canvas_viewer.cc:45-86
|
|
||||||
if (ImGui::BeginTable("##RoomProperties", 4, ...)) {
|
|
||||||
// Graphics | Layout | Floors | Message
|
|
||||||
gui::InputHexByte("Gfx", &room.blockset);
|
|
||||||
gui::InputHexByte("Sprite", &room.spriteset);
|
|
||||||
gui::InputHexByte("Palette", &room.palette);
|
|
||||||
// ... etc
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Compact Layer Controls (NEW)
|
|
||||||
```cpp
|
|
||||||
// dungeon_canvas_viewer.cc:90-107
|
|
||||||
if (ImGui::BeginTable("##LayerControls", 3, ...)) {
|
|
||||||
ImGui::Checkbox("Show BG1", &layer_settings.bg1_visible);
|
|
||||||
ImGui::Checkbox("Show BG2", &layer_settings.bg2_visible);
|
|
||||||
ImGui::Combo("##BG2Type", &layer_settings.bg2_layer_type, ...);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Room ID in Title (NEW)
|
|
||||||
```cpp
|
|
||||||
// dungeon_editor_v2.cc:378
|
|
||||||
base_name = absl::StrFormat("[%03X] %s", room_id, zelda3::kRoomNames[room_id]);
|
|
||||||
// Result: "[002] Behind Sanctuary (Switch)"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Related Documentation
|
## Related Documentation
|
||||||
|
|
||||||
- **E2-development-guide.md** - Core architectural patterns
|
- **E2-development-guide.md** - Core architectural patterns
|
||||||
|
|||||||
@@ -1,179 +1,70 @@
|
|||||||
# G1 - Canvas System and Automation
|
# Canvas System Overview
|
||||||
|
|
||||||
This document provides a comprehensive guide to the Canvas system in yaze, including its architecture, features, and the powerful automation API that enables programmatic control for the `z3ed` CLI, AI agents, and GUI testing.
|
## Canvas Architecture
|
||||||
|
- **Canvas States**: track `canvas`, `content`, and `draw` rectangles independently; expose size/scale through `CanvasState` inspection panel
|
||||||
|
- **Layer Stack**: background ➝ bitmaps ➝ entity overlays ➝ selection/tooltip layers
|
||||||
|
- **Interaction Modes**: Tile Paint, Tile Select, Rectangle Select, Entity Manipulation, Palette Editing, Diagnostics
|
||||||
|
- **Context Menu**: persistent menu with material icon sections (Mode, View, Info, Bitmap, Palette, BPP, Performance, Layout, Custom)
|
||||||
|
|
||||||
## 1. Core Concepts
|
## Core API Patterns
|
||||||
|
- Modern usage: `Begin/End` (auto grid/overlay, persistent context menu)
|
||||||
|
- Legacy helpers still available (`DrawBackground`, `DrawGrid`, `DrawSelectRect`, etc.)
|
||||||
|
- Unified state snapshot: `CanvasState` exposes geometry, zoom, scroll
|
||||||
|
- Interaction handler manages mode-specific tools (tile brush, select rect, entity gizmo)
|
||||||
|
|
||||||
### Canvas Structure
|
## Context Menu Sections
|
||||||
- **Background**: Drawing surface with border and optional scrolling
|
- **Mode Selector**: switch modes with icons (Brush, Select, Rect, Bitmap, Palette, BPP, Perf)
|
||||||
- **Content Layer**: Bitmaps, tiles, custom graphics
|
- **View & Grid**: reset/zoom, toggle grid/labels, advanced/scaling dialogs
|
||||||
- **Grid Overlay**: Optional grid with hex labels
|
- **Canvas Info**: real-time canvas/content size, scale, scroll, mouse position
|
||||||
- **Interaction Layer**: Hover previews, selection rectangles
|
- **Bitmap/Palette/BPP**: format conversion, palette analysis, BPP workflows with persistent modals
|
||||||
|
- **Performance**: profiler metrics, dashboard, usage report
|
||||||
|
- **Layout**: draggable toggle, auto resize, grid step
|
||||||
|
- **Custom Actions**: consumer-provided menu items
|
||||||
|
|
||||||
### Coordinate Systems
|
## Interaction Modes & Capabilities
|
||||||
- **Screen Space**: ImGui window coordinates
|
- **Tile Painting**: tile16 painter, brush size, finish stroke callbacks
|
||||||
- **Canvas Space**: Relative to canvas origin (0,0)
|
- Operations: finish_paint, reset_view, zoom, grid, scaling
|
||||||
- **Tile Space**: Grid-aligned tile indices
|
- **Tile Selection**: multi-select rectangle, copy/paste selection
|
||||||
- **World Space**: Overworld 4096x4096 large map coordinates
|
- Operations: select_all, clear_selection, reset_view, zoom, grid, scaling
|
||||||
|
- **Rectangle Selection**: drag-select area, clear selection
|
||||||
|
- Operations: clear_selection, reset_view, zoom, grid, scaling
|
||||||
|
- **Bitmap Editing**: format conversion, bitmap manipulation
|
||||||
|
- Operations: bitmap_convert, palette_edit, bpp_analysis, reset_view, zoom, grid, scaling
|
||||||
|
- **Palette Editing**: inline palette editor, ROM palette picker, color analysis
|
||||||
|
- Operations: palette_edit, palette_analysis, reset_palette, reset_view, zoom, grid, scaling
|
||||||
|
- **BPP Conversion**: format analysis, conversion workflows
|
||||||
|
- Operations: bpp_analysis, bpp_conversion, bitmap_convert, reset_view, zoom, grid, scaling
|
||||||
|
- **Performance Mode**: diagnostics, texture queue, performance overlays
|
||||||
|
- Operations: performance, usage_report, copy_metrics, reset_view, zoom, grid, scaling
|
||||||
|
|
||||||
## 2. Canvas API and Usage
|
## Debug & Diagnostics
|
||||||
|
- Persistent modals (`View→Advanced`, `View→Scaling`, `Palette`, `BPP`) stay open until closed
|
||||||
|
- Texture inspector shows current bitmap, VRAM sheet, palette group, usage stats
|
||||||
|
- State overlay: canvas size, content size, global scale, scroll, highlight entity
|
||||||
|
- Performance HUD: operation counts, timing graphs, usage recommendations
|
||||||
|
|
||||||
### Modern Begin/End Pattern
|
## Automation API
|
||||||
The recommended way to use the canvas is with the `Begin()`/`End()` pattern, which handles setup and teardown automatically.
|
- CanvasAutomationAPI: tile operations (`SetTileAt`, `SelectRect`), view control (`ScrollToTile`, `SetZoom`), entity manipulation hooks
|
||||||
|
- Exposed through CLI (`z3ed`) and gRPC service, matching UI modes
|
||||||
|
|
||||||
```cpp
|
## Integration Steps for Editors
|
||||||
canvas.Begin(ImVec2(512, 512));
|
1. Construct `Canvas`, set renderer (optional) and ID
|
||||||
canvas.DrawBitmap(bitmap, 0, 0, 2.0f);
|
2. Call `InitializePaletteEditor` and `SetUsageMode`
|
||||||
canvas.End(); // Automatic grid + overlay
|
3. Configure available modes: `SetAvailableModes({kTilePainting, kTileSelecting})`
|
||||||
```
|
4. Register mode callbacks (tile paint finish, selection clear, etc.)
|
||||||
|
5. During frame: `canvas.Begin(size)` → draw bitmaps/entities → `canvas.End()`
|
||||||
|
6. Provide custom menu items via `AddMenuItem`/`AddMenuItem(item, usage)`
|
||||||
|
7. Use `GetConfig()`/`GetSelection()` for state; respond to context menu commands via callback lambda in `Render`
|
||||||
|
|
||||||
### Feature: Tile Painting
|
## Migration Checklist
|
||||||
The canvas provides methods for painting single tiles, painting from a tilemap, and painting with solid colors.
|
- Replace direct `DrawContextMenu` logic with new render callback signature
|
||||||
|
- Move palette/BPP helpers into `canvas/` module; update includes
|
||||||
|
- Ensure persistent modals wired (advanced/scaling/palette/bpp/perf)
|
||||||
|
- Update usage tracker integrations to record mode switches
|
||||||
|
- Validate overworld/tile16/dungeon editors in tile paint, select, entity modes
|
||||||
|
|
||||||
```cpp
|
## Testing Notes
|
||||||
if (canvas.DrawTilePainter(current_tile_bitmap, 16, 2.0f)) {
|
- Manual regression: overworld paint/select, tile16 painter, dungeon entity drag
|
||||||
ImVec2 paint_pos = canvas.drawn_tile_position();
|
- Verify context menu persists and modals remain until closed
|
||||||
ApplyTileToMap(paint_pos, current_tile_id);
|
- Ensure palette/BPP modals populate with correct bitmap/palette data
|
||||||
}
|
- Automation: run CanvasAutomation API tests/end-to-end scripts for overworld edits
|
||||||
```
|
|
||||||
|
|
||||||
### Feature: Tile Selection
|
|
||||||
The canvas supports both single-tile and multi-tile rectangle selection.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
canvas.DrawSelectRect(current_map_id, 16, 1.0f);
|
|
||||||
|
|
||||||
if (canvas.select_rect_active()) {
|
|
||||||
const auto& selected_tiles = canvas.selected_tiles();
|
|
||||||
// Process selection...
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Feature: Large Map Support
|
|
||||||
The canvas can handle large maps with multiple local maps, including boundary clamping to prevent selection wrapping.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
canvas.SetClampRectToLocalMaps(true); // Default - prevents wrapping
|
|
||||||
```
|
|
||||||
|
|
||||||
### Feature: Context Menu
|
|
||||||
The canvas has a customizable context menu.
|
|
||||||
|
|
||||||
```cpp
|
|
||||||
canvas.AddContextMenuItem({
|
|
||||||
"My Action",
|
|
||||||
[this]() { DoAction(); }
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## 3. Canvas Automation API
|
|
||||||
|
|
||||||
The `CanvasAutomationAPI` provides a programmatic interface for controlling canvas operations, enabling scripted editing, AI agent integration, and automated testing.
|
|
||||||
|
|
||||||
### Accessing the API
|
|
||||||
```cpp
|
|
||||||
auto* api = canvas.GetAutomationAPI();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Tile Operations
|
|
||||||
```cpp
|
|
||||||
// Paint a single tile
|
|
||||||
bool SetTileAt(int x, int y, int tile_id);
|
|
||||||
|
|
||||||
// Get the tile ID at a specific location
|
|
||||||
int GetTileAt(int x, int y) const;
|
|
||||||
|
|
||||||
// Paint multiple tiles in a batch
|
|
||||||
bool SetTiles(const std::vector<std::tuple<int,int,int>>& tiles);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Selection Operations
|
|
||||||
```cpp
|
|
||||||
// Select a single tile
|
|
||||||
void SelectTile(int x, int y);
|
|
||||||
|
|
||||||
// Select a rectangular region of tiles
|
|
||||||
void SelectTileRect(int x1, int y1, int x2, int y2);
|
|
||||||
|
|
||||||
// Get the current selection state
|
|
||||||
SelectionState GetSelection() const;
|
|
||||||
|
|
||||||
// Clear the current selection
|
|
||||||
void ClearSelection();
|
|
||||||
```
|
|
||||||
|
|
||||||
### View Operations
|
|
||||||
```cpp
|
|
||||||
// Scroll the canvas to make a tile visible
|
|
||||||
void ScrollToTile(int x, int y);
|
|
||||||
|
|
||||||
// Set the canvas zoom level
|
|
||||||
void SetZoom(float zoom_level);
|
|
||||||
|
|
||||||
// Get the current zoom level
|
|
||||||
float GetZoom() const;
|
|
||||||
|
|
||||||
// Center the canvas view on a specific coordinate
|
|
||||||
void CenterOn(int x, int y);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Query Operations
|
|
||||||
```cpp
|
|
||||||
// Get the dimensions of the canvas in tiles
|
|
||||||
CanvasDimensions GetDimensions() const;
|
|
||||||
|
|
||||||
// Get the currently visible region of the canvas
|
|
||||||
VisibleRegion GetVisibleRegion() const;
|
|
||||||
|
|
||||||
// Check if a tile is currently visible
|
|
||||||
bool IsTileVisible(int x, int y) const;
|
|
||||||
|
|
||||||
// Get the cursor position in logical tile coordinates
|
|
||||||
ImVec2 GetCursorPosition() const;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Simulation Operations (for GUI Automation)
|
|
||||||
```cpp
|
|
||||||
// Simulate a mouse click at a specific tile
|
|
||||||
void SimulateClick(int x, int y, ImGuiMouseButton button = ImGuiMouseButton_Left);
|
|
||||||
|
|
||||||
// Simulate a drag operation between two tiles
|
|
||||||
void SimulateDrag(int x1, int y1, int x2, int y2);
|
|
||||||
|
|
||||||
// Wait for the canvas to finish processing
|
|
||||||
void WaitForIdle();
|
|
||||||
```
|
|
||||||
|
|
||||||
## 4. `z3ed` CLI Integration
|
|
||||||
|
|
||||||
The Canvas Automation API is exposed through the `z3ed` CLI, allowing for scripted overworld editing.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Set a tile
|
|
||||||
z3ed overworld set-tile --map 0 --x 10 --y 10 --tile-id 0x0042 --rom zelda3.sfc
|
|
||||||
|
|
||||||
# Get a tile
|
|
||||||
z3ed overworld get-tile --map 0 --x 10 --y 10 --rom zelda3.sfc
|
|
||||||
|
|
||||||
# Select a rectangle
|
|
||||||
z3ed overworld select-rect --map 0 --x1 5 --y1 5 --x2 15 --y2 15 --rom zelda3.sfc
|
|
||||||
|
|
||||||
# Scroll to a tile
|
|
||||||
z3ed overworld scroll-to --map 0 --x 20 --y 20 --center --rom zelda3.sfc
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. gRPC Service
|
|
||||||
|
|
||||||
The Canvas Automation API is also exposed via a gRPC service, allowing for remote control of the canvas.
|
|
||||||
|
|
||||||
**Proto Definition (`protos/canvas_automation.proto`):**
|
|
||||||
```protobuf
|
|
||||||
service CanvasAutomation {
|
|
||||||
rpc SetTileAt(SetTileRequest) returns (SetTileResponse);
|
|
||||||
rpc GetTileAt(GetTileRequest) returns (GetTileResponse);
|
|
||||||
rpc SelectTileRect(SelectTileRectRequest) returns (SelectTileRectResponse);
|
|
||||||
// ... and so on for all API methods
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This service is hosted by the `UnifiedGRPCServer` in the main yaze application, allowing tools like `grpcurl` or custom clients to interact with the canvas remotely.
|
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ SDL_Surface* Arena::AllocateSurface(int w, int h, int depth, int format) {
|
|||||||
- `src/app/emu/emu.cc` - Standalone emulator with SDL2Renderer
|
- `src/app/emu/emu.cc` - Standalone emulator with SDL2Renderer
|
||||||
|
|
||||||
### GUI/Widget Files
|
### GUI/Widget Files
|
||||||
- `src/app/gui/canvas_utils.cc` - Fixed palette application logic
|
- `src/app/gui/canvas/canvas_utils.cc` - Fixed palette application logic
|
||||||
- `src/app/gui/canvas/canvas_context_menu.cc` - Arena queue for bitmap ops
|
- `src/app/gui/canvas/canvas_context_menu.cc` - Arena queue for bitmap ops
|
||||||
- `src/app/gui/widgets/palette_widget.cc` - Arena queue for palette changes
|
- `src/app/gui/widgets/palette_widget.cc` - Arena queue for palette changes
|
||||||
- `src/app/gui/widgets/dungeon_object_emulator_preview.{h,cc}` - Optional renderer
|
- `src/app/gui/widgets/dungeon_object_emulator_preview.{h,cc}` - Optional renderer
|
||||||
|
|||||||
@@ -1115,7 +1115,6 @@ absl::Status OverworldEditor::CheckForCurrentMap() {
|
|||||||
break;
|
break;
|
||||||
case AreaSizeEnum::SmallArea:
|
case AreaSizeEnum::SmallArea:
|
||||||
default:
|
default:
|
||||||
// 1x1 grid (512x512)
|
|
||||||
ow_map_canvas_.DrawOutline(parent_map_x * kOverworldMapSize,
|
ow_map_canvas_.DrawOutline(parent_map_x * kOverworldMapSize,
|
||||||
parent_map_y * kOverworldMapSize,
|
parent_map_y * kOverworldMapSize,
|
||||||
kOverworldMapSize, kOverworldMapSize);
|
kOverworldMapSize, kOverworldMapSize);
|
||||||
@@ -1123,6 +1122,7 @@ absl::Status OverworldEditor::CheckForCurrentMap() {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Legacy logic for vanilla and v2 ROMs
|
// Legacy logic for vanilla and v2 ROMs
|
||||||
|
int world_offset = current_world_ * 0x40;
|
||||||
if (overworld_.overworld_map(current_map_)->is_large_map() ||
|
if (overworld_.overworld_map(current_map_)->is_large_map() ||
|
||||||
overworld_.overworld_map(current_map_)->large_index() != 0) {
|
overworld_.overworld_map(current_map_)->large_index() != 0) {
|
||||||
const int highlight_parent =
|
const int highlight_parent =
|
||||||
@@ -1141,6 +1141,8 @@ absl::Status OverworldEditor::CheckForCurrentMap() {
|
|||||||
current_map_x = current_highlighted_map % 8;
|
current_map_x = current_highlighted_map % 8;
|
||||||
current_map_y = current_highlighted_map / 8;
|
current_map_y = current_highlighted_map / 8;
|
||||||
} else if (current_world_ == 1) {
|
} else if (current_world_ == 1) {
|
||||||
|
// TODO: Vanilla fix dark world large map outline
|
||||||
|
// When switching to dark world, large maps dont outline properly.
|
||||||
// Dark World (0x40-0x7F)
|
// Dark World (0x40-0x7F)
|
||||||
current_map_x = (current_highlighted_map - 0x40) % 8;
|
current_map_x = (current_highlighted_map - 0x40) % 8;
|
||||||
current_map_y = (current_highlighted_map - 0x40) / 8;
|
current_map_y = (current_highlighted_map - 0x40) / 8;
|
||||||
@@ -2352,14 +2354,13 @@ void OverworldEditor::ScrollBlocksetCanvasToCurrentTile() {
|
|||||||
float tile_x = static_cast<float>(tile_col * kTileDisplaySize);
|
float tile_x = static_cast<float>(tile_col * kTileDisplaySize);
|
||||||
float tile_y = static_cast<float>(tile_row * kTileDisplaySize);
|
float tile_y = static_cast<float>(tile_row * kTileDisplaySize);
|
||||||
|
|
||||||
ImVec2 canvas_size = blockset_canvas_.canvas_size();
|
const ImVec2 window_size = ImGui::GetWindowSize();
|
||||||
float scroll_x = tile_x - (canvas_size.x / 2.0F) + (kTileDisplaySize / 2.0F);
|
float scroll_x = tile_x - (window_size.x / 2.0F) + (kTileDisplaySize / 2.0F);
|
||||||
float scroll_y = tile_y - (canvas_size.y / 2.0F) + (kTileDisplaySize / 2.0F);
|
float scroll_y = tile_y - (window_size.y / 2.0F) + (kTileDisplaySize / 2.0F);
|
||||||
|
|
||||||
if (scroll_x < 0) scroll_x = 0;
|
// Use ImGui scroll API so both the canvas and child scroll view stay in sync.
|
||||||
if (scroll_y < 0) scroll_y = 0;
|
ImGui::SetScrollX(std::max(0.0f, scroll_x));
|
||||||
|
ImGui::SetScrollY(std::max(0.0f, scroll_y));
|
||||||
blockset_canvas_.set_scrolling(ImVec2(-scroll_x, -scroll_y));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OverworldEditor::DrawOverworldProperties() {
|
void OverworldEditor::DrawOverworldProperties() {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include "app/gfx/performance/performance_profiler.h"
|
#include "app/gfx/performance/performance_profiler.h"
|
||||||
#include "app/gfx/performance/performance_dashboard.h"
|
#include "app/gfx/performance/performance_dashboard.h"
|
||||||
#include "app/gui/widgets/palette_widget.h"
|
#include "app/gui/widgets/palette_widget.h"
|
||||||
#include "app/gui/bpp_format_ui.h"
|
#include "app/gui/canvas/bpp_format_ui.h"
|
||||||
#include "app/gui/icons.h"
|
#include "app/gui/icons.h"
|
||||||
#include "imgui/imgui.h"
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include "app/gfx/bitmap.h"
|
#include "app/gfx/bitmap.h"
|
||||||
#include "app/gfx/snes_palette.h"
|
#include "app/gfx/snes_palette.h"
|
||||||
#include "app/gfx/bpp_format_manager.h"
|
#include "app/gfx/bpp_format_manager.h"
|
||||||
#include "app/gui/canvas_utils.h"
|
#include "app/gui/canvas/canvas_utils.h"
|
||||||
#include "imgui/imgui.h"
|
#include "imgui/imgui.h"
|
||||||
|
|
||||||
namespace yaze {
|
namespace yaze {
|
||||||
|
|||||||
Reference in New Issue
Block a user