[gtk/wip/otte/lottie: 22/56] path: Add gsk_path_measure_get_point()




commit 8741070649fab09cef9d0886bed7d73c86f75ba2
Author: Benjamin Otte <otte redhat com>
Date:   Thu Nov 19 09:33:35 2020 +0100

    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 ff3e037a71..89578e656e 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..792a390e3e 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) (optional) (caller-allocates): The coordinates
+ *    of the position at @distance
+ * @tangent: (out) (optional) (caller-allocates): 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 f38e77302b..72db10690c 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 4ccf4cf6d2..fe2928a1d6 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],


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]