660 lines
26 KiB
C++
660 lines
26 KiB
C++
#include "app/zelda3/dungeon/object_renderer.h"
|
|
#include "app/zelda3/dungeon/room.h"
|
|
#include "app/zelda3/dungeon/room_object.h"
|
|
#include "app/zelda3/dungeon/room_layout.h"
|
|
|
|
#include <gtest/gtest.h>
|
|
#include <memory>
|
|
#include <vector>
|
|
#include <chrono>
|
|
|
|
#include "app/rom.h"
|
|
#include "app/gfx/snes_palette.h"
|
|
#include "testing.h"
|
|
|
|
namespace yaze {
|
|
namespace test {
|
|
|
|
/**
|
|
* @brief Advanced tests for actual dungeon object rendering scenarios
|
|
*
|
|
* These tests focus on real-world dungeon editing scenarios including:
|
|
* - Complex room layouts with multiple object types
|
|
* - Object interaction and collision detection
|
|
* - Performance with realistic dungeon configurations
|
|
* - Edge cases in dungeon editing workflows
|
|
*/
|
|
class DungeonObjectRenderingTests : public ::testing::Test {
|
|
protected:
|
|
void SetUp() override {
|
|
// Load test ROM with actual dungeon data
|
|
test_rom_ = std::make_unique<Rom>();
|
|
ASSERT_TRUE(test_rom_->LoadFromFile("test_rom.sfc").ok());
|
|
|
|
// Create renderer
|
|
renderer_ = std::make_unique<zelda3::ObjectRenderer>(test_rom_.get());
|
|
|
|
// Setup realistic dungeon scenarios
|
|
SetupDungeonScenarios();
|
|
SetupTestPalettes();
|
|
}
|
|
|
|
void TearDown() override {
|
|
renderer_.reset();
|
|
test_rom_.reset();
|
|
}
|
|
|
|
std::unique_ptr<Rom> test_rom_;
|
|
std::unique_ptr<zelda3::ObjectRenderer> renderer_;
|
|
|
|
struct DungeonScenario {
|
|
std::string name;
|
|
std::vector<zelda3::RoomObject> objects;
|
|
zelda3::RoomLayout layout;
|
|
gfx::SnesPalette palette;
|
|
int expected_width;
|
|
int expected_height;
|
|
};
|
|
|
|
std::vector<DungeonScenario> scenarios_;
|
|
std::vector<gfx::SnesPalette> test_palettes_;
|
|
|
|
private:
|
|
void SetupDungeonScenarios() {
|
|
// Scenario 1: Empty room with basic walls
|
|
CreateEmptyRoomScenario();
|
|
|
|
// Scenario 2: Room with multiple object types
|
|
CreateMultiObjectScenario();
|
|
|
|
// Scenario 3: Complex room with all subtypes
|
|
CreateComplexRoomScenario();
|
|
|
|
// Scenario 4: Large room with many objects
|
|
CreateLargeRoomScenario();
|
|
|
|
// Scenario 5: Boss room configuration
|
|
CreateBossRoomScenario();
|
|
|
|
// Scenario 6: Puzzle room with interactive elements
|
|
CreatePuzzleRoomScenario();
|
|
}
|
|
|
|
void SetupTestPalettes() {
|
|
// Create different palettes for different dungeon themes
|
|
CreateDungeonPalette(); // Standard dungeon
|
|
CreateIcePalacePalette(); // Ice Palace theme
|
|
CreateDesertPalacePalette(); // Desert Palace theme
|
|
CreateDarkPalacePalette(); // Palace of Darkness theme
|
|
CreateBossRoomPalette(); // Boss room theme
|
|
}
|
|
|
|
void CreateEmptyRoomScenario() {
|
|
DungeonScenario scenario;
|
|
scenario.name = "Empty Room";
|
|
|
|
// Create basic wall objects around the perimeter
|
|
for (int x = 0; x < 16; x++) {
|
|
// Top and bottom walls
|
|
scenario.objects.emplace_back(0x10, x, 0, 0x12, 0); // Top wall
|
|
scenario.objects.emplace_back(0x10, x, 10, 0x12, 0); // Bottom wall
|
|
}
|
|
|
|
for (int y = 1; y < 10; y++) {
|
|
// Left and right walls
|
|
scenario.objects.emplace_back(0x11, 0, y, 0x12, 0); // Left wall
|
|
scenario.objects.emplace_back(0x11, 15, y, 0x12, 0); // Right wall
|
|
}
|
|
|
|
// Set ROM references and load tiles
|
|
for (auto& obj : scenario.objects) {
|
|
obj.set_rom(test_rom_.get());
|
|
obj.EnsureTilesLoaded();
|
|
}
|
|
|
|
scenario.palette = test_palettes_[0]; // Dungeon palette
|
|
scenario.expected_width = 256;
|
|
scenario.expected_height = 176;
|
|
|
|
scenarios_.push_back(scenario);
|
|
}
|
|
|
|
void CreateMultiObjectScenario() {
|
|
DungeonScenario scenario;
|
|
scenario.name = "Multi-Object Room";
|
|
|
|
// Walls
|
|
scenario.objects.emplace_back(0x10, 0, 0, 0x12, 0); // Wall
|
|
scenario.objects.emplace_back(0x10, 1, 0, 0x12, 0); // Wall
|
|
scenario.objects.emplace_back(0x10, 0, 1, 0x12, 0); // Wall
|
|
|
|
// Decorative objects
|
|
scenario.objects.emplace_back(0x20, 5, 5, 0x12, 0); // Statue
|
|
scenario.objects.emplace_back(0x21, 8, 7, 0x12, 0); // Pot
|
|
|
|
// Interactive objects
|
|
scenario.objects.emplace_back(0xF9, 10, 8, 0x12, 0); // Chest
|
|
scenario.objects.emplace_back(0x13, 3, 3, 0x12, 0); // Stairs
|
|
|
|
// Set ROM references and load tiles
|
|
for (auto& obj : scenario.objects) {
|
|
obj.set_rom(test_rom_.get());
|
|
obj.EnsureTilesLoaded();
|
|
}
|
|
|
|
scenario.palette = test_palettes_[0];
|
|
scenario.expected_width = 256;
|
|
scenario.expected_height = 176;
|
|
|
|
scenarios_.push_back(scenario);
|
|
}
|
|
|
|
void CreateComplexRoomScenario() {
|
|
DungeonScenario scenario;
|
|
scenario.name = "Complex Room";
|
|
|
|
// Subtype 1 objects (basic)
|
|
for (int i = 0; i < 10; i++) {
|
|
scenario.objects.emplace_back(i, (i % 8) * 2, (i / 8) * 2, 0x12, 0);
|
|
}
|
|
|
|
// Subtype 2 objects (complex)
|
|
for (int i = 0; i < 5; i++) {
|
|
scenario.objects.emplace_back(0x100 + i, (i % 4) * 3, (i / 4) * 3, 0x12, 0);
|
|
}
|
|
|
|
// Subtype 3 objects (special)
|
|
for (int i = 0; i < 3; i++) {
|
|
scenario.objects.emplace_back(0x200 + i, (i % 3) * 4, (i / 3) * 4, 0x12, 0);
|
|
}
|
|
|
|
// Set ROM references and load tiles
|
|
for (auto& obj : scenario.objects) {
|
|
obj.set_rom(test_rom_.get());
|
|
obj.EnsureTilesLoaded();
|
|
}
|
|
|
|
scenario.palette = test_palettes_[1]; // Ice Palace palette
|
|
scenario.expected_width = 256;
|
|
scenario.expected_height = 176;
|
|
|
|
scenarios_.push_back(scenario);
|
|
}
|
|
|
|
void CreateLargeRoomScenario() {
|
|
DungeonScenario scenario;
|
|
scenario.name = "Large Room";
|
|
|
|
// Create a room with many objects (stress test scenario)
|
|
for (int i = 0; i < 100; i++) {
|
|
int x = (i % 16) * 2;
|
|
int y = (i / 16) * 2;
|
|
int object_id = (i % 50) + 0x10; // Mix of different object types
|
|
|
|
scenario.objects.emplace_back(object_id, x, y, 0x12, i % 3);
|
|
}
|
|
|
|
// Set ROM references and load tiles
|
|
for (auto& obj : scenario.objects) {
|
|
obj.set_rom(test_rom_.get());
|
|
obj.EnsureTilesLoaded();
|
|
}
|
|
|
|
scenario.palette = test_palettes_[2]; // Desert Palace palette
|
|
scenario.expected_width = 512;
|
|
scenario.expected_height = 256;
|
|
|
|
scenarios_.push_back(scenario);
|
|
}
|
|
|
|
void CreateBossRoomScenario() {
|
|
DungeonScenario scenario;
|
|
scenario.name = "Boss Room";
|
|
|
|
// Boss room typically has special objects
|
|
scenario.objects.emplace_back(0x30, 7, 4, 0x12, 0); // Boss platform
|
|
scenario.objects.emplace_back(0x31, 7, 5, 0x12, 0); // Boss platform
|
|
scenario.objects.emplace_back(0x32, 8, 4, 0x12, 0); // Boss platform
|
|
scenario.objects.emplace_back(0x33, 8, 5, 0x12, 0); // Boss platform
|
|
|
|
// Walls around the room
|
|
for (int x = 0; x < 16; x++) {
|
|
scenario.objects.emplace_back(0x10, x, 0, 0x12, 0);
|
|
scenario.objects.emplace_back(0x10, x, 10, 0x12, 0);
|
|
}
|
|
|
|
for (int y = 1; y < 10; y++) {
|
|
scenario.objects.emplace_back(0x11, 0, y, 0x12, 0);
|
|
scenario.objects.emplace_back(0x11, 15, y, 0x12, 0);
|
|
}
|
|
|
|
// Set ROM references and load tiles
|
|
for (auto& obj : scenario.objects) {
|
|
obj.set_rom(test_rom_.get());
|
|
obj.EnsureTilesLoaded();
|
|
}
|
|
|
|
scenario.palette = test_palettes_[4]; // Boss room palette
|
|
scenario.expected_width = 256;
|
|
scenario.expected_height = 176;
|
|
|
|
scenarios_.push_back(scenario);
|
|
}
|
|
|
|
void CreatePuzzleRoomScenario() {
|
|
DungeonScenario scenario;
|
|
scenario.name = "Puzzle Room";
|
|
|
|
// Puzzle rooms have specific interactive elements
|
|
scenario.objects.emplace_back(0x40, 4, 4, 0x12, 0); // Switch
|
|
scenario.objects.emplace_back(0x41, 8, 6, 0x12, 0); // Block
|
|
scenario.objects.emplace_back(0x42, 6, 8, 0x12, 0); // Pressure plate
|
|
|
|
// Chests for puzzle rewards
|
|
scenario.objects.emplace_back(0xF9, 2, 2, 0x12, 0); // Small chest
|
|
scenario.objects.emplace_back(0xFA, 12, 2, 0x12, 0); // Large chest
|
|
|
|
// Decorative elements
|
|
scenario.objects.emplace_back(0x50, 1, 5, 0x12, 0); // Torch
|
|
scenario.objects.emplace_back(0x51, 14, 5, 0x12, 0); // Torch
|
|
|
|
// Set ROM references and load tiles
|
|
for (auto& obj : scenario.objects) {
|
|
obj.set_rom(test_rom_.get());
|
|
obj.EnsureTilesLoaded();
|
|
}
|
|
|
|
scenario.palette = test_palettes_[3]; // Dark Palace palette
|
|
scenario.expected_width = 256;
|
|
scenario.expected_height = 176;
|
|
|
|
scenarios_.push_back(scenario);
|
|
}
|
|
|
|
void CreateDungeonPalette() {
|
|
gfx::SnesPalette palette;
|
|
// Standard dungeon colors (grays and browns)
|
|
palette.AddColor(gfx::SnesColor(0x00, 0x00, 0x00)); // Black
|
|
palette.AddColor(gfx::SnesColor(0x20, 0x20, 0x20)); // Dark gray
|
|
palette.AddColor(gfx::SnesColor(0x40, 0x40, 0x40)); // Medium gray
|
|
palette.AddColor(gfx::SnesColor(0x60, 0x60, 0x60)); // Light gray
|
|
palette.AddColor(gfx::SnesColor(0x80, 0x80, 0x80)); // Very light gray
|
|
palette.AddColor(gfx::SnesColor(0xA0, 0xA0, 0xA0)); // Almost white
|
|
palette.AddColor(gfx::SnesColor(0xC0, 0xC0, 0xC0)); // White
|
|
palette.AddColor(gfx::SnesColor(0x80, 0x40, 0x20)); // Brown
|
|
palette.AddColor(gfx::SnesColor(0xA0, 0x60, 0x40)); // Light brown
|
|
palette.AddColor(gfx::SnesColor(0x60, 0x80, 0x40)); // Green
|
|
palette.AddColor(gfx::SnesColor(0x40, 0x60, 0x80)); // Blue
|
|
palette.AddColor(gfx::SnesColor(0x80, 0x40, 0x80)); // Purple
|
|
palette.AddColor(gfx::SnesColor(0x80, 0x80, 0x40)); // Yellow
|
|
palette.AddColor(gfx::SnesColor(0x80, 0x40, 0x40)); // Red
|
|
palette.AddColor(gfx::SnesColor(0x40, 0x80, 0x80)); // Cyan
|
|
palette.AddColor(gfx::SnesColor(0xFF, 0xFF, 0xFF)); // Pure white
|
|
test_palettes_.push_back(palette);
|
|
}
|
|
|
|
void CreateIcePalacePalette() {
|
|
gfx::SnesPalette palette;
|
|
// Ice Palace colors (blues and whites)
|
|
palette.AddColor(gfx::SnesColor(0x00, 0x00, 0x00)); // Black
|
|
palette.AddColor(gfx::SnesColor(0x20, 0x40, 0x80)); // Dark blue
|
|
palette.AddColor(gfx::SnesColor(0x40, 0x60, 0xA0)); // Medium blue
|
|
palette.AddColor(gfx::SnesColor(0x60, 0x80, 0xC0)); // Light blue
|
|
palette.AddColor(gfx::SnesColor(0x80, 0xA0, 0xE0)); // Very light blue
|
|
palette.AddColor(gfx::SnesColor(0xA0, 0xC0, 0xFF)); // Pale blue
|
|
palette.AddColor(gfx::SnesColor(0xC0, 0xE0, 0xFF)); // Almost white
|
|
palette.AddColor(gfx::SnesColor(0xE0, 0xF0, 0xFF)); // White
|
|
palette.AddColor(gfx::SnesColor(0x40, 0x80, 0xC0)); // Ice blue
|
|
palette.AddColor(gfx::SnesColor(0x60, 0xA0, 0xE0)); // Light ice
|
|
palette.AddColor(gfx::SnesColor(0x80, 0xC0, 0xFF)); // Pale ice
|
|
palette.AddColor(gfx::SnesColor(0x20, 0x60, 0xA0)); // Deep ice
|
|
palette.AddColor(gfx::SnesColor(0x00, 0x40, 0x80)); // Dark ice
|
|
palette.AddColor(gfx::SnesColor(0x60, 0x80, 0xA0)); // Gray-blue
|
|
palette.AddColor(gfx::SnesColor(0x80, 0xA0, 0xC0)); // Light gray-blue
|
|
palette.AddColor(gfx::SnesColor(0xFF, 0xFF, 0xFF)); // Pure white
|
|
test_palettes_.push_back(palette);
|
|
}
|
|
|
|
void CreateDesertPalacePalette() {
|
|
gfx::SnesPalette palette;
|
|
// Desert Palace colors (yellows, oranges, and browns)
|
|
palette.AddColor(gfx::SnesColor(0x00, 0x00, 0x00)); // Black
|
|
palette.AddColor(gfx::SnesColor(0x40, 0x20, 0x00)); // Dark brown
|
|
palette.AddColor(gfx::SnesColor(0x60, 0x40, 0x20)); // Medium brown
|
|
palette.AddColor(gfx::SnesColor(0x80, 0x60, 0x40)); // Light brown
|
|
palette.AddColor(gfx::SnesColor(0xA0, 0x80, 0x60)); // Very light brown
|
|
palette.AddColor(gfx::SnesColor(0xC0, 0xA0, 0x80)); // Tan
|
|
palette.AddColor(gfx::SnesColor(0xE0, 0xC0, 0xA0)); // Light tan
|
|
palette.AddColor(gfx::SnesColor(0xFF, 0xE0, 0xC0)); // Cream
|
|
palette.AddColor(gfx::SnesColor(0x80, 0x40, 0x00)); // Orange
|
|
palette.AddColor(gfx::SnesColor(0xA0, 0x60, 0x20)); // Light orange
|
|
palette.AddColor(gfx::SnesColor(0xC0, 0x80, 0x40)); // Pale orange
|
|
palette.AddColor(gfx::SnesColor(0xE0, 0xA0, 0x60)); // Very pale orange
|
|
palette.AddColor(gfx::SnesColor(0x60, 0x60, 0x20)); // Olive
|
|
palette.AddColor(gfx::SnesColor(0x80, 0x80, 0x40)); // Light olive
|
|
palette.AddColor(gfx::SnesColor(0xA0, 0xA0, 0x60)); // Very light olive
|
|
palette.AddColor(gfx::SnesColor(0xFF, 0xFF, 0xFF)); // Pure white
|
|
test_palettes_.push_back(palette);
|
|
}
|
|
|
|
void CreateDarkPalacePalette() {
|
|
gfx::SnesPalette palette;
|
|
// Palace of Darkness colors (dark purples and grays)
|
|
palette.AddColor(gfx::SnesColor(0x00, 0x00, 0x00)); // Black
|
|
palette.AddColor(gfx::SnesColor(0x20, 0x00, 0x20)); // Dark purple
|
|
palette.AddColor(gfx::SnesColor(0x40, 0x20, 0x40)); // Medium purple
|
|
palette.AddColor(gfx::SnesColor(0x60, 0x40, 0x60)); // Light purple
|
|
palette.AddColor(gfx::SnesColor(0x80, 0x60, 0x80)); // Very light purple
|
|
palette.AddColor(gfx::SnesColor(0xA0, 0x80, 0xA0)); // Pale purple
|
|
palette.AddColor(gfx::SnesColor(0xC0, 0xA0, 0xC0)); // Almost white purple
|
|
palette.AddColor(gfx::SnesColor(0x10, 0x10, 0x10)); // Very dark gray
|
|
palette.AddColor(gfx::SnesColor(0x30, 0x30, 0x30)); // Dark gray
|
|
palette.AddColor(gfx::SnesColor(0x50, 0x50, 0x50)); // Medium gray
|
|
palette.AddColor(gfx::SnesColor(0x70, 0x70, 0x70)); // Light gray
|
|
palette.AddColor(gfx::SnesColor(0x90, 0x90, 0x90)); // Very light gray
|
|
palette.AddColor(gfx::SnesColor(0xB0, 0xB0, 0xB0)); // Almost white
|
|
palette.AddColor(gfx::SnesColor(0xD0, 0xD0, 0xD0)); // Off white
|
|
palette.AddColor(gfx::SnesColor(0xF0, 0xF0, 0xF0)); // Near white
|
|
palette.AddColor(gfx::SnesColor(0xFF, 0xFF, 0xFF)); // Pure white
|
|
test_palettes_.push_back(palette);
|
|
}
|
|
|
|
void CreateBossRoomPalette() {
|
|
gfx::SnesPalette palette;
|
|
// Boss room colors (dramatic reds, golds, and blacks)
|
|
palette.AddColor(gfx::SnesColor(0x00, 0x00, 0x00)); // Black
|
|
palette.AddColor(gfx::SnesColor(0x40, 0x00, 0x00)); // Dark red
|
|
palette.AddColor(gfx::SnesColor(0x60, 0x20, 0x00)); // Dark red-orange
|
|
palette.AddColor(gfx::SnesColor(0x80, 0x40, 0x00)); // Red-orange
|
|
palette.AddColor(gfx::SnesColor(0xA0, 0x60, 0x20)); // Orange
|
|
palette.AddColor(gfx::SnesColor(0xC0, 0x80, 0x40)); // Light orange
|
|
palette.AddColor(gfx::SnesColor(0xE0, 0xA0, 0x60)); // Very light orange
|
|
palette.AddColor(gfx::SnesColor(0x80, 0x60, 0x00)); // Dark gold
|
|
palette.AddColor(gfx::SnesColor(0xA0, 0x80, 0x20)); // Gold
|
|
palette.AddColor(gfx::SnesColor(0xC0, 0xA0, 0x40)); // Light gold
|
|
palette.AddColor(gfx::SnesColor(0xE0, 0xC0, 0x60)); // Very light gold
|
|
palette.AddColor(gfx::SnesColor(0x20, 0x20, 0x20)); // Dark gray
|
|
palette.AddColor(gfx::SnesColor(0x40, 0x40, 0x40)); // Medium gray
|
|
palette.AddColor(gfx::SnesColor(0x60, 0x60, 0x60)); // Light gray
|
|
palette.AddColor(gfx::SnesColor(0x80, 0x80, 0x80)); // Very light gray
|
|
palette.AddColor(gfx::SnesColor(0xFF, 0xFF, 0xFF)); // Pure white
|
|
test_palettes_.push_back(palette);
|
|
}
|
|
};
|
|
|
|
// Scenario-based rendering tests
|
|
TEST_F(DungeonObjectRenderingTests, EmptyRoomRendering) {
|
|
ASSERT_GE(scenarios_.size(), 1) << "Empty room scenario not available";
|
|
|
|
const auto& scenario = scenarios_[0];
|
|
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
|
|
|
ASSERT_TRUE(result.ok()) << "Empty room rendering failed: " << result.status().message();
|
|
|
|
auto bitmap = std::move(result.value());
|
|
EXPECT_TRUE(bitmap.is_active()) << "Empty room bitmap not active";
|
|
EXPECT_GE(bitmap.width(), scenario.expected_width) << "Empty room width too small";
|
|
EXPECT_GE(bitmap.height(), scenario.expected_height) << "Empty room height too small";
|
|
|
|
// Verify wall objects are rendered
|
|
EXPECT_GT(bitmap.size(), 0) << "Empty room bitmap has no content";
|
|
}
|
|
|
|
TEST_F(DungeonObjectRenderingTests, MultiObjectRoomRendering) {
|
|
ASSERT_GE(scenarios_.size(), 2) << "Multi-object scenario not available";
|
|
|
|
const auto& scenario = scenarios_[1];
|
|
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
|
|
|
ASSERT_TRUE(result.ok()) << "Multi-object room rendering failed: " << result.status().message();
|
|
|
|
auto bitmap = std::move(result.value());
|
|
EXPECT_TRUE(bitmap.is_active()) << "Multi-object room bitmap not active";
|
|
EXPECT_GE(bitmap.width(), scenario.expected_width) << "Multi-object room width too small";
|
|
EXPECT_GE(bitmap.height(), scenario.expected_height) << "Multi-object room height too small";
|
|
|
|
// Verify different object types are rendered
|
|
EXPECT_GT(bitmap.size(), 0) << "Multi-object room bitmap has no content";
|
|
}
|
|
|
|
TEST_F(DungeonObjectRenderingTests, ComplexRoomRendering) {
|
|
ASSERT_GE(scenarios_.size(), 3) << "Complex room scenario not available";
|
|
|
|
const auto& scenario = scenarios_[2];
|
|
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
|
|
|
ASSERT_TRUE(result.ok()) << "Complex room rendering failed: " << result.status().message();
|
|
|
|
auto bitmap = std::move(result.value());
|
|
EXPECT_TRUE(bitmap.is_active()) << "Complex room bitmap not active";
|
|
EXPECT_GE(bitmap.width(), scenario.expected_width) << "Complex room width too small";
|
|
EXPECT_GE(bitmap.height(), scenario.expected_height) << "Complex room height too small";
|
|
|
|
// Verify all subtypes are rendered correctly
|
|
EXPECT_GT(bitmap.size(), 0) << "Complex room bitmap has no content";
|
|
}
|
|
|
|
TEST_F(DungeonObjectRenderingTests, LargeRoomRendering) {
|
|
ASSERT_GE(scenarios_.size(), 4) << "Large room scenario not available";
|
|
|
|
const auto& scenario = scenarios_[3];
|
|
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
|
|
|
ASSERT_TRUE(result.ok()) << "Large room rendering failed: " << result.status().message();
|
|
|
|
auto bitmap = std::move(result.value());
|
|
EXPECT_TRUE(bitmap.is_active()) << "Large room bitmap not active";
|
|
EXPECT_GE(bitmap.width(), scenario.expected_width) << "Large room width too small";
|
|
EXPECT_GE(bitmap.height(), scenario.expected_height) << "Large room height too small";
|
|
|
|
// Verify performance with many objects
|
|
auto stats = renderer_->GetPerformanceStats();
|
|
EXPECT_GT(stats.objects_rendered, 0) << "Large room objects not rendered";
|
|
EXPECT_GT(stats.tiles_rendered, 0) << "Large room tiles not rendered";
|
|
}
|
|
|
|
TEST_F(DungeonObjectRenderingTests, BossRoomRendering) {
|
|
ASSERT_GE(scenarios_.size(), 5) << "Boss room scenario not available";
|
|
|
|
const auto& scenario = scenarios_[4];
|
|
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
|
|
|
ASSERT_TRUE(result.ok()) << "Boss room rendering failed: " << result.status().message();
|
|
|
|
auto bitmap = std::move(result.value());
|
|
EXPECT_TRUE(bitmap.is_active()) << "Boss room bitmap not active";
|
|
EXPECT_GE(bitmap.width(), scenario.expected_width) << "Boss room width too small";
|
|
EXPECT_GE(bitmap.height(), scenario.expected_height) << "Boss room height too small";
|
|
|
|
// Verify boss-specific objects are rendered
|
|
EXPECT_GT(bitmap.size(), 0) << "Boss room bitmap has no content";
|
|
}
|
|
|
|
TEST_F(DungeonObjectRenderingTests, PuzzleRoomRendering) {
|
|
ASSERT_GE(scenarios_.size(), 6) << "Puzzle room scenario not available";
|
|
|
|
const auto& scenario = scenarios_[5];
|
|
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
|
|
|
ASSERT_TRUE(result.ok()) << "Puzzle room rendering failed: " << result.status().message();
|
|
|
|
auto bitmap = std::move(result.value());
|
|
EXPECT_TRUE(bitmap.is_active()) << "Puzzle room bitmap not active";
|
|
EXPECT_GE(bitmap.width(), scenario.expected_width) << "Puzzle room width too small";
|
|
EXPECT_GE(bitmap.height(), scenario.expected_height) << "Puzzle room height too small";
|
|
|
|
// Verify puzzle elements are rendered
|
|
EXPECT_GT(bitmap.size(), 0) << "Puzzle room bitmap has no content";
|
|
}
|
|
|
|
// Palette-specific rendering tests
|
|
TEST_F(DungeonObjectRenderingTests, PaletteConsistency) {
|
|
ASSERT_GE(scenarios_.size(), 1) << "Test scenario not available";
|
|
|
|
const auto& scenario = scenarios_[0];
|
|
|
|
// Render with different palettes
|
|
for (size_t i = 0; i < test_palettes_.size(); i++) {
|
|
auto result = renderer_->RenderObjects(scenario.objects, test_palettes_[i]);
|
|
ASSERT_TRUE(result.ok()) << "Palette " << i << " rendering failed: " << result.status().message();
|
|
|
|
auto bitmap = std::move(result.value());
|
|
EXPECT_TRUE(bitmap.is_active()) << "Palette " << i << " bitmap not active";
|
|
EXPECT_GT(bitmap.size(), 0) << "Palette " << i << " bitmap has no content";
|
|
}
|
|
}
|
|
|
|
// Performance tests with realistic scenarios
|
|
TEST_F(DungeonObjectRenderingTests, ScenarioPerformanceBenchmark) {
|
|
const int iterations = 10;
|
|
|
|
for (const auto& scenario : scenarios_) {
|
|
auto start_time = std::chrono::high_resolution_clock::now();
|
|
|
|
for (int i = 0; i < iterations; i++) {
|
|
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
|
ASSERT_TRUE(result.ok()) << "Scenario " << scenario.name
|
|
<< " rendering failed: " << result.status().message();
|
|
}
|
|
|
|
auto end_time = std::chrono::high_resolution_clock::now();
|
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time);
|
|
|
|
// Each scenario should render within reasonable time
|
|
EXPECT_LT(duration.count(), 5000) << "Scenario " << scenario.name
|
|
<< " performance below expectations: "
|
|
<< duration.count() << "ms";
|
|
}
|
|
}
|
|
|
|
// Memory usage tests with realistic scenarios
|
|
TEST_F(DungeonObjectRenderingTests, ScenarioMemoryUsage) {
|
|
size_t initial_memory = renderer_->GetMemoryUsage();
|
|
|
|
// Render all scenarios multiple times
|
|
for (int round = 0; round < 3; round++) {
|
|
for (const auto& scenario : scenarios_) {
|
|
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
|
ASSERT_TRUE(result.ok()) << "Scenario memory test failed: " << result.status().message();
|
|
}
|
|
}
|
|
|
|
size_t final_memory = renderer_->GetMemoryUsage();
|
|
|
|
// Memory usage should not grow excessively
|
|
EXPECT_LT(final_memory, initial_memory * 5) << "Memory leak detected in scenario tests: "
|
|
<< initial_memory << " -> " << final_memory;
|
|
|
|
// Clear cache and verify memory reduction
|
|
renderer_->ClearCache();
|
|
size_t memory_after_clear = renderer_->GetMemoryUsage();
|
|
EXPECT_LT(memory_after_clear, final_memory) << "Cache clear did not reduce memory usage";
|
|
}
|
|
|
|
// Object interaction tests
|
|
TEST_F(DungeonObjectRenderingTests, ObjectOverlapHandling) {
|
|
// Create objects that overlap
|
|
std::vector<zelda3::RoomObject> overlapping_objects;
|
|
|
|
// Two objects at the same position
|
|
overlapping_objects.emplace_back(0x10, 5, 5, 0x12, 0);
|
|
overlapping_objects.emplace_back(0x20, 5, 5, 0x12, 1); // Different layer
|
|
|
|
// Objects that partially overlap
|
|
overlapping_objects.emplace_back(0x30, 3, 3, 0x12, 0);
|
|
overlapping_objects.emplace_back(0x31, 4, 4, 0x12, 0);
|
|
|
|
// Set ROM references and load tiles
|
|
for (auto& obj : overlapping_objects) {
|
|
obj.set_rom(test_rom_.get());
|
|
obj.EnsureTilesLoaded();
|
|
}
|
|
|
|
auto result = renderer_->RenderObjects(overlapping_objects, test_palettes_[0]);
|
|
ASSERT_TRUE(result.ok()) << "Overlapping objects rendering failed: " << result.status().message();
|
|
|
|
auto bitmap = std::move(result.value());
|
|
EXPECT_TRUE(bitmap.is_active()) << "Overlapping objects bitmap not active";
|
|
EXPECT_GT(bitmap.size(), 0) << "Overlapping objects bitmap has no content";
|
|
}
|
|
|
|
TEST_F(DungeonObjectRenderingTests, LayerRenderingOrder) {
|
|
// Create objects on different layers
|
|
std::vector<zelda3::RoomObject> layered_objects;
|
|
|
|
// Background layer (0)
|
|
layered_objects.emplace_back(0x10, 5, 5, 0x12, 0);
|
|
|
|
// Middle layer (1)
|
|
layered_objects.emplace_back(0x20, 5, 5, 0x12, 1);
|
|
|
|
// Foreground layer (2)
|
|
layered_objects.emplace_back(0x30, 5, 5, 0x12, 2);
|
|
|
|
// Set ROM references and load tiles
|
|
for (auto& obj : layered_objects) {
|
|
obj.set_rom(test_rom_.get());
|
|
obj.EnsureTilesLoaded();
|
|
}
|
|
|
|
auto result = renderer_->RenderObjects(layered_objects, test_palettes_[0]);
|
|
ASSERT_TRUE(result.ok()) << "Layered objects rendering failed: " << result.status().message();
|
|
|
|
auto bitmap = std::move(result.value());
|
|
EXPECT_TRUE(bitmap.is_active()) << "Layered objects bitmap not active";
|
|
EXPECT_GT(bitmap.size(), 0) << "Layered objects bitmap has no content";
|
|
}
|
|
|
|
// Cache efficiency with realistic scenarios
|
|
TEST_F(DungeonObjectRenderingTests, ScenarioCacheEfficiency) {
|
|
renderer_->ClearCache();
|
|
|
|
// Render scenarios multiple times to test cache
|
|
for (int round = 0; round < 5; round++) {
|
|
for (const auto& scenario : scenarios_) {
|
|
auto result = renderer_->RenderObjects(scenario.objects, scenario.palette);
|
|
ASSERT_TRUE(result.ok()) << "Cache efficiency test failed: " << result.status().message();
|
|
}
|
|
}
|
|
|
|
auto stats = renderer_->GetPerformanceStats();
|
|
|
|
// Cache hit rate should be high after multiple renders
|
|
EXPECT_GT(stats.cache_hits, 0) << "No cache hits in scenario test";
|
|
EXPECT_GT(stats.cache_hit_rate(), 0.3) << "Cache hit rate too low: " << stats.cache_hit_rate();
|
|
}
|
|
|
|
// Edge cases in dungeon editing
|
|
TEST_F(DungeonObjectRenderingTests, BoundaryObjectPlacement) {
|
|
// Create objects at room boundaries
|
|
std::vector<zelda3::RoomObject> boundary_objects;
|
|
|
|
// Objects at exact boundaries
|
|
boundary_objects.emplace_back(0x10, 0, 0, 0x12, 0); // Top-left
|
|
boundary_objects.emplace_back(0x11, 15, 0, 0x12, 0); // Top-right
|
|
boundary_objects.emplace_back(0x12, 0, 10, 0x12, 0); // Bottom-left
|
|
boundary_objects.emplace_back(0x13, 15, 10, 0x12, 0); // Bottom-right
|
|
|
|
// Objects just outside boundaries (should be handled gracefully)
|
|
boundary_objects.emplace_back(0x14, -1, 5, 0x12, 0); // Left edge
|
|
boundary_objects.emplace_back(0x15, 16, 5, 0x12, 0); // Right edge
|
|
boundary_objects.emplace_back(0x16, 5, -1, 0x12, 0); // Top edge
|
|
boundary_objects.emplace_back(0x17, 5, 11, 0x12, 0); // Bottom edge
|
|
|
|
// Set ROM references and load tiles
|
|
for (auto& obj : boundary_objects) {
|
|
obj.set_rom(test_rom_.get());
|
|
obj.EnsureTilesLoaded();
|
|
}
|
|
|
|
auto result = renderer_->RenderObjects(boundary_objects, test_palettes_[0]);
|
|
ASSERT_TRUE(result.ok()) << "Boundary objects rendering failed: " << result.status().message();
|
|
|
|
auto bitmap = std::move(result.value());
|
|
EXPECT_TRUE(bitmap.is_active()) << "Boundary objects bitmap not active";
|
|
EXPECT_GT(bitmap.size(), 0) << "Boundary objects bitmap has no content";
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace yaze
|