1015 lines
32 KiB
C
1015 lines
32 KiB
C
#include <gtk/gtk.h>
|
|
#include "gsk/gskcurveprivate.h"
|
|
|
|
static void
|
|
init_random_point (graphene_point_t *p)
|
|
{
|
|
p->x = g_test_rand_double_range (0, 1000);
|
|
p->y = g_test_rand_double_range (0, 1000);
|
|
}
|
|
|
|
static float
|
|
random_weight (void)
|
|
{
|
|
if (g_test_rand_bit ())
|
|
return g_test_rand_double_range (0, 20);
|
|
else
|
|
return 1.0 / g_test_rand_double_range (1, 20);
|
|
}
|
|
|
|
static void
|
|
init_random_curve_with_op (GskCurve *curve,
|
|
GskPathOperation min_op,
|
|
GskPathOperation max_op)
|
|
{
|
|
switch (g_test_rand_int_range (min_op, max_op + 1))
|
|
{
|
|
case GSK_PATH_LINE:
|
|
{
|
|
graphene_point_t p[2];
|
|
|
|
init_random_point (&p[0]);
|
|
init_random_point (&p[1]);
|
|
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_LINE, p));
|
|
}
|
|
break;
|
|
|
|
case GSK_PATH_CURVE:
|
|
{
|
|
graphene_point_t p[4];
|
|
|
|
init_random_point (&p[0]);
|
|
init_random_point (&p[1]);
|
|
init_random_point (&p[2]);
|
|
init_random_point (&p[3]);
|
|
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CURVE, p));
|
|
}
|
|
break;
|
|
|
|
case GSK_PATH_CONIC:
|
|
{
|
|
graphene_point_t p[4];
|
|
|
|
init_random_point (&p[0]);
|
|
init_random_point (&p[1]);
|
|
p[2] = GRAPHENE_POINT_INIT (random_weight(), 0);
|
|
init_random_point (&p[3]);
|
|
gsk_curve_init (curve, gsk_pathop_encode (GSK_PATH_CONIC, p));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
g_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static void
|
|
init_random_curve (GskCurve *curve)
|
|
{
|
|
init_random_curve_with_op (curve, GSK_PATH_LINE, GSK_PATH_CONIC);
|
|
}
|
|
|
|
static void
|
|
test_curve_tangents (void)
|
|
{
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
GskCurve c;
|
|
graphene_vec2_t vec, exact;
|
|
|
|
init_random_curve (&c);
|
|
|
|
gsk_curve_get_tangent (&c, 0, &vec);
|
|
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&vec), 1.0f, 0.00001);
|
|
gsk_curve_get_start_tangent (&c, &exact);
|
|
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1.0f, 0.00001);
|
|
g_assert_true (graphene_vec2_near (&vec, &exact, 0.05));
|
|
|
|
gsk_curve_get_tangent (&c, 1, &vec);
|
|
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&vec), 1.0f, 0.00001);
|
|
gsk_curve_get_end_tangent (&c, &exact);
|
|
g_assert_cmpfloat_with_epsilon (graphene_vec2_length (&exact), 1.0f, 0.00001);
|
|
g_assert_true (graphene_vec2_near (&vec, &exact, 0.05));
|
|
}
|
|
}
|
|
|
|
static void
|
|
test_curve_points (void)
|
|
{
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
GskCurve c;
|
|
graphene_point_t p;
|
|
|
|
init_random_curve (&c);
|
|
|
|
/* We can assert equality here because evaluating the polynomials with 0
|
|
* has no effect on accuracy.
|
|
*/
|
|
gsk_curve_get_point (&c, 0, &p);
|
|
g_assert_true (graphene_point_equal (gsk_curve_get_start_point (&c), &p));
|
|
/* But here we evaluate the polynomials with 1 which gives the highest possible
|
|
* accuracy error. So we'll just be generous here.
|
|
*/
|
|
gsk_curve_get_point (&c, 1, &p);
|
|
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c), &p, 0.05));
|
|
}
|
|
}
|
|
|
|
static void
|
|
test_curve_bounds (void)
|
|
{
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
GskCurve c;
|
|
GskBoundingBox bounds;
|
|
GskBoundingBox bounds2;
|
|
graphene_point_t p;
|
|
|
|
init_random_curve (&c);
|
|
|
|
gsk_curve_get_tight_bounds (&c, &bounds);
|
|
gsk_curve_get_bounds (&c, &bounds2);
|
|
|
|
g_assert_true (gsk_bounding_box_contains_point (&bounds, gsk_curve_get_start_point (&c)));
|
|
g_assert_true (gsk_bounding_box_contains_point (&bounds, gsk_curve_get_end_point (&c)));
|
|
g_assert_true (gsk_bounding_box_contains_point (&bounds2, gsk_curve_get_start_point (&c)));
|
|
g_assert_true (gsk_bounding_box_contains_point (&bounds2, gsk_curve_get_end_point (&c)));
|
|
|
|
for (int j = 0; j < 20; j++)
|
|
{
|
|
float t = g_test_rand_double_range (0, 1);
|
|
gsk_curve_get_point (&c, t, &p);
|
|
g_assert_true (gsk_bounding_box_contains_point (&bounds, &p));
|
|
g_assert_true (gsk_bounding_box_contains_point (&bounds2, &p));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* at this point the subdivision stops and the decomposer
|
|
* violates tolerance rules
|
|
*/
|
|
#define MIN_PROGRESS (1/1024.f)
|
|
|
|
typedef struct
|
|
{
|
|
graphene_point_t p;
|
|
float t;
|
|
} PointOnLine;
|
|
|
|
static gboolean
|
|
add_line_to_array (const graphene_point_t *from,
|
|
const graphene_point_t *to,
|
|
float from_progress,
|
|
float to_progress,
|
|
gpointer user_data)
|
|
{
|
|
GArray *array = user_data;
|
|
PointOnLine *last = &g_array_index (array, PointOnLine, array->len - 1);
|
|
|
|
g_assert (array->len > 0);
|
|
g_assert_cmpfloat (from_progress, >=, 0.0f);
|
|
g_assert_cmpfloat (from_progress, <, to_progress);
|
|
g_assert_cmpfloat (to_progress, <=, 1.0f);
|
|
|
|
g_assert_true (graphene_point_equal (&last->p, from));
|
|
g_assert_cmpfloat (last->t, ==, from_progress);
|
|
|
|
g_array_append_vals (array, (PointOnLine[1]) { { *to, to_progress } }, 1);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
test_curve_decompose (void)
|
|
{
|
|
static const float tolerance = 0.5;
|
|
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
GArray *array;
|
|
GskCurve c;
|
|
|
|
init_random_curve (&c);
|
|
|
|
array = g_array_new (FALSE, FALSE, sizeof (PointOnLine));
|
|
g_array_append_vals (array, (PointOnLine[1]) { { *gsk_curve_get_start_point (&c), 0.f } }, 1);
|
|
|
|
g_assert_true (gsk_curve_decompose (&c, tolerance, add_line_to_array, array));
|
|
|
|
g_assert_cmpint (array->len, >=, 2); /* We at least got a line to the end */
|
|
g_assert_cmpfloat (g_array_index (array, PointOnLine, array->len - 1).t, ==, 1.0);
|
|
|
|
for (int j = 0; j < array->len; j++)
|
|
{
|
|
PointOnLine *pol = &g_array_index (array, PointOnLine, j);
|
|
graphene_point_t p;
|
|
|
|
/* Check that the points we got are actually on the line */
|
|
gsk_curve_get_point (&c, pol->t, &p);
|
|
g_assert_true (graphene_point_near (&pol->p, &p, 0.05));
|
|
|
|
/* Check that the mid point is not further than the tolerance */
|
|
if (j > 0)
|
|
{
|
|
PointOnLine *last = &g_array_index (array, PointOnLine, j - 1);
|
|
graphene_point_t mid;
|
|
|
|
if (pol->t - last->t > MIN_PROGRESS)
|
|
{
|
|
graphene_point_interpolate (&last->p, &pol->p, 0.5, &mid);
|
|
gsk_curve_get_point (&c, (pol->t + last->t) / 2, &p);
|
|
/* The decomposer does this cheaper Manhattan distance test,
|
|
* so graphene_point_near() does not work */
|
|
g_assert_cmpfloat (fabs (mid.x - p.x), <=, tolerance);
|
|
g_assert_cmpfloat (fabs (mid.y - p.y), <=, tolerance);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
add_curve_to_array (const graphene_point_t points[4],
|
|
gpointer user_data)
|
|
{
|
|
GArray *array = user_data;
|
|
GskCurve c;
|
|
|
|
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CURVE, points));
|
|
g_array_append_val (array, c);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
test_curve_decompose_curve (void)
|
|
{
|
|
g_test_skip ("No good error bounds for decomposing conics");
|
|
return;
|
|
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
GArray *array;
|
|
GskCurve c;
|
|
GskPathBuilder *builder;
|
|
GskPath *path;
|
|
GskPathMeasure *measure;
|
|
const graphene_point_t *s;
|
|
|
|
init_random_curve_with_op (&c, GSK_PATH_CONIC, GSK_PATH_CONIC);
|
|
|
|
builder = gsk_path_builder_new ();
|
|
|
|
s = gsk_curve_get_start_point (&c);
|
|
gsk_path_builder_move_to (builder, s->x, s->y);
|
|
gsk_curve_builder_to (&c, builder);
|
|
path = gsk_path_builder_free_to_path (builder);
|
|
measure = gsk_path_measure_new_with_tolerance (path, 0.1);
|
|
|
|
array = g_array_new (FALSE, FALSE, sizeof (GskCurve));
|
|
|
|
g_assert_true (gsk_curve_decompose_curve (&c, 0.1, add_curve_to_array, array));
|
|
|
|
g_assert_cmpint (array->len, >=, 1);
|
|
|
|
for (int j = 0; j < array->len; j++)
|
|
{
|
|
GskCurve *c2 = &g_array_index (array, GskCurve, j);
|
|
|
|
g_assert_true (c2->op == GSK_PATH_CURVE);
|
|
|
|
/* Check that the curves we got are approximating the conic */
|
|
for (int k = 0; k < 11; k++)
|
|
{
|
|
graphene_point_t p;
|
|
float dist;
|
|
|
|
gsk_curve_get_point (c2, k/10.0, &p);
|
|
dist = gsk_path_measure_get_closest_point (measure, &p, NULL);
|
|
g_assert_cmpfloat (dist, <, 0.5); // FIXME error bound ?
|
|
}
|
|
}
|
|
|
|
g_array_unref (array);
|
|
|
|
gsk_path_measure_unref (measure);
|
|
gsk_path_unref (path);
|
|
}
|
|
}
|
|
|
|
static void
|
|
test_line_line_intersection (void)
|
|
{
|
|
GskCurve c1, c2;
|
|
graphene_point_t p1[2], p2[2];
|
|
float t1, t2;
|
|
graphene_point_t p;
|
|
int n;
|
|
|
|
graphene_point_init (&p1[0], 10, 0);
|
|
graphene_point_init (&p1[1], 10, 100);
|
|
graphene_point_init (&p2[0], 0, 10);
|
|
graphene_point_init (&p2[1], 100, 10);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, 1);
|
|
|
|
g_assert_cmpint (n, ==, 1);
|
|
g_assert_cmpfloat_with_epsilon (t1, 0.1, 0.0001);
|
|
g_assert_cmpfloat_with_epsilon (t2, 0.1, 0.0001);
|
|
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (10, 10), 0.0001));
|
|
}
|
|
|
|
static void
|
|
test_line_line_end_intersection (void)
|
|
{
|
|
GskCurve c1, c2;
|
|
graphene_point_t p1[2], p2[2];
|
|
float t1, t2;
|
|
graphene_point_t p;
|
|
int n;
|
|
|
|
graphene_point_init (&p1[0], 10, 0);
|
|
graphene_point_init (&p1[1], 10, 100);
|
|
graphene_point_init (&p2[0], 10, 100);
|
|
graphene_point_init (&p2[1], 100, 10);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, 1);
|
|
|
|
g_assert_cmpint (n, ==, 1);
|
|
g_assert_cmpfloat_with_epsilon (t1, 1, 0.0001);
|
|
g_assert_cmpfloat_with_epsilon (t2, 0, 0.0001);
|
|
g_assert_true (graphene_point_near (&p, &GRAPHENE_POINT_INIT (10, 100), 0.0001));
|
|
}
|
|
|
|
static void
|
|
test_line_line_none_intersection (void)
|
|
{
|
|
GskCurve c1, c2;
|
|
graphene_point_t p1[2], p2[2];
|
|
float t1, t2;
|
|
graphene_point_t p;
|
|
int n;
|
|
|
|
graphene_point_init (&p1[0], 0, 0);
|
|
graphene_point_init (&p1[1], 10, 0);
|
|
graphene_point_init (&p2[0], 20, 0);
|
|
graphene_point_init (&p2[1], 30, 0);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, 1);
|
|
|
|
g_assert_cmpint (n, ==, 0);
|
|
|
|
graphene_point_init (&p1[0], 247.103424, 95.7965317);
|
|
graphene_point_init (&p1[1], 205.463974, 266.758484);
|
|
graphene_point_init (&p2[0], 183.735962, 355.968689);
|
|
graphene_point_init (&p2[1], 121.553253, 611.27655);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_LINE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, 1);
|
|
|
|
g_assert_cmpint (n, ==, 0);
|
|
}
|
|
|
|
static void
|
|
test_line_curve_intersection (void)
|
|
{
|
|
GskCurve c1, c2;
|
|
graphene_point_t p1[4], p2[2];
|
|
float t1[9], t2[9];
|
|
graphene_point_t p[9];
|
|
int n;
|
|
GskBoundingBox b;
|
|
|
|
graphene_point_init (&p1[0], 0, 100);
|
|
graphene_point_init (&p1[1], 50, 100);
|
|
graphene_point_init (&p1[2], 50, 0);
|
|
graphene_point_init (&p1[3], 100, 0);
|
|
graphene_point_init (&p2[0], 0, 0);
|
|
graphene_point_init (&p2[1], 100, 100);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CURVE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, 1);
|
|
|
|
g_assert_cmpint (n, ==, 1);
|
|
g_assert_cmpfloat_with_epsilon (t1[0], 0.5, 0.0001);
|
|
g_assert_cmpfloat_with_epsilon (t2[0], 0.5, 0.0001);
|
|
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (50, 50), 0.0001));
|
|
|
|
gsk_curve_get_tight_bounds (&c1, &b);
|
|
gsk_bounding_box_contains_point (&b, &p[0]);
|
|
|
|
gsk_curve_get_tight_bounds (&c2, &b);
|
|
gsk_bounding_box_contains_point (&b, &p[0]);
|
|
}
|
|
|
|
static void
|
|
test_line_curve_multiple_intersection (void)
|
|
{
|
|
GskCurve c1, c2;
|
|
graphene_point_t p1[4], p2[2];
|
|
float t1[9], t2[9];
|
|
graphene_point_t p[9];
|
|
graphene_point_t pp;
|
|
int n;
|
|
GskBoundingBox b1, b2;
|
|
|
|
graphene_point_init (&p1[0], 100, 200);
|
|
graphene_point_init (&p1[1], 350, 100);
|
|
graphene_point_init (&p1[2], 100, 350);
|
|
graphene_point_init (&p1[3], 400, 300);
|
|
|
|
graphene_point_init (&p2[0], 0, 0);
|
|
graphene_point_init (&p2[1], 100, 100);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CURVE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, 3);
|
|
|
|
g_assert_cmpint (n, ==, 0);
|
|
|
|
graphene_point_init (&p2[0], 0, 0);
|
|
graphene_point_init (&p2[1], 200, 200);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CURVE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, 3);
|
|
|
|
g_assert_cmpint (n, ==, 1);
|
|
|
|
g_assert_cmpfloat_with_epsilon (t1[0], 0.136196628, 0.0001);
|
|
g_assert_cmpfloat_with_epsilon (t2[0], 0.88487947, 0.0001);
|
|
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (176.975891, 176.975891), 0.001));
|
|
|
|
gsk_curve_get_point (&c1, t1[0], &pp);
|
|
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
|
|
|
|
gsk_curve_get_point (&c2, t2[0], &pp);
|
|
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
|
|
|
|
gsk_curve_get_tight_bounds (&c1, &b1);
|
|
gsk_curve_get_tight_bounds (&c2, &b2);
|
|
|
|
gsk_bounding_box_contains_point (&b1, &p[0]);
|
|
gsk_bounding_box_contains_point (&b2, &p[0]);
|
|
|
|
graphene_point_init (&p2[0], 0, 0);
|
|
graphene_point_init (&p2[1], 280, 280);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CURVE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, 3);
|
|
|
|
g_assert_cmpint (n, ==, 2);
|
|
|
|
g_assert_cmpfloat_with_epsilon (t1[0], 0.136196628, 0.0001);
|
|
g_assert_cmpfloat_with_epsilon (t2[0], 0.632056773, 0.0001);
|
|
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (176.975891, 176.975891), 0.001));
|
|
|
|
gsk_curve_get_point (&c1, t1[0], &pp);
|
|
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
|
|
|
|
gsk_curve_get_point (&c2, t2[0], &pp);
|
|
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
|
|
|
|
g_assert_cmpfloat_with_epsilon (t1[1], 0.499999911, 0.0001);
|
|
g_assert_cmpfloat_with_epsilon (t2[1], 0.825892806, 0.0001);
|
|
g_assert_true (graphene_point_near (&p[1], &GRAPHENE_POINT_INIT (231.25, 231.25), 0.001));
|
|
|
|
gsk_curve_get_point (&c1, t1[1], &pp);
|
|
g_assert_true (graphene_point_near (&p[1], &pp, 0.001));
|
|
|
|
gsk_curve_get_point (&c2, t2[1], &pp);
|
|
g_assert_true (graphene_point_near (&p[1], &pp, 0.001));
|
|
|
|
gsk_curve_get_tight_bounds (&c1, &b1);
|
|
gsk_curve_get_tight_bounds (&c2, &b2);
|
|
|
|
gsk_bounding_box_contains_point (&b1, &p[0]);
|
|
gsk_bounding_box_contains_point (&b1, &p[1]);
|
|
gsk_bounding_box_contains_point (&b2, &p[0]);
|
|
gsk_bounding_box_contains_point (&b2, &p[1]);
|
|
|
|
graphene_point_init (&p2[0], 0, 0);
|
|
graphene_point_init (&p2[1], 1000, 1000);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CURVE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, 3);
|
|
|
|
g_assert_cmpint (n, ==, 3);
|
|
|
|
g_assert_cmpfloat_with_epsilon (t1[0], 0.863803446, 0.0001);
|
|
g_assert_cmpfloat_with_epsilon (t2[0], 0.305377066, 0.0001);
|
|
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (305.377075, 305.377075), 0.001));
|
|
|
|
gsk_curve_get_point (&c1, t1[0], &pp);
|
|
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
|
|
|
|
gsk_curve_get_point (&c2, t2[0], &pp);
|
|
g_assert_true (graphene_point_near (&p[0], &pp, 0.001));
|
|
|
|
g_assert_cmpfloat_with_epsilon (t1[1], 0.136196628, 0.0001);
|
|
g_assert_cmpfloat_with_epsilon (t2[1], 0.176975891, 0.0001);
|
|
g_assert_true (graphene_point_near (&p[1], &GRAPHENE_POINT_INIT (176.975891, 176.975891), 0.001));
|
|
|
|
gsk_curve_get_point (&c1, t1[1], &pp);
|
|
g_assert_true (graphene_point_near (&p[1], &pp, 0.001));
|
|
|
|
gsk_curve_get_point (&c2, t2[1], &pp);
|
|
g_assert_true (graphene_point_near (&p[1], &pp, 0.001));
|
|
|
|
g_assert_cmpfloat_with_epsilon (t1[2], 0.5, 0.0001);
|
|
g_assert_cmpfloat_with_epsilon (t2[2], 0.231249988, 0.0001);
|
|
g_assert_true (graphene_point_near (&p[2], &GRAPHENE_POINT_INIT (231.249985, 231.249985), 0.001));
|
|
|
|
gsk_curve_get_point (&c1, t1[2], &pp);
|
|
g_assert_true (graphene_point_near (&p[2], &pp, 0.001));
|
|
|
|
gsk_curve_get_point (&c2, t2[2], &pp);
|
|
g_assert_true (graphene_point_near (&p[2], &pp, 0.001));
|
|
|
|
gsk_curve_get_tight_bounds (&c1, &b1);
|
|
gsk_curve_get_tight_bounds (&c2, &b2);
|
|
|
|
gsk_bounding_box_contains_point (&b1, &p[0]);
|
|
gsk_bounding_box_contains_point (&b1, &p[1]);
|
|
gsk_bounding_box_contains_point (&b1, &p[2]);
|
|
gsk_bounding_box_contains_point (&b2, &p[0]);
|
|
gsk_bounding_box_contains_point (&b2, &p[1]);
|
|
gsk_bounding_box_contains_point (&b2, &p[2]);
|
|
}
|
|
|
|
static void
|
|
test_line_curve_end_intersection (void)
|
|
{
|
|
GskCurve c1, c2;
|
|
graphene_point_t p1[4], p2[2];
|
|
float t1[9], t2[9];
|
|
graphene_point_t p[9];
|
|
int n;
|
|
|
|
graphene_point_init (&p1[0], 0, 100);
|
|
graphene_point_init (&p1[1], 50, 100);
|
|
graphene_point_init (&p1[2], 50, 0);
|
|
graphene_point_init (&p1[3], 100, 0);
|
|
graphene_point_init (&p2[0], 100, 0);
|
|
graphene_point_init (&p2[1], 100, 100);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CURVE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, 1);
|
|
|
|
g_assert_cmpint (n, ==, 1);
|
|
g_assert_cmpfloat_with_epsilon (t1[0], 1, 0.0001);
|
|
g_assert_cmpfloat_with_epsilon (t2[0], 0, 0.0001);
|
|
g_assert_true (graphene_point_near (&p[0], &GRAPHENE_POINT_INIT (100, 0), 0.0001));
|
|
}
|
|
|
|
static void
|
|
test_line_curve_none_intersection (void)
|
|
{
|
|
GskCurve c1, c2;
|
|
graphene_point_t p1[4], p2[2];
|
|
float t1[9], t2[9];
|
|
graphene_point_t p[9];
|
|
int n;
|
|
|
|
graphene_point_init (&p1[0], 333, 78);
|
|
graphene_point_init (&p1[1], 415, 78);
|
|
graphene_point_init (&p1[2], 463, 131);
|
|
graphene_point_init (&p1[3], 463, 223);
|
|
|
|
graphene_point_init (&p2[0], 520, 476);
|
|
graphene_point_init (&p2[1], 502, 418);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CURVE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, 1);
|
|
|
|
g_assert_cmpint (n, ==, 0);
|
|
}
|
|
|
|
static void
|
|
test_curve_curve_intersection (void)
|
|
{
|
|
GskCurve c1, c2;
|
|
graphene_point_t p1[4], p2[4];
|
|
float t1[9], t2[9];
|
|
graphene_point_t p[9];
|
|
int n;
|
|
GskBoundingBox b;
|
|
|
|
graphene_point_init (&p1[0], 0, 0);
|
|
graphene_point_init (&p1[1], 33.333, 100);
|
|
graphene_point_init (&p1[2], 66.667, 0);
|
|
graphene_point_init (&p1[3], 100, 100);
|
|
graphene_point_init (&p2[0], 0, 50);
|
|
graphene_point_init (&p2[1], 100, 0);
|
|
graphene_point_init (&p2[2], 20, 0); // weight 20
|
|
graphene_point_init (&p2[3], 50, 100);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CURVE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_CONIC, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, 9);
|
|
|
|
g_assert_cmpint (n, ==, 2);
|
|
g_assert_cmpfloat (t1[0], <, 0.5);
|
|
g_assert_cmpfloat (t1[1], >, 0.5);
|
|
g_assert_cmpfloat (t2[0], <, 0.5);
|
|
g_assert_cmpfloat (t2[1], >, 0.5);
|
|
|
|
gsk_curve_get_tight_bounds (&c1, &b);
|
|
gsk_bounding_box_contains_point (&b, &p[0]);
|
|
|
|
gsk_curve_get_tight_bounds (&c2, &b);
|
|
gsk_bounding_box_contains_point (&b, &p[0]);
|
|
}
|
|
|
|
static void
|
|
test_curve_curve_end_intersection (void)
|
|
{
|
|
GskCurve c1, c2;
|
|
graphene_point_t p1[4], p2[4];
|
|
float t1[9], t2[9];
|
|
graphene_point_t p[9];
|
|
int n;
|
|
|
|
graphene_point_init (&p1[0], 0, 0);
|
|
graphene_point_init (&p1[1], 33.333, 100);
|
|
graphene_point_init (&p1[2], 66.667, 0);
|
|
graphene_point_init (&p1[3], 100, 100);
|
|
graphene_point_init (&p2[0], 100, 100);
|
|
graphene_point_init (&p2[1], 100, 0);
|
|
graphene_point_init (&p2[2], 20, 0);
|
|
graphene_point_init (&p2[3], 10, 0);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CURVE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_CONIC, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, 9);
|
|
|
|
g_assert_cmpint (n, ==, 1);
|
|
g_assert_cmpfloat_with_epsilon (t1[0], 1, 0.0001);
|
|
g_assert_cmpfloat_with_epsilon (t2[0], 0, 0.0001);
|
|
}
|
|
|
|
static void
|
|
test_curve_curve_end_intersection2 (void)
|
|
{
|
|
GskCurve c, c1, c2;
|
|
graphene_point_t p1[4];
|
|
float t1[9], t2[9];
|
|
graphene_point_t p[9];
|
|
int n;
|
|
|
|
graphene_point_init (&p1[0], 200, 100);
|
|
graphene_point_init (&p1[1], 300, 300);
|
|
graphene_point_init (&p1[2], 100, 300);
|
|
graphene_point_init (&p1[3], 300, 100);
|
|
|
|
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CURVE, p1));
|
|
|
|
gsk_curve_split (&c, 0.5, &c1, &c2);
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, 9);
|
|
|
|
g_assert_cmpint (n, ==, 2);
|
|
}
|
|
|
|
static void
|
|
test_curve_curve_max_intersection (void)
|
|
{
|
|
GskCurve c1, c2;
|
|
graphene_point_t p1[4], p2[4];
|
|
float t1[9], t2[9];
|
|
graphene_point_t p[9];
|
|
int n;
|
|
|
|
graphene_point_init (&p1[0], 106, 100);
|
|
graphene_point_init (&p1[1], 118, 264);
|
|
graphene_point_init (&p1[2], 129, 4);
|
|
graphene_point_init (&p1[3], 128, 182);
|
|
|
|
graphene_point_init (&p2[0], 54, 135);
|
|
graphene_point_init (&p2[1], 263, 136);
|
|
graphene_point_init (&p2[2], 2, 143);
|
|
graphene_point_init (&p2[3], 141, 150);
|
|
|
|
gsk_curve_init (&c1, gsk_pathop_encode (GSK_PATH_CURVE, p1));
|
|
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_CURVE, p2));
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, 9);
|
|
|
|
g_assert_cmpint (n, ==, 9);
|
|
}
|
|
|
|
/* This showed up as artifacts in the stroker when our
|
|
* intersection code failed to find intersections with
|
|
* horizontal lines
|
|
*/
|
|
static void
|
|
test_curve_intersection_horizontal_line (void)
|
|
{
|
|
GskCurve c1, c2;
|
|
float t1, t2;
|
|
graphene_point_t p;
|
|
int n;
|
|
|
|
gsk_curve_init (&c1,
|
|
gsk_pathop_encode (GSK_PATH_CONIC,
|
|
(const graphene_point_t[4]) {
|
|
GRAPHENE_POINT_INIT (200.000, 165.000),
|
|
GRAPHENE_POINT_INIT (220.858, 165.000),
|
|
GRAPHENE_POINT_INIT (1.4142, 0),
|
|
GRAPHENE_POINT_INIT (292.929, 92.929),
|
|
}));
|
|
gsk_curve_init_foreach (&c2,
|
|
GSK_PATH_LINE,
|
|
(const graphene_point_t[2]) {
|
|
GRAPHENE_POINT_INIT (300, 110),
|
|
GRAPHENE_POINT_INIT (100, 110),
|
|
},
|
|
2,
|
|
0);
|
|
|
|
n = gsk_curve_intersect (&c1, &c2, &t1, &t2, &p, 1);
|
|
|
|
g_assert_true (n == 1);
|
|
}
|
|
|
|
/* Some sanity checks for splitting curves.
|
|
*/
|
|
static void
|
|
test_curve_split (void)
|
|
{
|
|
for (int i = 0; i < 100; i++)
|
|
{
|
|
GskCurve c;
|
|
GskPathBuilder *builder;
|
|
GskPath *path;
|
|
GskPathMeasure *measure;
|
|
const graphene_point_t *s;
|
|
GskCurve c1, c2;
|
|
graphene_point_t p;
|
|
graphene_vec2_t t, t1, t2;
|
|
|
|
init_random_curve (&c);
|
|
|
|
builder = gsk_path_builder_new ();
|
|
|
|
s = gsk_curve_get_start_point (&c);
|
|
gsk_path_builder_move_to (builder, s->x, s->y);
|
|
gsk_curve_builder_to (&c, builder);
|
|
path = gsk_path_builder_free_to_path (builder);
|
|
measure = gsk_path_measure_new_with_tolerance (path, 0.1);
|
|
|
|
gsk_curve_split (&c, 0.5, &c1, &c2);
|
|
|
|
g_assert_true (c1.op == c.op);
|
|
g_assert_true (c2.op == c.op);
|
|
|
|
g_assert_true (graphene_point_near (gsk_curve_get_start_point (&c),
|
|
gsk_curve_get_start_point (&c1), 0.005));
|
|
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c1),
|
|
gsk_curve_get_start_point (&c2), 0.005));
|
|
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c),
|
|
gsk_curve_get_end_point (&c2), 0.005));
|
|
gsk_curve_get_point (&c, 0.5, &p);
|
|
gsk_curve_get_tangent (&c, 0.5, &t);
|
|
g_assert_true (graphene_point_near (gsk_curve_get_end_point (&c1), &p, 0.005));
|
|
g_assert_true (graphene_point_near (gsk_curve_get_start_point (&c2), &p, 0.005));
|
|
|
|
gsk_curve_get_start_tangent (&c, &t1);
|
|
gsk_curve_get_start_tangent (&c1, &t2);
|
|
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
|
|
gsk_curve_get_end_tangent (&c1, &t1);
|
|
gsk_curve_get_start_tangent (&c2, &t2);
|
|
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
|
|
g_assert_true (graphene_vec2_near (&t, &t1, 0.005));
|
|
g_assert_true (graphene_vec2_near (&t, &t2, 0.005));
|
|
gsk_curve_get_end_tangent (&c, &t1);
|
|
gsk_curve_get_end_tangent (&c2, &t2);
|
|
g_assert_true (graphene_vec2_near (&t1, &t2, 0.005));
|
|
|
|
for (int k = 0; k < 20; k++)
|
|
{
|
|
graphene_point_t q;
|
|
float dist;
|
|
|
|
gsk_curve_get_point (&c1, k/19.0, &q);
|
|
gsk_path_measure_get_closest_point_full (measure, &q, INFINITY,
|
|
&dist, NULL, NULL, NULL);
|
|
g_assert_cmpfloat (dist, <=, 0.2);
|
|
|
|
gsk_curve_get_point (&c2, k/19.0, &q);
|
|
gsk_path_measure_get_closest_point_full (measure, &q, INFINITY,
|
|
&dist, NULL, NULL, NULL);
|
|
g_assert_cmpfloat (dist, <=, 0.2);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Test that reversing curves of all types produces the
|
|
* expected result
|
|
*/
|
|
static void
|
|
test_curve_reverse (void)
|
|
{
|
|
GskCurve c, r;
|
|
graphene_point_t p[4];
|
|
|
|
graphene_point_init (&p[0], 0, 0);
|
|
graphene_point_init (&p[1], 50, 50);
|
|
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_LINE, p));
|
|
|
|
gsk_curve_reverse (&c, &r);
|
|
|
|
g_assert_true (r.op == GSK_PATH_LINE);
|
|
g_assert_true (graphene_point_equal (&r.line.points[0], &p[1]));
|
|
g_assert_true (graphene_point_equal (&r.line.points[1], &p[0]));
|
|
|
|
graphene_point_init (&p[0], 0, 0);
|
|
graphene_point_init (&p[1], 50, 50);
|
|
graphene_point_init (&p[2], 100, 50);
|
|
graphene_point_init (&p[3], 200, 0);
|
|
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CURVE, p));
|
|
|
|
gsk_curve_reverse (&c, &r);
|
|
g_assert_true (r.op == GSK_PATH_CURVE);
|
|
g_assert_true (graphene_point_equal (&r.curve.points[0], &p[3]));
|
|
g_assert_true (graphene_point_equal (&r.curve.points[1], &p[2]));
|
|
g_assert_true (graphene_point_equal (&r.curve.points[2], &p[1]));
|
|
g_assert_true (graphene_point_equal (&r.curve.points[3], &p[0]));
|
|
|
|
graphene_point_init (&p[0], 0, 0);
|
|
graphene_point_init (&p[1], 50, 50);
|
|
graphene_point_init (&p[2], 100, 50);
|
|
gsk_curve_init_foreach (&c, GSK_PATH_CONIC, p, 3, 20);
|
|
|
|
gsk_curve_reverse (&c, &r);
|
|
g_assert_true (r.op == GSK_PATH_CONIC);
|
|
g_assert_true (r.conic.points[2].x == 20);
|
|
|
|
g_assert_true (graphene_point_equal (&r.conic.points[0], &c.conic.points[3]));
|
|
g_assert_true (graphene_point_equal (&r.conic.points[1], &c.conic.points[1]));
|
|
g_assert_true (graphene_point_equal (&r.conic.points[3], &c.conic.points[0]));
|
|
}
|
|
|
|
#define DEG_TO_RAD(x) ((x)*M_PI/180)
|
|
|
|
static float
|
|
line_point_distance (const graphene_point_t *a,
|
|
const graphene_point_t *b,
|
|
const graphene_point_t *p)
|
|
{
|
|
graphene_vec2_t n, ap, r;
|
|
|
|
graphene_vec2_init (&n, b->x - a->x, b->y - a->y);
|
|
graphene_vec2_normalize (&n, &n);
|
|
|
|
graphene_vec2_init (&ap, a->x - p->x, a->y - p->y);
|
|
|
|
graphene_vec2_scale (&n, graphene_vec2_dot (&ap, &n), &r);
|
|
|
|
graphene_vec2_subtract (&ap, &r, &r);
|
|
|
|
return graphene_vec2_length (&r);
|
|
}
|
|
|
|
/* Test simple cases of curve offsetting */
|
|
static void
|
|
test_curve_offset (void)
|
|
{
|
|
GskCurve c, r;
|
|
graphene_point_t p[4];
|
|
|
|
graphene_point_init (&p[0], 0, 0);
|
|
graphene_point_init (&p[1], 50, 0);
|
|
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_LINE, p));
|
|
|
|
gsk_curve_offset (&c, 10, &r);
|
|
|
|
g_assert_true (r.op == GSK_PATH_LINE);
|
|
g_assert_true (graphene_point_near (&r.line.points[0], &GRAPHENE_POINT_INIT (0, 10), 0.0001));
|
|
g_assert_true (graphene_point_near (&r.line.points[1], &GRAPHENE_POINT_INIT (50, 10), 0.0001));
|
|
|
|
gsk_curve_offset (&c, -10, &r);
|
|
|
|
g_assert_true (r.op == GSK_PATH_LINE);
|
|
g_assert_true (graphene_point_near (&r.line.points[0], &GRAPHENE_POINT_INIT (0, -10), 0.0001));
|
|
g_assert_true (graphene_point_near (&r.line.points[1], &GRAPHENE_POINT_INIT (50, -10), 0.0001));
|
|
|
|
graphene_point_init (&p[0], 0, 0);
|
|
graphene_point_init (&p[1], 50, 0);
|
|
graphene_point_init (&p[2], 100, 50);
|
|
graphene_point_init (&p[3], 100, 100);
|
|
|
|
gsk_curve_init (&c, gsk_pathop_encode (GSK_PATH_CURVE, p));
|
|
|
|
gsk_curve_offset (&c, 10, &r);
|
|
|
|
g_assert_true (r.op == GSK_PATH_CURVE);
|
|
g_assert_true (graphene_point_near (&r.curve.points[0], &GRAPHENE_POINT_INIT (0, 10), 0.0001));
|
|
|
|
g_assert_cmpfloat_with_epsilon (r.curve.points[1].y, 10.0, 0.001);
|
|
|
|
g_assert_cmpfloat_with_epsilon (line_point_distance (&p[0], &p[1], &r.curve.points[1]), 10.0, 0.001);
|
|
g_assert_cmpfloat_with_epsilon (line_point_distance (&p[1], &p[2], &r.curve.points[1]), 10.0, 0.001);
|
|
|
|
g_assert_cmpfloat_with_epsilon (r.curve.points[2].x, 90.0, 0.001);
|
|
|
|
g_assert_cmpfloat_with_epsilon (line_point_distance (&p[1], &p[2], &r.curve.points[2]), 10.0, 0.001);
|
|
g_assert_cmpfloat_with_epsilon (line_point_distance (&p[2], &p[3], &r.curve.points[2]), 10.0, 0.001);
|
|
|
|
g_assert_true (graphene_point_near (&r.curve.points[3], &GRAPHENE_POINT_INIT (90, 100), 0.0001));
|
|
|
|
gsk_curve_offset (&c, -10, &r);
|
|
|
|
g_assert_true (r.op == GSK_PATH_CURVE);
|
|
g_assert_true (graphene_point_near (&r.curve.points[0], &GRAPHENE_POINT_INIT (0, -10), 0.0001));
|
|
|
|
g_assert_cmpfloat_with_epsilon (r.curve.points[1].y, -10.0, 0.001);
|
|
|
|
g_assert_cmpfloat_with_epsilon (line_point_distance (&p[0], &p[1], &r.curve.points[1]), 10.0, 0.001);
|
|
g_assert_cmpfloat_with_epsilon (line_point_distance (&p[1], &p[2], &r.curve.points[1]), 10.0, 0.001);
|
|
|
|
g_assert_cmpfloat_with_epsilon (r.curve.points[2].x, 110.0, 0.001);
|
|
|
|
g_assert_cmpfloat_with_epsilon (line_point_distance (&p[1], &p[2], &r.curve.points[2]), 10.0, 0.001);
|
|
g_assert_cmpfloat_with_epsilon (line_point_distance (&p[2], &p[3], &r.curve.points[2]), 10.0, 0.001);
|
|
|
|
g_assert_true (graphene_point_near (&r.curve.points[3], &GRAPHENE_POINT_INIT (110, 100), 0.0001));
|
|
|
|
graphene_point_init (&p[0], 0, 0);
|
|
graphene_point_init (&p[1], 100, 0);
|
|
graphene_point_init (&p[2], 100, 100);
|
|
|
|
gsk_curve_init_foreach (&c, GSK_PATH_CONIC, p, 3, 20);
|
|
|
|
gsk_curve_offset (&c, 10, &r);
|
|
|
|
g_assert_true (r.op == GSK_PATH_CONIC);
|
|
|
|
g_assert_true (graphene_point_near (&r.curve.points[0], &GRAPHENE_POINT_INIT (0, 10), 0.0001));
|
|
g_assert_true (graphene_point_near (&r.curve.points[1], &GRAPHENE_POINT_INIT (90, 10), 0.0001));
|
|
g_assert_true (graphene_point_near (&r.curve.points[3], &GRAPHENE_POINT_INIT (90, 100), 0.0001));
|
|
|
|
gsk_curve_offset (&c, -10, &r);
|
|
|
|
g_assert_true (r.op == GSK_PATH_CONIC);
|
|
|
|
g_assert_true (graphene_point_near (&r.curve.points[0], &GRAPHENE_POINT_INIT (0, -10), 0.0001));
|
|
g_assert_true (graphene_point_near (&r.curve.points[1], &GRAPHENE_POINT_INIT (110, -10), 0.0001));
|
|
g_assert_true (graphene_point_near (&r.curve.points[3], &GRAPHENE_POINT_INIT (110, 100), 0.0001));
|
|
}
|
|
|
|
int
|
|
main (int argc, char *argv[])
|
|
{
|
|
g_test_init (&argc, &argv, NULL);
|
|
|
|
g_test_add_func ("/curve/points", test_curve_points);
|
|
g_test_add_func ("/curve/bounds", test_curve_bounds);
|
|
g_test_add_func ("/curve/tangents", test_curve_tangents);
|
|
g_test_add_func ("/curve/decompose", test_curve_decompose);
|
|
g_test_add_func ("/curve/decompose-curve", test_curve_decompose_curve);
|
|
g_test_add_func ("/curve/intersection/line-line", test_line_line_intersection);
|
|
g_test_add_func ("/curve/intersection/line-line-none", test_line_line_none_intersection);
|
|
g_test_add_func ("/curve/intersection/line-line-end", test_line_line_end_intersection);
|
|
g_test_add_func ("/curve/intersection/line-curve", test_line_curve_intersection);
|
|
g_test_add_func ("/curve/intersection/line-curve-end", test_line_curve_end_intersection);
|
|
g_test_add_func ("/curve/intersection/line-curve-none", test_line_curve_none_intersection);
|
|
g_test_add_func ("/curve/intersection/line-curve-multiple", test_line_curve_multiple_intersection);
|
|
g_test_add_func ("/curve/intersection/curve-curve", test_curve_curve_intersection);
|
|
g_test_add_func ("/curve/intersection/curve-curve-end", test_curve_curve_end_intersection);
|
|
g_test_add_func ("/curve/intersection/curve-curve-end2", test_curve_curve_end_intersection2);
|
|
g_test_add_func ("/curve/intersection/curve-curve-max", test_curve_curve_max_intersection);
|
|
g_test_add_func ("/curve/intersection/horizontal-line", test_curve_intersection_horizontal_line);
|
|
g_test_add_func ("/curve/split", test_curve_split);
|
|
g_test_add_func ("/curve/reverse", test_curve_reverse);
|
|
g_test_add_func ("/curve/offset", test_curve_offset);
|
|
|
|
return g_test_run ();
|
|
}
|