aboutsummaryrefslogblamecommitdiffstats
path: root/rtgrpblib_draw_cubic_bezier.c
blob: ca5f40ba3b7903d31c46da8221264a0bce05092f (plain) (tree)

























































































                                                                                              
                                                                                                   




















                                                                                                
                                                                   





















































                                                                                                                                                 
                                                        



                                                                   
                                                           
































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