#include #include <2geom/path.h> #include <2geom/basic-intersection.h> #include <2geom/pathvector.h> #include <2geom/exception.h> #include #include #include #include #include #include #define NULL_IDX UINT_MAX #include <2geom/orphan-code/intersection-by-smashing.h> #include "../2geom/orphan-code/intersection-by-smashing.cpp" using namespace Geom; using namespace std; /* The sweeper class takes a PathVector as input and generates "events" to let clients construct the relevant graph. The basic strategy is the following: The path is split into "tiles": a tile consists in 2 boxes related by a (monotonic) curve. The tiles are created at the very beginning, using a sweep, but *no care* is taken to topology information at this step! All the boxes of all the tiles are then enlarged so that they are either equal or disjoint. [TODO: we should look for curves traversing boxes, split them and repeat the process...] The sweeper maintains a virtual sweepline, that is the limit of the "known area". The tiles can have 2 states: open if they have one end in the known area, and one in the unknown, closed otherwise. [TODO: open/close should belong to tiles pointers, not tiles...] The sorted list of open tiles intersecting the sweep line is called the "context". *!*WARNING*!*: because the sweep line is not straight, closed tiles can still be in the context!! they can only be removed once the end of the last box is reached. The events are changes in the context when the sweep line crosses boxes. They are obtained by sorting the tiles according to one or the other of theire end boxes depending on the open/close state. A "big" event happens when the sweep line reaches a new 'box'. After such a "big" event, the sweep line goes round the new box along it's 3 other sides. N.B.: in an ideal world, all tiles ending at one box would be on one side, all the tiles starting there on the other. Unfortunately, because we have boxes as vertices, things are not that nice: open/closed tiles can appear in any order around a vertex, even in the monotonic case(!). Morover, our fat vertices have a non zero "duration", during which many things can happen: this is why we have to keep closed edges in the context until both ends of theire boxes are reached... To keep things uniform, such "big" events are split into elementary ones: opening/closing of a single edge. One such event is generated for each tile around the current 'box', in CCW order (geometrically, the sweepline is deformed in a neighborhood of the box to go round it for a certain amount, enter the box and come back inside the box; the piece inside the box is a "virtual edge" that is not added for good but that we keep track of). The event knows if it's the last one in such a sequence, so that the client knows when to do the additional work required to "close" the vertex construction. Hmmm. It's hard to explain the moves without a drawing here...(see sweep.svg in the doc dir). There are *Closings: insert a new the relevant tile in the context with a "exit" flag. *Openings: insert a new the relevant tile in the context with a "entry" flag. At the end of a box, the relevant exit/entries are purged from the context. N.B. I doubt we can do boolops without building the full graph, i.e. having different clients to obtain different outputs. So splitting sweeper/grpah builder is maybe not so relevant w/r to functionality (only code organization). */ //TODO: decline intersections algorithms for each kind of curves... //TODO: write an intersector that can work on sub domains. //TODO: factor computation of derivative and the like out. std::vector monotonic_smash_intersect( Curve const &a, Interval a_dom, Curve const &b, Interval b_dom, double tol){ std::vector result; D2 asb = a.toSBasis(); asb = portion( asb, a_dom ); D2 bsb = b.toSBasis(); bsb = portion( bsb, b_dom ); result = monotonic_smash_intersect(asb, bsb, tol ); for (auto & i : result){ i.times[X] *= a_dom.extent(); i.times[X] += a_dom.min(); i.times[Y] *= b_dom.extent(); i.times[Y] += b_dom.min(); } return result; } class Sweeper{ public: //--------------------------- // utils... //--------------------------- //near predicate utilized in process_splits template struct NearPredicate { double tol; NearPredicate(double eps):tol(eps){} NearPredicate(){tol = EPSILON;}//??? bool operator()(T x, T y) { return are_near(x, y, tol); } }; // ensures that f and t are elements of a vector, sorts and uniqueifies // also asserts that no values fall outside of f and t // if f is greater than t, the sort is in reverse void process_splits(std::vector &splits, double f, double t, double tol=EPSILON) { //splits.push_back(f); //splits.push_back(t); std::sort(splits.begin(), splits.end()); std::vector::iterator end = std::unique(splits.begin(), splits.end(), NearPredicate(tol)); splits.resize(end - splits.begin()); //remove any splits which fall outside t / f while(!splits.empty() && splits.front() < f+tol) splits.erase(splits.begin()); splits.insert(splits.begin(), f); //splits[0] = f; while(!splits.empty() && splits.back() > t-tol) splits.erase(splits.end() - 1); splits.push_back(t); //splits.back() = t; } struct IntersectionMinTimeOrder { unsigned which; IntersectionMinTimeOrder (unsigned idx) : which(idx) {} bool operator()(SmashIntersection const &a, SmashIntersection const &b) const { return a.times[which].min() < b.times[which].min(); } }; // ensures that f and t are elements of a vector, sorts and uniqueifies // also asserts that no values fall outside of f and t // if f is greater than t, the sort is in reverse std::vector > process_intersections(std::vector &inters, unsigned which, unsigned tileidx) { std::vector > result; std::pair apair; Interval dom ( tiles_data[tileidx].f, tiles_data[tileidx].t ); apair.first = Interval( dom.min() ); apair.second = tiles_data[tileidx].fbox; result.push_back( apair ); std::sort(inters.begin(), inters.end(), IntersectionMinTimeOrder(which) ); for (auto & inter : inters){ if ( !inter.times[which].intersects( dom ) )//this should never happen. continue; if ( result.back().first.intersects( inter.times[which] ) ){ result.back().first.unionWith( inter.times[which] ); result.back().second.unionWith( inter.bbox ); }else{ apair.first = inter.times[which]; apair.second = inter.bbox; result.push_back( apair ); } } apair.first = Interval( dom.max() ); apair.second = tiles_data[tileidx].tbox; if ( result.size() > 1 && result.back().first.intersects( apair.first ) ){ result.back().first.unionWith( apair.first ); result.back().second.unionWith( apair.second ); }else{ result.push_back( apair ); } return result; } //--------------------------- // Tiles. //--------------------------- //A tile is a "light edge": just two boxes, joint by a curve. //it is open iff intersected by the sweepline. class Tile{ public: unsigned path; unsigned curve; double f; double t; Rect fbox, tbox; bool reversed;//with respect to sweep direction. Flip f/t instead? bool open;//means sweepline currently cuts it (i.e. one end in the known area, the other in the unknown). int state;//-1: both ends in unknown area, 0:one end in each, 1: both in known area. //Warning: we can not delete a tile immediately when it's past(=closed again), only when the end of it's tbox is!. Rect bbox(){Rect b = fbox; b.unionWith(tbox); return b;} Point min(){return ( bbox().min() ); } Point max(){return ( bbox().max() ); } // Rect cur_box() const {return ((open)^(reversed) ) ? tbox : fbox; } Rect cur_box() const { return ((state>=0)^(reversed) ) ? tbox : fbox; } Rect first_box() const {return ( reversed ) ? tbox : fbox; } Rect last_box() const {return ( reversed ) ? fbox : tbox; } }; D2 tileToSB(Tile const &tile){ //TODO: don't convert each time!!!!!! assert( tile.path < paths.size() ); assert( tile.curve < paths[tile.path].size() ); D2 c = paths[tile.path][tile.curve].toSBasis(); c = portion( c, Interval( tile.f, tile.t ) ); return c; } //SweepOrder for Rects or Tiles. class SweepOrder{ public: Dim2 dim; SweepOrder(Dim2 d) : dim(d) {} bool operator()(const Rect &a, const Rect &b) const { return Point::LexLessRt(dim)(a.min(), b.min()); } bool operator()(const Tile &a, const Tile &b) const { return Point::LexLessRt(dim)(a.cur_box().min(), b.cur_box().min()); } }; class PtrSweepOrder{ public: Dim2 dim; std::vector::iterator const begin; PtrSweepOrder(std::vector::iterator const beg, Dim2 d) : dim(d), begin(beg){} bool operator()(const unsigned a, const unsigned b) const { return Point::LexLessRt(dim)((begin+a)->cur_box().min(), (begin+b)->cur_box().min()); } }; //--------------------------- // Vertices. //--------------------------- //A ray is nothing but an edge ending or starting at a given vertex, + some info about when/where it exited a "separating" box; struct Ray{ public: unsigned tile; bool centrifuge;//true if the intrinsic orientation of curve points away from the vertex. //exit info: unsigned exit_side;//0:y=min; 1:x=max; 2:y=max; 3:x=min. double exit_place; //x or y value on the exit line. double exit_time; //exit time on curve. Ray(){tile = NULL_IDX; exit_side = 4;} Ray(unsigned tile_idx, unsigned s, double p, double t){ tile = tile_idx; exit_side =s; exit_place = p; exit_time = t; } Ray(unsigned tile_idx, bool outward){ tile = tile_idx; exit_side = 4; centrifuge = outward; exit_time = (centrifuge) ? 2 : -1 ; } void setExitInfo( unsigned side, double place, double time){ exit_side = side; exit_place = place; exit_time = time; } }; class FatVertex : public Rect{ public: std::vector rays; FatVertex(const Rect &r, unsigned const tile, bool centrifuge) : Rect(r){ rays.push_back( Ray(tile, centrifuge) ); } FatVertex(Rect r) : Rect(r){} FatVertex() : Rect(){} void erase(unsigned from, unsigned to){ unsigned size = to-from; from = from % rays.size(); to = from + size; if (to >= rays.size() ){ to = to % rays.size(); rays.erase( rays.begin()+from, rays.end() ); rays.erase( rays.begin(), rays.begin()+to ); }else{ rays.erase( rays.begin()+from, rays.begin()+to ); } } }; //--------------------------- // Context related stuff. //--------------------------- class Event{ public: bool opening;//true means an edge is added, otherwise an edge is removed from context. unsigned tile;//which tile to open/close. unsigned insert_at;//where to insert the next tile in the context. //unsigned erase_at;//idx of the tile to close in the context. = context.find(tile). bool to_be_continued; bool empty(){ return tile==NULL_IDX; } Event(){ opening = false; insert_at = 0; //erase_at = 0; tile = NULL_IDX; to_be_continued = false; } }; void printEvent(Event const &e){ std::printf("Event: "); std::printf("%s, ", e.opening?"opening":"closing"); std::printf("insert_at:%u, ", e.insert_at); //std::printf("erase_at:%u, ", e.erase_at); std::printf("tile:%u.\n", e.tile); } class Context : public std::vector >{//first = tile, second = true if it's a birth (+). public: Point last_pos; FatVertex pending_vertex; Event pending_event; unsigned find(unsigned const tile, bool positive_only=false){ for (unsigned i=0; i unmatched_closed_tiles = std::vector(); // std::printf("Scan context.\n"); for (unsigned i=0; i= 0 ); //if the tile is open (i.e. not both ends in the known area) and point is below/above the tile's bbox: if ( tile.state == 0 ){ // std::printf("opened tile, "); if (pt[1-dim] < tile.min()[1-dim] ) { // printContext(); // std::printf("below bbox %u!\n", i); rank = i; break; } if (pt[1-dim] > tile.max()[1-dim] ){ // std::printf("above bbox %u!\n", i); continue; } //TODO: don't convert each time!!!!!! D2 c = tileToSB( tile ); std::vector times = roots(c[dim]-pt[dim]); if (times.size()==0){ assert( tile.first_box()[dim].contains(pt[dim]) ); if ( pt[1-dim] < tile.first_box()[1-dim].min() ){ // std::printf("open+hit box %u!\n", i); rank = i; break; }else{ continue; } } if ( pt[1-dim] < c[1-dim](times.front()) ){ // std::printf("open+hit curve %u!\n", i); rank = i; break; } } // std::printf("closed tile, "); //At this point, the tile is closed (i.e. both ends are in the known area) //Such tiles do 'nested parens' like travels in the unknown area. //We are interested in the second occurrence only (to give a chance to open tiles to exist in between). if ( unmatched_closed_tiles.size()==0 || tile_idx != unmatched_closed_tiles.back() ){ unmatched_closed_tiles.push_back( tile_idx ); // std::printf("open paren %u\n",tile_idx); continue; } unmatched_closed_tiles.pop_back(); // std::printf("close paren, "); if ( !tile.bbox().contains( pt ) ){ continue; } // std::printf("in bbox, "); //At least one of fbox[dim], tbox[dim] has to contain the pt[dim]: assert it? //Find intersection with the hline(vline if dim=Y) through the point double hit_place; //TODO: don't convert each time!!!!!! D2 c = tileToSB( tile ); std::vector times = roots(c[1-dim]-pt[1-dim]); if ( times.size()>0 ){ // std::printf("hit curve,"); hit_place = c[dim](times.front()); }else{ // std::printf("hit box, "); //if there was no intersection, the line went through the first_box assert( tile.first_box()[1-dim].contains(pt[1-dim]) ); continue; } if ( pt[dim] > hit_place ){ // std::printf("wrong side, "); continue; } // std::printf("good side, "); rank = i; break; } // std::printf("rank %u.\n", rank); // printContext(); assert( rank<=tiles_data.size() ); return rank; } //TODO: optimize this. //it's done the slow way for debugging purpose... void purgeDeadTiles(){ //std::printf("purge "); //printContext(); for (unsigned i=0; i(event.tile, false) ); }else{ unsigned idx = event.insert_at; tiles_data[event.tile].open = true; tiles_data[event.tile].state = 0; context.insert(context.begin()+idx, std::pair(event.tile, true) ); sortTiles(); } context.last_pos = context.pending_vertex.min(); context.last_pos[1-dim] = context.pending_vertex.max()[1-dim]; // std::printf(" new "); // printContext(); // std::printf("\n"); //context.pending_event = Event();is this a good idea? } //--------------------------- // Sweeper. //--------------------------- PathVector paths; std::vector tiles_data; std::vector tiles; std::vector vtxboxes; Context context; double tol; Dim2 dim; //------------------------------- //-- Tiles preparation. //------------------------------- //split input paths into monotonic pieces... void createMonotonicTiles(){ for ( unsigned i=0; i deriv = derivative( paths[i][j].toSBasis() ); std::vector splits0 = roots( deriv[X] ); std::vector splits90 = roots( deriv[Y] ); std::vector splits45 = roots( deriv[X]- deriv[Y] ); std::vector splits135 = roots( deriv[X] + deriv[Y] ); std::vector splits; splits.insert(splits.begin(), splits0.begin(), splits0.end() ); splits.insert(splits.begin(), splits90.begin(), splits90.end() ); splits.insert(splits.begin(), splits45.begin(), splits45.end() ); splits.insert(splits.begin(), splits135.begin(), splits135.end() ); process_splits(splits,0,1); for(unsigned k = 1; k < splits.size(); k++){ Tile tile; tile.path = i; tile.curve = j; tile.f = splits[k-1]; tile.t = splits[k]; //TODO: use meaningful tolerance here!! Point fp = paths[i][j].pointAt(tile.f); Point tp = paths[i][j].pointAt(tile.t); tile.fbox = Rect(fp, fp ); tile.tbox = Rect(tp, tp ); tile.open = false; tile.state = -1; tile.reversed = Point::LexLessRt(dim)(tp, fp); tiles_data.push_back(tile); } } } std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) ); } void splitTile(unsigned i, double t, double tolerance=0, bool sort = true){ assert( i const ×, double tolerance=0, bool sort = true){ if ( times.size()<3 ) return; assert( i pieces ( times.size()-2, tiles_data[i] ); Rect prevbox = tiles_data[i].fbox; for (unsigned k=0; k < times.size()-2; k++){ pieces[k].f = times[k]; pieces[k].t = times[k+1]; pieces[k].fbox = prevbox; //TODO: use relevant precision here. prevbox = fatPoint(paths[tiles_data[i].path][tiles_data[i].curve].pointAt(times[k+1]), tolerance ); pieces[k].tbox = prevbox; } tiles_data.insert(tiles_data.begin()+i, pieces.begin(), pieces.end() ); unsigned newi = i + times.size()-2; assert( newi=1 ); tiles_data[newi].f = tiles_data[newi-1].t; tiles_data[newi].fbox = tiles_data[newi-1].tbox; if (sort) std::sort(tiles_data.begin()+i, tiles_data.end(), SweepOrder(dim) ); } #else void splitTile(unsigned i, std::vector > const &cuts, bool sort = true){ assert ( cuts.size() >= 2 ); assert( i pieces ( cuts.size()-1, tiles_data[i] ); for (unsigned k=1; k+1 < cuts.size(); k++){ pieces[k-1].t = cuts[k].first.middle(); pieces[k ].f = cuts[k].first.middle(); pieces[k-1].tbox = cuts[k].second; pieces[k ].fbox = cuts[k].second; } pieces.front().fbox.unionWith( cuts[0].second ); pieces.back().tbox.unionWith( cuts.back().second ); tiles_data.insert(tiles_data.begin()+i, pieces.begin(), pieces.end()-1 ); unsigned newi = i + cuts.size()-2; assert( newi < tiles_data.size() ); tiles_data[newi] = pieces.back(); if (sort) std::sort(tiles_data.begin()+i, tiles_data.end(), SweepOrder(dim) ); } #endif //TODO: maybe not optimal. For a fully optimized sweep, it would be nice to have //an efficient way to way find *only the first* intersection (in sweep direction)... void splitIntersectingTiles(){ //make sure it is sorted, but should be ok. (remove sorting at the end of monotonic tiles creation? std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) ); // std::printf("\nFind intersections: tiles_data.size():%u\n", tiles_data.size() ); for (unsigned i=0; i+1 inters_on_i; for (unsigned j=i+1; j intersections; intersections = monotonic_smash_intersect(paths[pi][ci], Interval(tiles_data[i].f, tiles_data[i].t), paths[pj][cj], Interval(tiles_data[j].f, tiles_data[j].t), tol ); inters_on_i.insert( inters_on_i.end(), intersections.begin(), intersections.end() ); std::vector > cuts = process_intersections(intersections, 1, j); // std::printf(" >|%u/%u|=%u. times_j:%u", i, j, crossings.size(), times_j.size() ); splitTile(j, cuts, false); j += cuts.size()-2; } //process_splits(times_i, tiles_data[i].f, tiles_data[i].t); //assert(times_i.size()>=2); //splitTile(i, times_i, tol, false); //i+=times_i.size()-2; std::vector > cuts_on_i = process_intersections(inters_on_i, 0, i); splitTile(i, cuts_on_i, false); i += cuts_on_i.size()-2; //std::printf("new i:%u, tiles_data: %u\n",i ,tiles_data.size()); //std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) ); std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) ); } //this last sorting should be useless!! std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) ); } void sortTiles(){ std::sort(tiles.begin(), tiles.end(), PtrSweepOrder(tiles_data.begin(), dim) ); } //------------------------------- //-- Vertices boxes cookup. //------------------------------- void fuseInsert(const Rect &b, std::vector &boxes, Dim2 dim){ //TODO: this can be optimized... for (unsigned i=0; i::iterator pos = std::lower_bound(boxes.begin(), boxes.end(), b, SweepOrder(dim) ); boxes.insert( pos, b ); } //debug only!! bool isContained(Rect b, std::vector const &boxes ){ for (const auto & boxe : boxes){ if ( boxe.contains(b) ) return true; } return false; } //Collect vertex boxes. Fuse overlapping ones. //NB: enlarging a vertex may create intersection with already scanned ones... std::vector collectBoxes(){ std::vector ret; for (auto & i : tiles_data){ fuseInsert(i.fbox, ret, dim); fuseInsert(i.tbox, ret, dim); } return ret; } //enlarge tiles ends to match the vertices bounding boxes. //remove edges fully contained in one vertex bbox. void enlargeTilesEnds(const std::vector &boxes ){ for (unsigned i=0; i::const_iterator f_it; f_it = std::lower_bound(boxes.begin(), boxes.end(), tiles_data[i].fbox, SweepOrder(dim) ); if ( f_it==boxes.end() ) f_it--; while (!(*f_it).contains(tiles_data[i].fbox) && f_it != boxes.begin()){ f_it--; } assert( (*f_it).contains(tiles_data[i].fbox) ); tiles_data[i].fbox = *f_it; std::vector::const_iterator t_it; t_it = std::lower_bound(boxes.begin(), boxes.end(), tiles_data[i].tbox, SweepOrder(dim) ); if ( t_it==boxes.end() ) t_it--; while (!(*t_it).contains(tiles_data[i].tbox) && t_it != boxes.begin()){ t_it--; } assert( (*t_it).contains(tiles_data[i].tbox) ); tiles_data[i].tbox = *t_it; //NB: enlarging the ends may swapp their sweep order!!! tiles_data[i].reversed = Point::LexLessRt(dim)( tiles_data[i].tbox.min(), tiles_data[i].fbox.min()); if ( f_it==t_it ){ tiles_data.erase(tiles_data.begin()+i); i-=1; } } } //Make sure tiles stop at vertices. Split them if needed. //Returns true if at least one tile was split. bool splitTilesThroughFatPoints(std::vector &boxes ){ std::sort(tiles.begin(), tiles.end(), PtrSweepOrder(tiles_data.begin(), dim) ); std::sort(boxes.begin(), boxes.end(), SweepOrder(dim) ); bool result = false; for (unsigned i=0; i c = tileToSB( tiles_data[i] ); //----------> use level-set!! for (unsigned corner=0; corner<4; corner++){ unsigned D = corner % 2; double val = boxe.corner(corner)[D]; std::vector times = roots( c[D] - val ); if ( times.size()>0 ){ double t = lerp( times.front(), tiles_data[i].f, tiles_data[i].t ); double hit_place = c[1-D](times.front()); if ( boxe[1-D].contains(hit_place) ){ result = true; //taking a point on the boundary is dangerous!! //Either use >0 tolerance here, or find 2 intersection points and split in between. splitTile( i, t, tol, false); break; } } } } } return result; } //TODO: rewrite all this!... //------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------- //------------------------------- //-- ccw Sorting of rays around a vertex. //------------------------------- //------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------- //------------------------------------------------------------------------------------------- //returns an (infinite) rect around "a" separating it from "b". Nota: 3 sides are infinite! //TODO: place the cut where there is most space... OptRect separate(Rect const &a, Rect const &b){ Rect ret ( Interval( -infinity(), infinity() ) , Interval(-infinity(), infinity() ) ); double gap = 0; unsigned dir = 4; if (b[X].min() - a[X].max() > gap){ gap = b[X].min() - a[X].max(); dir = 0; } if (a[X].min() - b[X].max() > gap){ gap = a[X].min() - b[X].max(); dir = 1; } if (b[Y].min() - a[Y].max() > gap){ gap = b[Y].min() - a[Y].max(); dir = 2; } if (a[Y].min() - b[Y].max() > gap){ gap = a[Y].min() - b[Y].max(); dir = 3; } switch (dir) { case 0: ret[X].setMax(( a.max()[X] + b.min()[X] )/ 2); break; case 1: ret[X].setMin(( b.max()[X] + a.min()[X] )/ 2); break; case 2: ret[Y].setMax(( a.max()[Y] + b.min()[Y] )/ 2); break; case 3: ret[Y].setMin(( b.max()[Y] + a.min()[Y] )/ 2); break; case 4: return OptRect(); } return OptRect(ret); } //Find 4 lines (returned as a Rect sides) that cut all the rays (=edges). *!* some side might be infinite. OptRect isolateVertex(Rect const &box){ OptRect sep ( Interval( -infinity(), infinity() ) , Interval(-infinity(), infinity() ) ); //separate this vertex from the others. Find a better way. for (auto & vtxboxe : vtxboxes){ if ( Point::LexLessRt(dim)( sep->max(), vtxboxe.min() ) ){ break; } if ( vtxboxe!=box ){//&& !vtxboxes[i].intersects(box) ){ OptRect sepi = separate(box, vtxboxe); if ( sep && sepi ){ sep = intersect(*sep, *sepi); }else{ std::cout<<"box="< b.exit_side ) return false; if ( a.exit_side <= 1) { return ( a.exit_place < b.exit_place ); } return ( a.exit_place > b.exit_place ); } }; void printRay(Ray const &r){ std::printf("Ray: tile=%u, centrifuge=%u, side=%u, place=%f\n", r.tile, r.centrifuge, r.exit_side, r.exit_place); } void printVertex(FatVertex const &v){ std::printf("Vertex: [%f,%f]x[%f,%f]\n", v[X].min(),v[X].max(),v[Y].min(),v[Y].max() ); for (const auto & ray : v.rays){ printRay(ray); } } //TODO: use a partial order on input coming from the context + Try quadrant order just in case it's enough. //TODO: use monotonic assumption. void sortRays( FatVertex &v ){ OptRect sep = isolateVertex(v); for (unsigned i=0; i < v.rays.size(); i++){ v.rays[i].centrifuge = (tiles_data[ v.rays[i].tile ].fbox == v); v.rays[i].exit_time = v.rays[i].centrifuge ? 1 : 0 ; } for (unsigned i=0; i < v.rays.size(); i++){ //TODO: don't convert each time!!! assert( v.rays[i].tile < tiles_data.size() ); D2 c = tileToSB( tiles_data[ v.rays[i].tile ] ); for (unsigned side=0; side<4; side++){//scan X or Y direction, on level min or max... double level = sep->corner( side )[1-side%2]; if (level != infinity() && level != -infinity() ){ std::vector times = roots(c[1-side%2]-level); if ( times.size() > 0 ) { double t; assert( v.rays[i].tile < tiles_data.size() ); if (tiles_data[ v.rays[i].tile ].fbox == v){ t = times.front(); if ( v.rays[i].exit_side > 3 || v.rays[i].exit_time > t ){ v.rays[i].setExitInfo( side, c[side%2](t), t); } }else{ t = times.back(); if ( v.rays[i].exit_side > 3 || v.rays[i].exit_time < t ){ v.rays[i].setExitInfo( side, c[side%2](t), t); } } } } } } //Rk: at this point, side == 4 means the edge is contained in the intersection box (?)...; std::sort( v.rays.begin(), v.rays.end(), ExitOrder() ); } //------------------------------- //-- initialize all data. //------------------------------- Sweeper(){} Sweeper(PathVector const &input_paths, Dim2 sweep_dir, double tolerance=1e-5){ paths = input_paths;//use a ptr... dim = sweep_dir; tol = tolerance; //split paths into monotonic tiles createMonotonicTiles(); //split at tiles intersections splitIntersectingTiles(); //handle overlapping end boxes/and tiles traversing boxes. do{ vtxboxes = collectBoxes(); }while ( splitTilesThroughFatPoints(vtxboxes) ); enlargeTilesEnds(vtxboxes); //now create the pointers to the tiles. tiles = std::vector(tiles_data.size(), 0); for (unsigned i=0; i0){ context.clear(); context.last_pos = tiles_data[tiles.front()].min(); context.last_pos[dim] -= 1; context.pending_vertex = FatVertex(); context.pending_event = Event(); } // std::printf("Sweeper initialized (%u tiles)\n", tiles_data.size()); } //------------------------------- //-- Event walk. //------------------------------- Event getNextEvent(){ // std::printf("getNextEvent():\n"); // std::printf("initial contex:\n"); // printContext(); Event old_event = context.pending_event, event; // std::printf("apply old event\n"); applyEvent(context.pending_event); // printContext(); if (context.pending_vertex.rays.size()== 0){ // std::printf("cook up a new vertex\n"); //find the edges at the next vertex. //TODO: implement this as a lower bound!! std::vector::iterator low, high; //Warning: bad looking test, but make sure we advance even in case of 0 width boxes... for ( low = tiles.begin(); low != tiles.end() && ( tiles_data[*low].state==1 || Point::LexLessRt(dim)(tiles_data[*low].cur_box().min(), context.last_pos) ); low++){} if ( low == tiles.end() ){ // std::printf("no more event found\n"); return(Event()); } Rect pos = tiles_data[ *low ].cur_box(); context.last_pos = pos.min(); context.last_pos[1-dim] = pos.max()[1-dim]; // printContext(); // std::printf("purgeDeadTiles\n"); purgeDeadTiles(); // printContext(); FatVertex v(pos); high = low; do{ // v.rays.push_back( Ray(*high, !tiles_data[*high].open) ); v.rays.push_back( Ray(*high, tiles_data[*high].state!=0) ); high++; }while( high != tiles.end() && tiles_data[ *high ].cur_box()==pos ); // std::printf("sortRays\n"); sortRays(v); //Look for an opened tile unsigned i=0; for( i=0; i 0 ){ std::vector head; head.assign ( v.rays.begin(), v.rays.begin()+i ); v.rays.erase ( v.rays.begin(), v.rays.begin()+i ); v.rays.insert( v.rays.end(), head.begin(), head.end()); } //assert( tiles_data[ v.rays[0].tile ].open ); assert( tiles_data[ v.rays[0].tile ].state==0 ); event.insert_at = context.find( v.rays.front().tile )+1; // event.erase_at = context.find( v.rays.front().tile ); // std::printf("at least one closing!\n"); } context.pending_vertex = v; }else{ // std::printf("continue biting exiting vertex\n"); event.tile = context.pending_vertex.rays.front().tile; event.insert_at = old_event.insert_at; // if (old_event.opening){ // event.insert_at++; // }else{ // if (old_event.erase_at < old_event.insert_at){ // event.insert_at--; // } // } event.insert_at++; } event.tile = context.pending_vertex.rays.front().tile; // event.opening = !tiles_data[ event.tile ].open; event.opening = tiles_data[ event.tile ].state!=0; event.to_be_continued = context.pending_vertex.rays.size()>1; // if ( !event.opening ) event.erase_at = context.find(event.tile); context.pending_vertex.rays.erase(context.pending_vertex.rays.begin()); context.pending_event = event; // printEvent(event); return event; } }; /* 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 :