/* See LICENSE file for copyright and license details. */ #include "common.h" #ifndef TEST static size_t solve_cubic_bezier(double *restrict ts, double a, double b, double c, double d, double z) { /* * B(t) = t(q - p) + p * where * p = t(v - u) + u * q = t(w - v) + v * where * u = t(b - a) + a * v = t(c - b) + b * w = t(d - c) + c * * B(t) = t³(d - 3c + 3b - a) + t²(3c - 6b + 3a) + t(3b - 3a) + a * * z = t³(d - 3c + 3b - a) + t²(3c - 6b + 3a) + t(3b - 3a) + a * * t³(d - 3c + 3b - a) + t²(3c - 6b + 3a) + t(3b - 3a) + a - z = 0 * * A := d - 3c + 3b - a * B := 3c - 6b + 3a * C := 3b - 3a * D := a - z * * t³A + t²B + tC + D = 0 */ double c3 = 3.0 * c; double b3 = 3.0 * b; double a3 = 3.0 * a; double A = (d - c3) + (b3 - a); double B = (c3 - b3) - (b3 - a3); double C = b3 - a3; double D = a - z; if (A) return solve_cubic(ts, A, B, C, D); else if (B) return solve_quadratic(ts, B, C, D); else if (C) return solve_linear(ts, C, D); else return 0; } static double evaluate_cubic_bezier(double t, double a, double b, double c, double d) { double u = fma(t, b - a, a); double v = fma(t, c - b, b); double w = fma(t, d - c, c); double p = fma(t, v - u, u); double q = fma(t, w - v, v); return fma(t, q - p, p); } static double evaluate_cubic_bezier_derivative(double t, double a, double b, double c, double d) { double u = fma(t, b - a, a); double v = fma(t, c - b, b); double w = fma(t, d - c, c); return fma(t, (w - v) + (u - v), v - u); } static void draw_bounded_cubic_bezier(RASTER *restrict raster, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, double t1, double t2) { double t = t1, x0, y0, dx, dy, x, y; double w = (double)raster->width - 0.0001; double h = (double)raster->height - 0.0001; x0 = evaluate_cubic_bezier(t, x1, x2, x3, x4); y0 = evaluate_cubic_bezier(t, y1, y2, y3, y4); x0 = fmin(fmax(0, x0), w); y0 = fmin(fmax(0, y0), h); for (; t < t2; x0 = x, y0 = y) { dx = evaluate_cubic_bezier_derivative(t, x1, x2, x3, x4); dy = evaluate_cubic_bezier_derivative(t, y1, y2, y3, y4); t += raster->draftness / hypot(dx, dy); /* TODO deal with unchanged t and oversized steps (e.g. locally zero derivative) */ t = fmin(t, t2); x = evaluate_cubic_bezier(t, x1, x2, x3, x4); y = evaluate_cubic_bezier(t, y1, y2, y3, y4); x = fmin(fmax(0, x), w); y = fmin(fmax(0, y), h); draw_bounded_line(raster, x0, y0, x, y); } } void rtgrpblib_draw_cubic_bezier(RASTER *restrict raster, double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4) { double x, y, yt1, yt2, dy; double ts[2+4*3], t, t1, t2; size_t nts = 0; size_t i; #ifdef TODO /* untested */ /* Can we downgrade the curve to a quadratic Bézier curve? * * B(t) = (1-t)²Q₁ + 2(1-t)tQ₂ + t²Q₃ * B(t) = (1-t)B(t) + tB(t) = * = (1-t)((1-t)²Q₁ + 2(1-t)tQ₂ + t²Q₃) + t((1-t)²Q₁ + 2(1-t)tQ₂ + t²Q₃) * = (1-t)³Q₁ + 2(1-t)²tQ₂ + (1-t)t²Q₃ + (1-t)²tQ₁ + 2(1-t)t²Q₂ + t³Q₃ * = (1-t)³Q₁ + 2(1-t)²tQ₂ + (1-t)²tQ₁ + 2(1-t)t²Q₂ + (1-t)t²Q₃ + t³Q₃ * = (1-t)³Q₁ + (1-t)²t(Q₁ + 2Q₂) + (1-t)t²(2Q₂ + Q₃) + t³Q₃ * = (1-t)³Q₁ + 3(1-t)²t(Q₁ + 2Q₂)/3 + 3(1-t)t²(2Q₂ + Q₃)/3 + t³Q₃ * * B(t) = (1-t)³C₁ + 3(1-t)²tC₂ + 3(1-t)t²C₃ + t³C₄ * * C₁ = Q₁, C₄ = Q₃ * * B(t) = (1-t)³Q₁ + 3(1-t)²t(Q₁ + 2Q₂)/3 + 3(1-t)t²(2Q₂ + Q₃)/3 + t³Q₃ * B(t) = (1-t)³C₁ + 3(1-t)²tC₂ + 3(1-t)t²C₃ + t³C₄ * * (1-t)³Q₁ + 3(1-t)²t(Q₁ + 2Q₂)/3 + 3(1-t)t²(2Q₂ + Q₃)/3 + t³Q₃ = (1-t)³C₁ + 3(1-t)²tC₂ + 3(1-t)t²C₃ + t³C₄w * 3(1-t)²t(Q₁ + 2Q₂)/3 + 3(1-t)t²(2Q₂ + Q₃)/3 = 3(1-t)²tC₂ + 3(1-t)t²C₃ * (1-t)²t(Q₁ + 2Q₂)/3 + (1-t)t²(2Q₂ + Q₃)/3 = (1-t)²tC₂ + (1-t)t²C₃ * (1-t)t(Q₁ + 2Q₂)/3 + t²(2Q₂ + Q₃)/3 = (1-t)tC₂ + t²C₃ * (1-t)(Q₁ + 2Q₂)/3 + t(2Q₂ + Q₃)/3 = (1-t)C₂ + tC₃ * s(Q₁ + 2Q₂)/3 + t(2Q₂ + Q₃)/3 = sC₂ + tC₃ * (Q₁ + 2Q₂)/3 = C₂, (2Q₂ + Q₃)/3 = C₃ * Q₁ + 2Q₂ = 3C₂, 2Q₂ + Q₃ = 3C₃ * C₁ + 2Q₂ = 3C₂, 2Q₂ + C₄ = 3C₃ * 2Q₂ = 3C₂ - C₁, 2Q₂ = 3C₃ - C₄ * 2Q₂ = 3C₂ - C₁ = 3C₃ - C₄ */ if (x2 + x2 + x2 - x1 == x3 + x3 + x3 - x4 && y2 + y2 + y2 - y1 == y3 + y3 + y3 - y4) { rtgrpblib_draw_quadratic_bezier(raster, x1, y1, x2 + (x2 - x1) / 2.0, y2 + (y2 - y1) / 2.0, x4, y4); return; } #endif /* Beginning and end of curve */ ts[nts++] = 0.0; ts[nts++] = 1.0; /* Get points where the end intersects one of the edges of the raster */ nts += solve_cubic_bezier(&ts[nts], y1, y2, y3, y4, 0); nts += solve_cubic_bezier(&ts[nts], y1, y2, y3, y4, (double)raster->height); nts += solve_cubic_bezier(&ts[nts], x1, x2, x3, x4, 0); nts += solve_cubic_bezier(&ts[nts], x1, x2, x3, x4, (double)raster->width); /* Sort all points of interest */ qsort(ts, nts, sizeof(*ts), doublepcmp); for (i = 0; i < nts - 1; i++) { t1 = ts[i]; t2 = ts[i + 1]; if (t1 == t2) continue; t = 0.5 * (t1 + t2); /* Remove any segments above or below the raster */ y = evaluate_cubic_bezier(t, y1, y2, y3, y4); if (y < 0 || y > (double)raster->height) continue; /* If the segment is inside the raster, draw it, */ x = evaluate_cubic_bezier(t, x1, x2, x3, x4); if (0 <= x && x <= (double)raster->width) { draw_bounded_cubic_bezier(raster, x1, y1, x2, y2, x3, y3, x4, y4, t1, t2); continue; } /* otherwise draw it flattened to the edge; however, * we will reduce it to a from the start to the end * and draw its project on the edge, which works * because if the curve bends it will cancel it self * out except within this project; this project will * also preserv the direction of part of the curve * that is not cancelled out */ yt1 = evaluate_cubic_bezier(t1, y1, y2, y3, y4); yt2 = evaluate_cubic_bezier(t2, y1, y2, y3, y4); dy = yt2 - yt1; if (x < 0) draw_vertical_line(raster, 0, yt1, yt2, SIGNUM(dy)); else draw_vertical_line_opposite_only(raster, raster->width - 1, yt1, yt2, SIGNUM(dy)); } } #else int main(void) { return 0; /* TODO add test */ } #endif