several small fixes in polygon code
[swftools.git] / lib / gfxpoly / poly.c
blobca81be1d57e54d06b0f2712b174c2a4634736e0f
1 #include <stdlib.h>
2 #include <memory.h>
3 #include <math.h>
4 #include "../mem.h"
5 #include "../types.h"
6 #include "../q.h"
7 #include "../MD5.h"
8 #include "poly.h"
9 #include "active.h"
10 #include "xrow.h"
11 #include "wind.h"
13 static gfxpoly_t*current_polygon = 0;
14 void gfxpoly_fail(char*expr, char*file, int line, const char*function)
16 if(!current_polygon) {
17 fprintf(stderr, "assert(%s) failed in %s in line %d: %s\n", expr, file, line, function);
18 exit(1);
21 void*md5 = init_md5();
23 edge_t* s = current_polygon->edges;
24 while(s) {
25 update_md5(md5, (unsigned char*)&s->a.x, sizeof(s->a.x));
26 update_md5(md5, (unsigned char*)&s->a.y, sizeof(s->a.y));
27 update_md5(md5, (unsigned char*)&s->b.x, sizeof(s->b.x));
28 update_md5(md5, (unsigned char*)&s->b.y, sizeof(s->b.y));
29 s = s->next;
31 unsigned char h[16];
32 char filename[32+4+1];
33 finish_md5(md5, h);
34 sprintf(filename, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x.ps",
35 h[0],h[1],h[2],h[3],h[4],h[5],h[6],h[7],h[8],h[9],h[10],h[11],h[12],h[13],h[14],h[15]);
37 fprintf(stderr, "assert(%s) failed in %s in line %d: %s\n", expr, file, line, function);
38 fprintf(stderr, "I'm saving a debug file \"%s\" to the current directory.\n", filename);
40 gfxpoly_save(current_polygon, filename);
41 exit(1);
44 static char point_equals(const void*o1, const void*o2)
46 const point_t*p1 = o1;
47 const point_t*p2 = o2;
48 return p1->x == p2->x && p1->y == p2->y;
50 static unsigned int point_hash(const void*o)
52 const point_t*p = o;
53 return p->x^p->y;
55 static void* point_dup(const void*o)
57 const point_t*p = o;
58 point_t*n = malloc(sizeof(point_t));
59 n->x = p->x;
60 n->y = p->y;
61 return n;
63 static void point_free(void*o)
65 point_t*p = o;
66 p->x = 0;
67 p->y = 0;
68 free(p);
70 static type_t point_type = {
71 equals: point_equals,
72 hash: point_hash,
73 dup: point_dup,
74 free: point_free,
77 typedef struct _status {
78 int32_t y;
79 actlist_t*actlist;
80 heap_t*queue;
81 edge_t*output;
82 xrow_t*xrow;
83 windrule_t*windrule;
84 windcontext_t*context;
85 segment_t*ending_segments;
86 #ifdef CHECKS
87 dict_t*seen_crossings; //list of crossing we saw so far
88 dict_t*intersecting_segs; //list of segments intersecting in this scanline
89 dict_t*segs_with_point; //lists of segments that received a point in this scanline
90 #endif
91 } status_t;
93 /* compare_events_simple differs from compare_events in that it schedules
94 events from left to right regardless of type. It's only used in horizontal
95 processing, in order to get an x-wise sorting of the current scanline */
96 static int compare_events_simple(const void*_a,const void*_b)
98 event_t* a = (event_t*)_a;
99 event_t* b = (event_t*)_b;
100 int d = b->p.y - a->p.y;
101 if(d) return d;
102 d = b->p.x - a->p.x;
103 if(d) return d;
104 return 0;
107 static int compare_events(const void*_a,const void*_b)
109 event_t* a = (event_t*)_a;
110 event_t* b = (event_t*)_b;
111 int d = b->p.y - a->p.y;
112 if(d) return d;
113 /* we need to schedule end after intersect (so that a segment about
114 to end has a chance to tear up a few other segs first) and start
115 events after end (in order not to confuse the intersection check, which
116 assumes there's an actual y overlap between active segments, and
117 because ending segments in the active list make it difficult to insert
118 starting segments at the right position)).
119 Horizontal lines come last, because the only purpose
120 they have is to create snapping coordinates for the segments (still)
121 existing in this scanline.
123 d = b->type - a->type;
124 if(d) return d;
125 return 0;
127 /* I don't see any reason why we would need to order by x- at least as long
128 as we do horizontal lines in a seperate pass */
129 //d = b->p.x - a->p.x;
130 //return d;
133 gfxpoly_t* gfxpoly_new(double gridsize)
135 gfxpoly_t*p = (gfxpoly_t*)rfx_calloc(sizeof(gfxpoly_t));
136 p->gridsize = gridsize;
137 return p;
139 void gfxpoly_destroy(gfxpoly_t*poly)
141 edge_t* s = poly->edges;
142 while(s) {
143 edge_t*next = s->next;
144 free(s);
145 s = next;
147 free(poly);
149 int gfxpoly_size(gfxpoly_t*poly)
151 edge_t* s = poly->edges;
152 int t=0;
153 while(s) {
154 s = s->next;t++;
156 return t;
159 char gfxpoly_check(gfxpoly_t*poly)
161 edge_t* s = poly->edges;
162 dict_t*d = dict_new2(&point_type);
163 while(s) {
164 if(!dict_contains(d, &s->a)) {
165 dict_put(d, &s->a, (void*)(ptroff_t)1);
166 } else {
167 int count = (ptroff_t)dict_lookup(d, &s->a);
168 dict_del(d, &s->a);
169 count++;
170 dict_put(d, &s->a, (void*)(ptroff_t)count);
172 if(!dict_contains(d, &s->b)) {
173 dict_put(d, &s->b, (void*)(ptroff_t)1);
174 } else {
175 int count = (ptroff_t)dict_lookup(d, &s->b);
176 dict_del(d, &s->b);
177 count++;
178 dict_put(d, &s->b, (void*)(ptroff_t)count);
180 s = s->next;
182 DICT_ITERATE_ITEMS(d, point_t*, p, void*, c) {
183 int count = (ptroff_t)c;
184 if(count&1) {
185 fprintf(stderr, "Point (%f,%f) occurs %d times\n", p->x*poly->gridsize, p->y*poly->gridsize, count);
186 dict_destroy(d);
187 return 0;
190 dict_destroy(d);
191 return 1;
194 void gfxpoly_dump(gfxpoly_t*poly)
196 edge_t* s = poly->edges;
197 double g = poly->gridsize;
198 fprintf(stderr, "polyon %08x (gridsize: %f)\n", poly, poly->gridsize);
199 while(s) {
200 fprintf(stderr, "(%f,%f) -> (%f,%f)\n", s->a.x*g, s->a.y*g, s->b.x*g, s->b.y*g);
201 s = s->next;
205 gfxpoly_t* gfxpoly_save(gfxpoly_t*poly, const char*filename)
207 FILE*fi = fopen(filename, "wb");
208 fprintf(fi, "%% gridsize %f\n", poly->gridsize);
209 fprintf(fi, "%% begin\n");
210 edge_t* s = poly->edges;
211 while(s) {
212 fprintf(fi, "%g setgray\n", s->b.y < s->a.y ? 0.7 : 0);
213 fprintf(fi, "%d %d moveto\n", s->a.x, s->a.y);
214 fprintf(fi, "%d %d lineto\n", s->b.x, s->b.y);
215 fprintf(fi, "stroke\n");
216 s = s->next;
218 fprintf(fi, "showpage\n");
219 fclose(fi);
222 inline static event_t event_new()
224 event_t e;
225 memset(&e, 0, sizeof(e));
226 return e;
229 static void event_dump(event_t*e)
231 if(e->type == EVENT_HORIZONTAL) {
232 fprintf(stderr, "Horizontal [%d] (%d,%d) -> (%d,%d)\n", e->s1->nr, e->s1->a.x, e->s1->a.y, e->s1->b.x, e->s1->b.y);
233 } else if(e->type == EVENT_START) {
234 fprintf(stderr, "event: segment [%d] starts at (%d,%d)\n", e->s1->nr, e->p.x, e->p.y);
235 } else if(e->type == EVENT_END) {
236 fprintf(stderr, "event: segment [%d] ends at (%d,%d)\n", e->s1->nr, e->p.x, e->p.y);
237 } else if(e->type == EVENT_CROSS) {
238 fprintf(stderr, "event: segment [%d] and [%d] intersect at (%d,%d)\n", e->s1->nr, e->s2->nr, e->p.x, e->p.y);
239 } else if(e->type == EVENT_CORNER) {
240 fprintf(stderr, "event: segment [%d] ends, segment [%d] starts, at (%d,%d)\n", e->s1->nr, e->s2->nr, e->p.x, e->p.y);
241 } else {
242 assert(0);
246 static inline max32(int32_t v1, int32_t v2) {return v1>v2?v1:v2;}
247 static inline min32(int32_t v1, int32_t v2) {return v1<v2?v1:v2;}
249 static void segment_dump(segment_t*s)
251 fprintf(stderr, "[%d] (%d,%d)->(%d,%d) ", s->nr, s->a.x, s->a.y, s->b.x, s->b.y);
252 fprintf(stderr, " dx:%d dy:%d k:%f dx/dy=%f\n", s->delta.x, s->delta.y, s->k,
253 (double)s->delta.x / s->delta.y);
256 static void segment_init(segment_t*s, int32_t x1, int32_t y1, int32_t x2, int32_t y2, int polygon_nr)
258 if(y1<y2) {
259 s->dir = DIR_DOWN;
260 } else if(y1>y2) {
261 int32_t x = x1;x1=x2;x2=x;
262 int32_t y = y1;y1=y2;y2=y;
263 s->dir = DIR_UP;
264 } else {
265 /* up/down for horizontal segments is handled by "rotating"
266 them 90° anticlockwise in screen coordinates (tilt your head to
267 the right) */
268 s->dir = DIR_UP;
269 if(x1>x2) {
270 s->dir = DIR_DOWN;
271 int32_t x = x1;x1=x2;x2=x;
272 int32_t y = y1;y1=y2;y2=y;
275 s->a.x = x1;
276 s->a.y = y1;
277 s->b.x = x2;
278 s->b.y = y2;
279 s->k = (double)x1*y2-(double)x2*y1;
280 s->left = s->right = 0;
281 s->delta.x = x2-x1;
282 s->delta.y = y2-y1;
283 s->minx = min32(x1,x2);
284 s->maxx = max32(x1,x2);
286 s->pos = s->a;
287 s->polygon_nr = polygon_nr;
288 #define XDEBUG
289 #ifdef XDEBUG
290 static int segment_count=0;
291 s->nr = segment_count++;
292 #endif
294 #ifdef CHECKS
295 assert(LINE_EQ(s->a, s) == 0);
296 assert(LINE_EQ(s->b, s) == 0);
298 /* check that all signs are in order:
300 |\ /|
301 | \ / |
302 minx-b b--maxx
303 < 0 > 0
305 point_t p = s->b;
306 p.x = min32(s->a.x, s->b.x);
307 assert(LINE_EQ(p, s) <= 0);
308 p.x = max32(s->a.x, s->b.x);
309 assert(LINE_EQ(p, s) >= 0);
310 #endif
312 dict_init2(&s->scheduled_crossings, &ptr_type, 0);
315 static segment_t* segment_new(int32_t x1, int32_t y1, int32_t x2, int32_t y2, int polygon_nr)
317 segment_t*s = (segment_t*)rfx_calloc(sizeof(segment_t));
318 segment_init(s, x1, y1, x2, y2, polygon_nr);
319 return s;
322 static void segment_destroy(segment_t*s)
324 dict_clear(&s->scheduled_crossings);
325 free(s);
328 static void gfxpoly_enqueue(edge_t*list, heap_t*queue, int polygon_nr)
330 edge_t*l;
331 for(l=list;l;l=l->next) {
332 if(l->a.x == l->b.x &&
333 l->a.y == l->b.y) {
334 fprintf(stderr, "Warning: intersector input contains zero-length segments\n");
335 continue;
337 segment_t*s = segment_new(l->a.x, l->a.y, l->b.x, l->b.y, polygon_nr);
338 #ifdef DEBUG
339 if(l->tmp)
340 s->nr = l->tmp;
341 fprintf(stderr, "[%d] (%d,%d) -> (%d,%d) %s\n",
342 s->nr, s->a.x, s->a.y, s->b.x, s->b.y,
343 s->dir==DIR_UP?"up":"down");
344 #endif
345 event_t e = event_new();
346 e.type = s->delta.y ? EVENT_START : EVENT_HORIZONTAL;
347 e.p = s->a;
348 e.s1 = s;
349 e.s2 = 0;
350 heap_put(queue, &e);
354 static void schedule_endpoint(status_t*status, segment_t*s)
356 // schedule end point of segment
357 assert(s->b.y > status->y);
358 event_t e;
359 e.type = EVENT_END;
360 e.p = s->b;
361 e.s1 = s;
362 e.s2 = 0;
363 heap_put(status->queue, &e);
366 static void schedule_crossing(status_t*status, segment_t*s1, segment_t*s2)
368 /* the code that's required (and the checks you can perform) before
369 it can be said with 100% certainty that we indeed have a valid crossing
370 amazes me every time. -mk */
371 #ifdef CHECKS
372 assert(s1!=s2);
373 assert(s1->right == s2);
374 assert(s2->left == s1);
375 int32_t miny1 = min32(s1->a.y,s1->b.y);
376 int32_t maxy1 = max32(s1->a.y,s1->b.y);
377 int32_t miny2 = min32(s2->a.y,s2->b.y);
378 int32_t maxy2 = max32(s2->a.y,s2->b.y);
379 int32_t minx1 = min32(s1->a.x,s1->b.x);
380 int32_t minx2 = min32(s2->a.x,s2->b.x);
381 int32_t maxx1 = max32(s1->a.x,s1->b.x);
382 int32_t maxx2 = max32(s2->a.x,s2->b.x);
383 /* check that precomputation is sane */
384 assert(minx1 == s1->minx && minx2 == s2->minx);
385 assert(maxx1 == s1->maxx && maxx2 == s2->maxx);
386 /* both segments are active, so this can't happen */
387 assert(!(maxy1 <= miny2 || maxy2 <= miny1));
388 /* we know that right now, s2 is to the right of s1, so there's
389 no way the complete bounding box of s1 is to the right of s1 */
390 assert(!(s1->minx > s2->maxx));
391 assert(s1->minx != s2->maxx || (!s1->delta.x && !s2->delta.x));
392 #endif
394 if(s1->maxx <= s2->minx) {
395 /* bounding boxes don't intersect */
396 return;
399 if(dict_contains(&s1->scheduled_crossings, s2)) {
400 /* FIXME: this whole segment hashing thing is really slow */
401 //fprintf(stderr, "Encountered crossing between [%d] and [%d] twice\n", s1->nr, s2->nr);
402 return; // we already know about this one
405 double det = (double)s1->delta.x*s2->delta.y - (double)s1->delta.y*s2->delta.x;
406 if(!det) {
407 if(s1->k == s2->k) {
408 // lines are exactly on top of each other (ignored)
409 #ifdef DEBUG
410 fprintf(stderr, "Notice: segments [%d] and [%d] are exactly on top of each other\n", s1->nr, s2->nr);
411 #endif
412 return;
413 } else {
414 /* lines are parallel */
415 return;
418 double asign2 = LINE_EQ(s1->a, s2);
419 double bsign2 = LINE_EQ(s1->b, s2);
420 if(asign2<0 && bsign2<0) {
421 // segment1 is completely to the left of segment2
422 return;
424 if(asign2>0 && bsign2>0) {
425 // segment2 is completely to the left of segment1
426 return;
428 if(asign2==0) {
429 // segment1 touches segment2 in a single point (ignored)
430 #ifdef DEBUG
431 fprintf(stderr, "Notice: segment [%d]'s start point touches segment [%d]\n", s1->nr, s2->nr);
432 #endif
433 return;
435 if(bsign2==0) {
436 // segment1 touches segment2 in a single point (ignored)
437 #ifdef DEBUG
438 fprintf(stderr, "Notice: segment [%d]'s end point touches segment [%d]\n", s1->nr, s2->nr);
439 #endif
440 return;
442 double asign1 = LINE_EQ(s2->a, s1);
443 double bsign1 = LINE_EQ(s2->b, s1);
444 if(asign1<0 && bsign1<0) {
445 // segment1 is completely to the left of segment2
446 return;
448 if(asign1>0 && bsign1>0) {
449 // segment2 is completely to the left of segment1
450 return;
452 if(asign1==0) {
453 // segment2 touches segment1 in a single point (ignored)
454 #ifdef DEBUG
455 fprintf(stderr, "Notice: segment [%d]'s start point touches segment [%d]\n", s2->nr, s1->nr);
456 #endif
457 return;
459 if(asign2==0) {
460 // segment2 touches segment1 in a single point (ignored)
461 #ifdef DEBUG
462 fprintf(stderr, "Notice: segment [%d]'s end point touches segment [%d]\n", s2->nr, s1->nr);
463 #endif
464 return;
467 /* TODO: should we precompute these? */
468 double la = (double)s1->a.x*(double)s1->b.y - (double)s1->a.y*(double)s1->b.x;
469 double lb = (double)s2->a.x*(double)s2->b.y - (double)s2->a.y*(double)s2->b.x;
471 point_t p;
472 p.x = (int32_t)ceil((-la*s2->delta.x + lb*s1->delta.x) / det);
473 p.y = (int32_t)ceil((+lb*s1->delta.y - la*s2->delta.y) / det);
475 assert(p.y >= status->y);
476 #ifdef CHECKS
477 assert(p.x >= s1->minx && p.x <= s1->maxx);
478 assert(p.x >= s2->minx && p.x <= s2->maxx);
480 point_t pair;
481 pair.x = s1->nr;
482 pair.y = s2->nr;
483 assert(!dict_contains(status->seen_crossings, &pair));
484 dict_put(status->seen_crossings, &pair, 0);
485 #endif
486 #ifdef DEBUG
487 fprintf(stderr, "schedule crossing between [%d] and [%d] at (%d,%d)\n", s1->nr, s2->nr, p.x, p.y);
488 #endif
490 /* we insert into each other's intersection history because these segments might switch
491 places and we still want to look them up quickly after they did */
492 dict_put(&s1->scheduled_crossings, s2, 0);
493 dict_put(&s2->scheduled_crossings, s1, 0);
495 event_t e = event_new();
496 e.type = EVENT_CROSS;
497 e.p = p;
498 e.s1 = s1;
499 e.s2 = s2;
500 heap_put(status->queue, &e);
501 return;
504 static void exchange_two(status_t*status, event_t*e)
506 //exchange two segments in list
507 segment_t*s1 = e->s1;
508 segment_t*s2 = e->s2;
509 #ifdef CHECKS
510 if(!dict_contains(status->intersecting_segs, s1))
511 dict_put(status->intersecting_segs, s1, 0);
512 if(!dict_contains(status->intersecting_segs, s2))
513 dict_put(status->intersecting_segs, s2, 0);
514 #endif
515 assert(s2->left == s1);
516 assert(s1->right == s2);
517 actlist_swap(status->actlist, s1, s2);
518 assert(s2->right == s1);
519 assert(s1->left == s2);
520 segment_t*left = s2->left;
521 segment_t*right = s1->right;
522 if(left)
523 schedule_crossing(status, left, s2);
524 if(right)
525 schedule_crossing(status, s1, right);
528 typedef struct _box {
529 point_t left1, left2, right1, right2;
530 } box_t;
531 static inline box_t box_new(int32_t x, int32_t y)
533 box_t box;
534 box.right1.x = box.right2.x = x;
535 box.left1.x = box.left2.x = x-1;
536 box.left1.y = box.right1.y = y-1;
537 box.left2.y = box.right2.y = y;
538 return box;
542 static void insert_point_into_segment(status_t*status, segment_t*s, point_t p)
544 assert(s->pos.x != p.x || s->pos.y != p.y);
546 #ifdef CHECKS
547 if(!dict_contains(status->segs_with_point, s))
548 dict_put(status->segs_with_point, s, 0);
549 assert(s->fs_out_ok);
550 #endif
552 if(s->fs_out) {
553 #ifdef DEBUG
554 fprintf(stderr, "[%d] receives next point (%d,%d)->(%d,%d) (drawing)\n", s->nr,
555 s->pos.x, s->pos.y, p.x, p.y);
556 #endif
557 // omit horizontal lines
558 if(s->pos.y != p.y) {
559 edge_t*e = rfx_calloc(sizeof(edge_t));
560 #ifdef DEBUG
561 e->tmp = s->nr;
562 #endif
563 e->a = s->pos;
564 e->b = p;
565 assert(e->a.y != e->b.y);
566 e->next = status->output;
567 status->output = e;
569 } else {
570 #ifdef DEBUG
571 fprintf(stderr, "[%d] receives next point (%d,%d) (omitting)\n", s->nr, p.x, p.y);
572 #endif
574 s->pos = p;
577 typedef struct _segrange {
578 double xmin;
579 segment_t*segmin;
580 double xmax;
581 segment_t*segmax;
582 } segrange_t;
584 static void segrange_adjust_endpoints(segrange_t*range, int32_t y)
586 #define XPOS_EQ(s1,s2,ypos) (XPOS((s1),(ypos))==XPOS((s2),(ypos)))
587 segment_t*min = range->segmin;
588 segment_t*max = range->segmax;
590 /* we need this because if two segments intersect exactly on
591 the scanline, segrange_test_segment_{min,max} can't tell which
592 one is smaller/larger */
593 if(min) while(min->left && XPOS_EQ(min, min->left, y)) {
594 min = min->left;
596 if(max) while(max->right && XPOS_EQ(max, max->right, y)) {
597 max = max->right;
599 range->segmin = min;
600 range->segmax = max;
602 static void segrange_test_segment_min(segrange_t*range, segment_t*seg, int32_t y)
604 if(!seg) return;
605 /* we need to calculate the xpos anew (and can't use start coordinate or
606 intersection coordinate), because we need the xpos exactly at the end of
607 this scanline.
609 double x = XPOS(seg, y);
610 if(!range->segmin || x<range->xmin) {
611 range->segmin = seg;
612 range->xmin = x;
615 static void segrange_test_segment_max(segrange_t*range, segment_t*seg, int32_t y)
617 if(!seg) return;
618 double x = XPOS(seg, y);
619 if(!range->segmax || x>range->xmax) {
620 range->segmax = seg;
621 range->xmax = x;
626 SLOPE_POSITIVE:
627 \+ \ +
628 ------ I \I
629 -I\---- I
630 I \ --I\---
631 I \ I \ -------
632 + \ + \
634 static void add_points_to_positively_sloped_segments(status_t*status, int32_t y, segrange_t*range)
636 segment_t*first=0, *last = 0;
637 int t;
638 for(t=0;t<status->xrow->num;t++) {
639 box_t box = box_new(status->xrow->x[t], y);
640 segment_t*seg = actlist_find(status->actlist, box.left2, box.left2);
642 seg = actlist_right(status->actlist, seg);
643 while(seg) {
644 if(seg->a.y == y) {
645 // this segment started in this scanline, ignore it
646 seg->changed = 1;last = seg;if(!first) {first=seg;}
647 } else if(seg->delta.x <= 0) {
648 // ignore segment w/ negative slope
649 } else {
650 last = seg;if(!first) {first=seg;}
651 double d1 = LINE_EQ(box.right1, seg);
652 double d2 = LINE_EQ(box.right2, seg);
653 if(d1>0 || d2>=0) {
654 seg->changed = 1;
655 insert_point_into_segment(status, seg, box.right2);
656 } else {
657 /* we unfortunately can't break here- the active list is sorted according
658 to the *bottom* of the scanline. hence pretty much everything that's still
659 coming might reach into our box */
660 //break;
663 seg = seg->right;
666 segrange_test_segment_min(range, first, y);
667 segrange_test_segment_max(range, last, y);
669 /* SLOPE_NEGATIVE:
670 | + /| + / /
671 | I / | I / /
672 | I / | I/ /
673 | I/ | I /
674 | I | /I /
675 | /+ |/ + /
677 static void add_points_to_negatively_sloped_segments(status_t*status, int32_t y, segrange_t*range)
679 segment_t*first=0, *last = 0;
680 int t;
681 for(t=status->xrow->num-1;t>=0;t--) {
682 box_t box = box_new(status->xrow->x[t], y);
683 segment_t*seg = actlist_find(status->actlist, box.right2, box.right2);
685 while(seg) {
686 if(seg->a.y == y) {
687 // this segment started in this scanline, ignore it
688 seg->changed = 1;last = seg;if(!first) {first=seg;}
689 } else if(seg->delta.x > 0) {
690 // ignore segment w/ positive slope
691 } else {
692 last = seg;if(!first) {first=seg;}
693 double d1 = LINE_EQ(box.left1, seg);
694 double d2 = LINE_EQ(box.left2, seg);
695 if(d1<0 || d2<0) {
696 seg->changed = 1;
697 insert_point_into_segment(status, seg, box.right2);
698 } else {
699 //break;
702 seg = seg->left;
705 segrange_test_segment_min(range, last, y);
706 segrange_test_segment_max(range, first, y);
709 /* segments ending in the current scanline need xrow treatment like everything else.
710 (consider an intersection taking place just above a nearly horizontal segment
711 ending on the current scanline- the intersection would snap down *below* the
712 ending segment if we don't add the intersection point to the latter right away)
713 we need to treat ending segments seperately, however. we have to delete them from
714 the active list right away to make room for intersect operations (which might
715 still be in the current scanline- consider two 45° polygons and a vertical polygon
716 intersecting on an integer coordinate). but once they're no longer in the active list,
717 we can't use the add_points_to_*_sloped_segments() functions anymore, and re-adding
718 them to the active list just for point snapping would be overkill.
719 (One other option to consider, however, would be to create a new active list only
720 for ending segments)
722 static void add_points_to_ending_segments(status_t*status, int32_t y)
724 segment_t*seg = status->ending_segments;
725 while(seg) {
726 segment_t*next = seg->right;seg->right=0;
728 assert(seg->b.y == status->y);
730 if(status->xrow->num == 1) {
731 // shortcut
732 assert(seg->b.x == status->xrow->x[0]);
733 point_t p = {status->xrow->x[0], y};
734 insert_point_into_segment(status, seg, p);
735 } else {
736 int t;
737 int start=0,end=status->xrow->num,dir=1;
738 if(seg->delta.x < 0) {
739 start = status->xrow->num-1;
740 end = dir = -1;
742 for(t=start;t!=end;t+=dir) {
743 box_t box = box_new(status->xrow->x[t], y);
744 double d0 = LINE_EQ(box.left1, seg);
745 double d1 = LINE_EQ(box.left2, seg);
746 double d2 = LINE_EQ(box.right1, seg);
747 double d3 = LINE_EQ(box.right2, seg);
748 if(!(d0>=0 && d1>=0 && d2>=0 && d3>0 ||
749 d0<=0 && d1<=0 && d2<=0 && d3<0)) {
750 insert_point_into_segment(status, seg, box.right2);
751 break;
755 #ifdef CHECKS
756 /* we *need* to find a point to insert. the segment's own end point
757 is in that list, for Pete's sake. */
758 assert(t!=end);
759 #endif
761 // now that this is done, too, we can also finally free this segment
762 segment_destroy(seg);
763 seg = next;
765 status->ending_segments = 0;
768 static void recalculate_windings(status_t*status, segrange_t*range)
770 #ifdef DEBUG
771 fprintf(stderr, "range: [%d]..[%d]\n", SEGNR(range->segmin), SEGNR(range->segmax));
772 #endif
773 segrange_adjust_endpoints(range, status->y);
775 segment_t*s = range->segmin;
776 segment_t*end = range->segmax;
777 segment_t*last = 0;
779 #ifdef DEBUG
780 s = actlist_leftmost(status->actlist);
781 while(s) {
782 fprintf(stderr, "[%d]%d%s ", s->nr, s->changed,
783 s == range->segmin?"S":(
784 s == range->segmax?"E":""));
785 s = s->right;
787 fprintf(stderr, "\n");
788 s = range->segmin;
789 #endif
790 #ifdef CHECKS
791 /* test sanity: check that we don't have changed segments
792 outside of the given range */
793 s = actlist_leftmost(status->actlist);
794 while(s && s!=range->segmin) {
795 assert(!s->changed);
796 s = s->right;
798 s = actlist_rightmost(status->actlist);
799 while(s && s!=range->segmax) {
800 assert(!s->changed);
801 s = s->left;
803 /* in check mode, go through the whole interval so we can test
804 that all polygons where the fillstyle changed also have seg->changed=1 */
805 s = actlist_leftmost(status->actlist);
806 end = 0;
807 #endif
809 if(end)
810 end = end->right;
811 while(s!=end) {
812 #ifndef CHECKS
813 if(s->changed)
814 #endif
816 segment_t* left = actlist_left(status->actlist, s);
817 windstate_t wind = left?left->wind:status->windrule->start(status->context);
818 s->wind = status->windrule->add(status->context, wind, s->fs, s->dir, s->polygon_nr);
819 fillstyle_t*fs_old = s->fs_out;
820 s->fs_out = status->windrule->diff(&wind, &s->wind);
822 assert(!(!s->changed && fs_old!=s->fs_out));
823 s->changed = 0;
825 #ifdef CHECKS
826 s->fs_out_ok = 1;
827 #endif
828 #ifdef DEBUG
829 fprintf(stderr, "[%d] %s/%d/%s/%s %s\n", s->nr, s->dir==DIR_UP?"up":"down", s->wind.wind_nr, s->wind.is_filled?"fill":"nofill", s->fs_out?"draw":"omit",
830 fs_old!=s->fs_out?"CHANGED":"");
831 #endif
833 s = s->right;
837 /* we need to handle horizontal lines in order to add points to segments
838 we otherwise would miss during the windrule re-evaluation */
839 static void intersect_with_horizontal(status_t*status, segment_t*h)
841 segment_t* left = actlist_find(status->actlist, h->a, h->a);
842 segment_t* right = actlist_find(status->actlist, h->b, h->b);
844 /* not strictly necessary, also done by the event */
845 xrow_add(status->xrow, h->a.x);
846 point_t o = h->a;
848 if(!right) {
849 assert(!left);
850 return;
853 left = actlist_right(status->actlist, left); //first seg to the right of h->a
854 right = right->right; //first seg to the right of h->b
855 segment_t* s = left;
857 while(s!=right) {
858 assert(s);
859 int32_t x = XPOS_INT(s, status->y);
860 #ifdef DEBUG
861 fprintf(stderr, "...into [%d] (%d,%d) -> (%d,%d) at (%d,%d)\n", s->nr,
862 s->a.x, s->a.y,
863 s->b.x, s->b.y,
864 x, status->y
866 #endif
867 assert(x >= h->a.x);
868 assert(x <= h->b.x);
869 assert(s->delta.x > 0 && x >= s->a.x || s->delta.x <= 0 && x <= s->a.x);
870 assert(s->delta.x > 0 && x <= s->b.x || s->delta.x <= 0 && x >= s->b.x);
871 xrow_add(status->xrow, x);
873 s = s->right;
877 static void event_apply(status_t*status, event_t*e)
879 switch(e->type) {
880 case EVENT_HORIZONTAL: {
881 #ifdef DEBUG
882 event_dump(e);
883 #endif
884 intersect_with_horizontal(status, e->s1);
885 segment_destroy(e->s1);e->s1=0;
886 break;
888 case EVENT_END: {
889 //delete segment from list
890 segment_t*s = e->s1;
891 #ifdef DEBUG
892 event_dump(e);
893 #endif
894 #ifdef CHECKS
895 dict_del(status->intersecting_segs, s);
896 dict_del(status->segs_with_point, s);
897 assert(!dict_contains(status->intersecting_segs, s));
898 assert(!dict_contains(status->segs_with_point, s));
899 #endif
900 segment_t*left = s->left;
901 segment_t*right = s->right;
902 actlist_delete(status->actlist, s);
903 if(left && right)
904 schedule_crossing(status, left, right);
906 /* schedule segment for xrow handling */
907 s->left = 0; s->right = status->ending_segments;
908 status->ending_segments = s;
909 break;
911 case EVENT_START: {
912 //insert segment into list
913 #ifdef DEBUG
914 event_dump(e);
915 #endif
916 segment_t*s = e->s1;
917 assert(e->p.x == s->a.x && e->p.y == s->a.y);
918 actlist_insert(status->actlist, s->a, s->b, s);
919 segment_t*left = s->left;
920 segment_t*right = s->right;
921 if(left)
922 schedule_crossing(status, left, s);
923 if(right)
924 schedule_crossing(status, s, right);
925 schedule_endpoint(status, e->s1);
926 break;
928 case EVENT_CROSS: {
929 // exchange two segments
930 #ifdef DEBUG
931 event_dump(e);
932 #endif
933 if(e->s1->right == e->s2) {
934 assert(e->s2->left == e->s1);
935 exchange_two(status, e);
936 } else {
937 assert(e->s2->left != e->s1);
938 #ifdef DEBUG
939 fprintf(stderr, "Ignore this crossing ([%d] not next to [%d])\n", e->s1->nr, e->s2->nr);
940 #endif
941 /* ignore this crossing for now (there are some line segments in between).
942 it'll get rescheduled as soon as the "obstacles" are gone */
943 char del1 = dict_del(&e->s1->scheduled_crossings, e->s2);
944 char del2 = dict_del(&e->s2->scheduled_crossings, e->s1);
945 assert(del1 && del2);
946 #ifdef CHECKS
947 point_t pair;
948 pair.x = e->s1->nr;
949 pair.y = e->s2->nr;
950 assert(dict_contains(status->seen_crossings, &pair));
951 dict_del(status->seen_crossings, &pair);
952 #endif
958 #ifdef CHECKS
959 static void check_status(status_t*status)
961 DICT_ITERATE_KEY(status->intersecting_segs, segment_t*, s) {
962 if((s->pos.x != s->b.x ||
963 s->pos.y != s->b.y) &&
964 !dict_contains(status->segs_with_point, s)) {
965 fprintf(stderr, "Error: segment [%d] (%sslope) intersects in scanline %d, but it didn't receive a point\n",
966 s->nr,
967 s->delta.x<0?"-":"+",
968 status->y);
969 assert(0);
973 #endif
975 static void add_horizontals(gfxpoly_t*poly, windrule_t*windrule, windcontext_t*context)
978 |..| |...........| | |
979 |..| |...........| | |
980 |..+ + +..| +--+ +--+
981 |...........| |..| | |
982 |...........| |..| | |
985 #ifdef DEBUG
986 fprintf(stderr, "========================================================================\n");
987 #endif
988 heap_t* queue = heap_new(sizeof(event_t), compare_events_simple);
989 gfxpoly_enqueue(poly->edges, queue, 0);
991 actlist_t* actlist = actlist_new();
993 event_t*e = heap_chopmax(queue);
994 while(e) {
995 int32_t y = e->p.y;
996 int32_t x = 0;
997 char fill = 0;
998 #ifdef DEBUG
999 fprintf(stderr, "----------------------------------- %d\n", y);
1000 actlist_dump(actlist, y-1);
1001 #endif
1002 #ifdef CHECKS
1003 actlist_verify(actlist, y-1);
1004 #endif
1005 do {
1006 if(fill && x != e->p.x) {
1007 #ifdef DEBUG
1008 fprintf(stderr, "%d) draw horizontal line from %d to %d\n", y, x, e->p.x);
1009 #endif
1010 assert(x<e->p.x);
1011 edge_t*l= malloc(sizeof(edge_t));
1012 l->a.y = l->b.y = y;
1013 /* we draw from low x to high x so that left/right fillstyles add up
1014 (because the horizontal line's fill style controls the area *below* the line)
1016 l->a.x = e->p.x;
1017 l->b.x = x;
1018 l->next = poly->edges;
1019 poly->edges = l;
1020 #ifdef CHECKS
1021 /* the output should always be intersection free polygons, so check this horizontal
1022 line isn't hacking through any segments in the active list */
1023 segment_t* start = actlist_find(actlist, l->b, l->b);
1024 segment_t* s = actlist_find(actlist, l->a, l->a);
1025 while(s!=start) {
1026 assert(s->a.y == y || s->b.y == y);
1027 s = s->left;
1029 #endif
1031 segment_t*left = 0;
1032 segment_t*s = e->s1;
1034 switch(e->type) {
1035 case EVENT_START: {
1036 assert(e->p.x == s->a.x && e->p.y == s->a.y);
1037 actlist_insert(actlist, s->a, s->b, s);
1038 event_t e;
1039 e.type = EVENT_END;
1040 e.p = s->b;
1041 e.s1 = s;
1042 e.s2 = 0;
1043 heap_put(queue, &e);
1044 left = actlist_left(actlist, s);
1045 break;
1047 case EVENT_END: {
1048 left = actlist_left(actlist, s);
1049 actlist_delete(actlist, s);
1050 break;
1052 default: assert(0);
1055 x = e->p.x;
1056 fill ^= 1;//(before.is_filled != after.is_filled);
1057 #ifdef DEBUG
1058 fprintf(stderr, "%d) event=%s[%d] left:[%d] x:%d\n",
1059 y, e->type==EVENT_START?"start":"end",
1060 s->nr,
1061 left?left->nr:-1,
1063 #endif
1065 if(e->type == EVENT_END)
1066 segment_destroy(s);
1068 free(e);
1069 e = heap_chopmax(queue);
1070 } while(e && y == e->p.y);
1072 assert(!fill); // check that polygon is not bleeding
1074 actlist_destroy(actlist);
1075 heap_destroy(queue);
1078 gfxpoly_t* gfxpoly_process(gfxpoly_t*poly, windrule_t*windrule, windcontext_t*context)
1080 current_polygon = poly;
1081 heap_t* queue = heap_new(sizeof(event_t), compare_events);
1083 gfxpoly_enqueue(poly->edges, queue, /*polygon nr*/0);
1085 status_t status;
1086 memset(&status, 0, sizeof(status_t));
1087 status.queue = queue;
1088 status.windrule = windrule;
1089 status.context = context;
1090 status.actlist = actlist_new();
1091 #ifdef CHECKS
1092 status.seen_crossings = dict_new2(&point_type);
1093 #endif
1095 status.xrow = xrow_new();
1097 event_t*e = heap_chopmax(queue);
1098 while(e) {
1099 status.y = e->p.y;
1100 #ifdef CHECKS
1101 status.intersecting_segs = dict_new2(&ptr_type);
1102 status.segs_with_point = dict_new2(&ptr_type);
1103 #endif
1105 #ifdef DEBUG
1106 fprintf(stderr, "----------------------------------- %d\n", status.y);
1107 actlist_dump(status.actlist, status.y-1);
1108 #endif
1109 #ifdef CHECKS
1110 actlist_verify(status.actlist, status.y-1);
1111 #endif
1112 xrow_reset(status.xrow);
1113 do {
1114 xrow_add(status.xrow, e->p.x);
1115 event_apply(&status, e);
1116 free(e);
1117 e = heap_chopmax(queue);
1118 } while(e && status.y == e->p.y);
1120 xrow_sort(status.xrow);
1121 segrange_t range;
1122 memset(&range, 0, sizeof(range));
1123 #ifdef DEBUG
1124 actlist_dump(status.actlist, status.y);
1125 #endif
1126 add_points_to_positively_sloped_segments(&status, status.y, &range);
1127 add_points_to_negatively_sloped_segments(&status, status.y, &range);
1128 add_points_to_ending_segments(&status, status.y);
1130 recalculate_windings(&status, &range);
1131 #ifdef CHECKS
1132 check_status(&status);
1133 dict_destroy(status.intersecting_segs);
1134 dict_destroy(status.segs_with_point);
1135 #endif
1137 #ifdef CHECKS
1138 dict_destroy(status.seen_crossings);
1139 #endif
1140 actlist_destroy(status.actlist);
1141 heap_destroy(queue);
1142 xrow_destroy(status.xrow);
1144 gfxpoly_t*p = gfxpoly_new(poly->gridsize);
1145 p->edges = status.output;
1147 add_horizontals(p, &windrule_evenodd, context); // output is always even/odd
1148 return p;