From 9685a476935c13c77cc29c2155b6a20fa8834e85 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Thu, 19 Nov 2020 09:33:35 +0100 Subject: [PATCH] path: Add gsk_path_measure_get_point() Allows querying the coordinates and direction of any specific point on a path. --- gsk/gskpath.c | 192 +++++++++++++++++++++++++++++++++++++---- gsk/gskpathmeasure.c | 82 +++++++++++++++++- gsk/gskpathmeasure.h | 5 ++ gsk/gskpathprivate.h | 6 ++ gsk/gskspline.c | 35 ++++++++ gsk/gsksplineprivate.h | 4 + 6 files changed, 307 insertions(+), 17 deletions(-) diff --git a/gsk/gskpath.c b/gsk/gskpath.c index 0985701bd0..811b10c393 100644 --- a/gsk/gskpath.c +++ b/gsk/gskpath.c @@ -71,6 +71,11 @@ struct _GskContourClass float *out_length); void (* free_measure) (const GskContour *contour, gpointer measure_data); + void (* get_point) (const GskContour *contour, + gpointer measure_data, + float distance, + graphene_point_t *pos, + graphene_vec2_t *tangent); void (* copy) (const GskContour *contour, GskContour *dest); void (* add_segment) (const GskContour *contour, @@ -220,6 +225,51 @@ gsk_rect_contour_free_measure (const GskContour *contour, { } +static void +gsk_rect_contour_get_point (const GskContour *contour, + gpointer measure_data, + float distance, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + const GskRectContour *self = (const GskRectContour *) contour; + + if (distance < fabsf (self->width)) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x + copysignf (distance, self->width), self->y); + if (tangent) + graphene_vec2_init (tangent, copysignf (1.0f, self->width), 0.0f); + return; + } + distance -= fabsf (self->width); + + if (distance < fabsf (self->height)) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x + self->width, self->y + copysignf (distance, self->height)); + if (tangent) + graphene_vec2_init (tangent, 0.0f, copysignf (self->height, 1.0f)); + return; + } + distance -= fabs (self->height); + + if (distance < fabsf (self->width)) + { + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x + self->width - copysignf (distance, self->width), self->y + self->height); + if (tangent) + graphene_vec2_init (tangent, - copysignf (1.0f, self->width), 0.0f); + return; + } + distance -= fabsf (self->width); + + if (pos) + *pos = GRAPHENE_POINT_INIT (self->x, self->y + self->height - copysignf (distance, self->height)); + if (tangent) + graphene_vec2_init (tangent, 0.0f, - copysignf (self->height, 1.0f)); +} + static void gsk_rect_contour_copy (const GskContour *contour, GskContour *dest) @@ -306,6 +356,7 @@ static const GskContourClass GSK_RECT_CONTOUR_CLASS = gsk_rect_contour_foreach, gsk_rect_contour_init_measure, gsk_rect_contour_free_measure, + gsk_rect_contour_get_point, gsk_rect_contour_copy, gsk_rect_contour_add_segment }; @@ -455,6 +506,34 @@ gsk_circle_contour_free_measure (const GskContour *contour, { } +static void +gsk_circle_contour_get_point (const GskContour *contour, + gpointer measure_data, + float distance, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + const GskCircleContour *self = (const GskCircleContour *) contour; + float delta = self->end_angle - self->start_angle; + float length = self->radius * DEG_TO_RAD (delta); + float angle = self->start_angle + distance/length * delta; + graphene_point_t p; + + p = GRAPHENE_POINT_INIT (self->center.x + cos (DEG_TO_RAD (angle)) * self->radius, + self->center.y + sin (DEG_TO_RAD (angle)) * self->radius); + + if (pos) + *pos = p; + + if (tangent) + { + graphene_vec2_init (tangent, + p.y - self->center.y, + - p.x + self->center.x); + graphene_vec2_normalize (tangent, tangent); + } +} + static void gsk_circle_contour_copy (const GskContour *contour, GskContour *dest) @@ -503,6 +582,7 @@ static const GskContourClass GSK_CIRCLE_CONTOUR_CLASS = gsk_circle_contour_foreach, gsk_circle_contour_init_measure, gsk_circle_contour_free_measure, + gsk_circle_contour_get_point, gsk_circle_contour_copy, gsk_circle_contour_add_segment }; @@ -788,6 +868,89 @@ gsk_standard_contour_free_measure (const GskContour *contour, g_array_free (data, TRUE); } +static int +gsk_standard_contour_find_measure (gconstpointer m, + gconstpointer l) +{ + const GskStandardContourMeasure *measure = m; + float length = *(const float *) l; + + if (measure->start > length) + return 1; + else if (measure->end <= length) + return -1; + else + return 0; +} + +static void +gsk_standard_contour_measure_get_point (GskStandardContour *self, + gsize op, + float progress, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + const graphene_point_t *pts; + + pts = &self->points[self->ops[op].point]; + switch (self->ops[op].op) + { + case GSK_PATH_LINE: + case GSK_PATH_CLOSE: + if (pos) + graphene_point_interpolate (&pts[0], &pts[1], progress, pos); + if (tangent) + { + graphene_vec2_init (tangent, pts[1].x - pts[0].x, pts[1].y - pts[0].y); + graphene_vec2_normalize (tangent, tangent); + } + break; + + case GSK_PATH_CURVE: + gsk_spline_get_point_cubic (pts, progress, pos, tangent); + break; + + case GSK_PATH_MOVE: + default: + g_assert_not_reached (); + return; + } +} + +static void +gsk_standard_contour_get_point (const GskContour *contour, + gpointer measure_data, + float distance, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + GskStandardContour *self = (GskStandardContour *) contour; + GArray *array = measure_data; + guint index; + float progress; + GskStandardContourMeasure *measure; + + if (array->len == 0) + { + g_assert (distance == 0); + g_assert (self->ops[0].op == GSK_PATH_MOVE); + if (pos) + *pos = self->points[0]; + if (tangent) + graphene_vec2_init (tangent, 1.f, 0.f); + return; + } + + if (!g_array_binary_search (array, &distance, gsk_standard_contour_find_measure, &index)) + index = array->len - 1; + measure = &g_array_index (array, GskStandardContourMeasure, index); + progress = (distance - measure->start) / (measure->end - measure->start); + progress = measure->start_progress + (measure->end_progress - measure->start_progress) * progress; + g_assert (progress >= 0 && progress <= 1); + + gsk_standard_contour_measure_get_point (self, measure->op, progress, pos, tangent); +} + static void gsk_standard_contour_init (GskContour *contour, GskPathFlags flags, @@ -805,21 +968,6 @@ gsk_standard_contour_copy (const GskContour *contour, gsk_standard_contour_init (dest, self->flags, self->ops, self->n_ops, self->points, self->n_points); } -static int -gsk_standard_contour_find_measure (gconstpointer m, - gconstpointer l) -{ - const GskStandardContourMeasure *measure = m; - float length = *(const float *) l; - - if (measure->start > length) - return 1; - else if (measure->end <= length) - return -1; - else - return 0; -} - static void gsk_standard_contour_add_segment (const GskContour *contour, GskPathBuilder *builder, @@ -985,6 +1133,7 @@ static const GskContourClass GSK_STANDARD_CONTOUR_CLASS = gsk_standard_contour_foreach, gsk_standard_contour_init_measure, gsk_standard_contour_free_measure, + gsk_standard_contour_get_point, gsk_standard_contour_copy, gsk_standard_contour_add_segment }; @@ -1068,6 +1217,19 @@ gsk_contour_free_measure (GskPath *path, self->klass->free_measure (self, data); } +void +gsk_contour_get_point (GskPath *path, + gsize i, + gpointer measure_data, + float distance, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + GskContour *self = path->contours[i]; + + self->klass->get_point (self, measure_data, distance, pos, tangent); +} + /* PATH */ static GskPath * diff --git a/gsk/gskpathmeasure.c b/gsk/gskpathmeasure.c index 61b802508b..d467b53146 100644 --- a/gsk/gskpathmeasure.c +++ b/gsk/gskpathmeasure.c @@ -182,6 +182,84 @@ gsk_path_measure_get_length (GskPathMeasure *self) return self->length; } +static float +gsk_path_measure_clamp_distance (GskPathMeasure *self, + float distance) +{ + if (isnan (distance)) + return 0; + + return CLAMP (distance, 0, self->length); +} + +/** + * gsk_path_measure_get_point: + * @self: a #GskPathMeasure + * @distance: distance into the path + * @pos: (out caller-allocates) (optional): The coordinates + * of the position at @distance + * @tangent: (out caller-allocates) (optional): The tangent + * to the position at @distance + * + * Calculates the coordinates and tangent of the point @distance + * units into the path. The value will be clamped to the length + * of the path. + * + * If the point is a discontinuous edge in the path, the returned + * point and tangent will describe the line starting at that point + * going forward. + * + * If @self describes an empty path, the returned point will be + * set to `(0, 0)` and the tangent will be the x axis or `(1, 0)`. + **/ +void +gsk_path_measure_get_point (GskPathMeasure *self, + float distance, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + gsize i; + + g_return_if_fail (self != NULL); + + if (pos == NULL && tangent == NULL) + return; + + distance = gsk_path_measure_clamp_distance (self, distance); + + for (i = 0; i < self->n_contours; i++) + { + if (distance < self->measures[i].length) + break; + + distance -= self->measures[i].length; + } + + /* weird corner cases */ + if (i == self->n_contours) + { + /* the empty path goes here */ + if (self->n_contours == 0) + { + if (pos) + graphene_point_init (pos, 0.f, 0.f); + if (tangent) + graphene_vec2_init (tangent, 1.f, 0.f); + return; + } + /* rounding errors can make this happen */ + i = self->n_contours - 1; + distance = self->measures[i].length; + } + + gsk_contour_get_point (self->path, + i, + self->measures[i].contour_data, + distance, + pos, + tangent); +} + /** * gsk_path_measure_add_segment: * @self: a #GskPathMeasure @@ -209,8 +287,8 @@ gsk_path_measure_add_segment (GskPathMeasure *self, g_return_if_fail (self != NULL); g_return_if_fail (builder != NULL); - start = CLAMP (start, 0, self->length); - end = CLAMP (end, 0, self->length); + start = gsk_path_measure_clamp_distance (self, start); + end = gsk_path_measure_clamp_distance (self, end); if (start >= end) return; diff --git a/gsk/gskpathmeasure.h b/gsk/gskpathmeasure.h index beb07ac763..d16491c5de 100644 --- a/gsk/gskpathmeasure.h +++ b/gsk/gskpathmeasure.h @@ -46,6 +46,11 @@ void gsk_path_measure_unref (GskPathMeasure GDK_AVAILABLE_IN_ALL float gsk_path_measure_get_length (GskPathMeasure *self); +GDK_AVAILABLE_IN_ALL +void gsk_path_measure_get_point (GskPathMeasure *self, + float distance, + graphene_point_t *pos, + graphene_vec2_t *tangent); GDK_AVAILABLE_IN_ALL void gsk_path_measure_add_segment (GskPathMeasure *self, diff --git a/gsk/gskpathprivate.h b/gsk/gskpathprivate.h index 6c51d09524..7af9dbcaff 100644 --- a/gsk/gskpathprivate.h +++ b/gsk/gskpathprivate.h @@ -41,6 +41,12 @@ gpointer gsk_contour_init_measure (GskPath void gsk_contour_free_measure (GskPath *path, gsize i, gpointer data); +void gsk_contour_get_point (GskPath *path, + gsize i, + gpointer measure_data, + float distance, + graphene_point_t *pos, + graphene_vec2_t *tangent); void gsk_path_builder_add_contour (GskPathBuilder *builder, GskPath *path, diff --git a/gsk/gskspline.c b/gsk/gskspline.c index fc91816673..e1e7f39c53 100644 --- a/gsk/gskspline.c +++ b/gsk/gskspline.c @@ -47,6 +47,41 @@ gsk_spline_decompose_add_point (GskCubicDecomposition *decomp, decomp->last_progress += progress; } +static void +gsk_spline_cubic_get_coefficients (graphene_point_t coeffs[4], + const graphene_point_t pts[4]) +{ + coeffs[0] = GRAPHENE_POINT_INIT (pts[3].x - 3.0f * pts[2].x + 3.0f * pts[1].x - pts[0].x, + pts[3].y - 3.0f * pts[2].y + 3.0f * pts[1].y - pts[0].y); + coeffs[1] = GRAPHENE_POINT_INIT (3.0f * pts[2].x - 6.0f * pts[1].x + 3.0f * pts[0].x, + 3.0f * pts[2].y - 6.0f * pts[1].y + 3.0f * pts[0].y); + coeffs[2] = GRAPHENE_POINT_INIT (3.0f * pts[1].x - 3.0f * pts[0].x, + 3.0f * pts[1].y - 3.0f * pts[0].y); + coeffs[3] = pts[0]; +} + +void +gsk_spline_get_point_cubic (const graphene_point_t pts[4], + float progress, + graphene_point_t *pos, + graphene_vec2_t *tangent) +{ + graphene_point_t c[4]; + + gsk_spline_cubic_get_coefficients (c, pts); + + if (pos) + *pos = GRAPHENE_POINT_INIT (((c[0].x * progress + c[1].x) * progress +c[2].x) * progress + c[3].x, + ((c[0].y * progress + c[1].y) * progress +c[2].y) * progress + c[3].y); + if (tangent) + { + graphene_vec2_init (tangent, + (3.0f * c[0].x * progress + 2.0f * c[1].x) * progress + c[2].x, + (3.0f * c[0].y * progress + 2.0f * c[1].y) * progress + c[2].y); + graphene_vec2_normalize (tangent, tangent); + } +} + void gsk_spline_split_cubic (const graphene_point_t pts[4], graphene_point_t result1[4], diff --git a/gsk/gsksplineprivate.h b/gsk/gsksplineprivate.h index a266b0d1a1..7bd6282402 100644 --- a/gsk/gsksplineprivate.h +++ b/gsk/gsksplineprivate.h @@ -31,6 +31,10 @@ typedef void (* GskSplineAddPointFunc) (const graphene_point_t *from, float to_progress, gpointer user_data); +void gsk_spline_get_point_cubic (const graphene_point_t pts[4], + float progress, + graphene_point_t *pos, + graphene_vec2_t *tangent); void gsk_spline_split_cubic (const graphene_point_t pts[4], graphene_point_t result1[4], graphene_point_t result2[4],