From 45ba4e1bda8c98b69e8f622ecf9705eb905e3604 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Mon, 7 Dec 2020 01:22:39 -0500 Subject: [PATCH] curve: Add gsk_curve_get_bounds Add getters for bounding boxes of curves. Bounding boxes are needed to implement intersection via bisecting. --- gsk/gskcurve.c | 249 ++++++++++++++++++++++++++++++++++++++++++ gsk/gskcurveprivate.h | 5 + 2 files changed, 254 insertions(+) diff --git a/gsk/gskcurve.c b/gsk/gskcurve.c index 87857cc4ef..0cfe34e4f4 100644 --- a/gsk/gskcurve.c +++ b/gsk/gskcurve.c @@ -66,6 +66,10 @@ struct _GskCurveClass graphene_vec2_t *tangent); void (* reverse) (const GskCurve *curve, GskCurve *reverse); + void (* get_bounds) (const GskCurve *curve, + GskBoundingBox *bounds); + void (* get_tight_bounds) (const GskCurve *curve, + GskBoundingBox *bounds); }; static void @@ -233,6 +237,16 @@ gsk_line_curve_reverse (const GskCurve *curve, reverse->line.points[1] = self->points[0]; } +static void +gsk_line_curve_get_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskLineCurve *self = &curve->line; + const graphene_point_t *pts = self->points; + + gsk_bounding_box_init (bounds, &pts[0], &pts[1]); +} + static const GskCurveClass GSK_LINE_CURVE_CLASS = { gsk_line_curve_init, gsk_line_curve_init_foreach, @@ -248,6 +262,8 @@ static const GskCurveClass GSK_LINE_CURVE_CLASS = { gsk_line_curve_get_start_end_tangent, gsk_line_curve_get_start_end_tangent, gsk_line_curve_reverse, + gsk_line_curve_get_bounds, + gsk_line_curve_get_bounds, }; /** QUADRATIC **/ @@ -521,6 +537,58 @@ gsk_quad_curve_reverse (const GskCurve *curve, reverse->cubic.has_coefficients = FALSE; } +static void +gsk_quad_curve_get_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskQuadCurve *self = &curve->quad; + const graphene_point_t *pts = self->points; + + gsk_bounding_box_init (bounds, &pts[0], &pts[2]); + gsk_bounding_box_expand (bounds, &pts[1]); +} + +/* Solve P' = 0 where P is + * P = (1-t)^2*pa + 2*t*(1-t)*pb + t^2*pc + */ +static int +get_quadratic_extrema (float pa, float pb, float pc, float t[1]) +{ + float d = pa - 2 * pb + pc; + + if (fabs (d) > 0.0001) + { + t[0] = (pa - pb) / d; + return 1; + } + + return 0; +} + +static void +gsk_quad_curve_get_tight_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskQuadCurve *self = &curve->quad; + const graphene_point_t *pts = self->points; + float t[4]; + int n; + + gsk_bounding_box_init (bounds, &pts[0], &pts[2]); + + n = 0; + n += get_quadratic_extrema (pts[0].x, pts[1].x, pts[2].x, &t[n]); + n += get_quadratic_extrema (pts[0].y, pts[1].y, pts[2].y, &t[n]); + + for (int i = 0; i < n; i++) + { + graphene_point_t p; + + gsk_quad_curve_get_point (curve, t[i], &p); + gsk_bounding_box_expand (bounds, &p); + } +} + static const GskCurveClass GSK_QUAD_CURVE_CLASS = { gsk_quad_curve_init, gsk_quad_curve_init_foreach, @@ -536,6 +604,8 @@ static const GskCurveClass GSK_QUAD_CURVE_CLASS = { gsk_quad_curve_get_start_tangent, gsk_quad_curve_get_end_tangent, gsk_quad_curve_reverse, + gsk_quad_curve_get_bounds, + gsk_quad_curve_get_tight_bounds, }; /** CUBIC **/ @@ -786,6 +856,91 @@ gsk_cubic_curve_reverse (const GskCurve *curve, reverse->cubic.has_coefficients = FALSE; } +static void +gsk_cubic_curve_get_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskCubicCurve *self = &curve->cubic; + const graphene_point_t *pts = self->points; + + gsk_bounding_box_init (bounds, &pts[0], &pts[3]); + gsk_bounding_box_expand (bounds, &pts[1]); + gsk_bounding_box_expand (bounds, &pts[2]); +} + +static inline gboolean +acceptable (float t) +{ + return 0 <= t && t <= 1; +} + +/* Solve P' = 0 where P is + * P = (1-t)^3*pa + 3*t*(1-t)^2*pb + 3*t^2*(1-t)*pc + t^3*pd + */ +static int +get_cubic_extrema (float pa, float pb, float pc, float pd, float t[2]) +{ + float a, b, c; + float d, tt; + int n = 0; + + a = 3 * (pd - 3*pc + 3*pb - pa); + b = 6 * (pc - 2*pb + pa); + c = 3 * (pb - pa); + + if (fabs (a) > 0.0001) + { + if (b*b > 4*a*c) + { + d = sqrt (b*b - 4*a*c); + tt = (-b + d)/(2*a); + if (acceptable (tt)) + t[n++] = tt; + tt = (-b - d)/(2*a); + if (acceptable (tt)) + t[n++] = tt; + } + else + { + tt = -b / (2*a); + if (acceptable (tt)) + t[n++] = tt; + } + } + else if (fabs (b) > 0.0001) + { + tt = -c / b; + if (acceptable (tt)) + t[n++] = tt; + } + + return n; +} + +static void +gsk_cubic_curve_get_tight_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskCubicCurve *self = &curve->cubic; + const graphene_point_t *pts = self->points; + float t[4]; + int n; + + gsk_bounding_box_init (bounds, &pts[0], &pts[3]); + + n = 0; + n += get_cubic_extrema (pts[0].x, pts[1].x, pts[2].x, pts[3].x, &t[n]); + n += get_cubic_extrema (pts[0].y, pts[1].y, pts[2].y, pts[3].y, &t[n]); + + for (int i = 0; i < n; i++) + { + graphene_point_t p; + + gsk_cubic_curve_get_point (curve, t[i], &p); + gsk_bounding_box_expand (bounds, &p); + } +} + static const GskCurveClass GSK_CUBIC_CURVE_CLASS = { gsk_cubic_curve_init, gsk_cubic_curve_init_foreach, @@ -801,6 +956,8 @@ static const GskCurveClass GSK_CUBIC_CURVE_CLASS = { gsk_cubic_curve_get_start_tangent, gsk_cubic_curve_get_end_tangent, gsk_cubic_curve_reverse, + gsk_cubic_curve_get_bounds, + gsk_cubic_curve_get_tight_bounds, }; /** CONIC **/ @@ -1320,6 +1477,82 @@ gsk_conic_curve_reverse (const GskCurve *curve, reverse->conic.has_coefficients = FALSE; } +static void +gsk_conic_curve_get_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskConicCurve *self = &curve->conic; + const graphene_point_t *pts = self->points; + + gsk_bounding_box_init (bounds, &pts[0], &pts[3]); + gsk_bounding_box_expand (bounds, &pts[1]); +} + +/* Solve N = 0 where N is the numerator of (P/Q)', with + * P = (1-t)^2*a + 2*t*(1-t)*w*b + t^2*c + * Q = (1-t)^2 + 2*t*(1-t)*w + t^2 + */ +static int +get_conic_extrema (float a, float b, float c, float w, float t[4]) +{ + float q, tt; + int n = 0; + float w2 = w*w; + float wac = (w - 1)*(a - c); + + if (wac != 0) + { + q = - sqrt (a*a - 4*a*b*w2 + 4*a*c*w2 - 2*a*c + 4*b*b*w2 - 4*b*c*w2 + c*c); + + tt = (- q + 2*a*w - a - 2*b*w + c)/(2*wac); + + if (acceptable (tt)) + t[n++] = tt; + + tt = (q + 2*a*w - a - 2*b*w + c)/(2*wac); + + if (acceptable (tt)) + t[n++] = tt; + } + + if (w * (b - c) != 0 && a == c) + t[n++] = 0.5; + + if (w == 1 && a - 2*b + c != 0) + { + tt = (a - b) / (a - 2*b + c); + if (acceptable (tt)) + t[n++] = tt; + } + + return n; +} + +static void +gsk_conic_curve_get_tight_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + const GskConicCurve *self = &curve->conic; + float w = gsk_conic_curve_get_weight (self); + const graphene_point_t *pts = self->points; + float t[8]; + int n; + + gsk_bounding_box_init (bounds, &pts[0], &pts[3]); + + n = 0; + n += get_conic_extrema (pts[0].x, pts[1].x, pts[3].x, w, &t[n]); + n += get_conic_extrema (pts[0].y, pts[1].y, pts[3].y, w, &t[n]); + + for (int i = 0; i < n; i++) + { + graphene_point_t p; + + gsk_conic_curve_get_point (curve, t[i], &p); + gsk_bounding_box_expand (bounds, &p); + } +} + static const GskCurveClass GSK_CONIC_CURVE_CLASS = { gsk_conic_curve_init, gsk_conic_curve_init_foreach, @@ -1335,6 +1568,8 @@ static const GskCurveClass GSK_CONIC_CURVE_CLASS = { gsk_conic_curve_get_start_tangent, gsk_conic_curve_get_end_tangent, gsk_conic_curve_reverse, + gsk_conic_curve_get_bounds, + gsk_conic_curve_get_tight_bounds, }; /** API **/ @@ -1472,6 +1707,20 @@ gsk_curve_reverse (const GskCurve *curve, get_class (curve->op)->reverse (curve, reverse); } +void +gsk_curve_get_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + get_class (curve->op)->get_bounds (curve, bounds); +} + +void +gsk_curve_get_tight_bounds (const GskCurve *curve, + GskBoundingBox *bounds) +{ + get_class (curve->op)->get_tight_bounds (curve, bounds); +} + static inline void _sincosf (float angle, float *out_s, diff --git a/gsk/gskcurveprivate.h b/gsk/gskcurveprivate.h index 46c72d6194..5ab9a9bfec 100644 --- a/gsk/gskcurveprivate.h +++ b/gsk/gskcurveprivate.h @@ -23,6 +23,7 @@ #include "gskpathopprivate.h" #include "gskpath.h" +#include "gskboundingboxprivate.h" G_BEGIN_DECLS @@ -152,6 +153,10 @@ void gsk_curve_reverse (const GskCurve void gsk_curve_elevate (const GskCurve *curve, GskCurve *elevated); +void gsk_curve_get_bounds (const GskCurve *curve, + GskBoundingBox *bounds); +void gsk_curve_get_tight_bounds (const GskCurve *curve, + GskBoundingBox *bounds); int gsk_curve_get_curvature_points (const GskCurve *curve, float t[3]);