Files
filament/libs/geometry/tests/test_tangent_space_mesh.cpp
Powei Feng b3513c7d1f geometry: Add TangentSpaceMesh implementations (#6530)
geometry: Add TangentSpaceMesh implementations

Added:
 - Hughes-Moller
 - Frisvad's method
 - Flat shading
2023-02-10 09:34:49 -08:00

251 lines
8.2 KiB
C++

/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <geometry/TangentSpaceMesh.h>
#include <math/quat.h>
#include <math/vec3.h>
#include <utils/Log.h>
#include <gtest/gtest.h>
class TangentSpaceMeshTest : public testing::Test {};
using namespace filament::geometry;
using namespace filament::math;
namespace {
const std::vector<float3> CUBE_VERTS {
float3{0, 0, 0},
float3{0, 0, 1},
float3{0, 1, 0},
float3{0, 1, 1},
float3{1, 0, 0},
float3{1, 0, 1},
float3{1, 1, 0},
float3{1, 1, 1}
};
const std::vector<float2> CUBE_UVS{
float2{0, 0},
float2{1, 0},
float2{0, 1},
float2{1, 1},
float2{.5, 0},
float2{0, .5},
float2{.5, .5},
float2{0, 0}
};
const std::vector<ushort3> CUBE_TRIANGLES {
ushort3{0, 6, 4}, ushort3{0, 2, 6}, // XY-plane at z=0, normal=(0, 0, -1)
ushort3{4, 7, 5}, ushort3{4, 6, 7}, // YZ-plane at x=1, normal=(1, 0 , 0)
ushort3{2, 7, 6}, ushort3{2, 3, 7}, // XZ-plane at y=1, normal=(0, 1, 0)
ushort3{1, 2, 0}, ushort3{1, 3, 2}, // YZ-plane at x=0, normal=(-1, 0, 0)
ushort3{1, 4, 5}, ushort3{1, 0, 4}, // XZ-plane at y=0, normal=(0, -1, 0)
ushort3{1, 7, 3}, ushort3{1, 5, 7} // XY-plane at z=1, normal=(0, 0, 1)
};
// Corresponding to the faces in CUBE_TRIANGLES
const std::vector<float3> CUBE_FACE_NORMALS {
float3{0, 0, -1},
float3{1, 0, 0},
float3{0, 1, 0},
float3{-1, 0, 0},
float3{0, -1, 0},
float3{0, 0, 1}
};
const std::vector<float3> TEST_NORMALS {
float3{1, 0, 0},
float3{0, 1, 0},
float3{0, 0, 1},
normalize(float3{0, 1, 1}),
normalize(float3{1, 1, 0}),
normalize(float3{1, 1, 1})
};
const float3 NORMAL_AXIS{0, 0, 1};
const float3 TANGENT_AXIS{1, 0, 0};
const float3 BITANGENT_AXIS{0, 1, 0};
bool isAlmostEqual3(const float3& a, const float3& b) noexcept {
const float3 diff = a - b;
const size_t steps = sizeof(float3) / sizeof(float);
for (int i = 0; i < steps; ++i) {
if (abs(diff[i]) > std::numeric_limits<float>::epsilon()) {
return false;
}
}
return true;
}
bool isAlmostEqual2(const float2& a, const float2& b) noexcept {
const float2 diff = a - b;
const size_t steps = sizeof(float2) / sizeof(float);
for (int i = 0; i < steps; ++i) {
if (abs(diff[i]) > std::numeric_limits<float>::epsilon()) {
return false;
}
}
return true;
}
} // anonymous namespace
TEST_F(TangentSpaceMeshTest, BuilderDefaultAlgorithms) {
TangentSpaceMesh* mesh = TangentSpaceMesh::Builder()
.vertexCount(CUBE_VERTS.size())
.positions(CUBE_VERTS.data())
.triangleCount(CUBE_TRIANGLES.size())
.triangles(CUBE_TRIANGLES.data())
.build();
EXPECT_EQ(mesh->getAlgorithm(), TangentSpaceMesh::Algorithm::FLAT_SHADING);
TangentSpaceMesh::destroy(mesh);
mesh = TangentSpaceMesh::Builder()
.vertexCount(1)
.normals(TEST_NORMALS.data())
.build();
EXPECT_EQ(mesh->getAlgorithm(), TangentSpaceMesh::Algorithm::FRISVAD);
TangentSpaceMesh::destroy(mesh);
}
// Remeshed vertices/uvs should map to input vertices/uvs
TEST_F(TangentSpaceMeshTest, FlatShadingRemesh) {
TangentSpaceMesh* mesh = TangentSpaceMesh::Builder()
.vertexCount(CUBE_VERTS.size())
.positions(CUBE_VERTS.data())
.triangleCount(CUBE_TRIANGLES.size())
.triangles(CUBE_TRIANGLES.data())
.uvs(CUBE_UVS.data())
.algorithm(TangentSpaceMesh::Algorithm::FLAT_SHADING)
.build();
// Number of triangles should remain the same
ASSERT_EQ(mesh->getTriangleCount(), CUBE_TRIANGLES.size());
std::vector<float3> outPositions(mesh->getVertexCount());
mesh->getPositions(outPositions.data());
std::vector<float2> outUVs(mesh->getVertexCount());
mesh->getUVs(outUVs.data());
for (size_t i = 0; i < outPositions.size(); ++i) {
const auto& outPos = outPositions[i];
const auto& outUV = outUVs[i];
bool found = false;
for (size_t j = 0; j < CUBE_VERTS.size(); ++j) {
const auto& inPos = CUBE_VERTS[j];
const auto& inUV = CUBE_UVS[j];
if (isAlmostEqual3(outPos, inPos)) {
found = true;
EXPECT_PRED2(isAlmostEqual2, outUV, inUV);
break;
}
}
EXPECT_TRUE(found);
}
TangentSpaceMesh::destroy(mesh);
}
TEST_F(TangentSpaceMeshTest, FlatShading) {
TangentSpaceMesh* mesh = TangentSpaceMesh::Builder()
.vertexCount(CUBE_VERTS.size())
.positions(CUBE_VERTS.data())
.triangleCount(CUBE_TRIANGLES.size())
.triangles(CUBE_TRIANGLES.data())
.algorithm(TangentSpaceMesh::Algorithm::FLAT_SHADING)
.build();
ASSERT_EQ(mesh->getVertexCount(), CUBE_TRIANGLES.size() * 3);
ASSERT_EQ(mesh->getTriangleCount(), CUBE_TRIANGLES.size());
std::vector<quatf> quats(mesh->getVertexCount());
std::vector<ushort3> triangles(mesh->getTriangleCount());
mesh->getTriangles(triangles.data());
mesh->getQuats(quats.data());
for (size_t i = 0; i < CUBE_TRIANGLES.size(); ++i) {
size_t faceInd = i / 2;
const float3& expectedNormal = CUBE_FACE_NORMALS[faceInd];
for (int j = 0; j < 3; ++j) {
const quatf& quat = quats[triangles[i][j]];
EXPECT_PRED2(isAlmostEqual3, quat * NORMAL_AXIS, expectedNormal);
}
}
TangentSpaceMesh::destroy(mesh);
}
TEST_F(TangentSpaceMeshTest, Frisvad) {
TangentSpaceMesh* mesh = TangentSpaceMesh::Builder()
.vertexCount(TEST_NORMALS.size())
.normals(TEST_NORMALS.data())
.algorithm(TangentSpaceMesh::Algorithm::FRISVAD)
.build();
ASSERT_EQ(mesh->getVertexCount(), TEST_NORMALS.size());
ASSERT_EQ(mesh->getTriangleCount(), 0);
std::vector<quatf> quats(mesh->getVertexCount());
mesh->getQuats(quats.data());
for (size_t i = 0; i < TEST_NORMALS.size(); ++i) {
const float3 n = quats[i] * NORMAL_AXIS;
EXPECT_PRED2(isAlmostEqual3, n, TEST_NORMALS[i]);
const float3 b = quats[i] * BITANGENT_AXIS;
const float3 t = quats[i] * TANGENT_AXIS;
EXPECT_LT(abs(dot(b, t)), std::numeric_limits<float>::epsilon());
EXPECT_LT(abs(dot(n, t)), std::numeric_limits<float>::epsilon());
EXPECT_LT(abs(dot(n, b)), std::numeric_limits<float>::epsilon());
EXPECT_PRED2(isAlmostEqual3, cross(n, t), b);
}
TangentSpaceMesh::destroy(mesh);
}
TEST_F(TangentSpaceMeshTest, HughesMoller) {
TangentSpaceMesh* mesh = TangentSpaceMesh::Builder()
.vertexCount(TEST_NORMALS.size())
.normals(TEST_NORMALS.data())
.algorithm(TangentSpaceMesh::Algorithm::HUGHES_MOLLER)
.build();
ASSERT_EQ(mesh->getVertexCount(), TEST_NORMALS.size());
ASSERT_EQ(mesh->getTriangleCount(), 0);
std::vector<quatf> quats(mesh->getVertexCount());
mesh->getQuats(quats.data());
for (size_t i = 0; i < TEST_NORMALS.size(); ++i) {
const float3 n = quats[i] * NORMAL_AXIS;
EXPECT_PRED2(isAlmostEqual3, n, TEST_NORMALS[i]);
const float3 b = quats[i] * BITANGENT_AXIS;
const float3 t = quats[i] * TANGENT_AXIS;
EXPECT_LT(abs(dot(b, t)), std::numeric_limits<float>::epsilon());
EXPECT_LT(abs(dot(n, t)), std::numeric_limits<float>::epsilon());
EXPECT_LT(abs(dot(n, b)), std::numeric_limits<float>::epsilon());
EXPECT_PRED2(isAlmostEqual3, cross(n, t), b);
}
TangentSpaceMesh::destroy(mesh);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}