curve: Add gsk_curve_get_bounds

Add getters for bounding boxes of curves.

We have cheap ones, which are just the bounding
box of the control points, and tighter ones, which
require finding the actual extrema.

Bounding boxes are needed to implement intersection
via bisecting.
This commit is contained in:
Matthias Clasen
2020-12-07 01:22:39 -05:00
parent 9d5c23f342
commit ba442fc24e
2 changed files with 254 additions and 0 deletions

View File

@@ -70,6 +70,10 @@ struct _GskCurveClass
float tolerance,
GskCurveAddCurveFunc add_curve_func,
gpointer user_data);
void (* get_bounds) (const GskCurve *curve,
GskBoundingBox *bounds);
void (* get_tight_bounds) (const GskCurve *curve,
GskBoundingBox *bounds);
};
/* {{{ Utilities */
@@ -296,6 +300,16 @@ gsk_line_curve_decompose_curve (const GskCurve *curve,
return add_curve_func (GSK_PATH_LINE, self->points, 2, 0.f, user_data);
}
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,
@@ -313,6 +327,8 @@ static const GskCurveClass GSK_LINE_CURVE_CLASS = {
gsk_line_curve_segment,
gsk_line_curve_decompose,
gsk_line_curve_decompose_curve,
gsk_line_curve_get_bounds,
gsk_line_curve_get_bounds,
};
/* }}} */
@@ -609,6 +625,58 @@ gsk_quad_curve_decompose_curve (const GskCurve *curve,
}
}
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,
@@ -626,6 +694,8 @@ static const GskCurveClass GSK_QUAD_CURVE_CLASS = {
gsk_quad_curve_segment,
gsk_quad_curve_decompose,
gsk_quad_curve_decompose_curve,
gsk_quad_curve_get_bounds,
gsk_quad_curve_get_tight_bounds,
};
/* }}} */
@@ -1004,6 +1074,91 @@ gsk_cubic_curve_decompose_curve (const GskCurve *curve,
&(AddLineData) { add_curve_func, user_data });
}
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,
@@ -1021,6 +1176,8 @@ static const GskCurveClass GSK_CUBIC_CURVE_CLASS = {
gsk_cubic_curve_segment,
gsk_cubic_curve_decompose,
gsk_cubic_curve_decompose_curve,
gsk_cubic_curve_get_bounds,
gsk_cubic_curve_get_tight_bounds,
};
/* }}} */
@@ -1585,6 +1742,82 @@ gsk_conic_curve_decompose_curve (const GskCurve *curve,
&(AddLineData) { add_curve_func, user_data });
}
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,
@@ -1602,6 +1835,8 @@ static const GskCurveClass GSK_CONIC_CURVE_CLASS = {
gsk_conic_curve_segment,
gsk_conic_curve_decompose,
gsk_conic_curve_decompose_curve,
gsk_conic_curve_get_bounds,
gsk_conic_curve_get_tight_bounds,
};
/* }}} */
@@ -1780,6 +2015,20 @@ gsk_curve_decompose_curve (const GskCurve *curve,
return get_class (curve->op)->decompose_curve (curve, flags, tolerance, add_curve_func, user_data);
}
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);
}
/* }}} */
/* vim:set foldmethod=marker expandtab: */

View File

@@ -22,6 +22,7 @@
#include "gskpathopprivate.h"
#include "gskpath.h"
#include "gskboundingboxprivate.h"
G_BEGIN_DECLS
@@ -158,6 +159,10 @@ gboolean gsk_curve_decompose_curve (const GskCurve
float gsk_curve_get_curvature (const GskCurve *curve,
float t,
graphene_point_t *center);
void gsk_curve_get_bounds (const GskCurve *curve,
GskBoundingBox *bounds);
void gsk_curve_get_tight_bounds (const GskCurve *curve,
GskBoundingBox *bounds);
G_END_DECLS