// InsertVertex in the paper static void insert_cavity_vertex_helper(MutableTriangleTopology& mesh, RawField<const Perturbed2,VertexId> X, RawField<bool,VertexId> marked, const HalfedgeId vw) { // If wv is a boundary edge, or we're already Delaunay and properly oriented, we're done const auto wv = mesh.reverse(vw); if (mesh.is_boundary(wv)) return; const auto u = mesh.opposite(vw), v = mesh.src(vw), w = mesh.dst(vw), x = mesh.opposite(wv); const auto Xu = X[u], Xv = X[v], Xw = X[w], Xx = X[x]; const bool in = incircle(Xu,Xv,Xw,Xx); if (!in && triangle_oriented(Xu,Xv,Xw)) return; // Flip edge and recurse const auto xu = mesh.flip_edge(wv); assert(mesh.vertices(xu)==vec(x,u)); const auto vx = mesh.prev(xu), xw = mesh.next(mesh.reverse(xu)); // Grab this now before the recursive call changes uvx insert_cavity_vertex_helper(mesh,X,marked,vx), insert_cavity_vertex_helper(mesh,X,marked,xw); if (!in) marked[u] = marked[v] = marked[w] = marked[x] = true; }
// Delaunay retriangulate a triangle fan static void chew_fan(MutableTriangleTopology& parent_mesh, RawField<const Perturbed2,VertexId> X, const VertexId u, RawArray<HalfedgeId> fan, Random& random) { chew_fan_count_ += 1; #ifndef NDEBUG for (const auto e : fan) assert(parent_mesh.opposite(e)==u); for (int i=0;i<fan.size()-1;i++) GEODE_ASSERT(parent_mesh.src(fan[i])==parent_mesh.dst(fan[i+1])); #endif const int n = fan.size(); if (n < 2) return; chew_fan_count_ += 1024*n; // Collect vertices const Field<VertexId,VertexId> vertices(n+2,uninit); vertices.flat[0] = u; vertices.flat[1] = parent_mesh.src(fan[n-1]); for (int i=0;i<n;i++) vertices.flat[i+2] = parent_mesh.dst(fan[n-1-i]); // Delete original vertices for (const auto e : fan) parent_mesh.erase(parent_mesh.face(e)); // Make the vertices into a doubly linked list const Field<VertexId,VertexId> prev(n+2,uninit), next(n+2,uninit); prev.flat[0].id = n+1; next.flat[n+1].id = 0; for (int i=0;i<n+1;i++) { prev.flat[i+1].id = i; next.flat[i].id = i+1; } // Randomly shuffle the vertices, then pulling elements off the linked list in reverse order of our final shuffle. const Array<VertexId> pi(n+2,uninit); for (int i=0;i<n+2;i++) pi[i].id = i; random.shuffle(pi); for (int i=n+1;i>=0;i--) { const auto j = pi[i]; prev[next[j]] = prev[j]; next[prev[j]] = next[j]; } // Make a new singleton mesh const auto mesh = new_<MutableTriangleTopology>(); mesh->add_vertices(n+2); small_sort(pi[0],pi[1],pi[2]); mesh->add_face(vec(pi[0],pi[1],pi[2])); // Insert remaining vertices Array<HalfedgeId> work; for (int i=3;i<n+2;i++) { const auto j = pi[i]; const auto f = mesh->add_face(vec(j,next[j],prev[j])); work.append(mesh->reverse(mesh->opposite(f,j))); while (work.size()) { auto e = work.pop(); if ( !mesh->is_boundary(e) && incircle(X[vertices[mesh->src(e)]], X[vertices[mesh->dst(e)]], X[vertices[mesh->opposite(e)]], X[vertices[mesh->opposite(mesh->reverse(e))]])) { work.append(mesh->reverse(mesh->next(e))); work.append(mesh->reverse(mesh->prev(e))); e = mesh->unsafe_flip_edge(e); } } } // Copy triangles back to parent for (const auto f : mesh->faces()) { const auto vs = mesh->vertices(f); parent_mesh.add_face(vec(vertices[vs.x],vertices[vs.y],vertices[vs.z])); } }
void decimate_inplace(MutableTriangleTopology& mesh, RawField<TV,VertexId> X, const T distance, const T max_angle, const int min_vertices, const T boundary_distance) { if (mesh.n_vertices() <= min_vertices) return; const T area = sqr(distance); const T sign_sqr_min_cos = sign_sqr(max_angle > .99*pi ? -1 : cos(max_angle)); // Finds the best edge to collapse v along. Returns (q(e),dst(e)). const auto best_collapse = [&mesh,X](const VertexId v) { Quadric q = compute_quadric(mesh,X,v); // Find the best edge, ignoring normal constraints T min_q = inf; HalfedgeId min_e; for (const auto e : mesh.outgoing(v)) { const T qx = q(X[mesh.dst(e)]); if (min_q > qx) { min_q = qx; min_e = e; } } return tuple(min_q,mesh.dst(min_e)); }; // Initialize quadrics and heap Heap heap(mesh.n_vertices_); for (const auto v : mesh.vertices()) { const auto qe = best_collapse(v); if (qe.x <= area) heap.inv_heap[v] = heap.heap.append(tuple(v,qe.x,qe.y)); } heap.make(); // Update the quadric information for a vertex const auto update = [&heap,best_collapse,area](const VertexId v) { const auto qe = best_collapse(v); if (qe.x <= area) heap.set(v,qe.x,qe.y); else heap.erase(v); }; // Repeatedly collapse the best vertex while (heap.size()) { const auto v = heap.pop(); // Do these vertices still exist? if (mesh.valid(v.x) && mesh.valid(v.y)) { const auto e = mesh.halfedge(v.x,v.y); // Is the collapse invalid? if (e.valid() && mesh.is_collapse_safe(e)) { const auto vs = mesh.src(e), vd = mesh.dst(e); const auto xs = X[vs], xd = X[vd]; // Are we moving a boundary vertex too far from its two boundary lines? { const auto b = mesh.halfedge(vs); if (mesh.is_boundary(b)) { const auto x0 = X[mesh.dst(b)], x1 = X[mesh.src(mesh.prev(b))]; if ( line_point_distance(simplex(xs,x0),xd) > boundary_distance || line_point_distance(simplex(xs,x1),xd) > boundary_distance) goto bad; } } // Do the normals change too much? if (sign_sqr_min_cos > -1) for (const auto ee : mesh.outgoing(vs)) if (e!=ee && !mesh.is_boundary(ee)) { const auto v2 = mesh.opposite(ee); if (v2 != vd) { const auto x1 = X[mesh.dst(ee)], x2 = X[v2]; const auto n0 = cross(x2-x1,xs-x1), n1 = cross(x2-x1,xd-x1); if (sign_sqr(dot(n0,n1)) < sign_sqr_min_cos*sqr_magnitude(n0)*sqr_magnitude(n1)) goto bad; } } // Collapse vs onto vd, then update the heap mesh.unsafe_collapse(e); if (mesh.n_vertices() <= min_vertices) break; update(vd); for (const auto e : mesh.outgoing(vd)) update(mesh.dst(e)); } } bad:; } }