/* Last edited on 2024-11-20 04:54:26 by stolfi */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* INTERNAL PROTOTYPES */ r2_t pz_plot_interpolate_segment ( double frac, pz_segment_t *s, pz_r3_chain_t *c ); /* IMPLEMENTATIONS */ void pz_plot_curve ( PSStream *f, pz_r3_chain_t *c, bool_t closed, int drawEvery, /* (:= 1) */ int phase /* (:= 0) */ ) { int k; if (drawEvery == 0) { drawEvery = 1; } int m = c->nel, h = phase % drawEvery; k = 0; int i; for (i = 1; i <= m-2; i++) { if ((i % drawEvery) == h) { pswr_segment(f, c->el[k].c[0], c->el[k].c[1], c->el[i].c[0], c->el[i].c[1]); k = i; } } if (k != m-1) { pswr_segment(f, c->el[k].c[0], c->el[k].c[1], c->el[m-1].c[0], c->el[m-1].c[1]); } if (closed) { pswr_segment(f, c->el[m-1].c[0], c->el[m-1].c[1], c->el[0].c[0], c->el[0].c[1]); } } void pz_plot_dots ( PSStream *f, pz_r3_chain_t *c, double size, /* (:= 2.0) */ int drawEvery, /* (:= 1) */ int phase /* (:= 0) */ ) { if (drawEvery == 0) { drawEvery = 1; } int m = c->nel, h = phase % drawEvery; int i; for (i = 0; i < m; i++) { if ((i % drawEvery) == h) { pswr_dot(f, c->el[i].c[0], c->el[i].c[1], size, TRUE, TRUE); } } } void pz_plot_frame( PSStream *f, r3_t color, double lineWidth ) { pswr_set_pen(f, color.c[0], color.c[1], color.c[2], lineWidth, 0, 0); pswr_frame(f); } void pz_plot_grid ( PSStream *f, double gridStep, r3_t color, /* (:= (r3_t){{0,0,0}}) */ double lineWidth /* (:= 0.05) */ ) { if (gridStep > 0.0) { pswr_set_pen(f, color.c[0], color.c[1], color.c[2], lineWidth, 0, 0); /* Draw HOR lines */ { interval_t xr = (interval_t){{ f->xMin, f->xMax }}; int ixmin = (int)ceil(xr.end[0]/gridStep); int ixmax = (int)floor(xr.end[1]/gridStep); int ix; for (ix = ixmin; ix <= ixmax; ix++) { pswr_coord_line(f, HOR, gridStep * ((double)ix)); } } /* Draw VER lines */ { interval_t yr = (interval_t){{ f->yMin, f->yMax }}; int iymin = (int)ceil(yr.end[0]/gridStep); int iymax = (int)floor(yr.end[1]/gridStep); int iy; for (iy = iymin; iy <= iymax; iy++) { pswr_coord_line(f, VER, gridStep * ((double)iy)); } } } } #define pt_per_mm (72.0/25.4) void pz_plot_star ( PSStream *f, r3_t color, double radius ) { /* Hack - should be in PSPlot. */ /* Scale factors on each axis: */ double xScale = f->xScale / pt_per_mm; double yScale = f->yScale / pt_per_mm; /* Leave 2mm of distance from the drawing's border: */ double xDelta = ((double)3.0 + radius) / xScale; double yDelta = ((double)3.0 + radius) / yScale; /* Coordinate ranges of plotting area: */ interval_t xRange = (interval_t){{ f->xMin, f->xMax }}; interval_t yRange = (interval_t){{ f->yMin, f->yMax }}; /* Ok, compute center: */ double x = xRange.end[0] + xDelta; double y = yRange.end[1] - yDelta; /* do */ pswr_set_pen(f, color.c[0], color.c[1], color.c[2], radius, 0, 0); pswr_dot(f, x, y, 1.0, TRUE, FALSE); } void pz_plot_match ( PSStream *f, pz_match_t *m, pz_r3_chain_t *vA, pz_r3_chain_t *vB, pz_segment_t *segA, pz_segment_t *segB, int drawEvery, int phaseA, int phaseB ) { int iA, iB; int dA, dB; int dAnt = 0; affirm(drawEvery > 0, "bad drawEvery"); affirm(vA->nel == segA->tot, "wrong size vA"); affirm(vB->nel == segB->tot, "wrong size vB"); int i; for (i = 0; i < m->nel; i++) { /* Compute the indices of samples that are paired by {m[i]}. Compute also the signed distances {dA,dB} from {iA,iB} TO {phaseA,phaseB}, in the direction of travel for each segment: */ if (segA->rev) { iA = (segA->ini + segA->ns - 1 - m->el[i].c[0]) % segA->tot; dA = phaseA - iA; } else { iA = (segA->ini + m->el[i].c[0]) % segA->tot; dA = iA - phaseA; } affirm((iA - segA->ini) % segA->tot < segA->ns, "bug"); if (segB->rev) { iB = (segB->ini + segB->ns - 1 - m->el[i].c[1]) % segB->tot; dB = phaseB - iB; } else { iB = (segB->ini + m->el[i].c[1]) % segB->tot; dB = iB - phaseB; } affirm((iB - segB->ini) % segB->tot < segB->ns, "bug"); /* Progress along the match is measured by the quantity {d(i) == dA + dB}. The increments of {d} are either +1 or +2. Therefore we must draw the pairing line whenever (a) {d(i)} is a multiple OF {m}, or (b) {d(i) == d(i-1)+2}, and {d(i)-1} is a multiple {m}. The parameter {phaseM} is chosen so that {p(i)} is congruent to {phaseM} when {m[i]} connects {vA[phaseA]} to {vB[phaseB]}. */ int d = dA + dB, r = d % drawEvery; if ((i == 0) || (i == (m->nel - 1)) || (r == 0) || ((d == dAnt + 2) && (r == 1))) { r3_t *pA = &(vA->el[iA]); r3_t *pB = &(vB->el[iB]); pswr_segment(f, pA->c[0], pA->c[1], pB->c[0], pB->c[1]); } dAnt = d; } } void pz_plot_segment ( PSStream *f, pz_segment_t *s, pz_r3_chain_t *c, bool_t closed, int drawEvery, int phase ) { affirm(drawEvery > 0, "bad drawEvery"); affirm(c->nel == s->tot, "wrong s->tot"); int m = c->nel; int ini = s->ini % m; int fin = (ini + s->ns - 1) % m; if (s->ns > m) { affirm(closed, "not closed"); pz_plot_curve(f, c, FALSE, drawEvery, phase); r3_t *pA = &(c->el[m-1]); r3_t *pB = &(c->el[0]); pswr_segment(f, pA->c[0], pA->c[1], pB->c[0], pB->c[1]); } else if (ini <= fin) { pz_r3_chain_t cs = (pz_r3_chain_t){s->ns, c->el + ini}; pz_plot_curve(f, &cs, FALSE, drawEvery, phase - ini); } else { pz_r3_chain_t cs = (pz_r3_chain_t){m-ini, c->el + ini}; pz_r3_chain_t ct = (pz_r3_chain_t){fin+1, c->el}; pz_plot_curve(f, &cs, FALSE, drawEvery, phase - ini); if (closed) { r3_t *pA = &(c->el[m-1]); r3_t *pB = &(c->el[0]); pswr_segment(f, pA->c[0], pA->c[1], pB->c[0], pB->c[1]); } pz_plot_curve(f, &ct, FALSE, drawEvery, phase);; } } void pz_plot_segment_dots ( PSStream *f, pz_segment_t *s, pz_r3_chain_t *c, double size, /* (:= 2.0) */ int drawEvery, /* (:= 1) */ int phase /* (:= 0) */ ) { affirm(drawEvery > 0, "bad drawEvery"); affirm(c->nel == s->tot, "wrong s.tot"); int m = c->nel; int ini = s->ini % m; int fin = (ini + s->ns - 1) % m; if (s->ns > m) { pz_plot_dots(f, c, size, drawEvery, phase); } else if (ini <= fin) { pz_r3_chain_t cs = (pz_r3_chain_t){s->ns, c->el + ini}; pz_plot_dots(f, &cs, size, drawEvery, phase - ini); } else { pz_r3_chain_t cs = (pz_r3_chain_t){m-ini, c->el + ini}; pz_r3_chain_t ct = (pz_r3_chain_t){fin+1, c->el}; pz_plot_dots(f, &cs, size, drawEvery, phase-ini); pz_plot_dots(f, &ct, size, drawEvery, phase); } } void pz_plot_segment_label ( PSStream *f, pz_segment_t *s, pz_r3_chain_t *c, double minRelDist, char *label, double size, /* (:= 12.0) */ char *font /* (:= "Courier") */ ) { int m = c->nel; int mid = (s->ini + s->ns / 2) % m; r2_t sMid = (r2_t){{c->el[mid].c[0], c->el[mid].c[1]}}; int opp = (mid + m / 2) % m; r2_t sOpp = (r2_t){{c->el[opp].c[0], c->el[opp].c[1]}}; r2_t cCtr; r2_mix(0.5, &sMid, 0.5, &sOpp, &cCtr); double frac = fmin(1.0, ((double)s->ns)/((double)m)); double cWeight = fmax(minRelDist, frac*frac); double sWeight = 1.0 - cWeight; r2_t labPos; r2_mix(sWeight, &sMid, cWeight, &cCtr, &labPos); affirm(s->tot == m, "wrong s.tot"); pswr_set_label_font(f, font, size); pswr_label(f, label, labPos.c[0], labPos.c[1], 0.0, 0.5, 0.5); } void pz_plot_segment_pointer ( PSStream *f, pz_segment_t *s, pz_r3_chain_t *c, double frac, double width, /* (:= 4.0) */ double length, /* (:= 8.0) */ bool_t inside /* (:= TRUE) */ ) { r2_t p; int m = c->nel; /* Point to point at: */ r2_t q = pz_plot_interpolate_segment(frac, s, c); int iu = (s->ini + s->ns / 2) % m; r2_t u = (r2_t){{c->el[iu].c[0], c->el[iu].c[1]}}; int iv = (iu + m / 2) % m; r2_t v = (r2_t){{c->el[iv].c[0], c->el[iv].c[1]}}; /* Approximate center: */ r2_mix(0.5, &u, 0.5, &v, &p); if ((p.c[0] == q.c[0]) && (p.c[1] == q.c[1])) { p = (r2_t){{q.c[0] - 1.0, q.c[1] - 1.0}}; } if (! inside) { r2_mix(-1.0, &p, 2.0, &q, &p); } pswr_arrowhead(f, p.c[0], p.c[1], q.c[0], q.c[1], width, length, 1.0, TRUE,FALSE); } void pz_plot_segment_brackets ( PSStream *f, pz_segment_t *s, pz_r3_chain_t *c, double width, /* (:= 4.0) */ double length /* (:= 8.0) */ ) { affirm(s->tot == c->nel, "wrong s.tot"); r3_t pIni = c->el[(s->ini + 1) % s->tot]; r3_t qIni = c->el[s->ini % s->tot]; r3_t pFin = c->el[(s->ini + s->ns - 2) % s->tot]; r3_t qFin = c->el[(s->ini + s->ns - 1) % s->tot]; r2_t p[2] = { (r2_t){{pIni.c[0], pIni.c[1]}}, (r2_t){{pFin.c[0], pFin.c[1]}} }; r2_t q[2] = { (r2_t){{qIni.c[0], qIni.c[1]}}, (r2_t){{qFin.c[0], qFin.c[1]}} }; r2_t d[2] = { (r2_t){{-1.0e-3,0.0}}, (r2_t){{+1.0e-3,0.0}} }; int j; for (j = 0; j < 2; j++) { r2_t *u = &(p[j]), *v = &(q[j]); if (r2_dist(u,v) < 0.0001) { u = &(q[1-j]); } if (r2_dist(u,v) < 0.0001) { r2_add(v, &(d[j]), u); } pswr_arrowhead(f, u->c[0], u->c[1], v->c[0], v->c[1], width, length, 1.0, TRUE,FALSE); } } r2_t pz_plot_interpolate_segment ( double frac, pz_segment_t *s, pz_r3_chain_t *c ) { int ia, ib; int m = c->nel; double fx = frac*((double)s->ns); int ix = (int)floor(fx); double sx = fx - ((double)ix); affirm(s->tot == m, "wrong s.tot"); if (s->rev) { ia = (s->ini + s->ns - 1 - ix) % m; ib = (s->ini + s->ns - 1 - ix - 1) % m; } else { ia = (s->ini + ix) % m; ib = (s->ini + ix + 1) % m; } r2_t a = (r2_t){{c->el[ia].c[0], c->el[ia].c[1]}}; r2_t b = (r2_t){{c->el[ib].c[0], c->el[ib].c[1]}}; r2_t o; r2_mix(1.0 - sx, &a, sx, &b, &o); return o; } #define LabelFont "Courier" void pz_plot_candidate ( PSStream *f, pz_candidate_t *cand, pz_r3_chain_t *c0, pz_r3_chain_t *c1, /* Curves, already mapped; */ bool_t whole, bool_t closed, bool_t colors, bool_t thicker, bool_t dots, bool_t pointers, double labelSize, int drawEvery, /* (:= 1) */ int drawMatchEvery /* (:= 0) */ ) { r3_t color0, color1; affirm(drawEvery > 0, "bad drawEvery"); pz_segment_t *s0 = &(cand->seg.c[0]); pz_segment_t *s1 = &(cand->seg.c[1]); if (drawMatchEvery > 0) { if (colors) { pswr_set_pen(f, 0.00,0.50,0.00, 0.1, 0, 0); } else { pswr_set_pen(f, 0.30,0.30,0.30, 0.1, 0, 0); } pz_match_t m; if (cand->pm != NULL) { m = pz_match_unpack(cand->pm); } else { m = pz_match_most_perfect(s0->ns, s1->ns); } int midA = (s0->ini + (s0->ns / 2)) % (c0->nel); int midB = (s1->ini + (s1->ns / 2)) % (c1->nel); pz_plot_match(f, &m, c0, c1, s0, s1, drawMatchEvery, midA, midB); } if (colors) { color0 = (r3_t){{1.0,0.0,0.0}}; color1 = (r3_t){{0.0,0.3,1.0}}; } else { color0 = (r3_t){{0.0,0.0,0.0}}; color1 = (r3_t){{0.0,0.0,0.0}}; } pz_plot_half_candidate ( f, s0, c0, /* whole */ whole, /* closed */ closed, /* color */ color0, /* thicker */ thicker, /* dots */ dots, /* pointers */ pointers, /* labelSize */ labelSize, /* drawEvery */ drawEvery ); pz_plot_half_candidate( f, s1, c1, /* whole */ whole, /* closed */ closed, /* color */ color1, /* thicker */ thicker, /* dots */ dots, /* pointers */ pointers, /* labelSize */ labelSize, /* drawEvery */ drawEvery ); } #define NormalWd (0.1) /* millimeters */ #define DoubleWd (0.2) /* millimeters */ #define ArrowLength (2.0) /* millimeters */ void pz_plot_half_candidate ( PSStream *f, pz_segment_t *s, pz_r3_chain_t *c, /* pz_plot_curve, already mapped; */ bool_t whole, bool_t closed, r3_t color, bool_t thicker, bool_t dots, bool_t pointers, double labelSize, int drawEvery /* (:= 1) */ ) { double minLabelRelDist; affirm(s->tot == c->nel, "wrong s.tot"); if (labelSize > 0.0) { pswr_set_pen(f, 0,0,0, 0.1, 0, 0); if (whole) { minLabelRelDist = 0.5; } else { minLabelRelDist = 0.15; } char *xcvx = jsprintf("%d", s->cvx); pz_plot_segment_label(f, s, c, minLabelRelDist, xcvx, labelSize, LabelFont); free(xcvx); } int phase = s->ini + s->ns / 2; if (whole) { pswr_set_pen(f, 0,0,0, NormalWd, 0, 0); pz_segment_t r = pz_segment_complement(s); pz_plot_segment(f, &r, c, closed, drawEvery, phase); if (dots) { pz_plot_segment_dots(f, &r, c, 2.0, drawEvery, phase); } } if (thicker) { pswr_set_pen(f, color.c[0],color.c[1],color.c[2], DoubleWd, 0,0); } else { pswr_set_pen(f, color.c[0],color.c[1],color.c[2], NormalWd, 0,0); } pz_plot_segment(f, s, c, closed, drawEvery, phase); if (dots) { pz_plot_segment_dots(f, s, c, 2.0, drawEvery, phase); } if (pointers) { double lineWd = NormalWd; double relLength = ArrowLength/lineWd; double relWidth = relLength/2.0; pswr_set_pen(f, color.c[0],color.c[1],color.c[2], lineWd, 0,0); pz_plot_segment_pointer(f, s, c, 0.0, relWidth, relLength, TRUE); pz_plot_segment_pointer(f, s, c, 1.0, relWidth, relLength, TRUE); } } void pz_plot_try_all_shifts ( pz_segment_pair *s, pz_segment_pair *sRef, pz_plot_seg_seg_proc_t proc ) { auto bool_t segs_overlap( pz_segment_t *sa, pz_segment_t *sb ); bool_t segs_overlap ( pz_segment_t *sa, pz_segment_t *sb ) /* TRUE if {sa} intersects {sb}, ignoring wrap-around. */ { return (sa->ini < sb->ini + sb->ns) && (sb->ini < sa->ini + sa->ns); } pz_segment_pair t; /* Translated copy of {s} */ pz_segment_pair tRef; /* Reduced copy of {sRef} */ /* Get a copy {tRef} of {sRef} with {ini} in {[tot..2*tot-1]}: */ tRef = *sRef; int j; for (j = 0; j <= 1; j++) { affirm(sRef->c[j].tot == s->c[j].tot, "wrong size"); tRef.c[j].ini = (sRef->c[j].ini % sRef->c[j].tot) + sRef->c[j].tot; } /* Find all translates {t} of {s} that segs_overlap {tRef}: */ t = *s; int k0, k1; for (k0 = 0; k0 < 3; k0++) { for (k1 = 0; k1 < 3; k1++) { t.c[0].ini = s->c[0].ini % s->c[0].tot + k0*s->c[0].tot; t.c[1].ini = s->c[1].ini % s->c[1].tot + k1*s->c[1].tot; if ( segs_overlap(&(t.c[0]), &(tRef.c[0])) && segs_overlap(&(t.c[1]), &(tRef.c[1])) ) { proc(&t, &tRef); } } } } int pz_plot_compute_draw_every ( PSStream *f, double eps, double step, bool_t all ) { if (all || (step <= 0.0)) { return 1; } else { double sx = f->xScale / pt_per_mm; double sy = f->yScale / pt_per_mm; return imax(1, (int)floor(eps/fmax(sx, sy)/step)); } } /* MULTI-DRAWING FILE CONTROL */ #define Inch (72.0) /* One inch in pt */ #define TinySize (1e-10) /* A tiny object size. */ #define HugeSize (1e+20) /* A huge object size. */ pz_plot_paging_options_t *pz_plot_parse_paging_options ( argparser_t *pp, bool_t single, double defaultHor, /* Default plotting area width in mm. */ double defaultVer /* Default plotting area height in mm. */ ) { pz_plot_paging_options_t *o = malloc(sizeof(pz_plot_paging_options_t)); affirm(o != NULL, "out of mem") ; /* "-epsFormat" option: */ o->epsFormat = argparser_keyword_present(pp, "-epsFormat"); /* "-window" or "-objectSize" option: */ { double xmin, xmax, ymin, ymax; if (argparser_keyword_present(pp, "-window")) { xmin = argparser_get_next_double(pp, -HugeSize/2, +HugeSize/2); double xeps = 1.0e-6*fabs(xmin) + TinySize; xmax = argparser_get_next_double(pp, xmin + xeps, xmin + HugeSize); ymin = argparser_get_next_double(pp, -HugeSize, +HugeSize); double yeps = 1.0e-6*fabs(ymin) + TinySize; ymax = argparser_get_next_double(pp, ymin + yeps, ymin + HugeSize); } else if (argparser_keyword_present(pp, "-objectSize")) { xmax = argparser_get_next_double(pp, TinySize, HugeSize)/2.0; xmin = - xmax; ymax = argparser_get_next_double(pp, TinySize, HugeSize)/2.0; ymin = - ymax; } else { /* Caller must fix it somehow: */ xmin = xmax = ymin = ymax = 0.0; } o->window.r[HOR].end[0] = xmin; o->window.r[HOR].end[1] = xmax; o->window.r[VER].end[0] = ymin; o->window.r[VER].end[1] = ymax; } /* "-actualSize" option: */ if (argparser_keyword_present(pp, "-actualSize")) { o->actualHor = argparser_get_next_double(pp, 1.0, 1000.0); o->actualVer = argparser_get_next_double(pp, 1.0, 1000.0); } else if (single) { o->actualHor = defaultHor; o->actualVer = defaultVer; } /* "-nDrawingsPerPage" option: */ if ((single) || (o->epsFormat)) { if (argparser_keyword_present(pp, "-nDrawingsPerPage")) { argparser_get_next_int(pp, 1, 100); argparser_get_next_int(pp, 1, 100); fprintf(stderr, "warning: \"-nDrawingsPerPage\" ignored");; } o->nCols = 1; o->nRows = 1; } else { double ptHor, ptVer; /* Page size in points. */ pswr_get_paper_dimensions("letter", FALSE, &ptHor, &ptVer); if (argparser_keyword_present(pp, "-nDrawingsPerPage")) { o->nCols= argparser_get_next_int(pp, 1, 100); o->nRows= argparser_get_next_int(pp, 1, 100); } else { o->nCols = imax(1, (int)floor((ptHor - 1.0 * Inch)/o->actualHor)); o->nRows = imax(1, (int)floor((ptVer - 2.0 * Inch)/o->actualVer)); } /* Reduce actual height and width, if needed, to fit page: */ double maxHor = (ptHor - 2.0 * Inch)/((double)o->nCols); double maxVer = (ptVer - 2.0 * Inch)/((double)o->nRows); o->actualHor = fmin(o->actualHor, maxHor) / pt_per_mm; o->actualVer = fmin(o->actualVer, maxVer) / pt_per_mm; } return o; } /* Copyright © 2001 Universidade Estadual de Campinas (UNICAMP). Authors: Helena C. G. Leitão and Jorge Stolfi. This file can be freely distributed, used, and modified, provided that this copyright and authorship notice is preserved, and that any modified versions are clearly marked as such. This software has NO WARRANTY of correctness or applicability for any purpose. Neither the authors nor their employers chall be held responsible for any losses or damages that may result from its use. */