Files
XCEngine/tests/Resources/Mesh/test_builtin_primitive_mesh.cpp

108 lines
3.7 KiB
C++

#include <gtest/gtest.h>
#include <XCEngine/Core/Math/Vector3.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Mesh/Mesh.h>
#include <cmath>
using namespace XCEngine::Math;
using namespace XCEngine::Resources;
namespace {
constexpr float kAlignmentEpsilon = 1e-6f;
XCEngine::Core::uint32 GetIndexValue(const Mesh& mesh, XCEngine::Core::uint32 index) {
if (mesh.IsUse32BitIndex()) {
return static_cast<const XCEngine::Core::uint32*>(mesh.GetIndexData())[index];
}
return static_cast<const XCEngine::Core::uint32>(
static_cast<const XCEngine::Core::uint16*>(mesh.GetIndexData())[index]);
}
float ComputeTriangleNormalAlignment(const Mesh& mesh, XCEngine::Core::uint32 triangleStartIndex) {
const auto* vertices = static_cast<const StaticMeshVertex*>(mesh.GetVertexData());
if (vertices == nullptr || triangleStartIndex + 2 >= mesh.GetIndexCount()) {
return 0.0f;
}
const XCEngine::Core::uint32 i0 = GetIndexValue(mesh, triangleStartIndex);
const XCEngine::Core::uint32 i1 = GetIndexValue(mesh, triangleStartIndex + 1);
const XCEngine::Core::uint32 i2 = GetIndexValue(mesh, triangleStartIndex + 2);
if (i0 >= mesh.GetVertexCount() || i1 >= mesh.GetVertexCount() || i2 >= mesh.GetVertexCount()) {
return 0.0f;
}
const Vector3& p0 = vertices[i0].position;
const Vector3& p1 = vertices[i1].position;
const Vector3& p2 = vertices[i2].position;
const Vector3 faceNormal = Vector3::Cross(p1 - p0, p2 - p0);
if (faceNormal.SqrMagnitude() <= kAlignmentEpsilon) {
return 0.0f;
}
const Vector3 averagedVertexNormal =
(vertices[i0].normal + vertices[i1].normal + vertices[i2].normal) / 3.0f;
if (averagedVertexNormal.SqrMagnitude() <= kAlignmentEpsilon) {
return 0.0f;
}
return Vector3::Dot(faceNormal, averagedVertexNormal);
}
float FindReferenceAlignment(const Mesh& mesh) {
for (XCEngine::Core::uint32 index = 0; index + 2 < mesh.GetIndexCount(); index += 3) {
const float alignment = ComputeTriangleNormalAlignment(mesh, index);
if (std::abs(alignment) > kAlignmentEpsilon) {
return alignment;
}
}
return 0.0f;
}
size_t CountTrianglesWithUnexpectedAlignment(const Mesh& mesh, float expectedSign) {
size_t mismatchCount = 0u;
for (XCEngine::Core::uint32 index = 0; index + 2 < mesh.GetIndexCount(); index += 3) {
const float alignment = ComputeTriangleNormalAlignment(mesh, index);
if (std::abs(alignment) <= kAlignmentEpsilon) {
continue;
}
if (alignment * expectedSign <= 0.0f) {
++mismatchCount;
}
}
return mismatchCount;
}
TEST(BuiltinPrimitiveMesh, SphereUsesSameTriangleWindingConventionAsCube) {
LoadResult cubeResult = CreateBuiltinMeshResource(GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Cube));
ASSERT_TRUE(cubeResult);
ASSERT_NE(cubeResult.resource, nullptr);
LoadResult sphereResult = CreateBuiltinMeshResource(GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Sphere));
ASSERT_TRUE(sphereResult);
ASSERT_NE(sphereResult.resource, nullptr);
auto* cubeMesh = static_cast<Mesh*>(cubeResult.resource);
auto* sphereMesh = static_cast<Mesh*>(sphereResult.resource);
ASSERT_NE(cubeMesh, nullptr);
ASSERT_NE(sphereMesh, nullptr);
const float cubeAlignment = FindReferenceAlignment(*cubeMesh);
ASSERT_GT(std::abs(cubeAlignment), kAlignmentEpsilon);
const float expectedSign = cubeAlignment > 0.0f ? 1.0f : -1.0f;
EXPECT_EQ(CountTrianglesWithUnexpectedAlignment(*cubeMesh, expectedSign), 0u);
EXPECT_EQ(CountTrianglesWithUnexpectedAlignment(*sphereMesh, expectedSign), 0u);
delete cubeMesh;
delete sphereMesh;
}
} // namespace