8000 Fixed edge collapse by elalish · Pull Request #137 · elalish/manifold · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Fixed edge collapse #137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,18 @@
"set": "cpp",
"memory_resource": "cpp",
"random": "cpp",
"variant": "cpp"
"variant": "cpp",
"hash_map": "cpp",
"hash_set": "cpp",
"codecvt": "cpp",
"regex": "cpp",
"future": "cpp",
"shared_mutex": "cpp"
},
"C_Cpp.clang_format_fallbackStyle": "google",
"editor.formatOnSave": true,
"cmake.configureArgs": [
"-DTHRUST_BACKEND=CPP"
"-DMANIFOLD_USE_CUDA=OFF",
"-DMANIFOLD_PAR=NONE"
],
}
76 changes: 75 additions & 1 deletion manifold/src/edge_op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ __host__ __device__ bool Is01Longest(glm::vec2 v0, glm::vec2 v1, glm::vec2 v2) {
return l[0] > l[1] && l[0] > l[2];
}

struct DuplicateEdge {
const Halfedge* sortedHalfedge;

__host__ __device__ bool operator()(int edge) {
const Halfedge& halfedge = sortedHalfedge[edge];
const Halfedge& nextHalfedge = sortedHalfedge[edge + 1];
return halfedge.startVert == nextHalfedge.startVert &&
halfedge.endVert == nextHalfedge.endVert;
}
};

struct ShortEdge {
const Halfedge* halfedge;
const glm::vec3* vertPos;
Expand Down Expand Up @@ -110,6 +121,11 @@ namespace manifold {
* It also performs edge swaps on the long edges of degenerate triangles, though
* there are some configurations of degenerates that cannot be removed this way.
*
* Before collapsing edges, the mesh is checked for duplicate edges (more than
* one pair of triangles sharing the same edge), which are removed by
* duplicating one vert and adding two triangles. These degenerate triangles are
* likely to be collapsed again in the subsequent simplification.
*
* Note when an edge collapse would result in something non-manifold, the
* vertices are duplicated in such a way as to remove handles or separate
* meshes, thus decreasing the Genus(). It only increases when meshes that have
Expand All @@ -119,9 +135,26 @@ namespace manifold {
* removal, by setting vertPos to NaN and halfedge to {-1, -1, -1, -1}.
*/
void Manifold::Impl::SimplifyTopology() {
VecDH<int> flaggedEdges(halfedge_.size());
auto policy = autoPolicy(halfedge_.size());

VecDH<Halfedge> halfedge(halfedge_);
VecDH<int> idx(halfedge_.size());
sequence(policy, idx.begin(), idx.end());
sort_by_key(policy, halfedge.begin(), halfedge.end(), idx.begin());

VecDH<int> flaggedEdges(halfedge_.size());

int numFlagged =
copy_if<decltype(flaggedEdges.begin())>(
policy, idx.begin(), idx.end() - 1, countAt(0), flaggedEdges.begin(),
DuplicateEdge({halfedge.cptrD()})) -
flaggedEdges.begin();
flaggedEdges.resize(numFlagged);

for (const int edge : flaggedEdges) DedupeEdge(edge);

flaggedEdges.resize(halfedge_.size());
numFlagged =
copy_if<decltype(flaggedEdges.begin())>(
policy, countAt(0), countAt(halfedge_.size()), flaggedEdges.begin(),
ShortEdge({halfedge_.cptrD(), vertPos_.cptrD(), precision_})) -
Expand Down Expand Up @@ -154,6 +187,47 @@ void Manifold::Impl::SimplifyTopology() {
}
}

void Manifold::Impl::DedupeEdge(const int edge) {
// Orbit endVert
const int startVert = halfedge_[edge].startVert;
const int endVert = halfedge_[edge].endVert;
int current = halfedge_[NextHalfedge(edge)].pairedHalfedge;
while (current != edge) {
const int vert = halfedge_[current].startVert;
if (vert == startVert) {
int newVert = vertPos_.size();
vertPos_.push_back(vertPos_[endVert]);
current = halfedge_[NextHalfedge(current)].pairedHalfedge;
int opposite = halfedge_[NextHalfedge(edge)].pairedHalfedge;

UpdateVert(newVert, current, opposite);

int newHalfedge = halfedge_.size();
int newFace = newHalfedge / 3;
int outsideVert = halfedge_[current].startVert;
halfedge_.push_back({endVert, newVert, -1, newFace});
halfedge_.push_back({newVert, outsideVert, -1, newFace});
halfedge_.push_back({outsideVert, endVert, -1, newFace});
PairUp(newHalfedge + 2, halfedge_[current].pairedHalfedge);
PairUp(newHalfedge + 1, current);

newHalfedge += 3;
++newFace;
outsideVert = halfedge_[opposite].startVert;
halfedge_.push_back({newVert, endVert, -1, newFace});
halfedge_.push_back({endVert, outsideVert, -1, newFace});
halfedge_.push_back({outsideVert, newVert, -1, newFace});
PairUp(newHalfedge + 2, halfedge_[opposite].pairedHalfedge);
PairUp(newHalfedge + 1, opposite);
PairUp(newHalfedge, newHalfedge - 3);

break;
}

current = halfedge_[NextHalfedge(current)].pairedHalfedge;
}
}

void Manifold::Impl::PairUp(int edge0, int edge1) {
halfedge_[edge0].pairedHalfedge = edge1;
halfedge_[edge1].pairedHalfedge = edge0;
Expand Down
2 changes: 1 addition & 1 deletion manifold/src/face_op.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ void Manifold::Impl::Face2Tri(const VecDH<int>& faceEdge,
}
}
faceNormal_ = std::move(triNormal);
CreateAndFixHalfedges(triVerts);
CreateHalfedges(triVerts);
}

/**
Expand Down
61 changes: 3 additions & 58 deletions manifold/src/impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,41 +132,6 @@ struct LinkHalfedges {
}
};

struct SwapHalfedges {
Halfedge* halfedges;
const TmpEdge* edges;

__host__ void operator()(int k) {
const int i = 2 * k;
const int j = i - 2;
const TmpEdge thisEdge = edges[i];
const TmpEdge lastEdge = edges[j];
if (thisEdge.first == lastEdge.first &&
thisEdge.second == lastEdge.second) {
const int swap0idx = thisEdge.halfedgeIdx;
Halfedge& swap0 = halfedges[swap0idx];
const int swap1idx = swap0.pairedHalfedge;
Halfedge& swap1 = halfedges[swap1idx];

const int next0idx = swap0idx + ((swap0idx + 1) % 3 == 0 ? -2 : 1);
const int next1idx = swap1idx + ((swap1idx + 1) % 3 == 0 ? -2 : 1);
Halfedge& next0 = halfedges[next0idx];
Halfedge& next1 = halfedges[next1idx];

next0.startVert = swap0.endVert = next1.endVert;
swap0.pairedHalfedge = next1.pairedHalfedge;
halfedges[swap0.pairedHalfedge].pairedHalfedge = swap0idx;

next1.startVert = swap1.endVert = next0.endVert;
swap1.pairedHalfedge = next0.pairedHalfedge;
halfedges[swap1.pairedHalfedge].pairedHalfedge = swap1idx;

next0.pairedHalfedge = next1idx;
next1.pairedHalfedge = next0idx;
}
}
};

struct InitializeBaryRef {
const int meshID;
const Halfedge* halfedge;
Expand Down Expand Up @@ -297,7 +262,7 @@ Manifold::Impl::Impl(const Mesh& mesh,
CheckDevice();
CalculateBBox();
SetPrecision();
CreateAndFixHalfedges(mesh.triVerts);
CreateHalfedges(mesh.triVerts);
ALWAYS_ASSERT(IsManifold(), topologyErr, "Input mesh is not manifold!");
CalculateNormals();
InitializeNewReference(triProperties, properties, propertyTolerance);
Expand Down Expand Up @@ -478,23 +443,6 @@ int Manifold::Impl::InitializeNewReference(
* Create the halfedge_ data structure from an input triVerts array like Mesh.
*/
void Manifold::Impl::CreateHalfedges(const VecDH<glm::ivec3>& triVerts) {
const int numTri = triVerts.size();
halfedge_.resize(3 * numTri);
VecDH<TmpEdge> edge(3 * numTri);
auto policy = autoPolicy(numTri);
for_each_n(policy, zip(countAt(0), triVerts.begin()), numTri,
Tri2Halfedges({halfedge_.ptrD(), edge.ptrD()}));
sort(policy, edge.begin(), edge.end());
for_each_n(policy, countAt(0), halfedge_.size() / 2,
LinkHalfedges({halfedge_.ptrD(), edge.cptrD()}));
}

/**
* Create the halfedge_ data structure from an input triVerts array like Mesh.
* Check that the input is an even-manifold, and if it is not 2-manifold,
* perform edge swaps until it is. This is a host function.
*/
void Manifold::Impl::CreateAndFixHalfedges(const VecDH<glm::ivec3>& triVerts) {
const int numTri = triVerts.size();
// drop the old value first to avoid copy
halfedge_.resize(0);
Expand All @@ -506,14 +454,11 @@ void Manifold::Impl::CreateAndFixHalfedges(const VecDH<glm::ivec3>& triVerts) {
// Stable sort is required here so that halfedges from the same face are
// paired together (the triangles were created in face order). In some
// degenerate situations the triangulator can add the same internal edge in
// two different faces, causing this edge to not be 2-manifold. We detect this
// and fix it by swapping one of the identical edges, so it is important that
// we have the edges paired according to their face.
// two different faces, causing this edge to not be 2-manifold. These are
// fixed by duplicating verts in SimplifyTopology.
stable_sort(policy, edge.begin(), edge.end());
thrust::for_each_n(thrust::host, countAt(0), halfedge_.size() / 2,
LinkHalfedges({halfedge_.ptrH(), edge.cptrH()}));
thrust::for_each(thrust::host, countAt(1), countAt(halfedge_.size() / 2),
SwapHalfedges({halfedge_.ptrH(), edge.cptrH()}));
}

/**
Expand Down
2 changes: 1 addition & 1 deletion manifold/src/impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ struct Manifold::Impl {

void ReinitializeReference(int meshID);
void CreateHalfedges(const VecDH<glm::ivec3>& triVerts);
void CreateAndFixHalfedges(const VecDH<glm::ivec3>& triVerts);
void CalculateNormals();
void UpdateMeshIDs(VecDH<int>& meshIDs, VecDH<int>& originalIDs,
int startTri = 0, int n = -1, int startID = 0);
Expand Down Expand Up @@ -109,6 +108,7 @@ struct Manifold::Impl {

// edge_op.cu
void SimplifyTopology();
void DedupeEdge(int edge);
void CollapseEdge(int edge);
void RecursiveEdgeSwap(int edge);
void RemoveIfFolded(int edge);
Expand Down
8 changes: 5 additions & 3 deletions manifold/src/properties.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,14 @@ bool Manifold::Impl::IsManifold() const {
auto policy = autoPolicy(halfedge_.size());
bool isManifold = all_of(policy, countAt(0), countAt(halfedge_.size()),
CheckManifold({halfedge_.cptrD()}));
// std::cout << (isManifold ? "" : "Not ") << "Manifold" << std::endl;

VecDH<Halfedge> halfedge(halfedge_);
sort(policy, halfedge.begin(), halfedge.end());
isManifold &= all_of(policy, countAt(0), countAt(2 * NumEdge() - 1),
NoDuplicates({halfedge.cptrD()}));
return isManifold;
bool noDupes = all_of(policy, countAt(0), countAt(2 * NumEdge() - 1),
NoDuplicates({halfedge.cptrD()}));
// std::cout << (noDupes ? "" : "Not ") << "2-Manifold" << std::endl;
return isManifold && noDupes;
}

/**
Expand Down
40 changes: 28 additions & 12 deletions test/mesh_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -781,24 +781,25 @@ TEST(Boolean, Gyroid) {
Related(result, input, meshID2idx);
}

TEST(Boolean, DISABLED_Cylinders) {
Manifold rod = Manifold::Cylinder(1.0, 0.4, -1.0, 20);
TEST(Boolean, Cylinders) {
Manifold rod = Manifold::Cylinder(1.0, 0.4, -1.0, 12);
float arrays1[][12] = {
{0, 0, 1, 3, //
-1, 0, 0, 3, //
0, -1, 0, 6}, //
{0, 0, 1, 2, //
-1, 0, 0, 3, //
0, -1, 0, 8}, //

{0, 0, 1, 1, //
-1, 0, 0, 2, //
0, -1, 0, 7}, //
{1, 0, 0, 3, //
0, 1, 0, 2, //
0, 0, 1, 6}, //
{0, 0, 1, 3, //
-1, 0, 0, 3, //
0, -1, 0, 6}, //
{0, 0, 1, 3, //
-1, 0, 0, 3, //
0, -1, 0, 7}, //
{0, 0, 1, 2, //
-1, 0, 0, 3, //
0, -1, 0, 8}, //
{0, 0, 1, 1, //
-1, 0, 0, 3, //
0, -1, 0, 7}, //
Expand All @@ -810,15 +811,16 @@ TEST(Boolean, DISABLED_Cylinders) {
0, -1, 0, 6}, //
};
float arrays2[][12] = {
{0, 0, 1, 2, //
-1, 0, 0, 2, //
0, -1, 0, 7}, //
{1, 0, 0, 3, //
0, 0, 1, 2, //
0, -1, 0, 6}, //
{1, 0, 0, 4, //
0, 1, 0, 3, //
0, 0, 1, 6}, //

{0, 0, 1, 2, //
-1, 0, 0, 2, //
0, -1, 0, 7}, //
{1, 0, 0, 3, //
0, 1, 0, 3, //
0, 0, 1, 7}, //
Expand Down Expand Up @@ -860,10 +862,24 @@ TEST(Boolean, DISABLED_Cylinders) {
}
m2 += Manifold(rod).Transform(mat);
}

m1 += m2;

EXPECT_TRUE(m1.IsManifold());
EXPECT_TRUE(m1.MatchesTriNormals());
EXPECT_LE(m1.NumDegenerateTris(), 12);
}

TEST(Boolean, Cubes) {
Manifold result = Manifold::Cube({1.2, 1, 1}, true).Translate({0, -0.5, 0.5});
result += Manifold::Cube({1, 0.8, 0.5}).Translate({-0.5, 0, 0.5});
result += Manifold::Cube({1.2, 0.1, 0.5}).Translate({-0.6, -0.1, 0});

EXPECT_TRUE(result.IsManifold());
EXPECT_TRUE(result.MatchesTriNormals());
EXPECT_LE(result.NumDegenerateTris(), 0);
auto prop = result.GetProperties();
EXPECT_NEAR(prop.volume, 1.6, 0.001);
EXPECT_NEAR(prop.surfaceArea, 9.2, 0.01);

if (options.exportModels) ExportMesh("cubes.glb", result.GetMesh(), {});
}
2 changes: 0 additions & 2 deletions test/test_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ void print_usage() {
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);

int c;

for (int i = 1; i < argc; i++) {
if (argv[i][0] != '-') {
fprintf(stderr, "Unknown option: %s\n", argv[i]);
Expand Down
0