Compare commits
9 Commits
main
...
curve-inte
Author | SHA1 | Date | |
---|---|---|---|
|
6a1660b78e | ||
|
821fc35941 | ||
|
af0492ed0c | ||
|
dcdbb73702 | ||
|
60b2cf5d4a | ||
|
2fcb20cc4a | ||
|
89567767c5 | ||
|
6577f386b0 | ||
|
6ce8bd6581 |
@@ -339,6 +339,7 @@
|
||||
<file>path_fill.c</file>
|
||||
<file>path_maze.c</file>
|
||||
<file>path_spinner.c</file>
|
||||
<file>path_sweep.c</file>
|
||||
<file>path_walk.c</file>
|
||||
<file>path_text.c</file>
|
||||
<file>peg_solitaire.c</file>
|
||||
@@ -426,6 +427,10 @@
|
||||
<gresource prefix="/fontrendering">
|
||||
<file>fontrendering.ui</file>
|
||||
</gresource>
|
||||
<gresource prefix="/path_sweep">
|
||||
<file>path_sweep.ui</file>
|
||||
<file compressed="true">path_world.txt</file>
|
||||
</gresource>
|
||||
<gresource prefix="/path_walk">
|
||||
<file>path_walk.ui</file>
|
||||
<file compressed="true">path_world.txt</file>
|
||||
|
@@ -75,6 +75,7 @@ demos = files([
|
||||
'path_fill.c',
|
||||
'path_maze.c',
|
||||
'path_spinner.c',
|
||||
'path_sweep.c',
|
||||
'path_walk.c',
|
||||
'path_text.c',
|
||||
'peg_solitaire.c',
|
||||
|
319
demos/gtk-demo/path_sweep.c
Normal file
319
demos/gtk-demo/path_sweep.c
Normal file
@@ -0,0 +1,319 @@
|
||||
/* Path/Sweep
|
||||
*
|
||||
* This demo shows how path intersections can be used.
|
||||
*
|
||||
* The world map that is used here is a path with 211 lines and 1569 cubic
|
||||
* Bėzier segments in 121 contours.
|
||||
*/
|
||||
|
||||
#include <glib/gi18n.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#define GTK_TYPE_PATH_SWEEP (gtk_path_sweep_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GtkPathSweep, gtk_path_sweep, GTK, PATH_SWEEP, GtkWidget)
|
||||
|
||||
#define POINT_SIZE 8
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_PATH,
|
||||
N_PROPS
|
||||
};
|
||||
|
||||
struct _GtkPathSweep
|
||||
{
|
||||
GtkWidget parent_instance;
|
||||
|
||||
GskPath *path;
|
||||
graphene_rect_t bounds;
|
||||
float y_pos;
|
||||
gboolean in;
|
||||
};
|
||||
|
||||
struct _GtkPathSweepClass
|
||||
{
|
||||
GtkWidgetClass parent_class;
|
||||
};
|
||||
|
||||
static GParamSpec *properties[N_PROPS] = { NULL, };
|
||||
|
||||
G_DEFINE_TYPE (GtkPathSweep, gtk_path_sweep, GTK_TYPE_WIDGET)
|
||||
|
||||
static gboolean
|
||||
intersection_cb (GskPath *path1,
|
||||
const GskPathPoint *point1,
|
||||
GskPath *path2,
|
||||
const GskPathPoint *point2,
|
||||
GskPathIntersection kind,
|
||||
gpointer data)
|
||||
{
|
||||
GskPathBuilder *builder = data;
|
||||
graphene_point_t p;
|
||||
|
||||
gsk_path_point_get_position (point1, path1, &p);
|
||||
gsk_path_builder_add_circle (builder, &p, 4);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static GskPath *
|
||||
get_intersection_path (GskPath *path1,
|
||||
GskPath *path2)
|
||||
{
|
||||
GskPathBuilder *builder = gsk_path_builder_new ();
|
||||
|
||||
gsk_path_foreach_intersection (path1, path2, intersection_cb, builder);
|
||||
|
||||
return gsk_path_builder_to_path (builder);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_snapshot (GtkWidget *widget,
|
||||
GtkSnapshot *snapshot)
|
||||
{
|
||||
GtkPathSweep *self = GTK_PATH_SWEEP (widget);
|
||||
GskStroke *stroke;
|
||||
|
||||
if (self->path == NULL)
|
||||
return;
|
||||
|
||||
gtk_snapshot_save (snapshot);
|
||||
|
||||
stroke = gsk_stroke_new (2.0);
|
||||
|
||||
gtk_snapshot_append_stroke (snapshot, self->path, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
|
||||
|
||||
if (self->in)
|
||||
{
|
||||
graphene_rect_t bounds;
|
||||
GskPathBuilder *builder;
|
||||
GskPath *line, *isecs;
|
||||
|
||||
gsk_path_get_stroke_bounds (self->path, stroke, &bounds);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_move_to (builder, bounds.origin.x, bounds.origin.y + self->y_pos);
|
||||
gsk_path_builder_line_to (builder, bounds.origin.x + bounds.size.width, bounds.origin.y + self->y_pos);
|
||||
line = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
gtk_snapshot_append_stroke (snapshot, line, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
|
||||
|
||||
isecs = get_intersection_path (self->path, line);
|
||||
|
||||
gtk_snapshot_append_fill (snapshot, isecs, GSK_FILL_RULE_WINDING, &(GdkRGBA) { 1, 0, 0, 1 });
|
||||
gtk_snapshot_append_stroke (snapshot, isecs, stroke, &(GdkRGBA) { 0, 0, 0, 1 });
|
||||
|
||||
gsk_path_unref (isecs);
|
||||
gsk_path_unref (line);
|
||||
}
|
||||
|
||||
gsk_stroke_free (stroke);
|
||||
|
||||
gtk_snapshot_restore (snapshot);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_measure (GtkWidget *widget,
|
||||
GtkOrientation orientation,
|
||||
int for_size,
|
||||
int *minimum,
|
||||
int *natural,
|
||||
int *minimum_baseline,
|
||||
int *natural_baseline)
|
||||
{
|
||||
GtkPathSweep *self = GTK_PATH_SWEEP (widget);
|
||||
|
||||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
*minimum = *natural = (int) ceilf (self->bounds.size.width);
|
||||
else
|
||||
*minimum = *natural = (int) ceilf (self->bounds.size.height);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_set_path (GtkPathSweep *self,
|
||||
GskPath *path)
|
||||
{
|
||||
if (self->path == path)
|
||||
return;
|
||||
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
graphene_rect_init (&self->bounds, 0, 0, 0, 0);
|
||||
if (path)
|
||||
{
|
||||
GskStroke *stroke;
|
||||
|
||||
self->path = gsk_path_ref (path);
|
||||
stroke = gsk_stroke_new (2.0);
|
||||
gsk_path_get_stroke_bounds (path, stroke, &self->bounds);
|
||||
gsk_stroke_free (stroke);
|
||||
}
|
||||
|
||||
gtk_widget_queue_resize (GTK_WIDGET (self));
|
||||
|
||||
g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PATH]);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
|
||||
{
|
||||
GtkPathSweep *self = GTK_PATH_SWEEP (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PATH:
|
||||
gtk_path_sweep_set_path (self, g_value_get_boxed (value));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
GtkPathSweep *self = GTK_PATH_SWEEP (object);
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PATH:
|
||||
g_value_set_boxed (value, self->path);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_dispose (GObject *object)
|
||||
{
|
||||
GtkPathSweep *self = GTK_PATH_SWEEP (object);
|
||||
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
|
||||
G_OBJECT_CLASS (gtk_path_sweep_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_class_init (GtkPathSweepClass *klass)
|
||||
{
|
||||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = gtk_path_sweep_dispose;
|
||||
object_class->set_property = gtk_path_sweep_set_property;
|
||||
object_class->get_property = gtk_path_sweep_get_property;
|
||||
|
||||
widget_class->snapshot = gtk_path_sweep_snapshot;
|
||||
widget_class->measure = gtk_path_sweep_measure;
|
||||
|
||||
properties[PROP_PATH] =
|
||||
g_param_spec_boxed ("path", NULL, NULL,
|
||||
GSK_TYPE_PATH,
|
||||
G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS);
|
||||
|
||||
g_object_class_install_properties (object_class, N_PROPS, properties);
|
||||
}
|
||||
|
||||
static void
|
||||
motion_cb (GtkEventControllerMotion *controller,
|
||||
double x,
|
||||
double y,
|
||||
gpointer data)
|
||||
{
|
||||
GtkPathSweep *self = data;
|
||||
|
||||
self->y_pos = y;
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
enter_cb (GtkEventControllerMotion *controller,
|
||||
double x,
|
||||
double y,
|
||||
gpointer data)
|
||||
{
|
||||
GtkPathSweep *self = data;
|
||||
|
||||
self->in = TRUE;
|
||||
self->y_pos = y;
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
leave_cb (GtkEventControllerMotion *controller,
|
||||
gpointer data)
|
||||
{
|
||||
GtkPathSweep *self = data;
|
||||
|
||||
self->in = FALSE;
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
gtk_path_sweep_init (GtkPathSweep *self)
|
||||
{
|
||||
GtkEventController *controller;
|
||||
|
||||
/* Data taken from
|
||||
* https://commons.wikimedia.org/wiki/Maps_of_the_world#/media/File:Simplified_blank_world_map_without_Antartica_(no_borders).svg
|
||||
*/
|
||||
GBytes *data = g_resources_lookup_data ("/path_sweep/path_world.txt", 0, NULL);
|
||||
GskPath *path = gsk_path_parse (g_bytes_get_data (data, NULL));
|
||||
g_bytes_unref (data);
|
||||
gtk_path_sweep_set_path (self, path);
|
||||
gsk_path_unref (path);
|
||||
|
||||
controller = gtk_event_controller_motion_new ();
|
||||
g_signal_connect (controller, "motion", G_CALLBACK (motion_cb), self);
|
||||
g_signal_connect (controller, "enter", G_CALLBACK (enter_cb), self);
|
||||
g_signal_connect (controller, "leave", G_CALLBACK (leave_cb), self);
|
||||
gtk_widget_add_controller (GTK_WIDGET (self), controller);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
gtk_path_sweep_new (void)
|
||||
{
|
||||
GtkPathSweep *self;
|
||||
|
||||
self = g_object_new (GTK_TYPE_PATH_SWEEP, NULL);
|
||||
|
||||
return GTK_WIDGET (self);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
do_path_sweep (GtkWidget *do_widget)
|
||||
{
|
||||
static GtkWidget *window = NULL;
|
||||
|
||||
if (!window)
|
||||
{
|
||||
GtkBuilder *builder;
|
||||
|
||||
g_type_ensure (GTK_TYPE_PATH_SWEEP);
|
||||
|
||||
builder = gtk_builder_new_from_resource ("/path_sweep/path_sweep.ui");
|
||||
window = GTK_WIDGET (gtk_builder_get_object (builder, "window"));
|
||||
gtk_window_set_display (GTK_WINDOW (window),
|
||||
gtk_widget_get_display (do_widget));
|
||||
g_object_add_weak_pointer (G_OBJECT (window), (gpointer *) &window);
|
||||
g_object_unref (builder);
|
||||
}
|
||||
|
||||
if (!gtk_widget_get_visible (window))
|
||||
gtk_window_present (GTK_WINDOW (window));
|
||||
else
|
||||
gtk_window_destroy (GTK_WINDOW (window));
|
||||
|
||||
return window;
|
||||
}
|
17
demos/gtk-demo/path_sweep.ui
Normal file
17
demos/gtk-demo/path_sweep.ui
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<object class="GtkWindow" id="window">
|
||||
<property name="title" translatable="yes">World Map</property>
|
||||
<property name="child">
|
||||
<object class="GtkBox">
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkPathSweep" id="view">
|
||||
<property name="hexpand">true</property>
|
||||
<property name="vexpand">true</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</property>
|
||||
</object>
|
||||
</interface>
|
@@ -1749,6 +1749,19 @@ gsk_circle_contour_new (const graphene_point_t *center,
|
||||
return (GskContour *) self;
|
||||
}
|
||||
|
||||
void
|
||||
gsk_circle_contour_get_params (const GskContour *contour,
|
||||
graphene_point_t *center,
|
||||
float *radius,
|
||||
gboolean *ccw)
|
||||
{
|
||||
const GskCircleContour *self = (const GskCircleContour *) contour;
|
||||
|
||||
*center = self->center;
|
||||
*radius = self->radius;
|
||||
*ccw = self->ccw;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ Rectangle */
|
||||
|
||||
|
@@ -36,6 +36,10 @@ GskContour * gsk_standard_contour_new (GskPathFlags
|
||||
|
||||
GskContour * gsk_circle_contour_new (const graphene_point_t *center,
|
||||
float radius);
|
||||
void gsk_circle_contour_get_params (const GskContour *contour,
|
||||
graphene_point_t *center,
|
||||
float *radius,
|
||||
gboolean *ccw);
|
||||
GskContour * gsk_rect_contour_new (const graphene_rect_t *rect);
|
||||
GskContour * gsk_rounded_rect_contour_new (const GskRoundedRect *rounded_rect);
|
||||
|
||||
|
205
gsk/gskcurve.c
205
gsk/gskcurve.c
@@ -2330,6 +2330,30 @@ gsk_curve_get_crossing (const GskCurve *curve,
|
||||
return get_class (curve->op)->get_crossing (curve, point);
|
||||
}
|
||||
|
||||
float
|
||||
gsk_curve_get_length_to (const GskCurve *curve,
|
||||
float t)
|
||||
{
|
||||
return get_class (curve->op)->get_length_to (curve, t);
|
||||
}
|
||||
|
||||
float
|
||||
gsk_curve_get_length (const GskCurve *curve)
|
||||
{
|
||||
return gsk_curve_get_length_to (curve, 1);
|
||||
}
|
||||
|
||||
float
|
||||
gsk_curve_at_length (const GskCurve *curve,
|
||||
float length,
|
||||
float epsilon)
|
||||
{
|
||||
return get_class (curve->op)->get_at_length (curve, length, epsilon);
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
/* {{{ Closest point */
|
||||
|
||||
static gboolean
|
||||
project_point_onto_line (const GskCurve *curve,
|
||||
const graphene_point_t *point,
|
||||
@@ -2451,187 +2475,6 @@ gsk_curve_get_closest_point (const GskCurve *curve,
|
||||
return find_closest_point (curve, point, threshold, 0, 1, out_dist, out_t);
|
||||
}
|
||||
|
||||
float
|
||||
gsk_curve_get_length_to (const GskCurve *curve,
|
||||
float t)
|
||||
{
|
||||
return get_class (curve->op)->get_length_to (curve, t);
|
||||
}
|
||||
|
||||
float
|
||||
gsk_curve_get_length (const GskCurve *curve)
|
||||
{
|
||||
return gsk_curve_get_length_to (curve, 1);
|
||||
}
|
||||
|
||||
/* Compute the inverse of the arclength using bisection,
|
||||
* to a given precision
|
||||
*/
|
||||
float
|
||||
gsk_curve_at_length (const GskCurve *curve,
|
||||
float length,
|
||||
float epsilon)
|
||||
{
|
||||
return get_class (curve->op)->get_at_length (curve, length, epsilon);
|
||||
}
|
||||
|
||||
static inline void
|
||||
_sincosf (float angle,
|
||||
float *out_s,
|
||||
float *out_c)
|
||||
{
|
||||
#ifdef HAVE_SINCOSF
|
||||
sincosf (angle, out_s, out_c);
|
||||
#else
|
||||
*out_s = sinf (angle);
|
||||
*out_c = cosf (angle);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
align_points (const graphene_point_t *p,
|
||||
const graphene_point_t *a,
|
||||
const graphene_point_t *b,
|
||||
graphene_point_t *q,
|
||||
int n)
|
||||
{
|
||||
graphene_vec2_t n1;
|
||||
float angle;
|
||||
float s, c;
|
||||
|
||||
get_tangent (a, b, &n1);
|
||||
angle = - atan2f (graphene_vec2_get_y (&n1), graphene_vec2_get_x (&n1));
|
||||
_sincosf (angle, &s, &c);
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
q[i].x = (p[i].x - a->x) * c - (p[i].y - a->y) * s;
|
||||
q[i].y = (p[i].x - a->x) * s + (p[i].y - a->y) * c;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
filter_allowable (float t[3],
|
||||
int n)
|
||||
{
|
||||
float g[3];
|
||||
int j = 0;
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
if (0 < t[i] && t[i] < 1)
|
||||
g[j++] = t[i];
|
||||
for (int i = 0; i < j; i++)
|
||||
t[i] = g[i];
|
||||
return j;
|
||||
}
|
||||
|
||||
/* find solutions for at^2 + bt + c = 0 */
|
||||
static int
|
||||
solve_quadratic (float a, float b, float c, float t[2])
|
||||
{
|
||||
float d;
|
||||
int n = 0;
|
||||
|
||||
if (fabsf (a) > 0.0001)
|
||||
{
|
||||
if (b*b > 4*a*c)
|
||||
{
|
||||
d = sqrtf (b*b - 4*a*c);
|
||||
t[n++] = (-b + d)/(2*a);
|
||||
t[n++] = (-b - d)/(2*a);
|
||||
}
|
||||
else
|
||||
{
|
||||
t[n++] = -b / (2*a);
|
||||
}
|
||||
}
|
||||
else if (fabsf (b) > 0.0001)
|
||||
{
|
||||
t[n++] = -c / b;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
int
|
||||
gsk_curve_get_curvature_points (const GskCurve *curve,
|
||||
float t[3])
|
||||
{
|
||||
const graphene_point_t *pts = curve->cubic.points;
|
||||
graphene_point_t p[4];
|
||||
float a, b, c, d;
|
||||
float x, y, z;
|
||||
int n;
|
||||
|
||||
if (curve->op != GSK_PATH_CUBIC)
|
||||
return 0; /* FIXME */
|
||||
|
||||
align_points (pts, &pts[0], &pts[3], p, 4);
|
||||
|
||||
a = p[2].x * p[1].y;
|
||||
b = p[3].x * p[1].y;
|
||||
c = p[1].x * p[2].y;
|
||||
d = p[3].x * p[2].y;
|
||||
|
||||
x = - 3*a + 2*b + 3*c - d;
|
||||
y = 3*a - b - 3*c;
|
||||
z = c - a;
|
||||
|
||||
n = solve_quadratic (x, y, z, t);
|
||||
return filter_allowable (t, n);
|
||||
}
|
||||
|
||||
/* Find cusps inside the open interval from 0 to 1.
|
||||
*
|
||||
* According to Stone & deRose, A Geometric Characterization
|
||||
* of Parametric Cubic curves, a necessary and sufficient
|
||||
* condition is that the first derivative vanishes.
|
||||
*/
|
||||
int
|
||||
gsk_curve_get_cusps (const GskCurve *curve,
|
||||
float t[2])
|
||||
{
|
||||
const graphene_point_t *pts = curve->cubic.points;
|
||||
graphene_point_t p[3];
|
||||
float ax, bx, cx;
|
||||
float ay, by, cy;
|
||||
float tx[3];
|
||||
int nx;
|
||||
int n = 0;
|
||||
|
||||
if (curve->op != GSK_PATH_CUBIC)
|
||||
return 0;
|
||||
|
||||
p[0].x = 3 * (pts[1].x - pts[0].x);
|
||||
p[0].y = 3 * (pts[1].y - pts[0].y);
|
||||
p[1].x = 3 * (pts[2].x - pts[1].x);
|
||||
p[1].y = 3 * (pts[2].y - pts[1].y);
|
||||
p[2].x = 3 * (pts[3].x - pts[2].x);
|
||||
p[2].y = 3 * (pts[3].y - pts[2].y);
|
||||
|
||||
ax = p[0].x - 2 * p[1].x + p[2].x;
|
||||
bx = - 2 * p[0].x + 2 * p[1].x;
|
||||
cx = p[0].x;
|
||||
|
||||
nx = solve_quadratic (ax, bx, cx, tx);
|
||||
nx = filter_allowable (tx, nx);
|
||||
|
||||
ay = p[0].y - 2 * p[1].y + p[2].y;
|
||||
by = - 2 * p[0].y + 2 * p[1].y;
|
||||
cy = p[0].y;
|
||||
|
||||
for (int i = 0; i < nx; i++)
|
||||
{
|
||||
float ti = tx[i];
|
||||
|
||||
if (0 < ti && ti < 1 &&
|
||||
fabsf (ay * ti * ti + by * ti + cy) < 0.001)
|
||||
t[n++] = ti;
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
/* }}} */
|
||||
|
||||
/* vim:set foldmethod=marker expandtab: */
|
||||
|
1115
gsk/gskcurveintersect.c
Normal file
1115
gsk/gskcurveintersect.c
Normal file
File diff suppressed because it is too large
Load Diff
@@ -187,6 +187,19 @@ int gsk_curve_get_curvature_points (const GskCurve
|
||||
int gsk_curve_get_cusps (const GskCurve *curve,
|
||||
float t[2]);
|
||||
|
||||
int gsk_curve_intersect (const GskCurve *curve1,
|
||||
const GskCurve *curve2,
|
||||
float *t1,
|
||||
float *t2,
|
||||
graphene_point_t *p,
|
||||
GskPathIntersection *kind,
|
||||
int n);
|
||||
|
||||
int gsk_curve_self_intersect (const GskCurve *curve,
|
||||
float *t1,
|
||||
graphene_point_t *p,
|
||||
int n);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
@@ -144,6 +144,55 @@ gboolean gsk_path_foreach (GskPath
|
||||
GskPathForeachFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
/**
|
||||
* GskPathIntersection:
|
||||
* @GSK_PATH_INTERSECTION_NONE: No intersection
|
||||
* @GSK_PATH_INTERSECTION_NORMAL: A normal intersection, where the two paths
|
||||
* cross each other
|
||||
* @GSK_PATH_INTERSECTION_START: The start of a segment where the two paths coincide
|
||||
* @GSK_PATH_INTERSECTION_END: The end of a segment where the two paths coincide
|
||||
*
|
||||
* The values of this enumeration classify intersections
|
||||
* between paths.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
GSK_PATH_INTERSECTION_NONE,
|
||||
GSK_PATH_INTERSECTION_NORMAL,
|
||||
GSK_PATH_INTERSECTION_START,
|
||||
GSK_PATH_INTERSECTION_END,
|
||||
} GskPathIntersection;
|
||||
|
||||
/**
|
||||
* GskPathIntersectionFunc:
|
||||
* @path1: the first path
|
||||
* @point1: the intersection as point on @path1
|
||||
* @path2: the second path
|
||||
* @point2: the intersection as point on @path2
|
||||
* @kind: specify the nature of the intersection
|
||||
* @user_data: user data
|
||||
*
|
||||
* Prototype of the callback to iterate through the
|
||||
* intersections of two paths.
|
||||
*
|
||||
* Returns: %TRUE to continue iterating, %FALSE to
|
||||
* immediately abort and not call the function again
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
typedef gboolean (* GskPathIntersectionFunc) (GskPath *path1,
|
||||
const GskPathPoint *point1,
|
||||
GskPath *path2,
|
||||
const GskPathPoint *point2,
|
||||
GskPathIntersection kind,
|
||||
gpointer user_data);
|
||||
|
||||
GDK_AVAILABLE_IN_4_14
|
||||
gboolean gsk_path_foreach_intersection (GskPath *path1,
|
||||
GskPath *path2,
|
||||
GskPathIntersectionFunc func,
|
||||
gpointer user_data);
|
||||
|
||||
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GskPath, gsk_path_unref)
|
||||
|
||||
G_END_DECLS
|
||||
|
705
gsk/gskpathintersect.c
Normal file
705
gsk/gskpathintersect.c
Normal file
@@ -0,0 +1,705 @@
|
||||
/*
|
||||
* Copyright © 2020 Benjamin Otte
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Authors: Benjamin Otte <otte@gnome.org>
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "gskpathprivate.h"
|
||||
|
||||
#include "gskcurveprivate.h"
|
||||
#include "gskpathbuilder.h"
|
||||
#include "gskpathpoint.h"
|
||||
#include "gskcontourprivate.h"
|
||||
|
||||
typedef struct
|
||||
{
|
||||
gsize count;
|
||||
gboolean closed;
|
||||
gboolean z_is_empty;
|
||||
} CountCurveData;
|
||||
|
||||
static gboolean
|
||||
count_cb (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight,
|
||||
gpointer data)
|
||||
{
|
||||
CountCurveData *ccd = data;
|
||||
|
||||
(ccd->count)++;
|
||||
|
||||
if (op ==GSK_PATH_CLOSE)
|
||||
{
|
||||
ccd->closed = TRUE;
|
||||
ccd->z_is_empty = graphene_point_equal (&pts[0], &pts[1]);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gsize
|
||||
count_curves (const GskContour *contour,
|
||||
gboolean *closed,
|
||||
gboolean *z_is_empty)
|
||||
{
|
||||
CountCurveData data;
|
||||
|
||||
data.count = 0;
|
||||
data.closed = FALSE;
|
||||
data.z_is_empty = FALSE;
|
||||
|
||||
gsk_contour_foreach (contour, count_cb, &data);
|
||||
|
||||
*closed = data.closed;
|
||||
*z_is_empty = data.z_is_empty;
|
||||
|
||||
return data.count;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GskPathPoint point1;
|
||||
GskPathPoint point2;
|
||||
GskPathIntersection kind;
|
||||
} Intersection;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GskPath *path1;
|
||||
GskPath *path2;
|
||||
GskPathIntersectionFunc func;
|
||||
gpointer data;
|
||||
|
||||
gsize contour1;
|
||||
gsize contour2;
|
||||
gsize idx1;
|
||||
gsize idx2;
|
||||
|
||||
const GskContour *c1;
|
||||
const GskContour *c2;
|
||||
GskCurve curve1;
|
||||
GskCurve curve2;
|
||||
|
||||
gboolean c1_closed;
|
||||
gboolean c2_closed;
|
||||
gboolean c1_z_is_empty;
|
||||
gboolean c2_z_is_empty;
|
||||
|
||||
gsize c1_count;
|
||||
gsize c2_count;
|
||||
|
||||
GArray *points;
|
||||
GArray *all_points;
|
||||
} PathIntersectData;
|
||||
|
||||
static gboolean
|
||||
intersect_curve2 (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight,
|
||||
gpointer data)
|
||||
{
|
||||
PathIntersectData *pd = data;
|
||||
float t1[10], t2[10];
|
||||
graphene_point_t p[10];
|
||||
GskPathIntersection kind[10];
|
||||
int n;
|
||||
|
||||
if (op == GSK_PATH_MOVE)
|
||||
{
|
||||
if (gsk_contour_get_n_ops (pd->c2) == 1)
|
||||
{
|
||||
float dist, tt;
|
||||
|
||||
if (gsk_curve_get_closest_point (&pd->curve1, &pts[0], 1, &dist, &tt) && dist == 0)
|
||||
{
|
||||
Intersection is;
|
||||
|
||||
is.kind = GSK_PATH_INTERSECTION_NORMAL;
|
||||
is.point1.contour = pd->contour1;
|
||||
is.point1.idx = pd->idx1;
|
||||
is.point1.t = tt;
|
||||
is.point2.contour = pd->contour2;
|
||||
is.point2.idx = 0;
|
||||
is.point2.t = 1;
|
||||
|
||||
g_array_append_val (pd->points, is);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (op == GSK_PATH_CLOSE)
|
||||
{
|
||||
if (graphene_point_equal (&pts[0], &pts[1]))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
pd->idx2++;
|
||||
|
||||
gsk_curve_init_foreach (&pd->curve2, op, pts, n_pts, weight);
|
||||
|
||||
n = gsk_curve_intersect (&pd->curve1, &pd->curve2, t1, t2, p, kind, 19);
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
Intersection is;
|
||||
|
||||
is.point1.contour = pd->contour1;
|
||||
is.point2.contour = pd->contour2;
|
||||
is.point1.idx = pd->idx1;
|
||||
is.point2.idx = pd->idx2;
|
||||
is.point1.t = t1[i];
|
||||
is.point2.t = t2[i];
|
||||
is.kind = kind[i];
|
||||
|
||||
#if 0
|
||||
g_print ("append p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
|
||||
is.point1.contour, is.point1.idx, is.point1.t,
|
||||
is.point2.contour, is.point2.idx, is.point2.t,
|
||||
kn[is.kind]);
|
||||
#endif
|
||||
g_array_append_val (pd->points, is);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
intersect_curve (GskPathOperation op,
|
||||
const graphene_point_t *pts,
|
||||
gsize n_pts,
|
||||
float weight,
|
||||
gpointer data)
|
||||
{
|
||||
PathIntersectData *pd = data;
|
||||
GskBoundingBox b1, b2;
|
||||
|
||||
if (op == GSK_PATH_MOVE)
|
||||
{
|
||||
if (gsk_contour_get_n_ops (pd->c1) == 1)
|
||||
{
|
||||
GskPathPoint point;
|
||||
float dist;
|
||||
|
||||
if (gsk_contour_get_closest_point (pd->c2, &pts[0], 1, &point, &dist) && dist == 0)
|
||||
{
|
||||
Intersection is;
|
||||
|
||||
is.kind = GSK_PATH_INTERSECTION_NORMAL;
|
||||
is.point1.contour = pd->contour1;
|
||||
is.point1.idx = 0;
|
||||
is.point1.t = 1;
|
||||
is.point2.contour = pd->contour2;
|
||||
is.point2.idx = point.idx;
|
||||
is.point2.t = point.t;
|
||||
|
||||
g_array_append_val (pd->points, is);
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (op == GSK_PATH_CLOSE)
|
||||
{
|
||||
if (graphene_point_equal (&pts[0], &pts[1]))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
pd->idx1++;
|
||||
|
||||
gsk_curve_init_foreach (&pd->curve1, op, pts, n_pts, weight);
|
||||
gsk_curve_get_bounds (&pd->curve1, &b1);
|
||||
|
||||
gsk_contour_get_bounds (pd->c2, &b2);
|
||||
|
||||
if (gsk_bounding_box_intersection (&b1, &b2, NULL))
|
||||
{
|
||||
pd->idx2 = 0;
|
||||
|
||||
if (!gsk_contour_foreach (pd->c2, intersect_curve2, pd))
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_path_point_near (const GskPathPoint *p1,
|
||||
const GskPathPoint *p2,
|
||||
gboolean closed,
|
||||
gsize count,
|
||||
gboolean z_is_empty,
|
||||
float epsilon)
|
||||
{
|
||||
if (p1->idx == p2->idx && fabsf (p1->t - p2->t) < epsilon)
|
||||
return TRUE;
|
||||
|
||||
if (p1->idx + 1 == p2->idx && (1 - p1->t + p2->t < epsilon))
|
||||
return TRUE;
|
||||
|
||||
if (p2->idx + 1 == p1->idx && (1 - p2->t + p1->t < epsilon))
|
||||
return TRUE;
|
||||
|
||||
if (closed)
|
||||
{
|
||||
if (p1->idx == 1 && p2->idx == count - 1 && (1 - p2->t + p1->t < epsilon))
|
||||
return TRUE;
|
||||
|
||||
if (p2->idx == 1 && p1->idx == count - 1 && (1 - p1->t + p2->t < epsilon))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (closed && z_is_empty)
|
||||
{
|
||||
if (p1->idx == 1 && p2->idx == count - 2 && (1 - p2->t + p1->t < epsilon))
|
||||
return TRUE;
|
||||
|
||||
if (p2->idx == 1 && p1->idx == count - 2 && (1 - p1->t + p2->t < epsilon))
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static int cmp_path1 (gconstpointer p1, gconstpointer p2);
|
||||
|
||||
static void
|
||||
default_contour_collect_intersections (const GskContour *contour1,
|
||||
const GskContour *contour2,
|
||||
PathIntersectData *pd)
|
||||
{
|
||||
pd->idx1 = 0;
|
||||
|
||||
g_array_set_size (pd->points, 0);
|
||||
|
||||
gsk_contour_foreach (contour1, intersect_curve, pd);
|
||||
|
||||
g_array_sort (pd->points, cmp_path1);
|
||||
|
||||
#if 0
|
||||
g_print ("after sorting\n");
|
||||
for (gsize i = 0; i < pd->points->len; i++)
|
||||
{
|
||||
Intersection *is = &g_array_index (pd->points, Intersection, i);
|
||||
const char *kn[] = { "none", "normal", "start", "end" };
|
||||
|
||||
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
|
||||
is->point1.contour, is->point1.idx, is->point1.t,
|
||||
is->point2.contour, is->point2.idx, is->point2.t,
|
||||
kn[is->kind]);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (gsize i = 0; i < pd->points->len; i++)
|
||||
{
|
||||
Intersection *is1 = &g_array_index (pd->points, Intersection, i);
|
||||
|
||||
for (gsize j = i + 1; j < pd->points->len; j++)
|
||||
{
|
||||
Intersection *is2 = &g_array_index (pd->points, Intersection, j);
|
||||
|
||||
if (!gsk_path_point_near (&is1->point1, &is2->point1,
|
||||
pd->c1_closed, pd->c1_count, pd->c1_z_is_empty,
|
||||
0.001))
|
||||
continue;
|
||||
|
||||
if (!gsk_path_point_near (&is1->point2, &is2->point2,
|
||||
pd->c2_closed, pd->c2_count, pd->c2_z_is_empty,
|
||||
0.001))
|
||||
continue;
|
||||
|
||||
if (is1->kind == GSK_PATH_INTERSECTION_NORMAL && is2->kind != GSK_PATH_INTERSECTION_NONE)
|
||||
is1->kind = GSK_PATH_INTERSECTION_NONE;
|
||||
else if (is2->kind == GSK_PATH_INTERSECTION_NORMAL && is1->kind != GSK_PATH_INTERSECTION_NONE)
|
||||
is2->kind = GSK_PATH_INTERSECTION_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
g_print ("after collapsing\n");
|
||||
for (gsize i = 0; i < pd->points->len; i++)
|
||||
{
|
||||
Intersection *is = &g_array_index (pd->points, Intersection, i);
|
||||
const char *kn[] = { "none", "normal", "start", "end" };
|
||||
|
||||
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
|
||||
is->point1.contour, is->point1.idx, is->point1.t,
|
||||
is->point2.contour, is->point2.idx, is->point2.t,
|
||||
kn[is->kind]);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (gsize i = 0; i < pd->points->len; i++)
|
||||
{
|
||||
Intersection *is1 = &g_array_index (pd->points, Intersection, i);
|
||||
|
||||
for (gsize j = i + 1; j < pd->points->len; j++)
|
||||
{
|
||||
Intersection *is2 = &g_array_index (pd->points, Intersection, j);
|
||||
|
||||
if (!gsk_path_point_near (&is1->point1, &is2->point1, FALSE, 0, FALSE, 0.001))
|
||||
break;
|
||||
|
||||
if (!gsk_path_point_near (&is1->point2, &is2->point2,
|
||||
pd->c2_closed, pd->c2_count, pd->c2_z_is_empty,
|
||||
0.001))
|
||||
break;
|
||||
|
||||
if ((is1->kind == GSK_PATH_INTERSECTION_END &&
|
||||
is2->kind == GSK_PATH_INTERSECTION_START) ||
|
||||
(is1->kind == GSK_PATH_INTERSECTION_START &&
|
||||
is2->kind == GSK_PATH_INTERSECTION_END))
|
||||
{
|
||||
is1->kind = GSK_PATH_INTERSECTION_NONE;
|
||||
is2->kind = GSK_PATH_INTERSECTION_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
g_print ("after merging segments\n");
|
||||
for (gsize i = 0; i < pd->points->len; i++)
|
||||
{
|
||||
Intersection *is = &g_array_index (pd->points, Intersection, i);
|
||||
const char *kn[] = { "none", "normal", "start", "end" };
|
||||
|
||||
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
|
||||
is->point1.contour, is->point1.idx, is->point1.t,
|
||||
is->point2.contour, is->point2.idx, is->point2.t,
|
||||
kn[is->kind]);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (gsize j = 0; j < pd->points->len; j++)
|
||||
{
|
||||
Intersection *is = &g_array_index (pd->points, Intersection, j);
|
||||
|
||||
if (is->kind != GSK_PATH_INTERSECTION_NONE)
|
||||
g_array_append_val (pd->all_points, *is);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
circle_intersect (const graphene_point_t *center1,
|
||||
float radius1,
|
||||
const graphene_point_t *center2,
|
||||
float radius2,
|
||||
graphene_point_t points[2])
|
||||
{
|
||||
float d;
|
||||
float a, h;
|
||||
graphene_point_t m;
|
||||
graphene_vec2_t n;
|
||||
|
||||
g_assert (radius1 >= 0);
|
||||
g_assert (radius2 >= 0);
|
||||
|
||||
d = graphene_point_distance (center1, center2, NULL, NULL);
|
||||
|
||||
if (d < fabsf (radius1 - radius2))
|
||||
return 0;
|
||||
|
||||
if (d > radius1 + radius2)
|
||||
return 0;
|
||||
|
||||
if (d == radius1 + radius2)
|
||||
{
|
||||
graphene_point_interpolate (center1, center2, radius1 / (radius1 + radius2), &points[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
a = (radius1*radius1 - radius2*radius2 + d*d)/(2*d);
|
||||
h = sqrtf (radius1*radius1 - a*a);
|
||||
|
||||
graphene_point_interpolate (center1, center2, a/d, &m);
|
||||
graphene_vec2_init (&n, center2->y - center1->y, center1->x - center2->x);
|
||||
graphene_vec2_normalize (&n, &n);
|
||||
|
||||
graphene_point_init (&points[0], m.x + graphene_vec2_get_x (&n) * h,
|
||||
m.y + graphene_vec2_get_y (&n) * h);
|
||||
graphene_point_init (&points[1], m.x - graphene_vec2_get_x (&n) * h,
|
||||
m.y - graphene_vec2_get_y (&n) * h);
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
static void
|
||||
circle_contour_collect_intersections (const GskContour *contour1,
|
||||
const GskContour *contour2,
|
||||
PathIntersectData *pd)
|
||||
{
|
||||
graphene_point_t center1, center2;
|
||||
float radius1, radius2;
|
||||
gboolean ccw1, ccw2;
|
||||
graphene_point_t p[2];
|
||||
int n;
|
||||
Intersection is[2];
|
||||
|
||||
gsk_circle_contour_get_params (contour1, ¢er1, &radius1, &ccw1);
|
||||
gsk_circle_contour_get_params (contour2, ¢er2, &radius2, &ccw2);
|
||||
|
||||
if (graphene_point_equal (¢er1, ¢er2) && radius1 == radius2)
|
||||
{
|
||||
is[0].kind = GSK_PATH_INTERSECTION_START;
|
||||
is[0].point1.contour = pd->contour1;
|
||||
is[0].point1.idx = 1;
|
||||
is[0].point1.t = 0;
|
||||
is[0].point2.contour = pd->contour2;
|
||||
is[0].point2.idx = 1;
|
||||
is[0].point2.t = 0;
|
||||
|
||||
is[1].kind = GSK_PATH_INTERSECTION_END;
|
||||
is[1].point1.contour = pd->contour1;
|
||||
is[1].point1.idx = 1;
|
||||
is[1].point1.t = 1;
|
||||
is[1].point2.contour = pd->contour2;
|
||||
is[1].point2.idx = 1;
|
||||
is[1].point2.t = 1;
|
||||
|
||||
if (ccw1 != ccw2)
|
||||
{
|
||||
is[0].point2.t = 1;
|
||||
is[1].point2.t = 0;
|
||||
}
|
||||
|
||||
g_array_append_val (pd->all_points, is[0]);
|
||||
g_array_append_val (pd->all_points, is[1]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
n = circle_intersect (¢er1, radius1, ¢er2, radius2, p);
|
||||
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
float d;
|
||||
|
||||
is[i].kind = GSK_PATH_INTERSECTION_NORMAL;
|
||||
is[i].point1.contour = pd->contour1;
|
||||
is[i].point2.contour = pd->contour2;
|
||||
|
||||
gsk_contour_get_closest_point (contour1, &p[i], 1, &is[i].point1, &d);
|
||||
gsk_contour_get_closest_point (contour2, &p[i], 1, &is[i].point2, &d);
|
||||
}
|
||||
|
||||
if (n == 1)
|
||||
{
|
||||
g_array_append_val (pd->all_points, is[0]);
|
||||
}
|
||||
else if (n == 2)
|
||||
{
|
||||
if (gsk_path_point_compare (&is[0].point1, &is[1].point1) < 0)
|
||||
{
|
||||
g_array_append_val (pd->all_points, is[0]);
|
||||
g_array_append_val (pd->all_points, is[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_array_append_val (pd->all_points, is[1]);
|
||||
g_array_append_val (pd->all_points, is[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
contour_collect_intersections (const GskContour *contour1,
|
||||
const GskContour *contour2,
|
||||
PathIntersectData *pd)
|
||||
{
|
||||
if (strcmp (gsk_contour_get_type_name (contour1), "GskCircleContour") == 0 &&
|
||||
strcmp (gsk_contour_get_type_name (contour2), "GskCircleContour") == 0)
|
||||
circle_contour_collect_intersections (contour1, contour2, pd);
|
||||
else
|
||||
default_contour_collect_intersections (contour1, contour2, pd);
|
||||
}
|
||||
|
||||
static int
|
||||
cmp_path1 (gconstpointer p1,
|
||||
gconstpointer p2)
|
||||
{
|
||||
const Intersection *i1 = p1;
|
||||
const Intersection *i2 = p2;
|
||||
int i;
|
||||
|
||||
i = gsk_path_point_compare (&i1->point1, &i2->point1);
|
||||
if (i != 0)
|
||||
return i;
|
||||
|
||||
return gsk_path_point_compare (&i1->point2, &i2->point2);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
contour_foreach_intersection (const GskContour *contour1,
|
||||
PathIntersectData *pd)
|
||||
{
|
||||
GskBoundingBox b1, b2;
|
||||
|
||||
gsk_contour_get_bounds (contour1, &b1);
|
||||
|
||||
g_array_set_size (pd->all_points, 0);
|
||||
|
||||
for (gsize i = 0; i < gsk_path_get_n_contours (pd->path2); i++)
|
||||
{
|
||||
const GskContour *contour2 = gsk_path_get_contour (pd->path2, i);
|
||||
|
||||
gsk_contour_get_bounds (contour1, &b2);
|
||||
|
||||
if (gsk_bounding_box_intersection (&b1, &b2, NULL))
|
||||
{
|
||||
pd->contour2 = i;
|
||||
pd->c2 = contour2;
|
||||
pd->c2_count = count_curves (contour2, &pd->c2_closed, &pd->c2_z_is_empty);
|
||||
|
||||
contour_collect_intersections (contour1, contour2, pd);
|
||||
}
|
||||
}
|
||||
|
||||
g_array_sort (pd->all_points, cmp_path1);
|
||||
|
||||
#if 0
|
||||
g_print ("after sorting\n");
|
||||
for (gsize i = 0; i < pd->all_points->len; i++)
|
||||
{
|
||||
Intersection *is = &g_array_index (pd->all_points, Intersection, i);
|
||||
const char *kn[] = { "none", "normal", "start", "end" };
|
||||
|
||||
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
|
||||
is->point1.contour, is->point1.idx, is->point1.t,
|
||||
is->point2.contour, is->point2.idx, is->point2.t,
|
||||
kn[is->kind]);
|
||||
}
|
||||
|
||||
for (gsize i = 0; i + 1 < pd->all_points->len; i++)
|
||||
{
|
||||
Intersection *is1 = &g_array_index (pd->all_points, Intersection, i);
|
||||
Intersection *is2 = &g_array_index (pd->all_points, Intersection, i + 1);
|
||||
|
||||
if (gsk_path_point_equal (&is1->point1, &is2->point1) &&
|
||||
gsk_path_point_equal (&is1->point2, &is2->point2))
|
||||
{
|
||||
if (is1->kind == GSK_PATH_INTERSECTION_END &&
|
||||
is2->kind == GSK_PATH_INTERSECTION_START)
|
||||
{
|
||||
is1->kind = GSK_PATH_INTERSECTION_NONE;
|
||||
is2->kind = GSK_PATH_INTERSECTION_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
is2->kind = MAX (is1->kind, is2->kind);
|
||||
is1->kind = GSK_PATH_INTERSECTION_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
g_print ("emitting\n");
|
||||
for (gsize i = 0; i < pd->all_points->len; i++)
|
||||
{
|
||||
Intersection *is = &g_array_index (pd->all_points, Intersection, i);
|
||||
const char *kn[] = { "none", "normal", "start", "end" };
|
||||
|
||||
g_print ("p1 { %lu %lu %f } p2 { %lu %lu %f } %s\n",
|
||||
is->point1.contour, is->point1.idx, is->point1.t,
|
||||
is->point2.contour, is->point2.idx, is->point2.t,
|
||||
kn[is->kind]);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (gsize i = 0; i < pd->all_points->len; i++)
|
||||
{
|
||||
Intersection *is = &g_array_index (pd->all_points, Intersection, i);
|
||||
|
||||
if (is->kind != GSK_PATH_INTERSECTION_NONE)
|
||||
{
|
||||
|
||||
if (!pd->func (pd->path1, &is->point1, pd->path2, &is->point2, is->kind, pd->data))
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_foreach_intersection:
|
||||
* @path1: the first path
|
||||
* @path2: the second path
|
||||
* @func: (scope call) (closure user_data): the function to call for intersections
|
||||
* @user_data: (nullable): user data passed to @func
|
||||
*
|
||||
* Finds intersections between two paths.
|
||||
*
|
||||
* This function finds intersections between @path1 and @path2,
|
||||
* and calls @func for each of them, in increasing order for @path1.
|
||||
*
|
||||
* When segments of the paths coincide, the callback is called once
|
||||
* for the start of the segment, with @GSK_PATH_INTERSECTION_START, and
|
||||
* once for the end of the segment, with @GSK_PATH_INTERSECTION_END.
|
||||
* Note that other intersections may occur between the start and end
|
||||
* of such a segment.
|
||||
*
|
||||
* If @func returns `FALSE`, the iteration is stopped.
|
||||
*
|
||||
* Returns: `FALSE` if @func returned FALSE`, `TRUE` otherwise.
|
||||
*
|
||||
* Since: 4.14
|
||||
*/
|
||||
gboolean
|
||||
gsk_path_foreach_intersection (GskPath *path1,
|
||||
GskPath *path2,
|
||||
GskPathIntersectionFunc func,
|
||||
gpointer data)
|
||||
{
|
||||
PathIntersectData pd = {
|
||||
.path1 = path1,
|
||||
.path2 = path2,
|
||||
.func = func,
|
||||
.data = data,
|
||||
};
|
||||
gboolean ret;
|
||||
|
||||
pd.points = g_array_new (FALSE, FALSE, sizeof (Intersection));
|
||||
pd.all_points = g_array_new (FALSE, FALSE, sizeof (Intersection));
|
||||
|
||||
ret = TRUE;
|
||||
for (gsize i = 0; i < gsk_path_get_n_contours (path1); i++)
|
||||
{
|
||||
const GskContour *contour1 = gsk_path_get_contour (path1, i);
|
||||
|
||||
pd.contour1 = i;
|
||||
pd.c1 = contour1;
|
||||
pd.c1_count = count_curves (contour1, &pd.c1_closed, &pd.c1_z_is_empty);
|
||||
pd.idx1 = 0;
|
||||
|
||||
if (!contour_foreach_intersection (contour1, &pd))
|
||||
{
|
||||
ret = FALSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_array_unref (pd.points);
|
||||
g_array_unref (pd.all_points);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* vim:set foldmethod=marker expandtab: */
|
@@ -28,6 +28,7 @@ gsk_public_sources = files([
|
||||
'gskglshader.c',
|
||||
'gskpath.c',
|
||||
'gskpathbuilder.c',
|
||||
'gskpathintersect.c',
|
||||
'gskpathmeasure.c',
|
||||
'gskpathparser.c',
|
||||
'gskpathpoint.c',
|
||||
@@ -45,6 +46,7 @@ gsk_private_sources = files([
|
||||
'gskcairoblur.c',
|
||||
'gskcontour.c',
|
||||
'gskcurve.c',
|
||||
'gskcurveintersect.c',
|
||||
'gskdebug.c',
|
||||
'gskprivate.c',
|
||||
'gskprofiler.c',
|
||||
|
551
testsuite/gsk/curve-intersect.c
Normal file
551
testsuite/gsk/curve-intersect.c
Normal file
@@ -0,0 +1,551 @@
|
||||
#include <gtk/gtk.h>
|
||||
#include "gsk/gskcurveprivate.h"
|
||||
|
||||
static void
|
||||
test_line_line_intersection (void)
|
||||
{
|
||||
GskCurve c1, c2;
|
||||
graphene_point_t p1[2], p2[2];
|
||||
float t1, t2;
|
||||
graphene_point_t p;
|
||||
GskPathIntersection kind;
|
||||
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, &kind, 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;
|
||||
GskPathIntersection kind;
|
||||
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, &kind, 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;
|
||||
GskPathIntersection kind;
|
||||
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, &kind, 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, &kind, 1);
|
||||
|
||||
g_assert_cmpint (n, ==, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
test_line_line_parallel (void)
|
||||
{
|
||||
GskCurve c1, c2;
|
||||
graphene_point_t p1[2], p2[2];
|
||||
float t1[2], t2[2];
|
||||
graphene_point_t p[2];
|
||||
GskPathIntersection kind[2];
|
||||
int n;
|
||||
|
||||
graphene_point_init (&p1[0], 10, 10);
|
||||
graphene_point_init (&p1[1], 110, 10);
|
||||
graphene_point_init (&p2[0], 20, 10);
|
||||
graphene_point_init (&p2[1], 120, 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, kind, 2);
|
||||
|
||||
g_assert_cmpint (n, ==, 2);
|
||||
g_assert_cmpfloat_with_epsilon (t1[0], 0.1f, 0.01);
|
||||
g_assert_cmpfloat_with_epsilon (t1[1], 1.f, 0.01);
|
||||
g_assert_cmpfloat_with_epsilon (t2[0], 0.f, 0.01);
|
||||
g_assert_cmpfloat_with_epsilon (t2[1], 0.9f, 0.01);
|
||||
}
|
||||
|
||||
static void
|
||||
test_line_line_same (void)
|
||||
{
|
||||
GskCurve c1, c2;
|
||||
graphene_point_t p1[2], p2[2];
|
||||
float t1[2], t2[2];
|
||||
graphene_point_t p[2];
|
||||
GskPathIntersection kind[2];
|
||||
int n;
|
||||
|
||||
graphene_point_init (&p1[0], 10, 10);
|
||||
graphene_point_init (&p1[1], 100, 10);
|
||||
graphene_point_init (&p2[0], 10, 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, kind, 2);
|
||||
|
||||
g_assert_cmpint (n, ==, 2);
|
||||
g_assert_cmpfloat_with_epsilon (t1[0], 0.f, 0.01);
|
||||
g_assert_cmpfloat_with_epsilon (t1[1], 1.f, 0.01);
|
||||
g_assert_cmpfloat_with_epsilon (t2[0], 0.f, 0.01);
|
||||
g_assert_cmpfloat_with_epsilon (t2[1], 1.f, 0.01);
|
||||
}
|
||||
|
||||
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];
|
||||
GskPathIntersection kind[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_CUBIC, p1));
|
||||
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
||||
|
||||
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 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];
|
||||
GskPathIntersection kind[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_CUBIC, p1));
|
||||
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
||||
|
||||
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 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_CUBIC, p1));
|
||||
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
||||
|
||||
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 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_CUBIC, p1));
|
||||
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
||||
|
||||
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 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_CUBIC, p1));
|
||||
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
||||
|
||||
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 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];
|
||||
GskPathIntersection kind[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_CUBIC, p1));
|
||||
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
||||
|
||||
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 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];
|
||||
GskPathIntersection kind[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_CUBIC, p1));
|
||||
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_LINE, p2));
|
||||
|
||||
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 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];
|
||||
GskPathIntersection kind[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_CUBIC, p1));
|
||||
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_CONIC, p2));
|
||||
|
||||
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 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];
|
||||
GskPathIntersection kind[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_CUBIC, p1));
|
||||
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_CONIC, p2));
|
||||
|
||||
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 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];
|
||||
GskPathIntersection kind[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_CUBIC, p1));
|
||||
|
||||
gsk_curve_split (&c, 0.5, &c1, &c2);
|
||||
|
||||
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 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];
|
||||
GskPathIntersection kind[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_CUBIC, p1));
|
||||
gsk_curve_init (&c2, gsk_pathop_encode (GSK_PATH_CUBIC, p2));
|
||||
|
||||
n = gsk_curve_intersect (&c1, &c2, t1, t2, p, kind, 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;
|
||||
GskPathIntersection kind;
|
||||
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, &kind, 1);
|
||||
|
||||
g_assert_true (n == 1);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
(g_test_init) (&argc, &argv, NULL);
|
||||
|
||||
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-line-parallel", test_line_line_parallel);
|
||||
g_test_add_func ("/curve/intersection/line-line-same", test_line_line_same);
|
||||
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);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
@@ -374,6 +374,7 @@ tests = [
|
||||
['shader'],
|
||||
['path', [ 'path-utils.c' ] ],
|
||||
['path-special-cases'],
|
||||
['path-intersect'],
|
||||
]
|
||||
|
||||
test_cargs = []
|
||||
@@ -406,6 +407,7 @@ endforeach
|
||||
internal_tests = [
|
||||
[ 'curve' ],
|
||||
[ 'curve-special-cases' ],
|
||||
[ 'curve-intersect' ],
|
||||
[ 'path-private' ],
|
||||
[ 'diff' ],
|
||||
[ 'half-float' ],
|
||||
|
806
testsuite/gsk/path-intersect.c
Normal file
806
testsuite/gsk/path-intersect.c
Normal file
@@ -0,0 +1,806 @@
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#define assert_path_point_equal(point,_contour,_idx,_t) \
|
||||
g_assert_cmpint ((point)->contour, ==, (_contour)); \
|
||||
g_assert_cmpint ((point)->idx, ==, (_idx)); \
|
||||
g_assert_cmpfloat_with_epsilon ((point)->t, (_t), 0.0001);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GskPathPoint point1[20];
|
||||
GskPathPoint point2[20];
|
||||
GskPathIntersection kind[20];
|
||||
int found;
|
||||
} CollectData;
|
||||
|
||||
static gboolean
|
||||
collect_cb (GskPath *path1,
|
||||
const GskPathPoint *point1,
|
||||
GskPath *path2,
|
||||
const GskPathPoint *point2,
|
||||
GskPathIntersection kind,
|
||||
gpointer data)
|
||||
{
|
||||
CollectData *res = data;
|
||||
#if 0
|
||||
const char *kn[] = { "none", "normal", "start", "end" };
|
||||
|
||||
g_print ("%s idx1 %lu t1 %f idx2 %lu t2 %f\n",
|
||||
kn[kind],
|
||||
point1->idx, point1->t, point2->idx, point2->t);
|
||||
#endif
|
||||
|
||||
g_assert_true (res->found < 20);
|
||||
|
||||
res->point1[res->found] = *point1;
|
||||
res->point2[res->found] = *point2;
|
||||
res->kind[res->found] = kind;
|
||||
res->found++;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_simple (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
graphene_point_t p1, p2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
|
||||
path2 = gsk_path_parse ("M 150 150 h 200 v 100 h -200 z");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 2);
|
||||
assert_path_point_equal (&res.point1[0], 0, 2, 0.5);
|
||||
assert_path_point_equal (&res.point1[1], 0, 3, 0.75);
|
||||
assert_path_point_equal (&res.point2[0], 0, 1, 0.75);
|
||||
assert_path_point_equal (&res.point2[1], 0, 4, 0.5);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
|
||||
gsk_path_point_get_position (&res.point1[0], path1, &p1);
|
||||
gsk_path_point_get_position (&res.point2[0], path2, &p2);
|
||||
g_assert_true (graphene_point_equal (&p1, &p2));
|
||||
|
||||
gsk_path_point_get_position (&res.point1[1], path1, &p1);
|
||||
gsk_path_point_get_position (&res.point2[1], path2, &p2);
|
||||
g_assert_true (graphene_point_equal (&p1, &p2));
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_simple2 (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
|
||||
path2 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 2);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_simple3 (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
|
||||
path2 = gsk_path_parse ("M 300 100 h -200 v 100 h 200 z");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 2);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_reverse (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
graphene_point_t p1, p2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
|
||||
path2 = gsk_path_parse ("M 150 150 v 100 h 200 v -100 z");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 2);
|
||||
assert_path_point_equal (&res.point1[0], 0, 2, 0.5);
|
||||
assert_path_point_equal (&res.point1[1], 0, 3, 0.75);
|
||||
assert_path_point_equal (&res.point2[0], 0, 4, 0.25);
|
||||
assert_path_point_equal (&res.point2[1], 0, 1, 0.5);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
|
||||
gsk_path_point_get_position (&res.point1[0], path1, &p1);
|
||||
gsk_path_point_get_position (&res.point2[0], path2, &p2);
|
||||
g_assert_true (graphene_point_equal (&p1, &p2));
|
||||
|
||||
gsk_path_point_get_position (&res.point1[1], path1, &p1);
|
||||
gsk_path_point_get_position (&res.point2[1], path2, &p2);
|
||||
g_assert_true (graphene_point_equal (&p1, &p2));
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_line_box (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
graphene_point_t p1, p2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 50 150 l 300 0");
|
||||
path2 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 2);
|
||||
assert_path_point_equal (&res.point1[0], 0, 1, 50.f/300.f);
|
||||
assert_path_point_equal (&res.point1[1], 0, 1, 250.f/300.f);
|
||||
assert_path_point_equal (&res.point2[0], 0, 4, 0.5);
|
||||
assert_path_point_equal (&res.point2[1], 0, 2, 0.5);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
|
||||
gsk_path_point_get_position (&res.point1[0], path1, &p1);
|
||||
gsk_path_point_get_position (&res.point2[0], path2, &p2);
|
||||
g_assert_true (graphene_point_equal (&p1, &p2));
|
||||
|
||||
gsk_path_point_get_position (&res.point1[1], path1, &p1);
|
||||
gsk_path_point_get_position (&res.point2[1], path2, &p2);
|
||||
g_assert_true (graphene_point_equal (&p1, &p2));
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_xplus (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 0 0 L 100 100 M 0 100 L 100 0");
|
||||
path2 = gsk_path_parse ("M 0 50 L 100 50 M 50 0 L 50 100");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 4);
|
||||
assert_path_point_equal (&res.point1[0], 0, 1, 0.5);
|
||||
assert_path_point_equal (&res.point1[1], 0, 1, 0.5);
|
||||
assert_path_point_equal (&res.point1[2], 1, 1, 0.5);
|
||||
assert_path_point_equal (&res.point1[3], 1, 1, 0.5);
|
||||
|
||||
assert_path_point_equal (&res.point2[0], 0, 1, 0.5);
|
||||
assert_path_point_equal (&res.point2[1], 1, 1, 0.5);
|
||||
assert_path_point_equal (&res.point2[2], 0, 1, 0.5);
|
||||
assert_path_point_equal (&res.point2[3], 1, 1, 0.5);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_point (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 0 50");
|
||||
path2 = gsk_path_parse ("M 0 0 L 0 100");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
|
||||
g_assert_true (res.found == 1);
|
||||
assert_path_point_equal (&res.point1[0], 0, 0, 1);
|
||||
assert_path_point_equal (&res.point2[0], 0, 1, 0.5);
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path2, path1, collect_cb, &res);
|
||||
|
||||
g_assert_true (res.found == 1);
|
||||
assert_path_point_equal (&res.point1[0], 0, 1, 0.5);
|
||||
assert_path_point_equal (&res.point2[0], 0, 0, 1);
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path1, collect_cb, &res);
|
||||
|
||||
g_assert_true (res.found == 1);
|
||||
assert_path_point_equal (&res.point1[0], 0, 0, 1);
|
||||
assert_path_point_equal (&res.point2[0], 0, 0, 1);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_contours (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 0 100 L 200 100");
|
||||
path2 = gsk_path_parse ("M 150 0 150 200 M 50 0 50 200");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 2);
|
||||
assert_path_point_equal (&res.point1[0], 0, 1, 0.25f);
|
||||
assert_path_point_equal (&res.point1[1], 0, 1, 0.75f);
|
||||
assert_path_point_equal (&res.point2[0], 1, 1, 0.5f);
|
||||
assert_path_point_equal (&res.point2[1], 0, 1, 0.5f);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_contours2 (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 0 100 L 200 100");
|
||||
path2 = gsk_path_parse ("M 150 0 L 150 200 M 50 0 L 50 200 M 60 100 L 140 100");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 4);
|
||||
assert_path_point_equal (&res.point1[0], 0, 1, 0.25f);
|
||||
assert_path_point_equal (&res.point1[1], 0, 1, 0.3f);
|
||||
assert_path_point_equal (&res.point2[0], 1, 1, 0.5f);
|
||||
assert_path_point_equal (&res.point2[1], 2, 1, 0.f);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_END);
|
||||
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_contours3 (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 150 0 L 150 200 M 50 0 L 50 200 M 60 100 L 140 100");
|
||||
path2 = gsk_path_parse ("M 0 100 L 200 100");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 4);
|
||||
assert_path_point_equal (&res.point1[0], 0, 1, 0.5f);
|
||||
assert_path_point_equal (&res.point1[1], 1, 1, 0.5f);
|
||||
assert_path_point_equal (&res.point2[0], 0, 1, 0.75f);
|
||||
assert_path_point_equal (&res.point2[1], 0, 1, 0.25f);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_coincide (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
|
||||
path2 = gsk_path_parse ("M 150 100 h 100 v 50 h -100 z");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 2);
|
||||
assert_path_point_equal (&res.point1[0], 0, 1, 0.25);
|
||||
assert_path_point_equal (&res.point1[1], 0, 1, 0.75);
|
||||
assert_path_point_equal (&res.point2[0], 0, 1, 0);
|
||||
assert_path_point_equal (&res.point2[1], 0, 1, 1);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_coincide2 (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 150 100 h 100 v 50 h -100 z");
|
||||
path2 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 2);
|
||||
assert_path_point_equal (&res.point1[0], 0, 1, 0);
|
||||
assert_path_point_equal (&res.point1[1], 0, 1, 1);
|
||||
assert_path_point_equal (&res.point2[0], 0, 1, 0.25);
|
||||
assert_path_point_equal (&res.point2[1], 0, 1, 0.75);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_coincide3 (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
|
||||
path2 = gsk_path_parse ("M 150 100 h 100 v 50 h -25 v -50 h -50 v 50 h -25 z");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 4);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_END);
|
||||
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
|
||||
|
||||
assert_path_point_equal (&res.point1[0], 0, 1, 0.25);
|
||||
assert_path_point_equal (&res.point1[1], 0, 1, 0.375);
|
||||
assert_path_point_equal (&res.point1[2], 0, 1, 0.625);
|
||||
assert_path_point_equal (&res.point1[3], 0, 1, 0.75);
|
||||
|
||||
assert_path_point_equal (&res.point2[0], 0, 1, 0);
|
||||
assert_path_point_equal (&res.point2[1], 0, 5, 1);
|
||||
assert_path_point_equal (&res.point2[2], 0, 5, 0);
|
||||
assert_path_point_equal (&res.point2[3], 0, 1, 1);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_coincide4 (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 100 100 h 200 v 100 h -200 z");
|
||||
path2 = gsk_path_parse ("M 150 100 h 100 v 50 h -25 v -100 h -50 v 100 h -25 z");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 4);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
|
||||
|
||||
assert_path_point_equal (&res.point1[0], 0, 1, 0.25);
|
||||
assert_path_point_equal (&res.point1[1], 0, 1, 0.375);
|
||||
assert_path_point_equal (&res.point1[2], 0, 1, 0.625);
|
||||
assert_path_point_equal (&res.point1[3], 0, 1, 0.75);
|
||||
|
||||
assert_path_point_equal (&res.point2[0], 0, 1, 0);
|
||||
assert_path_point_equal (&res.point2[1], 0, 6, 0.5);
|
||||
assert_path_point_equal (&res.point2[2], 0, 4, 0.5);
|
||||
assert_path_point_equal (&res.point2[3], 0, 1, 1);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
/* the next few tests explore overlapping segments */
|
||||
static void
|
||||
test_intersect_coincide5 (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 150 100 h 100 v 100 h -100 z");
|
||||
path2 = gsk_path_parse ("M 100 100 h 200 v 50 h -100 v -50 h 25 v -50 h -100 z");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 5);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_END);
|
||||
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
|
||||
g_assert_true (res.kind[4] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
|
||||
assert_path_point_equal (&res.point1[0], 0, 1, 0);
|
||||
assert_path_point_equal (&res.point1[1], 0, 1, 0.5);
|
||||
assert_path_point_equal (&res.point1[2], 0, 1, 0.75);
|
||||
assert_path_point_equal (&res.point1[3], 0, 1, 1);
|
||||
assert_path_point_equal (&res.point1[4], 0, 2, 0.5);
|
||||
|
||||
assert_path_point_equal (&res.point2[0], 0, 1, 0.25);
|
||||
assert_path_point_equal (&res.point2[1], 0, 5, 0);
|
||||
assert_path_point_equal (&res.point2[2], 0, 5, 1);
|
||||
assert_path_point_equal (&res.point2[3], 0, 1, 0.75);
|
||||
assert_path_point_equal (&res.point2[4], 0, 3, 0.5);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_coincide6 (void)
|
||||
{
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
path1 = gsk_path_parse ("M 150 100 h 75 l 25 50 v 50 h -100 z");
|
||||
path2 = gsk_path_parse ("M 100 100 h 200 v 50 h -100 v -50 h 50 v -50 h -125 z");
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 5);
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_END);
|
||||
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
|
||||
g_assert_true (res.kind[4] == GSK_PATH_INTERSECTION_NORMAL);
|
||||
|
||||
assert_path_point_equal (&res.point1[0], 0, 1, 0);
|
||||
assert_path_point_equal (&res.point1[1], 0, 1, 2./3.);
|
||||
assert_path_point_equal (&res.point1[2], 0, 1, 1);
|
||||
assert_path_point_equal (&res.point1[3], 0, 1, 1);
|
||||
assert_path_point_equal (&res.point1[4], 0, 3, 0);
|
||||
|
||||
assert_path_point_equal (&res.point2[0], 0, 1, 0.25);
|
||||
assert_path_point_equal (&res.point2[1], 0, 5, 0);
|
||||
assert_path_point_equal (&res.point2[2], 0, 1, 0.625);
|
||||
assert_path_point_equal (&res.point2[3], 0, 5, 0.5);
|
||||
assert_path_point_equal (&res.point2[4], 0, 3, 0.5);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static int
|
||||
circle_intersect (const graphene_point_t *center1,
|
||||
float radius1,
|
||||
const graphene_point_t *center2,
|
||||
float radius2,
|
||||
graphene_point_t points[2])
|
||||
{
|
||||
float d;
|
||||
float a, h;
|
||||
graphene_point_t m;
|
||||
graphene_vec2_t n;
|
||||
|
||||
g_assert (radius1 >= 0);
|
||||
g_assert (radius2 >= 0);
|
||||
|
||||
d = graphene_point_distance (center1, center2, NULL, NULL);
|
||||
|
||||
if (d < fabsf (radius1 - radius2))
|
||||
return 0;
|
||||
|
||||
if (d > radius1 + radius2)
|
||||
return 0;
|
||||
|
||||
if (d == radius1 + radius2)
|
||||
{
|
||||
graphene_point_interpolate (center1, center2, radius1 / (radius1 + radius2), &points[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
a + b = d;
|
||||
|
||||
a^2 + h^2 = r1^2
|
||||
b^2 + h^2 = r2^2
|
||||
|
||||
a^2 - (d - a)^2 = r1^2 - r2^2
|
||||
a^2 - (d^2 - 2ad + a2) = r1^2 - r2^2
|
||||
|
||||
2ad -d^2 = r1^2 - r2^2
|
||||
2ad = r1^2 - r2^2 + d^2
|
||||
a = (r1^2 - r2^2 + d^2) / (2d)
|
||||
|
||||
p1/2 = c1 + a/d * (c2 - c1) +/- h * n(c1,c2);
|
||||
*/
|
||||
|
||||
a = (radius1*radius1 - radius2*radius2 + d*d)/(2*d);
|
||||
h = sqrtf (radius1*radius1 - a*a);
|
||||
|
||||
graphene_point_interpolate (center1, center2, a/d, &m);
|
||||
graphene_vec2_init (&n, center2->y - center1->y, center1->x - center2->x);
|
||||
graphene_vec2_normalize (&n, &n);
|
||||
|
||||
graphene_point_init (&points[0], m.x + graphene_vec2_get_x (&n) * h,
|
||||
m.y + graphene_vec2_get_y (&n) * h);
|
||||
graphene_point_init (&points[1], m.x - graphene_vec2_get_x (&n) * h,
|
||||
m.y - graphene_vec2_get_y (&n) * h);
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_circle (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (1, 1), 10);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 0);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_circle2 (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 25), 10);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 0);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_circle3 (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 22), 10);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 1);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_circle4 (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
graphene_point_t p[2], pos;
|
||||
int n;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 18), 10);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 2);
|
||||
|
||||
n = circle_intersect (&GRAPHENE_POINT_INIT (0, 0), 12,
|
||||
&GRAPHENE_POINT_INIT (0, 18), 10,
|
||||
p);
|
||||
|
||||
g_assert_true (n == 2);
|
||||
gsk_path_point_get_position (&res.point1[0], path1, &pos);
|
||||
g_assert_true (graphene_point_near (&p[0], &pos, 0.01));
|
||||
gsk_path_point_get_position (&res.point1[1], path1, &pos);
|
||||
g_assert_true (graphene_point_near (&p[1], &pos, 0.01));
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_circle5 (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
graphene_point_t p[2], pos;
|
||||
int n;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (0, 0), 12);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (10, 10), 10);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 2);
|
||||
|
||||
n = circle_intersect (&GRAPHENE_POINT_INIT (0, 0), 12,
|
||||
&GRAPHENE_POINT_INIT (10, 10), 10,
|
||||
p);
|
||||
|
||||
g_assert_true (n == 2);
|
||||
gsk_path_point_get_position (&res.point1[0], path1, &pos);
|
||||
g_assert_true (graphene_point_near (&p[0], &pos, 0.01));
|
||||
gsk_path_point_get_position (&res.point1[1], path1, &pos);
|
||||
g_assert_true (graphene_point_near (&p[1], &pos, 0.01));
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_circle6 (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path1;
|
||||
CollectData res;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 12);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path1, collect_cb, &res);
|
||||
g_assert_true (res.found == 2);
|
||||
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_circle7 (void)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (100, 100), 12);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_reverse_path (builder, path1);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 2);
|
||||
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_intersect_circle_rounded_rect (void)
|
||||
{
|
||||
GskRoundedRect rr;
|
||||
GskPathBuilder *builder;
|
||||
GskPath *path1, *path2;
|
||||
CollectData res;
|
||||
|
||||
rr.bounds = GRAPHENE_RECT_INIT (10, 10, 100, 100);
|
||||
rr.corner[GSK_CORNER_TOP_LEFT] = GRAPHENE_SIZE_INIT (20, 20);
|
||||
rr.corner[GSK_CORNER_TOP_RIGHT] = GRAPHENE_SIZE_INIT (20, 20);
|
||||
rr.corner[GSK_CORNER_BOTTOM_RIGHT] = GRAPHENE_SIZE_INIT (20, 20);
|
||||
rr.corner[GSK_CORNER_BOTTOM_LEFT] = GRAPHENE_SIZE_INIT (20, 20);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_rounded_rect (builder, &rr);
|
||||
path1 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (30, 30), 20);
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (90, 30), 20);
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (90, 90), 20);
|
||||
gsk_path_builder_add_circle (builder, &GRAPHENE_POINT_INIT (30, 90), 20);
|
||||
path2 = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
res.found = 0;
|
||||
gsk_path_foreach_intersection (path1, path2, collect_cb, &res);
|
||||
g_assert_true (res.found == 8);
|
||||
|
||||
g_assert_true (res.kind[0] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[1] == GSK_PATH_INTERSECTION_END);
|
||||
g_assert_true (res.kind[2] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[3] == GSK_PATH_INTERSECTION_END);
|
||||
g_assert_true (res.kind[4] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[5] == GSK_PATH_INTERSECTION_END);
|
||||
g_assert_true (res.kind[6] == GSK_PATH_INTERSECTION_START);
|
||||
g_assert_true (res.kind[7] == GSK_PATH_INTERSECTION_END);
|
||||
|
||||
gsk_path_unref (path1);
|
||||
gsk_path_unref (path2);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char *argv[])
|
||||
{
|
||||
(g_test_init) (&argc, &argv, NULL);
|
||||
|
||||
g_test_add_func ("/path/intersect/simple", test_intersect_simple);
|
||||
g_test_add_func ("/path/intersect/simple2", test_intersect_simple2);
|
||||
g_test_add_func ("/path/intersect/simple3", test_intersect_simple3);
|
||||
g_test_add_func ("/path/intersect/reverse", test_intersect_reverse);
|
||||
g_test_add_func ("/path/intersect/line-box", test_intersect_line_box);
|
||||
g_test_add_func ("/path/intersect/xplus", test_intersect_xplus);
|
||||
g_test_add_func ("/path/intersect/point", test_intersect_point);
|
||||
g_test_add_func ("/path/intersect/contours", test_intersect_contours);
|
||||
g_test_add_func ("/path/intersect/contours2", test_intersect_contours2);
|
||||
g_test_add_func ("/path/intersect/contours3", test_intersect_contours3);
|
||||
g_test_add_func ("/path/intersect/coincide", test_intersect_coincide);
|
||||
g_test_add_func ("/path/intersect/coincide2", test_intersect_coincide2);
|
||||
g_test_add_func ("/path/intersect/coincide3", test_intersect_coincide3);
|
||||
g_test_add_func ("/path/intersect/coincide4", test_intersect_coincide4);
|
||||
g_test_add_func ("/path/intersect/coincide5", test_intersect_coincide5);
|
||||
g_test_add_func ("/path/intersect/coincide6", test_intersect_coincide6);
|
||||
g_test_add_func ("/path/intersect/circle", test_intersect_circle);
|
||||
g_test_add_func ("/path/intersect/circle2", test_intersect_circle2);
|
||||
g_test_add_func ("/path/intersect/circle3", test_intersect_circle3);
|
||||
g_test_add_func ("/path/intersect/circle4", test_intersect_circle4);
|
||||
g_test_add_func ("/path/intersect/circle5", test_intersect_circle5);
|
||||
g_test_add_func ("/path/intersect/circle6", test_intersect_circle6);
|
||||
g_test_add_func ("/path/intersect/circle7", test_intersect_circle7);
|
||||
g_test_add_func ("/path/intersect/circle-rounded-rect", test_intersect_circle_rounded_rect);
|
||||
|
||||
return g_test_run ();
|
||||
}
|
@@ -32,12 +32,14 @@
|
||||
#include "path-view.h"
|
||||
|
||||
static void
|
||||
show_path_fill (GskPath *path,
|
||||
show_path_fill (GskPath *path1,
|
||||
GskPath *path2,
|
||||
GskFillRule fill_rule,
|
||||
const GdkRGBA *fg_color,
|
||||
const GdkRGBA *bg_color,
|
||||
gboolean show_points,
|
||||
gboolean show_controls,
|
||||
gboolean show_intersections,
|
||||
const GdkRGBA *point_color)
|
||||
{
|
||||
GtkWidget *window, *sw, *child;
|
||||
@@ -52,14 +54,17 @@ show_path_fill (GskPath *path,
|
||||
gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE);
|
||||
gtk_window_set_child (GTK_WINDOW (window), sw);
|
||||
|
||||
child = path_view_new (path);
|
||||
child = g_object_new (PATH_TYPE_VIEW, NULL);
|
||||
g_object_set (child,
|
||||
"path1", path1,
|
||||
"path2", path2,
|
||||
"do-fill", TRUE,
|
||||
"fill-rule", fill_rule,
|
||||
"fg-color", fg_color,
|
||||
"bg-color", bg_color,
|
||||
"show-points", show_points,
|
||||
"show-controls", show_controls,
|
||||
"show-intersections", show_intersections,
|
||||
"point-color", point_color,
|
||||
NULL);
|
||||
|
||||
@@ -74,12 +79,14 @@ show_path_fill (GskPath *path,
|
||||
}
|
||||
|
||||
static void
|
||||
show_path_stroke (GskPath *path,
|
||||
show_path_stroke (GskPath *path1,
|
||||
GskPath *path2,
|
||||
GskStroke *stroke,
|
||||
const GdkRGBA *fg_color,
|
||||
const GdkRGBA *bg_color,
|
||||
gboolean show_points,
|
||||
gboolean show_controls,
|
||||
gboolean show_intersections,
|
||||
const GdkRGBA *point_color)
|
||||
{
|
||||
GtkWidget *window, *sw, *child;
|
||||
@@ -94,14 +101,17 @@ show_path_stroke (GskPath *path,
|
||||
gtk_scrolled_window_set_propagate_natural_height (GTK_SCROLLED_WINDOW (sw), TRUE);
|
||||
gtk_window_set_child (GTK_WINDOW (window), sw);
|
||||
|
||||
child = path_view_new (path);
|
||||
child = g_object_new (PATH_TYPE_VIEW, NULL);
|
||||
g_object_set (child,
|
||||
"path1", path1,
|
||||
"path2", path2,
|
||||
"do-fill", FALSE,
|
||||
"stroke", stroke,
|
||||
"fg-color", fg_color,
|
||||
"bg-color", bg_color,
|
||||
"show-points", show_points,
|
||||
"show-controls", show_controls,
|
||||
"show-intersections", show_intersections,
|
||||
"point-color", point_color,
|
||||
NULL);
|
||||
|
||||
@@ -123,6 +133,7 @@ do_show (int *argc,
|
||||
gboolean do_stroke = FALSE;
|
||||
gboolean show_points = FALSE;
|
||||
gboolean show_controls = FALSE;
|
||||
gboolean show_intersections = FALSE;
|
||||
const char *fill = "winding";
|
||||
const char *fg_color = "black";
|
||||
const char *bg_color = "white";
|
||||
@@ -141,6 +152,7 @@ do_show (int *argc,
|
||||
{ "stroke", 0, 0, G_OPTION_ARG_NONE, &do_stroke, N_("Stroke the path"), NULL },
|
||||
{ "points", 0, 0, G_OPTION_ARG_NONE, &show_points, N_("Show path points"), NULL },
|
||||
{ "controls", 0, 0, G_OPTION_ARG_NONE, &show_controls, N_("Show control points"), NULL },
|
||||
{ "intersections", 0, 0, G_OPTION_ARG_NONE, &show_intersections, N_("Show intersections"), NULL },
|
||||
{ "fg-color", 0, 0, G_OPTION_ARG_STRING, &fg_color, N_("Foreground color"), N_("COLOR") },
|
||||
{ "bg-color", 0, 0, G_OPTION_ARG_STRING, &bg_color, N_("Background color"), N_("COLOR") },
|
||||
{ "point-color", 0, 0, G_OPTION_ARG_STRING, &point_color, N_("Point color"), N_("COLOR") },
|
||||
@@ -160,7 +172,8 @@ do_show (int *argc,
|
||||
{ "dash-offset", 0, 0, G_OPTION_ARG_DOUBLE, &dash_offset, N_("Dash offset (number)"), N_("VALUE") },
|
||||
{ NULL, }
|
||||
};
|
||||
GskPath *path;
|
||||
GskPath *path1;
|
||||
GskPath *path2;
|
||||
GskFillRule fill_rule;
|
||||
GdkRGBA fg, bg, pc;
|
||||
GskLineCap line_cap;
|
||||
@@ -210,13 +223,17 @@ do_show (int *argc,
|
||||
exit (1);
|
||||
}
|
||||
|
||||
if (g_strv_length (args) > 1)
|
||||
if (g_strv_length (args) > 2)
|
||||
{
|
||||
g_printerr ("%s\n", _("Can only show a single path"));
|
||||
g_printerr ("%s\n", _("Can only show one or two paths"));
|
||||
exit (1);
|
||||
}
|
||||
|
||||
path = get_path (args[0]);
|
||||
path1 = get_path (args[0]);
|
||||
if (g_strv_length (args) > 1)
|
||||
path2 = get_path (args[1]);
|
||||
else
|
||||
path2 = NULL;
|
||||
|
||||
fill_rule = get_enum_value (GSK_TYPE_FILL_RULE, _("fill rule"), fill);
|
||||
get_color (&fg, fg_color);
|
||||
@@ -234,11 +251,12 @@ do_show (int *argc,
|
||||
_gsk_stroke_set_dashes (stroke, dashes);
|
||||
|
||||
if (do_stroke)
|
||||
show_path_stroke (path, stroke, &fg, &bg, show_points, show_controls, &pc);
|
||||
show_path_stroke (path1, path2, stroke, &fg, &bg, show_points, show_controls, show_intersections, &pc);
|
||||
else
|
||||
show_path_fill (path, fill_rule, &fg, &bg, show_points, show_controls, &pc);
|
||||
show_path_fill (path1, path2, fill_rule, &fg, &bg, show_points, show_controls, show_intersections, &pc);
|
||||
|
||||
gsk_path_unref (path);
|
||||
g_clear_pointer (&path1, gsk_path_unref);
|
||||
g_clear_pointer (&path2, gsk_path_unref);
|
||||
|
||||
g_strfreev (args);
|
||||
}
|
||||
|
@@ -25,6 +25,8 @@ struct _PathView
|
||||
{
|
||||
GtkWidget parent_instance;
|
||||
|
||||
GskPath *path1;
|
||||
GskPath *path2;
|
||||
GskPath *path;
|
||||
GskStroke *stroke;
|
||||
graphene_rect_t bounds;
|
||||
@@ -35,13 +37,17 @@ struct _PathView
|
||||
gboolean do_fill;
|
||||
gboolean show_points;
|
||||
gboolean show_controls;
|
||||
gboolean show_intersections;
|
||||
GskPath *line_path;
|
||||
GskPath *point_path;
|
||||
GdkRGBA point_color;
|
||||
GskPath *intersection_line_path;
|
||||
GskPath *intersection_point_path;
|
||||
};
|
||||
|
||||
enum {
|
||||
PROP_PATH = 1,
|
||||
PROP_PATH1 = 1,
|
||||
PROP_PATH2,
|
||||
PROP_DO_FILL,
|
||||
PROP_STROKE,
|
||||
PROP_FILL_RULE,
|
||||
@@ -50,6 +56,7 @@ enum {
|
||||
PROP_POINT_COLOR,
|
||||
PROP_SHOW_POINTS,
|
||||
PROP_SHOW_CONTROLS,
|
||||
PROP_SHOW_INTERSECTIONS,
|
||||
N_PROPERTIES
|
||||
};
|
||||
|
||||
@@ -79,10 +86,14 @@ path_view_dispose (GObject *object)
|
||||
{
|
||||
PathView *self = PATH_VIEW (object);
|
||||
|
||||
g_clear_pointer (&self->path1, gsk_path_unref);
|
||||
g_clear_pointer (&self->path2, gsk_path_unref);
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
g_clear_pointer (&self->stroke, gsk_stroke_free);
|
||||
g_clear_pointer (&self->line_path, gsk_path_unref);
|
||||
g_clear_pointer (&self->point_path, gsk_path_unref);
|
||||
g_clear_pointer (&self->intersection_line_path, gsk_path_unref);
|
||||
g_clear_pointer (&self->intersection_point_path, gsk_path_unref);
|
||||
|
||||
G_OBJECT_CLASS (path_view_parent_class)->dispose (object);
|
||||
}
|
||||
@@ -97,8 +108,12 @@ path_view_get_property (GObject *object,
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PATH:
|
||||
g_value_set_boxed (value, self->path);
|
||||
case PROP_PATH1:
|
||||
g_value_set_boxed (value, self->path1);
|
||||
break;
|
||||
|
||||
case PROP_PATH2:
|
||||
g_value_set_boxed (value, self->path2);
|
||||
break;
|
||||
|
||||
case PROP_DO_FILL:
|
||||
@@ -129,6 +144,10 @@ path_view_get_property (GObject *object,
|
||||
g_value_set_boolean (value, self->show_controls);
|
||||
break;
|
||||
|
||||
case PROP_SHOW_INTERSECTIONS:
|
||||
g_value_set_boolean (value, self->show_intersections);
|
||||
break;
|
||||
|
||||
case PROP_POINT_COLOR:
|
||||
g_value_set_boxed (value, &self->point_color);
|
||||
break;
|
||||
@@ -268,6 +287,93 @@ update_controls (PathView *self)
|
||||
update_bounds (self);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
GskPathBuilder *line_builder;
|
||||
GskPathBuilder *point_builder;
|
||||
GskPathPoint start;
|
||||
int segment;
|
||||
} IntersectionData;
|
||||
|
||||
static gboolean
|
||||
intersection_cb (GskPath *path1,
|
||||
const GskPathPoint *point1,
|
||||
GskPath *path2,
|
||||
const GskPathPoint *point2,
|
||||
GskPathIntersection kind,
|
||||
gpointer data)
|
||||
{
|
||||
IntersectionData *id = data;
|
||||
graphene_point_t pos;
|
||||
|
||||
switch (kind)
|
||||
{
|
||||
case GSK_PATH_INTERSECTION_NORMAL:
|
||||
gsk_path_point_get_position (point1, path1, &pos);
|
||||
gsk_path_builder_add_circle (id->point_builder, &pos, 3);
|
||||
break;
|
||||
|
||||
case GSK_PATH_INTERSECTION_START:
|
||||
if (id->segment == 0)
|
||||
id->start = *point1;
|
||||
id->segment++;
|
||||
break;
|
||||
|
||||
case GSK_PATH_INTERSECTION_END:
|
||||
id->segment--;
|
||||
if (id->segment == 0)
|
||||
gsk_path_builder_add_segment (id->line_builder, path1, &id->start, point1);
|
||||
break;
|
||||
|
||||
case GSK_PATH_INTERSECTION_NONE:
|
||||
default:
|
||||
g_assert_not_reached ();
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
update_intersections (PathView *self)
|
||||
{
|
||||
IntersectionData id;
|
||||
|
||||
g_clear_pointer (&self->intersection_line_path, gsk_path_unref);
|
||||
g_clear_pointer (&self->intersection_point_path, gsk_path_unref);
|
||||
|
||||
if (!self->show_intersections || !self->path1 || !self->path2)
|
||||
return;
|
||||
|
||||
id.line_builder = gsk_path_builder_new ();
|
||||
id.point_builder = gsk_path_builder_new ();
|
||||
id.segment = 0;
|
||||
|
||||
gsk_path_foreach_intersection (self->path1, self->path2, intersection_cb, &id);
|
||||
|
||||
self->intersection_line_path = gsk_path_builder_free_to_path (id.line_builder);
|
||||
self->intersection_point_path = gsk_path_builder_free_to_path (id.point_builder);
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
}
|
||||
|
||||
static void
|
||||
update_path (PathView *self)
|
||||
{
|
||||
GskPathBuilder *builder;
|
||||
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
|
||||
builder = gsk_path_builder_new ();
|
||||
if (self->path1)
|
||||
gsk_path_builder_add_path (builder, self->path1);
|
||||
|
||||
if (self->path2)
|
||||
gsk_path_builder_add_path (builder, self->path2);
|
||||
|
||||
self->path = gsk_path_builder_free_to_path (builder);
|
||||
|
||||
update_intersections (self);
|
||||
update_controls (self);
|
||||
}
|
||||
|
||||
static void
|
||||
path_view_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
@@ -278,11 +384,16 @@ path_view_set_property (GObject *object,
|
||||
|
||||
switch (prop_id)
|
||||
{
|
||||
case PROP_PATH1:
|
||||
g_clear_pointer (&self->path1, gsk_path_unref);
|
||||
self->path1 = g_value_dup_boxed (value);
|
||||
update_path (self);
|
||||
break;
|
||||
|
||||
case PROP_PATH:
|
||||
g_clear_pointer (&self->path, gsk_path_unref);
|
||||
self->path = g_value_dup_boxed (value);
|
||||
update_controls (self);
|
||||
case PROP_PATH2:
|
||||
g_clear_pointer (&self->path2, gsk_path_unref);
|
||||
self->path2 = g_value_dup_boxed (value);
|
||||
update_path (self);
|
||||
break;
|
||||
|
||||
case PROP_DO_FILL:
|
||||
@@ -321,6 +432,11 @@ path_view_set_property (GObject *object,
|
||||
update_controls (self);
|
||||
break;
|
||||
|
||||
case PROP_SHOW_INTERSECTIONS:
|
||||
self->show_intersections = g_value_get_boolean (value);
|
||||
update_intersections (self);
|
||||
break;
|
||||
|
||||
case PROP_POINT_COLOR:
|
||||
self->point_color = *(GdkRGBA *) g_value_get_boxed (value);
|
||||
gtk_widget_queue_draw (GTK_WIDGET (self));
|
||||
@@ -344,9 +460,9 @@ path_view_measure (GtkWidget *widget,
|
||||
PathView *self = PATH_VIEW (widget);
|
||||
|
||||
if (orientation == GTK_ORIENTATION_HORIZONTAL)
|
||||
*minimum = *natural = (int) ceilf (self->bounds.size.width) + 2 * self->padding;
|
||||
*minimum = *natural = (int) ceilf (self->bounds.origin.x + self->bounds.size.width) + 2 * self->padding;
|
||||
else
|
||||
*minimum = *natural = (int) ceilf (self->bounds.size.height) + 2 * self->padding;
|
||||
*minimum = *natural = (int) ceilf (self->bounds.origin.y + self->bounds.size.height) + 2 * self->padding;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -383,6 +499,18 @@ path_view_snapshot (GtkWidget *widget,
|
||||
gtk_snapshot_append_stroke (snapshot, self->point_path, stroke, &self->fg);
|
||||
}
|
||||
|
||||
if (self->intersection_line_path)
|
||||
{
|
||||
GskStroke *stroke = gsk_stroke_new (gsk_stroke_get_line_width (self->stroke));
|
||||
|
||||
gtk_snapshot_append_stroke (snapshot, self->intersection_line_path, stroke, &self->point_color);
|
||||
}
|
||||
|
||||
if (self->intersection_point_path)
|
||||
{
|
||||
gtk_snapshot_append_fill (snapshot, self->intersection_point_path, GSK_FILL_RULE_WINDING, &self->point_color);
|
||||
}
|
||||
|
||||
gtk_snapshot_restore (snapshot);
|
||||
}
|
||||
|
||||
@@ -399,8 +527,13 @@ path_view_class_init (PathViewClass *class)
|
||||
widget_class->measure = path_view_measure;
|
||||
widget_class->snapshot = path_view_snapshot;
|
||||
|
||||
properties[PROP_PATH]
|
||||
= g_param_spec_boxed ("path", NULL, NULL,
|
||||
properties[PROP_PATH1]
|
||||
= g_param_spec_boxed ("path1", NULL, NULL,
|
||||
GSK_TYPE_PATH,
|
||||
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
properties[PROP_PATH2]
|
||||
= g_param_spec_boxed ("path2", NULL, NULL,
|
||||
GSK_TYPE_PATH,
|
||||
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
@@ -440,6 +573,11 @@ path_view_class_init (PathViewClass *class)
|
||||
FALSE,
|
||||
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
properties[PROP_SHOW_INTERSECTIONS]
|
||||
= g_param_spec_boolean ("show-intersections", NULL, NULL,
|
||||
FALSE,
|
||||
G_PARAM_READWRITE|G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
properties[PROP_POINT_COLOR]
|
||||
= g_param_spec_boxed ("point-color", NULL, NULL,
|
||||
GDK_TYPE_RGBA,
|
||||
@@ -452,6 +590,6 @@ GtkWidget *
|
||||
path_view_new (GskPath *path)
|
||||
{
|
||||
return g_object_new (PATH_TYPE_VIEW,
|
||||
"path", path,
|
||||
"path1", path,
|
||||
NULL);
|
||||
}
|
||||
|
Reference in New Issue
Block a user