void PoseGraph::clearGraph (void) { /// Clear out data associated with the graph _graph.clear(); _nextVertexId = VertexId(1); _nextEdgeId = EdgeId(1); _vertexMap.clear(); _edgeMap.clear(); //graphCleared(); }
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); }
// Retriangulate a cavity formed when a constraint edge is inserted, following Shewchuck and Brown. // The cavity is defined by a counterclockwise list of vertices v[0] to v[m-1] as in Shewchuck and Brown, Figure 5. static void cavity_delaunay(MutableTriangleTopology& parent_mesh, RawField<const EV,VertexId> X, RawArray<const VertexId> cavity, Random& random) { // Since the algorithm generates meshes which may be inconsistent with the outer mesh, and the cavity // array may have duplicate vertices, we use a temporary mesh and then copy the triangles over when done. // In the temporary, vertices are indexed consecutively from 0 to m-1. const int m = cavity.size(); assert(m >= 3); const auto mesh = new_<MutableTriangleTopology>(); Field<Perturbed2,VertexId> Xc(m,uninit); for (const int i : range(m)) Xc.flat[i] = Perturbed2(cavity[i].id,X[cavity[i]]); mesh->add_vertices(m); const auto xs = Xc.flat[0], xe = Xc.flat[m-1]; // Set up data structures for prev, next, pi in the paper const Field<VertexId,VertexId> prev(m,uninit), next(m,uninit); for (int i=0;i<m-1;i++) { next.flat[i] = VertexId(i+1); prev.flat[i+1] = VertexId(i); } const Array<VertexId> pi_(m-2,uninit); for (int i=1;i<=m-2;i++) pi_[i-1] = VertexId(i); #define PI(i) pi_[(i)-1] // Randomly shuffle [1,m-2], subject to vertices closer to xs-xe than both their neighbors occurring later for (int i=m-2;i>=2;i--) { int j; for (;;) { j = random.uniform<int>(0,i)+1; const auto pj = PI(j); if (!( segment_directions_oriented(xe,xs,Xc[pj],Xc[prev[pj]]) && segment_directions_oriented(xe,xs,Xc[pj],Xc[next[pj]]))) break; } swap(PI(i),PI(j)); // Remove PI(i) from the list const auto pi = PI(i); next[prev[pi]] = next[pi]; prev[next[pi]] = prev[pi]; } // Add the first triangle mesh->add_face(vec(VertexId(0),PI(1),VertexId(m-1))); // Add remaining triangles, flipping to ensure Delaunay const Field<bool,VertexId> marked(m); Array<HalfedgeId> fan; bool used_chew = false; for (int i=2;i<m-1;i++) { const auto pi = PI(i); insert_cavity_vertex(mesh,Xc,marked,pi,next[pi],prev[pi]); if (marked[pi]) { used_chew = true; marked[pi] = false; // Retriangulate the fans of triangles that have all three vertices marked auto e = mesh->reverse(mesh->halfedge(pi)); auto v = mesh->src(e); bool mv = marked[v]; marked[v] = false; fan.clear(); do { const auto h = mesh->prev(e); e = mesh->reverse(mesh->next(e)); v = mesh->src(e); const bool mv2 = marked[v]; marked[v] = false; if (mv) { if (mv2) fan.append(h); if (!mv2 || mesh->is_boundary(e)) { chew_fan(mesh,Xc,pi,fan,random); fan.clear(); } } mv = mv2; } while (!mesh->is_boundary(e)); } } #undef PI // If we ran Chew's algorithm, validate the output. I haven't tested this // case enough to be confident of its correctness. if (used_chew) assert_delaunay("Failure in extreme special case. If this triggers, please email [email protected]: ", mesh,Xc,Tuple<>(),false,false); // Copy triangles from temporary mesh to real mesh for (const auto f : mesh->faces()) { const auto v = mesh->vertices(f); parent_mesh.add_face(vec(cavity[v.x.id],cavity[v.y.id],cavity[v.z.id])); } }
// This routine assumes the sentinel points have already been added, and processes points in order GEODE_NEVER_INLINE static Ref<MutableTriangleTopology> deterministic_exact_delaunay(RawField<const Perturbed2,VertexId> X, const bool validate) { const int n = X.size()-3; const auto mesh = new_<MutableTriangleTopology>(); IntervalScope scope; // Initialize the mesh to a Delaunay triangle containing the sentinels at infinity. mesh->add_vertices(n+3); mesh->add_face(vec(VertexId(n+0),VertexId(n+1),VertexId(n+2))); if (self_check) { mesh->assert_consistent(); assert_delaunay("self check 0: ",mesh,X); } // The randomized incremental construction algorithm uses the history of the triangles // as the acceleration structure. Specifically, we maintain a BSP tree where the nodes // are edge tests (are we only the left or right of an edge) and the leaves are triangles. // There are two operations that modify this tree: // // 1. Split: A face is split into three by the insertion of an interior vertex. // 2. Flip: An edge is flipped, turning two triangles into two different triangles. // // The three starts out empty, since one triangle needs zero tests. Array<Node> bsp; // All BSP nodes including leaves bsp.preallocate(3*n); // The minimum number of possible BSP nodes Field<Vector<int,2>,FaceId> face_to_bsp; // Map from FaceId to up to two BSP leaf points (2*node+(right?0:1)) face_to_bsp.flat.preallocate(2*n+1); // The exact maximum number of faces face_to_bsp.flat.append_assuming_enough_space(vec(0,-1)); // By the time we call set_links, node 0 will be valid if (self_check) check_bsp(*mesh,bsp,face_to_bsp,X); // Allocate a stack to simulate recursion when flipping non-Delaunay edges. // Invariant: if edge is on the stack, the other edges of face(edge) are Delaunay. // Since halfedge ids change during edge flips in a corner mesh, we store half edges as directed vertex pairs. Array<Tuple<HalfedgeId,Vector<VertexId,2>>> stack; stack.preallocate(8); // Insert all vertices into the mesh in random order, maintaining the Delaunay property for (const auto i : range(n)) { const VertexId v(i); check_interrupts(); // Search through the BSP tree to find the containing triangle const auto f0 = bsp_search(bsp,X,v); const auto vs = mesh->vertices(f0); // Split the face by inserting the new vertex and update the BSP tree accordingly. mesh->split_face(f0,v); const auto e0 = mesh->halfedge(v), e1 = mesh->left(e0), e2 = mesh->right(e0); assert(mesh->dst(e0)==vs.x); const auto f1 = mesh->face(e1), f2 = mesh->face(e2); const int base = bsp.extend(3,uninit); set_links(bsp,face_to_bsp[f0],base); bsp[base+0] = Node(vec(v,vs.x),base+2,base+1); bsp[base+1] = Node(vec(v,vs.y),~f0.id,~f1.id); bsp[base+2] = Node(vec(v,vs.z),~f1.id,~f2.id); face_to_bsp[f0] = vec(2*(base+1)+0,-1); face_to_bsp.flat.append_assuming_enough_space(vec(2*(base+1)+1,2*(base+2)+0)); face_to_bsp.flat.append_assuming_enough_space(vec(2*(base+2)+1,-1)); if (self_check) { mesh->assert_consistent(); check_bsp(*mesh,bsp,face_to_bsp,X); } // Fix all non-Delaunay edges stack.copy(vec(tuple(mesh->next(e0),vec(vs.x,vs.y)), tuple(mesh->next(e1),vec(vs.y,vs.z)), tuple(mesh->next(e2),vec(vs.z,vs.x)))); if (self_check) assert_delaunay("self check 1: ",mesh,X,Tuple<>(),true); while (stack.size()) { const auto evs = stack.pop(); auto e = mesh->vertices(evs.x)==evs.y ? evs.x : mesh->halfedge(evs.y.x,evs.y.y); if (e.valid() && !is_delaunay(*mesh,X,e)) { // Our mesh is linearly embedded in the plane, so edge flips are always safe assert(mesh->is_flip_safe(e)); e = mesh->unsafe_flip_edge(e); GEODE_ASSERT(is_delaunay(*mesh,X,e)); // Update the BSP tree for the triangle flip const auto f0 = mesh->face(e), f1 = mesh->face(mesh->reverse(e)); const int node = bsp.append(Node(mesh->vertices(e),~f1.id,~f0.id)); set_links(bsp,face_to_bsp[f0],node); set_links(bsp,face_to_bsp[f1],node); face_to_bsp[f0] = vec(2*node+1,-1); face_to_bsp[f1] = vec(2*node+0,-1); if (self_check) { mesh->assert_consistent(); check_bsp(*mesh,bsp,face_to_bsp,X); } // Recurse to successor edges to e const auto e0 = mesh->next(e), e1 = mesh->prev(mesh->reverse(e)); stack.extend(vec(tuple(e0,mesh->vertices(e0)), tuple(e1,mesh->vertices(e1)))); if (self_check) assert_delaunay("self check 2: ",mesh,X,Tuple<>(),true); } } if (self_check) { mesh->assert_consistent(); assert_delaunay("self check 3: ",mesh,X); } } // Remove sentinels for (int i=0;i<3;i++) mesh->erase_last_vertex_with_reordering(); // If desired, check that the final mesh is Delaunay if (validate) assert_delaunay("delaunay validate: ",mesh,X); // Return the mesh with the sentinels removed return mesh; }