path: Add gsk_path_measure_get_closest_point()
... and gsk_path_measure_get_closest_point_full(). Those 2 functions allow finding the closest point on a path to a given point.
This commit is contained in:
@@ -370,6 +370,8 @@ gsk_path_measure_unref
|
||||
<SUBSECTION>
|
||||
gsk_path_measure_get_length
|
||||
gsk_path_measure_get_point
|
||||
gsk_path_measure_get_closest_point
|
||||
gsk_path_measure_get_closest_point_full
|
||||
gsk_path_measure_add_segment
|
||||
<SUBSECTION Private>
|
||||
GSK_TYPE_PATH_MEASURE
|
||||
|
||||
316
gsk/gskpath.c
316
gsk/gskpath.c
@@ -76,6 +76,15 @@ struct _GskContourClass
|
||||
float distance,
|
||||
graphene_point_t *pos,
|
||||
graphene_vec2_t *tangent);
|
||||
gboolean (* get_closest_point) (const GskContour *contour,
|
||||
gpointer measure_data,
|
||||
float tolerance,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_offset,
|
||||
graphene_point_t *out_pos,
|
||||
float *out_distance,
|
||||
graphene_vec2_t *out_tangent);
|
||||
void (* copy) (const GskContour *contour,
|
||||
GskContour *dest);
|
||||
void (* add_segment) (const GskContour *contour,
|
||||
@@ -118,6 +127,39 @@ static GskContour *
|
||||
gsk_path_builder_add_contour_by_klass (GskPathBuilder *builder,
|
||||
const GskContourClass *klass);
|
||||
|
||||
static void
|
||||
gsk_find_point_on_line (const graphene_point_t *a,
|
||||
const graphene_point_t *b,
|
||||
const graphene_point_t *p,
|
||||
float *offset,
|
||||
graphene_point_t *pos)
|
||||
{
|
||||
graphene_vec2_t n;
|
||||
graphene_vec2_t ap;
|
||||
float t;
|
||||
|
||||
graphene_vec2_init (&n, b->x - a->x, b->y - a->y);
|
||||
graphene_vec2_init (&ap, p->x - a->x, p->y - a->y);
|
||||
|
||||
t = graphene_vec2_dot (&ap, &n) / graphene_vec2_dot (&n, &n);
|
||||
|
||||
if (t <= 0)
|
||||
{
|
||||
*pos = *a;
|
||||
*offset = 0;
|
||||
}
|
||||
else if (t >= 1)
|
||||
{
|
||||
*pos = *b;
|
||||
*offset = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
graphene_point_interpolate (a, b, t, pos);
|
||||
*offset = t;
|
||||
}
|
||||
}
|
||||
|
||||
/* RECT CONTOUR */
|
||||
|
||||
typedef struct _GskRectContour GskRectContour;
|
||||
@@ -270,6 +312,95 @@ gsk_rect_contour_get_point (const GskContour *contour,
|
||||
graphene_vec2_init (tangent, 0.0f, - copysignf (self->height, 1.0f));
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_rect_contour_get_closest_point (const GskContour *contour,
|
||||
gpointer measure_data,
|
||||
float tolerance,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_distance,
|
||||
graphene_point_t *out_pos,
|
||||
float *out_offset,
|
||||
graphene_vec2_t *out_tangent)
|
||||
{
|
||||
const GskRectContour *self = (const GskRectContour *) contour;
|
||||
graphene_point_t t, p;
|
||||
float distance;
|
||||
|
||||
/* offset coords to be relative to rectangle */
|
||||
t.x = point->x - self->x;
|
||||
t.y = point->y - self->y;
|
||||
|
||||
if (self->width)
|
||||
{
|
||||
/* do unit square math */
|
||||
t.x /= self->width;
|
||||
/* move point onto the square */
|
||||
t.x = CLAMP (t.x, 0.f, 1.f);
|
||||
}
|
||||
else
|
||||
t.x = 0.f;
|
||||
|
||||
if (self->height)
|
||||
{
|
||||
t.y /= self->height;
|
||||
t.y = CLAMP (t.y, 0.f, 1.f);
|
||||
}
|
||||
else
|
||||
t.y = 0.f;
|
||||
|
||||
if (t.x > 0 && t.x < 1 && t.y > 0 && t.y < 1)
|
||||
{
|
||||
float diff = MIN (t.x, 1.f - t.x) * ABS (self->width) - MIN (t.y, 1.f - t.y) * ABS (self->height);
|
||||
|
||||
if (diff < 0.f)
|
||||
t.x = ceilf (t.x - 0.5f); /* round 0.5 down */
|
||||
else if (diff > 0.f)
|
||||
t.y = roundf (t.y); /* round 0.5 up */
|
||||
else
|
||||
{
|
||||
/* at least 2 points match, return the first one in the stroke */
|
||||
if (t.y <= 1.f - t.y)
|
||||
t.y = 0.f;
|
||||
else if (1.f - t.x <= t.x)
|
||||
t.x = 1.f;
|
||||
else
|
||||
t.y = 1.f;
|
||||
}
|
||||
}
|
||||
|
||||
p = GRAPHENE_POINT_INIT (self->x + t.x * self->width,
|
||||
self->y + t.y * self->height);
|
||||
|
||||
distance = graphene_point_distance (point, &p, NULL, NULL);
|
||||
if (distance > threshold)
|
||||
return FALSE;
|
||||
|
||||
if (out_distance)
|
||||
*out_distance = distance;
|
||||
|
||||
if (out_pos)
|
||||
*out_pos = p;
|
||||
|
||||
if (out_offset)
|
||||
*out_offset = (t.x == 0.0 && self->width > 0 ? 2 - t.y : t.y) * ABS (self->height) +
|
||||
(t.y == 1.0 ? 2 - t.x : t.x) * ABS (self->width);
|
||||
|
||||
if (out_tangent)
|
||||
{
|
||||
if (t.y == 0 && t.x < 1)
|
||||
graphene_vec2_init (out_tangent, copysignf(1.0, self->width), 0);
|
||||
else if (t.x == 0)
|
||||
graphene_vec2_init (out_tangent, 0, - copysignf(1.0, self->height));
|
||||
else if (t.y == 1)
|
||||
graphene_vec2_init (out_tangent, - copysignf(1.0, self->width), 0);
|
||||
else if (t.x == 1)
|
||||
graphene_vec2_init (out_tangent, 0, copysignf(1.0, self->height));
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_rect_contour_copy (const GskContour *contour,
|
||||
GskContour *dest)
|
||||
@@ -357,6 +488,7 @@ static const GskContourClass GSK_RECT_CONTOUR_CLASS =
|
||||
gsk_rect_contour_init_measure,
|
||||
gsk_rect_contour_free_measure,
|
||||
gsk_rect_contour_get_point,
|
||||
gsk_rect_contour_get_closest_point,
|
||||
gsk_rect_contour_copy,
|
||||
gsk_rect_contour_add_segment
|
||||
};
|
||||
@@ -380,6 +512,7 @@ gsk_rect_contour_init (GskContour *contour,
|
||||
/* CIRCLE CONTOUR */
|
||||
|
||||
#define DEG_TO_RAD(x) ((x) * (G_PI / 180.f))
|
||||
#define RAD_TO_DEG(x) ((x) / (G_PI / 180.f))
|
||||
|
||||
typedef struct _GskCircleContour GskCircleContour;
|
||||
struct _GskCircleContour
|
||||
@@ -534,6 +667,72 @@ gsk_circle_contour_get_point (const GskContour *contour,
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_circle_contour_get_closest_point (const GskContour *contour,
|
||||
gpointer measure_data,
|
||||
float tolerance,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_distance,
|
||||
graphene_point_t *out_pos,
|
||||
float *out_offset,
|
||||
graphene_vec2_t *out_tangent)
|
||||
{
|
||||
const GskCircleContour *self = (const GskCircleContour *) contour;
|
||||
float angle;
|
||||
float closest_angle;
|
||||
float offset;
|
||||
graphene_point_t pos;
|
||||
graphene_vec2_t tangent;
|
||||
float distance;
|
||||
|
||||
if (graphene_point_distance (point, &self->center, NULL, NULL) > threshold + self->radius)
|
||||
return FALSE;
|
||||
|
||||
angle = atan2f (point->y - self->center.y, point->x - self->center.x);
|
||||
angle = RAD_TO_DEG (angle);
|
||||
if (angle < 0)
|
||||
angle += 360;
|
||||
|
||||
if ((self->start_angle <= angle && angle <= self->end_angle) ||
|
||||
(self->end_angle <= angle && angle <= self->start_angle))
|
||||
{
|
||||
closest_angle = angle;
|
||||
}
|
||||
else
|
||||
{
|
||||
float d1, d2;
|
||||
|
||||
d1 = fabs (self->start_angle - angle);
|
||||
d1 = MIN (d1, 360 - d1);
|
||||
d2 = fabs (self->end_angle - angle);
|
||||
d2 = MIN (d2, 360 - d2);
|
||||
if (d1 < d2)
|
||||
closest_angle = self->start_angle;
|
||||
else
|
||||
closest_angle = self->end_angle;
|
||||
}
|
||||
|
||||
offset = self->radius * 2 * M_PI * (closest_angle - self->start_angle) / (self->end_angle - self->start_angle);
|
||||
|
||||
gsk_circle_contour_get_point (contour, NULL, offset, &pos, out_tangent ? &tangent : NULL);
|
||||
|
||||
distance = graphene_point_distance (&pos, point, NULL, NULL);
|
||||
if (threshold < distance)
|
||||
return FALSE;
|
||||
|
||||
if (out_offset)
|
||||
*out_offset = offset;
|
||||
if (out_pos)
|
||||
*out_pos = pos;
|
||||
if (out_distance)
|
||||
*out_distance = distance;
|
||||
if (out_tangent)
|
||||
*out_tangent = tangent;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_circle_contour_copy (const GskContour *contour,
|
||||
GskContour *dest)
|
||||
@@ -583,6 +782,7 @@ static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS =
|
||||
gsk_circle_contour_init_measure,
|
||||
gsk_circle_contour_free_measure,
|
||||
gsk_circle_contour_get_point,
|
||||
gsk_circle_contour_get_closest_point,
|
||||
gsk_circle_contour_copy,
|
||||
gsk_circle_contour_add_segment
|
||||
};
|
||||
@@ -951,6 +1151,96 @@ gsk_standard_contour_get_point (const GskContour *contour,
|
||||
gsk_standard_contour_measure_get_point (self, measure->op, progress, pos, tangent);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gsk_standard_contour_get_closest_point (const GskContour *contour,
|
||||
gpointer measure_data,
|
||||
float tolerance,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_distance,
|
||||
graphene_point_t *out_pos,
|
||||
float *out_offset,
|
||||
graphene_vec2_t *out_tangent)
|
||||
{
|
||||
GskStandardContour *self = (GskStandardContour *) contour;
|
||||
GskStandardContourMeasure *measure;
|
||||
float progress, dist;
|
||||
GArray *array = measure_data;
|
||||
graphene_point_t p, last_point;
|
||||
gsize i;
|
||||
gboolean result = FALSE;
|
||||
|
||||
g_assert (self->ops[0].op == GSK_PATH_MOVE);
|
||||
last_point = self->points[0];
|
||||
|
||||
if (array->len == 0)
|
||||
{
|
||||
/* This is the special case for point-only */
|
||||
dist = graphene_point_distance (&last_point, point, NULL, NULL);
|
||||
|
||||
if (dist > threshold)
|
||||
return FALSE;
|
||||
|
||||
if (out_offset)
|
||||
*out_offset = 0;
|
||||
|
||||
if (out_distance)
|
||||
*out_distance = dist;
|
||||
|
||||
if (out_pos)
|
||||
*out_pos = last_point;
|
||||
|
||||
if (out_tangent)
|
||||
*out_tangent = *graphene_vec2_x_axis ();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
for (i = 0; i < array->len; i++)
|
||||
{
|
||||
measure = &g_array_index (array, GskStandardContourMeasure, i);
|
||||
|
||||
gsk_find_point_on_line (&last_point,
|
||||
&measure->end_point,
|
||||
point,
|
||||
&progress,
|
||||
&p);
|
||||
last_point = measure->end_point;
|
||||
dist = graphene_point_distance (point, &p, NULL, NULL);
|
||||
/* add some wiggleroom for the accurate check below */
|
||||
//g_print ("%zu: (%g-%g) dist %g\n", i, measure->start, measure->end, dist);
|
||||
if (dist <= threshold + 1.0f)
|
||||
{
|
||||
graphene_vec2_t t;
|
||||
float found_progress;
|
||||
|
||||
found_progress = measure->start_progress + (measure->end_progress - measure->start_progress) * progress;
|
||||
|
||||
gsk_standard_contour_measure_get_point (self, measure->op, found_progress, &p, out_tangent ? &t : NULL);
|
||||
dist = graphene_point_distance (point, &p, NULL, NULL);
|
||||
/* double check that the point actually is closer */
|
||||
//g_print ("!!! %zu: (%g-%g) dist %g\n", i, measure->start, measure->end, dist);
|
||||
if (dist <= threshold)
|
||||
{
|
||||
if (out_distance)
|
||||
*out_distance = dist;
|
||||
if (out_pos)
|
||||
*out_pos = p;
|
||||
if (out_offset)
|
||||
*out_offset = measure->start + (measure->end - measure->start) * progress;
|
||||
if (out_tangent)
|
||||
*out_tangent = t;
|
||||
result = TRUE;
|
||||
if (tolerance >= dist)
|
||||
return TRUE;
|
||||
threshold = dist - tolerance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void
|
||||
gsk_standard_contour_init (GskContour *contour,
|
||||
GskPathFlags flags,
|
||||
@@ -1134,6 +1424,7 @@ static const GskContourClass GSK_STANDARD_CONTOUR_CLASS =
|
||||
gsk_standard_contour_init_measure,
|
||||
gsk_standard_contour_free_measure,
|
||||
gsk_standard_contour_get_point,
|
||||
gsk_standard_contour_get_closest_point,
|
||||
gsk_standard_contour_copy,
|
||||
gsk_standard_contour_add_segment
|
||||
};
|
||||
@@ -1230,6 +1521,31 @@ gsk_contour_get_point (GskPath *path,
|
||||
self->klass->get_point (self, measure_data, distance, pos, tangent);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gsk_contour_get_closest_point (GskPath *path,
|
||||
gsize i,
|
||||
gpointer measure_data,
|
||||
float tolerance,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_distance,
|
||||
graphene_point_t *out_pos,
|
||||
float *out_offset,
|
||||
graphene_vec2_t *out_tangent)
|
||||
{
|
||||
GskContour *self = path->contours[i];
|
||||
|
||||
return self->klass->get_closest_point (self,
|
||||
measure_data,
|
||||
tolerance,
|
||||
point,
|
||||
threshold,
|
||||
out_distance,
|
||||
out_pos,
|
||||
out_offset,
|
||||
out_tangent);
|
||||
}
|
||||
|
||||
/* PATH */
|
||||
|
||||
static GskPath *
|
||||
|
||||
@@ -260,6 +260,120 @@ gsk_path_measure_get_point (GskPathMeasure *self,
|
||||
tangent);
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_measure_get_closest_point:
|
||||
* @self: a #GskPathMeasure
|
||||
* @point: the point to fond the closest point to
|
||||
* @out_pos: (out) (optional) (caller-allocates): return location
|
||||
* for the closest point
|
||||
*
|
||||
* Gets the point on the path that is closest to @point.
|
||||
*
|
||||
* If the path being measured is empty, return 0 and set
|
||||
* @out_pos to (0, 0).
|
||||
*
|
||||
* This is a simpler and slower version of
|
||||
* gsk_path_measure_get_closest_point_full(). Use that one if you
|
||||
* need more control.
|
||||
*
|
||||
* Returns: The offset into the path of the closest point
|
||||
**/
|
||||
float
|
||||
gsk_path_measure_get_closest_point (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
graphene_point_t *out_pos)
|
||||
{
|
||||
float result;
|
||||
|
||||
g_return_val_if_fail (self != NULL, 0.0f);
|
||||
|
||||
if (gsk_path_measure_get_closest_point_full (self,
|
||||
point,
|
||||
INFINITY,
|
||||
&result,
|
||||
out_pos,
|
||||
NULL,
|
||||
NULL))
|
||||
return result;
|
||||
|
||||
if (out_pos)
|
||||
*out_pos = GRAPHENE_POINT_INIT (0, 0);
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_measure_get_closest_point_full:
|
||||
* @self: a #GskPathMeasure
|
||||
* @point: the point to fond the closest point to
|
||||
* @threshold: The maximum allowed distance between the path and @point.
|
||||
* Use INFINITY to look for any point.
|
||||
* @out_distance: (out) (optional) (caller-allocates): The
|
||||
* distance between the found closest point on the path and the given
|
||||
* @point.
|
||||
* @out_pos: (out) (optional) (caller-allocates): return location
|
||||
* for the closest point
|
||||
* @out_offset: (out) (optional) (caller-allocates): The offset into
|
||||
* the path of the found point
|
||||
* @out_tangent: (out) (optional) (caller-allocates): return location for
|
||||
* the tangent at the closest point
|
||||
*
|
||||
* Gets the point on the path that is closest to @point. If no point on
|
||||
* path is closer to @point than @threshold, return %FALSE.
|
||||
*
|
||||
* Returns: %TRUE if a pointwas found, %FALSE otherwise.
|
||||
**/
|
||||
gboolean
|
||||
gsk_path_measure_get_closest_point_full (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_distance,
|
||||
graphene_point_t *out_pos,
|
||||
float *out_offset,
|
||||
graphene_vec2_t *out_tangent)
|
||||
{
|
||||
gboolean result;
|
||||
gsize i;
|
||||
float distance, length;
|
||||
|
||||
g_return_val_if_fail (self != NULL, FALSE);
|
||||
g_return_val_if_fail (point != NULL, FALSE);
|
||||
|
||||
result = FALSE;
|
||||
length = 0;
|
||||
|
||||
for (i = 0; i < self->n_contours; i++)
|
||||
{
|
||||
if (gsk_contour_get_closest_point (self->path,
|
||||
i,
|
||||
self->measures[i].contour_data,
|
||||
self->tolerance,
|
||||
point,
|
||||
threshold,
|
||||
&distance,
|
||||
out_pos,
|
||||
out_offset,
|
||||
out_tangent))
|
||||
{
|
||||
result = TRUE;
|
||||
if (out_offset)
|
||||
*out_offset += length;
|
||||
|
||||
if (distance < self->tolerance)
|
||||
break;
|
||||
threshold = distance - self->tolerance;
|
||||
}
|
||||
|
||||
length += self->measures[i].length;
|
||||
}
|
||||
|
||||
if (result && out_distance)
|
||||
*out_distance = distance;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* gsk_path_measure_add_segment:
|
||||
* @self: a #GskPathMeasure
|
||||
@@ -308,8 +422,10 @@ gsk_path_measure_add_segment (GskPathMeasure *self,
|
||||
self->measures[i].contour_data,
|
||||
start,
|
||||
len);
|
||||
start = 0;
|
||||
end -= len;
|
||||
start = 0;
|
||||
if (end <= 0)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -51,6 +51,18 @@ void gsk_path_measure_get_point (GskPathMeasure
|
||||
float distance,
|
||||
graphene_point_t *pos,
|
||||
graphene_vec2_t *tangent);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
float gsk_path_measure_get_closest_point (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
graphene_point_t *out_pos);
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
gboolean gsk_path_measure_get_closest_point_full (GskPathMeasure *self,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_distance,
|
||||
graphene_point_t *out_pos,
|
||||
float *out_offset,
|
||||
graphene_vec2_t *out_tangent);
|
||||
|
||||
GDK_AVAILABLE_IN_ALL
|
||||
void gsk_path_measure_add_segment (GskPathMeasure *self,
|
||||
|
||||
@@ -47,6 +47,16 @@ void gsk_contour_get_point (GskPath
|
||||
float distance,
|
||||
graphene_point_t *pos,
|
||||
graphene_vec2_t *tangent);
|
||||
gboolean gsk_contour_get_closest_point (GskPath *path,
|
||||
gsize i,
|
||||
gpointer measure_data,
|
||||
float tolerance,
|
||||
const graphene_point_t *point,
|
||||
float threshold,
|
||||
float *out_distance,
|
||||
graphene_point_t *out_pos,
|
||||
float *out_offset,
|
||||
graphene_vec2_t *out_tangent);
|
||||
|
||||
void gsk_path_builder_add_contour (GskPathBuilder *builder,
|
||||
GskPath *path,
|
||||
|
||||
Reference in New Issue
Block a user