feat: Introduce Dungeon Object Emulator Preview and Object Drawing Enhancements
- Added DungeonObjectEmulatorPreview for rendering dungeon objects using the SNES emulator, allowing real-time visualization of object graphics. - Implemented ObjectDrawer class to handle drawing of various object types to background buffers, utilizing game-specific patterns. - Updated DungeonCanvasViewer to integrate object rendering and improve background layer management. - Enhanced DungeonEditorV2 to support the new emulator preview, providing a more interactive editing experience. - Improved error handling and logging for better debugging during object rendering operations.
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/input.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/dungeon/object_drawer.h"
|
||||
#include "app/zelda3/dungeon/object_renderer.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "app/zelda3/sprite/sprite.h"
|
||||
@@ -111,10 +112,13 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
|
||||
room.LoadObjects();
|
||||
}
|
||||
|
||||
// Render background layers with proper positioning
|
||||
// NOTE: Don't draw objects here - RenderRoomBackgroundLayers() already does it
|
||||
// via room.RenderRoomGraphics() which calls RenderObjectsToBackground()
|
||||
|
||||
// Render background layers from arena buffers
|
||||
RenderRoomBackgroundLayers(room_id);
|
||||
|
||||
// Render room objects with proper graphics
|
||||
// Render room objects with proper graphics (old system as fallback)
|
||||
if (current_palette_id_ < current_palette_group_.size()) {
|
||||
auto room_palette = current_palette_group_[current_palette_id_];
|
||||
|
||||
@@ -652,31 +656,45 @@ absl::Status DungeonCanvasViewer::UpdateRoomBackgroundLayers(int room_id) {
|
||||
|
||||
void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) {
|
||||
if (room_id < 0 || room_id >= 128) {
|
||||
printf("[Canvas] Invalid room_id: %d\n", room_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
printf("[Canvas] ROM not loaded\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!rooms_) {
|
||||
printf("[Canvas] Rooms pointer is null\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get canvas dimensions to limit rendering
|
||||
// Get canvas dimensions
|
||||
int canvas_width = canvas_.width();
|
||||
int canvas_height = canvas_.height();
|
||||
|
||||
// Validate canvas dimensions
|
||||
printf("[Canvas] Canvas size: %dx%d\n", canvas_width, canvas_height);
|
||||
|
||||
if (canvas_width <= 0 || canvas_height <= 0) {
|
||||
printf("[Canvas] Invalid canvas dimensions\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Render the room's background layers using the graphics arena
|
||||
// BG1 (background layer 1) - main room graphics
|
||||
// Render BG1 (background layer 1) - main room graphics
|
||||
auto& bg1_bitmap = gfx::Arena::Get().bg1().bitmap();
|
||||
printf("[Canvas] BG1: active=%d, size=%dx%d, texture=%p\n",
|
||||
bg1_bitmap.is_active(), bg1_bitmap.width(), bg1_bitmap.height(),
|
||||
(void*)bg1_bitmap.texture());
|
||||
|
||||
if (bg1_bitmap.is_active() && bg1_bitmap.width() > 0 && bg1_bitmap.height() > 0) {
|
||||
// Scale the background to fit the canvas
|
||||
// Ensure texture exists
|
||||
if (!bg1_bitmap.texture()) {
|
||||
printf("[Canvas] WARNING: BG1 has no texture, creating...\n");
|
||||
core::Renderer::Get().RenderBitmap(&bg1_bitmap);
|
||||
}
|
||||
|
||||
// Scale to fit canvas
|
||||
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);
|
||||
@@ -686,13 +704,21 @@ void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) {
|
||||
int offset_x = (canvas_width - scaled_width) / 2;
|
||||
int offset_y = (canvas_height - scaled_height) / 2;
|
||||
|
||||
printf("[Canvas] Drawing BG1 at offset=(%d,%d), scaled_size=%dx%d, scale=%.2f\n",
|
||||
offset_x, offset_y, scaled_width, scaled_height, scale);
|
||||
|
||||
canvas_.DrawBitmap(bg1_bitmap, offset_x, offset_y, scale, 255);
|
||||
} else {
|
||||
printf("[Canvas] BG1 not ready for rendering\n");
|
||||
}
|
||||
|
||||
// BG2 (background layer 2) - sprite graphics (overlay)
|
||||
// Render 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) {
|
||||
// Scale the background to fit the canvas
|
||||
if (!bg2_bitmap.texture()) {
|
||||
core::Renderer::Get().RenderBitmap(&bg2_bitmap);
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -702,8 +728,13 @@ void DungeonCanvasViewer::RenderRoomBackgroundLayers(int room_id) {
|
||||
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); // Semi-transparent overlay
|
||||
printf("[Canvas] Drawing BG2 at offset=(%d,%d), scaled_size=%dx%d, scale=%.2f\n",
|
||||
offset_x, offset_y, scaled_width, scaled_height, scale);
|
||||
|
||||
canvas_.DrawBitmap(bg2_bitmap, offset_x, offset_y, scale, 200);
|
||||
}
|
||||
|
||||
printf("[Canvas] RenderRoomBackgroundLayers complete\n");
|
||||
}
|
||||
|
||||
} // namespace yaze::editor
|
||||
|
||||
@@ -11,15 +11,11 @@
|
||||
|
||||
namespace yaze::editor {
|
||||
|
||||
using ImGui::BeginTable;
|
||||
using ImGui::EndTable;
|
||||
using ImGui::TableHeadersRow;
|
||||
using ImGui::TableNextColumn;
|
||||
using ImGui::TableNextRow;
|
||||
using ImGui::TableSetupColumn;
|
||||
// No table layout needed - all cards are independent
|
||||
|
||||
void DungeonEditorV2::Initialize() {
|
||||
// No complex initialization needed - components handle themselves
|
||||
// Don't initialize emulator preview yet - ROM might not be loaded
|
||||
// Will be initialized in Load() instead
|
||||
}
|
||||
|
||||
absl::Status DungeonEditorV2::Load() {
|
||||
@@ -52,20 +48,35 @@ absl::Status DungeonEditorV2::Load() {
|
||||
object_selector_.SetCurrentPaletteId(current_palette_id_);
|
||||
object_selector_.set_rooms(&rooms_);
|
||||
|
||||
// NOW initialize emulator preview with loaded ROM
|
||||
object_emulator_preview_.Initialize(rom_);
|
||||
|
||||
is_loaded_ = true;
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status DungeonEditorV2::Update() {
|
||||
if (!is_loaded_) {
|
||||
ImGui::Text("Loading...");
|
||||
// Show minimal loading message in parent window
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f), "Dungeon Editor Loading...");
|
||||
ImGui::TextWrapped("Independent editor cards will appear once ROM data is loaded.");
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// Minimize parent window content - just show a toolbar
|
||||
DrawToolset();
|
||||
gui::VerticalSpacing(2.0f);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextColored(ImVec4(0.5f, 0.5f, 0.5f, 1.0f),
|
||||
"Editor cards are independent windows - dock them anywhere!");
|
||||
ImGui::TextWrapped(
|
||||
"Room Selector, Object Selector, and Room cards can be freely arranged. "
|
||||
"This parent window can be minimized or closed.");
|
||||
|
||||
// Render all independent cards (these create their own top-level windows)
|
||||
object_emulator_preview_.Render();
|
||||
DrawLayout();
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
@@ -98,32 +109,33 @@ void DungeonEditorV2::DrawToolset() {
|
||||
}
|
||||
|
||||
void DungeonEditorV2::DrawLayout() {
|
||||
// Simple 3-column layout as designed
|
||||
if (BeginTable("##DungeonEditTable", 3,
|
||||
ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV,
|
||||
ImVec2(0, 0))) {
|
||||
TableSetupColumn("Room Selector", ImGuiTableColumnFlags_WidthFixed, 250);
|
||||
TableSetupColumn("Canvas", ImGuiTableColumnFlags_WidthStretch);
|
||||
TableSetupColumn("Object Selector", ImGuiTableColumnFlags_WidthFixed, 300);
|
||||
TableHeadersRow();
|
||||
TableNextRow();
|
||||
|
||||
// Column 1: Room Selector (fully delegated)
|
||||
TableNextColumn();
|
||||
room_selector_.Draw();
|
||||
|
||||
// Column 2: Canvas area for active room cards
|
||||
TableNextColumn();
|
||||
// This column is now just a docking space. The cards themselves are independent windows.
|
||||
|
||||
// Column 3: Object Selector (fully delegated)
|
||||
TableNextColumn();
|
||||
object_selector_.Draw();
|
||||
|
||||
EndTable();
|
||||
// NO TABLE LAYOUT - All independent dockable EditorCards
|
||||
|
||||
// 1. Room Selector Card (independent, dockable)
|
||||
{
|
||||
static bool show_room_selector = true;
|
||||
gui::EditorCard selector_card(
|
||||
MakeCardTitle("Room Selector").c_str(),
|
||||
ICON_MD_LIST, &show_room_selector);
|
||||
if (selector_card.Begin()) {
|
||||
room_selector_.Draw();
|
||||
}
|
||||
selector_card.End();
|
||||
}
|
||||
|
||||
// Draw active rooms as individual, dockable EditorCards
|
||||
// 2. Object Selector/Manager Card (independent, dockable)
|
||||
{
|
||||
static bool show_object_selector = true;
|
||||
gui::EditorCard object_card(
|
||||
MakeCardTitle("Object Selector").c_str(),
|
||||
ICON_MD_CATEGORY, &show_object_selector);
|
||||
if (object_card.Begin()) {
|
||||
object_selector_.Draw();
|
||||
}
|
||||
object_card.End();
|
||||
}
|
||||
|
||||
// 3. Active Room Cards (independent, dockable, no inheritance)
|
||||
for (int i = 0; i < active_rooms_.Size; i++) {
|
||||
int room_id = active_rooms_[i];
|
||||
bool open = true;
|
||||
@@ -138,13 +150,13 @@ void DungeonEditorV2::DrawLayout() {
|
||||
|
||||
std::string card_name_str = absl::StrFormat("%s###RoomCard%d",
|
||||
MakeCardTitle(base_name).c_str(), room_id);
|
||||
const char* card_name = card_name_str.c_str();
|
||||
|
||||
gui::EditorCard room_card(card_name, ICON_MD_GRID_ON, &open);
|
||||
// Each room card is COMPLETELY independent - no parent windows
|
||||
gui::EditorCard room_card(card_name_str.c_str(), ICON_MD_GRID_ON, &open);
|
||||
if (room_card.Begin()) {
|
||||
DrawRoomTab(room_id);
|
||||
}
|
||||
room_card.End(); // ALWAYS call End after Begin
|
||||
room_card.End();
|
||||
|
||||
if (!open) {
|
||||
active_rooms_.erase(active_rooms_.Data + i);
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "app/zelda3/dungeon/room_entrance.h"
|
||||
#include "app/gui/editor_layout.h"
|
||||
#include "app/gui/widgets/dungeon_object_emulator_preview.h"
|
||||
#include "imgui/imgui.h"
|
||||
|
||||
namespace yaze {
|
||||
@@ -39,7 +40,8 @@ class DungeonEditorV2 : public Editor {
|
||||
room_loader_(rom),
|
||||
room_selector_(rom),
|
||||
canvas_viewer_(rom),
|
||||
object_selector_(rom) {
|
||||
object_selector_(rom),
|
||||
object_emulator_preview_() {
|
||||
type_ = EditorType::kDungeon;
|
||||
}
|
||||
|
||||
@@ -62,6 +64,7 @@ class DungeonEditorV2 : public Editor {
|
||||
room_selector_.set_rom(rom);
|
||||
canvas_viewer_.SetRom(rom);
|
||||
object_selector_.SetRom(rom);
|
||||
object_emulator_preview_.Initialize(rom);
|
||||
}
|
||||
Rom* rom() const { return rom_; }
|
||||
|
||||
@@ -105,6 +108,7 @@ class DungeonEditorV2 : public Editor {
|
||||
DungeonRoomSelector room_selector_;
|
||||
DungeonCanvasViewer canvas_viewer_;
|
||||
DungeonObjectSelector object_selector_;
|
||||
gui::DungeonObjectEmulatorPreview object_emulator_preview_;
|
||||
|
||||
bool is_loaded_ = false;
|
||||
};
|
||||
|
||||
@@ -767,6 +767,7 @@ class Cpu {
|
||||
}
|
||||
|
||||
auto mutable_log_instructions() -> bool* { return &log_instructions_; }
|
||||
bool stopped() const { return stopped_; }
|
||||
|
||||
private:
|
||||
void compare(uint16_t register_value, uint16_t memory_value) {
|
||||
|
||||
@@ -321,7 +321,6 @@ class Ppu {
|
||||
// Set pixel output format (0 = BGRX, 1 = XBGR)
|
||||
void SetPixelFormat(uint8_t format) { pixelOutputFormat = format; }
|
||||
|
||||
private:
|
||||
int GetPixelForMode7(int x, int layer, bool priority);
|
||||
|
||||
const int cyclesPerScanline = 341; // SNES PPU has 341 cycles per scanline
|
||||
@@ -343,6 +342,9 @@ class Ppu {
|
||||
|
||||
// cgram access
|
||||
uint16_t cgram[0x100];
|
||||
|
||||
private:
|
||||
|
||||
uint8_t cgram_pointer_;
|
||||
bool cgram_second_write_;
|
||||
uint8_t cgram_buffer_;
|
||||
|
||||
@@ -3,6 +3,7 @@ set(
|
||||
app/gui/modules/asset_browser.cc
|
||||
app/gui/modules/text_editor.cc
|
||||
app/gui/widgets/agent_chat_widget.cc
|
||||
app/gui/widgets/dungeon_object_emulator_preview.cc
|
||||
app/gui/widgets/collaboration_panel.cc
|
||||
app/gui/canvas.cc
|
||||
app/gui/canvas_utils.cc
|
||||
|
||||
296
src/app/gui/widgets/dungeon_object_emulator_preview.cc
Normal file
296
src/app/gui/widgets/dungeon_object_emulator_preview.cc
Normal file
@@ -0,0 +1,296 @@
|
||||
#include "app/gui/widgets/dungeon_object_emulator_preview.h"
|
||||
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "app/zelda3/dungeon/room_object.h"
|
||||
#include "app/gui/widgets/widget_auto_register.h"
|
||||
#include "app/core/window.h"
|
||||
#include <cstdio>
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
|
||||
DungeonObjectEmulatorPreview::DungeonObjectEmulatorPreview() {
|
||||
snes_instance_ = std::make_unique<emu::Snes>();
|
||||
object_texture_ = SDL_CreateTexture(core::Renderer::Get().renderer(),
|
||||
SDL_PIXELFORMAT_ARGB8888,
|
||||
SDL_TEXTUREACCESS_STREAMING, 256, 256);
|
||||
}
|
||||
|
||||
DungeonObjectEmulatorPreview::~DungeonObjectEmulatorPreview() {
|
||||
if (object_texture_) {
|
||||
SDL_DestroyTexture(object_texture_);
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonObjectEmulatorPreview::Initialize(Rom* rom) {
|
||||
rom_ = rom;
|
||||
if (rom_ && rom_->is_loaded()) {
|
||||
auto rom_data = rom_->vector();
|
||||
snes_instance_->Init(rom_data);
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonObjectEmulatorPreview::Render() {
|
||||
if (!show_window_) return;
|
||||
|
||||
if (ImGui::Begin("Dungeon Object Emulator Preview", &show_window_, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
AutoWidgetScope scope("DungeonEditor/EmulatorPreview");
|
||||
|
||||
// ROM status indicator
|
||||
if (rom_ && rom_->is_loaded()) {
|
||||
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "ROM: Loaded ✓");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "ROM: Not loaded ✗");
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
RenderControls();
|
||||
ImGui::Separator();
|
||||
|
||||
// Preview image with border
|
||||
if (object_texture_) {
|
||||
ImGui::BeginChild("PreviewRegion", ImVec2(260, 260), true, ImGuiWindowFlags_NoScrollbar);
|
||||
ImGui::Image((ImTextureID)object_texture_, ImVec2(256, 256));
|
||||
ImGui::EndChild();
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(1.0f, 1.0f, 0.0f, 1.0f), "No texture available");
|
||||
}
|
||||
|
||||
// Debug info section
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Execution:");
|
||||
ImGui::Indent();
|
||||
ImGui::Text("Cycles: %d %s", last_cycle_count_,
|
||||
last_cycle_count_ >= 100000 ? "(TIMEOUT)" : "");
|
||||
ImGui::Unindent();
|
||||
|
||||
// Status with color coding
|
||||
ImGui::Text("Status:");
|
||||
ImGui::Indent();
|
||||
if (last_error_.empty()) {
|
||||
ImGui::TextColored(ImVec4(0.0f, 1.0f, 0.0f, 1.0f), "✓ OK");
|
||||
} else {
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "✗ %s", last_error_.c_str());
|
||||
}
|
||||
ImGui::Unindent();
|
||||
|
||||
// Help text
|
||||
ImGui::Separator();
|
||||
ImGui::TextWrapped("This tool uses the SNES emulator to render objects by "
|
||||
"executing the game's native drawing routines from bank $01.");
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void DungeonObjectEmulatorPreview::RenderControls() {
|
||||
ImGui::Text("Object Configuration:");
|
||||
ImGui::Indent();
|
||||
|
||||
// Object ID with hex display
|
||||
AutoInputInt("Object ID", &object_id_, 1, 10, ImGuiInputTextFlags_CharsHexadecimal);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("($%03X)", object_id_);
|
||||
|
||||
// Room context
|
||||
AutoInputInt("Room Context", &room_id_, 1, 10);
|
||||
ImGui::SameLine();
|
||||
ImGui::TextDisabled("(for graphics/palette)");
|
||||
|
||||
// Position controls
|
||||
AutoSliderInt("X Position", &object_x_, 0, 63);
|
||||
AutoSliderInt("Y Position", &object_y_, 0, 63);
|
||||
|
||||
ImGui::Unindent();
|
||||
|
||||
// Render button - large and prominent
|
||||
ImGui::Separator();
|
||||
if (ImGui::Button("Render Object", ImVec2(-1, 0))) {
|
||||
TriggerEmulatedRender();
|
||||
}
|
||||
|
||||
// Quick test buttons
|
||||
if (ImGui::BeginPopup("QuickTests")) {
|
||||
if (ImGui::MenuItem("Floor tile (0x00)")) { object_id_ = 0x00; TriggerEmulatedRender(); }
|
||||
if (ImGui::MenuItem("Wall N (0x60)")) { object_id_ = 0x60; TriggerEmulatedRender(); }
|
||||
if (ImGui::MenuItem("Door (0xF0)")) { object_id_ = 0xF0; TriggerEmulatedRender(); }
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
if (ImGui::Button("Quick Tests...", ImVec2(-1, 0))) {
|
||||
ImGui::OpenPopup("QuickTests");
|
||||
}
|
||||
}
|
||||
|
||||
void DungeonObjectEmulatorPreview::TriggerEmulatedRender() {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
last_error_ = "ROM not loaded";
|
||||
return;
|
||||
}
|
||||
|
||||
last_error_.clear();
|
||||
last_cycle_count_ = 0;
|
||||
|
||||
// 1. Reset and configure the SNES state
|
||||
snes_instance_->Reset(true);
|
||||
auto& cpu = snes_instance_->cpu();
|
||||
auto& ppu = snes_instance_->ppu();
|
||||
auto& memory = snes_instance_->memory();
|
||||
|
||||
// 2. Load room context (graphics, palettes)
|
||||
zelda3::Room default_room = zelda3::LoadRoomFromRom(rom_, room_id_);
|
||||
|
||||
// 3. Load palette into CGRAM
|
||||
auto dungeon_main_pal_group = rom_->palette_group().dungeon_main;
|
||||
|
||||
// Validate and clamp palette ID
|
||||
int palette_id = default_room.palette;
|
||||
if (palette_id < 0 || palette_id >= static_cast<int>(dungeon_main_pal_group.size())) {
|
||||
printf("[EMU] Warning: Room palette %d out of bounds, using palette 0\n", palette_id);
|
||||
palette_id = 0;
|
||||
}
|
||||
|
||||
auto palette = dungeon_main_pal_group[palette_id];
|
||||
for (size_t i = 0; i < palette.size() && i < 256; ++i) {
|
||||
ppu.cgram[i] = palette[i].snes();
|
||||
}
|
||||
|
||||
// 4. Load graphics into VRAM
|
||||
default_room.LoadRoomGraphics(default_room.blockset);
|
||||
default_room.CopyRoomGraphicsToBuffer();
|
||||
const auto& gfx_buffer = default_room.get_gfx_buffer();
|
||||
for (size_t i = 0; i < gfx_buffer.size() / 2 && i < 0x8000; ++i) {
|
||||
ppu.vram[i] = gfx_buffer[i * 2] | (gfx_buffer[i * 2 + 1] << 8);
|
||||
}
|
||||
|
||||
// 5. CRITICAL: Initialize tilemap buffers in WRAM
|
||||
// Game uses $7E:2000 for BG1 tilemap buffer, $7E:4000 for BG2
|
||||
for (uint32_t i = 0; i < 0x2000; i++) {
|
||||
snes_instance_->Write(0x7E2000 + i, 0x00); // BG1 tilemap buffer
|
||||
snes_instance_->Write(0x7E4000 + i, 0x00); // BG2 tilemap buffer
|
||||
}
|
||||
|
||||
// 6. Setup PPU registers for dungeon rendering
|
||||
snes_instance_->Write(0x002105, 0x09); // BG Mode 1 (4bpp for BG1/2)
|
||||
snes_instance_->Write(0x002107, 0x40); // BG1 tilemap at VRAM $4000 (32x32)
|
||||
snes_instance_->Write(0x002108, 0x48); // BG2 tilemap at VRAM $4800 (32x32)
|
||||
snes_instance_->Write(0x002109, 0x00); // BG1 chr data at VRAM $0000
|
||||
snes_instance_->Write(0x00210A, 0x00); // BG2 chr data at VRAM $0000
|
||||
snes_instance_->Write(0x00212C, 0x03); // Enable BG1+BG2 on main screen
|
||||
snes_instance_->Write(0x002100, 0x0F); // Screen display on, full brightness
|
||||
|
||||
// 7. Setup WRAM variables for drawing context
|
||||
snes_instance_->Write(0x7E00AF, room_id_ & 0xFF);
|
||||
snes_instance_->Write(0x7E049C, 0x00);
|
||||
snes_instance_->Write(0x7E049E, 0x00);
|
||||
|
||||
// 8. Create object and encode to bytes
|
||||
zelda3::RoomObject obj(object_id_, object_x_, object_y_, 0, 0);
|
||||
auto bytes = obj.EncodeObjectToBytes();
|
||||
|
||||
const uint32_t object_data_addr = 0x7E1000;
|
||||
snes_instance_->Write(object_data_addr, bytes.b1);
|
||||
snes_instance_->Write(object_data_addr + 1, bytes.b2);
|
||||
snes_instance_->Write(object_data_addr + 2, bytes.b3);
|
||||
snes_instance_->Write(object_data_addr + 3, 0xFF); // Terminator
|
||||
snes_instance_->Write(object_data_addr + 4, 0xFF);
|
||||
|
||||
// 9. Setup object pointer in WRAM
|
||||
snes_instance_->Write(0x7E00B7, object_data_addr & 0xFF);
|
||||
snes_instance_->Write(0x7E00B8, (object_data_addr >> 8) & 0xFF);
|
||||
snes_instance_->Write(0x7E00B9, (object_data_addr >> 16) & 0xFF);
|
||||
|
||||
// 10. Setup CPU state
|
||||
cpu.PB = 0x01;
|
||||
cpu.DB = 0x7E;
|
||||
cpu.D = 0x0000;
|
||||
cpu.SetSP(0x01FF);
|
||||
cpu.status = 0x30; // 8-bit mode
|
||||
|
||||
// Calculate X register (tilemap position)
|
||||
cpu.X = (object_y_ * 0x80) + (object_x_ * 2);
|
||||
cpu.Y = 0; // Object data offset
|
||||
|
||||
// 11. Lookup the object's drawing handler
|
||||
uint16_t handler_offset = 0;
|
||||
auto rom_data = rom_->data();
|
||||
uint32_t table_addr = 0;
|
||||
|
||||
if (object_id_ < 0x100) {
|
||||
table_addr = 0x018200 + (object_id_ * 2);
|
||||
} else if (object_id_ < 0x200) {
|
||||
table_addr = 0x018470 + ((object_id_ - 0x100) * 2);
|
||||
} else {
|
||||
table_addr = 0x0185F0 + ((object_id_ - 0x200) * 2);
|
||||
}
|
||||
|
||||
if (table_addr < rom_->size() - 1) {
|
||||
uint8_t lo = rom_data[table_addr];
|
||||
uint8_t hi = rom_data[table_addr + 1];
|
||||
handler_offset = lo | (hi << 8);
|
||||
} else {
|
||||
last_error_ = "Object ID out of bounds for handler lookup";
|
||||
return;
|
||||
}
|
||||
|
||||
if (handler_offset == 0x0000) {
|
||||
char buf[256];
|
||||
snprintf(buf, sizeof(buf), "Object $%04X has no drawing routine", object_id_);
|
||||
last_error_ = buf;
|
||||
return;
|
||||
}
|
||||
|
||||
// 12. Setup return address and jump to handler
|
||||
const uint16_t return_addr = 0x8000;
|
||||
snes_instance_->Write(0x018000, 0x6B); // RTL instruction (0x6B not 0x60!)
|
||||
|
||||
// Push return address for RTL (3 bytes: bank, high, low)
|
||||
uint16_t sp = cpu.SP();
|
||||
snes_instance_->Write(0x010000 | sp--, 0x01); // Bank byte
|
||||
snes_instance_->Write(0x010000 | sp--, (return_addr - 1) >> 8); // High
|
||||
snes_instance_->Write(0x010000 | sp--, (return_addr - 1) & 0xFF); // Low
|
||||
cpu.SetSP(sp);
|
||||
|
||||
// Jump to handler (offset is relative to RoomDrawObjectData base)
|
||||
cpu.PC = handler_offset;
|
||||
|
||||
printf("[EMU] Rendering object $%04X at (%d,%d), handler=$%04X\n",
|
||||
object_id_, object_x_, object_y_, handler_offset);
|
||||
|
||||
// 13. Run emulator with timeout
|
||||
int max_cycles = 100000;
|
||||
int cycles = 0;
|
||||
while (cycles < max_cycles) {
|
||||
if (cpu.PB == 0x01 && cpu.PC == return_addr) {
|
||||
break; // Hit return address
|
||||
}
|
||||
snes_instance_->RunCycle();
|
||||
cycles++;
|
||||
}
|
||||
|
||||
last_cycle_count_ = cycles;
|
||||
|
||||
printf("[EMU] Completed after %d cycles, PC=$%02X:%04X\n",
|
||||
cycles, cpu.PB, cpu.PC);
|
||||
|
||||
if (cycles >= max_cycles) {
|
||||
last_error_ = "Timeout: exceeded max cycles";
|
||||
return;
|
||||
}
|
||||
|
||||
// 14. Force PPU to render the tilemaps
|
||||
ppu.HandleFrameStart();
|
||||
for (int line = 0; line < 224; line++) {
|
||||
ppu.RunLine(line);
|
||||
}
|
||||
ppu.HandleVblank();
|
||||
|
||||
// 15. Get the rendered pixels from PPU
|
||||
void* pixels = nullptr;
|
||||
int pitch = 0;
|
||||
if (SDL_LockTexture(object_texture_, nullptr, &pixels, &pitch) == 0) {
|
||||
snes_instance_->SetPixels(static_cast<uint8_t*>(pixels));
|
||||
SDL_UnlockTexture(object_texture_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
40
src/app/gui/widgets/dungeon_object_emulator_preview.h
Normal file
40
src/app/gui/widgets/dungeon_object_emulator_preview.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#ifndef YAZE_APP_GUI_WIDGETS_DUNGEON_OBJECT_EMULATOR_PREVIEW_H_
|
||||
#define YAZE_APP_GUI_WIDGETS_DUNGEON_OBJECT_EMULATOR_PREVIEW_H_
|
||||
|
||||
#include "app/emu/snes.h"
|
||||
#include "app/rom.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace gui {
|
||||
|
||||
class DungeonObjectEmulatorPreview {
|
||||
public:
|
||||
DungeonObjectEmulatorPreview();
|
||||
~DungeonObjectEmulatorPreview();
|
||||
|
||||
void Initialize(Rom* rom);
|
||||
void Render();
|
||||
|
||||
private:
|
||||
void RenderControls();
|
||||
void TriggerEmulatedRender();
|
||||
|
||||
Rom* rom_ = nullptr;
|
||||
std::unique_ptr<emu::Snes> snes_instance_;
|
||||
SDL_Texture* object_texture_ = nullptr;
|
||||
|
||||
int object_id_ = 0;
|
||||
int room_id_ = 0;
|
||||
int object_x_ = 16;
|
||||
int object_y_ = 16;
|
||||
bool show_window_ = true;
|
||||
|
||||
// Debug info
|
||||
int last_cycle_count_ = 0;
|
||||
std::string last_error_;
|
||||
};
|
||||
|
||||
} // namespace gui
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_GUI_WIDGETS_DUNGEON_OBJECT_EMULATOR_PREVIEW_H_
|
||||
220
src/app/zelda3/dungeon/object_drawer.cc
Normal file
220
src/app/zelda3/dungeon/object_drawer.cc
Normal file
@@ -0,0 +1,220 @@
|
||||
#include "object_drawer.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
ObjectDrawer::ObjectDrawer(Rom* rom) : rom_(rom) {}
|
||||
|
||||
absl::Status ObjectDrawer::DrawObject(const RoomObject& object,
|
||||
gfx::BackgroundBuffer& bg1,
|
||||
gfx::BackgroundBuffer& bg2) {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
// Ensure object has tiles loaded
|
||||
auto mutable_obj = const_cast<RoomObject&>(object);
|
||||
mutable_obj.set_rom(rom_);
|
||||
mutable_obj.EnsureTilesLoaded();
|
||||
|
||||
// Get tiles - silently skip objects that can't load tiles
|
||||
if (object.tiles().empty()) {
|
||||
// Many objects may not have tiles loaded yet - this is normal
|
||||
// Just skip them rather than failing the whole draw operation
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
const auto& tile = object.tiles()[0]; // Base tile for object
|
||||
|
||||
// Select buffer based on layer
|
||||
auto& target_bg = (object.layer_ == RoomObject::LayerType::BG2) ? bg2 : bg1;
|
||||
|
||||
// Dispatch to pattern-specific drawing based on object ID
|
||||
// This is reverse-engineered from the game's drawing routines
|
||||
|
||||
if (object.id_ == 0x34) {
|
||||
// Object 0x34: 1x1 solid block (simplest)
|
||||
Draw1x1Solid(object, target_bg, tile);
|
||||
}
|
||||
else if (object.id_ >= 0x00 && object.id_ <= 0x08) {
|
||||
// Objects 0x00-0x08: Rightward 2x2 patterns
|
||||
DrawRightwards2x2(object, target_bg, tile);
|
||||
}
|
||||
else if (object.id_ >= 0x60 && object.id_ <= 0x68) {
|
||||
// Objects 0x60-0x68: Downward 2x2 patterns
|
||||
DrawDownwards2x2(object, target_bg, tile);
|
||||
}
|
||||
else if (object.id_ >= 0x09 && object.id_ <= 0x14) {
|
||||
// Objects 0x09-0x14: Diagonal acute patterns
|
||||
DrawDiagonalAcute(object, target_bg, tile);
|
||||
}
|
||||
else if (object.id_ >= 0x15 && object.id_ <= 0x20) {
|
||||
// Objects 0x15-0x20: Diagonal grave patterns
|
||||
DrawDiagonalGrave(object, target_bg, tile);
|
||||
}
|
||||
else if (object.id_ == 0x33 || (object.id_ >= 0x70 && object.id_ <= 0x71)) {
|
||||
// 4x4 block objects
|
||||
Draw4x4Block(object, target_bg, tile);
|
||||
}
|
||||
else {
|
||||
// Default: Draw as simple 1x1 at position
|
||||
Draw1x1Solid(object, target_bg, tile);
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::Status ObjectDrawer::DrawObjectList(
|
||||
const std::vector<RoomObject>& objects,
|
||||
gfx::BackgroundBuffer& bg1,
|
||||
gfx::BackgroundBuffer& bg2) {
|
||||
|
||||
int drawn_count = 0;
|
||||
int skipped_count = 0;
|
||||
|
||||
for (const auto& object : objects) {
|
||||
auto status = DrawObject(object, bg1, bg2);
|
||||
if (status.ok()) {
|
||||
drawn_count++;
|
||||
} else {
|
||||
skipped_count++;
|
||||
// Only print errors that aren't "no tiles" (which is common and expected)
|
||||
if (status.code() != absl::StatusCode::kOk) {
|
||||
// Skip silently - many objects don't have tiles loaded yet
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (drawn_count > 0 || skipped_count > 0) {
|
||||
printf("[ObjectDrawer] Drew %d objects, skipped %d\n", drawn_count, skipped_count);
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Pattern Drawing Implementations
|
||||
// ============================================================================
|
||||
|
||||
void ObjectDrawer::Draw1x1Solid(const RoomObject& obj,
|
||||
gfx::BackgroundBuffer& bg,
|
||||
const gfx::Tile16& tile) {
|
||||
// Simple 1x1 tile placement
|
||||
WriteTile16(bg, obj.x_, obj.y_, tile);
|
||||
}
|
||||
|
||||
void ObjectDrawer::DrawRightwards2x2(const RoomObject& obj,
|
||||
gfx::BackgroundBuffer& bg,
|
||||
const gfx::Tile16& tile) {
|
||||
// Pattern: Draws 2x2 tiles rightward
|
||||
// Size byte determines how many times to repeat
|
||||
int repeat_count = (obj.size_ & 0x0F) + 1; // Low nibble = width
|
||||
|
||||
for (int i = 0; i < repeat_count; i++) {
|
||||
// Each iteration draws a 2x2 tile16
|
||||
int tile_x = obj.x_ + (i * 2); // Each tile16 is 2x2 8x8 tiles
|
||||
int tile_y = obj.y_;
|
||||
|
||||
WriteTile16(bg, tile_x, tile_y, tile);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectDrawer::DrawDownwards2x2(const RoomObject& obj,
|
||||
gfx::BackgroundBuffer& bg,
|
||||
const gfx::Tile16& tile) {
|
||||
// Pattern: Draws 2x2 tiles downward
|
||||
// Size byte determines how many times to repeat
|
||||
int repeat_count = ((obj.size_ >> 4) & 0x0F) + 1; // High nibble = height
|
||||
|
||||
for (int i = 0; i < repeat_count; i++) {
|
||||
int tile_x = obj.x_;
|
||||
int tile_y = obj.y_ + (i * 2);
|
||||
|
||||
WriteTile16(bg, tile_x, tile_y, tile);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectDrawer::DrawDiagonalAcute(const RoomObject& obj,
|
||||
gfx::BackgroundBuffer& bg,
|
||||
const gfx::Tile16& tile) {
|
||||
// Pattern: Diagonal line going down-right (/)
|
||||
int length = (obj.size_ & 0x0F) + 1;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int tile_x = obj.x_ + i;
|
||||
int tile_y = obj.y_ + i;
|
||||
|
||||
WriteTile16(bg, tile_x, tile_y, tile);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectDrawer::DrawDiagonalGrave(const RoomObject& obj,
|
||||
gfx::BackgroundBuffer& bg,
|
||||
const gfx::Tile16& tile) {
|
||||
// Pattern: Diagonal line going down-left (\)
|
||||
int length = (obj.size_ & 0x0F) + 1;
|
||||
|
||||
for (int i = 0; i < length; i++) {
|
||||
int tile_x = obj.x_ - i;
|
||||
int tile_y = obj.y_ + i;
|
||||
|
||||
WriteTile16(bg, tile_x, tile_y, tile);
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectDrawer::Draw4x4Block(const RoomObject& obj,
|
||||
gfx::BackgroundBuffer& bg,
|
||||
const gfx::Tile16& tile) {
|
||||
// Pattern: 4x4 tile16 block (8x8 8x8 tiles total)
|
||||
for (int yy = 0; yy < 4; yy++) {
|
||||
for (int xx = 0; xx < 4; xx++) {
|
||||
int tile_x = obj.x_ + (xx * 2);
|
||||
int tile_y = obj.y_ + (yy * 2);
|
||||
|
||||
WriteTile16(bg, tile_x, tile_y, tile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Utility Methods
|
||||
// ============================================================================
|
||||
|
||||
void ObjectDrawer::WriteTile16(gfx::BackgroundBuffer& bg, int tile_x, int tile_y,
|
||||
const gfx::Tile16& tile) {
|
||||
// A Tile16 is 2x2 8x8 tiles, so we write 4 tile entries
|
||||
|
||||
// Top-left (tile0)
|
||||
if (IsValidTilePosition(tile_x, tile_y)) {
|
||||
bg.SetTileAt(tile_x, tile_y, gfx::TileInfoToWord(tile.tile0_));
|
||||
}
|
||||
|
||||
// Top-right (tile1)
|
||||
if (IsValidTilePosition(tile_x + 1, tile_y)) {
|
||||
bg.SetTileAt(tile_x + 1, tile_y, gfx::TileInfoToWord(tile.tile1_));
|
||||
}
|
||||
|
||||
// Bottom-left (tile2)
|
||||
if (IsValidTilePosition(tile_x, tile_y + 1)) {
|
||||
bg.SetTileAt(tile_x, tile_y + 1, gfx::TileInfoToWord(tile.tile2_));
|
||||
}
|
||||
|
||||
// Bottom-right (tile3)
|
||||
if (IsValidTilePosition(tile_x + 1, tile_y + 1)) {
|
||||
bg.SetTileAt(tile_x + 1, tile_y + 1, gfx::TileInfoToWord(tile.tile3_));
|
||||
}
|
||||
}
|
||||
|
||||
bool ObjectDrawer::IsValidTilePosition(int tile_x, int tile_y) const {
|
||||
return tile_x >= 0 && tile_x < kMaxTilesX &&
|
||||
tile_y >= 0 && tile_y < kMaxTilesY;
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
|
||||
84
src/app/zelda3/dungeon/object_drawer.h
Normal file
84
src/app/zelda3/dungeon/object_drawer.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#ifndef YAZE_APP_ZELDA3_DUNGEON_OBJECT_DRAWER_H
|
||||
#define YAZE_APP_ZELDA3_DUNGEON_OBJECT_DRAWER_H
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "app/gfx/background_buffer.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/dungeon/room_object.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
/**
|
||||
* @brief Draws dungeon objects to background buffers using game patterns
|
||||
*
|
||||
* This class interprets object IDs and draws them to BG1/BG2 buffers
|
||||
* using the patterns extracted from the game's drawing routines.
|
||||
*
|
||||
* Architecture:
|
||||
* 1. Load tile data from ROM for the object
|
||||
* 2. Determine drawing pattern (rightward, downward, diagonal, special)
|
||||
* 3. Write tiles to BackgroundBuffer according to pattern
|
||||
* 4. Handle size bytes for repeating patterns
|
||||
*/
|
||||
class ObjectDrawer {
|
||||
public:
|
||||
explicit ObjectDrawer(Rom* rom);
|
||||
|
||||
/**
|
||||
* @brief Draw a room object to background buffers
|
||||
* @param object The object to draw
|
||||
* @param bg1 Background layer 1 buffer
|
||||
* @param bg2 Background layer 2 buffer
|
||||
* @return Status of the drawing operation
|
||||
*/
|
||||
absl::Status DrawObject(const RoomObject& object,
|
||||
gfx::BackgroundBuffer& bg1,
|
||||
gfx::BackgroundBuffer& bg2);
|
||||
|
||||
/**
|
||||
* @brief Draw all objects in a room
|
||||
* @param objects Vector of room objects
|
||||
* @param bg1 Background layer 1 buffer
|
||||
* @param bg2 Background layer 2 buffer
|
||||
* @return Status of the drawing operation
|
||||
*/
|
||||
absl::Status DrawObjectList(const std::vector<RoomObject>& objects,
|
||||
gfx::BackgroundBuffer& bg1,
|
||||
gfx::BackgroundBuffer& bg2);
|
||||
|
||||
private:
|
||||
// Pattern-specific drawing methods
|
||||
void DrawRightwards2x2(const RoomObject& obj, gfx::BackgroundBuffer& bg,
|
||||
const gfx::Tile16& tile);
|
||||
void DrawDownwards2x2(const RoomObject& obj, gfx::BackgroundBuffer& bg,
|
||||
const gfx::Tile16& tile);
|
||||
void DrawDiagonalAcute(const RoomObject& obj, gfx::BackgroundBuffer& bg,
|
||||
const gfx::Tile16& tile);
|
||||
void DrawDiagonalGrave(const RoomObject& obj, gfx::BackgroundBuffer& bg,
|
||||
const gfx::Tile16& tile);
|
||||
void Draw1x1Solid(const RoomObject& obj, gfx::BackgroundBuffer& bg,
|
||||
const gfx::Tile16& tile);
|
||||
void Draw4x4Block(const RoomObject& obj, gfx::BackgroundBuffer& bg,
|
||||
const gfx::Tile16& tile);
|
||||
|
||||
// Utility methods
|
||||
void WriteTile16(gfx::BackgroundBuffer& bg, int tile_x, int tile_y,
|
||||
const gfx::Tile16& tile);
|
||||
bool IsValidTilePosition(int tile_x, int tile_y) const;
|
||||
|
||||
Rom* rom_;
|
||||
|
||||
// Canvas dimensions in tiles (64x64 = 512x512 pixels)
|
||||
static constexpr int kMaxTilesX = 64;
|
||||
static constexpr int kMaxTilesY = 64;
|
||||
};
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_ZELDA3_DUNGEON_OBJECT_DRAWER_H
|
||||
|
||||
@@ -310,30 +310,38 @@ void Room::RenderRoomGraphics() {
|
||||
int num_palettes = dungeon_pal_group.size();
|
||||
int palette_id = palette;
|
||||
|
||||
std::printf("5a. Dungeon palette group has %d palettes total\n", num_palettes);
|
||||
|
||||
// Validate palette ID and fall back to palette 0 if invalid
|
||||
if (palette_id < 0 || palette_id >= num_palettes) {
|
||||
//palette_id = 0;
|
||||
std::printf("5a. WARNING: palette_id %d is out of bounds [0, %d), using palette 0\n",
|
||||
palette_id, num_palettes);
|
||||
palette_id = 0;
|
||||
}
|
||||
|
||||
// Load the 90-color dungeon palette directly
|
||||
// The palette contains colors for BG layers - sprite colors are handled separately
|
||||
auto bg1_palette = dungeon_pal_group.palette(palette_id);
|
||||
auto bg1_palette = dungeon_pal_group[palette_id]; // Use operator[] to get a proper reference
|
||||
|
||||
std::printf("5a. Palette loaded: room palette_id=%d (requested=%d), size=%zu colors\n",
|
||||
palette_id, palette, bg1_palette.size());
|
||||
|
||||
// CRITICAL: Apply palette to bitmaps BEFORE creating/updating textures
|
||||
bg1_bmp.SetPaletteWithTransparent(bg1_palette, 0);
|
||||
bg2_bmp.SetPaletteWithTransparent(bg1_palette, 0);
|
||||
std::printf("5b. Palette applied to bitmaps\n");
|
||||
// CRITICAL: Only apply palette if it's valid
|
||||
if (bg1_palette.size() > 0) {
|
||||
bg1_bmp.SetPaletteWithTransparent(bg1_palette, 0);
|
||||
bg2_bmp.SetPaletteWithTransparent(bg1_palette, 0);
|
||||
std::printf("5b. Palette applied to bitmaps\n");
|
||||
} else {
|
||||
std::printf("5b. WARNING: Palette is empty, skipping SetPalette\n");
|
||||
}
|
||||
|
||||
// ALWAYS recreate textures when palette changes (UpdateBitmap doesn't update palette!)
|
||||
std::printf("6. Recreating bitmap textures with new palette\n");
|
||||
core::Renderer::Get().CreateAndRenderBitmap(
|
||||
0x200, 0x200, 0x200, gfx::Arena::Get().bg1().bitmap().vector(),
|
||||
0x200, 0x200, 8, gfx::Arena::Get().bg1().bitmap().vector(),
|
||||
gfx::Arena::Get().bg1().bitmap(), bg1_palette);
|
||||
core::Renderer::Get().CreateAndRenderBitmap(
|
||||
0x200, 0x200, 0x200, gfx::Arena::Get().bg2().bitmap().vector(),
|
||||
0x200, 0x200, 8, gfx::Arena::Get().bg2().bitmap().vector(),
|
||||
gfx::Arena::Get().bg2().bitmap(), bg1_palette);
|
||||
|
||||
std::printf("7. BG1 has texture: %d\n", bg1_bmp.texture() != nullptr);
|
||||
|
||||
@@ -347,6 +347,7 @@ class Room {
|
||||
auto& mutable_blocks() { return blocks_; }
|
||||
auto rom() { return rom_; }
|
||||
auto mutable_rom() { return rom_; }
|
||||
const std::array<uint8_t, 0x4000>& get_gfx_buffer() const { return current_gfx16_; }
|
||||
|
||||
private:
|
||||
Rom* rom_;
|
||||
|
||||
@@ -14,6 +14,7 @@ set(
|
||||
app/zelda3/dungeon/room_object.cc
|
||||
app/zelda3/dungeon/object_parser.cc
|
||||
app/zelda3/dungeon/object_renderer.cc
|
||||
app/zelda3/dungeon/object_drawer.cc
|
||||
app/zelda3/dungeon/room_layout.cc
|
||||
app/zelda3/dungeon/room_diagnostic.cc
|
||||
app/zelda3/dungeon/room_visual_diagnostic.cc
|
||||
|
||||
Reference in New Issue
Block a user