// +------------------------------------------------------------------------- // | mesh.remesh.tpp // | // | Author: Gilbert Bernstein // +------------------------------------------------------------------------- // | COPYRIGHT: // | Copyright Gilbert Bernstein 2013 // | See the included COPYRIGHT file for further details. // | // | This file is part of the Cork library. // | // | Cork is free software: you can redistribute it and/or modify // | it under the terms of the GNU Lesser General Public License as // | published by the Free Software Foundation, either version 3 of // | the License, or (at your option) any later version. // | // | Cork is distributed in the hope that it will be useful, // | but WITHOUT ANY WARRANTY; without even the implied warranty of // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // | GNU Lesser General Public License for more details. // | // | You should have received a copy // | of the GNU Lesser General Public License // | along with Cork. If not, see . // +------------------------------------------------------------------------- #pragma once #include #include #include "../util/memPool.h" //#include "files.h" #include "mesh.topoCache.tpp" enum EdgeRemeshOperation { EDGE_SPLIT, EDGE_COLLAPSE, EDGE_NOTHING }; struct RemeshEdgeAuxiliary { double score; // priority for queue EdgeRemeshOperation op; // operation to perform }; template struct Mesh::RemeshScratchpad { TopoCache cache; MemPool edge_data; std::set< std::pair > queue; RemeshScratchpad(Mesh *mesh) : cache(mesh) {} }; template void Mesh::remesh() { //std::cout << "remesh initial tri count: " << tris.size() << std::endl; if(verts.size() == 0) return; // pathology guard /*{ // dump out mesh before performing re-mesh Files::FileMesh filemesh; filemesh = transduce (raw(), [](Files::FileVertex &out, const VertData &in) { out.pos = in.pos; }, [](Files::FileTriangle &out, const TriData &in) { out.a = in.a; out.b = in.b; out.c = in.c; }); if(Files::writeTriMesh("remesh_in.off", &filemesh) > 0) { CORK_ERROR("Unable to write out remesh input to remesh_in.off"); } }*/ // create a scratchpad and set it up RemeshScratchpad scratchpad(this); // compute which vertices are boundary or not for(VertData &v : verts) { v.manifold = true; } scratchpad.cache.edges.for_each([this, &scratchpad](Eptr edge) { if(edge->tris.size() != 2) { verts[edge->verts[0]->ref].manifold = false; verts[edge->verts[1]->ref].manifold = false; } }); // Then, we set up the priority queue // (allocating auxiliary data as we go) scratchpad.cache.edges.for_each([this, &scratchpad](Eptr edge) { edge->data = scratchpad.edge_data.alloc(); scoreAndEnqueue(scratchpad.queue, edge); }); int cutoff = 10000; // now let's go into a loop pulling work off of the queue while(scratchpad.queue.size() > 0) { // pop the end of the queue auto it_top = scratchpad.queue.end(); it_top--; //double top_score = it_top->first; Eptr top_edge = it_top->second; scratchpad.queue.erase(it_top); // Which operation should be performed to this edge? EdgeRemeshOperation op = reinterpret_cast(top_edge->data)->op; if(op == EDGE_SPLIT) { //std::cout << "edge split" << std::endl; edgeSplit(scratchpad, top_edge); } else if (op == EDGE_COLLAPSE) { //std::cout << "edge collapse" << std::endl; edgeCollapse(scratchpad, top_edge, true); } // else do nothing (should have score <= 0.0 then...) cutoff--; if(cutoff < 0) break; } //std::cout << " cache.tris is " // << scratchpad.cache.tris.size() << std::endl; //std::cout << "prefinalize tri count: " << tris.size() << std::endl; // finally, we commit all of the results of this remeshing operation // causing the data storage to defrag and clean out dead items scratchpad.cache.commit(); //std::cout << "remesh final tri count: " << tris.size() << std::endl; } template void Mesh::scoreAndEnqueue( std::set< std::pair > &queue, Eptr edge ) { double score = computeEdgeScore(edge); if(score > 0.0) // only enqueue edges with actual work to do queue.insert(std::make_pair(score, edge)); } template void Mesh::dequeue( std::set< std::pair > &queue, Eptr edge ) { double score = reinterpret_cast(edge->data)->score; auto it = queue.find(std::make_pair(score, edge)); if(it != queue.end()) queue.erase(it); } template inline void Mesh::edgeNeighborhood( Eptr edge, std::function once, std::function each_tri ) { Vptr v0 = edge->verts[0]; Vptr v1 = edge->verts[1]; VertData &data0 = verts[v0->ref]; VertData &data1 = verts[v1->ref]; once(data0, data1); for(Tptr tri : edge->tris) { TriData &tdata = tris[tri->ref].data; for(uint i=0; i<3; i++) { if(tri->edges[i] == edge) { Vptr vopp = tri->verts[i]; VertData &dataopp = verts[vopp->ref]; each_tri(data0, data1, dataopp, tdata); } } } } template double Mesh::computeEdgeScore(Eptr edge) { double edge_length; double min_angle = 360.0; // clearly overkill double max_angle = 0.0; edgeNeighborhood(edge, [&edge_length] (VertData &v0, VertData &v1) { // once edge_length = len(v1.pos - v0.pos); }, [&min_angle, &max_angle] (VertData &v0, VertData &v1, VertData &vopp, TriData &) { // per tri Vec3d e0 = v0.pos - vopp.pos; Vec3d e1 = v1.pos - vopp.pos; double cos_angle = dot(e0, e1) / (len(e0)*len(e1)); double angle = rad2deg(acos(cos_angle)); if(angle < min_angle) min_angle = angle; if(angle > max_angle) max_angle = angle; // CURRENTLY OMITTING DIHEDRAL ANGLE CRITERION }); RemeshEdgeAuxiliary *edge_aux = reinterpret_cast(edge->data); // extract violation quantities: // if any of these are positive, // then an operation should be performed to this edge double excess_length = edge_length - remesh_options.maxEdgeLength; double deficient_length = remesh_options.minEdgeLength - edge_length; double excess_angle = max_angle - remesh_options.maxAngle; double deficient_angle = remesh_options.minAngle - max_angle; if(excess_length <= 0.0 && deficient_length <= 0.0 && excess_angle <= 0.0 && deficient_angle <= 0.0) { // no op on edge edge_aux->score = -1.0; edge_aux->op = EDGE_NOTHING; return edge_aux->score; } // rescale violation quantities: // make quantities more commensurate for use as priorities excess_length /= remesh_options.maxEdgeLength; deficient_length /= remesh_options.minEdgeLength; excess_angle /= (180.0 - remesh_options.maxAngle); deficient_angle /= remesh_options.minAngle; edge_aux->score = -1.0; if(excess_length > edge_aux->score) { edge_aux->score = excess_length; edge_aux->op = EDGE_SPLIT; } if(deficient_length > edge_aux->score) { edge_aux->score = deficient_length; edge_aux->op = EDGE_COLLAPSE; } if(excess_angle > edge_aux->score) { edge_aux->score = excess_angle; edge_aux->op = EDGE_SPLIT; } if(deficient_angle > edge_aux->score) { edge_aux->score = deficient_angle; edge_aux->op = EDGE_COLLAPSE; } return edge_aux->score; } // erase the first occurrence of a value from a short vector, // !!! KNOWING that it MUST BE PRESENT template inline void remove(ShortVec &vec, T val) { uint last_i = vec.size()-1; for(uint i=0; i inline void maybe_erase(ShortVec &vec, T val) { uint N = vec.size(); for(uint i=0; i inline void Mesh::populateTriFromTopoTri(Tptr tri) { Tri &tri_ref = tris[tri->ref]; for(uint k=0; k<3; k++) tri_ref.v[k] = tri->verts[k]->ref; } template Eptr Mesh::allocateRemeshEdge( RemeshScratchpad &scratchpad ) { Eptr e = scratchpad.cache.newEdge(); e->data = scratchpad.edge_data.alloc(); return e; } template void Mesh::deallocateRemeshEdge( RemeshScratchpad &scratchpad, Eptr e ) { dequeue(scratchpad.queue, e); scratchpad.cache.freeEdge(e); } template inline void Mesh::merge_tris( uint tid_result, uint tid0, uint tid1 ) { tris[tid_result].data.merge(tris[tid0].data, tris[tid1].data); } template inline void Mesh::split_tris( uint t0ref, uint t1ref, uint t_orig_ref ) { TriData::split(tris[t0ref].data, tris[t1ref].data, tris[t_orig_ref].data); } template inline void Mesh::move_tri(Tri &t_new, Tri &t_old) { t_new.data.move(t_old.data); } /* Strategy: 1. Construct new geometry in parallel to old geometry w/o tampering with old geometry in any way. 2. Fill out data for new geometry 3. Determine whether or not to commit or abort the operation 4. Connect new geometry to old geometry simultaneously to old geometry. 5. Delete the old geometry appropriately */ struct EdgeWedge { Eptr e0, e1; EdgeWedge() : e0(nullptr), e1(nullptr) {} bool full() const { return e0 && e1; } Eptr one() const { return (e0)? e0 : e1; } }; struct TriWedge { Tptr t0, t1; TriWedge() : t0(nullptr), t1(nullptr) {} bool full() const { return t0 && t1; } Tptr one() const { return (t0)? t0 : t1; } }; struct VptrRemap { Vptr v0, v1, v_merged; VptrRemap(Vptr v0_, Vptr v1_, Vptr v_merged_) : v0(v0_), v1(v1_), v_merged(v_merged_) {} Vptr operator[](Vptr key) { if(key == v0 || key == v1) return v_merged; else return key; } }; template struct PtrRemap { std::map remap; void set(T* key, T* val) { remap[key] = val; } T* operator[](T* key) { auto it = remap.find(key); if(it == remap.end()) return key; else return it->second; } }; template void Mesh::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 tris_collapse = e_collapse->tris; Vptr v0 = e_collapse->verts[0]; Vptr v1 = e_collapse->verts[1]; ShortVec edge_wedges; ShortVec edges_moving; ShortVec tri_wedges; ShortVec tris_moving; // BUILD all the edge wedges std::map 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 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 edges_merged(edge_wedges.size()); ShortVec edges_moved(edges_moving.size()); ShortVec tris_merged(tri_wedges.size()); ShortVec tris_moved(tris_moving.size()); VptrRemap vptr_remap(v0, v1, v_merged); PtrRemap eptr_remap; PtrRemap tptr_remap; // Create new edges and enter re-mappings eptr_remap.set(e_collapse, nullptr); // mark this edge as dying for(uint i=0; iverts[k] = vptr_remap[t0->verts[k]]; t_new->edges[k] = eptr_remap[t0->edges[k]]; } populateTriFromTopoTri(t_new); } } for(uint i=0; iverts[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; itris) { 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; itris) { 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; iref, t0->ref, t1->ref); } // update moved triangles' data for(uint i=0; iref]; 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 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 new_edges; ShortVec 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 dead_tris; ShortVec dead_edges; ShortVec 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 } /* Strategy: 1. Construct new geometry in parallel to old geometry w/o tampering with old geometry in any way. 2. Fill out data for new geometry 3. Determine whether or not to commit or abort the operation 4. Connect new geometry to old geometry simultaneously to old geometry. 5. Delete the old geometry appropriately */ template void Mesh::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 ts_orig = e_split->tris; Vptr v0 = e_split->verts[0]; Vptr v1 = e_split->verts[1]; ShortVec vs_opp(ts_orig.size()); for(uint i=0; iedges[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 t0s_new(ts_orig.size()); ShortVec t1s_new(ts_orig.size()); ShortVec es_mid(ts_orig.size()); for(uint i=0; iverts[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; itris.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; iedges.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; iref, 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 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; iedges.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); } }