/* 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;
/* 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 ((y2 - y1) * (x3 - x2) == (y3 - y2) * (x2 - x1)) {
/* Drawing (x1, y1)--(x2, y2) plus (x2, y2)--(x3, y3)
* is equivalent to drawing (x1, y1)--(x3, y3) because
* the result is signed and if (x2, y2) is outside the
* (x1, y1)--(x3, y3) line (x2, y2)--(x3, y3) erases
* the part that (x1, y1)--(x2, y2) extends to
* (x1, y1)--(x3, y3) */
rtgrpblib_draw_linear_bezier(raster, x1, y1, x3, y3);
return;
}
/* 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
static RASTER *raster;
static RASTER *refraster;
static double
frand(double a, double b)
{
int i = rand();
double f = (double)i / (double)RAND_MAX;
double max = fmax(a, b);
double min = fmin(a, b);
return fma(f, max - min, min);
}
static void
check_line(double x1, double y1, double x2, double y2, double x3, double y3, int exact)
{
size_t i;
alarm(5);
rtgrpblib_draw_quadratic_bezier(raster, x1, y1, x2, y2, x3, y3);
alarm(5);
rtgrpblib_draw_linear_bezier(refraster, x1, y1, x3, y3);
alarm(0);
if (exact) {
ASSERT(!memcmp(raster->cells, refraster->cells, 100 * sizeof(*raster->cells)));
} else {
for (i = 0; i < 100; i++) {
if (!eq(raster->cells[i].cell_coverage, refraster->cells[i].cell_coverage))
fprintf(stderr, "c%02zu: %la ref: %la; diff: %lg=%la (tol: %la)\n", i, raster->cells[i].cell_coverage, refraster->cells[i].cell_coverage, refraster->cells[i].cell_coverage - raster->cells[i].cell_coverage, refraster->cells[i].cell_coverage - raster->cells[i].cell_coverage, TOLERANCE);
if (!eq(raster->cells[i].opposite_coverage, refraster->cells[i].opposite_coverage))
fprintf(stderr, "o%02zu: %la ref: %la; diff: %lg=%la (tol: %la)\n", i, raster->cells[i].opposite_coverage, refraster->cells[i].opposite_coverage, raster->cells[i].opposite_coverage - refraster->cells[i].opposite_coverage, raster->cells[i].opposite_coverage - refraster->cells[i].opposite_coverage, TOLERANCE);
}
for (i = 0; i < 100; i++) {
if (!eq(raster->cells[i].cell_coverage, refraster->cells[i].cell_coverage) ||
!eq(raster->cells[i].opposite_coverage, refraster->cells[i].opposite_coverage)) {
print_raster(refraster);
print_raster(raster);
}
ASSERT(eq(raster->cells[i].cell_coverage, refraster->cells[i].cell_coverage));
ASSERT(eq(raster->cells[i].opposite_coverage, refraster->cells[i].opposite_coverage));
}
}
rtgrpblib_reset_raster(raster, 10, 10);
rtgrpblib_reset_raster(refraster, 10, 10);
}
static void
check_simple_lines(void)
{
check_line(1, 1, 2, 2, 3, 3, 1);
check_line(1, 1, 3, 3, 2, 2, 1);
check_line(2, 2, 1, 1, 3, 3, 1);
check_line(2, 2, 3, 3, 1, 1, 1);
check_line(3, 3, 1, 1, 2, 2, 1);
check_line(3, 3, 2, 2, 1, 1, 1);
check_line(1, 1, 3, 3, 3, 3, 1);
check_line(1, 1, 1, 1, 3, 3, 1);
check_line(1, 4, 2, 4, 3, 4, 1);
check_line(1, 4, 3, 4, 2, 4, 1);
check_line(2, 4, 1, 4, 3, 4, 1);
check_line(2, 4, 3, 4, 1, 4, 1);
check_line(3, 4, 1, 4, 2, 4, 1);
check_line(3, 4, 2, 4, 1, 4, 1);
check_line(1, 4, 3, 4, 3, 4, 1);
check_line(1, 4, 1, 4, 3, 4, 1);
check_line(4, 1, 4, 2, 4, 3, 1);
check_line(4, 1, 4, 3, 4, 2, 1);
check_line(4, 2, 4, 1, 4, 3, 1);
check_line(4, 2, 4, 3, 4, 1, 1);
check_line(4, 3, 4, 1, 4, 2, 1);
check_line(4, 3, 4, 2, 4, 1, 1);
check_line(4, 1, 4, 3, 4, 3, 1);
check_line(4, 1, 4, 1, 4, 3, 1);
}
static void
check_random_bounded_lines(int exact)
{
double x1, x2, x3;
double y1, y2, y3;
double mid;
size_t i;
for (i = 0; i < 100000UL; i++) {
x1 = floor(frand(0, 11));
y1 = floor(frand(0, 11));
x3 = floor(frand(0, 11));
y3 = floor(frand(0, 11));
x1 -= (double)(x1 > 10);
x2 -= (double)(x2 > 10);
y1 -= (double)(y1 > 10);
y2 -= (double)(y2 > 10);
mid = frand(0, 1);
if (x3 - x1) {
x2 = floor(mid * (x3 - x1) + x1);
y2 = y1 + (x2 - x1) * (y3 - y1) / (x3 - x1);
} else if (y3 - y1) {
x2 = x1;
y2 = floor(mid * (y3 - y1) + y1);
} else {
x2 = x1;
y2 = y1;
}
if (!exact)
check_line(x1, y1, x2, y2, x3, y3, x2 == floor(x2) && y2 == floor(y2));
else if (x2 == floor(x2) && y2 == floor(y2))
check_line(x1, y1, x2, y2, x3, y3, 1);
}
}
static void
check_random_unbounded_lines(int exact)
{
double x1, x2, x3;
double y1, y2, y3;
double mid;
size_t i;
for (i = 0; i < 100000UL; i++) {
x1 = floor(frand(-5, 15));
y1 = floor(frand(-5, 15));
x3 = floor(frand(-5, 15));
y3 = floor(frand(-5, 15));
mid = frand(-2, 2);
if (x3 - x1) {
x2 = floor(mid * (x3 - x1) + x1);
y2 = y1 + (x2 - x1) * (y3 - y1) / (x3 - x1);
} else if (y3 - y1) {
x2 = x1;
y2 = floor(mid * (y3 - y1) + y1);
} else {
x2 = x1;
y2 = y1;
}
if (!exact)
check_line(x1, y1, x2, y2, x3, y3, x2 == floor(x2) && y2 == floor(y2));
else if (x2 == floor(x2) && y2 == floor(y2))
check_line(x1, y1, x2, y2, x3, y3, 1);
}
}
int
main(void)
{
raster = rtgrpblib_create_raster(10, 10);
ASSERT(raster);
refraster = rtgrpblib_create_raster(10, 10);
ASSERT(refraster);
srand((unsigned)(uintptr_t)raster);
check_simple_lines();
check_random_bounded_lines(1);
check_random_unbounded_lines(1);
//TODO check_random_bounded_lines(0);
//TODO check_random_unbounded_lines(0);
raster->draftness = -1;
refraster->draftness = -1;
check_simple_lines();
check_random_bounded_lines(1);
check_random_unbounded_lines(1);
#ifdef TODO_NOT_IMPLEMENTED
check_random_bounded_lines(0);
check_random_unbounded_lines(0);
#endif
raster->draftness = 0.5;
refraster->draftness = 0.5;
/* TODO add tests */
free(raster);
free(refraster);
return 0;
}
#endif