// +------------------------------------------------------------------------- // | mesh.bool.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 template class Mesh::BoolProblem { public: BoolProblem(Mesh *owner) : mesh(owner) {} virtual ~BoolProblem() {} // do things bool doSetup(Mesh &rhs, const double EPSILON); // perturbation epsilon // choose what to remove enum TriCode { KEEP_TRI, DELETE_TRI, FLIP_TRI }; void doDeleteAndFlip( std::function classify ); private: // methods struct BoolEdata { bool is_isct; }; inline byte& boolData(uint tri_id) { return mesh->tris[tri_id].data.bool_alg_data; } void populateECache() { ecache = mesh->createEGraphCache(); // label some of the edges as intersection edges and others as not ecache.for_each([&](uint i, uint j, EGraphEntry &entry) { entry.data.is_isct = false; byte operand = boolData(entry.tids[0]); for(uint k=1; k &tids)> action ) { ecache.for_each([&](uint i, uint j, EGraphEntry &entry) { if(entry.data.is_isct) { ShortVec tid0s; ShortVec tid1s; for(uint tid : entry.tids) { if(boolData(tid) & 1) tid1s.push_back(tid); else tid0s.push_back(tid); } action(i,j, true, tid1s); action(i,j, true, tid0s); } else { action(i,j, false, entry.tids); } }); } void prepInsideOutsideTests() { } bool isInside(uint tid, byte operand) { // find the point to trace outward from... Vec3d p(0,0,0); p += mesh->verts[mesh->tris[tid].a].pos; p += mesh->verts[mesh->tris[tid].b].pos; p += mesh->verts[mesh->tris[tid].c].pos; p /= 3.0; // ok, we've got the point, now let's pick a direction Ray3d r; r.p = p; r.r = Vec3d(drand(0.5,1.5), drand(0.5,1.5), drand(0.5, 1.5)); int winding = 0; // pass all triangles over ray for(Tri &tri : mesh->tris) { // ignore triangles from the same operand surface if((tri.data.bool_alg_data & 1) == operand) continue; double flip = 1.0; uint a = tri.a; uint b = tri.b; uint c = tri.c; Vec3d va = mesh->verts[a].pos; Vec3d vb = mesh->verts[b].pos; Vec3d vc = mesh->verts[c].pos; // normalize vertex order (to prevent leaks) if(a > b) { std::swap(a, b); std::swap(va, vb); flip = -flip; } if(b > c) { std::swap(b, c); std::swap(vb, vc); flip = -flip; } if(a > b) { std::swap(a, b); std::swap(va, vb); flip = -flip; } double t; Vec3d bary; if(isct_ray_triangle(r, va, vb, vc, &t, &bary)) { Vec3d normal = flip * cross(vb - va, vc - va); if(dot(normal, r.r) > 0.0) { // UNSAFE winding++; } else { winding--; } } } // now, we've got a winding number to work with... return winding > 0; } private: // data Mesh *mesh; EGraphCache ecache; }; static inline double triArea( Vec3d a, Vec3d b, Vec3d c ) { return len(cross(b-a, c-a)); } template bool Mesh::BoolProblem::doSetup( Mesh &rhs, const double EPSILON ) { // Label surfaces... mesh->for_tris([](TriData &tri, VertData&, VertData&, VertData&) { tri.bool_alg_data = 0; }); rhs.for_tris([](TriData &tri, VertData&, VertData&, VertData&) { tri.bool_alg_data = 1; }); mesh->disjointUnion(rhs); if(!(mesh->resolveIntersections(EPSILON))) return false; populateECache(); // form connected components; // we get one component for each connected component in one // of the two input meshes. // These components are not necessarily uniformly inside or outside // of the other operand mesh. UnionFind uf(mesh->tris.size()); for_ecache([&](uint, uint, bool, const ShortVec &tids) { uint tid0 = tids[0]; for(uint k=1; k uq_ids(mesh->tris.size(), uint(-1)); std::vector< std::vector > components; for(uint i=0; itris.size(); i++) { uint ufid = uf.find(i); if(uq_ids[ufid] == uint(-1)) { // unassigned uint N = components.size(); components.push_back(std::vector()); uq_ids[ufid] = uq_ids[i] = N; components[N].push_back(i); } else { // assigned already uq_ids[i] = uq_ids[ufid]; // propagate assignment components[uq_ids[i]].push_back(i); } } std::vector visited(mesh->tris.size(), false); // find the "best" triangle in each component, // and ray cast to determine inside-ness vs. outside-ness for(auto &comp : components) { // find max according to score uint best_tid = comp[0]; double best_area = 0.0; // SEARCH for(uint tid : comp) { Vec3d va = mesh->verts[mesh->tris[tid].a].pos; Vec3d vb = mesh->verts[mesh->tris[tid].b].pos; Vec3d vc = mesh->verts[mesh->tris[tid].c].pos; double area = triArea(va, vb, vc); if(area > best_area) { best_area = area; best_tid = tid; } } byte operand = boolData(best_tid); bool inside = isInside(best_tid, operand); // NOW PROPAGATE classification throughout the component. // do a breadth first propagation std::queue work; // begin by tagging the first triangle boolData(best_tid) |= (inside)? 2 : 0; visited[best_tid] = true; work.push(best_tid); while(!work.empty()) { uint curr_tid = work.front(); work.pop(); for(uint k=0; k<3; k++) { uint a = mesh->tris[curr_tid].v[k]; uint b = mesh->tris[curr_tid].v[(k+1)%3]; auto &entry = ecache(a,b); byte inside_sig = boolData(curr_tid) & 2; if(entry.data.is_isct) inside_sig ^= 2; for(uint tid : entry.tids) { if(visited[tid]) continue; if((boolData(tid)&1) != operand) continue; boolData(tid) |= inside_sig; visited[tid] = true; work.push(tid); } } } } return true; } template void Mesh::BoolProblem::doDeleteAndFlip( std::function classify ) { TopoCache topocache(mesh); std::vector toDelete; topocache.tris.for_each([&](Tptr tptr) { TriCode code = classify(boolData(tptr->ref)); switch(code) { case DELETE_TRI: toDelete.push_back(tptr); break; case FLIP_TRI: topocache.flipTri(tptr); break; case KEEP_TRI: default: break; } }); for(Tptr tptr : toDelete) { topocache.deleteTri(tptr); } topocache.commit(); } template bool Mesh::boolUnion(Mesh &rhs, const double EPSILON) // perturbation epsilon { BoolProblem bprob(this); if(!bprob.doSetup(rhs, EPSILON)) return false; bprob.doDeleteAndFlip([](byte data) -> typename BoolProblem::TriCode { if((data & 2) == 2) // part of op 0/1 INSIDE op 1/0 return BoolProblem::DELETE_TRI; else // part of op 0/1 OUTSIDE op 1/0 return BoolProblem::KEEP_TRI; }); return true; } template bool Mesh::boolDiff(Mesh &rhs, const double EPSILON) { BoolProblem bprob(this); if(!bprob.doSetup(rhs, EPSILON)) return false; bprob.doDeleteAndFlip([](byte data) -> typename BoolProblem::TriCode { if(data == 2 || // part of op 0 INSIDE op 1 data == 1) // part of op 1 OUTSIDE op 0 return BoolProblem::DELETE_TRI; else if(data == 3) // part of op 1 INSIDE op 1 return BoolProblem::FLIP_TRI; else // part of op 0 OUTSIDE op 1 return BoolProblem::KEEP_TRI; }); return true; } template bool Mesh::boolIsct(Mesh &rhs, const double EPSILON) // perturbation epsilon { BoolProblem bprob(this); if(!bprob.doSetup(rhs, EPSILON)) return false; bprob.doDeleteAndFlip([](byte data) -> typename BoolProblem::TriCode { if((data & 2) == 0) // part of op 0/1 OUTSIDE op 1/0 return BoolProblem::DELETE_TRI; else // part of op 0/1 INSIDE op 1/0 return BoolProblem::KEEP_TRI; }); return true; } template bool Mesh::boolXor(Mesh &rhs, const double EPSILON) // perturbation epsilon { BoolProblem bprob(this); if(!bprob.doSetup(rhs, EPSILON)) return false; bprob.doDeleteAndFlip([](byte data) -> typename BoolProblem::TriCode { if((data & 2) == 0) // part of op 0/1 OUTSIDE op 1/0 return BoolProblem::KEEP_TRI; else // part of op 0/1 INSIDE op 1/0 return BoolProblem::FLIP_TRI; }); return true; }