Files
XCEngine/tests/Rendering/unit/test_render_scene_utility.cpp

248 lines
8.7 KiB
C++

#include <gtest/gtest.h>
#include <XCEngine/Components/CameraComponent.h>
#include <XCEngine/Components/MeshFilterComponent.h>
#include <XCEngine/Components/MeshRendererComponent.h>
#include <XCEngine/Core/Asset/IResource.h>
#include <XCEngine/Core/Math/Quaternion.h>
#include <XCEngine/Rendering/Extraction/RenderSceneUtility.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <XCEngine/Scene/Scene.h>
#include <cmath>
#include <memory>
#include <new>
#include <type_traits>
#include <vector>
namespace {
using XCEngine::Components::CameraComponent;
using XCEngine::Components::CameraProjectionType;
using XCEngine::Components::GameObject;
using XCEngine::Components::MeshFilterComponent;
using XCEngine::Components::MeshRendererComponent;
using XCEngine::Components::Scene;
using XCEngine::Math::Matrix4x4;
using XCEngine::Math::Vector3;
using XCEngine::Rendering::BuildRenderCameraData;
using XCEngine::Rendering::CompareVisibleRenderItemsStable;
using XCEngine::Rendering::CollectRenderItemsForEntityIds;
using XCEngine::Rendering::VisibleRenderItem;
using XCEngine::Resources::Mesh;
using XCEngine::Resources::MeshSection;
using XCEngine::Resources::StaticMeshVertex;
bool NearlyEqual(float lhs, float rhs, float epsilon = 1e-4f) {
return std::abs(lhs - rhs) <= epsilon;
}
bool NearlyEqual(const Matrix4x4& lhs, const Matrix4x4& rhs, float epsilon = 1e-4f) {
for (int row = 0; row < 4; ++row) {
for (int column = 0; column < 4; ++column) {
if (!NearlyEqual(lhs.m[row][column], rhs.m[row][column], epsilon)) {
return false;
}
}
}
return true;
}
std::unique_ptr<Mesh> CreateSelectionMesh(uint64_t guid, bool splitIntoTwoSections) {
auto mesh = std::make_unique<Mesh>();
XCEngine::Resources::IResource::ConstructParams params = {};
params.name = "SelectionMesh";
params.path = "memory://selection_mesh";
params.guid = XCEngine::Resources::ResourceGUID(guid);
mesh->Initialize(params);
const StaticMeshVertex vertices[] = {
{ Vector3(-1.0f, -1.0f, 0.0f) },
{ Vector3(1.0f, -1.0f, 0.0f) },
{ Vector3(0.0f, 1.0f, 0.0f) },
{ Vector3(-1.0f, -1.0f, 0.0f) },
{ Vector3(0.0f, 1.0f, 0.0f) },
{ Vector3(-2.0f, 1.0f, 0.0f) }
};
const uint16_t indices[] = { 0, 1, 2, 3, 4, 5 };
mesh->SetVertexData(
vertices,
sizeof(vertices),
static_cast<uint32_t>(std::size(vertices)),
sizeof(StaticMeshVertex),
XCEngine::Resources::VertexAttribute::Position);
mesh->SetIndexData(indices, sizeof(indices), static_cast<uint32_t>(std::size(indices)), false);
mesh->SetBounds(XCEngine::Math::Bounds(Vector3(-0.5f, 0.0f, 0.0f), Vector3(3.0f, 2.0f, 0.0f)));
MeshSection firstSection = {};
firstSection.baseVertex = 0;
firstSection.vertexCount = 3;
firstSection.startIndex = 0;
firstSection.indexCount = 3;
firstSection.materialID = 0;
firstSection.bounds = mesh->GetBounds();
mesh->AddSection(firstSection);
if (splitIntoTwoSections) {
MeshSection secondSection = {};
secondSection.baseVertex = 3;
secondSection.vertexCount = 3;
secondSection.startIndex = 3;
secondSection.indexCount = 3;
secondSection.materialID = 1;
secondSection.bounds = mesh->GetBounds();
mesh->AddSection(secondSection);
}
return mesh;
}
class RenderSceneUtilityTest : public ::testing::Test {
protected:
GameObject* CreateMeshObject(
Scene& scene,
const std::string& name,
const Vector3& position,
bool enabled = true,
bool splitIntoTwoSections = false) {
std::unique_ptr<Mesh> mesh = CreateSelectionMesh(++m_nextMeshGuid, splitIntoTwoSections);
Mesh* meshPtr = mesh.get();
m_meshes.push_back(std::move(mesh));
GameObject* object = scene.CreateGameObject(name);
object->GetTransform()->SetPosition(position);
auto* meshFilter = object->AddComponent<MeshFilterComponent>();
auto* meshRenderer = object->AddComponent<MeshRendererComponent>();
meshFilter->SetMesh(meshPtr);
meshFilter->SetEnabled(enabled);
meshRenderer->SetEnabled(enabled);
return object;
}
private:
uint64_t m_nextMeshGuid = 1;
std::vector<std::unique_ptr<Mesh>> m_meshes;
};
TEST_F(RenderSceneUtilityTest, BuildRenderCameraDataMatchesCameraTransformConvention) {
Scene scene("RenderSceneUtilityScene");
GameObject* cameraObject = scene.CreateGameObject("Camera");
auto* camera = cameraObject->AddComponent<CameraComponent>();
ASSERT_NE(camera, nullptr);
cameraObject->GetTransform()->SetPosition(Vector3(2.0f, 3.0f, -8.0f));
cameraObject->GetTransform()->SetRotation(XCEngine::Math::Quaternion::FromEulerAngles(
12.0f * XCEngine::Math::DEG_TO_RAD,
-37.0f * XCEngine::Math::DEG_TO_RAD,
5.0f * XCEngine::Math::DEG_TO_RAD));
camera->SetProjectionType(CameraProjectionType::Perspective);
camera->SetFieldOfView(53.0f);
camera->SetNearClipPlane(0.03f);
camera->SetFarClipPlane(1500.0f);
const auto cameraData = BuildRenderCameraData(*camera, 1280, 720);
EXPECT_TRUE(NearlyEqual(
cameraData.view.Transpose(),
cameraObject->GetTransform()->GetWorldToLocalMatrix()));
EXPECT_TRUE(NearlyEqual(
cameraData.projection.Transpose(),
Matrix4x4::Perspective(
53.0f * XCEngine::Math::DEG_TO_RAD,
1280.0f / 720.0f,
0.03f,
1500.0f)));
EXPECT_EQ(cameraData.viewportWidth, 1280u);
EXPECT_EQ(cameraData.viewportHeight, 720u);
EXPECT_EQ(cameraData.worldPosition, cameraObject->GetTransform()->GetPosition());
EXPECT_FLOAT_EQ(cameraData.clearColor.r, camera->GetClearColor().r);
EXPECT_FLOAT_EQ(cameraData.clearColor.g, camera->GetClearColor().g);
EXPECT_FLOAT_EQ(cameraData.clearColor.b, camera->GetClearColor().b);
EXPECT_FLOAT_EQ(cameraData.clearColor.a, camera->GetClearColor().a);
}
TEST_F(RenderSceneUtilityTest, CollectRenderItemsForEntityIdsFiltersInvalidTargets) {
Scene scene("RenderSceneUtilityScene");
GameObject* validObject = CreateMeshObject(scene, "Valid", Vector3(0.0f, 0.0f, 4.0f));
GameObject* disabledObject = CreateMeshObject(scene, "Disabled", Vector3(2.0f, 0.0f, 4.0f), false);
GameObject* inactiveObject = CreateMeshObject(scene, "Inactive", Vector3(-2.0f, 0.0f, 4.0f));
inactiveObject->SetActive(false);
const std::vector<uint64_t> entityIds = {
0u,
validObject->GetID(),
disabledObject->GetID(),
inactiveObject->GetID(),
validObject->GetID(),
987654321u
};
const auto renderables = CollectRenderItemsForEntityIds(
scene,
entityIds,
Vector3(2.0f, 3.0f, -8.0f));
ASSERT_EQ(renderables.size(), 1u);
EXPECT_EQ(renderables[0].gameObject, validObject);
EXPECT_EQ(renderables[0].meshRenderer, validObject->GetComponent<MeshRendererComponent>());
EXPECT_TRUE(renderables[0].hasSection);
}
TEST_F(RenderSceneUtilityTest, CollectRenderItemsForEntityIdsExpandsMeshSections) {
Scene scene("RenderSceneUtilityScene");
GameObject* object = CreateMeshObject(
scene,
"Sectioned",
Vector3(1.0f, 0.0f, 6.0f),
true,
true);
const auto renderables = CollectRenderItemsForEntityIds(
scene,
{ object->GetID() },
Vector3(2.0f, 3.0f, -8.0f));
ASSERT_EQ(renderables.size(), 2u);
EXPECT_EQ(renderables[0].gameObject, object);
EXPECT_EQ(renderables[0].sectionIndex, 0u);
EXPECT_EQ(renderables[0].materialIndex, 0u);
EXPECT_EQ(renderables[1].sectionIndex, 1u);
EXPECT_EQ(renderables[1].materialIndex, 1u);
}
TEST(RenderSceneUtilityStandaloneTest, CompareVisibleRenderItemsUsesStableObjectIdOrdering) {
using Storage = std::aligned_storage_t<sizeof(GameObject), alignof(GameObject)>;
Storage storage[2];
GameObject* lowerIdHigherAddress = new (&storage[1]) GameObject("LowerId");
GameObject* higherIdLowerAddress = new (&storage[0]) GameObject("HigherId");
ASSERT_LT(lowerIdHigherAddress->GetID(), higherIdLowerAddress->GetID());
ASSERT_GT(
reinterpret_cast<uintptr_t>(lowerIdHigherAddress),
reinterpret_cast<uintptr_t>(higherIdLowerAddress));
VisibleRenderItem lhs = {};
lhs.gameObject = lowerIdHigherAddress;
lhs.renderQueue = 2000;
lhs.cameraDistanceSq = 9.0f;
VisibleRenderItem rhs = {};
rhs.gameObject = higherIdLowerAddress;
rhs.renderQueue = 2000;
rhs.cameraDistanceSq = 9.0f;
EXPECT_TRUE(CompareVisibleRenderItemsStable(lhs, rhs));
EXPECT_FALSE(CompareVisibleRenderItemsStable(rhs, lhs));
higherIdLowerAddress->~GameObject();
lowerIdHigherAddress->~GameObject();
}
} // namespace