From 0a51ae2a0cc206db37ad6c6e27da245d65edb057 Mon Sep 17 00:00:00 2001 From: Clifford Yapp <238416+starseeker@users.noreply.github.com> Date: Sat, 19 Oct 2024 00:11:55 -0400 Subject: [PATCH] Define a test for simple offset booleans (#998) * Define a test for simple offset booleans This is intended to check for performance regressions - the BRL-CAD logic performing this operation using v2.4.5 ran considerably faster. Note - removing in-loop assertions for triangle count > 0 with the intermediate forms greatly increases the speed, but they are placed there deliberately to simulate what BRL-CAD does to validate the output of the booleans on an incremental basis. In the event of a boolean failure or invalid output being produced for any reason, we want to be able to capture the *exact* inputs responsible - which means we need to check after each boolean operation to validate the results and catch any bad intermediate states at the time they are produced. * formatting * More formatting * Per suggestion from elalish, simplify edge pair setup * Switch to a Status() check - appears to have the same slowing effect. * Move the checks out of the loops * Use initializer lists for better compactness * Restore in-loop checks with explanation * Simplify with += syntax. * Revert "Simplify with += syntax." Getting CI failure, see if this had an impact somehow... This reverts commit 55ca9a9dea3f3fddad14ea7858603e35a4a2f629. * Revert "Use initializer lists for better compactness" Still not passing?? Revert another commit. This reverts commit 89065dd7833d55933c8367f4734091ae43b4517e. * Reapply "Simplify with += syntax." OK, starting from a passing state, see if this works. This reverts commit ba6027e003e4d6d64deda35eed8c540bff4a0f80. * Break the initialization changes down - start with faces array. * Initialize pts array * Ah ha. There it is. Indexing problem with second half of pnts setup. * formatting * Test * Revert "Test" This reverts commit c137a4825824c43a56ba51ea32b073072c9af20e. * See if the failure triggers if we only check once at the end. * Revert "See if the failure triggers if we only check once at the end." This reverts commit 35f0eb0c96685defbfb44910f75bf5974b5c754e. --- test/boolean_complex_test.cpp | 111 +++++++++++++++++++++++++++ test/models/Generic_Twin_91.1.t0.glb | Bin 0 -> 6204 bytes 2 files changed, 111 insertions(+) create mode 100644 test/models/Generic_Twin_91.1.t0.glb diff --git a/test/boolean_complex_test.cpp b/test/boolean_complex_test.cpp index 5f99e88eb..ca5b1517f 100644 --- a/test/boolean_complex_test.cpp +++ b/test/boolean_complex_test.cpp @@ -15,6 +15,7 @@ #ifdef MANIFOLD_CROSS_SECTION #include "manifold/cross_section.h" #endif +#include "../src/utils.h" // For RotateUp #include "manifold/manifold.h" #include "manifold/polygon.h" #include "test.h" @@ -1030,4 +1031,114 @@ TEST(BooleanComplex, HullMask) { MeshGL mesh = ret.GetMeshGL(); } +// Note - For the moment, the Status() checks are included in the loops to +// (more or less) mimic the BRL-CAD behavior of checking the mesh for +// unexpected output after each iteration. Doing so is not ideal - it +// *massively* slows the overall evaluation - but it also seems to be +// triggering behavior that avoids a triangulation failure. +// +// Eventually, once other issues are resolved, the in-loop checks should be +// removed in favor of the top level checks. +TEST(BooleanComplex, SimpleOffset) { + std::string file = __FILE__; + std::string dir = file.substr(0, file.rfind('/')); + MeshGL seeds = ImportMesh(dir + "/models/" + "Generic_Twin_91.1.t0.glb"); + EXPECT_TRUE(seeds.NumTri() > 10); + EXPECT_TRUE(seeds.NumVert() > 10); + // Unique edges + std::vector> edges; + for (size_t i = 0; i < seeds.NumTri(); i++) { + const int k[3] = {1, 2, 0}; + for (const int j : {0, 1, 2}) { + int v1 = seeds.triVerts[i * 3 + j]; + int v2 = seeds.triVerts[i * 3 + k[j]]; + if (v2 > v1) edges.push_back(std::make_pair(v1, v2)); + } + } + manifold::Manifold c; + // Vertex Spheres + Manifold sph = Manifold::Sphere(1, 8); + for (size_t i = 0; i < seeds.NumVert(); i++) { + vec3 vpos(seeds.vertProperties[3 * i + 0], seeds.vertProperties[3 * i + 1], + seeds.vertProperties[3 * i + 2]); + Manifold vsph = sph.Translate(vpos); + if (!vsph.NumTri()) continue; + c += vsph; + // See above discussion + EXPECT_EQ(c.Status(), Manifold::Error::NoError); + } + // See above discussion + // EXPECT_EQ(c.Status(), Manifold::Error::NoError); + // Edge Cylinders + for (size_t i = 0; i < edges.size(); i++) { + vec3 ev1 = vec3(seeds.vertProperties[3 * edges[i].first + 0], + seeds.vertProperties[3 * edges[i].first + 1], + seeds.vertProperties[3 * edges[i].first + 2]); + vec3 ev2 = vec3(seeds.vertProperties[3 * edges[i].second + 0], + seeds.vertProperties[3 * edges[i].second + 1], + seeds.vertProperties[3 * edges[i].second + 2]); + vec3 edge = ev2 - ev1; + double len = la::length(edge); + if (len < std::numeric_limits::min()) continue; + // TODO - workaround, shouldn't be necessary + if (len < 0.03) continue; + manifold::Manifold origin_cyl = manifold::Manifold::Cylinder(len, 1, 1, 8); + vec3 evec(-1 * edge.x, -1 * edge.y, edge.z); + manifold::Manifold rotated_cyl = + origin_cyl.Transform(manifold::RotateUp(evec)); + manifold::Manifold right = rotated_cyl.Translate(ev1); + if (!right.NumTri()) continue; + c += right; + // See above discussion + EXPECT_EQ(c.Status(), Manifold::Error::NoError); + } + // See above discussion + // EXPECT_EQ(c.Status(), Manifold::Error::NoError); + // Triangle Volumes + for (size_t i = 0; i < seeds.NumTri(); i++) { + int eind[3]; + for (int j = 0; j < 3; j++) eind[j] = seeds.triVerts[i * 3 + j]; + std::vector ev; + for (int j = 0; j < 3; j++) { + ev.push_back(vec3(seeds.vertProperties[3 * eind[j] + 0], + seeds.vertProperties[3 * eind[j] + 1], + seeds.vertProperties[3 * eind[j] + 2])); + } + vec3 a = ev[0] - ev[2]; + vec3 b = ev[1] - ev[2]; + vec3 n = la::normalize(la::cross(a, b)); + // Extrude the points above and below the plane of the triangle + vec3 pnts[6]; + for (int j = 0; j < 3; j++) pnts[j] = ev[j] + n; + for (int j = 3; j < 6; j++) pnts[j] = ev[j - 3] - n; + // Construct the points and faces of the new manifold + double pts[3 * 6] = {pnts[4].x, pnts[4].y, pnts[4].z, pnts[3].x, pnts[3].y, + pnts[3].z, pnts[0].x, pnts[0].y, pnts[0].z, pnts[1].x, + pnts[1].y, pnts[1].z, pnts[5].x, pnts[5].y, pnts[5].z, + pnts[2].x, pnts[2].y, pnts[2].z}; + int faces[24] = { + faces[0] = 0, faces[1] = 1, faces[2] = 4, // 1 2 5 + faces[3] = 2, faces[4] = 3, faces[5] = 5, // 3 4 6 + faces[6] = 1, faces[7] = 0, faces[8] = 3, // 2 1 4 + faces[9] = 3, faces[10] = 2, faces[11] = 1, // 4 3 2 + faces[12] = 3, faces[13] = 0, faces[14] = 4, // 4 1 5 + faces[15] = 4, faces[16] = 5, faces[17] = 3, // 5 6 4 + faces[18] = 5, faces[19] = 4, faces[20] = 1, // 6 5 2 + faces[21] = 1, faces[22] = 2, faces[23] = 5 // 2 3 6 + }; + manifold::MeshGL64 tri_m; + for (int j = 0; j < 18; j++) + tri_m.vertProperties.insert(tri_m.vertProperties.end(), pts[j]); + for (int j = 0; j < 24; j++) + tri_m.triVerts.insert(tri_m.triVerts.end(), faces[j]); + manifold::Manifold right(tri_m); + if (!right.NumTri()) continue; + c += right; + // See above discussion + EXPECT_EQ(c.Status(), Manifold::Error::NoError); + } + // See above discussion + // EXPECT_EQ(c.Status(), Manifold::Error::NoError); +} + #endif diff --git a/test/models/Generic_Twin_91.1.t0.glb b/test/models/Generic_Twin_91.1.t0.glb new file mode 100644 index 0000000000000000000000000000000000000000..e0f6906e521eb020ac9fe79f2915f6d821d5ca94 GIT binary patch literal 6204 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�yeeGId&rb|4QFJ^?82i5hKj?y!pJbUsEWW4=5~{)nA`tlWXep#V=Wj`W;&da zVV)H!^BvB}l=+5N7#ZeS5oTCXS(rx#hA_j5l$j1^WSC(^%1nndGR(IkWxm51nKIw- z3M0cjE5ZybQf4}wkzs}vDKj0;$S}`}l=%*4WXi0=D~zlqSB0pXd4(!PRwdXaDj2