248 lines
8.6 KiB
C++
248 lines
8.6 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/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
|