Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
sweeper.cpp
Go to the documentation of this file.
1#include <iostream>
2#include <2geom/path.h>
4#include <2geom/pathvector.h>
5#include <2geom/exception.h>
6
7#include <cstdlib>
8#include <cstdio>
9#include <set>
10#include <vector>
11#include <algorithm>
12
13#include <limits.h>
14#define NULL_IDX UINT_MAX
15
17#include "../2geom/orphan-code/intersection-by-smashing.cpp"
18
19using namespace Geom;
20using namespace std;
21
22
23/*
24The sweeper class takes a PathVector as input and generates "events" to let clients construct the relevant graph.
25
26The basic strategy is the following:
27The path is split into "tiles": a tile consists in 2 boxes related by a (monotonic) curve.
28
29The tiles are created at the very beginning, using a sweep, but *no care* is taken to topology
30information at this step! All the boxes of all the tiles are then enlarged so that they are
31either equal or disjoint.
32[TODO: we should look for curves traversing boxes, split them and repeat the process...]
33
34The sweeper maintains a virtual sweepline, that is the limit of the "known area". The tiles can have 2 states:
35open if they have one end in the known area, and one in the unknown, closed otherwise.
36[TODO: open/close should belong to tiles pointers, not tiles...]
37
38The sorted list of open tiles intersecting the sweep line is called the "context".
39*!*WARNING*!*: because the sweep line is not straight, closed tiles can still be in the context!!
40they can only be removed once the end of the last box is reached.
41
42The events are changes in the context when the sweep line crosses boxes.
43They are obtained by sorting the tiles according to one or the other of theire end boxes depending
44on the open/close state.
45
46A "big" event happens when the sweep line reaches a new 'box'. After such a "big" event, the sweep
47line goes round the new box along it's 3 other sides.
48N.B.: in an ideal world, all tiles ending at one box would be on one side, all the tiles starting
49there on the other. Unfortunately, because we have boxes as vertices, things are not that nice:
50open/closed tiles can appear in any order around a vertex, even in the monotonic case(!). Morover,
51our fat vertices have a non zero "duration", during which many things can happen: this is why we
52have to keep closed edges in the context until both ends of theire boxes are reached...
53
54
55To keep things uniform, such "big" events are split into elementary ones: opening/closing of a single
56edge. One such event is generated for each tile around the current 'box', in CCW order (geometrically,
57the sweepline is deformed in a neighborhood of the box to go round it for a certain amount, enter the
58box and come back inside the box; the piece inside the box is a "virtual edge" that is not added for
59good but that we keep track of). The event knows if it's the last one in such a sequence, so that the
60client knows when to do the additional work required to "close" the vertex construction. Hmmm. It's
61hard to explain the moves without a drawing here...(see sweep.svg in the doc dir). There are
62
63*Closings: insert a new the relevant tile in the context with a "exit" flag.
64
65*Openings: insert a new the relevant tile in the context with a "entry" flag.
66
67At the end of a box, the relevant exit/entries are purged from the context.
68
69
70N.B. I doubt we can do boolops without building the full graph, i.e. having different clients to obtain
71different outputs. So splitting sweeper/grpah builder is maybe not so relevant w/r to functionality
72(only code organization).
73*/
74
75
76//TODO: decline intersections algorithms for each kind of curves...
77//TODO: write an intersector that can work on sub domains.
78//TODO: factor computation of derivative and the like out.
79std::vector<SmashIntersection> monotonic_smash_intersect( Curve const &a, Interval a_dom,
80 Curve const &b, Interval b_dom, double tol){
81 std::vector<SmashIntersection> result;
82 D2<SBasis> asb = a.toSBasis();
83 asb = portion( asb, a_dom );
84 D2<SBasis> bsb = b.toSBasis();
85 bsb = portion( bsb, b_dom );
86 result = monotonic_smash_intersect(asb, bsb, tol );
87 for (auto & i : result){
88 i.times[X] *= a_dom.extent();
89 i.times[X] += a_dom.min();
90 i.times[Y] *= b_dom.extent();
91 i.times[Y] += b_dom.min();
92 }
93 return result;
94}
95
96
97
98class Sweeper{
99public:
100
101 //---------------------------
102 // utils...
103 //---------------------------
104
105 //near predicate utilized in process_splits
106 template<typename T>
107 struct NearPredicate {
108 double tol;
109 NearPredicate(double eps):tol(eps){}
110 NearPredicate(){tol = EPSILON;}//???
111 bool operator()(T x, T y) { return are_near(x, y, tol); } };
112
113 // ensures that f and t are elements of a vector, sorts and uniqueifies
114 // also asserts that no values fall outside of f and t
115 // if f is greater than t, the sort is in reverse
116 void process_splits(std::vector<double> &splits, double f, double t, double tol=EPSILON) {
117 //splits.push_back(f);
118 //splits.push_back(t);
119 std::sort(splits.begin(), splits.end());
120 std::vector<double>::iterator end = std::unique(splits.begin(), splits.end(), NearPredicate<double>(tol));
121 splits.resize(end - splits.begin());
122
123 //remove any splits which fall outside t / f
124 while(!splits.empty() && splits.front() < f+tol) splits.erase(splits.begin());
125 splits.insert(splits.begin(), f);
126 //splits[0] = f;
127 while(!splits.empty() && splits.back() > t-tol) splits.erase(splits.end() - 1);
128 splits.push_back(t);
129 //splits.back() = t;
130 }
131
132 struct IntersectionMinTimeOrder {
133 unsigned which;
134 IntersectionMinTimeOrder (unsigned idx) : which(idx) {}
135 bool operator()(SmashIntersection const &a, SmashIntersection const &b) const {
136 return a.times[which].min() < b.times[which].min();
137 }
138 };
139
140 // ensures that f and t are elements of a vector, sorts and uniqueifies
141 // also asserts that no values fall outside of f and t
142 // if f is greater than t, the sort is in reverse
143 std::vector<std::pair<Interval, Rect> >
144 process_intersections(std::vector<SmashIntersection> &inters, unsigned which, unsigned tileidx) {
145 std::vector<std::pair<Interval, Rect> > result;
146 std::pair<Interval, Rect> apair;
147 Interval dom ( tiles_data[tileidx].f, tiles_data[tileidx].t );
148 apair.first = Interval( dom.min() );
149 apair.second = tiles_data[tileidx].fbox;
150 result.push_back( apair );
151
152 std::sort(inters.begin(), inters.end(), IntersectionMinTimeOrder(which) );
153 for (auto & inter : inters){
154 if ( !inter.times[which].intersects( dom ) )//this should never happen.
155 continue;
156 if ( result.back().first.intersects( inter.times[which] ) ){
157 result.back().first.unionWith( inter.times[which] );
158 result.back().second.unionWith( inter.bbox );
159 }else{
160 apair.first = inter.times[which];
161 apair.second = inter.bbox;
162 result.push_back( apair );
163 }
164 }
165 apair.first = Interval( dom.max() );
166 apair.second = tiles_data[tileidx].tbox;
167 if ( result.size() > 1 && result.back().first.intersects( apair.first ) ){
168 result.back().first.unionWith( apair.first );
169 result.back().second.unionWith( apair.second );
170 }else{
171 result.push_back( apair );
172 }
173 return result;
174 }
175
176
177 //---------------------------
178 // Tiles.
179 //---------------------------
180
181 //A tile is a "light edge": just two boxes, joint by a curve.
182 //it is open iff intersected by the sweepline.
183 class Tile{
184 public:
185 unsigned path;
186 unsigned curve;
187 double f;
188 double t;
189 Rect fbox, tbox;
190 bool reversed;//with respect to sweep direction. Flip f/t instead?
191 bool open;//means sweepline currently cuts it (i.e. one end in the known area, the other in the unknown).
192 int state;//-1: both ends in unknown area, 0:one end in each, 1: both in known area.
193 //Warning: we can not delete a tile immediately when it's past(=closed again), only when the end of it's tbox is!.
194 Rect bbox(){Rect b = fbox; b.unionWith(tbox); return b;}
195 Point min(){return ( bbox().min() ); }
196 Point max(){return ( bbox().max() ); }
197// Rect cur_box() const {return ((open)^(reversed) ) ? tbox : fbox; }
198 Rect cur_box() const { return ((state>=0)^(reversed) ) ? tbox : fbox; }
199 Rect first_box() const {return ( reversed ) ? tbox : fbox; }
200 Rect last_box() const {return ( reversed ) ? fbox : tbox; }
201 };
202
203 D2<SBasis> tileToSB(Tile const &tile){
204 //TODO: don't convert each time!!!!!!
205 assert( tile.path < paths.size() );
206 assert( tile.curve < paths[tile.path].size() );
207 D2<SBasis> c = paths[tile.path][tile.curve].toSBasis();
208 c = portion( c, Interval( tile.f, tile.t ) );
209 return c;
210 }
211
212 //SweepOrder for Rects or Tiles.
213 class SweepOrder{
214 public:
215 Dim2 dim;
216 SweepOrder(Dim2 d) : dim(d) {}
217 bool operator()(const Rect &a, const Rect &b) const {
218 return Point::LexLessRt(dim)(a.min(), b.min());
219 }
220 bool operator()(const Tile &a, const Tile &b) const {
221 return Point::LexLessRt(dim)(a.cur_box().min(), b.cur_box().min());
222 }
223 };
224
225 class PtrSweepOrder{
226 public:
227 Dim2 dim;
228 std::vector<Tile>::iterator const begin;
229 PtrSweepOrder(std::vector<Tile>::iterator const beg, Dim2 d) : dim(d), begin(beg){}
230 bool operator()(const unsigned a, const unsigned b) const {
231 return Point::LexLessRt(dim)((begin+a)->cur_box().min(), (begin+b)->cur_box().min());
232 }
233 };
234
235
236 //---------------------------
237 // Vertices.
238 //---------------------------
239
240 //A ray is nothing but an edge ending or starting at a given vertex, + some info about when/where it exited a "separating" box;
241 struct Ray{
242 public:
243 unsigned tile;
244 bool centrifuge;//true if the intrinsic orientation of curve points away from the vertex.
245 //exit info:
246 unsigned exit_side;//0:y=min; 1:x=max; 2:y=max; 3:x=min.
247 double exit_place; //x or y value on the exit line.
248 double exit_time; //exit time on curve.
249 Ray(){tile = NULL_IDX; exit_side = 4;}
250 Ray(unsigned tile_idx, unsigned s, double p, double t){
251 tile = tile_idx;
252 exit_side =s;
253 exit_place = p;
254 exit_time = t;
255 }
256 Ray(unsigned tile_idx, bool outward){
257 tile = tile_idx;
258 exit_side = 4;
259 centrifuge = outward;
260 exit_time = (centrifuge) ? 2 : -1 ;
261 }
262 void setExitInfo( unsigned side, double place, double time){
263 exit_side = side;
264 exit_place = place;
265 exit_time = time;
266 }
267 };
268
269 class FatVertex : public Rect{
270 public:
271 std::vector<Ray> rays;
272 FatVertex(const Rect &r, unsigned const tile, bool centrifuge) : Rect(r){
273 rays.push_back( Ray(tile, centrifuge) );
274 }
275 FatVertex(Rect r) : Rect(r){}
276 FatVertex() : Rect(){}
277 void erase(unsigned from, unsigned to){
278 unsigned size = to-from;
279 from = from % rays.size();
280 to = from + size;
281
282 if (to >= rays.size() ){
283 to = to % rays.size();
284 rays.erase( rays.begin()+from, rays.end() );
285 rays.erase( rays.begin(), rays.begin()+to );
286 }else{
287 rays.erase( rays.begin()+from, rays.begin()+to );
288 }
289
290 }
291 };
292
293 //---------------------------
294 // Context related stuff.
295 //---------------------------
296
297 class Event{
298 public:
299 bool opening;//true means an edge is added, otherwise an edge is removed from context.
300 unsigned tile;//which tile to open/close.
301 unsigned insert_at;//where to insert the next tile in the context.
302 //unsigned erase_at;//idx of the tile to close in the context. = context.find(tile).
303 bool to_be_continued;
304 bool empty(){
305 return tile==NULL_IDX;
306 }
307 Event(){
308 opening = false;
309 insert_at = 0;
310 //erase_at = 0;
311 tile = NULL_IDX;
312 to_be_continued = false;
313 }
314 };
315
316 void printEvent(Event const &e){
317 std::printf("Event: ");
318 std::printf("%s, ", e.opening?"opening":"closing");
319 std::printf("insert_at:%u, ", e.insert_at);
320 //std::printf("erase_at:%u, ", e.erase_at);
321 std::printf("tile:%u.\n", e.tile);
322 }
323
324 class Context : public std::vector<std::pair<unsigned,bool> >{//first = tile, second = true if it's a birth (+).
325 public:
326 Point last_pos;
327 FatVertex pending_vertex;
328 Event pending_event;
329
330 unsigned find(unsigned const tile, bool positive_only=false){
331 for (unsigned i=0; i<size(); i++){
332 if ( (*this)[i].first == tile ){
333 if ( (*this)[i].second || !positive_only ) return i;
334 }
335 }
336 return (*this).size();
337 }
338 };
339 void printContext(){
340 std::printf("context:[");
341 for (unsigned i=0; i<context.size(); i++){
342 unsigned tile = context[i].first;
343 assert( tile<tiles_data.size() );
344 std::printf(" %s%u%s", (tiles_data[ tile ].reversed)?"-":"+", tile, (context[i].second)?"o":"c");
345// assert( context[i].second || !tiles_data[ tile ].open);
346 assert( context[i].second || tiles_data[ tile ].state==1);
347 }
348 std::printf("]\n");
349 }
350
351
352
353 //----
354 //This is the heart of it all!! Take particular care to non linear sweep line...
355 //----
356 //Given a point on the sweep line, (supposed to be the min() of a vertex not yet connected to the already known part),
357 //find the first edge "after" it in the context. Pretty tricky atm :-(
358 //TODO: implement this as a lower_bound (?).
359
360 unsigned contextRanking(Point const &pt){
361
362// std::printf("contextRanking:------------------------------------\n");
363
364 unsigned rank = context.size();
365 std::vector<unsigned> unmatched_closed_tiles = std::vector<unsigned>();
366
367// std::printf("Scan context.\n");
368
369 for (unsigned i=0; i<context.size(); i++){
370
371 unsigned tile_idx = context[i].first;
372 assert( tile_idx < tiles_data.size() );
373// std::printf("testing %u (e=%u),", i, tile_idx);
374
375 Tile tile = tiles_data[tile_idx];
376 assert( tile.state >= 0 );
377
378 //if the tile is open (i.e. not both ends in the known area) and point is below/above the tile's bbox:
379 if ( tile.state == 0 ){
380// std::printf("opened tile, ");
381 if (pt[1-dim] < tile.min()[1-dim] ) {
382// printContext();
383// std::printf("below bbox %u!\n", i);
384 rank = i;
385 break;
386 }
387 if (pt[1-dim] > tile.max()[1-dim] ){
388// std::printf("above bbox %u!\n", i);
389 continue;
390 }
391
392 //TODO: don't convert each time!!!!!!
393 D2<SBasis> c = tileToSB( tile );
394
395 std::vector<double> times = roots(c[dim]-pt[dim]);
396 if (times.size()==0){
397 assert( tile.first_box()[dim].contains(pt[dim]) );
398 if ( pt[1-dim] < tile.first_box()[1-dim].min() ){
399// std::printf("open+hit box %u!\n", i);
400 rank = i;
401 break;
402 }else{
403 continue;
404 }
405 }
406 if ( pt[1-dim] < c[1-dim](times.front()) ){
407// std::printf("open+hit curve %u!\n", i);
408 rank = i;
409 break;
410 }
411 }
412
413// std::printf("closed tile, ");
414
415
416 //At this point, the tile is closed (i.e. both ends are in the known area)
417 //Such tiles do 'nested parens' like travels in the unknown area.
418 //We are interested in the second occurrence only (to give a chance to open tiles to exist in between).
419 if ( unmatched_closed_tiles.size()==0 || tile_idx != unmatched_closed_tiles.back() ){
420 unmatched_closed_tiles.push_back( tile_idx );
421// std::printf("open paren %u\n",tile_idx);
422 continue;
423 }
424 unmatched_closed_tiles.pop_back();
425
426// std::printf("close paren, ");
427
428 if ( !tile.bbox().contains( pt ) ){
429 continue;
430 }
431
432// std::printf("in bbox, ");
433
434 //At least one of fbox[dim], tbox[dim] has to contain the pt[dim]: assert it?
435
436 //Find intersection with the hline(vline if dim=Y) through the point
437 double hit_place;
438 //TODO: don't convert each time!!!!!!
439 D2<SBasis> c = tileToSB( tile );
440 std::vector<double> times = roots(c[1-dim]-pt[1-dim]);
441 if ( times.size()>0 ){
442// std::printf("hit curve,");
443 hit_place = c[dim](times.front());
444 }else{
445// std::printf("hit box, ");
446 //if there was no intersection, the line went through the first_box
447 assert( tile.first_box()[1-dim].contains(pt[1-dim]) );
448 continue;
449 }
450
451 if ( pt[dim] > hit_place ){
452// std::printf("wrong side, ");
453 continue;
454 }
455// std::printf("good side, ");
456 rank = i;
457 break;
458 }
459
460// std::printf("rank %u.\n", rank);
461// printContext();
462 assert( rank<=tiles_data.size() );
463 return rank;
464 }
465
466 //TODO: optimize this.
467 //it's done the slow way for debugging purpose...
468 void purgeDeadTiles(){
469 //std::printf("purge ");
470 //printContext();
471 for (unsigned i=0; i<context.size(); i++){
472 assert( context[i].first<tiles_data.size() );
473 Tile tile = tiles_data[context[i].first];
474 if (tile.state==1 && Point::LexLessRt(dim)( tile.fbox.max(), context.last_pos ) && Point::LexLessRt(dim)( tile.tbox.max(), context.last_pos ) ){
475// if (!tile.open && Point::LexLessRt(dim)( tile.fbox.max(), context.last_pos ) && Point::LexLessRt(dim)( tile.tbox.max(), context.last_pos ) ){
476 unsigned j;
477 for (j=i+1; j<context.size() && context[j].first != context[i].first; j++){}
478 assert ( j < context.size() );
479 if ( context[j].first == context[i].first){
480 assert ( context[j].second == !context[i].second );
481 context.erase(context.begin()+j);
482 context.erase(context.begin()+i);
483// printContext();
484 i--;
485 }
486 }
487 }
488 return;
489 }
490
491 void applyEvent(Event event){
492// std::printf("Apply event : ");
493 if(event.empty()){
494// std::printf("empty event!\n");
495 return;
496 }
497
498// printEvent(event);
499// std::printf(" old ");
500// printContext();
501
502 assert ( context.begin() + event.insert_at <= context.end() );
503
504 if (!event.opening){
505// unsigned idx = event.erase_at;
506// assert( idx == context.find(event.tile) );
507// assert( context[idx].first == event.tile);
508 tiles_data[event.tile].open = false;
509 tiles_data[event.tile].state = 1;
510 //context.erase(context.begin()+idx);
511 unsigned idx = event.insert_at;
512 context.insert(context.begin()+idx, std::pair<unsigned, bool>(event.tile, false) );
513 }else{
514 unsigned idx = event.insert_at;
515 tiles_data[event.tile].open = true;
516 tiles_data[event.tile].state = 0;
517 context.insert(context.begin()+idx, std::pair<unsigned, bool>(event.tile, true) );
518 sortTiles();
519 }
520 context.last_pos = context.pending_vertex.min();
521 context.last_pos[1-dim] = context.pending_vertex.max()[1-dim];
522
523// std::printf(" new ");
524// printContext();
525// std::printf("\n");
526 //context.pending_event = Event();is this a good idea?
527 }
528
529
530
531 //---------------------------
532 // Sweeper.
533 //---------------------------
534
536 std::vector<Tile> tiles_data;
537 std::vector<unsigned> tiles;
538 std::vector<Rect> vtxboxes;
539 Context context;
540 double tol;
541 Dim2 dim;
542
543
544 //-------------------------------
545 //-- Tiles preparation.
546 //-------------------------------
547
548 //split input paths into monotonic pieces...
549 void createMonotonicTiles(){
550 for ( unsigned i=0; i<paths.size(); i++){
551 for ( unsigned j=0; j<paths[i].size(); j++){
552 //find the points where slope is 0°, 45°, 90°, 135°...
553 D2<SBasis> deriv = derivative( paths[i][j].toSBasis() );
554 std::vector<double> splits0 = roots( deriv[X] );
555 std::vector<double> splits90 = roots( deriv[Y] );
556 std::vector<double> splits45 = roots( deriv[X]- deriv[Y] );
557 std::vector<double> splits135 = roots( deriv[X] + deriv[Y] );
558 std::vector<double> splits;
559 splits.insert(splits.begin(), splits0.begin(), splits0.end() );
560 splits.insert(splits.begin(), splits90.begin(), splits90.end() );
561 splits.insert(splits.begin(), splits45.begin(), splits45.end() );
562 splits.insert(splits.begin(), splits135.begin(), splits135.end() );
563 process_splits(splits,0,1);
564
565 for(unsigned k = 1; k < splits.size(); k++){
566 Tile tile;
567 tile.path = i;
568 tile.curve = j;
569 tile.f = splits[k-1];
570 tile.t = splits[k];
571 //TODO: use meaningful tolerance here!!
572 Point fp = paths[i][j].pointAt(tile.f);
573 Point tp = paths[i][j].pointAt(tile.t);
574 tile.fbox = Rect(fp, fp );
575 tile.tbox = Rect(tp, tp );
576 tile.open = false;
577 tile.state = -1;
578 tile.reversed = Point::LexLessRt(dim)(tp, fp);
579
580 tiles_data.push_back(tile);
581 }
582 }
583 }
584 std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) );
585 }
586
587 void splitTile(unsigned i, double t, double tolerance=0, bool sort = true){
588 assert( i<tiles_data.size() );
589 Tile newtile = tiles_data[i];
590 assert( newtile.f < t && t < newtile.t );
591 newtile.f = t;
592 //newtile.fbox = fatPoint(paths[newtile.path][newtile.curve].pointAt(t), tolerance );
593 Point p = paths[newtile.path][newtile.curve].pointAt(t);
594 newtile.fbox = Rect(p, p);
595 newtile.fbox.expandBy( tolerance );
596 tiles_data[i].tbox = newtile.fbox;
597 tiles_data[i].t = t;
598 tiles_data.insert(tiles_data.begin()+i+1, newtile);
599 if (sort)
600 std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) );
601 }
602 void splitTile(unsigned i, SmashIntersection inter, unsigned which,bool sort = true){
603 double t = inter.times[which].middle();
604 assert( i<tiles_data.size() );
605 Tile newtile = tiles_data[i];
606 assert( newtile.f < t && t < newtile.t );
607 newtile.f = t;
608 newtile.fbox = inter.bbox;
609 tiles_data[i].tbox = newtile.fbox;
610 tiles_data[i].t = t;
611 tiles_data.insert(tiles_data.begin()+i+1, newtile);
612 if (sort)
613 std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) );
614 }
615#if 0
616 void splitTile(unsigned i, std::vector<double> const &times, double tolerance=0, bool sort = true){
617 if ( times.size()<3 ) return;
618 assert( i<tiles_data.size() );
619 std::vector<Tile> pieces ( times.size()-2, tiles_data[i] );
620 Rect prevbox = tiles_data[i].fbox;
621 for (unsigned k=0; k < times.size()-2; k++){
622 pieces[k].f = times[k];
623 pieces[k].t = times[k+1];
624 pieces[k].fbox = prevbox;
625 //TODO: use relevant precision here.
626 prevbox = fatPoint(paths[tiles_data[i].path][tiles_data[i].curve].pointAt(times[k+1]), tolerance );
627 pieces[k].tbox = prevbox;
628 }
629 tiles_data.insert(tiles_data.begin()+i, pieces.begin(), pieces.end() );
630 unsigned newi = i + times.size()-2;
631 assert( newi<tiles_data.size() );
632 assert( newi>=1 );
633 tiles_data[newi].f = tiles_data[newi-1].t;
634 tiles_data[newi].fbox = tiles_data[newi-1].tbox;
635
636 if (sort)
637 std::sort(tiles_data.begin()+i, tiles_data.end(), SweepOrder(dim) );
638 }
639#else
640 void splitTile(unsigned i, std::vector<std::pair<Interval,Rect> > const &cuts, bool sort = true){
641 assert ( cuts.size() >= 2 );
642 assert( i<tiles_data.size() );
643 std::vector<Tile> pieces ( cuts.size()-1, tiles_data[i] );
644 for (unsigned k=1; k+1 < cuts.size(); k++){
645 pieces[k-1].t = cuts[k].first.middle();
646 pieces[k ].f = cuts[k].first.middle();
647 pieces[k-1].tbox = cuts[k].second;
648 pieces[k ].fbox = cuts[k].second;
649 }
650 pieces.front().fbox.unionWith( cuts[0].second );
651 pieces.back().tbox.unionWith( cuts.back().second );
652
653 tiles_data.insert(tiles_data.begin()+i, pieces.begin(), pieces.end()-1 );
654 unsigned newi = i + cuts.size()-2;
655 assert( newi < tiles_data.size() );
656 tiles_data[newi] = pieces.back();
657
658 if (sort)
659 std::sort(tiles_data.begin()+i, tiles_data.end(), SweepOrder(dim) );
660 }
661#endif
662
663 //TODO: maybe not optimal. For a fully optimized sweep, it would be nice to have
664 //an efficient way to way find *only the first* intersection (in sweep direction)...
665 void splitIntersectingTiles(){
666 //make sure it is sorted, but should be ok. (remove sorting at the end of monotonic tiles creation?
667 std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) );
668
669// std::printf("\nFind intersections: tiles_data.size():%u\n", tiles_data.size() );
670
671 for (unsigned i=0; i+1<tiles_data.size(); i++){
672 //std::printf("\ni=%u (%u([%f,%f]))\n", i, tiles_data[i].curve, tiles_data[i].f, tiles_data[i].t );
673 std::vector<SmashIntersection> inters_on_i;
674 for (unsigned j=i+1; j<tiles_data.size(); j++){
675 //std::printf(" j=%u (%u)\n", j,tiles_data[j].curve );
676 if ( Point::LexLessRt(dim)(tiles_data[i].max(), tiles_data[j].min()) ) break;
677
678 unsigned pi = tiles_data[i].path;
679 unsigned ci = tiles_data[i].curve;
680 unsigned pj = tiles_data[j].path;
681 unsigned cj = tiles_data[j].curve;
682 std::vector<SmashIntersection> intersections;
683
684 intersections = monotonic_smash_intersect(paths[pi][ci], Interval(tiles_data[i].f, tiles_data[i].t),
685 paths[pj][cj], Interval(tiles_data[j].f, tiles_data[j].t), tol );
686 inters_on_i.insert( inters_on_i.end(), intersections.begin(), intersections.end() );
687 std::vector<std::pair<Interval, Rect> > cuts = process_intersections(intersections, 1, j);
688
689// std::printf(" >|%u/%u|=%u. times_j:%u", i, j, crossings.size(), times_j.size() );
690
691 splitTile(j, cuts, false);
692 j += cuts.size()-2;
693 }
694
695 //process_splits(times_i, tiles_data[i].f, tiles_data[i].t);
696 //assert(times_i.size()>=2);
697 //splitTile(i, times_i, tol, false);
698 //i+=times_i.size()-2;
699 std::vector<std::pair<Interval, Rect> > cuts_on_i = process_intersections(inters_on_i, 0, i);
700 splitTile(i, cuts_on_i, false);
701 i += cuts_on_i.size()-2;
702 //std::printf("new i:%u, tiles_data: %u\n",i ,tiles_data.size());
703 //std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) );
704 std::sort(tiles_data.begin()+i+1, tiles_data.end(), SweepOrder(dim) );
705 }
706 //this last sorting should be useless!!
707 std::sort(tiles_data.begin(), tiles_data.end(), SweepOrder(dim) );
708 }
709
710 void sortTiles(){
711 std::sort(tiles.begin(), tiles.end(), PtrSweepOrder(tiles_data.begin(), dim) );
712 }
713
714
715 //-------------------------------
716 //-- Vertices boxes cookup.
717 //-------------------------------
718
719 void fuseInsert(const Rect &b, std::vector<Rect> &boxes, Dim2 dim){
720 //TODO: this can be optimized...
721 for (unsigned i=0; i<boxes.size(); i++){
722 if ( Point::LexLessRt(dim)( b.max(), boxes[i].min() ) ) break;
723 if ( b.intersects( boxes[i] ) ){
724 Rect bigb = b;
725 bigb.unionWith( boxes[i] );
726 boxes.erase( boxes.begin()+i );
727 fuseInsert( bigb, boxes, dim);
728 return;
729 }
730 }
731 std::vector<Rect>::iterator pos = std::lower_bound(boxes.begin(), boxes.end(), b, SweepOrder(dim) );
732 boxes.insert( pos, b );
733 }
734
735 //debug only!!
736 bool isContained(Rect b, std::vector<Rect> const &boxes ){
737 for (const auto & boxe : boxes){
738 if ( boxe.contains(b) ) return true;
739 }
740 return false;
741 }
742
743 //Collect vertex boxes. Fuse overlapping ones.
744 //NB: enlarging a vertex may create intersection with already scanned ones...
745 std::vector<Rect> collectBoxes(){
746 std::vector<Rect> ret;
747 for (auto & i : tiles_data){
748 fuseInsert(i.fbox, ret, dim);
749 fuseInsert(i.tbox, ret, dim);
750 }
751 return ret;
752 }
753
754 //enlarge tiles ends to match the vertices bounding boxes.
755 //remove edges fully contained in one vertex bbox.
756 void enlargeTilesEnds(const std::vector<Rect> &boxes ){
757 for (unsigned i=0; i<tiles_data.size(); i++){
758 std::vector<Rect>::const_iterator f_it;
759 f_it = std::lower_bound(boxes.begin(), boxes.end(), tiles_data[i].fbox, SweepOrder(dim) );
760 if ( f_it==boxes.end() ) f_it--;
761 while (!(*f_it).contains(tiles_data[i].fbox) && f_it != boxes.begin()){
762 f_it--;
763 }
764 assert( (*f_it).contains(tiles_data[i].fbox) );
765 tiles_data[i].fbox = *f_it;
766
767 std::vector<Rect>::const_iterator t_it;
768 t_it = std::lower_bound(boxes.begin(), boxes.end(), tiles_data[i].tbox, SweepOrder(dim) );
769 if ( t_it==boxes.end() ) t_it--;
770 while (!(*t_it).contains(tiles_data[i].tbox) && t_it != boxes.begin()){
771 t_it--;
772 }
773 assert( (*t_it).contains(tiles_data[i].tbox) );
774 tiles_data[i].tbox = *t_it;
775
776 //NB: enlarging the ends may swapp their sweep order!!!
777 tiles_data[i].reversed = Point::LexLessRt(dim)( tiles_data[i].tbox.min(), tiles_data[i].fbox.min());
778
779 if ( f_it==t_it ){
780 tiles_data.erase(tiles_data.begin()+i);
781 i-=1;
782 }
783 }
784 }
785
786 //Make sure tiles stop at vertices. Split them if needed.
787 //Returns true if at least one tile was split.
788 bool splitTilesThroughFatPoints(std::vector<Rect> &boxes ){
789
790 std::sort(tiles.begin(), tiles.end(), PtrSweepOrder(tiles_data.begin(), dim) );
791 std::sort(boxes.begin(), boxes.end(), SweepOrder(dim) );
792
793 bool result = false;
794 for (unsigned i=0; i<tiles_data.size(); i++){
795 for (auto & boxe : boxes){
796 if ( Point::LexLessRt(dim)( tiles_data[i].max(), boxe.min()) ) break;
797 if ( Point::LexLessRt(dim)( boxe.max(), tiles_data[i].min()) ) continue;
798 if ( !boxe.intersects( tiles_data[i].bbox() ) ) continue;
799 if ( tiles_data[i].fbox.intersects( boxe ) ) continue;
800 if ( tiles_data[i].tbox.intersects( boxe ) ) continue;
801
802 //at this point box[k] intersects the curve bbox away from the fbox and tbox.
803
804 D2<SBasis> c = tileToSB( tiles_data[i] );
805//----------> use level-set!!
806 for (unsigned corner=0; corner<4; corner++){
807 unsigned D = corner % 2;
808 double val = boxe.corner(corner)[D];
809 std::vector<double> times = roots( c[D] - val );
810 if ( times.size()>0 ){
811 double t = Geom::lerp(times.front(), tiles_data[i].f, tiles_data[i].t);
812 double hit_place = c[1-D](times.front());
813 if ( boxe[1-D].contains(hit_place) ){
814 result = true;
815 //taking a point on the boundary is dangerous!!
816 //Either use >0 tolerance here, or find 2 intersection points and split in between.
817 splitTile( i, t, tol, false);
818 break;
819 }
820 }
821 }
822 }
823 }
824 return result;
825 }
826
827
828
829
830
831 //TODO: rewrite all this!...
832 //-------------------------------------------------------------------------------------------
833 //-------------------------------------------------------------------------------------------
834 //-------------------------------------------------------------------------------------------
835 //-------------------------------
836 //-- ccw Sorting of rays around a vertex.
837 //-------------------------------
838 //-------------------------------------------------------------------------------------------
839 //-------------------------------------------------------------------------------------------
840 //-------------------------------------------------------------------------------------------
841 //returns an (infinite) rect around "a" separating it from "b". Nota: 3 sides are infinite!
842 //TODO: place the cut where there is most space...
843 OptRect separate(Rect const &a, Rect const &b){
844 Rect ret ( Interval( -infinity(), infinity() ) , Interval(-infinity(), infinity() ) );
845 double gap = 0;
846 unsigned dir = 4;
847 if (b[X].min() - a[X].max() > gap){
848 gap = b[X].min() - a[X].max();
849 dir = 0;
850 }
851 if (a[X].min() - b[X].max() > gap){
852 gap = a[X].min() - b[X].max();
853 dir = 1;
854 }
855 if (b[Y].min() - a[Y].max() > gap){
856 gap = b[Y].min() - a[Y].max();
857 dir = 2;
858 }
859 if (a[Y].min() - b[Y].max() > gap){
860 gap = a[Y].min() - b[Y].max();
861 dir = 3;
862 }
863 switch (dir) {
864 case 0: ret[X].setMax(( a.max()[X] + b.min()[X] )/ 2); break;
865 case 1: ret[X].setMin(( b.max()[X] + a.min()[X] )/ 2); break;
866 case 2: ret[Y].setMax(( a.max()[Y] + b.min()[Y] )/ 2); break;
867 case 3: ret[Y].setMin(( b.max()[Y] + a.min()[Y] )/ 2); break;
868 case 4: return OptRect();
869 }
870 return OptRect(ret);
871 }
872
873 //Find 4 lines (returned as a Rect sides) that cut all the rays (=edges). *!* some side might be infinite.
874 OptRect isolateVertex(Rect const &box){
875 OptRect sep ( Interval( -infinity(), infinity() ) , Interval(-infinity(), infinity() ) );
876 //separate this vertex from the others. Find a better way.
877 for (auto & vtxboxe : vtxboxes){
878 if ( Point::LexLessRt(dim)( sep->max(), vtxboxe.min() ) ){
879 break;
880 }
881 if ( vtxboxe!=box ){//&& !vtxboxes[i].intersects(box) ){
882 OptRect sepi = separate(box, vtxboxe);
883 if ( sep && sepi ){
884 sep = intersect(*sep, *sepi);
885 }else{
886 std::cout<<"box="<<box<<"\n";
887 std::cout<<"vtxboxes[i]="<<vtxboxe<<"\n";
888 assert(sepi);
889 }
890 }
891 }
892 if (!sep) THROW_EXCEPTION("Invalid intersection data.");
893 return sep;
894 }
895
896 //TODO: argh... rewrite to have "dim"=min first place.
897 struct ExitPoint{
898 public:
899 unsigned side; //0:y=min; 1:x=max; 2:y=max; 3:x=min.
900 double place; //x or y value on the exit line.
901 unsigned ray_idx;
902 double time; //exit time on curve.
903 ExitPoint(){}
904 ExitPoint(unsigned s, double p, unsigned r, double t){
905 side =s;
906 place = p;
907 ray_idx = r;
908 time = t;
909 }
910 };
911
912
913 class ExitOrder{
914 public:
915 bool operator()(Ray a, Ray b) const {
916 if ( a.exit_side < b.exit_side ) return true;
917 if ( a.exit_side > b.exit_side ) return false;
918 if ( a.exit_side <= 1) {
919 return ( a.exit_place < b.exit_place );
920 }
921 return ( a.exit_place > b.exit_place );
922 }
923 };
924
925 void printRay(Ray const &r){
926 std::printf("Ray: tile=%u, centrifuge=%u, side=%u, place=%f\n",
927 r.tile, r.centrifuge, r.exit_side, r.exit_place);
928 }
929 void printVertex(FatVertex const &v){
930 std::printf("Vertex: [%f,%f]x[%f,%f]\n", v[X].min(),v[X].max(),v[Y].min(),v[Y].max() );
931 for (const auto & ray : v.rays){
932 printRay(ray);
933 }
934 }
935
936 //TODO: use a partial order on input coming from the context + Try quadrant order just in case it's enough.
937 //TODO: use monotonic assumption.
938 void sortRays( FatVertex &v ){
939 OptRect sep = isolateVertex(v);
940
941 for (unsigned i=0; i < v.rays.size(); i++){
942 v.rays[i].centrifuge = (tiles_data[ v.rays[i].tile ].fbox == v);
943 v.rays[i].exit_time = v.rays[i].centrifuge ? 1 : 0 ;
944 }
945
946 for (unsigned i=0; i < v.rays.size(); i++){
947 //TODO: don't convert each time!!!
948 assert( v.rays[i].tile < tiles_data.size() );
949 D2<SBasis> c = tileToSB( tiles_data[ v.rays[i].tile ] );
950
951 for (unsigned side=0; side<4; side++){//scan X or Y direction, on level min or max...
952 double level = sep->corner( side )[1-side%2];
953 if (level != infinity() && level != -infinity() ){
954 std::vector<double> times = roots(c[1-side%2]-level);
955 if ( times.size() > 0 ) {
956 double t;
957 assert( v.rays[i].tile < tiles_data.size() );
958 if (tiles_data[ v.rays[i].tile ].fbox == v){
959 t = times.front();
960 if ( v.rays[i].exit_side > 3 || v.rays[i].exit_time > t ){
961 v.rays[i].setExitInfo( side, c[side%2](t), t);
962 }
963 }else{
964 t = times.back();
965 if ( v.rays[i].exit_side > 3 || v.rays[i].exit_time < t ){
966 v.rays[i].setExitInfo( side, c[side%2](t), t);
967 }
968 }
969 }
970 }
971 }
972 }
973
974 //Rk: at this point, side == 4 means the edge is contained in the intersection box (?)...;
975 std::sort( v.rays.begin(), v.rays.end(), ExitOrder() );
976 }
977
978
979 //-------------------------------
980 //-- initialize all data.
981 //-------------------------------
982
983 Sweeper(){}
984 Sweeper(PathVector const &input_paths, Dim2 sweep_dir, double tolerance=1e-5){
985 paths = input_paths;//use a ptr...
986 dim = sweep_dir;
987 tol = tolerance;
988
989 //split paths into monotonic tiles
990 createMonotonicTiles();
991
992 //split at tiles intersections
993 splitIntersectingTiles();
994
995 //handle overlapping end boxes/and tiles traversing boxes.
996 do{
997 vtxboxes = collectBoxes();
998 }while ( splitTilesThroughFatPoints(vtxboxes) );
999
1000 enlargeTilesEnds(vtxboxes);
1001
1002 //now create the pointers to the tiles.
1003 tiles = std::vector<unsigned>(tiles_data.size(), 0);
1004 for (unsigned i=0; i<tiles_data.size(); i++){
1005 tiles[i] = i;
1006 }
1007 sortTiles();
1008
1009 //initialize the context.
1010 if (tiles_data.size()>0){
1011 context.clear();
1012 context.last_pos = tiles_data[tiles.front()].min();
1013 context.last_pos[dim] -= 1;
1014 context.pending_vertex = FatVertex();
1015 context.pending_event = Event();
1016 }
1017
1018// std::printf("Sweeper initialized (%u tiles)\n", tiles_data.size());
1019 }
1020
1021
1022 //-------------------------------
1023 //-- Event walk.
1024 //-------------------------------
1025
1026
1027 Event getNextEvent(){
1028// std::printf("getNextEvent():\n");
1029
1030// std::printf("initial contex:\n");
1031// printContext();
1032 Event old_event = context.pending_event, event;
1033// std::printf("apply old event\n");
1034 applyEvent(context.pending_event);
1035// printContext();
1036
1037 if (context.pending_vertex.rays.size()== 0){
1038// std::printf("cook up a new vertex\n");
1039
1040 //find the edges at the next vertex.
1041 //TODO: implement this as a lower bound!!
1042 std::vector<unsigned>::iterator low, high;
1043 //Warning: bad looking test, but make sure we advance even in case of 0 width boxes...
1044 for ( low = tiles.begin(); low != tiles.end() &&
1045 ( tiles_data[*low].state==1 || Point::LexLessRt(dim)(tiles_data[*low].cur_box().min(), context.last_pos) ); low++){}
1046
1047 if ( low == tiles.end() ){
1048// std::printf("no more event found\n");
1049 return(Event());
1050 }
1051 Rect pos = tiles_data[ *low ].cur_box();
1052 context.last_pos = pos.min();
1053 context.last_pos[1-dim] = pos.max()[1-dim];
1054
1055// printContext();
1056// std::printf("purgeDeadTiles\n");
1057 purgeDeadTiles();
1058// printContext();
1059
1060 FatVertex v(pos);
1061 high = low;
1062 do{
1063// v.rays.push_back( Ray(*high, !tiles_data[*high].open) );
1064 v.rays.push_back( Ray(*high, tiles_data[*high].state!=0) );
1065 high++;
1066 }while( high != tiles.end() && tiles_data[ *high ].cur_box()==pos );
1067
1068// std::printf("sortRays\n");
1069 sortRays(v);
1070
1071 //Look for an opened tile
1072 unsigned i=0;
1073
1074 for( i=0; i<v.rays.size(); ++i){assert( v.rays[i].tile<tiles_data.size() );}
1075// for( i=0; i<v.rays.size() && !tiles_data[ v.rays[i].tile ].open; ++i){}
1076 for( i=0; i<v.rays.size() && tiles_data[ v.rays[i].tile ].state!=0; ++i){}
1077
1078 //if there are only openings:
1079 if (i == v.rays.size() ){
1080// std::printf("only openings!\n");
1081 event.insert_at = contextRanking(pos.min());
1082 }
1083 //if there is at least one closing: make it first, and catch 'insert_at'.
1084 else{
1085// std::printf("not only openings\n");
1086 if( i > 0 ){
1087 std::vector<Ray> head;
1088 head.assign ( v.rays.begin(), v.rays.begin()+i );
1089 v.rays.erase ( v.rays.begin(), v.rays.begin()+i );
1090 v.rays.insert( v.rays.end(), head.begin(), head.end());
1091 }
1092 //assert( tiles_data[ v.rays[0].tile ].open );
1093 assert( tiles_data[ v.rays[0].tile ].state==0 );
1094 event.insert_at = context.find( v.rays.front().tile )+1;
1095// event.erase_at = context.find( v.rays.front().tile );
1096// std::printf("at least one closing!\n");
1097 }
1098 context.pending_vertex = v;
1099 }else{
1100// std::printf("continue biting exiting vertex\n");
1101 event.tile = context.pending_vertex.rays.front().tile;
1102 event.insert_at = old_event.insert_at;
1103// if (old_event.opening){
1104// event.insert_at++;
1105// }else{
1106// if (old_event.erase_at < old_event.insert_at){
1107// event.insert_at--;
1108// }
1109// }
1110 event.insert_at++;
1111 }
1112 event.tile = context.pending_vertex.rays.front().tile;
1113// event.opening = !tiles_data[ event.tile ].open;
1114 event.opening = tiles_data[ event.tile ].state!=0;
1115 event.to_be_continued = context.pending_vertex.rays.size()>1;
1116// if ( !event.opening ) event.erase_at = context.find(event.tile);
1117
1118 context.pending_vertex.rays.erase(context.pending_vertex.rays.begin());
1119 context.pending_event = event;
1120// printEvent(event);
1121 return event;
1122 }
1123};
1124
1125
1126/*
1127 Local Variables:
1128 mode:c++
1129 c-file-style:"stroustrup"
1130 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1131 indent-tabs-mode:nil
1132 fill-column:99
1133 End:
1134*/
1135// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=4:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Defines the different types of exceptions that 2geom can throw.
Path - a sequence of contiguous curves.
Basic intersection routines.
std::vector< Tile > tiles
Definition canvas.cpp:191
Abstract continuous curve on a plane defined on [0,1].
Definition curve.h:78
virtual D2< SBasis > toSBasis() const =0
Convert the curve to a symmetric power basis polynomial.
Adaptor that creates 2D functions from 1D ones.
Definition d2.h:55
D2< SBasis > toSBasis() const
Definition d2.h:148
constexpr C extent() const
constexpr C min() const
bool intersects(GenericRect< C > const &r) const
Check whether the rectangles have any common points.
CInterval f[2]
void unionWith(CRect const &b)
Enlarge the rectangle to contain the argument.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
CPoint max() const
Get the corner of the rectangle with largest coordinate values.
Range of real numbers that is never empty.
Definition interval.h:59
Axis-aligned rectangle that can be empty.
Definition rect.h:203
Sequence of subpaths.
Definition pathvector.h:122
Two-dimensional point that doubles as a vector.
Definition point.h:66
Straight ray from a specific point to infinity.
Definition ray.h:53
Axis aligned, non-empty rectangle.
Definition rect.h:92
Generic sweepline algorithm.
Definition sweeper.h:93
Css & result
Geom::IntPoint size
double c[8][4]
constexpr Coord lerp(Coord t, Coord a, Coord b)
Numerically stable linear interpolation.
Definition coord.h:97
Dim2
2D axis enumeration (X or Y).
Definition coord.h:48
constexpr Coord infinity()
Get a value representing infinity.
Definition coord.h:88
constexpr Coord EPSILON
Default "acceptably small" value.
Definition coord.h:84
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Geom::Point end
vector< vector< Point > > paths
Definition metro.cpp:36
size_t v
Various utility functions.
Definition affine.h:22
MultiDegree< n > max(MultiDegree< n > const &p, MultiDegree< n > const &q)
Returns the maximal degree appearing in the two arguments for each variables.
Definition sbasisN.h:158
bool contains(Path const &p, Point const &i, bool evenodd=true)
std::vector< double > roots(SBasis const &s)
std::vector< SmashIntersection > monotonic_smash_intersect(D2< SBasis > const &a, D2< SBasis > const &b, double tol)
Bezier portion(const Bezier &a, double from, double to)
Definition bezier.cpp:250
Bezier derivative(Bezier const &a)
Definition bezier.cpp:282
Piecewise< SBasis > min(SBasis const &f, SBasis const &g)
Return the more negative of the two functions pointwise.
SBasis toSBasis(SBasisN< 1 > f)
Definition sbasisN.h:640
bool are_near(Affine const &a1, Affine const &a2, Coord eps=EPSILON)
std::vector< Point > intersect(const xAx &C1, const xAx &C2)
Definition conicsec.cpp:361
std::unique_ptr< SPDocument > open(Extension *key, char const *filename, bool is_importing)
This is a generic function to use the open function of a module (including Autodetect)
Definition system.cpp:66
constexpr float pi
Definition ok-color.cpp:37
STL namespace.
PathVector - a sequence of subpaths.
Definition curve.h:24