// 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; }
static void insert_cavity_vertex(MutableTriangleTopology& mesh, RawField<const Perturbed2,VertexId> X, RawField<bool,VertexId> marked, const VertexId u, const VertexId v, const VertexId w) { #ifndef NDEBUG { const auto vw = mesh.halfedge(v); assert(mesh.is_boundary(vw) && mesh.dst(vw)==w); } #endif mesh.add_face(vec(u,v,w)); const auto vw = mesh.prev(mesh.reverse(mesh.halfedge(u))); assert(mesh.vertices(vw)==vec(v,w)); insert_cavity_vertex_helper(mesh,X,marked,vw); }
GEODE_NEVER_INLINE static void add_constraint_edges(MutableTriangleTopology& mesh, RawField<const EV,VertexId> X, RawArray<const Vector<int,2>> edges, const bool validate) { if (!edges.size()) return; IntervalScope scope; Hashtable<Vector<VertexId,2>> constrained; Array<VertexId> left_cavity, right_cavity; // List of vertices for both cavities const auto random = new_<Random>(key+7); for (int i=0;i<edges.size();i++) { // Randomly choose an edge to ensure optimal time complexity const auto edge = edges[int(random_permute(edges.size(),key+5,i))].sorted(); auto v0 = VertexId(edge.x), v1 = VertexId(edge.y); const auto vs = vec(v0,v1); GEODE_ASSERT(mesh.valid(v0) && mesh.valid(v1)); { // Check if the edge already exists in the triangulation. To ensure optimal complexity, // we loop around both vertices interleaved so that our time is O(min(degree(v0),degree(v1))). const auto s0 = mesh.halfedge(v0), s1 = mesh.halfedge(v1); { auto e0 = s0, e1 = s1; do { if (mesh.dst(e0)==v1 || mesh.dst(e1)==v0) goto success; // The edge already exists, so there's nothing to be done. e0 = mesh.left(e0); e1 = mesh.left(e1); } while (e0!=s0 && e1!=s1); } // Find a triangle touching v0 or v1 containing part of the v0-v1 segment. // As above, we loop around both vertices interleaved. auto e0 = s0; { auto e1 = s1; if (mesh.is_boundary(e0)) e0 = mesh.left(e0); if (mesh.is_boundary(e1)) e1 = mesh.left(e1); const auto x0 = Perturbed2(v0.id,X[v0]), x1 = Perturbed2(v1.id,X[v1]); const auto e0d = mesh.dst(e0), e1d = mesh.dst(e1); bool e0o = triangle_oriented(x0,Perturbed2(e0d.id,X[e0d]),x1), e1o = triangle_oriented(x1,Perturbed2(e1d.id,X[e1d]),x0); for (;;) { // No need to check for an end condition, since we're guaranteed to terminate const auto n0 = mesh.left(e0), n1 = mesh.left(e1); const auto n0d = mesh.dst(n0), n1d = mesh.dst(n1); const bool n0o = triangle_oriented(x0,Perturbed2(n0d.id,X[n0d]),x1), n1o = triangle_oriented(x1,Perturbed2(n1d.id,X[n1d]),x0); if (e0o && !n0o) break; if (e1o && !n1o) { // Swap v0 with v1 and e0 with e1 so that our ray starts at v0 swap(v0,v1); swap(e0,e1); break; } e0 = n0; e1 = n1; e0o = n0o; e1o = n1o; } } // If we only need to walk one step, the retriangulation is a single edge flip auto cut = mesh.reverse(mesh.next(e0)); if (mesh.dst(mesh.next(cut))==v1) { if (constrained.contains(vec(mesh.src(cut),mesh.dst(cut)).sorted())) throw DelaunayConstraintConflict(vec(v0.id,v1.id),vec(mesh.src(cut).id,mesh.dst(cut).id)); cut = mesh.flip_edge(cut); goto success; } // Walk from v0 to v1, collecting the two cavities. const auto x0 = Perturbed2(v0.id,X[v0]), x1 = Perturbed2(v1.id,X[v1]); right_cavity.copy(vec(v0,mesh.dst(cut))); left_cavity .copy(vec(v0,mesh.src(cut))); mesh.erase(mesh.face(e0)); for (;;) { if (constrained.contains(vec(mesh.src(cut),mesh.dst(cut)).sorted())) throw DelaunayConstraintConflict(vec(v0.id,v1.id),vec(mesh.src(cut).id,mesh.dst(cut).id)); const auto n = mesh.reverse(mesh.next(cut)), p = mesh.reverse(mesh.prev(cut)); const auto v = mesh.src(n); mesh.erase(mesh.face(cut)); if (v == v1) { left_cavity.append(v); right_cavity.append(v); break; } else if (triangle_oriented(x0,x1,Perturbed2(v.id,X[v]))) { left_cavity.append(v); cut = n; } else { right_cavity.append(v); cut = p; } } // Retriangulate both cavities left_cavity.reverse(); cavity_delaunay(mesh,X,left_cavity,random), cavity_delaunay(mesh,X,right_cavity,random); } success: constrained.set(vs); } // If desired, check that the final mesh is constrained Delaunay if (validate) assert_delaunay("constrained delaunay validate: ",mesh,X,constrained); }
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:; } }