#include #include #include #include #include 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(mesh.GetIndexData())[index]; } return static_cast( static_cast(mesh.GetIndexData())[index]); } float ComputeTriangleNormalAlignment(const Mesh& mesh, XCEngine::Core::uint32 triangleStartIndex) { const auto* vertices = static_cast(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(cubeResult.resource); auto* sphereMesh = static_cast(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