Path: blob/master/thirdparty/manifold/src/edge_op.cpp
9903 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;204205// In the case of a very bad triangulation, it is possible to create pinched206// verts. They must be removed before edge collapse.207SplitPinchedVerts();208DedupeEdges();209}210211/**212* Collapses degenerate triangles by removing edges shorter than tolerance_ and213* any edge that is preceeded by an edge that joins the same two face relations.214* It also performs edge swaps on the long edges of degenerate triangles, though215* there are some configurations of degenerates that cannot be removed this way.216*217* Before collapsing edges, the mesh is checked for duplicate edges (more than218* one pair of triangles sharing the same edge), which are removed by219* duplicating one vert and adding two triangles. These degenerate triangles are220* likely to be collapsed again in the subsequent simplification.221*222* Note when an edge collapse would result in something non-manifold, the223* vertices are duplicated in such a way as to remove handles or separate224* meshes, thus decreasing the Genus(). It only increases when meshes that have225* collapsed to just a pair of triangles are removed entirely.226*227* Verts with index less than firstNewVert will be left uncollapsed. This is228* zero by default so that everything can be collapsed.229*230* Rather than actually removing the edges, this step merely marks them for231* removal, by setting vertPos to NaN and halfedge to {-1, -1, -1, -1}.232*/233void Manifold::Impl::SimplifyTopology(int firstNewVert) {234if (!halfedge_.size()) return;235236CleanupTopology();237CollapseShortEdges(firstNewVert);238CollapseColinearEdges(firstNewVert);239SwapDegenerates(firstNewVert);240}241242void Manifold::Impl::RemoveDegenerates(int firstNewVert) {243if (!halfedge_.size()) return;244245CleanupTopology();246CollapseShortEdges(firstNewVert);247SwapDegenerates(firstNewVert);248}249250void Manifold::Impl::CollapseShortEdges(int firstNewVert) {251ZoneScopedN("CollapseShortEdge");252FlagStore s;253size_t numFlagged = 0;254const size_t nbEdges = halfedge_.size();255256std::vector<int> scratchBuffer;257scratchBuffer.reserve(10);258// Short edges get to skip several checks and hence remove more classes of259// degenerate triangles than flagged edges do, but this could in theory lead260// to error stacking where a vertex moves too far. For this reason this is261// restricted to epsilon, rather than tolerance.262ShortEdge se{halfedge_, vertPos_, epsilon_, firstNewVert};263s.run(nbEdges, se, [&](size_t i) {264const bool didCollapse = CollapseEdge(i, scratchBuffer);265if (didCollapse) numFlagged++;266scratchBuffer.resize(0);267});268269#ifdef MANIFOLD_DEBUG270if (ManifoldParams().verbose > 0 && numFlagged > 0) {271std::cout << "collapsed " << numFlagged << " short edges" << std::endl;272}273#endif274}275276void Manifold::Impl::CollapseColinearEdges(int firstNewVert) {277FlagStore s;278size_t numFlagged = 0;279const size_t nbEdges = halfedge_.size();280std::vector<int> scratchBuffer;281scratchBuffer.reserve(10);282while (1) {283ZoneScopedN("CollapseFlaggedEdge");284numFlagged = 0;285// Collapse colinear edges, but only remove new verts, i.e. verts with286// index287// >= firstNewVert. This is used to keep the Boolean from changing the288// non-intersecting parts of the input meshes. Colinear is defined not by a289// local check, but by the global MarkCoplanar function, which keeps this290// from being vulnerable to error stacking.291FlagEdge se{halfedge_, meshRelation_.triRef, firstNewVert};292s.run(nbEdges, se, [&](size_t i) {293const bool didCollapse = CollapseEdge(i, scratchBuffer);294if (didCollapse) numFlagged++;295scratchBuffer.resize(0);296});297if (numFlagged == 0) break;298299#ifdef MANIFOLD_DEBUG300if (ManifoldParams().verbose > 0 && numFlagged > 0) {301std::cout << "collapsed " << numFlagged << " colinear edges" << std::endl;302}303#endif304}305}306307void Manifold::Impl::SwapDegenerates(int firstNewVert) {308ZoneScopedN("RecursiveEdgeSwap");309FlagStore s;310size_t numFlagged = 0;311const size_t nbEdges = halfedge_.size();312std::vector<int> scratchBuffer;313scratchBuffer.reserve(10);314315SwappableEdge se{halfedge_, vertPos_, faceNormal_, tolerance_, firstNewVert};316std::vector<int> edgeSwapStack;317std::vector<int> visited(halfedge_.size(), -1);318int tag = 0;319s.run(nbEdges, se, [&](size_t i) {320numFlagged++;321tag++;322RecursiveEdgeSwap(i, tag, visited, edgeSwapStack, scratchBuffer);323while (!edgeSwapStack.empty()) {324int last = edgeSwapStack.back();325edgeSwapStack.pop_back();326RecursiveEdgeSwap(last, tag, visited, edgeSwapStack, scratchBuffer);327}328});329330#ifdef MANIFOLD_DEBUG331if (ManifoldParams().verbose > 0 && numFlagged > 0) {332std::cout << "swapped " << numFlagged << " edges" << std::endl;333}334#endif335}336337// Deduplicate the given 4-manifold edge by duplicating endVert, thus making the338// edges distinct. Also duplicates startVert if it becomes pinched.339void Manifold::Impl::DedupeEdge(const int edge) {340// Orbit endVert341const int startVert = halfedge_[edge].startVert;342const int endVert = halfedge_[edge].endVert;343const int endProp = halfedge_[NextHalfedge(edge)].propVert;344int current = halfedge_[NextHalfedge(edge)].pairedHalfedge;345while (current != edge) {346const int vert = halfedge_[current].startVert;347if (vert == startVert) {348// Single topological unit needs 2 faces added to be split349const int newVert = vertPos_.size();350vertPos_.push_back(vertPos_[endVert]);351if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);352current = halfedge_[NextHalfedge(current)].pairedHalfedge;353const int opposite = halfedge_[NextHalfedge(edge)].pairedHalfedge;354355UpdateVert(newVert, current, opposite);356357int newHalfedge = halfedge_.size();358int oldFace = current / 3;359int outsideVert = halfedge_[current].startVert;360halfedge_.push_back({endVert, newVert, -1, endProp});361halfedge_.push_back({newVert, outsideVert, -1, endProp});362halfedge_.push_back(363{outsideVert, endVert, -1, halfedge_[current].propVert});364PairUp(newHalfedge + 2, halfedge_[current].pairedHalfedge);365PairUp(newHalfedge + 1, current);366if (meshRelation_.triRef.size() > 0)367meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);368if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);369370newHalfedge += 3;371oldFace = opposite / 3;372outsideVert = halfedge_[opposite].startVert;373halfedge_.push_back({newVert, endVert, -1, endProp}); // fix prop374halfedge_.push_back({endVert, outsideVert, -1, endProp});375halfedge_.push_back(376{outsideVert, newVert, -1, halfedge_[opposite].propVert});377PairUp(newHalfedge + 2, halfedge_[opposite].pairedHalfedge);378PairUp(newHalfedge + 1, opposite);379PairUp(newHalfedge, newHalfedge - 3);380if (meshRelation_.triRef.size() > 0)381meshRelation_.triRef.push_back(meshRelation_.triRef[oldFace]);382if (faceNormal_.size() > 0) faceNormal_.push_back(faceNormal_[oldFace]);383384break;385}386387current = halfedge_[NextHalfedge(current)].pairedHalfedge;388}389390if (current == edge) {391// Separate topological unit needs no new faces to be split392const int newVert = vertPos_.size();393vertPos_.push_back(vertPos_[endVert]);394if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);395396ForVert(NextHalfedge(current), [this, newVert](int e) {397halfedge_[e].startVert = newVert;398halfedge_[halfedge_[e].pairedHalfedge].endVert = newVert;399});400}401402// Orbit startVert403const int pair = halfedge_[edge].pairedHalfedge;404current = halfedge_[NextHalfedge(pair)].pairedHalfedge;405while (current != pair) {406const int vert = halfedge_[current].startVert;407if (vert == endVert) {408break; // Connected: not a pinched vert409}410current = halfedge_[NextHalfedge(current)].pairedHalfedge;411}412413if (current == pair) {414// Split the pinched vert the previous split created.415const int newVert = vertPos_.size();416vertPos_.push_back(vertPos_[endVert]);417if (vertNormal_.size() > 0) vertNormal_.push_back(vertNormal_[endVert]);418419ForVert(NextHalfedge(current), [this, newVert](int e) {420halfedge_[e].startVert = newVert;421halfedge_[halfedge_[e].pairedHalfedge].endVert = newVert;422});423}424}425426void Manifold::Impl::PairUp(int edge0, int edge1) {427halfedge_[edge0].pairedHalfedge = edge1;428halfedge_[edge1].pairedHalfedge = edge0;429}430431// Traverses CW around startEdge.endVert from startEdge to endEdge432// (edgeEdge.endVert must == startEdge.endVert), updating each edge to point433// to vert instead.434void Manifold::Impl::UpdateVert(int vert, int startEdge, int endEdge) {435int current = startEdge;436while (current != endEdge) {437halfedge_[current].endVert = vert;438current = NextHalfedge(current);439halfedge_[current].startVert = vert;440current = halfedge_[current].pairedHalfedge;441DEBUG_ASSERT(current != startEdge, logicErr, "infinite loop in decimator!");442}443}444445// In the event that the edge collapse would create a non-manifold edge,446// instead we duplicate the two verts and attach the manifolds the other way447// across this edge.448void Manifold::Impl::FormLoop(int current, int end) {449int startVert = vertPos_.size();450vertPos_.push_back(vertPos_[halfedge_[current].startVert]);451int endVert = vertPos_.size();452vertPos_.push_back(vertPos_[halfedge_[current].endVert]);453454int oldMatch = halfedge_[current].pairedHalfedge;455int newMatch = halfedge_[end].pairedHalfedge;456457UpdateVert(startVert, oldMatch, newMatch);458UpdateVert(endVert, end, current);459460halfedge_[current].pairedHalfedge = newMatch;461halfedge_[newMatch].pairedHalfedge = current;462halfedge_[end].pairedHalfedge = oldMatch;463halfedge_[oldMatch].pairedHalfedge = end;464465RemoveIfFolded(end);466}467468void Manifold::Impl::CollapseTri(const ivec3& triEdge) {469if (halfedge_[triEdge[1]].pairedHalfedge == -1) return;470int pair1 = halfedge_[triEdge[1]].pairedHalfedge;471int pair2 = halfedge_[triEdge[2]].pairedHalfedge;472halfedge_[pair1].pairedHalfedge = pair2;473halfedge_[pair2].pairedHalfedge = pair1;474for (int i : {0, 1, 2}) {475halfedge_[triEdge[i]] = {-1, -1, -1, halfedge_[triEdge[i]].propVert};476}477}478479void Manifold::Impl::RemoveIfFolded(int edge) {480const ivec3 tri0edge = TriOf(edge);481const ivec3 tri1edge = TriOf(halfedge_[edge].pairedHalfedge);482if (halfedge_[tri0edge[1]].pairedHalfedge == -1) return;483if (halfedge_[tri0edge[1]].endVert == halfedge_[tri1edge[1]].endVert) {484if (halfedge_[tri0edge[1]].pairedHalfedge == tri1edge[2]) {485if (halfedge_[tri0edge[2]].pairedHalfedge == tri1edge[1]) {486for (int i : {0, 1, 2})487vertPos_[halfedge_[tri0edge[i]].startVert] = vec3(NAN);488} else {489vertPos_[halfedge_[tri0edge[1]].startVert] = vec3(NAN);490}491} else {492if (halfedge_[tri0edge[2]].pairedHalfedge == tri1edge[1]) {493vertPos_[halfedge_[tri1edge[1]].startVert] = vec3(NAN);494}495}496PairUp(halfedge_[tri0edge[1]].pairedHalfedge,497halfedge_[tri1edge[2]].pairedHalfedge);498PairUp(halfedge_[tri0edge[2]].pairedHalfedge,499halfedge_[tri1edge[1]].pairedHalfedge);500for (int i : {0, 1, 2}) {501halfedge_[tri0edge[i]] = {-1, -1, -1};502halfedge_[tri1edge[i]] = {-1, -1, -1};503}504}505}506507// Collapses the given edge by removing startVert - returns false if the edge508// cannot be collapsed. May split the mesh topologically if the collapse would509// have resulted in a 4-manifold edge. Do not collapse an edge if startVert is510// pinched - the vert would be marked NaN, but other edges could still be511// pointing to it.512bool Manifold::Impl::CollapseEdge(const int edge, std::vector<int>& edges) {513Vec<TriRef>& triRef = meshRelation_.triRef;514515const Halfedge toRemove = halfedge_[edge];516if (toRemove.pairedHalfedge < 0) return false;517518const int endVert = toRemove.endVert;519const ivec3 tri0edge = TriOf(edge);520const ivec3 tri1edge = TriOf(toRemove.pairedHalfedge);521522const vec3 pNew = vertPos_[endVert];523const vec3 pOld = vertPos_[toRemove.startVert];524const vec3 delta = pNew - pOld;525const bool shortEdge = la::dot(delta, delta) < epsilon_ * epsilon_;526527// Orbit startVert528int start = halfedge_[tri1edge[1]].pairedHalfedge;529int current = tri1edge[2];530if (!shortEdge) {531current = start;532TriRef refCheck = triRef[toRemove.pairedHalfedge / 3];533vec3 pLast = vertPos_[halfedge_[tri1edge[1]].endVert];534while (current != tri1edge[0]) {535current = NextHalfedge(current);536vec3 pNext = vertPos_[halfedge_[current].endVert];537const int tri = current / 3;538const TriRef ref = triRef[tri];539const mat2x3 projection = GetAxisAlignedProjection(faceNormal_[tri]);540// Don't collapse if the edge is not redundant (this may have changed due541// to the collapse of neighbors).542if (!ref.SameFace(refCheck)) {543const TriRef oldRef = refCheck;544refCheck = triRef[edge / 3];545if (!ref.SameFace(refCheck)) {546return false;547}548if (ref.meshID != oldRef.meshID || ref.faceID != oldRef.faceID ||549la::dot(faceNormal_[toRemove.pairedHalfedge / 3],550faceNormal_[tri]) < -0.5) {551// Restrict collapse to colinear edges when the edge separates faces552// or the edge is sharp. This ensures large shifts are not introduced553// parallel to the tangent plane.554if (CCW(projection * pLast, projection * pOld, projection * pNew,555epsilon_) != 0)556return false;557}558}559560// Don't collapse edge if it would cause a triangle to invert.561if (CCW(projection * pNext, projection * pLast, projection * pNew,562epsilon_) < 0)563return false;564565pLast = pNext;566current = halfedge_[current].pairedHalfedge;567}568}569570// Orbit endVert571{572int current = halfedge_[tri0edge[1]].pairedHalfedge;573while (current != tri1edge[2]) {574current = NextHalfedge(current);575edges.push_back(current);576current = halfedge_[current].pairedHalfedge;577}578}579580// Remove toRemove.startVert and replace with endVert.581vertPos_[toRemove.startVert] = vec3(NAN);582CollapseTri(tri1edge);583584// Orbit startVert585const int tri0 = edge / 3;586const int tri1 = toRemove.pairedHalfedge / 3;587current = start;588while (current != tri0edge[2]) {589current = NextHalfedge(current);590591if (NumProp() > 0) {592// Update the shifted triangles to the vertBary of endVert593const int tri = current / 3;594if (triRef[tri].SameFace(triRef[tri0])) {595halfedge_[current].propVert = halfedge_[NextHalfedge(edge)].propVert;596} else if (triRef[tri].SameFace(triRef[tri1])) {597halfedge_[current].propVert =598halfedge_[toRemove.pairedHalfedge].propVert;599}600}601602const int vert = halfedge_[current].endVert;603const int next = halfedge_[current].pairedHalfedge;604for (size_t i = 0; i < edges.size(); ++i) {605if (vert == halfedge_[edges[i]].endVert) {606FormLoop(edges[i], current);607start = next;608edges.resize(i);609break;610}611}612current = next;613}614615UpdateVert(endVert, start, tri0edge[2]);616CollapseTri(tri0edge);617RemoveIfFolded(start);618return true;619}620621void Manifold::Impl::RecursiveEdgeSwap(const int edge, int& tag,622std::vector<int>& visited,623std::vector<int>& edgeSwapStack,624std::vector<int>& edges) {625Vec<TriRef>& triRef = meshRelation_.triRef;626627if (edge < 0) return;628const int pair = halfedge_[edge].pairedHalfedge;629if (pair < 0) return;630631// avoid infinite recursion632if (visited[edge] == tag && visited[pair] == tag) return;633634const ivec3 tri0edge = TriOf(edge);635const ivec3 tri1edge = TriOf(pair);636637mat2x3 projection = GetAxisAlignedProjection(faceNormal_[edge / 3]);638vec2 v[4];639for (int i : {0, 1, 2})640v[i] = projection * vertPos_[halfedge_[tri0edge[i]].startVert];641// Only operate on the long edge of a degenerate triangle.642if (CCW(v[0], v[1], v[2], tolerance_) > 0 || !Is01Longest(v[0], v[1], v[2]))643return;644645// Switch to neighbor's projection.646projection = GetAxisAlignedProjection(faceNormal_[pair / 3]);647for (int i : {0, 1, 2})648v[i] = projection * vertPos_[halfedge_[tri0edge[i]].startVert];649v[3] = projection * vertPos_[halfedge_[tri1edge[2]].startVert];650651auto SwapEdge = [&]() {652// The 0-verts are swapped to the opposite 2-verts.653const int v0 = halfedge_[tri0edge[2]].startVert;654const int v1 = halfedge_[tri1edge[2]].startVert;655halfedge_[tri0edge[0]].startVert = v1;656halfedge_[tri0edge[2]].endVert = v1;657halfedge_[tri1edge[0]].startVert = v0;658halfedge_[tri1edge[2]].endVert = v0;659PairUp(tri0edge[0], halfedge_[tri1edge[2]].pairedHalfedge);660PairUp(tri1edge[0], halfedge_[tri0edge[2]].pairedHalfedge);661PairUp(tri0edge[2], tri1edge[2]);662// Both triangles are now subsets of the neighboring triangle.663const int tri0 = tri0edge[0] / 3;664const int tri1 = tri1edge[0] / 3;665faceNormal_[tri0] = faceNormal_[tri1];666triRef[tri0] = triRef[tri1];667const double l01 = la::length(v[1] - v[0]);668const double l02 = la::length(v[2] - v[0]);669const double a = std::max(0.0, std::min(1.0, l02 / l01));670// Update properties if applicable671if (properties_.size() > 0) {672Vec<double>& prop = properties_;673halfedge_[tri0edge[1]].propVert = halfedge_[tri1edge[0]].propVert;674halfedge_[tri0edge[0]].propVert = halfedge_[tri1edge[2]].propVert;675halfedge_[tri0edge[2]].propVert = halfedge_[tri1edge[2]].propVert;676const int numProp = NumProp();677const int newProp = prop.size() / numProp;678const int propIdx0 = halfedge_[tri1edge[0]].propVert;679const int propIdx1 = halfedge_[tri1edge[1]].propVert;680for (int p = 0; p < numProp; ++p) {681prop.push_back(a * prop[numProp * propIdx0 + p] +682(1 - a) * prop[numProp * propIdx1 + p]);683}684halfedge_[tri1edge[0]].propVert = newProp;685halfedge_[tri0edge[2]].propVert = newProp;686}687688// if the new edge already exists, duplicate the verts and split the mesh.689int current = halfedge_[tri1edge[0]].pairedHalfedge;690const int endVert = halfedge_[tri1edge[1]].endVert;691while (current != tri0edge[1]) {692current = NextHalfedge(current);693if (halfedge_[current].endVert == endVert) {694FormLoop(tri0edge[2], current);695RemoveIfFolded(tri0edge[2]);696return;697}698current = halfedge_[current].pairedHalfedge;699}700};701702// Only operate if the other triangles are not degenerate.703if (CCW(v[1], v[0], v[3], tolerance_) <= 0) {704if (!Is01Longest(v[1], v[0], v[3])) return;705// Two facing, long-edge degenerates can swap.706SwapEdge();707const vec2 e23 = v[3] - v[2];708if (la::dot(e23, e23) < tolerance_ * tolerance_) {709tag++;710CollapseEdge(tri0edge[2], edges);711edges.resize(0);712} else {713visited[edge] = tag;714visited[pair] = tag;715edgeSwapStack.insert(edgeSwapStack.end(), {tri1edge[1], tri1edge[0],716tri0edge[1], tri0edge[0]});717}718return;719} else if (CCW(v[0], v[3], v[2], tolerance_) <= 0 ||720CCW(v[1], v[2], v[3], tolerance_) <= 0) {721return;722}723// Normal path724SwapEdge();725visited[edge] = tag;726visited[pair] = tag;727edgeSwapStack.insert(edgeSwapStack.end(),728{halfedge_[tri1edge[0]].pairedHalfedge,729halfedge_[tri0edge[1]].pairedHalfedge});730}731732void Manifold::Impl::SplitPinchedVerts() {733ZoneScoped;734735auto nbEdges = halfedge_.size();736#if MANIFOLD_PAR == 1737if (nbEdges > 1e4) {738std::mutex mutex;739std::vector<size_t> pinched;740// This parallelized version is non-trivial so we can't reuse the code741//742// The idea here is to identify cycles of halfedges that can be iterated743// through using ForVert. Pinched verts are vertices where there are744// multiple cycles associated with the vertex. Each cycle is identified with745// the largest halfedge index within the cycle, and when there are multiple746// cycles associated with the same starting vertex but with different ids,747// it means we have a pinched vertex. This check is done by using a single748// atomic cas operation, the expected case is either invalid id (the vertex749// was not processed) or with the same id.750//751// The local store is to store the processed halfedges, so to avoid752// repetitive processing. Note that it only approximates the processed753// halfedges because it is thread local. This is why we need a vector to754// deduplicate the probematic halfedges we found.755std::vector<std::atomic<size_t>> largestEdge(NumVert());756for_each(ExecutionPolicy::Par, countAt(0), countAt(NumVert()),757[&largestEdge](size_t i) {758largestEdge[i].store(std::numeric_limits<size_t>::max());759});760tbb::combinable<std::vector<bool>> store(761[nbEdges]() { return std::vector<bool>(nbEdges, false); });762tbb::parallel_for(763tbb::blocked_range<size_t>(0, nbEdges),764[&store, &mutex, &pinched, &largestEdge, this](const auto& r) {765auto& local = store.local();766std::vector<size_t> pinchedLocal;767for (auto i = r.begin(); i < r.end(); ++i) {768if (local[i]) continue;769local[i] = true;770const int vert = halfedge_[i].startVert;771if (vert == -1) continue;772size_t largest = i;773ForVert(i, [&local, &largest](int current) {774local[current] = true;775largest = std::max(largest, static_cast<size_t>(current));776});777size_t expected = std::numeric_limits<size_t>::max();778if (!largestEdge[vert].compare_exchange_strong(expected, largest) &&779expected != largest) {780// we know that there is another loop...781pinchedLocal.push_back(largest);782}783}784if (!pinchedLocal.empty()) {785std::lock_guard<std::mutex> lock(mutex);786pinched.insert(pinched.end(), pinchedLocal.begin(),787pinchedLocal.end());788}789});790791manifold::stable_sort(pinched.begin(), pinched.end());792std::vector<bool> halfedgeProcessed(nbEdges, false);793for (size_t i : pinched) {794if (halfedgeProcessed[i]) continue;795vertPos_.push_back(vertPos_[halfedge_[i].startVert]);796const int vert = NumVert() - 1;797ForVert(i, [this, vert, &halfedgeProcessed](int current) {798halfedgeProcessed[current] = true;799halfedge_[current].startVert = vert;800halfedge_[halfedge_[current].pairedHalfedge].endVert = vert;801});802}803} else804#endif805{806std::vector<bool> vertProcessed(NumVert(), false);807std::vector<bool> halfedgeProcessed(nbEdges, false);808for (size_t i = 0; i < nbEdges; ++i) {809if (halfedgeProcessed[i]) continue;810int vert = halfedge_[i].startVert;811if (vert == -1) continue;812if (vertProcessed[vert]) {813vertPos_.push_back(vertPos_[vert]);814vert = NumVert() - 1;815} else {816vertProcessed[vert] = true;817}818ForVert(i, [this, &halfedgeProcessed, vert](int current) {819halfedgeProcessed[current] = true;820halfedge_[current].startVert = vert;821halfedge_[halfedge_[current].pairedHalfedge].endVert = vert;822});823}824}825}826827void Manifold::Impl::DedupeEdges() {828while (1) {829ZoneScopedN("DedupeEdge");830831const size_t nbEdges = halfedge_.size();832std::vector<size_t> duplicates;833auto localLoop = [&](size_t start, size_t end, std::vector<bool>& local,834std::vector<size_t>& results) {835// Iterate over all halfedges that start with the same vertex, and check836// for halfedges with the same ending vertex.837// Note: we use Vec and linear search when the number of neighbor is838// small because unordered_set requires allocations and is expensive.839// We switch to unordered_set when the number of neighbor is840// larger to avoid making things quadratic.841// We do it in two pass, the first pass to find the minimal halfedges with842// the target start and end verts, the second pass flag all the duplicated843// halfedges that are not having the minimal index as duplicates.844// This ensures deterministic result.845//846// The local store is to store the processed halfedges, so to avoid847// repetitive processing. Note that it only approximates the processed848// halfedges because it is thread local.849Vec<std::pair<int, int>> endVerts;850std::unordered_map<int, int> endVertSet;851for (auto i = start; i < end; ++i) {852if (local[i] || halfedge_[i].startVert == -1 ||853halfedge_[i].endVert == -1)854continue;855// we want to keep the allocation856endVerts.clear(false);857endVertSet.clear();858859// first iteration, populate entries860// this makes sure we always report the same set of entries861ForVert(i, [&local, &endVerts, &endVertSet, this](int current) {862local[current] = true;863if (halfedge_[current].startVert == -1 ||864halfedge_[current].endVert == -1) {865return;866}867int endV = halfedge_[current].endVert;868if (endVertSet.empty()) {869auto iter = std::find_if(endVerts.begin(), endVerts.end(),870[endV](const std::pair<int, int>& pair) {871return pair.first == endV;872});873if (iter != endVerts.end()) {874iter->second = std::min(iter->second, current);875} else {876endVerts.push_back({endV, current});877if (endVerts.size() > 32) {878endVertSet.insert(endVerts.begin(), endVerts.end());879endVerts.clear(false);880}881}882} else {883auto pair = endVertSet.insert({endV, current});884if (!pair.second)885pair.first->second = std::min(pair.first->second, current);886}887});888// second iteration, actually check for duplicates889// we always report the same set of duplicates, excluding the smallest890// halfedge in the set of duplicates891ForVert(i, [&endVerts, &endVertSet, &results, this](int current) {892if (halfedge_[current].startVert == -1 ||893halfedge_[current].endVert == -1) {894return;895}896int endV = halfedge_[current].endVert;897if (endVertSet.empty()) {898auto iter = std::find_if(endVerts.begin(), endVerts.end(),899[endV](const std::pair<int, int>& pair) {900return pair.first == endV;901});902if (iter->second != current) results.push_back(current);903} else {904auto iter = endVertSet.find(endV);905if (iter->second != current) results.push_back(current);906}907});908}909};910#if MANIFOLD_PAR == 1911if (nbEdges > 1e4) {912std::mutex mutex;913tbb::combinable<std::vector<bool>> store(914[nbEdges]() { return std::vector<bool>(nbEdges, false); });915tbb::parallel_for(916tbb::blocked_range<size_t>(0, nbEdges),917[&store, &mutex, &duplicates, &localLoop](const auto& r) {918auto& local = store.local();919std::vector<size_t> duplicatesLocal;920localLoop(r.begin(), r.end(), local, duplicatesLocal);921if (!duplicatesLocal.empty()) {922std::lock_guard<std::mutex> lock(mutex);923duplicates.insert(duplicates.end(), duplicatesLocal.begin(),924duplicatesLocal.end());925}926});927manifold::stable_sort(duplicates.begin(), duplicates.end());928duplicates.resize(929std::distance(duplicates.begin(),930std::unique(duplicates.begin(), duplicates.end())));931} else932#endif933{934std::vector<bool> local(nbEdges, false);935localLoop(0, nbEdges, local, duplicates);936}937938size_t numFlagged = 0;939for (size_t i : duplicates) {940DedupeEdge(i);941numFlagged++;942}943944if (numFlagged == 0) break;945946#ifdef MANIFOLD_DEBUG947if (ManifoldParams().verbose > 0) {948std::cout << "found " << numFlagged << " duplicate edges to split"949<< std::endl;950}951#endif952}953}954} // namespace manifold955956957