GEODE_COLD static void assert_delaunay(const char* prefix, const TriangleTopology& mesh, RawField<const EV,VertexId> X, const Hashtable<Vector<VertexId,2>>& constrained=Tuple<>(), const bool oriented_only=false, const bool check_boundary=true) { Field<Perturbed2,VertexId> Xp(X.size(),uninit); for (const int i : range(X.size())) Xp.flat[i] = Perturbed2(i,X.flat[i]); assert_delaunay(prefix,mesh,Xp,constrained,oriented_only,check_boundary); }
GEODE_UNUSED static void check_bsp(const TriangleTopology& mesh, RawArray<const Node> bsp, RawField<const Vector<int,2>,FaceId> face_to_bsp, RawField<const Perturbed2,VertexId> X_) { if (self_check) { cout << "bsp:\n"; #define CHILD(c) format("%c%d",(c<0?'f':'n'),(c<0?~c:c)) for (const int n : range(bsp.size())) cout << " "<<n<<" : test "<<bsp[n].test<<", children "<<CHILD(bsp[n].children[0])<<" "<<CHILD(bsp[n].children[1])<<endl; cout << "tris = "<<mesh.elements()<<endl; cout << "X = "<<X_.flat<<endl; } for (const int n : range(bsp.size())) for (const int i : range(2)) if (bsp[n].children[i]<0) GEODE_ASSERT(face_to_bsp[FaceId(~bsp[n].children[i])].contains(2*n+i)); auto X = X_.copy(); for (const auto f : mesh.faces()) { int i0,i1; face_to_bsp[f].get(i0,i1); if (bsp.size()) GEODE_ASSERT(bsp[i0/2].children[i0&1]==~f.id); if (i1>=0) GEODE_ASSERT(bsp[i1/2].children[i1&1]==~f.id); const auto v = mesh.vertices(f); if (!is_sentinel(X[v.x]) && !is_sentinel(X[v.y]) && !is_sentinel(X[v.z])) { const auto center = X.append(Perturbed2(X.size(),(X[v.x].value()+X[v.y].value()+X[v.z].value())/3)); const auto found = bsp_search(bsp,X,center); if (found!=f) { cout << "bsp search failed: f "<<f<<", v "<<v<<", found "<<found<<endl; GEODE_ASSERT(false); } X.flat.pop(); } } }
Tuple<Ref<const TriangleTopology>,Field<const TV,VertexId>> decimate(const TriangleTopology& mesh, RawField<const TV,VertexId> X, const T distance, const T max_angle, const int min_vertices, const T boundary_distance) { const auto rmesh = mesh.mutate(); const auto rX = X.copy(); decimate_inplace(rmesh,rX,distance,max_angle,min_vertices,boundary_distance); return Tuple<Ref<const TriangleTopology>,Field<const TV,VertexId>>(rmesh,rX); }
// 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; }