aboutsummaryrefslogblamecommitdiffstats
path: root/rtgrpblib_draw_quadratic_bezier.c
blob: 8aae11e5eb17706b2c5e159ad83de58c69b3d2fe (plain) (tree)





































































                                                                                                  
                                                                                                   





















                                                                              
                                                                








































































                                                                                                          
/* 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