refactor: Remove manual object renderer and dungeon renderer from the editor

- Deleted the ManualObjectRenderer and DungeonRenderer classes to streamline the codebase and reduce redundancy.
- Updated DungeonCanvasViewer and DungeonEditorV2 to remove references to the deleted renderers, ensuring proper functionality without them.
- Enhanced the rendering logic in DungeonCanvasViewer to directly handle object rendering and background layers, improving performance and maintainability.
- Added debug logging to track rendering processes and ensure proper graphics loading.
This commit is contained in:
scawful
2025-10-09 15:47:15 -04:00
parent 44800ceccc
commit 9d0b6737cd
21 changed files with 1789 additions and 1077 deletions

View File

@@ -71,23 +71,46 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
ImGui::BeginGroup();
if (rooms_) {
gui::InputHexByte("Layout", &(*rooms_)[room_id].layout);
auto& room = (*rooms_)[room_id];
// Store previous values to detect changes
static int prev_blockset = -1;
static int prev_palette = -1;
static int prev_layout = -1;
static int prev_spriteset = -1;
gui::InputHexByte("Layout", &room.layout);
ImGui::SameLine();
gui::InputHexByte("Blockset", &(*rooms_)[room_id].blockset);
gui::InputHexByte("Gfx", &room.blockset);
ImGui::SameLine();
gui::InputHexByte("Spriteset", &(*rooms_)[room_id].spriteset);
gui::InputHexByte("Spriteset", &room.spriteset);
ImGui::SameLine();
gui::InputHexByte("Palette", &(*rooms_)[room_id].palette);
gui::InputHexByte("Palette", &room.palette);
gui::InputHexByte("Floor1", &(*rooms_)[room_id].floor1);
gui::InputHexByte("Floor1", &room.floor1);
ImGui::SameLine();
gui::InputHexByte("Floor2", &(*rooms_)[room_id].floor2);
gui::InputHexByte("Floor2", &room.floor2);
ImGui::SameLine();
gui::InputHexWord("Message ID", &(*rooms_)[room_id].message_id_);
ImGui::SameLine();
if (Button("Load Room Graphics")) {
(void)LoadAndRenderRoomGraphics(room_id);
gui::InputHexWord("Message ID", &room.message_id_);
// Check if critical properties changed and trigger reload
if (prev_blockset != room.blockset || prev_palette != room.palette ||
prev_layout != room.layout || prev_spriteset != room.spriteset) {
// Only reload if ROM is properly loaded
if (room.rom() && room.rom()->is_loaded()) {
// Force reload of room graphics
room.LoadRoomGraphics(room.blockset);
room.RenderRoomGraphics();
// Update background layers
UpdateRoomBackgroundLayers(room_id);
}
prev_blockset = room.blockset;
prev_palette = room.palette;
prev_layout = room.layout;
prev_spriteset = room.spriteset;
}
}
@@ -106,9 +129,14 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
auto& bg1_bitmap = room.bg1_buffer().bitmap();
bool needs_render = !bg1_bitmap.is_active() || bg1_bitmap.width() == 0;
// Render immediately if needed
if (needs_render) {
// Render immediately if needed (but only once per room change)
static int last_rendered_room = -1;
static bool has_rendered = false;
if (needs_render && (last_rendered_room != room_id || !has_rendered)) {
printf("[DungeonCanvasViewer] Loading and rendering graphics for room %d\n", room_id);
(void)LoadAndRenderRoomGraphics(room_id);
last_rendered_room = room_id;
has_rendered = true;
}
// Load room objects if not already loaded
@@ -120,24 +148,6 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
// This already includes objects drawn by ObjectDrawer in Room::RenderObjectsToBackground()
RenderRoomBackgroundLayers(room_id);
// TEMPORARY: Render all objects as primitives until proper rendering is fixed
// This ensures we can see objects while debugging the texture pipeline
if (current_palette_id_ < current_palette_group_.size()) {
auto room_palette = current_palette_group_[current_palette_id_];
// Render regular objects as colored rectangles (FALLBACK)
for (const auto& object : room.GetTileObjects()) {
RenderObjectInCanvas(object, room_palette);
}
// Render special objects with primitive shapes (overlays)
RenderStairObjects(room, room_palette);
RenderChests(room);
RenderDoorObjects(room);
RenderWallObjects(room);
RenderPotObjects(room);
}
// Render sprites as simple 16x16 squares with labels
// (Sprites are not part of the background buffers)
RenderSprites(room);
@@ -175,107 +185,6 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
}
}
void DungeonCanvasViewer::RenderObjectInCanvas(const zelda3::RoomObject &object,
const gfx::SnesPalette &palette) {
// Validate ROM is loaded
if (!rom_ || !rom_->is_loaded()) {
return;
}
// Convert room coordinates to canvas coordinates
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
// Check if object is within canvas bounds
if (!IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
return; // Skip objects outside visible area
}
// Calculate palette hash for caching
uint64_t palette_hash = 0;
for (size_t i = 0; i < palette.size() && i < 16; ++i) {
palette_hash ^= std::hash<uint16_t>{}(palette[i].snes()) + 0x9e3779b9 +
(palette_hash << 6) + (palette_hash >> 2);
}
// Check cache first
for (auto& cached : object_render_cache_) {
if (cached.object_id == object.id_ && cached.object_x == object.x_ &&
cached.object_y == object.y_ && cached.object_size == object.size_ &&
cached.palette_hash == palette_hash && cached.is_valid) {
canvas_.DrawBitmap(cached.rendered_bitmap, canvas_x, canvas_y, 1.0f, 255);
return;
}
}
// Create a mutable copy of the object to ensure tiles are loaded
auto mutable_object = object;
mutable_object.set_rom(rom_);
mutable_object.EnsureTilesLoaded();
// Try to render the object with proper graphics
auto render_result = object_renderer_.RenderObject(mutable_object, palette);
if (render_result.ok()) {
auto object_bitmap = std::move(render_result.value());
// Ensure the bitmap is valid and has content
if (object_bitmap.width() > 0 && object_bitmap.height() > 0) {
object_bitmap.SetPalette(palette);
// Add to cache
ObjectRenderCache cache_entry;
cache_entry.object_id = object.id_;
cache_entry.object_x = object.x_;
cache_entry.object_y = object.y_;
cache_entry.object_size = object.size_;
cache_entry.palette_hash = palette_hash;
cache_entry.rendered_bitmap = std::move(object_bitmap); // Move bitmap into cache
cache_entry.is_valid = true;
if (object_render_cache_.size() >= 200) { // Limit cache size
object_render_cache_.erase(object_render_cache_.begin());
}
object_render_cache_.push_back(std::move(cache_entry));
// Get pointer to cached bitmap and queue texture creation
gfx::Bitmap* cached_bitmap = &object_render_cache_.back().rendered_bitmap;
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, cached_bitmap);
canvas_.DrawBitmap(*cached_bitmap, canvas_x, canvas_y, 1.0f, 255);
return;
}
}
// Fallback: Draw object as colored rectangle with ID if rendering fails
ImVec4 object_color;
// Color-code objects based on layer
switch (object.layer_) {
case zelda3::RoomObject::LayerType::BG1:
object_color = ImVec4(0.8f, 0.4f, 0.4f, 0.8f); // Red-ish for BG1
break;
case zelda3::RoomObject::LayerType::BG2:
object_color = ImVec4(0.4f, 0.8f, 0.4f, 0.8f); // Green-ish for BG2
break;
case zelda3::RoomObject::LayerType::BG3:
object_color = ImVec4(0.4f, 0.4f, 0.8f, 0.8f); // Blue-ish for BG3
break;
default:
object_color = ImVec4(0.6f, 0.6f, 0.6f, 0.8f); // Gray for unknown
break;
}
// Calculate object size (8x8 is base, size affects width/height)
int object_width = 8 + (object.size_ & 0x0F) * 8;
int object_height = 8 + ((object.size_ >> 4) & 0x0F) * 8;
canvas_.DrawRect(canvas_x, canvas_y, object_width, object_height, object_color);
canvas_.DrawRect(canvas_x, canvas_y, object_width, object_height,
ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); // Black border
// Draw object ID
std::string object_text = absl::StrFormat("0x%X", object.id_);
canvas_.DrawText(object_text, canvas_x + object_width + 2, canvas_y);
}
void DungeonCanvasViewer::DisplayObjectInfo(const zelda3::RoomObject &object,
int canvas_x, int canvas_y) {
@@ -287,37 +196,6 @@ void DungeonCanvasViewer::DisplayObjectInfo(const zelda3::RoomObject &object,
canvas_.DrawText(info_text, canvas_x, canvas_y - 12);
}
void DungeonCanvasViewer::RenderStairObjects(const zelda3::Room& room,
const gfx::SnesPalette& palette) {
// Render stair objects with special highlighting to show they enable layer transitions
// Stair object IDs from room.h: {0x139, 0x138, 0x13B, 0x12E, 0x12D}
constexpr uint16_t stair_ids[] = {0x139, 0x138, 0x13B, 0x12E, 0x12D};
for (const auto& object : room.GetTileObjects()) {
bool is_stair = false;
for (uint16_t stair_id : stair_ids) {
if (object.id_ == stair_id) {
is_stair = true;
break;
}
}
if (is_stair) {
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
// Draw stair object with special highlighting
canvas_.DrawRect(canvas_x - 2, canvas_y - 2, 20, 20,
ImVec4(1.0f, 1.0f, 0.0f, 0.8f)); // Yellow highlight
// Draw text label
std::string stair_text = absl::StrFormat("STAIR\n0x%X", object.id_);
canvas_.DrawText(stair_text, canvas_x + 22, canvas_y);
}
}
}
}
void DungeonCanvasViewer::RenderSprites(const zelda3::Room& room) {
// Render sprites as simple 8x8 squares with sprite name/ID
for (const auto& sprite : room.GetSprites()) {
@@ -364,179 +242,6 @@ void DungeonCanvasViewer::RenderSprites(const zelda3::Room& room) {
}
}
void DungeonCanvasViewer::RenderChests(const zelda3::Room& room) {
// Render chest objects from tile objects - chests are objects with IDs 0xF9, 0xFA
for (const auto& object : room.GetTileObjects()) {
if (object.id_ == 0xF9 || object.id_ == 0xFA) { // Chest object IDs
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 16)) {
// Determine if it's a big chest based on object ID
bool is_big_chest = (object.id_ == 0xFA);
// Draw chest base
ImVec4 chest_color = is_big_chest ?
ImVec4(0.8f, 0.6f, 0.2f, 0.9f) : // Gold for big chest
ImVec4(0.6f, 0.4f, 0.2f, 0.9f); // Brown for small chest
int chest_size = is_big_chest ? 16 : 8; // Big chests are larger
canvas_.DrawRect(canvas_x, canvas_y + 4, chest_size, 4, chest_color);
// Draw chest lid (slightly lighter)
ImVec4 lid_color = is_big_chest ?
ImVec4(0.9f, 0.7f, 0.3f, 0.9f) :
ImVec4(0.7f, 0.5f, 0.3f, 0.9f);
canvas_.DrawRect(canvas_x, canvas_y + 2, chest_size, 3, lid_color);
// Draw chest borders
canvas_.DrawRect(canvas_x, canvas_y + 2, chest_size, 6, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Draw text label
std::string chest_text = is_big_chest ? "BIG\nCHEST" : "CHEST";
canvas_.DrawText(chest_text, canvas_x + chest_size + 2, canvas_y + 3);
}
}
}
}
void DungeonCanvasViewer::RenderDoorObjects(const zelda3::Room& room) {
// Render door objects from tile objects based on IDs from assembly constants
constexpr uint16_t door_ids[] = {0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E};
for (const auto& object : room.GetTileObjects()) {
bool is_door = false;
for (uint16_t door_id : door_ids) {
if (object.id_ == door_id) {
is_door = true;
break;
}
}
if (is_door) {
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 16)) {
// Draw door frame
canvas_.DrawRect(canvas_x, canvas_y, 16, 16, ImVec4(0.5f, 0.3f, 0.2f, 0.8f)); // Brown frame
// Draw door opening (darker)
canvas_.DrawRect(canvas_x + 2, canvas_y + 2, 12, 12, ImVec4(0.1f, 0.1f, 0.1f, 0.9f));
// Draw door border
canvas_.DrawRect(canvas_x, canvas_y, 16, 16, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Draw text label
std::string door_text = absl::StrFormat("DOOR\n0x%X", object.id_);
canvas_.DrawText(door_text, canvas_x + 18, canvas_y + 4);
}
}
}
}
void DungeonCanvasViewer::RenderWallObjects(const zelda3::Room& room) {
// Render wall objects with proper dimensions based on properties
for (const auto& object : room.GetTileObjects()) {
if (object.id_ >= 0x10 && object.id_ <= 0x1F) { // Wall objects range
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
// Different wall types based on ID
ImVec4 wall_color;
std::string wall_type;
switch (object.id_) {
case 0x10: // Basic wall
wall_color = ImVec4(0.6f, 0.6f, 0.6f, 0.8f);
wall_type = "WALL";
break;
case 0x11: // Corner wall
wall_color = ImVec4(0.7f, 0.7f, 0.6f, 0.8f);
wall_type = "CORNER";
break;
case 0x12: // Decorative wall
wall_color = ImVec4(0.8f, 0.7f, 0.6f, 0.8f);
wall_type = "DEC_WALL";
break;
default:
wall_color = ImVec4(0.5f, 0.5f, 0.5f, 0.8f);
wall_type = "WALL";
break;
}
// Calculate wall size with proper length handling
int wall_width, wall_height;
// For walls, use the size field to determine length and orientation
if (object.id_ >= 0x10 && object.id_ <= 0x1F) {
uint8_t size_x = object.size_ & 0x0F;
uint8_t size_y = (object.size_ >> 4) & 0x0F;
if (size_x > size_y) {
// Horizontal wall
wall_width = 8 + size_x * 8;
wall_height = 8;
} else if (size_y > size_x) {
// Vertical wall
wall_width = 8;
wall_height = 8 + size_y * 8;
} else {
// Square wall or corner
wall_width = 8 + size_x * 4;
wall_height = 8 + size_y * 4;
}
} else {
// For other objects, use standard size calculation
wall_width = 8 + (object.size_ & 0x0F) * 4;
wall_height = 8 + ((object.size_ >> 4) & 0x0F) * 4;
}
wall_width = std::min(wall_width, 256);
wall_height = std::min(wall_height, 256);
canvas_.DrawRect(canvas_x, canvas_y, wall_width, wall_height, wall_color);
canvas_.DrawRect(canvas_x, canvas_y, wall_width, wall_height, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Add stone block pattern
for (int i = 0; i < wall_width; i += 8) {
for (int j = 0; j < wall_height; j += 8) {
canvas_.DrawRect(canvas_x + i, canvas_y + j, 6, 6,
ImVec4(wall_color.x * 0.9f, wall_color.y * 0.9f, wall_color.z * 0.9f, wall_color.w));
}
}
// Draw text label
std::string wall_text = absl::StrFormat("%s\n0x%X\n%dx%d", wall_type.c_str(), object.id_, wall_width/16, wall_height/16);
canvas_.DrawText(wall_text, canvas_x + wall_width + 2, canvas_y + 4);
}
}
}
}
void DungeonCanvasViewer::RenderPotObjects(const zelda3::Room& room) {
// Render pot objects based on assembly constants - Object_Pot is 0x2F
for (const auto& object : room.GetTileObjects()) {
if (object.id_ == 0x2F || object.id_ == 0x2B) { // Pot objects from assembly
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
if (IsWithinCanvasBounds(canvas_x, canvas_y, 8)) {
// Draw pot base (wider at bottom)
canvas_.DrawRect(canvas_x + 1, canvas_y + 5, 6, 3, ImVec4(0.7f, 0.5f, 0.3f, 0.8f)); // Brown base
// Draw pot middle
canvas_.DrawRect(canvas_x + 2, canvas_y + 3, 4, 3, ImVec4(0.8f, 0.6f, 0.4f, 0.8f)); // Lighter middle
// Draw pot rim
canvas_.DrawRect(canvas_x + 2, canvas_y + 2, 4, 2, ImVec4(0.9f, 0.7f, 0.5f, 0.8f)); // Lightest top
// Draw pot outline
canvas_.DrawRect(canvas_x + 1, canvas_y + 2, 6, 6, ImVec4(0.0f, 0.0f, 0.0f, 1.0f));
// Draw text label
std::string pot_text = absl::StrFormat("POT\n0x%X", object.id_);
canvas_.DrawText(pot_text, canvas_x + 9, canvas_y + 3);
}
}
}
}
// Coordinate conversion helper functions
std::pair<int, int> DungeonCanvasViewer::RoomToCanvasCoordinates(int room_x,
int room_y) const {
@@ -744,9 +449,15 @@ void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) {
// Queue texture creation for background layer 1 via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg1_bitmap);
// CRITICAL FIX: Process texture queue immediately to ensure texture is created before drawing
gfx::Arena::Get().ProcessTextureQueue(nullptr);
}
canvas_.DrawBitmap(bg1_bitmap, 0, 0, 1.0f, 255);
// Only draw if texture was successfully created
if (bg1_bitmap.texture()) {
canvas_.DrawBitmap(bg1_bitmap, 0, 0, 1.0f, 255);
}
}
if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0 && bg2_bitmap.height() > 0) {
@@ -754,8 +465,25 @@ void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) {
// Queue texture creation for background layer 2 via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &bg2_bitmap);
// CRITICAL FIX: Process texture queue immediately to ensure texture is created before drawing
gfx::Arena::Get().ProcessTextureQueue(nullptr);
}
canvas_.DrawBitmap(bg2_bitmap, 0, 0, 1.0f, 200);
// Only draw if texture was successfully created
if (bg2_bitmap.texture()) {
canvas_.DrawBitmap(bg2_bitmap, 0, 0, 1.0f, 200);
}
}
// DEBUG: Check if background buffers have content
if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0) {
printf("[RenderRoomBackgroundLayers] BG1 bitmap: %dx%d, active=%d\n",
bg1_bitmap.width(), bg1_bitmap.height(), bg1_bitmap.is_active());
}
if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0) {
printf("[RenderRoomBackgroundLayers] BG2 bitmap: %dx%d, active=%d\n",
bg2_bitmap.width(), bg2_bitmap.height(), bg2_bitmap.is_active());
}
// TEST: Draw a bright red rectangle to verify canvas drawing works

View File

@@ -65,17 +65,9 @@ class DungeonCanvasViewer {
}
private:
void RenderObjectInCanvas(const zelda3::RoomObject &object,
const gfx::SnesPalette &palette);
void DisplayObjectInfo(const zelda3::RoomObject &object, int canvas_x,
int canvas_y);
void RenderStairObjects(const zelda3::Room& room,
const gfx::SnesPalette& palette);
void RenderSprites(const zelda3::Room& room);
void RenderChests(const zelda3::Room& room);
void RenderDoorObjects(const zelda3::Room& room);
void RenderWallObjects(const zelda3::Room& room);
void RenderPotObjects(const zelda3::Room& room);
// Coordinate conversion helpers
std::pair<int, int> RoomToCanvasCoordinates(int room_x, int room_y) const;

View File

@@ -151,12 +151,6 @@ absl::Status DungeonEditor::Load() {
// Set up component callbacks
object_interaction_.SetCurrentRoom(&rooms_, current_room_id_);
object_interaction_.SetObjectPlacedCallback([this](const zelda3::RoomObject& object) {
renderer_.ClearObjectCache();
});
object_interaction_.SetCacheInvalidationCallback([this]() {
renderer_.ClearObjectCache();
});
// Set up toolset callbacks
toolset_.SetUndoCallback([this]() { PRINT_IF_ERROR(Undo()); });
@@ -492,10 +486,6 @@ void DungeonEditor::DrawRoomPropertiesDebugPopup() {
if (ImGui::Button("Reload Objects")) {
room.LoadObjects();
}
ImGui::SameLine();
if (ImGui::Button("Clear Cache")) {
renderer_.ClearObjectCache();
}
ImGui::SameLine();
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
@@ -609,14 +599,6 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
static bool show_layout_objects = false;
ImGui::Checkbox("Show Layout Objects", &show_layout_objects);
if (ImGui::Button("Clear Object Cache")) {
renderer_.ClearObjectCache();
}
ImGui::SameLine();
ImGui::Text("Cache: %zu objects", renderer_.GetCacheSize());
// Object statistics and metadata
ImGui::Separator();
ImGui::Text("Room Statistics:");
@@ -630,7 +612,6 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
// Palette information
ImGui::Text("Current Palette Group: %llu",
static_cast<unsigned long long>(current_palette_group_id_));
ImGui::Text("Cache Size: %zu objects", renderer_.GetCacheSize());
// Object type breakdown
ImGui::Separator();
@@ -755,9 +736,6 @@ void DungeonEditor::DrawDungeonCanvas(int room_id) {
canvas_.DrawBitmap(bg2_bitmap, 0, 0, 1.0f, 200);
}
// Render sprites as simple 16x16 squares with labels
// (Sprites are not part of the background buffers)
renderer_.RenderSprites(rooms_[room_id]);
}
// Phase 5: Render with integrated object editor

View File

@@ -32,7 +32,6 @@
#include "dungeon_object_selector.h"
#include "dungeon_toolset.h"
#include "dungeon_object_interaction.h"
#include "dungeon_renderer.h"
#include "dungeon_room_loader.h"
#include "dungeon_usage_tracker.h"
@@ -65,7 +64,7 @@ class DungeonEditor : public Editor {
explicit DungeonEditor(Rom* rom = nullptr)
: rom_(rom), object_renderer_(rom), preview_object_(0, 0, 0, 0, 0),
room_selector_(rom), canvas_viewer_(rom), object_selector_(rom),
object_interaction_(&canvas_), renderer_(&canvas_, rom), room_loader_(rom) {
object_interaction_(&canvas_), room_loader_(rom) {
type_ = EditorType::kDungeon;
// Initialize the new dungeon editor system
if (rom) {
@@ -197,7 +196,6 @@ class DungeonEditor : public Editor {
// Refactored components
DungeonToolset toolset_;
DungeonObjectInteraction object_interaction_;
DungeonRenderer renderer_;
DungeonRoomLoader room_loader_;
DungeonUsageTracker usage_tracker_;

View File

@@ -142,11 +142,6 @@ absl::Status DungeonEditorV2::Load() {
// Initialize unified object editor card
object_editor_card_ = std::make_unique<ObjectEditorCard>(renderer_, rom_, &canvas_viewer_);
// Initialize manual renderer for debugging (uses canvas from canvas_viewer_)
manual_renderer_ = std::make_unique<ManualObjectRenderer>(
&canvas_viewer_.canvas(), rom_);
printf("[DungeonEditorV2] Manual renderer initialized for debugging\n");
// Wire palette changes to trigger room re-renders
palette_editor_.SetOnPaletteChanged([this](int /*palette_id*/) {
// Re-render all active rooms when palette changes
@@ -422,9 +417,11 @@ void DungeonEditorV2::DrawRoomTab(int room_id) {
return;
}
auto& room = rooms_[room_id];
// Lazy load room data
if (!rooms_[room_id].IsLoaded()) {
auto status = room_loader_.LoadRoom(room_id, rooms_[room_id]);
if (!room.IsLoaded()) {
auto status = room_loader_.LoadRoom(room_id, room);
if (!status.ok()) {
ImGui::TextColored(ImVec4(1, 0, 0, 1), "Failed to load room: %s",
status.message().data());
@@ -432,10 +429,31 @@ void DungeonEditorV2::DrawRoomTab(int room_id) {
}
}
// Initialize room graphics and objects if not already done
// This ensures objects are drawn to background buffers before canvas displays them
if (room.IsLoaded()) {
// Load room graphics (populates blocks, gfx sheets)
if (room.blocks().empty()) {
room.RenderRoomGraphics();
}
// Load room objects (populates tile_objects_)
if (room.GetTileObjects().empty()) {
room.LoadObjects();
}
// Render objects to background buffers (CRITICAL: this must happen before canvas drawing)
// This uses ObjectDrawer to draw all objects into bg1_buffer_ and bg2_buffer_
auto& bg1_bitmap = room.bg1_buffer().bitmap();
if (!bg1_bitmap.is_active() || bg1_bitmap.width() == 0) {
room.RenderObjectsToBackground();
}
}
// Room info header
ImGui::Text("Room %03X", room_id);
ImGui::SameLine();
if (rooms_[room_id].IsLoaded()) {
if (room.IsLoaded()) {
ImGui::TextColored(ImVec4(0.4f, 0.8f, 0.4f, 1.0f), ICON_MD_CHECK " Loaded");
} else {
ImGui::TextColored(ImVec4(0.8f, 0.4f, 0.4f, 1.0f), ICON_MD_PENDING " Not Loaded");
@@ -840,6 +858,11 @@ void DungeonEditorV2::DrawRoomGraphicsCard() {
blocks = room.blocks();
}
// Only render room graphics if ROM is properly loaded
if (room.rom() && room.rom()->is_loaded()) {
room.RenderRoomGraphics();
}
int current_block = 0;
constexpr int max_blocks_per_row = 2;
constexpr int block_width = 128;
@@ -852,6 +875,13 @@ void DungeonEditorV2::DrawRoomGraphicsCard() {
if (block < static_cast<int>(gfx::Arena::Get().gfx_sheets().size())) {
auto& gfx_sheet = gfx::Arena::Get().gfx_sheets()[block];
// Create texture if it doesn't exist
if (!gfx_sheet.texture() && gfx_sheet.is_active() && gfx_sheet.width() > 0) {
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &gfx_sheet);
gfx::Arena::Get().ProcessTextureQueue(nullptr);
}
// Calculate grid position
int row = current_block / max_blocks_per_row;
int col = current_block % max_blocks_per_row;
@@ -865,6 +895,16 @@ void DungeonEditorV2::DrawRoomGraphicsCard() {
(ImTextureID)(intptr_t)gfx_sheet.texture(),
ImVec2(x, y),
ImVec2(x + block_width, y + block_height));
} else {
// Draw placeholder for missing graphics
room_gfx_canvas.draw_list()->AddRectFilled(
ImVec2(x, y),
ImVec2(x + block_width, y + block_height),
IM_COL32(64, 64, 64, 255));
room_gfx_canvas.draw_list()->AddText(
ImVec2(x + 10, y + 10),
IM_COL32(255, 255, 255, 255),
"No Graphics");
}
}
current_block++;

View File

@@ -14,7 +14,6 @@
#include "dungeon_object_selector.h"
#include "dungeon_room_loader.h"
#include "object_editor_card.h"
#include "manual_object_renderer.h"
#include "app/gui/editor_card_manager.h"
#include "app/zelda3/dungeon/room.h"
#include "app/zelda3/dungeon/room_entrance.h"
@@ -146,7 +145,6 @@ class DungeonEditorV2 : public Editor {
gui::DungeonObjectEmulatorPreview object_emulator_preview_;
gui::PaletteEditorWidget palette_editor_;
std::unique_ptr<ObjectEditorCard> object_editor_card_; // Unified object editor
std::unique_ptr<ManualObjectRenderer> manual_renderer_; // Debugging renderer
bool is_loaded_ = false;

View File

@@ -1,208 +0,0 @@
#include "dungeon_renderer.h"
#include "absl/strings/str_format.h"
#include "app/gfx/arena.h"
namespace yaze::editor {
void DungeonRenderer::RenderObjectInCanvas(const zelda3::RoomObject& object,
const gfx::SnesPalette& palette) {
// Validate ROM is loaded
if (!rom_ || !rom_->is_loaded()) {
return;
}
// Convert room coordinates to canvas coordinates
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(object.x_, object.y_);
// Check if object is within canvas bounds
if (!IsWithinCanvasBounds(canvas_x, canvas_y, 32)) {
return; // Skip objects outside visible area
}
// Calculate palette hash for caching
uint64_t palette_hash = 0;
for (size_t i = 0; i < palette.size() && i < 16; ++i) {
palette_hash ^= std::hash<uint16_t>{}(palette[i].snes()) + 0x9e3779b9 +
(palette_hash << 6) + (palette_hash >> 2);
}
// Check cache first
for (auto& cached : object_render_cache_) {
if (cached.object_id == object.id_ && cached.object_x == object.x_ &&
cached.object_y == object.y_ && cached.object_size == object.size_ &&
cached.palette_hash == palette_hash && cached.is_valid) {
canvas_->DrawBitmap(cached.rendered_bitmap, canvas_x, canvas_y, 1.0f, 255);
return;
}
}
// Create a mutable copy of the object to ensure tiles are loaded
auto mutable_object = object;
mutable_object.set_rom(rom_);
mutable_object.EnsureTilesLoaded();
// Try to render the object with proper graphics
auto render_result = object_renderer_.RenderObject(mutable_object, palette);
if (render_result.ok()) {
auto object_bitmap = std::move(render_result.value());
// Ensure the bitmap is valid and has meaningful content
if (object_bitmap.width() > 0 && object_bitmap.height() > 0 &&
object_bitmap.data() != nullptr) {
object_bitmap.SetPalette(palette);
// Queue texture creation for the object bitmap via Arena's deferred system
gfx::Arena::Get().QueueTextureCommand(
gfx::Arena::TextureCommandType::CREATE, &object_bitmap);
canvas_->DrawBitmap(object_bitmap, canvas_x, canvas_y, 1.0f, 255);
// Cache the successfully rendered bitmap
ObjectRenderCache cache_entry;
cache_entry.object_id = object.id_;
cache_entry.object_x = object.x_;
cache_entry.object_y = object.y_;
cache_entry.object_size = object.size_;
cache_entry.palette_hash = palette_hash;
cache_entry.rendered_bitmap = object_bitmap;
cache_entry.is_valid = true;
// Add to cache (limit cache size)
if (object_render_cache_.size() >= 100) {
object_render_cache_.erase(object_render_cache_.begin());
}
object_render_cache_.push_back(std::move(cache_entry));
return;
}
}
// Fallback: Draw object as colored rectangle with ID if rendering fails
ImVec4 object_color;
// Color-code objects based on layer for better identification
switch (object.layer_) {
case zelda3::RoomObject::LayerType::BG1:
object_color = ImVec4(0.8f, 0.4f, 0.4f, 0.8f); // Red-ish for BG1
break;
case zelda3::RoomObject::LayerType::BG2:
object_color = ImVec4(0.4f, 0.8f, 0.4f, 0.8f); // Green-ish for BG2
break;
case zelda3::RoomObject::LayerType::BG3:
object_color = ImVec4(0.4f, 0.4f, 0.8f, 0.8f); // Blue-ish for BG3
break;
default:
object_color = ImVec4(0.6f, 0.6f, 0.6f, 0.8f); // Gray for unknown
break;
}
// Calculate object size (16x16 is base, size affects width/height)
int object_width = 16 + (object.size_ & 0x0F) * 8;
int object_height = 16 + ((object.size_ >> 4) & 0x0F) * 8;
canvas_->DrawRect(canvas_x, canvas_y, object_width, object_height, object_color);
canvas_->DrawRect(canvas_x, canvas_y, object_width, object_height,
ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); // Black border
}
void DungeonRenderer::DisplayObjectInfo(const zelda3::RoomObject& object,
int canvas_x, int canvas_y) {
std::string info_text = absl::StrFormat("ID:%d X:%d Y:%d S:%d", object.id_,
object.x_, object.y_, object.size_);
canvas_->DrawText(info_text, canvas_x, canvas_y - 12);
}
void DungeonRenderer::RenderSprites(const zelda3::Room& room) {
// Render sprites as simple 16x16 squares with sprite name/ID
for (const auto& sprite : room.GetSprites()) {
auto [canvas_x, canvas_y] = RoomToCanvasCoordinates(sprite.x(), sprite.y());
if (IsWithinCanvasBounds(canvas_x, canvas_y, 16)) {
// Draw 16x16 square for sprite
ImVec4 sprite_color;
// Color-code sprites based on layer for identification
if (sprite.layer() == 0) {
sprite_color = ImVec4(0.2f, 0.8f, 0.2f, 0.8f); // Green for layer 0
} else {
sprite_color = ImVec4(0.2f, 0.2f, 0.8f, 0.8f); // Blue for layer 1
}
canvas_->DrawRect(canvas_x, canvas_y, 16, 16, sprite_color);
canvas_->DrawRect(canvas_x, canvas_y, 16, 16, ImVec4(0.0f, 0.0f, 0.0f, 1.0f)); // Border
}
}
}
void DungeonRenderer::RenderRoomBackgroundLayers(int room_id) {
// Get canvas dimensions to limit rendering
int canvas_width = canvas_->width();
int canvas_height = canvas_->height();
// Validate canvas dimensions
if (canvas_width <= 0 || canvas_height <= 0) {
return;
}
// BG1 (background layer 1) - main room graphics
auto& bg1_bitmap = gfx::Arena::Get().bg1().bitmap();
if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0 &&
bg1_bitmap.height() > 0) {
float scale_x = static_cast<float>(canvas_width) / bg1_bitmap.width();
float scale_y = static_cast<float>(canvas_height) / bg1_bitmap.height();
float scale = std::min(scale_x, scale_y);
int scaled_width = static_cast<int>(bg1_bitmap.width() * scale);
int scaled_height = static_cast<int>(bg1_bitmap.height() * scale);
int offset_x = (canvas_width - scaled_width) / 2;
int offset_y = (canvas_height - scaled_height) / 2;
canvas_->DrawBitmap(bg1_bitmap, offset_x, offset_y, scale, 255);
}
// BG2 (background layer 2) - sprite graphics (overlay)
auto& bg2_bitmap = gfx::Arena::Get().bg2().bitmap();
if (bg2_bitmap.is_active() && bg2_bitmap.width() > 0 &&
bg2_bitmap.height() > 0) {
float scale_x = static_cast<float>(canvas_width) / bg2_bitmap.width();
float scale_y = static_cast<float>(canvas_height) / bg2_bitmap.height();
float scale = std::min(scale_x, scale_y);
int scaled_width = static_cast<int>(bg2_bitmap.width() * scale);
int scaled_height = static_cast<int>(bg2_bitmap.height() * scale);
int offset_x = (canvas_width - scaled_width) / 2;
int offset_y = (canvas_height - scaled_height) / 2;
canvas_->DrawBitmap(bg2_bitmap, offset_x, offset_y, scale, 200);
}
}
absl::Status DungeonRenderer::RefreshGraphics(int room_id, uint64_t palette_id,
const gfx::PaletteGroup& palette_group) {
if (!rom_ || !rom_->is_loaded()) {
return absl::FailedPreconditionError("ROM not loaded");
}
// This would need access to room data - will be called from main editor
return absl::OkStatus();
}
std::pair<int, int> DungeonRenderer::RoomToCanvasCoordinates(int room_x, int room_y) const {
// Dungeon tiles are 8x8 pixels, convert room coordinates (tiles) to pixels
return {room_x * 8, room_y * 8};
}
std::pair<int, int> DungeonRenderer::CanvasToRoomCoordinates(int canvas_x, int canvas_y) const {
// Convert canvas pixels back to room coordinates (tiles)
return {canvas_x / 8, canvas_y / 8};
}
bool DungeonRenderer::IsWithinCanvasBounds(int canvas_x, int canvas_y, int margin) const {
auto canvas_size = canvas_->canvas_size();
auto global_scale = canvas_->global_scale();
int scaled_width = static_cast<int>(canvas_size.x * global_scale);
int scaled_height = static_cast<int>(canvas_size.y * global_scale);
return (canvas_x >= -margin && canvas_y >= -margin &&
canvas_x <= scaled_width + margin &&
canvas_y <= scaled_height + margin);
}
} // namespace yaze::editor

View File

@@ -1,75 +0,0 @@
#ifndef YAZE_APP_EDITOR_DUNGEON_DUNGEON_RENDERER_H
#define YAZE_APP_EDITOR_DUNGEON_DUNGEON_RENDERER_H
#include <vector>
#include "absl/status/status.h"
#include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/object_renderer.h"
#include "app/zelda3/dungeon/room.h"
#include "app/zelda3/dungeon/room_layout.h"
#include "app/zelda3/dungeon/room_object.h"
namespace yaze {
namespace editor {
/**
* @brief Handles rendering of dungeon objects, layouts, and backgrounds
*
* This component manages all rendering operations for the dungeon editor,
* including object caching, background layers, and layout visualization.
*/
class DungeonRenderer {
public:
explicit DungeonRenderer(gui::Canvas* canvas, Rom* rom)
: canvas_(canvas), rom_(rom), object_renderer_(rom) {}
// Object rendering
void RenderObjectInCanvas(const zelda3::RoomObject& object,
const gfx::SnesPalette& palette);
void DisplayObjectInfo(const zelda3::RoomObject& object, int canvas_x, int canvas_y);
void RenderSprites(const zelda3::Room& room);
// Background rendering
void RenderRoomBackgroundLayers(int room_id);
absl::Status RefreshGraphics(int room_id, uint64_t palette_id,
const gfx::PaletteGroup& palette_group);
// Graphics management
absl::Status LoadAndRenderRoomGraphics(int room_id,
std::array<zelda3::Room, 0x128>& rooms);
absl::Status ReloadAllRoomGraphics(std::array<zelda3::Room, 0x128>& rooms);
// Cache management
void ClearObjectCache() { object_render_cache_.clear(); }
size_t GetCacheSize() const { return object_render_cache_.size(); }
// 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;
private:
gui::Canvas* canvas_;
Rom* rom_;
zelda3::ObjectRenderer object_renderer_;
// Object rendering cache
struct ObjectRenderCache {
int object_id;
int object_x, object_y, object_size;
uint64_t palette_hash;
gfx::Bitmap rendered_bitmap;
bool is_valid;
};
std::vector<ObjectRenderCache> object_render_cache_;
uint64_t last_palette_hash_ = 0;
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_DUNGEON_DUNGEON_RENDERER_H

View File

@@ -1,170 +0,0 @@
#include "manual_object_renderer.h"
#include <cstdio>
#include <cstring>
#include "app/gfx/arena.h"
#include "app/gfx/bitmap.h"
namespace yaze {
namespace editor {
ManualObjectRenderer::ManualObjectRenderer(gui::Canvas* canvas, Rom* rom)
: canvas_(canvas), rom_(rom) {}
absl::Status ManualObjectRenderer::RenderSimpleBlock(uint16_t object_id, int x, int y,
const gfx::SnesPalette& palette) {
if (!canvas_ || !rom_) {
return absl::InvalidArgumentError("Canvas or ROM not initialized");
}
printf("[ManualRenderer] Rendering object 0x%04X at (%d, %d)\n", object_id, x, y);
// Create a simple 16x16 tile manually
auto tile_bitmap = CreateSimpleTile(object_id, palette);
if (!tile_bitmap) {
return absl::InternalError("Failed to create simple tile");
}
// Draw directly to canvas
canvas_->DrawBitmap(*tile_bitmap, x, y, 1.0f, 255);
return absl::OkStatus();
}
void ManualObjectRenderer::RenderTestPattern(int x, int y, int width, int height,
uint8_t color_index) {
if (!canvas_) return;
printf("[ManualRenderer] Drawing test pattern: %dx%d at (%d,%d) color=%d\n",
width, height, x, y, color_index);
// Create a simple colored rectangle using ImGui
ImVec4 color = ImVec4(
(color_index & 0x01) ? 1.0f : 0.0f, // Red bit
(color_index & 0x02) ? 1.0f : 0.0f, // Green bit
(color_index & 0x04) ? 1.0f : 0.0f, // Blue bit
1.0f // Alpha
);
// Draw using ImGui primitives for testing
ImDrawList* draw_list = ImGui::GetWindowDrawList();
if (draw_list) {
ImVec2 p1 = ImVec2(x, y);
ImVec2 p2 = ImVec2(x + width, y + height);
draw_list->AddRectFilled(p1, p2, ImGui::ColorConvertFloat4ToU32(color));
}
}
void ManualObjectRenderer::DebugGraphicsSheet(int sheet_index) {
if (!rom_ || sheet_index < 0 || sheet_index >= 223) {
printf("[ManualRenderer] Invalid sheet index: %d\n", sheet_index);
return;
}
auto& arena = gfx::Arena::Get();
const auto& sheet = arena.gfx_sheet(sheet_index);
printf("[ManualRenderer] Graphics Sheet %d Debug Info:\n", sheet_index);
printf(" - Is Active: %s\n", sheet.is_active() ? "YES" : "NO");
printf(" - Width: %d\n", sheet.width());
printf(" - Height: %d\n", sheet.height());
printf(" - Has Surface: %s\n", sheet.surface() ? "YES" : "NO");
printf(" - Has Texture: %s\n", sheet.texture() ? "YES" : "NO");
if (sheet.is_active() && sheet.width() > 0 && sheet.height() > 0) {
printf(" - Format: %s\n", sheet.surface() && sheet.surface()->format ?
SDL_GetPixelFormatName(sheet.surface()->format->format) : "Unknown");
}
}
void ManualObjectRenderer::TestPaletteRendering(int x, int y) {
printf("[ManualRenderer] Testing palette rendering at (%d, %d)\n", x, y);
// Draw test squares with different color indices
for (int i = 0; i < 8; i++) {
int test_x = x + (i * 20);
int test_y = y;
RenderTestPattern(test_x, test_y, 16, 16, i);
}
}
std::unique_ptr<gfx::Bitmap> ManualObjectRenderer::CreateSimpleTile(uint16_t tile_id,
const gfx::SnesPalette& palette) {
// Fill with a simple pattern based on tile_id
uint8_t base_color = tile_id & 0x07; // Use lower 3 bits for color
// Create tile data manually
auto tile_data = CreateSolidTile(base_color);
if (tile_data.empty()) {
printf("[ManualRenderer] Failed to create tile data\n");
return nullptr;
}
// Create a 16x16 bitmap with the tile data
auto bitmap = std::make_unique<gfx::Bitmap>();
bitmap->Create(16, 16, 8, tile_data); // 16x16 pixels, 8-bit depth
if (!bitmap->is_active()) {
printf("[ManualRenderer] Failed to create bitmap\n");
return nullptr;
}
// Apply palette
bitmap->SetPalette(palette);
printf("[ManualRenderer] Created simple tile: ID=0x%04X, color=%d\n", tile_id, base_color);
return bitmap;
}
std::vector<uint8_t> ManualObjectRenderer::CreateSolidTile(uint8_t color_index) {
std::vector<uint8_t> tile_data(16 * 16, color_index);
return tile_data;
}
std::vector<uint8_t> ManualObjectRenderer::CreatePatternTile(uint8_t pattern_type,
uint8_t color_index) {
std::vector<uint8_t> tile_data(16 * 16);
switch (pattern_type) {
case 0: // Solid
std::fill(tile_data.begin(), tile_data.end(), color_index);
break;
case 1: // Checkerboard
for (int y = 0; y < 16; y++) {
for (int x = 0; x < 16; x++) {
tile_data[y * 16 + x] = ((x + y) % 2) ? color_index : 0;
}
}
break;
case 2: // Horizontal stripes
for (int y = 0; y < 16; y++) {
uint8_t color = (y % 4 < 2) ? color_index : 0;
for (int x = 0; x < 16; x++) {
tile_data[y * 16 + x] = color;
}
}
break;
case 3: // Vertical stripes
for (int x = 0; x < 16; x++) {
uint8_t color = (x % 4 < 2) ? color_index : 0;
for (int y = 0; y < 16; y++) {
tile_data[y * 16 + x] = color;
}
}
break;
default:
std::fill(tile_data.begin(), tile_data.end(), color_index);
break;
}
return tile_data;
}
} // namespace editor
} // namespace yaze

View File

@@ -1,90 +0,0 @@
#ifndef YAZE_APP_EDITOR_DUNGEON_MANUAL_OBJECT_RENDERER_H
#define YAZE_APP_EDITOR_DUNGEON_MANUAL_OBJECT_RENDERER_H
#include <vector>
#include <memory>
#include "app/gfx/snes_palette.h"
#include "app/gui/canvas.h"
#include "app/rom.h"
#include "app/zelda3/dungeon/room_object.h"
namespace yaze {
namespace editor {
/**
* @brief Manual object renderer for debugging and testing basic object rendering
*
* This class provides simple, manual rendering of basic dungeon objects
* to help debug the graphics pipeline and understand object data structures.
*
* Features:
* - Manual tile creation for simple objects
* - Direct graphics sheet access
* - Palette debugging and testing
* - Simple pattern rendering (solid blocks, lines, etc.)
*/
class ManualObjectRenderer {
public:
explicit ManualObjectRenderer(gui::Canvas* canvas, Rom* rom);
/**
* @brief Render a simple solid block object manually
* @param object_id Object ID to render
* @param x X position in pixels
* @param y Y position in pixels
* @param palette Current palette to use
* @return Status of the rendering operation
*/
absl::Status RenderSimpleBlock(uint16_t object_id, int x, int y,
const gfx::SnesPalette& palette);
/**
* @brief Render a test pattern to verify graphics pipeline
* @param x X position in pixels
* @param y Y position in pixels
* @param width Width in pixels
* @param height Height in pixels
* @param color_index Color index to use
*/
void RenderTestPattern(int x, int y, int width, int height, uint8_t color_index);
/**
* @brief Debug graphics sheet loading and display info
* @param sheet_index Graphics sheet index to examine
*/
void DebugGraphicsSheet(int sheet_index);
/**
* @brief Test palette rendering with different colors
* @param x X position in pixels
* @param y Y position in pixels
*/
void TestPaletteRendering(int x, int y);
/**
* @brief Create a simple 16x16 tile manually
* @param tile_id Tile ID to create
* @param palette Palette to apply
* @return Created bitmap
*/
std::unique_ptr<gfx::Bitmap> CreateSimpleTile(uint16_t tile_id,
const gfx::SnesPalette& palette);
private:
gui::Canvas* canvas_;
Rom* rom_;
// Simple tile creation helpers
std::vector<uint8_t> CreateSolidTile(uint8_t color_index);
std::vector<uint8_t> CreatePatternTile(uint8_t pattern_type, uint8_t color_index);
// Graphics debugging
void LogGraphicsInfo(int sheet_index);
void LogPaletteInfo(const gfx::SnesPalette& palette);
};
} // namespace editor
} // namespace yaze
#endif // YAZE_APP_EDITOR_DUNGEON_MANUAL_OBJECT_RENDERER_H