Files
yaze/test/integration/zelda3/dungeon_object_rendering_tests.cc

269 lines
8.0 KiB
C++

// Integration tests for dungeon object rendering using ObjectDrawer
// Updated for DungeonEditorV2 architecture - uses ObjectDrawer (production
// system) instead of the obsolete ObjectRenderer
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include <gtest/gtest.h>
#include <chrono>
#include <memory>
#include <vector>
#include "app/gfx/render/background_buffer.h"
#include "app/gfx/types/snes_palette.h"
#include "rom/rom.h"
#include "test_utils.h"
#include "testing.h"
#include "zelda3/dungeon/object_drawer.h"
#include "zelda3/dungeon/room.h"
#include "zelda3/dungeon/room_object.h"
namespace yaze {
namespace test {
/**
* @brief Tests for ObjectDrawer with realistic dungeon scenarios
*
* These tests validate that ObjectDrawer correctly renders dungeon objects
* to BackgroundBuffers using pattern-based drawing routines.
*/
class DungeonObjectRenderingTests : public TestRomManager::BoundRomTest {
protected:
void SetUp() override {
BoundRomTest::SetUp();
// Create dummy graphics buffer
gfx_buffer_.resize(0x10000, 1); // Fill with 1s so we see something
drawer_ = std::make_unique<zelda3::ObjectDrawer>(rom(), 0, gfx_buffer_.data());
// Create background buffers
bg1_ = std::make_unique<gfx::BackgroundBuffer>(512, 512);
bg2_ = std::make_unique<gfx::BackgroundBuffer>(512, 512);
// Initialize bitmaps
std::vector<uint8_t> empty_data(512 * 512, 0);
bg1_->bitmap().Create(512, 512, 8, empty_data);
bg2_->bitmap().Create(512, 512, 8, empty_data);
// Setup test palette
palette_group_ = CreateTestPaletteGroup();
}
void TearDown() override {
bg2_.reset();
bg1_.reset();
drawer_.reset();
BoundRomTest::TearDown();
}
gfx::PaletteGroup CreateTestPaletteGroup() {
gfx::PaletteGroup group;
gfx::SnesPalette palette;
// Create standard dungeon palette
for (int i = 0; i < 16; i++) {
int intensity = i * 16;
palette.AddColor(gfx::SnesColor(intensity, intensity, intensity));
}
group.AddPalette(palette);
return group;
}
zelda3::RoomObject CreateTestObject(int id, int x, int y, int size = 0x12,
int layer = 0) {
zelda3::RoomObject obj(id, x, y, size, layer);
obj.SetRom(rom());
obj.EnsureTilesLoaded();
// Force add a tile if none loaded (for testing without real ROM data)
if (obj.tiles().empty()) {
gfx::TileInfo tile;
tile.id_ = 0;
tile.palette_ = 0;
obj.mutable_tiles().push_back(tile);
}
return obj;
}
std::unique_ptr<zelda3::ObjectDrawer> drawer_;
std::unique_ptr<gfx::BackgroundBuffer> bg1_;
std::unique_ptr<gfx::BackgroundBuffer> bg2_;
gfx::PaletteGroup palette_group_;
std::vector<uint8_t> gfx_buffer_;
};
// Test basic object drawing
TEST_F(DungeonObjectRenderingTests, BasicObjectDrawing) {
std::vector<zelda3::RoomObject> objects;
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // Wall
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 0)); // Floor
bg1_->ClearBuffer();
bg2_->ClearBuffer();
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
ASSERT_TRUE(status.ok()) << "Drawing failed: " << status.message();
// Verify buffers have content
auto& bg1_bitmap = bg1_->bitmap();
EXPECT_TRUE(bg1_bitmap.is_active());
EXPECT_GT(bg1_bitmap.width(), 0);
}
// Test objects on different layers
TEST_F(DungeonObjectRenderingTests, MultiLayerRendering) {
std::vector<zelda3::RoomObject> objects;
objects.push_back(CreateTestObject(0x10, 5, 5, 0x12, 0)); // BG1
objects.push_back(CreateTestObject(0x20, 10, 10, 0x22, 1)); // BG2
objects.push_back(CreateTestObject(0x30, 15, 15, 0x12, 2)); // BG3
bg1_->ClearBuffer();
bg2_->ClearBuffer();
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
ASSERT_TRUE(status.ok());
// Both buffers should be active
EXPECT_TRUE(bg1_->bitmap().is_active());
EXPECT_TRUE(bg2_->bitmap().is_active());
}
// Test that a compact buffer (preview-sized) receives pixels
TEST_F(DungeonObjectRenderingTests, PreviewBufferRendersContent) {
std::vector<zelda3::RoomObject> objects;
objects.push_back(CreateTestObject(0x10, 1, 1, 0x12, 0)); // Small wall
gfx::BackgroundBuffer preview_bg(64, 64);
gfx::BackgroundBuffer preview_bg2(64, 64);
// Initialize bitmaps
std::vector<uint8_t> empty_data(64 * 64, 0);
preview_bg.bitmap().Create(64, 64, 8, empty_data);
preview_bg2.bitmap().Create(64, 64, 8, empty_data);
preview_bg.ClearBuffer();
preview_bg2.ClearBuffer();
auto status =
drawer_->DrawObjectList(objects, preview_bg, preview_bg2, palette_group_);
ASSERT_TRUE(status.ok());
auto& bitmap = preview_bg.bitmap();
EXPECT_TRUE(bitmap.is_active());
const auto& data = bitmap.vector();
size_t non_zero = 0;
for (size_t i = 0; i < data.size(); i++) {
if (data[i] != 0) {
non_zero++;
}
}
EXPECT_GT(non_zero, 0u);
}
// Test empty object list
TEST_F(DungeonObjectRenderingTests, EmptyObjectList) {
std::vector<zelda3::RoomObject> objects; // Empty
bg1_->ClearBuffer();
bg2_->ClearBuffer();
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
// Should succeed (drawing nothing is valid)
EXPECT_TRUE(status.ok());
}
// Test large object set
TEST_F(DungeonObjectRenderingTests, LargeObjectSet) {
std::vector<zelda3::RoomObject> objects;
// Create 100 test objects
for (int i = 0; i < 100; i++) {
int x = (i % 10) * 5;
int y = (i / 10) * 5;
objects.push_back(CreateTestObject(0x10 + (i % 20), x, y, 0x12, i % 2));
}
bg1_->ClearBuffer();
bg2_->ClearBuffer();
auto start = std::chrono::high_resolution_clock::now();
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
auto end = std::chrono::high_resolution_clock::now();
ASSERT_TRUE(status.ok());
auto duration =
std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
// Should complete in reasonable time
EXPECT_LT(duration.count(), 1000)
<< "Rendered 100 objects in " << duration.count() << "ms";
}
// Test boundary conditions
TEST_F(DungeonObjectRenderingTests, BoundaryObjects) {
std::vector<zelda3::RoomObject> objects;
// Objects at boundaries
objects.push_back(CreateTestObject(0x10, 0, 0, 0x12, 0)); // Origin
objects.push_back(CreateTestObject(0x10, 63, 63, 0x12, 0)); // Max valid
objects.push_back(CreateTestObject(0x10, 32, 32, 0x12, 0)); // Center
bg1_->ClearBuffer();
bg2_->ClearBuffer();
auto status = drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
EXPECT_TRUE(status.ok());
}
// Test various object types
TEST_F(DungeonObjectRenderingTests, VariousObjectTypes) {
// Test common object types
std::vector<int> object_types = {
0x00, 0x01, 0x02, 0x03, // Floor/wall objects
0x09, 0x0A, // Diagonal objects
0x10, 0x11, 0x12, // Standard objects
0x20, 0x21, // Decorative objects
0x34, // Solid block
};
for (int obj_type : object_types) {
std::vector<zelda3::RoomObject> objects;
objects.push_back(CreateTestObject(obj_type, 10, 10, 0x12, 0));
bg1_->ClearBuffer();
bg2_->ClearBuffer();
auto status =
drawer_->DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
// Some object types might not be valid, that's okay
if (!status.ok()) {
std::cout << "Object type 0x" << std::hex << obj_type << std::dec
<< " not renderable: " << status.message() << std::endl;
}
}
}
// Test error handling
TEST_F(DungeonObjectRenderingTests, ErrorHandling) {
// Test with null ROM
zelda3::ObjectDrawer null_drawer(nullptr, 0);
std::vector<zelda3::RoomObject> objects;
objects.push_back(CreateTestObject(0x10, 5, 5));
bg1_->ClearBuffer();
bg2_->ClearBuffer();
auto status =
null_drawer.DrawObjectList(objects, *bg1_, *bg2_, palette_group_);
EXPECT_FALSE(status.ok());
EXPECT_EQ(status.code(), absl::StatusCode::kFailedPrecondition);
}
} // namespace test
} // namespace yaze