#include "segment.h" #include "StarlabDrawArea.h" #include "../CustomDrawObjects.h" #include #include "Graph.h" using namespace std; using namespace SurfaceMesh; #define qRanged(min, v, max) ( qMax(min, qMin(v, max)) ) #define RADIANS(deg) ((deg)/180.0 * M_PI) #define DEGREES(rad) ((rad)/M_PI * 180.0) typedef SurfaceMesh::Model::Halfedge_around_face_circulator HalfedgeFaceIter; typedef SurfaceMesh::Model::Vertex_around_face_circulator VertFaceIter; typedef SurfaceMesh::Model::Face_around_vertex_circulator FaceVertIter; typedef SurfaceMesh::Model::Vertex_around_vertex_circulator VertIter; uint qHash( const SurfaceMesh::Model::Face &key ){return qHash(key.idx()); } uint qHash( const SurfaceMesh::Model::Vertex &key ){return qHash(key.idx()); } void segment::initParameters(RichParameterSet* pars){ pars->addParam( new RichFloat("angle_threshold", 10.0f, "Angle threshold")); pars->addParam( new RichFloat("min_radius_scale", 0.1f, "Minimum component scale")); pars->addParam( new RichInt("k_nighbours", 3, "K-levels")); pars->addParam( new RichBool("visualize", true, "Visualize regions")); pars->addParam( new RichBool("extract", false, "Extract regions")); // Create helper object to access points and edge lengths SurfaceMeshHelper h(mesh()); points = h.getVector3VertexProperty(SurfaceMesh::VPOINT); farea = h.computeFaceAreas(); elen = h.computeEdgeLengths(); } void segment::initMesh() { resetClassfication(); resetVisitFlag(); resetVertexClass(); faceTarget = mesh()->face_property("f:target", ""); } void segment::resetClassfication() { mesh()->remove_face_property(fclass); fclass = mesh()->add_face_property("f:class", SHEET); } void segment::resetVisitFlag() { mesh()->remove_face_property(fvisited); fvisited = mesh()->add_face_property("f:visited", false); } void segment::resetVertexClass() { mesh()->remove_vertex_property(vclass); vclass = mesh()->add_vertex_property("v:class", 0); } void segment::applyFilter(RichParameterSet* pars) { drawArea()->deleteAllRenderObjects(); // Get user specified parameters double theta = pars->getFloat("angle_threshold"); double minRadiusScale = pars->getFloat("min_radius_scale"); int k = pars->getInt("k_nighbours"); bool visualize = pars->getBool("visualize"); bool extract = pars->getBool("extract"); double minRadius = minRadiusScale * mesh()->bbox().diagonal().norm(); // Perform segmentation performCurveSheetSegmentation(theta, minRadius, k, extract, visualize); } void segment::performCurveSheetSegmentation(double theta, double minRadius, int k, bool isExtract, bool isVisualize) { // Clear debug artifacts drawArea()->deleteAllRenderObjects(); PolygonSoup * poly_soup = new PolygonSoup; LineSegments * lines = new LineSegments; PointSoup * point_soup = new PointSoup; initMesh(); // 1) Check valid faces by minimum angle foreach(Face f, mesh()->faces()) if( minAngle(f) < RADIANS(theta) ) fclass[f] = CURVE; // 2) Assign vertex classification from faces classifyVertsFromFaces(); // 3) Blur classification foreach(Vertex v, mesh()->vertices()){ QSet k_rings; collectRing(v, k_rings, k); // Get median QVector nighbours; foreach(Vertex v, k_rings) nighbours.push_back(vclass[v]); vclass[v] = median(nighbours); if(isVisualize) point_soup->addPoint(points[v], qtJetColorMap(vclass[v])); } // 4) Cutoff foreach(Vertex v, mesh()->vertices()){ if(vclass[v] > 0.5){ Surface_mesh::Face_around_vertex_circulator adjF(mesh(), v), fend = adjF; do { fclass[adjF] = SHEET; } while(++adjF != fend); } } // 5) Filter very small components if(minRadius > 0) { GrowRegions(); foreach(Region r, sheet_regions) { Eigen::AlignedBox3d region_bbox; foreach(Face f, r) { Eigen::Vector3d center_f = center(f); region_bbox = region_bbox.merged( Eigen::AlignedBox3d(center_f,center_f) ); } if(region_bbox.diagonal().norm() < minRadius) invertRegion(r); } } // 6) Find set of faces in all regions GrowRegions(); // 7) Split curve regions into parts int curve_count = (int)curve_regions.size(); for(int i = 0; i < curve_count; i++) splitCurveRegion( curve_regions[i] ); // 8) Save as separate meshes if(isExtract){ int cid = 0, sid = 0; document()->pushBusy(); // Curves for(int i = 0; i < (int) curve_regions.size(); i++) { if(!curve_regions[i].size()) continue; SurfaceMesh::Model * m = new SurfaceMesh::Model("", QString("%1_curve%2").arg(mesh()->name).arg(cid++)); setMeshFromRegion(curve_regions[i], m); m->updateBoundingBox(); document()->addModel(m); } // Sheets for(int i = 0; i < (int) sheet_regions.size(); i++) { if(!sheet_regions[i].size()) continue; SurfaceMesh::Model * m = new SurfaceMesh::Model("", QString("%1_sheet%2").arg(mesh()->name).arg(sid++)); setMeshFromRegion(sheet_regions[i], m); m->updateBoundingBox(); document()->addModel(m); } document()->popBusy(); } // Debug if(isVisualize){ double e = mesh()->bbox().diagonal().norm() * 0.05 * 1; Eigen::Vector3d shift(e,e,e); static std::vector< std::vector > rclr = randomColors(curve_regions.size()); // Draw curves for(int r = 0; r < (int)curve_regions.size(); r++){ foreach(Face f, curve_regions[r]){ QVector p; VertFaceIter vit = mesh()->vertices(f), vend = vit; do{ p.push_back( QVector3(points[vit] + shift) ); } while(++vit != vend); for(int i = 0; i < p.size(); i++) lines->addLine(p[i], p[(i+1) % p.size()], QColor::fromRgbF(rclr[r][0], rclr[r][1], rclr[r][2])); } } // Draw sheets foreach(Region region, sheet_regions){ foreach(Face f, region){ QVector p; VertFaceIter vit = mesh()->vertices(f), vend = vit; do{ p.push_back( QVector3(points[vit] - shift) ); } while(++vit != vend); poly_soup->addPoly(p); } } drawArea()->addRenderObject(poly_soup); drawArea()->addRenderObject(lines); drawArea()->addRenderObject(point_soup); } qDebug() << (QString("Total = %1 | Sheets found = %2 | Curves = %3") .arg(sheet_regions.size() + curve_regions.size()).arg(sheet_regions.size()).arg(curve_regions.size())); // Experiments: if(isExtract) doGraph(); } void segment::doGraph() { QMap id; Graph g; // Assign target IDs foreach(Face f, mesh()->faces()) { id[ faceTarget[f] ] ++; } // Add edges foreach(Edge e, mesh()->edges()) { Face f1 = mesh()->face(mesh()->halfedge(e,0)); Face f2 = mesh()->face(mesh()->halfedge(e,1)); QString target1 = faceTarget[f1]; QString target2 = faceTarget[f2]; if(target1 == target2) continue; g.AddEdge(id[target1], id[target2], 1); } std::set< int > nodes = g.GetNodes(); std::vector< Graph::Edge > edges = g.GetEdges(); QFile out(QString("%1_graph.gexf").arg(mesh()->name)); out.open(QIODevice::WriteOnly|QIODevice::Text); out.write(" "); out.write(""); // Start nodes foreach(int n, nodes) { out.write( qPrintable(QString("").arg(n).arg(id.key(n).split('_').last())) ); } out.write(""); // End nodes out.write(""); // Start edges int eid = 0; for(std::vector< Graph::Edge >::iterator e = edges.begin(); e != edges.end(); e++) { out.write( qPrintable(QString("").arg(eid++).arg(e->index).arg(e->target)) ); } out.write(""); // End edges out.write(""); } void segment::classifyVertsFromFaces() { foreach(Vertex v, mesh()->vertices()){ Surface_mesh::Face_around_vertex_circulator adjF(mesh(), v), fend = adjF; do { if(fclass[adjF] == SHEET) { vclass[v] = 1.0; break; } } while(++adjF != fend); } } void segment::GrowRegions() { // Clear regions curve_regions.clear(); sheet_regions.clear(); resetVisitFlag(); foreach(Face f, mesh()->faces()) { // Skip visited if(fvisited[f]) continue; int currClass = fclass[f]; HalfedgeFaceIter h(mesh(), f), eend = h; do{ // Check if class of adjacent face doesn't match current face if(fclass[ mesh()->face(mesh()->opposite_halfedge(h)) ] != currClass) { if(currClass == CURVE) curve_regions.push_back( growRegion(f) ); if(currClass == SHEET) sheet_regions.push_back( growRegion(f) ); break; } } while(++h != eend); } // For single class classification of entire mesh if( !curve_regions.size() ){ Face f(0); int currClass = fclass[f]; if(currClass == CURVE) curve_regions.push_back( growRegion(f) ); if(currClass == SHEET) sheet_regions.push_back( growRegion(f) ); } } Region segment::growRegion( Face seedFace ) { Region region; QStack toVisit; toVisit.push(seedFace); int currClass = fclass[seedFace]; // Recursive grow: while( !toVisit.isEmpty() ) { // Get first item, add to growing region Face f = toVisit.pop(); region.insert(f); // Check adjacent faces HalfedgeFaceIter h(mesh(), f), eend = h; do{ Face adj = mesh()->face(mesh()->opposite_halfedge(h)); // If adjacent is not visited and has same class, push to stack if( !fvisited[adj] && fclass[adj] == currClass ) toVisit.push(adj); } while(++h != eend); // Mark as visited fvisited[f] = true; } return region; } void segment::invertRegion( Region & r ) { int currClass = fclass[*r.begin()]; int toClass = (currClass == SHEET) ? CURVE : SHEET; foreach(Face f, r) fclass[f] = toClass; } void segment::setMeshFromRegion( Region & r, SurfaceMesh::Model * m) { // Get set of vertices QSet verts; QMap< Face, QVector > adjV; foreach(Face f, r){ VertFaceIter v(mesh(), f), vend = v; do{ verts.insert(v); adjV[f].push_back(v); } while (++v != vend); } // VERTICES: Map them to ordered numbers, and add to new mesh QMap vmap; QMap inv_vmap; QSetIterator vi(verts); while(vi.hasNext()){ Vertex v = vi.next(); Vertex newV = Vertex(vmap.size()); vmap[v] = newV; m->add_vertex(points[v]); inv_vmap[newV] = v; } // FACES QMap fmap; foreach(Face f, r){ if(mesh()->valence(f) == 3) { fmap[Face(fmap.size())] = f; m->add_triangle(vmap[adjV[f][0]], vmap[adjV[f][1]], vmap[adjV[f][2]]); faceTarget[f] = m->name; } } // Mapping to original surface SurfaceMesh::Model::Vertex_property surface_vmap = m->vertex_property("v:original", Vertex()); SurfaceMesh::Model::Face_property surface_fmap = m->face_property("f:original", Face()); foreach(Vertex v, m->vertices()) surface_vmap[v] = inv_vmap[v]; foreach(Face f, m->faces()) surface_fmap[f] = fmap[f]; } void segment::collectRing( Vertex v, QSet & set, int level ) { if(level > 0){ VertIter vi(mesh(), v), vend = vi; do{ collectRing(vi, set, level - 1); set.insert(vi); } while(++vi != vend); } else set.insert(v); } double segment::median( QVector vec ) { typedef vector::size_type vec_sz; vec_sz size = vec.size(); if (size == 0) return DBL_MAX; qSort(vec.begin(), vec.end()); vec_sz mid = size / 2; return size % 2 == 0 ? (vec[mid] + vec[mid-1]) / 2 : vec[mid]; } double segment::minAngle(Face f) { double minAngle(DBL_MAX); HalfedgeFaceIter h(mesh(), f), eend = h; do{ Vector3 a = points[mesh()->to_vertex(h)]; Vector3 b = points[mesh()->from_vertex(h)]; Vector3 c = points[mesh()->to_vertex(mesh()->next_halfedge(h))]; double d = dot((b-a).normalized(), (c-a).normalized()); double angle = acos(qRanged(-1.0, d, 1.0)); minAngle = qMin(angle, minAngle); } while(++h != eend); return minAngle; } Vector3 segment::center( Face f ) { HalfedgeFaceIter h(mesh(), f), eend = h; Vector3 a = points[mesh()->to_vertex(h)]; Vector3 b = points[mesh()->from_vertex(h)]; Vector3 c = points[mesh()->to_vertex(mesh()->next_halfedge(h))]; return (a + b + c) / 3.0; } #include "CurveskelHelper.h" #include "MyPriorityQueue.h" using namespace CurveskelTypes; void segment::splitCurveRegion(Region & r) { SurfaceMesh::Model m; setMeshFromRegion(r, &m); SurfaceMesh::Vector3VertexProperty m_points = m.vertex_property(SurfaceMesh::VPOINT); SurfaceMesh::Model::Face_property original_face = m.face_property("f:original", Face()); // Perform edge collapses until we get 1D curves: CurveskelModel skel(""); // 1) Transfer vertices foreach(SurfaceMesh::Vertex v, m.vertices()){ SurfaceMesh::Point p = m_points[v]; skel.add_vertex(CurveskelTypes::Point(p[0], p[1], p[2]) ); } // 2) Faces foreach(SurfaceMesh::Face f, m.faces()){ std::vector m_vertices; Surface_mesh::Vertex_around_face_circulator vit = m.vertices(f),vend=vit; do{ m_vertices.push_back(CurveskelTypes::Vertex( SurfaceMesh::Vertex(vit).idx() )); } while(++vit != vend); skel.add_face( m_vertices ); } CurveskelHelper h( &skel ); CurveskelTypes::ScalarEdgeProperty elen = h.computeEdgeLengths(); CurveskelModel::Vertex_property< std::set > vrecord = skel.vertex_property< std::set >("v:collapse-from", std::set()); // 3) Add to priority queue MyPriorityQueue queue( &skel ); foreach(CurveskelTypes::Edge edge, skel.edges()) queue.insert(edge, elen[edge]); // first add yourself to the set foreach(CurveskelTypes::Vertex v, skel.vertices()) vrecord[v].insert(v); /// 4) Collapse cycle while (!queue.empty()){ /// Retrieve shortest edge CurveskelModel::Edge e = queue.pop(); /// Make sure edge was not already dealt with by previous collapses if(!skel.has_faces(e) || skel.is_deleted(e) || !skel.is_valid(e)) continue; CurveskelModel::Vertex v1 = skel.vertex(e, 0); // 'v1' will be deleted CurveskelModel::Vertex v2 = skel.vertex(e, 1); /// Do collapse skel.collapse(e); /// record collapsed vertex vrecord[v2].insert(v1); // carry its records too vrecord[v2].insert(vrecord[v1].begin(), vrecord[v1].end()); /// Update length of edges incident to remaining vertex CurveskelModel::Edge_around_vertex eit (&skel, v2); while(!eit.end()) { CurveskelModel::Edge edge = eit; double newLength = skel.edge_length(edge); // If edge still in queue, update its position if(queue.has(edge)) queue.update(edge, newLength); ++eit; } } // Save mapping between the region's surface and underlying skeleton curve graph QMap< int, std::set > vmap; int skel_vidx = 0; foreach(CurveskelModel::Vertex v, skel.vertices()){ if(!skel.is_deleted(v)){ foreach(CurveskelModel::Vertex vj, vrecord[v]) vmap[ skel_vidx ].insert( vj.idx() ); skel_vidx++; } } skel.garbage_collection(); // Prepare variables for visiting branches and assigning IDs CurveskelTypes::BoolVertexProperty visited = skel.vertex_property("v:visited", false); CurveskelTypes::IntVertexProperty branchID = skel.vertex_property("v:branch", false); std::set junctions = skel.junctions(); // Mark junctions as visited foreach(CurveskelTypes::Vertex v, junctions) visited[v] = true; int branch_id = 0; // Go over separate individual branches and assign IDs foreach(CurveskelTypes::Vertex vi, skel.vertices()) { if(visited[vi]) continue; QStack< CurveskelTypes::Vertex > stack; stack.push(vi); while(!stack.empty()){ CurveskelTypes::Vertex cur = stack.pop(); foreach(CurveskelTypes::Vertex vj, skel.adjacent_set(cur)){ branchID[vj] = branch_id; if( !visited[vj] ){ stack.push(vj); visited[vj] = true; } } } branch_id++; } // Propagate branch ID to original structure RegionVector sub_regions; sub_regions.resize( branch_id ); SurfaceMesh::Model::Face_property face_assigned = m.face_property("f:assignedID", false); foreach(CurveskelModel::Vertex skel_v, skel.vertices()){ int curr_id = branchID[ skel_v ]; // For each surface vertex belonging to skeleton vertex 'skel_v' foreach(int vi, vmap[ skel_v.idx() ]) { // For each adjacent face of vi Surface_mesh::Face_around_vertex_circulator adjF( &m, Surface_mesh::Vertex(vi) ), fend = adjF; do { if(!face_assigned[ adjF ]) { sub_regions[ curr_id ].insert( original_face[adjF] ); face_assigned[ adjF ] = true; } } while(++adjF != fend); } } // Empty out the original region r.clear(); // Add its sub-regions foreach(Region r, sub_regions) curve_regions.push_back(r); // DEBUG /*static std::vector< std::vector > rc = randomColors(branch_id + 1); foreach(CurveskelTypes::Vertex v, skel.vertices()){ int i = branchID[v]; CurveskelTypes::Vector3 pnt = skel.get_vertex_property(CurveskelTypes::VPOINT)[v]; Eigen::Vector3d p (pnt.x(), pnt.y(), pnt.z()); QColor color = QColor::fromRgbF(rc[i][0],rc[i][1],rc[i][2]); drawArea()->drawPoint(p,10, color); }*/ // DEUBG: /*CurveskelModel * s = new CurveskelModel; foreach(CurveskelModel::Vertex v, skel.vertices()) s->add_vertex(skel.get_vertex_property(CurveskelTypes::VPOINT)[v]); foreach(CurveskelModel::Edge e, skel.edges()) s->add_edge(skel.vertex(e, 0), skel.vertex(e, 1)); foreach(CurveskelTypes::Vertex v, s->junctions()) { CurveskelTypes::Vector3 pnt = skel.get_vertex_property(CurveskelTypes::VPOINT)[v]; Eigen::Vector3d p (pnt.x(), pnt.y(), pnt.z()); drawArea()->drawPoint(p,10); } document()->addModel(s);*/ } Q_EXPORT_PLUGIN(segment)