Path: blob/master/thirdparty/manifold/src/edge_op.cpp
20997 views
// Copyright 2021 The Manifold Authors.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314#include <unordered_map>1516#include "impl.h"17#include "parallel.h"18#include "shared.h"1920namespace {21using namespace manifold;2223ivec3 TriOf(int edge) {24ivec3 triEdge;25triEdge[0] = edge;26triEdge[1] = NextHalfedge(triEdge[0]);27triEdge[2] = NextHalfedge(triEdge[1]);28return triEdge;29}3031bool Is01Longest(vec2 v0, vec2 v1, vec2 v2) {32const vec2 e[3] = {v1 - v0, v2 - v1, v0 - v2};33double l[3];34for (int i : {0, 1, 2}) l[i] = la::dot(e[i], e[i]);35return l[0] > l[1] && l[0] > l[2];36}3738struct DuplicateEdge {39const Halfedge* sortedHalfedge;4041bool operator()(int edge) {42const Halfedge& halfedge = sortedHalfedge[edge];43const Halfedge& nextHalfedge = sortedHalfedge[edge + 1];44return halfedge.startVert == nextHalfedge.startVert &&45halfedge.endVert == nextHalfedge.endVert;46}47};4849struct ShortEdge {50VecView<const Halfedge> halfedge;51VecView<const vec3> vertPos;52const double epsilon;53const int firstNewVert;5455bool operator()(int edge) const {56const Halfedge& half = halfedge[edge];57if (half.pairedHalfedge < 0 ||58(half.startVert < firstNewVert && half.endVert < firstNewVert))59return false;60// Flag short edges61const vec3 delta = vertPos[half.endVert] - vertPos[half.startVert];62return la::dot(delta, delta) < epsilon * epsilon;63}64};6566struct FlagEdge {67VecView<const Halfedge> halfedge;68VecView<const TriRef> triRef;69const int firstNewVert;7071bool operator()(int edge) const {72const Halfedge& half = halfedge[edge];73if (half.pairedHalfedge < 0 || half.startVert < firstNewVert) return false;74// Flag redundant edges - those where the startVert is surrounded by only75// two original triangles.76const TriRef ref0 = triRef[edge / 3];77int current = NextHalfedge(half.pairedHalfedge);78TriRef ref1 = triRef[current / 3];79bool ref1Updated = !ref0.SameFace(ref1);80while (current != edge) {81current = NextHalfedge(halfedge[current].pairedHalfedge);82int tri = current / 3;83const TriRef ref = triRef[tri];84if (!ref.SameFace(ref0) && !ref.SameFace(ref1)) {85if (!ref1Updated) {86ref1 = ref;87ref1Updated = true;88} else {89return false;90}91}92}93return true;94}95};9697struct SwappableEdge {98VecView<const Halfedge> halfedge;99VecView<const vec3> vertPos;100VecView<const vec3> triNormal;101const double tolerance;102const int firstNewVert;103104bool operator()(int edge) const {105const Halfedge& half = halfedge[edge];106if (half.pairedHalfedge < 0) return false;107if (half.startVert < firstNewVert && half.endVert < firstNewVert &&108halfedge[NextHalfedge(edge)].endVert < firstNewVert &&109halfedge[NextHalfedge(half.pairedHalfedge)].endVert < firstNewVert)110return false;111112int tri = edge / 3;113ivec3 triEdge = TriOf(edge);114mat2x3 projection = GetAxisAlignedProjection(triNormal[tri]);115vec2 v[3];116for (int i : {0, 1, 2})117v[i] = projection * vertPos[halfedge[triEdge[i]].startVert];118if (CCW(v[0], v[1], v[2], tolerance) > 0 || !Is01Longest(v[0], v[1], v[2]))119return false;120121// Switch to neighbor's projection.122edge = half.pairedHalfedge;123tri = edge / 3;124triEdge = TriOf(edge);125projection = GetAxisAlignedProjection(triNormal[tri]);126for (int i : {0, 1, 2})127v[i] = projection * vertPos[halfedge[triEdge[i]].startVert];128return CCW(v[0], v[1], v[2], tolerance) > 0 ||129Is01Longest(v[0], v[1], v[2]);130}131};132133struct FlagStore {134#if MANIFOLD_PAR == 1135tbb::combinable<std::vector<size_t>> store;136#endif137std::vector<size_t> s;138139template <typename Pred, typename F>140void run_seq(size_t n, Pred pred, F f) {141for (size_t i = 0; i < n; ++i)142if (pred(i)) s.push_back(i);143for (size_t i : s) f(i);144s.clear();145}146147#if MANIFOLD_PAR == 1148template <typename Pred, typename F>149void run_par(size_t n, Pred pred, F f) {150// Test pred in parallel, store i into thread-local vectors when pred(i) is151// true. After testing pred, iterate and call f over the indices in152// ascending order by using a heap in a single thread153auto& store = this->store;154tbb::parallel_for(tbb::blocked_range<size_t>(0, n),155[&store, &pred](const auto& r) {156auto& local = store.local();157for (auto i = r.begin(); i < r.end(); ++i) {158if (pred(i)) local.push_back(i);159}160});161162std::vector<std::vector<size_t>> stores;163std::vector<size_t> result;164store.combine_each(165[&](auto& data) { stores.emplace_back(std::move(data)); });166std::vector<size_t> sizes;167size_t total_size = 0;168for (const auto& tmp : stores) {169sizes.push_back(total_size);170total_size += tmp.size();171}172result.resize(total_size);173for_each_n(ExecutionPolicy::Seq, countAt(0), stores.size(), [&](size_t i) {174std::copy(stores[i].begin(), stores[i].end(), result.begin() + sizes[i]);175});176stable_sort(autoPolicy(result.size()), result.begin(), result.end());177for (size_t x : result) f(x);178}179#endif180181template <typename Pred, typename F>182void run(size_t n, Pred pred, F f) {183#if MANIFOLD_PAR == 1184if (n > 1e5) {185run_par(n, pred, f);186} else187#endif188{189run_seq(n, pred, f);190}191}192};193194} // namespace195196namespace manifold {197198/**199* Duplicates just enough verts to covert an even-manifold to a proper200* 2-manifold, splitting non-manifold verts and edges with too many triangles.201*/202void Manifold::Impl::CleanupTopology() {203if (!halfedge_.size()) return;204DEBUG_ASSERT(IsManifold(), logicErr, "polygon mesh is not manifold!");205206// In the case of a very bad triangulation, it is possible to create pinched207// verts. They must be removed before edge collapse.208SplitPinchedVerts();209DedupeEdges();210}211212/**213* Collapses degenerate triangles by removing edges shorter than tolerance_ and214* any edge that is preceeded by an edge that joins the same two face relations.215* It also performs edge swaps on the long edges of degenerate triangles, though216* there are some configurations of degenerates that cannot be removed this way.217*218* Before collapsing edges, the mesh is checked for duplicate edges (more than219* one pair of triangles sharing the same edge), which are removed by220* duplicating one vert and adding two triangles. These degenerate triangles are221* likely to be collapsed again in the subsequent simplification.222*223* Note when an edge collapse would result in something non-manifold, the224* vertices are duplicated in such a way as to remove handles or separate225* meshes, thus decreasing the Genus(). It only increases when meshes that have226* collapsed to just a pair of triangles are removed entirely.227*228* Verts with index less than firstNewVert will be left uncollapsed. This is229* zero by default so that everything can be collapsed.230*231* Rather than actually removing the edges, this step merely marks them for232* removal, by setting vertPos to NaN and halfedge to {-1, -1, -1, -1}.233*/234void Manifold::Impl::SimplifyTopology(int firstNewVert) {235if (!halfedge_.size()) return;236237CleanupTopology();238CollapseShortEdges(firstNewVert);239CollapseColinearEdges(firstNewVert);240SwapDegenerates(firstNewVert);241}242243void Manifold::Impl::RemoveDegenerates(int firstNewVert) {244if (!halfedge_.size()) return;245246CleanupTopology();247CollapseShortEdges(firstNewVert);248SwapDegenerates(firstNewVert);249}250251void Manifold::Impl::CollapseShortEdges(int firstNewVert) {252ZoneScopedN("CollapseShortEdge");253FlagStore s;254size_t numFlagged = 0;255const size_t nbEdges = halfedge_.size();256257std::vector<int> scratchBuffer;258scratchBuffer.reserve(10);259// Short edges get to skip several checks and hence remove more classes of260// degenerate triangles than flagged edges do, but this could in theory lead261// to error stacking where a vertex moves too far. For this reason this is262// restricted to epsilon, rather than tolerance.263ShortEdge se{halfedge_, vertPos_, epsilon_, firstNewVert};264s.run(nbEdges, se, [&](size_t i) {265const bool didCollapse = CollapseEdge(i, scratchBuffer);266if (didCollapse) numFlagged++;267scratchBuffer.resize(0);268});269270#ifdef MANIFOLD_DEBUG271if (ManifoldParams().verbose > 0 && numFlagged > 0) {272std::cout << "collapsed " << numFlagged << " short edges" << std::endl;273}274#endif275}276277void Manifold::Impl::CollapseColinearEdges(int firstNewVert) {278FlagStore s;279size_t numFlagged = 0;280const size_t nbEdges = halfedge_.size();281std::vector<int> scratchBuffer;282scratchBuffer.reserve(10);283while (1) {284ZoneScopedN("CollapseFlaggedEdge");285numFlagged = 0;286// Collapse colinear edges, but only remove new verts, i.e. verts with287// index288// >= firstNewVert. This is used to keep the Boolean from changing the289// non-intersecting parts of the input meshes. Colinear is defined not by a290// local check, but by the global MarkCoplanar function, which keeps this291// from being vulnerable to error stacking.292FlagEdge se{halfedge_, meshRelation_.triRef, firstNewVert};293s.run(nbEdges, se, [&](size_t i) {294const bool didCollapse = CollapseEdge(i, scratchBuffer);295if (didCollapse) numFlagged++;296scratchBuffer.resize(0);297});298if (numFlagged == 0) break;299300#ifdef MANIFOLD_DEBUG301if (ManifoldParams().verbose > 0 && numFlagged > 0) {302std::cout << "collapsed " << numFlagged << " colinear edges" << std::endl;303}304#endif305}306}307308void Manifold::Impl::SwapDegenerates(int firstNewVert) {309ZoneScopedN("RecursiveEdgeSwap");310FlagStore s;311size_t numFlagged = 0;312const size_t nbEdges = halfedge_.size();313std::vector<int> scratchBuffer;314scratchBuffer.reserve(10);315316SwappableEdge se{halfedge_, vertPos_, faceNormal_, tolerance_, firstNewVert};317std::vector<int> edgeSwapStack;318std::vector<int> visited(halfedge_.size(), -1);319int tag = 0;320s.run(nbEdges, se, [&](size_t i) {321numFlagged++;322tag++;323RecursiveEdgeSwap(i, tag, visited, edgeSwapStack, scratchBuffer);324while (!edgeSwapStack.empty()) {325int last = edgeSwapStack.back();326edgeSwapStack.pop_back();327RecursiveEdgeSwap(last, tag, visited, edgeSwapStack, scratchBuffer);328}329});330331#ifdef MANIFOLD_DEBUG332if (ManifoldParams().verbose > 0 && numFlagged > 0) {333std::cout << "swapped " << numFlagged << " edges" << std::endl;334}335#endif336}337338// Deduplicate the given 4-manifold edge by duplicating endVert, thus making the339// edges distinct. Also duplicates startVert if it becomes pinched.340void Manifold::Impl::DedupeEdge(const int edge) {341// Orbit endVert342const int startVert = halfedge_[edge].startVert;343const int endVert = halfedge_[edge].endVert;344const int endProp = halfedge_[NextHalfedge(edge)].propVert;345int current = halfedge_[NextHalfedge(edge)].pairedHalfedge;346while (current != edge) {347const int vert = halfedge_[current].startVert;348if (vert == startVert) {349// Single topological unit needs 2 faces added to be split350const int newVert = vertPos_.size();351vertPos_.push_back(vertPos_[endVert]);352if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);353current = halfedge_[NextHalfedge(current)].pairedHalfedge;354const int opposite = halfedge_[NextHalfedge(edge)].pairedHalfedge;355356UpdateVert(newVert, current, opposite);357358int newHalfedge = halfedge_.size();359int oldFace = current / 3;360int outsideVert = halfedge_[current].startVert;361halfedge_.push_back({endVert, newVert, -1, endProp});362halfedge_.push_back({newVert, outsideVert, -1, endProp});363halfedge_.push_back(364{outsideVert, endVert, -1, halfedge_[current].propVert});365PairUp(newHalfedge + 2, halfedge_[current].pairedHalfedge);366PairUp(newHalfedge + 1, current);367if (meshRelation_.triRef.size() > 0)368meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);369if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);370371newHalfedge += 3;372oldFace = opposite / 3;373outsideVert = halfedge_[opposite].startVert;374halfedge_.push_back({newVert, endVert, -1, endProp}); // fix prop375halfedge_.push_back({endVert, outsideVert, -1, endProp});376halfedge_.push_back(377{outsideVert, newVert, -1, halfedge_[opposite].propVert});378PairUp(newHalfedge + 2, halfedge_[opposite].pairedHalfedge);379PairUp(newHalfedge + 1, opposite);380PairUp(newHalfedge, newHalfedge - 3);381if (meshRelation_.triRef.size() > 0)382meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);383if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);384385break;386}387388current = halfedge_[NextHalfedge(current)].pairedHalfedge;389}390391if (current == edge) {392// Separate topological unit needs no new faces to be split393const int newVert = vertPos_.size();394vertPos_.push_back(vertPos_[endVert]);395if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);396397ForVert(NextHalfedge(current), [this, newVert](int e) {398halfedge_[e].startVert = newVert;399halfedge_[halfedge_[e].pairedHalfedge].endVert = newVert;400});401}402403// Orbit startVert404const int pair = halfedge_[edge].pairedHalfedge;405current = halfedge_[NextHalfedge(pair)].pairedHalfedge;406while (current != pair) {407const int vert = halfedge_[current].startVert;408if (vert == endVert) {409break; // Connected: not a pinched vert410}411current = halfedge_[NextHalfedge(current)].pairedHalfedge;412}413414if (current == pair) {415// Split the pinched vert the previous split created.416const int newVert = vertPos_.size();417vertPos_.push_back(vertPos_[endVert]);418if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);419420ForVert(NextHalfedge(current), [this, newVert](int e) {421halfedge_[e].startVert = newVert;422halfedge_[halfedge_[e].pairedHalfedge].endVert = newVert;423});424}425}426427void Manifold::Impl::PairUp(int edge0, int edge1) {428halfedge_[edge0].pairedHalfedge = edge1;429halfedge_[edge1].pairedHalfedge = edge0;430}431432// Traverses CW around startEdge.endVert from startEdge to endEdge433// (edgeEdge.endVert must == startEdge.endVert), updating each edge to point434// to vert instead.435void Manifold::Impl::UpdateVert(int vert, int startEdge, int endEdge) {436int current = startEdge;437while (current != endEdge) {438halfedge_[current].endVert = vert;439current = NextHalfedge(current);440halfedge_[current].startVert = vert;441current = halfedge_[current].pairedHalfedge;442DEBUG_ASSERT(current != startEdge, logicErr, "infinite loop in decimator!");443}444}445446// In the event that the edge collapse would create a non-manifold edge,447// instead we duplicate the two verts and attach the manifolds the other way448// across this edge.449void Manifold::Impl::FormLoop(int current, int end) {450int startVert = vertPos_.size();451vertPos_.push_back(vertPos_[halfedge_[current].startVert]);452int endVert = vertPos_.size();453vertPos_.push_back(vertPos_[halfedge_[current].endVert]);454455int oldMatch = halfedge_[current].pairedHalfedge;456int newMatch = halfedge_[end].pairedHalfedge;457458UpdateVert(startVert, oldMatch, newMatch);459UpdateVert(endVert, end, current);460461halfedge_[current].pairedHalfedge = newMatch;462halfedge_[newMatch].pairedHalfedge = current;463halfedge_[end].pairedHalfedge = oldMatch;464halfedge_[oldMatch].pairedHalfedge = end;465466RemoveIfFolded(end);467}468469void Manifold::Impl::CollapseTri(const ivec3& triEdge) {470if (halfedge_[triEdge[1]].pairedHalfedge == -1) return;471int pair1 = halfedge_[triEdge[1]].pairedHalfedge;472int pair2 = halfedge_[triEdge[2]].pairedHalfedge;473halfedge_[pair1].pairedHalfedge = pair2;474halfedge_[pair2].pairedHalfedge = pair1;475for (int i : {0, 1, 2}) {476halfedge_[triEdge[i]] = {-1, -1, -1, halfedge_[triEdge[i]].propVert};477}478}479480void Manifold::Impl::RemoveIfFolded(int edge) {481const ivec3 tri0edge = TriOf(edge);482const ivec3 tri1edge = TriOf(halfedge_[edge].pairedHalfedge);483if (halfedge_[tri0edge[1]].pairedHalfedge == -1) return;484if (halfedge_[tri0edge[1]].endVert == halfedge_[tri1edge[1]].endVert) {485if (halfedge_[tri0edge[1]].pairedHalfedge == tri1edge[2]) {486if (halfedge_[tri0edge[2]].pairedHalfedge == tri1edge[1]) {487for (int i : {0, 1, 2})488vertPos_[halfedge_[tri0edge[i]].startVert] = vec3(NAN);489} else {490vertPos_[halfedge_[tri0edge[1]].startVert] = vec3(NAN);491}492} else {493if (halfedge_[tri0edge[2]].pairedHalfedge == tri1edge[1]) {494vertPos_[halfedge_[tri1edge[1]].startVert] = vec3(NAN);495}496}497PairUp(halfedge_[tri0edge[1]].pairedHalfedge,498halfedge_[tri1edge[2]].pairedHalfedge);499PairUp(halfedge_[tri0edge[2]].pairedHalfedge,500halfedge_[tri1edge[1]].pairedHalfedge);501for (int i : {0, 1, 2}) {502halfedge_[tri0edge[i]] = {-1, -1, -1};503halfedge_[tri1edge[i]] = {-1, -1, -1};504}505}506}507508// Collapses the given edge by removing startVert - returns false if the edge509// cannot be collapsed. May split the mesh topologically if the collapse would510// have resulted in a 4-manifold edge. Do not collapse an edge if startVert is511// pinched - the vert would be marked NaN, but other edges could still be512// pointing to it.513bool Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {514Vec<TriRef>& triRef = meshRelation_.triRef;515516const Halfedge toRemove = halfedge_[edge];517if (toRemove.pairedHalfedge < 0) return false;518519const int endVert = toRemove.endVert;520const ivec3 tri0edge = TriOf(edge);521const ivec3 tri1edge = TriOf(toRemove.pairedHalfedge);522523const vec3 pNew = vertPos_[endVert];524const vec3 pOld = vertPos_[toRemove.startVert];525const vec3 delta = pNew - pOld;526const bool shortEdge = la::dot(delta, delta) < epsilon_ * epsilon_;527528// Orbit startVert529int start = halfedge_[tri1edge[1]].pairedHalfedge;530int current = tri1edge[2];531if (!shortEdge) {532current = start;533TriRef refCheck = triRef[toRemove.pairedHalfedge / 3];534vec3 pLast = vertPos_[halfedge_[tri1edge[1]].endVert];535while (current != tri1edge[0]) {536current = NextHalfedge(current);537vec3 pNext = vertPos_[halfedge_[current].endVert];538const int tri = current / 3;539const TriRef ref = triRef[tri];540const mat2x3 projection = GetAxisAlignedProjection(faceNormal_[tri]);541// Don't collapse if the edge is not redundant (this may have changed due542// to the collapse of neighbors).543if (!ref.SameFace(refCheck)) {544const TriRef oldRef = refCheck;545refCheck = triRef[edge / 3];546if (!ref.SameFace(refCheck)) {547return false;548}549if (ref.meshID != oldRef.meshID || ref.faceID != oldRef.faceID ||550la::dot(faceNormal_[toRemove.pairedHalfedge / 3],551faceNormal_[tri]) < -0.5) {552// Restrict collapse to colinear edges when the edge separates faces553// or the edge is sharp. This ensures large shifts are not introduced554// parallel to the tangent plane.555if (CCW(projection * pLast, projection * pOld, projection * pNew,556epsilon_) != 0)557return false;558}559}560561// Don't collapse edge if it would cause a triangle to invert.562if (CCW(projection * pNext, projection * pLast, projection * pNew,563epsilon_) < 0)564return false;565566pLast = pNext;567current = halfedge_[current].pairedHalfedge;568}569}570571// Orbit endVert572{573int current = halfedge_[tri0edge[1]].pairedHalfedge;574while (current != tri1edge[2]) {575current = NextHalfedge(current);576edges.push_back(current);577current = halfedge_[current].pairedHalfedge;578}579}580581// Remove toRemove.startVert and replace with endVert.582vertPos_[toRemove.startVert] = vec3(NAN);583CollapseTri(tri1edge);584585// Orbit startVert586const int tri0 = edge / 3;587const int tri1 = toRemove.pairedHalfedge / 3;588current = start;589while (current != tri0edge[2]) {590current = NextHalfedge(current);591592if (NumProp() > 0) {593// Update the shifted triangles to the vertBary of endVert594const int tri = current / 3;595if (triRef[tri].SameFace(triRef[tri0])) {596halfedge_[current].propVert = halfedge_[NextHalfedge(edge)].propVert;597} else if (triRef[tri].SameFace(triRef[tri1])) {598halfedge_[current].propVert =599halfedge_[toRemove.pairedHalfedge].propVert;600}601}602603const int vert = halfedge_[current].endVert;604const int next = halfedge_[current].pairedHalfedge;605for (size_t i = 0; i < edges.size(); ++i) {606if (vert == halfedge_[edges[i]].endVert) {607FormLoop(edges[i], current);608start = next;609edges.resize(i);610break;611}612}613current = next;614}615616UpdateVert(endVert, start, tri0edge[2]);617CollapseTri(tri0edge);618RemoveIfFolded(start);619return true;620}621622void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag,623std::vector<int>& visited,624std::vector<int>& edgeSwapStack,625std::vector<int>& edges) {626Vec<TriRef>& triRef = meshRelation_.triRef;627628if (edge < 0) return;629const int pair = halfedge_[edge].pairedHalfedge;630if (pair < 0) return;631632// avoid infinite recursion633if (visited[edge] == tag && visited[pair] == tag) return;634635const ivec3 tri0edge = TriOf(edge);636const ivec3 tri1edge = TriOf(pair);637638mat2x3 projection = GetAxisAlignedProjection(faceNormal_[edge / 3]);639vec2 v[4];640for (int i : {0, 1, 2})641v[i] = projection * vertPos_[halfedge_[tri0edge[i]].startVert];642// Only operate on the long edge of a degenerate triangle.643if (CCW(v[0], v[1], v[2], tolerance_) > 0 || !Is01Longest(v[0], v[1], v[2]))644return;645646// Switch to neighbor's projection.647projection = GetAxisAlignedProjection(faceNormal_[pair / 3]);648for (int i : {0, 1, 2})649v[i] = projection * vertPos_[halfedge_[tri0edge[i]].startVert];650v[3] = projection * vertPos_[halfedge_[tri1edge[2]].startVert];651652auto SwapEdge = [&]() {653// The 0-verts are swapped to the opposite 2-verts.654const int v0 = halfedge_[tri0edge[2]].startVert;655const int v1 = halfedge_[tri1edge[2]].startVert;656halfedge_[tri0edge[0]].startVert = v1;657halfedge_[tri0edge[2]].endVert = v1;658halfedge_[tri1edge[0]].startVert = v0;659halfedge_[tri1edge[2]].endVert = v0;660PairUp(tri0edge[0], halfedge_[tri1edge[2]].pairedHalfedge);661PairUp(tri1edge[0], halfedge_[tri0edge[2]].pairedHalfedge);662PairUp(tri0edge[2], tri1edge[2]);663// Both triangles are now subsets of the neighboring triangle.664const int tri0 = tri0edge[0] / 3;665const int tri1 = tri1edge[0] / 3;666faceNormal_[tri0] = faceNormal_[tri1];667triRef[tri0] = triRef[tri1];668const double l01 = la::length(v[1] - v[0]);669const double l02 = la::length(v[2] - v[0]);670const double a = std::max(0.0, std::min(1.0, l02 / l01));671// Update properties if applicable672if (properties_.size() > 0) {673Vec<double>& prop = properties_;674halfedge_[tri0edge[1]].propVert = halfedge_[tri1edge[0]].propVert;675halfedge_[tri0edge[0]].propVert = halfedge_[tri1edge[2]].propVert;676halfedge_[tri0edge[2]].propVert = halfedge_[tri1edge[2]].propVert;677const int numProp = NumProp();678const int newProp = prop.size() / numProp;679const int propIdx0 = halfedge_[tri1edge[0]].propVert;680const int propIdx1 = halfedge_[tri1edge[1]].propVert;681for (int p = 0; p < numProp; ++p) {682prop.push_back(a * prop[numProp * propIdx0 + p] +683(1 - a) * prop[numProp * propIdx1 + p]);684}685halfedge_[tri1edge[0]].propVert = newProp;686halfedge_[tri0edge[2]].propVert = newProp;687}688689// if the new edge already exists, duplicate the verts and split the mesh.690int current = halfedge_[tri1edge[0]].pairedHalfedge;691const int endVert = halfedge_[tri1edge[1]].endVert;692while (current != tri0edge[1]) {693current = NextHalfedge(current);694if (halfedge_[current].endVert == endVert) {695FormLoop(tri0edge[2], current);696RemoveIfFolded(tri0edge[2]);697return;698}699current = halfedge_[current].pairedHalfedge;700}701};702703// Only operate if the other triangles are not degenerate.704if (CCW(v[1], v[0], v[3], tolerance_) <= 0) {705if (!Is01Longest(v[1], v[0], v[3])) return;706// Two facing, long-edge degenerates can swap.707SwapEdge();708const vec2 e23 = v[3] - v[2];709if (la::dot(e23, e23) < tolerance_ * tolerance_) {710tag++;711CollapseEdge(tri0edge[2], edges);712edges.resize(0);713} else {714visited[edge] = tag;715visited[pair] = tag;716edgeSwapStack.insert(edgeSwapStack.end(), {tri1edge[1], tri1edge[0],717tri0edge[1], tri0edge[0]});718}719return;720} else if (CCW(v[0], v[3], v[2], tolerance_) <= 0 ||721CCW(v[1], v[2], v[3], tolerance_) <= 0) {722return;723}724// Normal path725SwapEdge();726visited[edge] = tag;727visited[pair] = tag;728edgeSwapStack.insert(edgeSwapStack.end(),729{halfedge_[tri1edge[0]].pairedHalfedge,730halfedge_[tri0edge[1]].pairedHalfedge});731}732733void Manifold::Impl::SplitPinchedVerts() {734ZoneScoped;735736auto nbEdges = halfedge_.size();737#if MANIFOLD_PAR == 1738if (nbEdges > 1e4) {739std::mutex mutex;740std::vector<size_t> pinched;741// This parallelized version is non-trivial so we can't reuse the code742//743// The idea here is to identify cycles of halfedges that can be iterated744// through using ForVert. Pinched verts are vertices where there are745// multiple cycles associated with the vertex. Each cycle is identified with746// the largest halfedge index within the cycle, and when there are multiple747// cycles associated with the same starting vertex but with different ids,748// it means we have a pinched vertex. This check is done by using a single749// atomic cas operation, the expected case is either invalid id (the vertex750// was not processed) or with the same id.751//752// The local store is to store the processed halfedges, so to avoid753// repetitive processing. Note that it only approximates the processed754// halfedges because it is thread local. This is why we need a vector to755// deduplicate the probematic halfedges we found.756std::vector<std::atomic<size_t>> largestEdge(NumVert());757for_each(ExecutionPolicy::Par, countAt(0), countAt(NumVert()),758[&largestEdge](size_t i) {759largestEdge[i].store(std::numeric_limits<size_t>::max());760});761tbb::combinable<std::vector<bool>> store(762[nbEdges]() { return std::vector<bool>(nbEdges, false); });763tbb::parallel_for(764tbb::blocked_range<size_t>(0, nbEdges),765[&store, &mutex, &pinched, &largestEdge, this](const auto& r) {766auto& local = store.local();767std::vector<size_t> pinchedLocal;768for (auto i = r.begin(); i < r.end(); ++i) {769if (local[i]) continue;770local[i] = true;771const int vert = halfedge_[i].startVert;772if (vert == -1) continue;773size_t largest = i;774ForVert(i, [&local, &largest](int current) {775local[current] = true;776largest = std::max(largest, static_cast<size_t>(current));777});778size_t expected = std::numeric_limits<size_t>::max();779if (!largestEdge[vert].compare_exchange_strong(expected, largest) &&780expected != largest) {781// we know that there is another loop...782pinchedLocal.push_back(largest);783}784}785if (!pinchedLocal.empty()) {786std::lock_guard<std::mutex> lock(mutex);787pinched.insert(pinched.end(), pinchedLocal.begin(),788pinchedLocal.end());789}790});791792manifold::stable_sort(pinched.begin(), pinched.end());793std::vector<bool> halfedgeProcessed(nbEdges, false);794for (size_t i : pinched) {795if (halfedgeProcessed[i]) continue;796vertPos_.push_back(vertPos_[halfedge_[i].startVert]);797const int vert = NumVert() - 1;798ForVert(i, [this, vert, &halfedgeProcessed](int current) {799halfedgeProcessed[current] = true;800halfedge_[current].startVert = vert;801halfedge_[halfedge_[current].pairedHalfedge].endVert = vert;802});803}804} else805#endif806{807std::vector<bool> vertProcessed(NumVert(), false);808std::vector<bool> halfedgeProcessed(nbEdges, false);809for (size_t i = 0; i < nbEdges; ++i) {810if (halfedgeProcessed[i]) continue;811int vert = halfedge_[i].startVert;812if (vert == -1) continue;813if (vertProcessed[vert]) {814vertPos_.push_back(vertPos_[vert]);815vert = NumVert() - 1;816} else {817vertProcessed[vert] = true;818}819ForVert(i, [this, &halfedgeProcessed, vert](int current) {820halfedgeProcessed[current] = true;821halfedge_[current].startVert = vert;822halfedge_[halfedge_[current].pairedHalfedge].endVert = vert;823});824}825}826}827828void Manifold::Impl::DedupeEdges() {829while (1) {830ZoneScopedN("DedupeEdge");831832const size_t nbEdges = halfedge_.size();833std::vector<size_t> duplicates;834auto localLoop = [&](size_t start, size_t end, std::vector<bool>& local,835std::vector<size_t>& results) {836// Iterate over all halfedges that start with the same vertex, and check837// for halfedges with the same ending vertex.838// Note: we use Vec and linear search when the number of neighbor is839// small because unordered_set requires allocations and is expensive.840// We switch to unordered_set when the number of neighbor is841// larger to avoid making things quadratic.842// We do it in two pass, the first pass to find the minimal halfedges with843// the target start and end verts, the second pass flag all the duplicated844// halfedges that are not having the minimal index as duplicates.845// This ensures deterministic result.846//847// The local store is to store the processed halfedges, so to avoid848// repetitive processing. Note that it only approximates the processed849// halfedges because it is thread local.850Vec<std::pair<int, int>> endVerts;851std::unordered_map<int, int> endVertSet;852for (auto i = start; i < end; ++i) {853if (local[i] || halfedge_[i].startVert == -1 ||854halfedge_[i].endVert == -1)855continue;856// we want to keep the allocation857endVerts.clear(false);858endVertSet.clear();859860// first iteration, populate entries861// this makes sure we always report the same set of entries862ForVert(i, [&local, &endVerts, &endVertSet, this](int current) {863local[current] = true;864if (halfedge_[current].startVert == -1 ||865halfedge_[current].endVert == -1) {866return;867}868int endV = halfedge_[current].endVert;869if (endVertSet.empty()) {870auto iter = std::find_if(endVerts.begin(), endVerts.end(),871[endV](const std::pair<int, int>& pair) {872return pair.first == endV;873});874if (iter != endVerts.end()) {875iter->second = std::min(iter->second, current);876} else {877endVerts.push_back({endV, current});878if (endVerts.size() > 32) {879endVertSet.insert(endVerts.begin(), endVerts.end());880endVerts.clear(false);881}882}883} else {884auto pair = endVertSet.insert({endV, current});885if (!pair.second)886pair.first->second = std::min(pair.first->second, current);887}888});889// second iteration, actually check for duplicates890// we always report the same set of duplicates, excluding the smallest891// halfedge in the set of duplicates892ForVert(i, [&endVerts, &endVertSet, &results, this](int current) {893if (halfedge_[current].startVert == -1 ||894halfedge_[current].endVert == -1) {895return;896}897int endV = halfedge_[current].endVert;898if (endVertSet.empty()) {899auto iter = std::find_if(endVerts.begin(), endVerts.end(),900[endV](const std::pair<int, int>& pair) {901return pair.first == endV;902});903if (iter->second != current) results.push_back(current);904} else {905auto iter = endVertSet.find(endV);906if (iter->second != current) results.push_back(current);907}908});909}910};911#if MANIFOLD_PAR == 1912if (nbEdges > 1e4) {913std::mutex mutex;914tbb::combinable<std::vector<bool>> store(915[nbEdges]() { return std::vector<bool>(nbEdges, false); });916tbb::parallel_for(917tbb::blocked_range<size_t>(0, nbEdges),918[&store, &mutex, &duplicates, &localLoop](const auto& r) {919auto& local = store.local();920std::vector<size_t> duplicatesLocal;921localLoop(r.begin(), r.end(), local, duplicatesLocal);922if (!duplicatesLocal.empty()) {923std::lock_guard<std::mutex> lock(mutex);924duplicates.insert(duplicates.end(), duplicatesLocal.begin(),925duplicatesLocal.end());926}927});928manifold::stable_sort(duplicates.begin(), duplicates.end());929duplicates.resize(930std::distance(duplicates.begin(),931std::unique(duplicates.begin(), duplicates.end())));932} else933#endif934{935std::vector<bool> local(nbEdges, false);936localLoop(0, nbEdges, local, duplicates);937}938939size_t numFlagged = 0;940for (size_t i : duplicates) {941DedupeEdge(i);942numFlagged++;943}944945if (numFlagged == 0) break;946947#ifdef MANIFOLD_DEBUG948if (ManifoldParams().verbose > 0) {949std::cout << "found " << numFlagged << " duplicate edges to split"950<< std::endl;951}952#endif953}954}955} // namespace manifold956957958