Skip to content

Commit d418d0e

Browse files
committed
mesh consistent winding
1 parent 155ed39 commit d418d0e

File tree

3 files changed

+113
-0
lines changed

3 files changed

+113
-0
lines changed

sources/include/cage-core/meshAlgorithms.h

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace cage
1717

1818
CAGE_CORE_API void meshFlipNormals(Mesh *msh);
1919
CAGE_CORE_API void meshDuplicateSides(Mesh *msh);
20+
CAGE_CORE_API void meshConsistentWinding(Mesh *msh);
2021

2122
struct CAGE_CORE_API MeshMergeCloseVerticesConfig
2223
{

sources/libcore/mesh/meshAlgorithms.cpp

+89
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,95 @@ namespace cage
372372
// todo normals and other attributes
373373
}
374374

375+
void meshConsistentWinding(Mesh *msh)
376+
{
377+
MeshImpl *impl = (MeshImpl *)msh;
378+
if (impl->type != MeshTypeEnum::Triangles)
379+
return;
380+
meshConvertToIndexed(msh);
381+
if (impl->indices.empty())
382+
return;
383+
const uint32 tris = impl->indices.size() / 3;
384+
std::vector<bool> finished;
385+
finished.resize(tris, false);
386+
std::vector<FlatSet<uint32>> adjacent; // vertex -> triangles
387+
adjacent.resize(impl->positions.size());
388+
for (uint32 t = 0; t < tris; t++)
389+
{
390+
adjacent[impl->indices[t * 3 + 0]].insert(t);
391+
adjacent[impl->indices[t * 3 + 1]].insert(t);
392+
adjacent[impl->indices[t * 3 + 2]].insert(t);
393+
}
394+
std::vector<uint32> queue;
395+
queue.reserve(tris / 2);
396+
const auto &areNeighbors = [&](uint32 a, uint32 b) -> bool
397+
{
398+
std::array<uint32, 3> ai = { impl->indices[a * 3 + 0], impl->indices[a * 3 + 1], impl->indices[a * 3 + 2] };
399+
std::array<uint32, 3> bi = { impl->indices[b * 3 + 0], impl->indices[b * 3 + 1], impl->indices[b * 3 + 2] };
400+
std::sort(ai.begin(), ai.end());
401+
std::sort(bi.begin(), bi.end());
402+
std::array<uint32, 3> out = {};
403+
auto it = std::set_intersection(ai.begin(), ai.end(), bi.begin(), bi.end(), out.begin());
404+
return it == out.begin() + 2;
405+
};
406+
const auto &fixOrientation = [&](uint32 a, uint32 b)
407+
{
408+
CAGE_ASSERT(finished[a] && !finished[b]);
409+
CAGE_ASSERT(areNeighbors(a, b));
410+
std::array<uint32 *, 3> ai = { &impl->indices[a * 3 + 0], &impl->indices[a * 3 + 1], &impl->indices[a * 3 + 2] };
411+
std::array<uint32 *, 3> bi = { &impl->indices[b * 3 + 0], &impl->indices[b * 3 + 1], &impl->indices[b * 3 + 2] };
412+
const auto &contains = [](const std::array<uint32 *, 3> arr, uint32 *v) -> bool { return *arr[0] == *v || *arr[1] == *v || *arr[2] == *v; };
413+
CAGE_ASSERT(contains(ai, bi[0]) + contains(ai, bi[1]) + contains(ai, bi[2]) == 2);
414+
CAGE_ASSERT(contains(bi, ai[0]) + contains(bi, ai[1]) + contains(bi, ai[2]) == 2);
415+
while (contains(bi, ai[0]))
416+
turnLeft(ai[0], ai[1], ai[2]);
417+
while (contains(ai, bi[0]))
418+
turnLeft(bi[0], bi[1], bi[2]);
419+
CAGE_ASSERT(*ai[0] != *bi[0]);
420+
CAGE_ASSERT(*ai[1] == *bi[1] || *ai[1] == *bi[2]);
421+
CAGE_ASSERT(*ai[2] == *bi[2] || *ai[2] == *bi[1]);
422+
if (*ai[1] == *bi[1])
423+
std::swap(*bi[1], *bi[2]);
424+
finished[b] = true;
425+
};
426+
const auto &fixNeighbors = [&](uint32 tri)
427+
{
428+
CAGE_ASSERT(finished[tri]);
429+
for (uint32 v = 0; v < 3; v++)
430+
{
431+
for (uint32 adj : adjacent[impl->indices[tri * 3 + v]])
432+
{
433+
if (finished[adj])
434+
continue;
435+
if (!areNeighbors(tri, adj))
436+
continue;
437+
fixOrientation(tri, adj);
438+
queue.push_back(adj);
439+
}
440+
}
441+
};
442+
const auto &process = [&](uint32 start)
443+
{
444+
CAGE_ASSERT(queue.empty());
445+
queue.push_back(start);
446+
while (!queue.empty())
447+
{
448+
const uint32 t = queue.back();
449+
queue.pop_back();
450+
fixNeighbors(t);
451+
}
452+
};
453+
for (uint32 t = 0; t < tris; t++)
454+
{
455+
if (finished[t])
456+
continue;
457+
finished[t] = true; // start of a component
458+
process(t);
459+
CAGE_ASSERT(finished[t]);
460+
}
461+
CAGE_ASSERT(queue.empty());
462+
}
463+
375464
namespace
376465
{
377466
struct UnionFind

sources/test-core/mesh.cpp

+23
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,28 @@ namespace
951951
meshApplyTransform(+msh, Mat4(Mat3(10, 0, 0, 0, 0, 10, 0, -10, 0)));
952952
approxEqual(msh->boundingBox(), Aabb(Vec3(-10, -20, -10), Vec3(10, 20, 10)));
953953
}
954+
955+
void testMeshConsistentWinding()
956+
{
957+
CAGE_TESTCASE("mesh consistent winding");
958+
Holder<Mesh> msh = makeDoubleBalls();
959+
Holder<Collider> col = newCollider();
960+
col->importMesh(+msh);
961+
col->optimize();
962+
msh->importCollider(+col);
963+
meshMergeCloseVertices(+msh, {});
964+
const uint32 cnt = msh->indicesCount();
965+
CAGE_TEST(cnt == 1080); // sanity check
966+
{
967+
Holder<Mesh> cp = msh->copy();
968+
CAGE_TEST_THROWN(meshSimplify(+cp, {})); // sanity check
969+
}
970+
msh->exportFile("meshes/algorithms/consistentWindingBefore.obj");
971+
meshConsistentWinding(+msh);
972+
msh->exportFile("meshes/algorithms/consistentWindingAfter.obj");
973+
CAGE_TEST(msh->indicesCount() == cnt);
974+
meshSimplify(+msh, {}); // make sure that the resulting mesh has usable topology
975+
}
954976
}
955977

956978
void testMesh()
@@ -962,4 +984,5 @@ void testMesh()
962984
testMeshExports();
963985
testMeshRetexture();
964986
testMeshLines();
987+
testMeshConsistentWinding();
965988
}

0 commit comments

Comments
 (0)