/* See LICENSE file for copyright and license details. */
#include "common.h"
#ifndef TEST
static size_t
solve_quadratic_bezier(double *restrict ts, double a, double b, double c, double z)
{
/*
* B(t) = t(q - p) + p
* where
* p = t(b - a) + a
* q = t(c - b) + b
*
* B(t) = t²(c - 2b + a) + t(2b) + a
*
* z = t²(c - 2b + a) + t(2b) + a
*
* t²(c - 2b + a) + t(2b) + a - z = 0
*
* A := c - 2b + a
* B := 2b
* C := a - z
*
* t²A + tB + C = 0
*/
double A = (c - a) + (b + b);
double B = b + b;
double C = a - z;
if (A)
return solve_quadratic(ts, A, B, C);
else if (B)
return solve_linear(ts, B, C);
else
return 0;
}
static double
evaluate_quadratic_bezier(double t, double a, double b, double c)
{
double p = fma(t, b - a, a);
double q = fma(t, c - b, b);
return fma(t, q - p, p);
}
static void
draw_bounded_quadratic_bezier(RASTER *restrict raster, double x1, double y1, double x2, double y2,
double x3, double y3, 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;
double kx = (x3 - x2) + (x1 - x2), mx = x2 - x1;
double ky = (y3 - y2) + (y1 - y2), my = y2 - y1;
x0 = evaluate_quadratic_bezier(t, x1, x2, x3);
y0 = evaluate_quadratic_bezier(t, y1, y2, y3);
x0 = fmin(fmax(0, x0), w);
y0 = fmin(fmax(0, y0), h);
for (; t < t2; x0 = x, y0 = y) {
dx = fma(t, kx, mx);
dy = fma(t, ky, my);
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_quadratic_bezier(t, x1, x2, x3);
y = evaluate_quadratic_bezier(t, y1, y2, y3);
x = fmin(fmax(0, x), w);
y = fmin(fmax(0, y), h);
draw_bounded_line(raster, x0, y0, x, y);
}
}
void
rtgrpblib_draw_quadratic_bezier(RASTER *restrict raster, double x1, double y1,
double x2, double y2, double x3, double y3)
{
double x, y, yt1, yt2, dy;
double ts[2+2*4], t, t1, t2;
size_t nts = 0;
size_t i;
#ifdef TODO /* untested */
/* Can we downgrade the curve to a linear Bézier curve?
*
* (y2 - y1)/(x2 - x1) = (y3 - y2)/(x3 - x2) = (y3 - y1)/(x3 - x1)
* (y2 - y1)/(x2 - x1) = (y3 - y2)/(x3 - x2)
* (y2 - y1)(x3 - x2) = (y3 - y2)(x2 - x1)
*/
if ((y3 - y1) * (x3 - x2) == (y3 - y2) * (x2 - x1)) {
if (x1 <= x2 && x2 <= x3 && y1 <= y2 && y2 <= y3)
rtgrpblib_draw_linear_bezier(raster, x1, y1, x3, y3);
else if (x1 <= x3 && x3 <= x2 && y1 <= y3 && y3 <= y2)
rtgrpblib_draw_linear_bezier(raster, x1, y1, x2, y2);
else
rtgrpblib_draw_linear_bezier(raster, x2, y2, x3, y3);
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_quadratic_bezier(&ts[nts], y1, y2, y3, (double)raster->height);
nts += solve_quadratic_bezier(&ts[nts], x1, x2, x3, (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_quadratic_bezier(t, y1, y2, y3);
if (y < 0 || y >= (double)raster->height)
continue;
/* If the segment is inside the raster, draw it, */
x = evaluate_quadratic_bezier(t, x1, x2, x3);
if (0 <= x && x < (double)raster->width) {
draw_bounded_quadratic_bezier(raster, x1, y1, x2, y2, x3, y3, 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_quadratic_bezier(t1, y1, y2, y3);
yt2 = evaluate_quadratic_bezier(t2, y1, y2, y3);
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