Add object_renderer.cc
This commit is contained in:
161
src/app/zelda3/dungeon/object_renderer.cc
Normal file
161
src/app/zelda3/dungeon/object_renderer.cc
Normal file
@@ -0,0 +1,161 @@
|
||||
#include "app/zelda3/dungeon/object_renderer.h"
|
||||
|
||||
namespace yaze {
|
||||
namespace app {
|
||||
namespace zelda3 {
|
||||
namespace dungeon {
|
||||
|
||||
void DungeonObjectRenderer::LoadObject(uint16_t objectId,
|
||||
std::array<uint8_t, 16>& sheet_ids) {
|
||||
vram_.sheets = sheet_ids;
|
||||
|
||||
rom_data_ = rom()->vector();
|
||||
// Prepare the CPU and memory environment
|
||||
memory_.Initialize(rom_data_);
|
||||
|
||||
// Fetch the subtype pointers for the given object ID
|
||||
auto subtypeInfo = FetchSubtypeInfo(objectId);
|
||||
|
||||
// Configure the object based on the fetched information
|
||||
ConfigureObject(subtypeInfo);
|
||||
|
||||
// Run the CPU emulation for the object's draw routines
|
||||
RenderObject(subtypeInfo);
|
||||
}
|
||||
|
||||
SubtypeInfo DungeonObjectRenderer::FetchSubtypeInfo(uint16_t object_id) {
|
||||
SubtypeInfo info;
|
||||
|
||||
// Determine the subtype based on objectId
|
||||
uint8_t subtype = 1;
|
||||
|
||||
// Based on the subtype, fetch the correct pointers
|
||||
switch (subtype) {
|
||||
case 1: // Subtype 1
|
||||
info.subtype_ptr = core::subtype1_tiles + (object_id & 0xFF) * 2;
|
||||
info.routine_ptr = core::subtype1_tiles + 0x200 + (object_id & 0xFF) * 2;
|
||||
std::cout << "Subtype 1 " << std::hex << info.subtype_ptr << std::endl;
|
||||
std::cout << "Subtype 1 " << std::hex << info.routine_ptr << std::endl;
|
||||
break;
|
||||
case 2: // Subtype 2
|
||||
info.subtype_ptr = core::subtype2_tiles + (object_id & 0x7F) * 2;
|
||||
info.routine_ptr = core::subtype2_tiles + 0x80 + (object_id & 0x7F) * 2;
|
||||
break;
|
||||
case 3: // Subtype 3
|
||||
info.subtype_ptr = core::subtype3_tiles + (object_id & 0xFF) * 2;
|
||||
info.routine_ptr = core::subtype3_tiles + 0x100 + (object_id & 0xFF) * 2;
|
||||
break;
|
||||
default:
|
||||
// Handle unknown subtype
|
||||
throw std::runtime_error("Unknown subtype for object ID: " +
|
||||
std::to_string(object_id));
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void DungeonObjectRenderer::ConfigureObject(const SubtypeInfo& info) {
|
||||
cpu.A = 0x03D8;
|
||||
cpu.X = 0x03D8;
|
||||
cpu.DB = 0x7E;
|
||||
// VRAM target destinations
|
||||
cpu.WriteLong(0xBF, 0x7E2000);
|
||||
cpu.WriteLong(0xCB, 0x7E2080);
|
||||
cpu.WriteLong(0xC2, 0x7E2002);
|
||||
cpu.WriteLong(0xCE, 0x7E2082);
|
||||
cpu.SetAccumulatorSize(false);
|
||||
cpu.SetIndexSize(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example:
|
||||
* the STA $BF, $CD, $C2, $CE are the location of the object in the room
|
||||
* $B2 is used for size loop
|
||||
* so if object size is setted on 07 that draw code will be repeated 7 times
|
||||
* and since Y is increasing by 4 it makes the object draw from left to right
|
||||
|
||||
RoomDraw_Rightwards2x2_1to15or32:
|
||||
#_018B89: JSR RoomDraw_GetSize_1to15or32
|
||||
.next
|
||||
#_018B8C: JSR RoomDraw_Rightwards2x2
|
||||
#_018B8F: DEC.b $B2
|
||||
#_018B91: BNE .next
|
||||
#_018B93: RTS
|
||||
|
||||
RoomDraw_Rightwards2x2:
|
||||
#_019895: LDA.w RoomDrawObjectData+0,X
|
||||
#_019898: STA.b [$BF],Y
|
||||
#_01989A: LDA.w RoomDrawObjectData+2,X
|
||||
#_01989D: STA.b [$CB],Y
|
||||
#_01989F: LDA.w RoomDrawObjectData+4,X
|
||||
#_0198A2: STA.b [$C2],Y
|
||||
#_0198A4: LDA.w RoomDrawObjectData+6,X
|
||||
#_0198A7: STA.b [$CE],Y
|
||||
#_0198A9: INY #4
|
||||
#_0198AD: RTS
|
||||
*/
|
||||
void DungeonObjectRenderer::RenderObject(const SubtypeInfo& info) {
|
||||
cpu.PB = 0x01;
|
||||
cpu.PC = cpu.ReadWord(0x01 << 16 | info.routine_ptr);
|
||||
|
||||
// Push an initial value to the stack we can read later to confirm we are
|
||||
// done
|
||||
cpu.PushLong(0x01 << 16 | info.routine_ptr);
|
||||
|
||||
int i = 0;
|
||||
while (true) {
|
||||
uint8_t opcode = cpu.ReadByte(cpu.PB << 16 | cpu.PC);
|
||||
cpu.ExecuteInstruction(opcode);
|
||||
cpu.HandleInterrupts();
|
||||
|
||||
if ((i != 0 &&
|
||||
(cpu.ReadWord((0x00 << 16 | cpu.SP() + 2)) == info.routine_ptr) ||
|
||||
0x8b93 == cpu.PC)) {
|
||||
std::cout << std::hex << cpu.ReadWord((0x00 << 16 | cpu.SP() + 3))
|
||||
<< std::endl;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
UpdateObjectBitmap();
|
||||
}
|
||||
|
||||
// In the underworld, this holds a copy of the entire BG tilemap for
|
||||
// Layer 1 (BG2) in TILEMAPA
|
||||
// Layer 2 (BG1) in TILEMAPB
|
||||
void DungeonObjectRenderer::UpdateObjectBitmap() {
|
||||
tilemap_.reserve(0x2000);
|
||||
for (int i = 0; i < 0x2000; ++i) {
|
||||
tilemap_.push_back(0);
|
||||
}
|
||||
int tilemap_offset = 0;
|
||||
|
||||
// Iterate over tilemap in memory to read tile IDs
|
||||
for (int tile_index = 0; tile_index < 512; tile_index++) {
|
||||
// Read the tile ID from memory
|
||||
int tile_id = memory_.ReadWord(0x7E2000 + tile_index);
|
||||
std::cout << "Tile ID: " << std::hex << tile_id << std::endl;
|
||||
|
||||
int sheet_number = tile_id / 32;
|
||||
std::cout << "Sheet number: " << std::hex << sheet_number << std::endl;
|
||||
|
||||
int row = tile_id / 8;
|
||||
int column = tile_id % 8;
|
||||
|
||||
int x = column * 8;
|
||||
int y = row * 8;
|
||||
|
||||
auto sheet = rom()->mutable_graphics_sheet(vram_.sheets[sheet_number]);
|
||||
|
||||
// Copy the tile from VRAM using the read tile_id
|
||||
sheet->Get8x8Tile(tile_id, x, y, tilemap_, tilemap_offset);
|
||||
}
|
||||
|
||||
bitmap_.Create(256, 256, 8, tilemap_);
|
||||
}
|
||||
|
||||
} // namespace dungeon
|
||||
} // namespace zelda3
|
||||
} // namespace app
|
||||
} // namespace yaze
|
||||
@@ -18,176 +18,30 @@ namespace app {
|
||||
namespace zelda3 {
|
||||
namespace dungeon {
|
||||
|
||||
struct PseudoVram {
|
||||
std::array<uint8_t, 16> sheets;
|
||||
std::vector<gfx::SnesPalette> palettes;
|
||||
};
|
||||
|
||||
struct SubtypeInfo {
|
||||
uint32_t subtype_ptr;
|
||||
uint32_t routine_ptr;
|
||||
};
|
||||
|
||||
class DungeonObjectRenderer : public SharedROM {
|
||||
public:
|
||||
struct PseudoVram {
|
||||
std::array<uint8_t, 16> sheets;
|
||||
std::vector<gfx::SnesPalette> palettes;
|
||||
};
|
||||
|
||||
DungeonObjectRenderer() = default;
|
||||
|
||||
void LoadObject(uint16_t objectId, std::array<uint8_t, 16>& sheet_ids) {
|
||||
vram_.sheets = sheet_ids;
|
||||
|
||||
rom_data_ = rom()->vector();
|
||||
// Prepare the CPU and memory environment
|
||||
memory_.Initialize(rom_data_);
|
||||
|
||||
// Fetch the subtype pointers for the given object ID
|
||||
auto subtypeInfo = FetchSubtypeInfo(objectId);
|
||||
|
||||
// Configure the object based on the fetched information
|
||||
ConfigureObject(subtypeInfo);
|
||||
|
||||
// Run the CPU emulation for the object's draw routines
|
||||
RenderObject(subtypeInfo);
|
||||
}
|
||||
|
||||
void LoadObject(uint16_t objectId, std::array<uint8_t, 16>& sheet_ids);
|
||||
gfx::Bitmap* bitmap() { return &bitmap_; }
|
||||
auto memory() { return memory_; }
|
||||
auto mutable_memory() { return &memory_; }
|
||||
|
||||
private:
|
||||
struct SubtypeInfo {
|
||||
uint32_t subtype_ptr;
|
||||
uint32_t routine_ptr;
|
||||
};
|
||||
|
||||
SubtypeInfo FetchSubtypeInfo(uint16_t object_id) {
|
||||
SubtypeInfo info;
|
||||
|
||||
// Determine the subtype based on objectId
|
||||
uint8_t subtype = 1;
|
||||
|
||||
// Based on the subtype, fetch the correct pointers
|
||||
switch (subtype) {
|
||||
case 1: // Subtype 1
|
||||
info.subtype_ptr = core::subtype1_tiles + (object_id & 0xFF) * 2;
|
||||
info.routine_ptr =
|
||||
core::subtype1_tiles + 0x200 + (object_id & 0xFF) * 2;
|
||||
std::cout << "Subtype 1 " << std::hex << info.subtype_ptr << std::endl;
|
||||
std::cout << "Subtype 1 " << std::hex << info.routine_ptr << std::endl;
|
||||
break;
|
||||
case 2: // Subtype 2
|
||||
info.subtype_ptr = core::subtype2_tiles + (object_id & 0x7F) * 2;
|
||||
info.routine_ptr = core::subtype2_tiles + 0x80 + (object_id & 0x7F) * 2;
|
||||
break;
|
||||
case 3: // Subtype 3
|
||||
info.subtype_ptr = core::subtype3_tiles + (object_id & 0xFF) * 2;
|
||||
info.routine_ptr =
|
||||
core::subtype3_tiles + 0x100 + (object_id & 0xFF) * 2;
|
||||
break;
|
||||
default:
|
||||
// Handle unknown subtype
|
||||
throw std::runtime_error("Unknown subtype for object ID: " +
|
||||
std::to_string(object_id));
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void ConfigureObject(const SubtypeInfo& info) {
|
||||
cpu.A = 0x03D8;
|
||||
cpu.X = 0x03D8;
|
||||
cpu.DB = 0x7E;
|
||||
// VRAM target destinations
|
||||
cpu.WriteLong(0xBF, 0x7E2000);
|
||||
cpu.WriteLong(0xCB, 0x7E2080);
|
||||
cpu.WriteLong(0xC2, 0x7E2002);
|
||||
cpu.WriteLong(0xCE, 0x7E2082);
|
||||
cpu.SetAccumulatorSize(false);
|
||||
cpu.SetIndexSize(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Example:
|
||||
* the STA $BF, $CD, $C2, $CE are the location of the object in the room
|
||||
* $B2 is used for size loop
|
||||
* so if object size is setted on 07 that draw code will be repeated 7 times
|
||||
* and since Y is increasing by 4 it makes the object draw from left to right
|
||||
|
||||
RoomDraw_Rightwards2x2_1to15or32:
|
||||
#_018B89: JSR RoomDraw_GetSize_1to15or32
|
||||
.next
|
||||
#_018B8C: JSR RoomDraw_Rightwards2x2
|
||||
#_018B8F: DEC.b $B2
|
||||
#_018B91: BNE .next
|
||||
#_018B93: RTS
|
||||
|
||||
RoomDraw_Rightwards2x2:
|
||||
#_019895: LDA.w RoomDrawObjectData+0,X
|
||||
#_019898: STA.b [$BF],Y
|
||||
#_01989A: LDA.w RoomDrawObjectData+2,X
|
||||
#_01989D: STA.b [$CB],Y
|
||||
#_01989F: LDA.w RoomDrawObjectData+4,X
|
||||
#_0198A2: STA.b [$C2],Y
|
||||
#_0198A4: LDA.w RoomDrawObjectData+6,X
|
||||
#_0198A7: STA.b [$CE],Y
|
||||
#_0198A9: INY #4
|
||||
#_0198AD: RTS
|
||||
*/
|
||||
|
||||
void RenderObject(const SubtypeInfo& info) {
|
||||
cpu.PB = 0x01;
|
||||
cpu.PC = cpu.ReadWord(0x01 << 16 | info.routine_ptr);
|
||||
|
||||
// Push an initial value to the stack we can read later to confirm we are
|
||||
// done
|
||||
cpu.PushLong(0x01 << 16 | info.routine_ptr);
|
||||
|
||||
int i = 0;
|
||||
while (true) {
|
||||
uint8_t opcode = cpu.ReadByte(cpu.PB << 16 | cpu.PC);
|
||||
cpu.ExecuteInstruction(opcode);
|
||||
cpu.HandleInterrupts();
|
||||
|
||||
if ((i != 0 && (cpu.ReadWord((0x00 << 16 | cpu.SP() + 2)) ==
|
||||
info.routine_ptr) ||
|
||||
0x8b93 == cpu.PC)) {
|
||||
std::cout << std::hex << cpu.ReadWord((0x00 << 16 | cpu.SP() + 3))
|
||||
<< std::endl;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
UpdateObjectBitmap();
|
||||
}
|
||||
|
||||
// In the underworld, this holds a copy of the entire BG tilemap for
|
||||
// Layer 1 (BG2) in TILEMAPA
|
||||
// Layer 2 (BG1) in TILEMAPB
|
||||
void UpdateObjectBitmap() {
|
||||
tilemap_.reserve(0x2000);
|
||||
for (int i = 0; i < 0x2000; ++i) {
|
||||
tilemap_.push_back(0);
|
||||
}
|
||||
int tilemap_offset = 0;
|
||||
|
||||
// Iterate over tilemap in memory to read tile IDs
|
||||
for (int tile_index = 0; tile_index < 512; tile_index++) {
|
||||
// Read the tile ID from memory
|
||||
int tile_id = memory_.ReadWord(0x7E2000 + tile_index);
|
||||
std::cout << "Tile ID: " << std::hex << tile_id << std::endl;
|
||||
|
||||
int sheet_number = tile_id / 32;
|
||||
std::cout << "Sheet number: " << std::hex << sheet_number << std::endl;
|
||||
|
||||
int row = tile_id / 8;
|
||||
int column = tile_id % 8;
|
||||
|
||||
int x = column * 8;
|
||||
int y = row * 8;
|
||||
|
||||
auto sheet = rom()->mutable_graphics_sheet(vram_.sheets[sheet_number]);
|
||||
|
||||
// Copy the tile from VRAM using the read tile_id
|
||||
sheet->Get8x8Tile(tile_id, x, y, tilemap_, tilemap_offset);
|
||||
}
|
||||
|
||||
bitmap_.Create(256, 256, 8, tilemap_);
|
||||
}
|
||||
SubtypeInfo FetchSubtypeInfo(uint16_t object_id);
|
||||
void ConfigureObject(const SubtypeInfo& info);
|
||||
void RenderObject(const SubtypeInfo& info);
|
||||
void UpdateObjectBitmap();
|
||||
|
||||
std::vector<uint8_t> tilemap_;
|
||||
uint16_t pc_with_rts_;
|
||||
|
||||
Reference in New Issue
Block a user