#include "dungeon_renderer.h" #include "absl/strings/str_format.h" #include "app/core/window.h" #include "app/gfx/arena.h" #include "app/gui/color.h" namespace yaze::editor { using core::Renderer; 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{}(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); core::Renderer::Get().RenderBitmap(&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(canvas_width) / bg1_bitmap.width(); float scale_y = static_cast(canvas_height) / bg1_bitmap.height(); float scale = std::min(scale_x, scale_y); int scaled_width = static_cast(bg1_bitmap.width() * scale); int scaled_height = static_cast(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(canvas_width) / bg2_bitmap.width(); float scale_y = static_cast(canvas_height) / bg2_bitmap.height(); float scale = std::min(scale_x, scale_y); int scaled_width = static_cast(bg2_bitmap.width() * scale); int scaled_height = static_cast(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 DungeonRenderer::RoomToCanvasCoordinates(int room_x, int room_y) const { return {room_x * 16, room_y * 16}; } std::pair DungeonRenderer::CanvasToRoomCoordinates(int canvas_x, int canvas_y) const { return {canvas_x / 16, canvas_y / 16}; } 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(canvas_size.x * global_scale); int scaled_height = static_cast(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