// 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 inline FaceId bsp_search(RawArray<const Node> bsp, RawField<const Perturbed2,VertexId> X, const VertexId v) { if (!bsp.size()) return FaceId(0); int node = 0; do { const Node& n = bsp[node]; node = n.children[triangle_oriented(X,n.test.x,n.test.y,v)]; } while (node >= 0); return FaceId(~node); }
GEODE_ALWAYS_INLINE static inline bool triangle_oriented(const RawField<const Perturbed2,VertexId> X, VertexId v0, VertexId v1, VertexId v2) { const auto x0 = X[v0], x1 = X[v1], x2 = X[v2]; // If we have a nonsentinel triangle, use a normal orientation test if (maxabs(x0.value().x,x1.value().x,x2.value().x)!=bound) return triangle_oriented(x0,x1,x2); // Fall back to sentinel case analysis return triangle_oriented_sentinels(x0,x1,x2); }
GEODE_COLD static void assert_delaunay(const char* prefix, const TriangleTopology& mesh, RawField<const Perturbed2,VertexId> X, const Hashtable<Vector<VertexId,2>>& constrained=Tuple<>(), const bool oriented_only=false, const bool check_boundary=true) { // Verify that all faces are correctly oriented for (const auto f : mesh.faces()) { const auto v = mesh.vertices(f); GEODE_ASSERT(triangle_oriented(X,v.x,v.y,v.z)); } if (oriented_only) return; // Verify that all internal edges are Delaunay if (!constrained.size()) { for (const auto e : mesh.interior_halfedges()) if (!mesh.is_boundary(mesh.reverse(e)) && mesh.src(e)<mesh.dst(e)) if (!is_delaunay(mesh,X,e)) throw RuntimeError(format("%snon delaunay edge: e%d, v%d v%d",prefix,e.id,mesh.src(e).id,mesh.dst(e).id)); } else { for (const auto v : constrained) if (!mesh.halfedge(v.x,v.y).valid()) throw RuntimeError(format("%smissing constraint edge: v%d v%d",prefix,v.x.id,v.y.id)); for (const auto e : mesh.interior_halfedges()) { const auto s = mesh.src(e), d = mesh.dst(e); if (!mesh.is_boundary(mesh.reverse(e)) && s<d && !constrained.contains(vec(s,d))) if (!is_delaunay(mesh,X,e)) throw RuntimeError(format("%snon delaunay edge: e%d, v%d v%d",prefix,e.id,mesh.src(e).id,mesh.dst(e).id)); } } // Verify that all boundary vertices are convex if (check_boundary) for (const auto e : mesh.boundary_edges()) { const auto v0 = mesh.src(mesh.prev(e)), v1 = mesh.src(e), v2 = mesh.dst(e); GEODE_ASSERT(triangle_oriented(X,v2,v1,v0)); } }
// Test whether an edge containing sentinels is Delaunay GEODE_COLD GEODE_PURE static inline bool is_delaunay_sentinels(Perturbed2 x0, Perturbed2 x1, Perturbed2 x2, Perturbed2 x3) { // Unfortunately, the sentinels need to be at infinity for purposes of Delaunay testing, and our SOS predicates // don't support infinities. Therefore, we need some case analysis. First, we move all the sentinels to the end, // sorted in decreasing order of index. Different sentinels will be placed at different orders of infinity, // with earlier indices further away, so our order will place larger infinities last. bool parity = 0; COMPARATOR(0,1) COMPARATOR(2,3) COMPARATOR(0,2) COMPARATOR(1,3) COMPARATOR(1,2) if (!is_sentinel(x2)) // One infinity: A finite circle contains infinity iff it is inside out, so we reduce to an orientation test return parity^triangle_oriented(x0,x1,x2); else if (!is_sentinel(x1)) // Two infinities: also an orientation test, but with the last point at infinity return parity^segment_to_direction_oriented(x0,x1,x2); else // Three infinities: the finite point no longer matters. return parity^directions_oriented(x1,x2); }
static void predicate_tests() { IntervalScope scope; typedef Vector<double,2> TV2; typedef Vector<Quantized,2> QV2; // Compare triangle_oriented and incircle against approximate floating point versions struct F { static inline double triangle_oriented(const TV2 p0, const TV2 p1, const TV2 p2) { return edet(p1-p0,p2-p0); }; static inline double incircle(const TV2 p0, const TV2 p1, const TV2 p2, const TV2 p3) { const auto d0 = p0-p3, d1 = p1-p3, d2 = p2-p3; return edet(ROW(d0),ROW(d1),ROW(d2)); } }; const auto random = new_<Random>(9817241); for (int step=0;step<100;step++) { #define MAKE(i) \ const auto p##i = tuple(i,QV2(random->uniform<Vector<ExactInt,2>>(-exact::bound,exact::bound))); \ const TV2 x##i(p##i.y); MAKE(0) MAKE(1) MAKE(2) MAKE(3) GEODE_ASSERT(triangle_oriented(p0,p1,p2)==(F::triangle_oriented(x0,x1,x2)>0)); GEODE_ASSERT(incircle(p0,p1,p2,p3)==(F::incircle(x0,x1,x2,x3)>0)); } // Test behavior for large numbers, using the scale invariance and antisymmetry of incircle. for (const int i : range(exact::log_bound)) { const auto bound = ExactInt(1)<<i; const auto p0 = tuple(0,QV2(-bound,-bound)), // Four points on a circle of radius sqrt(2)*bound p1 = tuple(1,QV2( bound,-bound)), p2 = tuple(2,QV2( bound, bound)), p3 = tuple(3,QV2(-bound, bound)); GEODE_ASSERT(!incircle(p0,p1,p2,p3)); GEODE_ASSERT( incircle(p0,p1,p3,p2)); } }
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); }
bool segments_intersect(const P2 a0, const P2 a1, const P2 b0, const P2 b1) { return triangle_oriented(a0,a1,b0)!=triangle_oriented(a0,a1,b1) && triangle_oriented(b0,b1,a0)!=triangle_oriented(b0,b1,a1); }