void Mesh<VertData,TriData>::edgeSplit( RemeshScratchpad &scratchpad, Eptr e_split ) { /* Clean-up picture please... * * vs_opp * / \ * / \ * e0s / \ e1s * / \ * / ts_orig \ * / \ * v0 *---------------------------* v1 * e_split * _ * | * v * * * vs_opp * / | \ * / | \ * e0s / | <----------- es_mid * / | \ * / | \ * / t0s_new | t1s_new \ * v0 *-------------*-------------* v1 * e0_new v_new e1_new * BEGIN new vertex -- invoke interpolation callback FOR EACH TRIANGLE SUB-PROBLEM: new next triangle (from split) new prev triangle (from split) -- invoke triangle split callback delete triangle FIXUP all of the Tri-Edges */ /* Identify all the following components * and separate them according to type: * - e_split: the edge to be split * - ts_orig: the triangles to be split * - v0, v1: the two endpoint vertices of the edge * (the following is helper data; do not delete these vertices) * - vs_opp: the vertices opposite eid_split for each triangle */ ShortVec<Tptr, 2> ts_orig = e_split->tris; Vptr v0 = e_split->verts[0]; Vptr v1 = e_split->verts[1]; ShortVec<Vptr, 2> vs_opp(ts_orig.size()); for(uint i=0; i<ts_orig.size(); i++) { Tptr t_orig = ts_orig[i]; for(uint k=0; k<3; k++) { if(t_orig->edges[k] == e_split) { vs_opp[i] = t_orig->verts[k]; break; } } } /* Next, we need to create the new geometry which will supplant * the just enumerated parts: * - v_new: the vertex introduced by the split * - e0_new, * e1_new: the two pieces of e_split * - t0s_new, * t1s_new: the two pieces of each triangle in tids_orig * - es_mid: the new edges splitting each triangle */ Vptr v_new = scratchpad.cache.newVert(); Eptr e0_new = allocateRemeshEdge(scratchpad); Eptr e1_new = allocateRemeshEdge(scratchpad); ShortVec<Tptr, 2> t0s_new(ts_orig.size()); ShortVec<Tptr, 2> t1s_new(ts_orig.size()); ShortVec<Eptr, 2> es_mid(ts_orig.size()); for(uint i=0; i<ts_orig.size(); i++) { t0s_new[i] = scratchpad.cache.newTri(); t1s_new[i] = scratchpad.cache.newTri(); es_mid[i] = allocateRemeshEdge(scratchpad); } /* Now we want to connect up all of this new geometry to each other * and to the existing geometry, * BUT!!! * we also want to be careful not to point any existing geometry * at the new pieces. Before doing that, we want to be able to * compute data for the new geometry and confirm or deny that we * want to commit this operation. (e.g. check for unwanted collisions) * * We adopt the following strategy: * - first point the new triangles at edges and vertices * - next, point the new edges at vertices; and * point the new edges at new triangles. * - finally, point the new vertex at * - the new edges and new triangles */ // hook up t0s_new and t1s_new // also go ahead and hook up es_mid for(uint i=0; i<ts_orig.size(); i++) { Tptr t_orig = ts_orig[i]; Tptr t0 = t0s_new[i]; Tptr t1 = t1s_new[i]; Eptr e_mid = es_mid[i]; // replace every edge and vertex appropriately for the two variants for(uint k=0; k<3; k++) { Vptr v_orig = t_orig->verts[k]; Eptr e_orig = t_orig->edges[k]; if(v_orig == v0) { t0->verts[k] = v_orig; t0->edges[k] = e_mid; t1->verts[k] = v_new; t1->edges[k] = e_orig; } else if(v_orig == v1) { t0->verts[k] = v_new; t0->edges[k] = e_orig; t1->verts[k] = v_orig; t1->edges[k] = e_mid; } else { t0->verts[k] = v_orig; t0->edges[k] = e0_new; t1->verts[k] = v_orig; t1->edges[k] = e1_new; } } populateTriFromTopoTri(t0); populateTriFromTopoTri(t1); // set up the mid edge from the split e_mid->verts[0] = v_new; e_mid->verts[1] = vs_opp[i]; e_mid->tris.resize(2); e_mid->tris[0] = t0; e_mid->tris[1] = t1; } // hook up e0_new and e1_new e0_new->verts[0] = v0; e0_new->verts[1] = v_new; e1_new->verts[0] = v_new; e1_new->verts[1] = v1; for(uint i=0; i<ts_orig.size(); i++) { e0_new->tris.push_back(t0s_new[i]); e1_new->tris.push_back(t1s_new[i]); } // hook up v_new v_new->edges.push_back(e0_new); v_new->edges.push_back(e1_new); for(uint i=0; i<ts_orig.size(); i++) { v_new->edges.push_back(es_mid[i]); v_new->tris.push_back(t0s_new[i]); v_new->tris.push_back(t1s_new[i]); } // OK, here we get to finally compute data for all the new geometry // Once we've done that, we can also check to see whether we actually // want to commit this operation or not. // interpolate data onto the new vertex { VertData &data_new = verts[v_new->ref]; const VertData &data0 = verts[v0->ref]; const VertData &data1 = verts[v1->ref]; data_new.interpolate(data0, data1); data_new.manifold = (ts_orig.size() == 2); } // split triangles' data for(uint i=0; i<ts_orig.size(); i++) { Tptr t_orig = ts_orig[i]; Tptr t0 = t0s_new[i]; Tptr t1 = t1s_new[i]; split_tris(t0->ref, t1->ref, t_orig->ref); } // TODO: COMMIT OPTION will be left as a stub for now! if(false) { // DESTROY THE GEOMETRY WE JUST CREATED AND RETURN } // record border edges for later priority updates ShortVec<Eptr, 4> borderEdges; for(Tptr t : ts_orig) { // TODO: evacuate all of this to the end... for(uint k=0; k<3; k++) { if(t->edges[k] == e_split) continue; borderEdges.push_back(t->edges[k]); } } /* Now that we've got the go ahead, let's hook in the new geometry to * the existing geometry! * We can do this in the following order: * - take all the new edges, and add them to their existing endpoint's * edge list. (all new edges must have exactly one such endpoint) * - take all the new triangles, and add them to their two existing * endpoints' and one existing edge's triangle lists. */ // add new edges to v0 and v1 v0->edges.push_back(e0_new); v1->edges.push_back(e1_new); // now, let's tackle the other edges and triangles in tandem... for(uint i=0; i<ts_orig.size(); i++) { Tptr t_orig = ts_orig[i]; Vptr v_opp = vs_opp[i]; // add mid edge and two tris to v_opp v_opp->edges.push_back(es_mid[i]); v_opp->tris.push_back(t0s_new[i]); v_opp->tris.push_back(t1s_new[i]); // add resp. tris to v0 and v1 v0->tris.push_back(t0s_new[i]); v1->tris.push_back(t1s_new[i]); // find the two non-split edges and add resp. tris for(uint k=0; k<3; k++) { if(t_orig->verts[k] == v0) { Eptr e1 = t_orig->edges[k]; e1->tris.push_back(t1s_new[i]); } else if(t_orig->verts[k] == v1) { Eptr e0 = t_orig->edges[k]; e0->tris.push_back(t0s_new[i]); } } } /* Now, let's kill all the old geometry. This consists of: * - e_split: the edge to be split * - ts_orig: the triangles to be split * * Luckily, in triangle splitting we know exactly which things * must be deleted. A split cannot make any geometry newly singular. */ // kill triangles for(Tptr t : ts_orig) { // First, unhook this triangle from its faces for(uint k=0; k<3; k++) { Vptr v = t->verts[k]; remove(v->tris, t); Eptr e = t->edges[k]; remove(e->tris, t); } // now that we're disconnected, jettison the triangle scratchpad.cache.freeTri(t); } // now, kill the edge that we split remove(v0->edges, e_split); remove(v1->edges, e_split); deallocateRemeshEdge(scratchpad, e_split); // recompute edge scores for all edges whose scores might be effected // Don't need to dequeue newly created edges... scoreAndEnqueue(scratchpad.queue, e0_new); scoreAndEnqueue(scratchpad.queue, e1_new); for(Eptr e : es_mid) { scoreAndEnqueue(scratchpad.queue, e); } for(Eptr e : borderEdges) { dequeue(scratchpad.queue, e); scoreAndEnqueue(scratchpad.queue, e); } }
void Mesh<VertData, TriData>::edgeCollapse( RemeshScratchpad &scratchpad, Eptr e_collapse, bool collapsing_tetrahedra_disappear ) { /* * * Two cases: both with and without a triangle filling the * triangular arrangement (i.e. wedge) of edges * * * * / \ * / # # # \ * / # # # # \ * / # # # # # # # \ * / # # # # # # # # \ * / # # # # # # # # # # # \ * vid0 *---------------------------* vid1 * eid_collapse * * * * / \ * / \ * / \ * / \ * / \ * / \ * vid0 *---------------------------* vid1 * eid_collapse * * * Additionally, we need to worry about how tetrahedral structures * can collapse. If the collapse will identify the two * non-collapsing faces, then we call this structure a triangle wedge * * * * * /|\ * / | \ * / | \ * / # | # \ * / # | # \ * / # | # # \ * / # # * # # \ * / # / \ # \ * / # / \ # \ * / / \ \ * / / \ \ * / / \ \ * // \\ * vid0 *---------------------------* vid1 * eid_collapse * * * In summary, there are three features we should be interested in: -- hollow edge wedges that will be collapsed -- filled edge wedges that will be collapsed -- triangle wedges that will be collapsed We can identify all edge wedges (hollow or filled) via: JOIN FILTER(EDGES(A,X), X!=B) WITH FILTER(EDGES(B,Y), Y!=A) WHERE X=Y We can identify all triangle wedges via: JOIN FILTER(TRIANGLES(A,X,Y), X!=B && Y!=B) WITH FILTER(TRIANGLES(B,Z,W), Z!=A && W!=A) WHERE (X,Y)=(Z,W) or (X,Y)=(W,Z) */ /* Identify all the following components * and separate them according to type: * - e_collapse: the collapsing edge * - tris_collapse: the collapsing triangles * - v0, v1: the merging vertices * - edge_wedges: the merging edges * - tri_wedges: the merging (or disappearing) triangles * - edges_moving: the persisting, but moving edges * - tris_moving: the persisting, but moving triangles */ ShortVec<Tptr, 2> tris_collapse = e_collapse->tris; Vptr v0 = e_collapse->verts[0]; Vptr v1 = e_collapse->verts[1]; ShortVec<EdgeWedge, 2> edge_wedges; ShortVec<Eptr, 10> edges_moving; ShortVec<TriWedge, 2> tri_wedges; ShortVec<Tptr, 14> tris_moving; // BUILD all the edge wedges std::map<Vptr, EdgeWedge> edge_w_map; for(Eptr e : v0->edges) { if(e == e_collapse) continue; Vptr ev0 = e->verts[0]; Vptr ev1 = e->verts[1]; Vptr key = (ev0 != v0)? ev0 : ev1; edge_w_map[key].e0 = e; } for(Eptr e : v1->edges) { if(e == e_collapse) continue; Vptr ev0 = e->verts[0]; Vptr ev1 = e->verts[1]; Vptr key = (ev0 != v1)? ev0 : ev1; edge_w_map[key].e1 = e; } for(const auto &pair : edge_w_map) { if(pair.second.full()) edge_wedges.push_back(pair.second); else edges_moving.push_back(pair.second.one()); } // BUILD all the triangle wedges std::map<Eptr, TriWedge> tri_w_map; for(Tptr t : v0->tris) { if(t->edges[0] == e_collapse || t->edges[1] == e_collapse || t->edges[2] == e_collapse) continue; Eptr key = nullptr; for(uint k=0; k<3; k++) if(t->verts[k] == v0) key = t->edges[k]; ENSURE(key != NULL); tri_w_map[key].t0 = t; } for(Tptr t : v1->tris) { if(t->edges[0] == e_collapse || t->edges[1] == e_collapse || t->edges[2] == e_collapse) continue; Eptr key = nullptr; for(uint k=0; k<3; k++) if(t->verts[k] == v1) key = t->edges[k]; ENSURE(key != NULL); tri_w_map[key].t1 = t; } for(const auto &pair : tri_w_map) { if(pair.second.full()) tri_wedges.push_back(pair.second); else tris_moving.push_back(pair.second.one()); } /* Next, we need to create the new geometry which will supplant * the just enumerated parts: * - v_merged: the merged vertex * - edges_merged: the merged edges (parallel to edge_wedges) * - tris_merged: the merged triangles (parallel to tri_wedges) * - edges_moved: the moved edges (parallel to edges_moving) * - tris_moved: the moved triangles (parallel to tris_moving) */ /* As part of this, we will build a record of the remapping to * aid us when we start to connect all of this geometry */ Vptr v_merged = scratchpad.cache.newVert(); ShortVec<Eptr, 2> edges_merged(edge_wedges.size()); ShortVec<Eptr, 10> edges_moved(edges_moving.size()); ShortVec<Tptr, 2> tris_merged(tri_wedges.size()); ShortVec<Tptr, 14> tris_moved(tris_moving.size()); VptrRemap vptr_remap(v0, v1, v_merged); PtrRemap<TopoEdge> eptr_remap; PtrRemap<TopoTri> tptr_remap; // Create new edges and enter re-mappings eptr_remap.set(e_collapse, nullptr); // mark this edge as dying for(uint i=0; i<edge_wedges.size(); i++) { edges_merged[i] = allocateRemeshEdge(scratchpad); eptr_remap.set(edge_wedges[i].e0, edges_merged[i]); eptr_remap.set(edge_wedges[i].e1, edges_merged[i]); } for(uint i=0; i<edges_moving.size(); i++) { edges_moved[i] = allocateRemeshEdge(scratchpad); eptr_remap.set(edges_moving[i], edges_moved[i]); } // Create new triangles and enter re-mappings for(Tptr t : tris_collapse) // mark this triangle as dying tptr_remap.set(t, nullptr); for(uint i=0; i<tri_wedges.size(); i++) { if(collapsing_tetrahedra_disappear) { tris_merged[i] = nullptr; } else { tris_merged[i] = scratchpad.cache.newTri(); } tptr_remap.set(tri_wedges[i].t0, tris_merged[i]); tptr_remap.set(tri_wedges[i].t1, tris_merged[i]); } for(uint i=0; i<tris_moving.size(); i++) { tris_moved[i] = scratchpad.cache.newTri(); tptr_remap.set(tris_moving[i], tris_moved[i]); } /* Now we want to connect up all of this new geometry to each other * and to the existing geometry, * BUT!!! * we also want to be careful not to point any existing geometry * at the new pieces. Before doing that, we want to be able to * compute data for the new geometry and confirm or deny that we * want to commit this operation. (e.g. check for unwanted collisions) * * We adopt the following strategy: * - first point the new triangles at edges and vertices * - next, point the new edges at vertices; and * point the new edges at new triangles. * - finally, point the new vertex at * - the new edges and new triangles * * If an edge cannot find any valid triangles it is incident to, * then it must be marked for deletion, etc. * If the merged vertex cannot find any valid tris/edges it is incident to, * then it must be marked for deletion too! */ // First, the triangles if(!collapsing_tetrahedra_disappear) { for(uint i=0; i<tri_wedges.size(); i++) { Tptr t0 = tri_wedges[i].t0; //Tptr t1 = tri_wedges[i].t1; Tptr t_new = tris_merged[i]; for(uint k=0; k<3; k++) { t_new->verts[k] = vptr_remap[t0->verts[k]]; t_new->edges[k] = eptr_remap[t0->edges[k]]; } populateTriFromTopoTri(t_new); } } for(uint i=0; i<tris_moving.size(); i++) { Tptr t_old = tris_moving[i]; Tptr t_new = tris_moved[i]; for(uint k=0; k<3; k++) { t_new->verts[k] = vptr_remap[t_old->verts[k]]; t_new->edges[k] = eptr_remap[t_old->edges[k]]; } populateTriFromTopoTri(t_new); } // Next, the edges for(uint i=0; i<edge_wedges.size(); i++) { Eptr e0 = edge_wedges[i].e0; Eptr e1 = edge_wedges[i].e1; Eptr e_new = edges_merged[i]; // plug in all the valid triangles... for(Tptr t : e0->tris) { t = tptr_remap[t]; if(t) e_new->tris.push_back(t); } for(Tptr t : e1->tris) { t = tptr_remap[t]; if(t) e_new->tris.push_back(t); } if(e_new->tris.size() == 0) { // if there are no parent triangles left // then we need to kill this edge eptr_remap.set(e0, nullptr); eptr_remap.set(e1, nullptr); deallocateRemeshEdge(scratchpad, e_new); edges_merged[i] = nullptr; } else { // otherwise, let's go ahead and finish hooking up this edge for(uint k=0; k<2; k++) e_new->verts[k] = vptr_remap[e0->verts[k]]; } } for(uint i=0; i<edges_moving.size(); i++) { Eptr e_old = edges_moving[i]; Eptr e_new = edges_moved[i]; // note: should never have any dead/null triangles for(Tptr t : e_old->tris) { t = tptr_remap[t]; ENSURE(t); e_new->tris.push_back(t); } for(uint k=0; k<2; k++) e_new->verts[k] = vptr_remap[e_old->verts[k]]; } // Finally, the vertex { // Should do this directly, not via the remap translation. // Working via re-maps will lead to duplicates of merged geometry. // However, we can exploit the fact that this vertex is unique, // and that we already have lists of all the incident geometry for(Tptr t : tris_merged) if(t) v_merged->tris.push_back(t); for(Tptr t : tris_moved) // cannot be dead v_merged->tris.push_back(t); if(v_merged->tris.size() == 0) { scratchpad.cache.freeVert(v_merged); v_merged = nullptr; } else { for(Eptr e : edges_merged) if(e) v_merged->edges.push_back(e); for(Eptr e : edges_moved) // cannot be dead v_merged->edges.push_back(e); // it's impossible to have triangles incident w/o edges too. ENSURE(v_merged->edges.size() > 0); } } // OK, here we get to finally compute data for all the new geometry // Once we've done that, we can also check to see whether we actually // want to commit this operation or not. // merge vertices' data if(v_merged) { // REMEMBER: vertex could be deleted by now VertData &data_new = verts[v_merged->ref]; const VertData &data0 = verts[v0->ref]; const VertData &data1 = verts[v1->ref]; data_new.merge(data0, data1); } // merge triangles' data for(uint i=0; i<tri_wedges.size(); i++) { Tptr t_new = tris_merged[i]; // REMEMBER: these triangles could be deleted if(!t_new) continue; Tptr t0 = tri_wedges[i].t0; Tptr t1 = tri_wedges[i].t1; merge_tris(t_new->ref, t0->ref, t1->ref); } // update moved triangles' data for(uint i=0; i<tris_moving.size(); i++) { // NOTE: moved triangles cannot be deleted Tptr t_new = tris_moved[i]; Tri &tri_new = tris[t_new->ref]; Tptr t_old = tris_moving[i]; Tri &tri_old = tris[t_old->ref]; move_tri(tri_new, tri_old); } // TODO: COMMIT OPTION will be left as a stub for now! if(false) { // DESTROY THE GEOMETRY WE JUST CREATED // AND RETURN } // Find and Store all of the existing, unchanged edges // that border this operation. We will need these references // when we account for changes to edge operation priorities later ShortVec<Eptr, 16> borderEdges; for(const TriWedge &e_wedge : tri_wedges) { Tptr t0 = e_wedge.t0; for(uint k=0; k<3; k++) { if(t0->verts[k] == v0) { borderEdges.push_back(t0->edges[k]); break; } } } for(Tptr t : tris_moved) { for(uint k=0; k<3; k++) { if(t->verts[k] == v_merged) { borderEdges.push_back(t->edges[k]); break; } } } /* Now that we've got the go ahead, let's hook in the new geometry to * the existing geometry! * We can do this in the following order: * - take all the new edges, and add them to their existing endpoint's * edge list. (all new edges must have exactly one such endpoint) * - take all the new triangles, and add them to their two existing * endpoints' and one existing edge's triangle lists. */ // first, consolidate arrays ShortVec<Eptr, 12> new_edges; ShortVec<Tptr, 16> new_tris; { for(Eptr e : edges_merged) if(e) new_edges.push_back(e); for(Eptr e : edges_moved) // cannot be deleted new_edges.push_back(e); for(Tptr t : tris_merged) if(t) new_tris.push_back(t); for(Tptr t : tris_moved) // cannot be deleted new_tris.push_back(t); } // hook up edges to existing geometry for(Eptr edge : new_edges) { Vptr ev0 = edge->verts[0]; Vptr ev1 = edge->verts[1]; Vptr v_old = (ev0 != v_merged)? ev0 : ev1; v_old->edges.push_back(edge); } // hook up triangles to existing geometry for(Tptr tri : new_tris) { for(uint k=0; k<3; k++) { if(tri->verts[k] != v_merged) continue; Eptr e = tri->edges[k]; Vptr tv0 = e->verts[0]; Vptr tv1 = e->verts[1]; e->tris.push_back(tri); tv0->tris.push_back(tri); tv1->tris.push_back(tri); break; } } /* Now, let's kill all the old geometry. We can use the checklist * we built at the begining: * - e_collapse: the collapsing edge * - tris_collapse: the collapsing triangles * - v0, v1: the merging vertices * - edge_wedges: the merging edges * - tri_wedges: the merging (or disappearing) triangles * - edges_moving: the persisting, but moving edges * - tris_moving: the persisting, but moving triangles * * We need to be careful to free this geometry in top down order; * starting with the triangles and moving towards the vertices. * If we furthermore guarantee that any singular edges or vertices * created by a triangle deletion are also deleted, then we can * focus all of our attention on just deleting triangles */ ShortVec<Tptr, 16> dead_tris; ShortVec<Eptr, 16> dead_edges; ShortVec<Vptr, 2> dead_verts; // assemble the list of triangles to kill for(Tptr t : tris_collapse) dead_tris.push_back(t); for(const auto &t_wedge : tri_wedges) { dead_tris.push_back(t_wedge.t0); dead_tris.push_back(t_wedge.t1); } for(Tptr t : tris_moving) dead_tris.push_back(t); // process the list of triangles for(Tptr tri : dead_tris) { // Let's unhook this triangle from its faces first for(uint k=0; k<3; k++) { Vptr v = tri->verts[k]; remove(v->tris, tri); if(v->tris.size() == 0) dead_verts.push_back(v); Eptr e = tri->edges[k]; remove(e->tris, tri); if(e->tris.size() == 0) dead_edges.push_back(e); } // now that we're disconnected, go ahead and jettison the triangle scratchpad.cache.freeTri(tri); } // now, we can process the list of edges for(Eptr edge : dead_edges) { // Let's unhook this edge from its vertices for(uint k=0; k<2; k++) { Vptr v = edge->verts[k]; remove(v->edges, edge); // the triangle removal was enough to // determine which vertices should die. // re-adding them here would lead to duplicates } // and then jetisson the edge deallocateRemeshEdge(scratchpad, edge); // If this edge is in the border edge list, // then we need to remove it right away! maybe_erase(borderEdges, edge); } // Finally, polish off by getting rid of any vertices that talked too much for(Vptr vert : dead_verts) { scratchpad.cache.freeVert(vert); if(vert == v_merged) v_merged = nullptr; } // We pause a moment here to update the manifoldness of any // vertices for which it might have changed // ONLY do if the merged vertex is still alive... if(v_merged) { verts[v_merged->ref].manifold = true; for(Eptr e : v_merged->edges) { if(e->tris.size() != 2) verts[v_merged->ref].manifold = false; // process neighboring point Vptr v = e->verts[0]; if(v == v_merged) v = e->verts[1]; verts[v->ref].manifold = true; for(Eptr ee : v->edges) { if(ee->tris.size() != 2) { verts[v->ref].manifold = false; break; } } } } // Before we're completely done, we will go through and // adjust priorities for edges which might have been effected by this op. // Only explicitly dequeue pre-existing edges we did not delete! for(Eptr e : edges_merged) { if(e) { // might be deleted scoreAndEnqueue(scratchpad.queue, e); }} for(Eptr e : edges_moved) { // def. not deleted scoreAndEnqueue(scratchpad.queue, e); } for(Eptr e : borderEdges) { // border edges could have been deleted... dequeue(scratchpad.queue, e); scoreAndEnqueue(scratchpad.queue, e); } // that should more or less complete an edge collapse }