#include <2geom/path.h> #include <2geom/svg-path-parser.h> #include <2geom/path-intersection.h> #include <2geom/basic-intersection.h> #include <2geom/pathvector.h> #include <2geom/exception.h> #include #include #include "sweeper.cpp" /* Topology Class: This class mainly consists in 3 vectors: vertices, edges, and areas. -edges: have start/end, left/right pointing to vertices or areas. -vertices: have a "boundary"= the sequence of edges sorted in CCW order. -areas: have one outer "boundary" + a vector of inner boundaries, which are sequence of edges. To build this data, the strategy is to let a line sweep the plane (from left to right, say) and consider the topology of what is on the left of the sweep line. Topology changes are called events, and we call an external "sweeper" to generate them for us. So we start with an empty data, and respond to events to always describe the topology of what is on the left of the sweep line. [more precisely, we start with one region that has empty boundary, and since the external sweeper knows how many edges we'll have at the end, so we create them from scratch, leaving their ends as "unknown"] Note: see the sweeper for more info about events; they are essentially generated when the sweep line crosses a vertex (which is in fact a box), but are in fact split into smaller events, one for each edge around the vertex... The code is using a lot of vectors: unsing pointers instead of vectors could speed things up (?), but vector indices are easier to debug than memory addresses.:P */ using namespace Geom; using namespace std; class Topology { public: // -!- convention: // In a boundary, reversed edges point away from the vertex or CW around the area. struct OrientedEdge{ unsigned edge; //edge index. bool reversed; //true if the intrinsic edge orientation points away (from vertex) or backward (along area boundary) OrientedEdge(unsigned edge_idx, bool o){ edge = edge_idx; reversed = o; } OrientedEdge(){ edge = NULL_IDX; reversed = false; } bool operator == ( OrientedEdge const &other) const { return (edge == other.edge && edge!=NULL_IDX && reversed == other.reversed); } }; class Boundary : public std::vector{ public: bool of_area;//true if this is the boundary of an area. Fix this with templates? Boundary(bool area_type): of_area(area_type){} }; class Vertex{ public: Boundary boundary; // list of edges in CCW order around the vertex Geom::Rect bounds; Vertex():boundary(false){} }; class Area {//an area is a connected comp of the complement of the graph. . public: Boundary boundary; // outermost boundary component, CCW oriented (i.e. area is on the left of the boundary). std::vector inner_boundaries;//same conventions, area on the left, so this gives the CW orientation for inner components. std::vector windings;//one winding number for each input path. Area(unsigned size): boundary(true), windings(size, 0){} }; class Edge { public: unsigned left, right;// the indices of the areas on the left and on the right this edge. unsigned start, end; // the indices of vertices at start and at end of this edge. Geom::Interval portion; unsigned path; unsigned curve; Edge(){ left = NULL_IDX; right =NULL_IDX; start = NULL_IDX; end = NULL_IDX; portion = Interval(); path = NULL_IDX; curve = NULL_IDX; } }; vector areas; vector edges; vector vertices; PathVector input_paths;//we don't need our own copy... cairo_t* cr; //debug only!! int steps_max; //---------- //---------------------------------------------------- //-- utils... //---------------------------------------------------- void printIdx(unsigned idx){ (idx == NULL_IDX)? std::printf("?") : std::printf("%u", idx); } void printVertex(unsigned i){ std::printf("vertex %u: ", i); printBoundary(vertices[i].boundary); std::printf("\n"); } void printEdge(unsigned i){ std::printf("edge %u: ", i); printIdx(edges[i].start); std::printf(" -> "); printIdx(edges[i].end); std::printf(" ^"); printIdx(edges[i].left); std::printf(" _"); printIdx(edges[i].right); std::printf("\n"); } void printArea(unsigned i){ std::printf("area %u: ", i); printBoundary(areas[i].boundary); for (auto & inner_boundarie : areas[i].inner_boundaries){ std::printf(", "); printBoundary(inner_boundarie); } std::printf("\n"); } void printOrientedEdge(OrientedEdge const &f){ ( f.reversed ) ? std::printf("-") : std::printf("+"); printIdx(f.edge); std::printf(" "); } void printBoundary(Boundary const &bndry){ (bndry.of_area) ? std::printf("[") : std::printf("<"); for (unsigned i=0; i"); } void print(){ std::cout<<"\nCrossing Data:\n"; for (unsigned i=0; i edgeAsSBasis(unsigned e){ //beurk! optimize me. D2 c = input_paths[edges[e].path][edges[e].curve].toSBasis(); return portion(c, edges[e].portion); } Path edgeToPath(Topology::OrientedEdge o_edge){ Topology::Edge e = edges[o_edge.edge]; D2 p = input_paths[e.path][e.curve].toSBasis(); Interval dom = e.portion; p = portion(p, dom); if ( o_edge.reversed ){ p = compose( p, Linear(1.,0.) ); } Path ret; ret.setStitching(true); Point center; unsigned c_idx = source(o_edge, true); if ( c_idx == NULL_IDX ){ ret.append(p); }else{ center = vertices[c_idx].bounds.midpoint(); ret = Path(center); ret.append(p); } c_idx = target(o_edge, true); if ( c_idx == NULL_IDX ){ return ret; }else{ center = vertices[c_idx].bounds.midpoint(); if ( center != p.at1() ) ret.appendNew(center); return ret; } } Path boundaryToPath(Topology::Boundary b){ Point pt; Path bndary; bndary.setStitching(true); if (b.size()==0){ return Path(); } Topology::OrientedEdge o_edge = b.front(); unsigned first_v = source(o_edge, true); if ( first_v != NULL_IDX ){ pt = vertices[first_v].bounds.midpoint(); bndary = Path(pt); } for (unsigned i = 0; i < b.size(); i++){ bndary.append( edgeToPath(b[i])); } bndary.close(); return bndary; } //---------------------------------------------------- //-- Boundary Navigation/Modification //---------------------------------------------------- //TODO: this should be an OrientedEdge method, be requires access to the edges. unsigned source(OrientedEdge const &f, bool as_area_bndry){ unsigned prev; if (f.reversed ) prev = (as_area_bndry)? edges[f.edge].end : edges[f.edge].right; else prev = (as_area_bndry)? edges[f.edge].start : edges[f.edge].left; return prev; } unsigned target(OrientedEdge const &f, bool as_area_bndry){ unsigned prev; if (f.reversed ) prev = (as_area_bndry)? edges[f.edge].start : edges[f.edge].left; else prev = (as_area_bndry)? edges[f.edge].end : edges[f.edge].right; return prev; } //TODO: this should be a Boundary method, but access to the full data is required... bool prolongate( Boundary &bndry, OrientedEdge const &f){ if ( bndry.empty() ){ bndry.push_back(f); return true; } unsigned src = source(f, bndry.of_area); if ( src == target( bndry.back(), bndry.of_area ) && src != NULL_IDX ){ bndry.push_back(f); return true; } unsigned tgt = target( f, bndry.of_area ); if ( tgt == source( bndry.front(), bndry.of_area ) && tgt != NULL_IDX ){ bndry.insert( bndry.begin(), f); return true; } return false; } bool prolongate(Boundary &a, Boundary &b){ if (a.size()==0 || b.size()==0 || (a.of_area != b.of_area) ) return false; unsigned src; src = source(a.front(), a.of_area); // unsigned af = a.front().edge, as=source(a.front(), a.of_area), ab=a.back().edge, at=target(a.back(), a.of_area); // unsigned bf = b.front().edge, bs=source(b.front(), b.of_area), bb=b.back().edge, bt=target(b.back(), b.of_area); // std::printf("a=%u(%u)...(%u)%u\n", as, af,ab,at); // std::printf("b=%u(%u)...(%u)%u\n", bs, bf,bb,bt); // std::printf("%u == %u?\n", src, target( b.back(), b.of_area )); if ( src == target( b.back(), b.of_area ) && src != NULL_IDX ){ a.insert( a.begin(), b.begin(), b.end() ); // std::printf("boundaries fused!!\n"); return true; } src = source(b.front(), b.of_area); if ( src == target( a.back(), a.of_area ) && src != NULL_IDX ){ a.insert( a.end(), b.begin(), b.end() ); return true; } return false; } //TODO: this should be a Boundary or Area method, but requires access to the full data... //TODO: systematically check for connected boundaries before returning? void addAreaBoundaryPiece(unsigned a, OrientedEdge const &f){ if ( areas[a].boundary.size()>0 && prolongate( areas[a].boundary, f ) ) return; for (auto & inner_boundarie : areas[a].inner_boundaries){ // printBoundary(areas[a].inner_boundaries[i]); // printf(" matches "); // printOrientedEdge(f); // printf("?"); if ( inner_boundarie.size()>0 && prolongate( inner_boundarie, f) ) return; // printf("no. (%u vs %u)", target(areas[a].inner_boundaries[i].back(), true), source(f, true)); } Boundary new_comp(true); new_comp.push_back(f); areas[a].inner_boundaries.push_back(new_comp); } bool fuseConnectedBoundaries(unsigned a){ // std::printf(" fuseConnectedBoundaries %u\n",a); bool ret = false; if ( areas[a].boundary.size()>0 ){ for ( unsigned i=0; ib) swap(a,b);//this is important to keep track of the outermost component!! areas[a].inner_boundaries.push_back(areas[b].boundary); for (unsigned i=0; iexpandBy(50); bndary = Path(bbox->corner(0)); bndary.appendNew(bbox->corner(1)); bndary.appendNew(bbox->corner(2)); bndary.appendNew(bbox->corner(3)); bndary.appendNew(bbox->corner(0)); }else{ bndary = boundaryToPath(areas[a].boundary); } for (auto & inner_boundarie : areas[a].inner_boundaries){ bndary.append( boundaryToPath(inner_boundarie)); bndary.appendNew( bndary.initialPoint() ); } bndary.close(); return bndary; } void drawAreas( cairo_t *cr, bool fill=true ){ //don't draw the first one... for (unsigned a=0; a=areas.size()) return; Path bndary = glued_areaToPath(a); cairo_path(cr, bndary); if (fill){ cairo_fill(cr); }else{ cairo_stroke(cr); } } void highlightRay( cairo_t *cr, unsigned b, unsigned r ){ if (b>=vertices.size()) return; if (r>=vertices[b].boundary.size()) return; Rect box = vertices[b].bounds; //box.expandBy(2); cairo_rectangle(cr, box); cairo_set_source_rgba (cr, 1., 0., 0, 1.0); cairo_set_line_width (cr, 1); cairo_fill(cr); unsigned eidx = vertices[b].boundary[r].edge; Topology::Edge e = edges[eidx]; D2 p = input_paths[e.path][e.curve].toSBasis(); Interval dom = e.portion; if (vertices[b].boundary[r].reversed){ //dom[0] += e.portion.extent()*2./3; cairo_set_source_rgba (cr, 0., 1., 0., 1.0); }else{ //dom[1] -= e.portion.extent()*2./3; cairo_set_source_rgba (cr, 0., 0., 1., 1.0); } p = portion(p, dom); cairo_d2_sb(cr, p); cairo_set_source_rgba (cr, 1., 0., 0, 1.0); cairo_set_line_width (cr, 5); cairo_stroke(cr); } void drawEdge( cairo_t *cr, unsigned eidx ){ if (eidx>=edges.size()) return; Topology::Edge e = edges[eidx]; D2 p = input_paths[e.path][e.curve].toSBasis(); Interval dom = e.portion; p = portion(p, dom); cairo_d2_sb(cr, p); if (e.start == NULL_IDX || e.end == NULL_IDX ) cairo_set_source_rgba (cr, 0., 1., 0, 1.0); else cairo_set_source_rgba (cr, 0., 0., 0, 1.0); cairo_set_line_width (cr, 1); cairo_stroke(cr); } void drawEdges( cairo_t *cr){ for (unsigned e=0; e=vertices.size()) return; Rect box = vertices[b].bounds; //box.expandBy(5); cairo_rectangle(cr, box); cairo_set_source_rgba (cr, 1., 0., 0, .5); cairo_set_line_width (cr, 1); cairo_stroke(cr); cairo_rectangle(cr, box); cairo_set_source_rgba (cr, 1., 0., 0, .2); cairo_fill(cr); } void drawBoxes( cairo_t *cr){ for (unsigned b=0; b( sweeper.tiles_data.size(), Edge() ); for (unsigned i=0; i= 0 && step > steps_max ){ break; }else{ step++; } //--------- if (event.empty()){ //std::printf(" empty event received\n"); break; } //std::printf(" non empty event received:"); //sweeper.printEvent(event); //is this a new event or the continuation of an old one? unsigned v; Rect r = sweeper.context.pending_vertex; if (vertices.empty() || !r.intersects( vertices.back().bounds ) ){ v = vertices.size(); vertices.push_back(Vertex()); vertices[v].bounds = r; // std::printf(" new intersection created (%u).\n",v); }else{ v = vertices.size()-1; // std::printf(" continue last intersection (%u).\n",v); } //--Closing an edge:------------- if( !event.opening ){ unsigned e = event.tile, a, b; // std::printf(" closing edge %u\n", e); bool reversed = sweeper.tiles_data[e].reversed;//Warning: true means v==e.start if (reversed){ edges[e].start = v; a = edges[e].right; b = edges[e].left; }else{ edges[e].end = v; a = edges[e].left; b = edges[e].right; } OrientedEdge vert_edge(e, reversed); if (vertices[v].boundary.size()>0){//Make sure areas are compatible (only relevant if the last event was an opening). fuseAreas ( a, target( vertices[v].boundary.back(), false ) ); } assert( prolongate( vertices[v].boundary, vert_edge) ); fuseConnectedBoundaries(a);//there is no doing both: tests are performed twice but for 2 areas. fuseConnectedBoundaries(b);// }else{ //--Opening an edge:------------- unsigned e = event.tile; // std::printf(" opening edge %u\n", e); bool reversed = !sweeper.tiles_data[e].reversed;//Warning: true means v==start. //--Find first and last area around this vertex:------------- unsigned cur_a; if ( vertices[v].boundary.size() > 0 ){ cur_a = target( vertices[v].boundary.back(), false ); }else{//this vertex is empty if ( event.insert_at < sweeper.context.size() ){ unsigned upper_tile = sweeper.context[event.insert_at].first; cur_a = (sweeper.tiles_data[upper_tile].reversed) ? edges[upper_tile].left : edges[upper_tile].right; }else{ cur_a = 0; } } unsigned new_a = areas.size(); Area new_area(paths.size()); new_area.boundary.push_back( OrientedEdge(e, !reversed ) ); new_area.windings = areas[cur_a].windings;//FIXME: escape boundary cases!!! if ( input_paths[edges[e].path].closed() ){ new_area.windings[edges[e].path] += (reversed) ? +1 : -1; } areas.push_back(new_area); //update edge if (reversed){ edges[e].start = v; edges[e].left = new_a; edges[e].right = cur_a; }else{ edges[e].end = v; edges[e].left = cur_a; edges[e].right = new_a; } //update vertex OrientedEdge f(e, reversed); assert( prolongate( vertices[v].boundary, f) ); addAreaBoundaryPiece(cur_a, OrientedEdge(e, reversed) ); } if (!event.to_be_continued && vertices[v].boundary.size()>0){ unsigned first_a = source( vertices[v].boundary.front(), false ); unsigned last_a = target( vertices[v].boundary.back(), false ); fuseAreas(first_a, last_a); } // this->print(); // std::printf("----------------\n"); //std::printf("\n"); } } //---------------------------------------------------- //-- done. //---------------------------------------------------- }; /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :