feat: Introduce Layout Override Feature in DungeonCanvasViewer
- Added a new "Layout Override" section in the DungeonCanvasViewer, allowing users to enable or disable layout overrides for dungeon rooms. - Implemented a checkbox to toggle the override and a slider to select the layout ID when enabled. - Removed the previously disabled room layout drawing code to streamline the rendering process. - Updated the layout management to ensure proper handling of layout IDs and visibility settings. - Enhanced the overall user interface for better control over dungeon layout visualization.
This commit is contained in:
@@ -408,6 +408,16 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
|
||||
ImGui::Checkbox("BG2 Visible", &layer_settings.bg2_visible);
|
||||
ImGui::SliderInt("BG2 Type", &layer_settings.bg2_layer_type, 0, 4);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Layout Override");
|
||||
static bool enable_override = false;
|
||||
ImGui::Checkbox("Enable Override", &enable_override);
|
||||
if (enable_override) {
|
||||
ImGui::SliderInt("Layout ID", &layout_override_, 0, 7);
|
||||
} else {
|
||||
layout_override_ = -1; // Disable override
|
||||
}
|
||||
|
||||
if (show_object_bounds_) {
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Object Outline Filters");
|
||||
@@ -523,11 +533,7 @@ void DungeonCanvasViewer::DrawDungeonCanvas(int room_id) {
|
||||
if (rooms_ && rom_->is_loaded()) {
|
||||
auto& room = (*rooms_)[room_id];
|
||||
|
||||
// DISABLED: Room layout drawing - causes visual clutter
|
||||
// Layout tiles (2793) render over everything and obscure objects
|
||||
// if (show_layout_overlay_) {
|
||||
// DrawRoomLayout(room);
|
||||
// }
|
||||
// Draw the room layout first as the base layer
|
||||
|
||||
// VISUALIZATION: Draw object position rectangles (for debugging)
|
||||
// This shows where objects are placed regardless of whether graphics render
|
||||
@@ -693,60 +699,6 @@ void DungeonCanvasViewer::CalculateWallDimensions(const zelda3::RoomObject& obje
|
||||
}
|
||||
|
||||
// Room layout visualization
|
||||
void DungeonCanvasViewer::DrawRoomLayout(zelda3::Room& room) {
|
||||
// Draw room layout structural elements (walls, floors, pits)
|
||||
// This is the immovable BASE LAYER that defines room structure
|
||||
|
||||
auto& layout = room.GetLayout();
|
||||
|
||||
// Ensure layout is loaded (critical!)
|
||||
if (layout.GetObjects().empty()) {
|
||||
auto status = layout.LoadLayout(room.id());
|
||||
if (!status.ok()) {
|
||||
LOG_DEBUG("[DrawRoomLayout]", "Failed to load layout: %s", status.message().data());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Get structural elements by type
|
||||
auto walls = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kWall);
|
||||
auto floors = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kFloor);
|
||||
auto pits = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kPit);
|
||||
auto water = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kWater);
|
||||
auto doors = layout.GetObjectsByType(zelda3::RoomLayoutObject::Type::kDoor);
|
||||
|
||||
LOG_DEBUG("[DrawRoomLayout]", "Layout elements: %zu walls, %zu floors, %zu pits, %zu water, %zu doors",
|
||||
walls.size(), floors.size(), pits.size(), water.size(), doors.size());
|
||||
|
||||
// IMPORTANT: Use UNSCALED dimensions - canvas drawing functions apply scale internally
|
||||
constexpr int kTileSize = 8; // Dungeon tiles are 8x8 pixels (UNSCALED)
|
||||
|
||||
// Draw walls (dark gray, semi-transparent) - immovable structure
|
||||
for (const auto& wall : walls) {
|
||||
auto [x, y] = RoomToCanvasCoordinates(wall.x(), wall.y());
|
||||
canvas_.DrawRect(x, y, kTileSize, kTileSize, ImVec4(0.2f, 0.2f, 0.2f, 0.5f));
|
||||
}
|
||||
|
||||
// Draw pits (orange warning) - damage zones
|
||||
for (const auto& pit : pits) {
|
||||
auto [x, y] = RoomToCanvasCoordinates(pit.x(), pit.y());
|
||||
canvas_.DrawRect(x, y, kTileSize, kTileSize, ImVec4(1.0f, 0.4f, 0.0f, 0.6f));
|
||||
}
|
||||
|
||||
// Draw water (blue, semi-transparent)
|
||||
for (const auto& water_tile : water) {
|
||||
auto [x, y] = RoomToCanvasCoordinates(water_tile.x(), water_tile.y());
|
||||
canvas_.DrawRect(x, y, kTileSize, kTileSize, ImVec4(0.2f, 0.4f, 0.8f, 0.5f));
|
||||
}
|
||||
|
||||
// Draw doors (purple) - connection points
|
||||
for (const auto& door : doors) {
|
||||
auto [x, y] = RoomToCanvasCoordinates(door.x(), door.y());
|
||||
canvas_.DrawRect(x, y, kTileSize, kTileSize, ImVec4(0.8f, 0.2f, 0.8f, 0.7f));
|
||||
// Text is drawn in logical space, canvas scales it
|
||||
canvas_.DrawText("DOOR", x + 2, y + 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Object visualization methods
|
||||
void DungeonCanvasViewer::DrawObjectPositionOutlines(const zelda3::Room& room) {
|
||||
@@ -859,8 +811,9 @@ absl::Status DungeonCanvasViewer::LoadAndRenderRoomGraphics(int room_id) {
|
||||
current_palette_group_id_ = palette_id.value() / 180;
|
||||
if (current_palette_group_id_ < rom_->palette_group().dungeon_main.size()) {
|
||||
auto full_palette = rom_->palette_group().dungeon_main[current_palette_group_id_];
|
||||
// TODO: Fix palette assignment to buffer.
|
||||
ASSIGN_OR_RETURN(current_palette_group_,
|
||||
gfx::CreatePaletteGroupFromLargePalette(full_palette));
|
||||
gfx::CreatePaletteGroupFromLargePalette(full_palette, 16));
|
||||
LOG_DEBUG("[LoadAndRender]", "Palette loaded: group_id=%zu", current_palette_group_id_);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +104,6 @@ class DungeonCanvasViewer {
|
||||
void CalculateWallDimensions(const zelda3::RoomObject& object, int& width, int& height);
|
||||
|
||||
// Visualization
|
||||
void DrawRoomLayout(zelda3::Room& room); // Non-const to call LoadLayout
|
||||
void DrawObjectPositionOutlines(const zelda3::Room& room);
|
||||
|
||||
// Room graphics management
|
||||
@@ -155,13 +154,14 @@ class DungeonCanvasViewer {
|
||||
std::vector<ObjectRenderCache> object_render_cache_;
|
||||
uint64_t last_palette_hash_ = 0;
|
||||
|
||||
// Debug state (persistent between frames for debug windows)
|
||||
// Debug state flags
|
||||
bool show_room_debug_info_ = false;
|
||||
bool show_texture_debug_ = false;
|
||||
bool show_object_bounds_ = false;
|
||||
bool show_layer_info_ = false;
|
||||
|
||||
// Object outline category toggles
|
||||
int layout_override_ = -1; // -1 for no override
|
||||
|
||||
// Object outline filtering toggles
|
||||
struct ObjectOutlineToggles {
|
||||
bool show_type1_objects = true; // Standard objects (0x00-0xFF)
|
||||
bool show_type2_objects = true; // Extended objects (0x100-0x1FF)
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "app/gui/canvas.h"
|
||||
#include "app/gui/modules/asset_browser.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/dungeon/object_renderer.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "app/zelda3/dungeon/dungeon_editor_system.h"
|
||||
#include "app/zelda3/dungeon/dungeon_object_editor.h"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
|
||||
#include "app/gfx/performance_profiler.h"
|
||||
#include "app/gfx/performance/performance_profiler.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "util/log.h"
|
||||
|
||||
@@ -83,6 +83,9 @@ void BackgroundBuffer::DrawTile(const TileInfo& tile, uint8_t* canvas,
|
||||
|
||||
// Apply palette offset and write to canvas
|
||||
// For 3BPP: final color = base_pixel (0-7) + palette_offset (0, 8, 16, 24, ...)
|
||||
if (pixel_index == 0) {
|
||||
continue;
|
||||
}
|
||||
uint8_t final_color = pixel_index + palette_offset;
|
||||
int dest_index = indexoffset + (py * width_) + px;
|
||||
canvas[dest_index] = final_color;
|
||||
|
||||
@@ -14,7 +14,6 @@ namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
DungeonObjectEditor::DungeonObjectEditor(Rom* rom) : rom_(rom) {
|
||||
renderer_ = std::make_unique<ObjectRenderer>(rom);
|
||||
}
|
||||
|
||||
absl::Status DungeonObjectEditor::InitializeEditor() {
|
||||
@@ -1153,27 +1152,6 @@ absl::Status DungeonObjectEditor::HandleDragOperation(int current_x, int current
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
absl::StatusOr<gfx::Bitmap> DungeonObjectEditor::RenderRoom() {
|
||||
if (current_room_ == nullptr) {
|
||||
return absl::FailedPreconditionError("No room loaded");
|
||||
}
|
||||
|
||||
// Create a palette for rendering
|
||||
gfx::SnesPalette palette;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
int intensity = i * 16;
|
||||
palette.AddColor(gfx::SnesColor(intensity, intensity, intensity));
|
||||
}
|
||||
|
||||
// Render room objects
|
||||
auto result = renderer_->RenderObjects(current_room_->GetTileObjects(), palette);
|
||||
if (!result.ok()) {
|
||||
return result.status();
|
||||
}
|
||||
|
||||
return result.value();
|
||||
}
|
||||
|
||||
absl::Status DungeonObjectEditor::ValidateRoom() {
|
||||
if (current_room_ == nullptr) {
|
||||
return absl::FailedPreconditionError("No room loaded for validation");
|
||||
@@ -1213,9 +1191,6 @@ void DungeonObjectEditor::SetConfig(const EditorConfig& config) {
|
||||
|
||||
void DungeonObjectEditor::SetROM(Rom* rom) {
|
||||
rom_ = rom;
|
||||
if (renderer_) {
|
||||
renderer_->SetROM(rom);
|
||||
}
|
||||
// Reinitialize editor with new ROM
|
||||
InitializeEditor();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "app/zelda3/dungeon/room_object.h"
|
||||
#include "object_renderer.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
@@ -150,7 +149,6 @@ class DungeonObjectEditor {
|
||||
absl::Status ValidateObjectType(int object_type);
|
||||
|
||||
// Rendering and preview
|
||||
absl::StatusOr<gfx::Bitmap> RenderRoom();
|
||||
absl::StatusOr<gfx::Bitmap> RenderPreview(int x, int y);
|
||||
void SetPreviewPosition(int x, int y);
|
||||
void UpdatePreview();
|
||||
@@ -237,7 +235,6 @@ class DungeonObjectEditor {
|
||||
// Member variables
|
||||
Rom* rom_;
|
||||
std::unique_ptr<Room> current_room_;
|
||||
std::unique_ptr<ObjectRenderer> renderer_;
|
||||
|
||||
SelectionState selection_state_;
|
||||
EditingState editing_state_;
|
||||
|
||||
@@ -45,7 +45,10 @@ absl::Status ObjectDrawer::DrawObject(const RoomObject& object,
|
||||
// Look up draw routine for this object
|
||||
int routine_id = GetDrawRoutineId(object.id_);
|
||||
|
||||
LOG_DEBUG("ObjectDrawer", "Object %04X -> routine_id=%d", object.id_, routine_id);
|
||||
|
||||
if (routine_id < 0 || routine_id >= static_cast<int>(draw_routines_.size())) {
|
||||
LOG_DEBUG("ObjectDrawer", "Using fallback 1x1 drawing for object %04X", object.id_);
|
||||
// Fallback to simple 1x1 drawing using first 8x8 tile
|
||||
if (!mutable_obj.tiles().empty()) {
|
||||
const auto& tile16 = mutable_obj.tiles()[0];
|
||||
@@ -54,6 +57,7 @@ absl::Status ObjectDrawer::DrawObject(const RoomObject& object,
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
LOG_DEBUG("ObjectDrawer", "Executing draw routine %d for object %04X", routine_id, object.id_);
|
||||
// Execute the appropriate draw routine
|
||||
draw_routines_[routine_id](this, object, target_bg, mutable_obj.tiles());
|
||||
|
||||
@@ -78,197 +82,111 @@ absl::Status ObjectDrawer::DrawObjectList(
|
||||
// ============================================================================
|
||||
|
||||
void ObjectDrawer::InitializeDrawRoutines() {
|
||||
// Initialize draw routine registry based on ZScream's subtype1_routines table
|
||||
// This maps object IDs to draw routine indices (0-24)
|
||||
|
||||
// Routine 0: RoomDraw_Rightwards2x2_1to15or32
|
||||
for (int id = 0x00; id <= 0x00; id++) {
|
||||
object_to_routine_map_[id] = 0;
|
||||
}
|
||||
|
||||
// Routine 1: RoomDraw_Rightwards2x4_1to15or26
|
||||
for (int id = 0x01; id <= 0x02; id++) {
|
||||
object_to_routine_map_[id] = 1;
|
||||
}
|
||||
|
||||
// Routine 2: RoomDraw_Rightwards2x4spaced4_1to16
|
||||
for (int id = 0x03; id <= 0x04; id++) {
|
||||
object_to_routine_map_[id] = 2;
|
||||
}
|
||||
|
||||
// Routine 3: RoomDraw_Rightwards2x4spaced4_1to16_BothBG
|
||||
for (int id = 0x05; id <= 0x06; id++) {
|
||||
object_to_routine_map_[id] = 3;
|
||||
}
|
||||
|
||||
// Routine 4: RoomDraw_Rightwards2x2_1to16
|
||||
for (int id = 0x07; id <= 0x08; id++) {
|
||||
object_to_routine_map_[id] = 4;
|
||||
}
|
||||
|
||||
// Routine 5: RoomDraw_DiagonalAcute_1to16
|
||||
for (int id = 0x09; id <= 0x09; id++) {
|
||||
object_to_routine_map_[id] = 5;
|
||||
}
|
||||
|
||||
// Routine 6: RoomDraw_DiagonalGrave_1to16
|
||||
for (int id = 0x0A; id <= 0x0B; id++) {
|
||||
object_to_routine_map_[id] = 6;
|
||||
}
|
||||
|
||||
// Routine 7: RoomDraw_Downwards2x2_1to15or32 (object 0x60)
|
||||
for (int id = 0x60; id <= 0x60; id++) {
|
||||
object_to_routine_map_[id] = 7;
|
||||
}
|
||||
|
||||
// Routine 8: RoomDraw_Downwards4x2_1to15or26 (objects 0x61-0x62)
|
||||
for (int id = 0x61; id <= 0x62; id++) {
|
||||
object_to_routine_map_[id] = 8;
|
||||
}
|
||||
|
||||
// Routine 9: RoomDraw_Downwards4x2_1to16_BothBG (objects 0x63-0x64)
|
||||
for (int id = 0x63; id <= 0x64; id++) {
|
||||
object_to_routine_map_[id] = 9;
|
||||
}
|
||||
|
||||
// Routine 10: RoomDraw_DownwardsDecor4x2spaced4_1to16 (objects 0x65-0x66)
|
||||
for (int id = 0x65; id <= 0x66; id++) {
|
||||
object_to_routine_map_[id] = 10;
|
||||
}
|
||||
|
||||
// Routine 11: RoomDraw_Downwards2x2_1to16 (objects 0x67-0x68)
|
||||
for (int id = 0x67; id <= 0x68; id++) {
|
||||
object_to_routine_map_[id] = 11;
|
||||
}
|
||||
|
||||
// Routine 12: RoomDraw_DownwardsHasEdge1x1_1to16_plus3 (object 0x69)
|
||||
for (int id = 0x69; id <= 0x69; id++) {
|
||||
object_to_routine_map_[id] = 12;
|
||||
}
|
||||
|
||||
// Routine 13: RoomDraw_DownwardsEdge1x1_1to16 (objects 0x6A-0x6B)
|
||||
for (int id = 0x6A; id <= 0x6B; id++) {
|
||||
object_to_routine_map_[id] = 13;
|
||||
}
|
||||
|
||||
// Routine 14: RoomDraw_DownwardsLeftCorners2x1_1to16_plus12 (object 0x6C)
|
||||
for (int id = 0x6C; id <= 0x6C; id++) {
|
||||
object_to_routine_map_[id] = 14;
|
||||
}
|
||||
|
||||
// Routine 15: RoomDraw_DownwardsRightCorners2x1_1to16_plus12 (object 0x6D)
|
||||
for (int id = 0x6D; id <= 0x6D; id++) {
|
||||
object_to_routine_map_[id] = 15;
|
||||
}
|
||||
|
||||
// Continue mapping more object IDs...
|
||||
// (This is a simplified version - the full table has 248 entries)
|
||||
|
||||
// Initialize draw routine function array
|
||||
// This function maps object IDs to their corresponding draw routines.
|
||||
// The mapping is based on ZScream's DungeonObjectData.cs and the game's assembly code.
|
||||
// The order of functions in draw_routines_ MUST match the indices used here.
|
||||
|
||||
object_to_routine_map_.clear();
|
||||
draw_routines_.clear();
|
||||
draw_routines_.reserve(25);
|
||||
|
||||
// Subtype 1 Object Mappings (Horizontal)
|
||||
object_to_routine_map_[0x00] = 0;
|
||||
for (int id = 0x01; id <= 0x02; id++) { object_to_routine_map_[id] = 1; }
|
||||
for (int id = 0x03; id <= 0x04; id++) { object_to_routine_map_[id] = 2; }
|
||||
for (int id = 0x05; id <= 0x06; id++) { object_to_routine_map_[id] = 3; }
|
||||
for (int id = 0x07; id <= 0x08; id++) { object_to_routine_map_[id] = 4; }
|
||||
object_to_routine_map_[0x09] = 5;
|
||||
for (int id = 0x0A; id <= 0x0B; id++) { object_to_routine_map_[id] = 6; }
|
||||
|
||||
// Subtype 1 Object Mappings (Vertical)
|
||||
object_to_routine_map_[0x60] = 7;
|
||||
for (int id = 0x61; id <= 0x62; id++) { object_to_routine_map_[id] = 8; }
|
||||
for (int id = 0x63; id <= 0x64; id++) { object_to_routine_map_[id] = 9; }
|
||||
for (int id = 0x65; id <= 0x66; id++) { object_to_routine_map_[id] = 10; }
|
||||
for (int id = 0x67; id <= 0x68; id++) { object_to_routine_map_[id] = 11; }
|
||||
object_to_routine_map_[0x69] = 12;
|
||||
for (int id = 0x6A; id <= 0x6B; id++) { object_to_routine_map_[id] = 13; }
|
||||
object_to_routine_map_[0x6C] = 14;
|
||||
object_to_routine_map_[0x6D] = 15;
|
||||
|
||||
// Subtype 2 Object Mappings
|
||||
for (int id = 0x100; id <= 0x10F; id++) { object_to_routine_map_[id] = 16; }
|
||||
|
||||
// Additional object mappings from logs
|
||||
object_to_routine_map_[0x33] = 16;
|
||||
object_to_routine_map_[0xC6] = 7; // Vertical draw
|
||||
|
||||
// Initialize draw routine function array in the correct order
|
||||
draw_routines_.reserve(35);
|
||||
|
||||
// Routine 0
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwards2x2_1to15or32(obj, bg, tiles);
|
||||
});
|
||||
// Routine 1
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwards2x4_1to15or26(obj, bg, tiles);
|
||||
});
|
||||
// Routine 2
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwards2x4spaced4_1to16(obj, bg, tiles);
|
||||
});
|
||||
// Routine 3
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwards2x4spaced4_1to16_BothBG(obj, bg, tiles);
|
||||
});
|
||||
// Routine 4
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwards2x2_1to16(obj, bg, tiles);
|
||||
});
|
||||
// Routine 5
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDiagonalAcute_1to16(obj, bg, tiles);
|
||||
});
|
||||
// Routine 6
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDiagonalGrave_1to16(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDiagonalAcute_1to16_BothBG(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDiagonalGrave_1to16_BothBG(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwards1x2_1to16_plus2(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwardsHasEdge1x1_1to16_plus3(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwardsHasEdge1x1_1to16_plus2(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwardsTopCorners1x2_1to16_plus13(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwardsBottomCorners1x2_1to16_plus13(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->CustomDraw(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwards4x4_1to16(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwards1x1Solid_1to16_plus3(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDoorSwitcherer(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwardsDecor4x4spaced2_1to16(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwardsStatue2x3spaced2_1to16(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwardsPillar2x4spaced4_1to16(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwardsDecor4x3spaced4_1to16(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwardsDoubled2x2spaced2_1to16(obj, bg, tiles);
|
||||
});
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwardsDecor2x2spaced12_1to16(obj, bg, tiles);
|
||||
});
|
||||
|
||||
// Add missing Downwards routines
|
||||
// Routine 7
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDownwards2x2_1to15or32(obj, bg, tiles);
|
||||
});
|
||||
// Routine 8
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDownwards4x2_1to15or26(obj, bg, tiles);
|
||||
});
|
||||
// Routine 9
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDownwards4x2_1to16_BothBG(obj, bg, tiles);
|
||||
});
|
||||
// Routine 10
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDownwardsDecor4x2spaced4_1to16(obj, bg, tiles);
|
||||
});
|
||||
// Routine 11
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDownwards2x2_1to16(obj, bg, tiles);
|
||||
});
|
||||
// Routine 12
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDownwardsHasEdge1x1_1to16_plus3(obj, bg, tiles);
|
||||
});
|
||||
// Routine 13
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDownwardsEdge1x1_1to16(obj, bg, tiles);
|
||||
});
|
||||
// Routine 14
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDownwardsLeftCorners2x1_1to16_plus12(obj, bg, tiles);
|
||||
});
|
||||
// Routine 15
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawDownwardsRightCorners2x1_1to16_plus12(obj, bg, tiles);
|
||||
});
|
||||
// Routine 16
|
||||
draw_routines_.push_back([](ObjectDrawer* self, const RoomObject& obj, gfx::BackgroundBuffer& bg, const std::vector<gfx::Tile16>& tiles) {
|
||||
self->DrawRightwards4x4_1to16(obj, bg, tiles);
|
||||
});
|
||||
|
||||
routines_initialized_ = true;
|
||||
}
|
||||
@@ -732,9 +650,28 @@ void ObjectDrawer::DrawDownwards4x2_1to15or26(const RoomObject& obj, gfx::Backgr
|
||||
int size = obj.size_;
|
||||
if (size == 0) size = 26; // Special case
|
||||
|
||||
LOG_DEBUG("ObjectDrawer", "DrawDownwards4x2_1to15or26: obj=%04X tiles=%zu size=%d",
|
||||
obj.id_, tiles.size(), size);
|
||||
|
||||
for (int s = 0; s < size; s++) {
|
||||
if (tiles.size() >= 1) {
|
||||
// Draw 4x2 pattern using 8x8 tiles from the first Tile16
|
||||
if (tiles.size() >= 2) {
|
||||
// Draw 4x2 pattern using 8 tiles from 2 Tile16s (like ZScream)
|
||||
const auto& tile16_0 = tiles[0];
|
||||
const auto& tile16_1 = tiles[1];
|
||||
|
||||
// Top row: tiles 0,1,2,3
|
||||
WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tile16_0.tile0_); // Top-left
|
||||
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tile16_0.tile1_); // Top-right
|
||||
WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2), tile16_0.tile2_); // Top-middle-left
|
||||
WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2), tile16_0.tile3_); // Top-middle-right
|
||||
|
||||
// Bottom row: tiles 4,5,6,7
|
||||
WriteTile8(bg, obj.x_, obj.y_ + (s * 2) + 1, tile16_1.tile0_); // Bottom-left
|
||||
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2) + 1, tile16_1.tile1_); // Bottom-right
|
||||
WriteTile8(bg, obj.x_ + 2, obj.y_ + (s * 2) + 1, tile16_1.tile2_); // Bottom-middle-left
|
||||
WriteTile8(bg, obj.x_ + 3, obj.y_ + (s * 2) + 1, tile16_1.tile3_); // Bottom-middle-right
|
||||
} else if (tiles.size() >= 1) {
|
||||
// Fallback: use only first Tile16
|
||||
const auto& tile16 = tiles[0];
|
||||
WriteTile8(bg, obj.x_, obj.y_ + (s * 2), tile16.tile0_); // Top-left
|
||||
WriteTile8(bg, obj.x_ + 1, obj.y_ + (s * 2), tile16.tile1_); // Top-right
|
||||
@@ -863,19 +800,9 @@ void ObjectDrawer::WriteTile8(gfx::BackgroundBuffer& bg, int tile_x, int tile_y,
|
||||
return; // Bitmap not ready
|
||||
}
|
||||
|
||||
// Get graphics data - prefer room-specific buffer if available
|
||||
const uint8_t* gfx_data = nullptr;
|
||||
|
||||
if (room_gfx_buffer_) {
|
||||
// Use room-specific graphics buffer (current_gfx16_)
|
||||
gfx_data = room_gfx_buffer_;
|
||||
} else if (rom_ && rom_->is_loaded()) {
|
||||
// Fallback to ROM graphics buffer
|
||||
auto rom_gfx = rom_->mutable_graphics_buffer();
|
||||
if (rom_gfx && !rom_gfx->empty()) {
|
||||
gfx_data = rom_gfx->data();
|
||||
}
|
||||
}
|
||||
// The room-specific graphics buffer (current_gfx16_) contains the assembled
|
||||
// tile graphics for the current room. Object tile IDs are relative to this buffer.
|
||||
const uint8_t* gfx_data = room_gfx_buffer_;
|
||||
|
||||
if (!gfx_data) {
|
||||
LOG_DEBUG("ObjectDrawer", "ERROR: No graphics data available");
|
||||
@@ -907,16 +834,8 @@ void ObjectDrawer::DrawTileToBitmap(gfx::Bitmap& bitmap, const gfx::TileInfo& ti
|
||||
int tile_sheet_x = (tile_info.id_ % 16) * 8; // 16 tiles per row
|
||||
int tile_sheet_y = (tile_info.id_ / 16) * 8; // Each row is 16 tiles
|
||||
|
||||
// CRITICAL: Dungeon palettes are 90 colors organized as:
|
||||
// - 10 sub-palettes of 8 colors each (0-79)
|
||||
// - Plus 10 additional colors (80-89)
|
||||
// Each tile uses palette field (0-10) to select which 8-color sub-palette
|
||||
uint8_t palette_id = tile_info.palette_ & 0x0F; // 0-15 possible
|
||||
if (palette_id > 10) palette_id = 10; // Clamp to 0-10 for dungeons
|
||||
|
||||
// Calculate color offset in dungeon palette
|
||||
// For dungeon: palette 0 = colors 0-7, palette 1 = colors 8-15, etc.
|
||||
uint8_t palette_offset = palette_id * 8;
|
||||
// Palettes are 3bpp (8 colors). Convert palette index to base color offset.
|
||||
uint8_t palette_offset = (tile_info.palette_ & 0x0F) * 8;
|
||||
|
||||
// Draw 8x8 pixels
|
||||
for (int py = 0; py < 8; py++) {
|
||||
@@ -928,9 +847,10 @@ void ObjectDrawer::DrawTileToBitmap(gfx::Bitmap& bitmap, const gfx::TileInfo& ti
|
||||
// Read pixel from graphics sheet
|
||||
int src_index = (tile_sheet_y + src_y) * 128 + (tile_sheet_x + src_x);
|
||||
uint8_t pixel_index = tiledata[src_index];
|
||||
|
||||
// Apply palette and write to bitmap
|
||||
uint8_t final_color = pixel_index + palette_offset;
|
||||
if (pixel_index == 0) {
|
||||
continue;
|
||||
}
|
||||
uint8_t final_color = pixel_index + palette_offset;
|
||||
int dest_x = pixel_x + px;
|
||||
int dest_y = pixel_y + py;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,171 +0,0 @@
|
||||
#ifndef YAZE_APP_ZELDA3_DUNGEON_OBJECT_RENDERER_H
|
||||
#define YAZE_APP_ZELDA3_DUNGEON_OBJECT_RENDERER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <mutex>
|
||||
#include <chrono>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/gfx/bitmap.h"
|
||||
#include "app/gfx/snes_palette.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/dungeon/object_parser.h"
|
||||
#include "app/zelda3/dungeon/room_object.h"
|
||||
#include "app/zelda3/dungeon/room_layout.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
|
||||
/**
|
||||
* @brief Unified ObjectRenderer combining all optimizations and enhancements
|
||||
*
|
||||
* This class provides a complete, optimized solution for dungeon object rendering
|
||||
* that combines:
|
||||
* - Direct ROM parsing (50-100x faster than SNES emulation)
|
||||
* - Intelligent graphics sheet caching with LRU eviction
|
||||
* - Batch rendering optimizations
|
||||
* - Memory pool integration
|
||||
* - Thread-safe operations
|
||||
* - Comprehensive error handling and validation
|
||||
* - Real-time performance monitoring
|
||||
* - Support for all three object subtypes (0x00-0xFF, 0x100-0x1FF, 0x200+)
|
||||
*/
|
||||
class ObjectRenderer {
|
||||
public:
|
||||
explicit ObjectRenderer(Rom* rom);
|
||||
~ObjectRenderer();
|
||||
|
||||
// Core rendering methods
|
||||
absl::StatusOr<gfx::Bitmap> RenderObject(const RoomObject& object, const gfx::SnesPalette& palette);
|
||||
absl::StatusOr<gfx::Bitmap> RenderObjects(const std::vector<RoomObject>& objects, const gfx::SnesPalette& palette);
|
||||
absl::StatusOr<gfx::Bitmap> RenderRoom(const Room& room, const gfx::SnesPalette& palette);
|
||||
|
||||
// Performance and memory management
|
||||
void ClearCache();
|
||||
size_t GetMemoryUsage() const;
|
||||
|
||||
// Performance monitoring
|
||||
struct PerformanceStats {
|
||||
size_t cache_hits = 0;
|
||||
size_t cache_misses = 0;
|
||||
size_t tiles_rendered = 0;
|
||||
size_t objects_rendered = 0;
|
||||
std::chrono::milliseconds total_render_time{0};
|
||||
size_t memory_allocations = 0;
|
||||
size_t graphics_sheet_loads = 0;
|
||||
double cache_hit_rate() const {
|
||||
size_t total = cache_hits + cache_misses;
|
||||
return total > 0 ? static_cast<double>(cache_hits) / total : 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
PerformanceStats GetPerformanceStats() const;
|
||||
void ResetPerformanceStats();
|
||||
|
||||
// Configuration
|
||||
void SetROM(Rom* rom);
|
||||
void SetCacheSize(size_t max_cache_size);
|
||||
void EnablePerformanceMonitoring(bool enable);
|
||||
|
||||
// Legacy compatibility methods
|
||||
absl::StatusOr<gfx::Bitmap> RenderObjects(
|
||||
const std::vector<RoomObject>& objects, const gfx::SnesPalette& palette,
|
||||
int width, int height);
|
||||
absl::StatusOr<gfx::Bitmap> RenderObjectWithSize(
|
||||
const RoomObject& object, const gfx::SnesPalette& palette,
|
||||
const ObjectSizeInfo& size_info);
|
||||
absl::StatusOr<gfx::Bitmap> GetObjectPreview(const RoomObject& object,
|
||||
const gfx::SnesPalette& palette);
|
||||
|
||||
private:
|
||||
// Internal components
|
||||
class GraphicsCache;
|
||||
class MemoryPool;
|
||||
class PerformanceMonitor;
|
||||
class ObjectParser;
|
||||
|
||||
struct TileRenderInfo {
|
||||
const gfx::Tile16* tile;
|
||||
int x, y;
|
||||
int sheet_index;
|
||||
};
|
||||
|
||||
// Core rendering pipeline
|
||||
absl::Status ValidateInputs(const RoomObject& object, const gfx::SnesPalette& palette);
|
||||
absl::Status ValidateInputs(const std::vector<RoomObject>& objects, const gfx::SnesPalette& palette);
|
||||
absl::StatusOr<gfx::Bitmap> CreateBitmap(int width, int height);
|
||||
absl::Status RenderTileToBitmap(const gfx::Tile16& tile, gfx::Bitmap& bitmap, int x, int y, const gfx::SnesPalette& palette);
|
||||
absl::Status BatchRenderTiles(const std::vector<TileRenderInfo>& tiles, gfx::Bitmap& bitmap, const gfx::SnesPalette& palette);
|
||||
|
||||
// Tile rendering helpers
|
||||
void Render8x8Tile(gfx::Bitmap& bitmap, gfx::Bitmap* graphics_sheet, const gfx::TileInfo& tile_info, int x, int y, const gfx::SnesPalette& palette);
|
||||
void RenderTilePattern(gfx::Bitmap& bitmap, int x, int y, const gfx::TileInfo& tile_info, const gfx::SnesPalette& palette);
|
||||
|
||||
// Utility functions
|
||||
std::pair<int, int> CalculateOptimalBitmapSize(const std::vector<RoomObject>& objects);
|
||||
bool IsObjectInBounds(const RoomObject& object, int bitmap_width, int bitmap_height);
|
||||
|
||||
// Legacy compatibility methods
|
||||
absl::Status RenderTile(const gfx::Tile16& tile, gfx::Bitmap& bitmap, int x,
|
||||
int y, const gfx::SnesPalette& palette);
|
||||
absl::Status ApplyObjectSize(gfx::Bitmap& bitmap,
|
||||
const ObjectSizeInfo& size_info);
|
||||
|
||||
// Member variables
|
||||
Rom* rom_;
|
||||
std::unique_ptr<GraphicsCache> graphics_cache_;
|
||||
std::unique_ptr<MemoryPool> memory_pool_;
|
||||
std::unique_ptr<PerformanceMonitor> performance_monitor_;
|
||||
std::unique_ptr<ObjectParser> parser_;
|
||||
|
||||
// Configuration
|
||||
size_t max_cache_size_ = 100;
|
||||
bool performance_monitoring_enabled_ = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Factory function to create object renderer
|
||||
*/
|
||||
std::unique_ptr<ObjectRenderer> CreateObjectRenderer(Rom* rom);
|
||||
|
||||
/**
|
||||
* @brief Utility functions for object rendering optimization
|
||||
*/
|
||||
namespace ObjectRenderingUtils {
|
||||
|
||||
/**
|
||||
* @brief Validate object data before rendering
|
||||
*/
|
||||
absl::Status ValidateObjectData(const RoomObject& object, Rom* rom);
|
||||
|
||||
/**
|
||||
* @brief Estimate memory usage for rendering
|
||||
*/
|
||||
size_t EstimateMemoryUsage(const std::vector<RoomObject>& objects, int bitmap_width, int bitmap_height);
|
||||
|
||||
/**
|
||||
* @brief Check if object is within bitmap bounds
|
||||
*/
|
||||
bool IsObjectInBounds(const RoomObject& object, int bitmap_width, int bitmap_height);
|
||||
|
||||
/**
|
||||
* @brief Get object subtype from object ID
|
||||
*/
|
||||
int GetObjectSubtype(int16_t object_id);
|
||||
|
||||
/**
|
||||
* @brief Check if object ID is valid
|
||||
*/
|
||||
bool IsValidObjectID(int16_t object_id);
|
||||
|
||||
} // namespace ObjectRenderingUtils
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
|
||||
#endif // YAZE_APP_ZELDA3_DUNGEON_OBJECT_RENDERER_H
|
||||
@@ -182,9 +182,6 @@ Room LoadRoomFromRom(Rom *rom, int room_id) {
|
||||
int spr_ptr = 0x040000 | rooms_sprite_pointer;
|
||||
int sprite_address = SnesToPc(dungeon_spr_ptrs | spr_ptr + (room_id * 2));
|
||||
|
||||
// Load room layout
|
||||
room.LoadRoomLayout();
|
||||
|
||||
// Load additional room features
|
||||
room.LoadDoors();
|
||||
room.LoadTorches();
|
||||
@@ -284,6 +281,7 @@ void Room::CopyRoomGraphicsToBuffer() {
|
||||
|
||||
void Room::RenderRoomGraphics() {
|
||||
CopyRoomGraphicsToBuffer();
|
||||
LoadLayoutTilesToBuffer();
|
||||
|
||||
// Debug: Log floor graphics values
|
||||
LOG_DEBUG("[RenderRoomGraphics]", "Room %d: floor1=%d, floor2=%d, blocks_size=%zu",
|
||||
@@ -298,13 +296,7 @@ void Room::RenderRoomGraphics() {
|
||||
bg2_buffer_.DrawFloor(rom()->vector(), tile_address,
|
||||
tile_address_floor, floor2_graphics_);
|
||||
|
||||
// STEP 2: Load layout tiles into tile buffer before DrawBackground
|
||||
// This populates the buffer with wall/structure tiles from the layout
|
||||
// DISABLED: Layout tiles are overwriting object graphics
|
||||
// TODO: Research layout data format properly before re-enabling
|
||||
// LoadLayoutTilesToBuffer();
|
||||
|
||||
// STEP 3: Draw background tiles (walls from layout) to buffers
|
||||
// STEP 2: Draw background tiles (walls/structure) to buffers
|
||||
bg1_buffer_.DrawBackground(std::span<uint8_t>(current_gfx16_));
|
||||
bg2_buffer_.DrawBackground(std::span<uint8_t>(current_gfx16_));
|
||||
|
||||
@@ -366,6 +358,48 @@ void Room::RenderRoomGraphics() {
|
||||
LOG_DEBUG("[RenderRoomGraphics]", "Processed texture queue immediately");
|
||||
}
|
||||
|
||||
void Room::LoadLayoutTilesToBuffer() {
|
||||
LOG_DEBUG("RenderRoomGraphics", "LoadLayoutTilesToBuffer START for room %d", room_id_);
|
||||
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
LOG_DEBUG("RenderRoomGraphics", "ROM not loaded, aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& layout_objects = layout_.GetObjects();
|
||||
LOG_DEBUG("RenderRoomGraphics", "Layout has %zu objects", layout_objects.size());
|
||||
if (layout_objects.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int tiles_written_bg1 = 0;
|
||||
int tiles_written_bg2 = 0;
|
||||
int tiles_skipped = 0;
|
||||
|
||||
for (const auto& layout_obj : layout_objects) {
|
||||
uint8_t x = layout_obj.x();
|
||||
uint8_t y = layout_obj.y();
|
||||
|
||||
auto tile_result = layout_obj.GetTile(0);
|
||||
if (!tile_result.ok()) {
|
||||
tiles_skipped++;
|
||||
continue;
|
||||
}
|
||||
const auto* tile16 = tile_result.value();
|
||||
uint16_t tile_word = gfx::TileInfoToWord(tile16->tile0_);
|
||||
|
||||
if (layout_obj.GetLayerValue() == 1) {
|
||||
bg2_buffer_.SetTileAt(x, y, tile_word);
|
||||
tiles_written_bg2++;
|
||||
} else {
|
||||
bg1_buffer_.SetTileAt(x, y, tile_word);
|
||||
tiles_written_bg1++;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG("RenderRoomGraphics", "Layout tiles: BG1=%d BG2=%d skipped=%d", tiles_written_bg1, tiles_written_bg2, tiles_skipped);
|
||||
}
|
||||
|
||||
void Room::RenderObjectsToBackground() {
|
||||
LOG_DEBUG("[RenderObjectsToBackground]", "Starting object rendering");
|
||||
|
||||
@@ -393,7 +427,8 @@ void Room::RenderObjectsToBackground() {
|
||||
}
|
||||
|
||||
auto room_palette = dungeon_pal_group[palette_id];
|
||||
auto palette_group_result = gfx::CreatePaletteGroupFromLargePalette(room_palette);
|
||||
// Dungeon palettes are 16-color sub-palettes. Split the 90-color palette into 16-color groups.
|
||||
auto palette_group_result = gfx::CreatePaletteGroupFromLargePalette(room_palette, 16);
|
||||
if (!palette_group_result.ok()) {
|
||||
// Fallback to empty palette group
|
||||
gfx::PaletteGroup empty_group;
|
||||
@@ -875,85 +910,6 @@ void Room::LoadChests() {
|
||||
}
|
||||
}
|
||||
|
||||
void Room::LoadRoomLayout() {
|
||||
// Use the new RoomLayout system to load walls, floors, and structural elements
|
||||
auto status = layout_.LoadLayout(room_id_);
|
||||
if (!status.ok()) {
|
||||
// Log error but don't fail - some rooms might not have layout data
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the layout ID for compatibility with existing code
|
||||
layout = static_cast<uint8_t>(room_id_ & 0xFF);
|
||||
}
|
||||
|
||||
void Room::LoadLayoutTilesToBuffer() {
|
||||
// Load layout tiles into the BackgroundBuffer tile buffers
|
||||
// This populates the buffer with wall/structure tile IDs from the layout
|
||||
|
||||
LOG_DEBUG("RenderRoomGraphics", "LoadLayoutTilesToBuffer START for room %d", room_id_);
|
||||
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
LOG_DEBUG("RenderRoomGraphics", "ROM not loaded, aborting");
|
||||
return;
|
||||
}
|
||||
|
||||
// Get layout data
|
||||
auto& layout_objects = layout_.GetObjects();
|
||||
LOG_DEBUG("RenderRoomGraphics", "Layout has %zu objects", layout_objects.size());
|
||||
|
||||
if (layout_objects.empty()) {
|
||||
LOG_DEBUG("RenderRoomGraphics", "No layout objects for room %d", room_id_);
|
||||
return;
|
||||
}
|
||||
|
||||
int tiles_written_bg1 = 0;
|
||||
int tiles_written_bg2 = 0;
|
||||
int tiles_skipped = 0;
|
||||
|
||||
// Write each layout object's tile to the appropriate buffer
|
||||
for (const auto& layout_obj : layout_objects) {
|
||||
uint8_t x = layout_obj.x();
|
||||
uint8_t y = layout_obj.y();
|
||||
|
||||
// Get the tile16 for this layout object, passing room graphics buffer
|
||||
auto tile_result = layout_obj.GetTile(current_gfx16_.data());
|
||||
if (!tile_result.ok()) {
|
||||
tiles_skipped++;
|
||||
continue; // Skip objects that don't have valid tiles
|
||||
}
|
||||
|
||||
auto& tile16 = tile_result.value();
|
||||
|
||||
// Convert Tile16 to a 16-bit tile ID word (use first tile)
|
||||
uint16_t tile_word = gfx::TileInfoToWord(tile16.tile0_);
|
||||
|
||||
// Debug first few tiles to verify data
|
||||
if (tiles_written_bg1 + tiles_written_bg2 < 5) {
|
||||
LOG_DEBUG("RenderRoomGraphics", "Layout tile[%d] at (%d,%d): ID=0x%04X, palette=%d, type=%d, layer=%d",
|
||||
tiles_written_bg1 + tiles_written_bg2, x, y, tile16.tile0_.id_, tile16.tile0_.palette_,
|
||||
static_cast<int>(layout_obj.type()), layout_obj.layer());
|
||||
}
|
||||
|
||||
// Determine which buffer based on layer
|
||||
auto* target_buffer = &bg1_buffer_;
|
||||
if (layout_obj.layer() == 1) {
|
||||
target_buffer = &bg2_buffer_;
|
||||
tiles_written_bg2++;
|
||||
} else {
|
||||
tiles_written_bg1++;
|
||||
}
|
||||
|
||||
// Write tile ID to buffer at position (x, y)
|
||||
if (x < 64 && y < 64) { // 64x64 tiles = 512x512 pixels
|
||||
target_buffer->SetTileAt(x, y, tile_word);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_DEBUG("RenderRoomGraphics", "Wrote %d BG1 tiles, %d BG2 tiles, skipped %d",
|
||||
tiles_written_bg1, tiles_written_bg2, tiles_skipped);
|
||||
}
|
||||
|
||||
void Room::LoadDoors() {
|
||||
auto rom_data = rom()->vector();
|
||||
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
#include "app/rom.h"
|
||||
#include "app/gfx/background_buffer.h"
|
||||
#include "app/zelda3/dungeon/dungeon_rom_addresses.h"
|
||||
#include "app/zelda3/dungeon/room_layout.h"
|
||||
#include "app/zelda3/dungeon/room_object.h"
|
||||
#include "app/zelda3/dungeon/room_layout.h"
|
||||
#include "app/zelda3/sprite/sprite.h"
|
||||
|
||||
namespace yaze {
|
||||
@@ -182,7 +182,7 @@ enum TagKey {
|
||||
class Room {
|
||||
public:
|
||||
Room() = default;
|
||||
Room(int room_id, Rom* rom) : room_id_(room_id), rom_(rom), layout_(rom) {}
|
||||
Room(int room_id, Rom* rom) : room_id_(room_id), rom_(rom) {}
|
||||
|
||||
void LoadRoomGraphics(uint8_t entrance_blockset = 0xFF);
|
||||
void CopyRoomGraphicsToBuffer();
|
||||
@@ -193,15 +193,13 @@ class Room {
|
||||
void LoadObjects();
|
||||
void LoadSprites();
|
||||
void LoadChests();
|
||||
void LoadRoomLayout();
|
||||
void LoadLayoutTilesToBuffer(); // NEW: Write layout tiles to BG tile buffers
|
||||
void LoadDoors();
|
||||
void LoadTorches();
|
||||
void LoadBlocks();
|
||||
void LoadPits();
|
||||
void LoadRoomLayout();
|
||||
void LoadLayoutTilesToBuffer();
|
||||
|
||||
const RoomLayout& GetLayout() const { return layout_; }
|
||||
RoomLayout& GetLayout() { return layout_; }
|
||||
|
||||
// Public getters and manipulators for sprites
|
||||
const std::vector<zelda3::Sprite>& GetSprites() const { return sprites_; }
|
||||
@@ -215,6 +213,8 @@ class Room {
|
||||
const std::vector<staircase>& GetStairs() const { return z3_staircases_; }
|
||||
std::vector<staircase>& GetStairs() { return z3_staircases_; }
|
||||
|
||||
const RoomLayout& GetLayout() const { return layout_; }
|
||||
|
||||
|
||||
// Public getters and manipulators for tile objects
|
||||
const std::vector<RoomObject>& GetTileObjects() const {
|
||||
@@ -385,10 +385,9 @@ class Room {
|
||||
std::vector<zelda3::Sprite> sprites_;
|
||||
std::vector<staircase> z3_staircases_;
|
||||
std::vector<chest_data> chests_in_room_;
|
||||
|
||||
// Room layout system for walls, floors, and structural elements
|
||||
RoomLayout layout_;
|
||||
|
||||
|
||||
LayerMergeType layer_merging_;
|
||||
CollisionKey collision_;
|
||||
EffectKey effect_;
|
||||
|
||||
@@ -1,250 +1,75 @@
|
||||
#include "room_layout.h"
|
||||
|
||||
#include "absl/strings/str_format.h"
|
||||
#include "app/zelda3/dungeon/room.h"
|
||||
#include "app/snes.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
namespace yaze::zelda3 {
|
||||
|
||||
absl::StatusOr<gfx::Tile16> RoomLayoutObject::GetTile(const uint8_t* room_gfx_buffer) const {
|
||||
// Map layout code to actual VRAM tile ID
|
||||
// Layout codes (id_) are indices into a layout tilemap
|
||||
// The actual tile graphics are in the room's graphics buffer
|
||||
|
||||
// For dungeon layouts, the tile ID from the layout data directly maps to
|
||||
// a tile in the room's graphics sheets (current_gfx16_)
|
||||
// Layout codes typically range from 0x00 to 0xFF
|
||||
|
||||
// Use the layout code directly as tile ID
|
||||
// The palette will be determined by the tile's position and room palette
|
||||
uint16_t tile_id = static_cast<uint16_t>(id_);
|
||||
|
||||
// Determine palette based on object type
|
||||
uint8_t palette = 0;
|
||||
switch (type_) {
|
||||
case Type::kWall:
|
||||
palette = 2; // Walls typically use palette 2
|
||||
break;
|
||||
case Type::kFloor:
|
||||
palette = 0; // Floors use palette 0
|
||||
break;
|
||||
case Type::kWater:
|
||||
palette = 4; // Water uses palette 4
|
||||
break;
|
||||
case Type::kDoor:
|
||||
palette = 3; // Doors use palette 3
|
||||
break;
|
||||
default:
|
||||
palette = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
gfx::TileInfo tile_info;
|
||||
tile_info.id_ = tile_id;
|
||||
tile_info.palette_ = palette;
|
||||
tile_info.vertical_mirror_ = false;
|
||||
tile_info.horizontal_mirror_ = false;
|
||||
tile_info.over_ = false;
|
||||
namespace {
|
||||
constexpr uint16_t kLayerTerminator = 0xFFFF;
|
||||
|
||||
// Create a Tile16 with the same 8x8 tile in all 4 positions
|
||||
// This makes the layout tile appear as a single repeated pattern
|
||||
return gfx::Tile16(tile_info, tile_info, tile_info, tile_info);
|
||||
uint16_t ReadWord(const Rom* rom, int pc_addr) {
|
||||
const auto& data = rom->data();
|
||||
return static_cast<uint16_t>(data[pc_addr] | (data[pc_addr + 1] << 8));
|
||||
}
|
||||
}
|
||||
|
||||
std::string RoomLayoutObject::GetTypeName() const {
|
||||
switch (type_) {
|
||||
case Type::kWall:
|
||||
return "Wall";
|
||||
case Type::kFloor:
|
||||
return "Floor";
|
||||
case Type::kCeiling:
|
||||
return "Ceiling";
|
||||
case Type::kPit:
|
||||
return "Pit";
|
||||
case Type::kWater:
|
||||
return "Water";
|
||||
case Type::kStairs:
|
||||
return "Stairs";
|
||||
case Type::kDoor:
|
||||
return "Door";
|
||||
case Type::kUnknown:
|
||||
default:
|
||||
return "Unknown";
|
||||
absl::StatusOr<int> RoomLayout::GetLayoutAddress(int layout_id) const {
|
||||
if (!rom_ || !rom_->is_loaded()) {
|
||||
return absl::FailedPreconditionError("ROM not loaded");
|
||||
}
|
||||
|
||||
if (layout_id < 0 || layout_id >= static_cast<int>(kRoomLayoutPointers.size())) {
|
||||
return absl::InvalidArgumentError(absl::StrFormat("Invalid layout id %d", layout_id));
|
||||
}
|
||||
|
||||
uint32_t snes_addr = kRoomLayoutPointers[layout_id];
|
||||
int pc_addr = SnesToPc(static_cast<int>(snes_addr));
|
||||
if (pc_addr < 0 || pc_addr >= static_cast<int>(rom_->size())) {
|
||||
return absl::OutOfRangeError(absl::StrFormat("Layout pointer %d out of range", layout_id));
|
||||
}
|
||||
return pc_addr;
|
||||
}
|
||||
|
||||
absl::Status RoomLayout::LoadLayout(int room_id) {
|
||||
if (rom_ == nullptr) {
|
||||
return absl::InvalidArgumentError("ROM is null");
|
||||
}
|
||||
|
||||
// Validate room ID based on Link to the Past ROM structure
|
||||
if (room_id < 0 || room_id >= NumberOfRooms) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrFormat("Invalid room ID: %d (must be 0-%d)", room_id, NumberOfRooms - 1));
|
||||
}
|
||||
|
||||
auto rom_data = rom_->vector();
|
||||
|
||||
// Load room layout from room_object_layout_pointer
|
||||
// This follows the same pattern as the room object loading
|
||||
int layout_pointer = (rom_data[room_object_layout_pointer + 2] << 16) +
|
||||
(rom_data[room_object_layout_pointer + 1] << 8) +
|
||||
(rom_data[room_object_layout_pointer]);
|
||||
layout_pointer = SnesToPc(layout_pointer);
|
||||
|
||||
// Enhanced bounds checking for layout pointer
|
||||
if (layout_pointer < 0 || layout_pointer >= (int)rom_->size()) {
|
||||
return absl::OutOfRangeError(
|
||||
absl::StrFormat("Layout pointer out of range: %#06x", layout_pointer));
|
||||
}
|
||||
|
||||
// Get the layout address for this room
|
||||
int layout_address = layout_pointer + (room_id * 3);
|
||||
|
||||
// Enhanced bounds checking for layout address
|
||||
if (layout_address < 0 || layout_address + 2 >= (int)rom_->size()) {
|
||||
return absl::OutOfRangeError(
|
||||
absl::StrFormat("Layout address out of range: %#06x", layout_address));
|
||||
}
|
||||
|
||||
// Read the layout data (3 bytes: bank, high, low)
|
||||
uint8_t bank = rom_data[layout_address + 2];
|
||||
uint8_t high = rom_data[layout_address + 1];
|
||||
uint8_t low = rom_data[layout_address];
|
||||
|
||||
// Construct the layout data address with validation
|
||||
int layout_data_address = SnesToPc((bank << 16) | (high << 8) | low);
|
||||
|
||||
if (layout_data_address < 0 || layout_data_address >= (int)rom_->size()) {
|
||||
return absl::OutOfRangeError(absl::StrFormat(
|
||||
"Layout data address out of range: %#06x", layout_data_address));
|
||||
}
|
||||
|
||||
// Read layout data with enhanced error handling
|
||||
return LoadLayoutData(layout_data_address);
|
||||
}
|
||||
|
||||
absl::Status RoomLayout::LoadLayoutData(int layout_data_address) {
|
||||
auto rom_data = rom_->vector();
|
||||
|
||||
// Read layout data - this contains the room's wall/floor structure
|
||||
// The format varies by room type, but typically contains tile IDs for each position
|
||||
std::vector<uint8_t> layout_data;
|
||||
layout_data.reserve(width_ * height_);
|
||||
|
||||
// Read the layout data with comprehensive bounds checking
|
||||
for (int i = 0; i < width_ * height_; ++i) {
|
||||
if (layout_data_address + i < (int)rom_->size()) {
|
||||
layout_data.push_back(rom_data[layout_data_address + i]);
|
||||
} else {
|
||||
// Log warning but continue with default value
|
||||
layout_data.push_back(0); // Default to empty space
|
||||
}
|
||||
}
|
||||
|
||||
return ParseLayoutData(layout_data);
|
||||
}
|
||||
|
||||
absl::Status RoomLayout::ParseLayoutData(const std::vector<uint8_t>& data) {
|
||||
absl::Status RoomLayout::LoadLayout(int layout_id) {
|
||||
objects_.clear();
|
||||
objects_.reserve(width_ * height_);
|
||||
|
||||
// Parse the layout data to create layout objects
|
||||
// This is a simplified implementation - in reality, the format is more
|
||||
// complex
|
||||
for (int y = 0; y < height_; ++y) {
|
||||
for (int x = 0; x < width_; ++x) {
|
||||
int index = y * width_ + x;
|
||||
if (index >= (int)data.size()) continue;
|
||||
auto addr_result = GetLayoutAddress(layout_id);
|
||||
if (!addr_result.ok()) {
|
||||
return addr_result.status();
|
||||
}
|
||||
|
||||
uint8_t tile_id = data[index];
|
||||
int pos = addr_result.value();
|
||||
const auto& rom_data = rom_->data();
|
||||
int layer = 0;
|
||||
|
||||
// Determine object type based on tile ID
|
||||
// NOTE: Layout format needs research - using simplified heuristics
|
||||
RoomLayoutObject::Type type = RoomLayoutObject::Type::kUnknown;
|
||||
|
||||
if (tile_id == 0) {
|
||||
// Empty space - skip
|
||||
continue;
|
||||
while (pos + 2 < static_cast<int>(rom_->size())) {
|
||||
uint8_t b1 = rom_data[pos];
|
||||
uint8_t b2 = rom_data[pos + 1];
|
||||
|
||||
if (b1 == 0xFF && b2 == 0xFF) {
|
||||
layer++;
|
||||
pos += 2;
|
||||
if (layer >= 3) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Just mark everything as unknown for now
|
||||
// The room graphics bitmap handles the actual visual appearance
|
||||
// Layout objects are just for structural information
|
||||
type = RoomLayoutObject::Type::kUnknown;
|
||||
|
||||
// Create layout object
|
||||
objects_.emplace_back(tile_id, x, y, type, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pos + 2 >= static_cast<int>(rom_->size())) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint8_t b3 = rom_data[pos + 2];
|
||||
pos += 3;
|
||||
|
||||
RoomObject obj = RoomObject::DecodeObjectFromBytes(b1, b2, b3, static_cast<uint8_t>(layer));
|
||||
obj.set_rom(rom_);
|
||||
obj.EnsureTilesLoaded();
|
||||
objects_.push_back(obj);
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
RoomLayoutObject RoomLayout::CreateLayoutObject(int16_t tile_id, uint8_t x,
|
||||
uint8_t y, uint8_t layer) {
|
||||
// Determine type based on tile ID
|
||||
RoomLayoutObject::Type type = RoomLayoutObject::Type::kUnknown;
|
||||
if (tile_id >= 0x01 && tile_id <= 0x20) {
|
||||
type = RoomLayoutObject::Type::kWall;
|
||||
} else if (tile_id >= 0x21 && tile_id <= 0x40) {
|
||||
type = RoomLayoutObject::Type::kFloor;
|
||||
} else if (tile_id >= 0x41 && tile_id <= 0x60) {
|
||||
type = RoomLayoutObject::Type::kCeiling;
|
||||
} else if (tile_id >= 0x61 && tile_id <= 0x80) {
|
||||
type = RoomLayoutObject::Type::kWater;
|
||||
} else if (tile_id >= 0x81 && tile_id <= 0xA0) {
|
||||
type = RoomLayoutObject::Type::kStairs;
|
||||
} else if (tile_id >= 0xA1 && tile_id <= 0xC0) {
|
||||
type = RoomLayoutObject::Type::kDoor;
|
||||
}
|
||||
|
||||
return RoomLayoutObject(tile_id, x, y, type, layer);
|
||||
}
|
||||
|
||||
std::vector<RoomLayoutObject> RoomLayout::GetObjectsByType(
|
||||
RoomLayoutObject::Type type) const {
|
||||
std::vector<RoomLayoutObject> result;
|
||||
for (const auto& obj : objects_) {
|
||||
if (obj.type() == type) {
|
||||
result.push_back(obj);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
absl::StatusOr<RoomLayoutObject> RoomLayout::GetObjectAt(uint8_t x, uint8_t y,
|
||||
uint8_t layer) const {
|
||||
for (const auto& obj : objects_) {
|
||||
if (obj.x() == x && obj.y() == y && obj.layer() == layer) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
return absl::NotFoundError(
|
||||
absl::StrFormat("No object found at position (%d, %d, %d)", x, y, layer));
|
||||
}
|
||||
|
||||
bool RoomLayout::HasWall(uint8_t x, uint8_t y, uint8_t layer) const {
|
||||
for (const auto& obj : objects_) {
|
||||
if (obj.x() == x && obj.y() == y && obj.layer() == layer &&
|
||||
obj.type() == RoomLayoutObject::Type::kWall) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RoomLayout::HasFloor(uint8_t x, uint8_t y, uint8_t layer) const {
|
||||
for (const auto& obj : objects_) {
|
||||
if (obj.x() == x && obj.y() == y && obj.layer() == layer &&
|
||||
obj.type() == RoomLayoutObject::Type::kFloor) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
} // namespace yaze::zelda3
|
||||
|
||||
@@ -1,121 +1,33 @@
|
||||
#ifndef YAZE_APP_ZELDA3_DUNGEON_ROOM_LAYOUT_H
|
||||
#define YAZE_APP_ZELDA3_DUNGEON_ROOM_LAYOUT_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "app/gfx/snes_tile.h"
|
||||
#include "app/rom.h"
|
||||
#include "app/zelda3/dungeon/room_object.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace zelda3 {
|
||||
namespace yaze::zelda3 {
|
||||
|
||||
/**
|
||||
* @brief Represents a room layout object (wall, floor, etc.)
|
||||
*
|
||||
* Room layout objects are the basic building blocks of dungeon rooms.
|
||||
* They include walls, floors, ceilings, and other structural elements.
|
||||
* Unlike regular room objects, these are loaded from the room layout data
|
||||
* and represent the fundamental geometry of the room.
|
||||
*/
|
||||
class RoomLayoutObject {
|
||||
public:
|
||||
enum class Type {
|
||||
kWall = 0,
|
||||
kFloor = 1,
|
||||
kCeiling = 2,
|
||||
kPit = 3,
|
||||
kWater = 4,
|
||||
kStairs = 5,
|
||||
kDoor = 6,
|
||||
kUnknown = 7
|
||||
};
|
||||
|
||||
RoomLayoutObject(int16_t id, uint8_t x, uint8_t y, Type type, uint8_t layer = 0)
|
||||
: id_(id), x_(x), y_(y), type_(type), layer_(layer) {}
|
||||
|
||||
// Getters
|
||||
int16_t id() const { return id_; }
|
||||
uint8_t x() const { return x_; }
|
||||
uint8_t y() const { return y_; }
|
||||
Type type() const { return type_; }
|
||||
uint8_t layer() const { return layer_; }
|
||||
|
||||
// Setters
|
||||
void set_id(int16_t id) { id_ = id; }
|
||||
void set_x(uint8_t x) { x_ = x; }
|
||||
void set_y(uint8_t y) { y_ = y; }
|
||||
void set_type(Type type) { type_ = type; }
|
||||
void set_layer(uint8_t layer) { layer_ = layer; }
|
||||
|
||||
// Get tile data for this layout object
|
||||
// NOTE: Layout codes need to be mapped to actual VRAM tile IDs
|
||||
// The room_gfx_buffer provides the assembled graphics for this specific room
|
||||
absl::StatusOr<gfx::Tile16> GetTile(const uint8_t* room_gfx_buffer = nullptr) const;
|
||||
|
||||
// Get the name/description of this layout object type
|
||||
std::string GetTypeName() const;
|
||||
|
||||
private:
|
||||
int16_t id_;
|
||||
uint8_t x_;
|
||||
uint8_t y_;
|
||||
Type type_;
|
||||
uint8_t layer_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Manages room layout data and objects
|
||||
*
|
||||
* This class handles loading and managing room layout objects from ROM data.
|
||||
* It provides efficient access to wall, floor, and other layout elements
|
||||
* without copying large amounts of data.
|
||||
*/
|
||||
class RoomLayout {
|
||||
public:
|
||||
RoomLayout() = default;
|
||||
explicit RoomLayout(Rom* rom) : rom_(rom) {}
|
||||
|
||||
// Load layout data from ROM for a specific room
|
||||
absl::Status LoadLayout(int room_id);
|
||||
|
||||
// Load layout data from a specific address
|
||||
absl::Status LoadLayoutData(int layout_data_address);
|
||||
|
||||
// Get all layout objects of a specific type
|
||||
std::vector<RoomLayoutObject> GetObjectsByType(RoomLayoutObject::Type type) const;
|
||||
|
||||
// Get layout object at specific coordinates
|
||||
absl::StatusOr<RoomLayoutObject> GetObjectAt(uint8_t x, uint8_t y, uint8_t layer = 0) const;
|
||||
|
||||
// Get all layout objects
|
||||
const std::vector<RoomLayoutObject>& GetObjects() const { return objects_; }
|
||||
|
||||
// Check if a position has a wall
|
||||
bool HasWall(uint8_t x, uint8_t y, uint8_t layer = 0) const;
|
||||
|
||||
// Check if a position has a floor
|
||||
bool HasFloor(uint8_t x, uint8_t y, uint8_t layer = 0) const;
|
||||
|
||||
// Get room dimensions
|
||||
std::pair<uint8_t, uint8_t> GetDimensions() const { return {width_, height_}; }
|
||||
void set_rom(Rom* rom) { rom_ = rom; }
|
||||
|
||||
absl::Status LoadLayout(int layout_id);
|
||||
|
||||
const std::vector<RoomObject>& GetObjects() const { return objects_; }
|
||||
|
||||
private:
|
||||
absl::StatusOr<int> GetLayoutAddress(int layout_id) const;
|
||||
|
||||
Rom* rom_ = nullptr;
|
||||
std::vector<RoomLayoutObject> objects_;
|
||||
uint8_t width_ = 64; // Dungeon room width in tiles (512 pixels / 8)
|
||||
uint8_t height_ = 64; // Dungeon room height in tiles (512 pixels / 8)
|
||||
|
||||
// Parse layout data from ROM
|
||||
absl::Status ParseLayoutData(const std::vector<uint8_t>& data);
|
||||
|
||||
// Create layout object from tile data
|
||||
RoomLayoutObject CreateLayoutObject(int16_t tile_id, uint8_t x, uint8_t y, uint8_t layer);
|
||||
std::vector<RoomObject> objects_;
|
||||
};
|
||||
|
||||
} // namespace zelda3
|
||||
} // namespace yaze
|
||||
} // namespace yaze::zelda3
|
||||
|
||||
#endif // YAZE_APP_ZELDA3_DUNGEON_ROOM_LAYOUT_H
|
||||
|
||||
@@ -155,20 +155,17 @@ int RoomObject::GetTileCount() const {
|
||||
// Object Encoding/Decoding Implementation (Phase 1, Task 1.1)
|
||||
// ============================================================================
|
||||
|
||||
int RoomObject::DetermineObjectType(uint8_t b1, uint8_t b3) {
|
||||
int RoomObject::DetermineObjectType(uint8_t /* b1 */, uint8_t b3) {
|
||||
// Type 3: Objects with ID >= 0xF00
|
||||
// These have b3 >= 0xF8 (top nibble is 0xF)
|
||||
if (b3 >= 0xF8) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
// Type 2: Objects with ID >= 0x100
|
||||
// These have b1 >= 0xFC (marker for Type2 encoding)
|
||||
if (b1 >= 0xFC) {
|
||||
return 2;
|
||||
}
|
||||
// Type 1: Standard objects (ID 0x00-0xFF) - check this first
|
||||
// Type 2: Objects with ID >= 0x100 (these have b1 >= 0xFC)
|
||||
// We'll handle Type 2 in the decoding logic after Type 1
|
||||
|
||||
// Type 1: Standard objects (ID 0x00-0xFF)
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -179,39 +176,36 @@ RoomObject RoomObject::DecodeObjectFromBytes(uint8_t b1, uint8_t b2, uint8_t b3,
|
||||
uint8_t size = 0;
|
||||
uint16_t id = 0;
|
||||
|
||||
int type = DetermineObjectType(b1, b3);
|
||||
|
||||
switch (type) {
|
||||
case 1: // Type1: xxxxxxss yyyyyyss iiiiiiii
|
||||
x = (b1 & 0xFC) >> 2;
|
||||
y = (b2 & 0xFC) >> 2;
|
||||
size = ((b1 & 0x03) << 2) | (b2 & 0x03);
|
||||
id = b3;
|
||||
break;
|
||||
|
||||
case 2: // Type2: 111111xx xxxxyyyy yyiiiiii
|
||||
x = ((b1 & 0x03) << 4) | ((b2 & 0xF0) >> 4);
|
||||
// Follow ZScream's parsing logic exactly
|
||||
if (b3 >= 0xF8) {
|
||||
// Type 3: xxxxxxii yyyyyyii 11111iii
|
||||
// ZScream: oid = (ushort)((b3 << 4) | 0x80 + (((b2 & 0x03) << 2) + ((b1 & 0x03))));
|
||||
id = (static_cast<uint16_t>(b3) << 4) | 0x80 |
|
||||
((static_cast<uint16_t>(b2 & 0x03) << 2) + (b1 & 0x03));
|
||||
x = (b1 & 0xFC) >> 2;
|
||||
y = (b2 & 0xFC) >> 2;
|
||||
size = ((b1 & 0x03) << 2) | (b2 & 0x03);
|
||||
LOG_DEBUG("ObjectParser", "Type3: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d",
|
||||
b1, b2, b3, id, x, y, size);
|
||||
} else {
|
||||
// Type 1: xxxxxxss yyyyyyss iiiiiiii
|
||||
id = b3;
|
||||
x = (b1 & 0xFC) >> 2;
|
||||
y = (b2 & 0xFC) >> 2;
|
||||
size = ((b1 & 0x03) << 2) | (b2 & 0x03);
|
||||
|
||||
// Check for Type 2 override: 111111xx xxxxyyyy yyiiiiii
|
||||
if (b1 >= 0xFC) {
|
||||
id = (b3 & 0x3F) | 0x100;
|
||||
x = ((b2 & 0xF0) >> 4) | ((b1 & 0x03) << 4);
|
||||
y = ((b2 & 0x0F) << 2) | ((b3 & 0xC0) >> 6);
|
||||
size = 0;
|
||||
id = (b3 & 0x3F) | 0x100;
|
||||
break;
|
||||
|
||||
case 3: // Type3: xxxxxxii yyyyyyii 11111iii
|
||||
x = (b1 & 0xFC) >> 2;
|
||||
y = (b2 & 0xFC) >> 2;
|
||||
size = 0; // Type 3 has no size parameter in this encoding
|
||||
id = (static_cast<uint16_t>(b3) << 4) |
|
||||
((static_cast<uint16_t>(b2 & 0x03)) << 2) |
|
||||
(static_cast<uint16_t>(b1 & 0x03));
|
||||
// The above is a direct reversal of the encoding logic.
|
||||
// However, ZScream uses a slightly different formula which seems to be the source of truth.
|
||||
// ZScream: id = ((b3 << 4) & 0xF00) | ((b2 & 0x03) << 2) | (b1 & 0x03) | 0x80;
|
||||
// Let's use the ZScream one as it's the reference.
|
||||
id = (static_cast<uint16_t>(b3 & 0x0F) << 8) |
|
||||
((static_cast<uint16_t>(b2 & 0x03)) << 6) |
|
||||
((static_cast<uint16_t>(b1 & 0x03)) << 4) |
|
||||
(static_cast<uint16_t>(b3 >> 4));
|
||||
break;
|
||||
LOG_DEBUG("ObjectParser", "Type2: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d",
|
||||
b1, b2, b3, id, x, y, size);
|
||||
} else {
|
||||
LOG_DEBUG("ObjectParser", "Type1: b1=%02X b2=%02X b3=%02X -> id=%04X x=%d y=%d size=%d",
|
||||
b1, b2, b3, id, x, y, size);
|
||||
}
|
||||
}
|
||||
|
||||
return RoomObject(static_cast<int16_t>(id), x, y, size, layer);
|
||||
|
||||
@@ -13,9 +13,7 @@ set(
|
||||
app/zelda3/dungeon/room.cc
|
||||
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/dungeon_editor_system.cc
|
||||
app/zelda3/dungeon/dungeon_object_editor.cc
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user