Files
gtk/gsk/gskconvexity.c
Matthias Clasen def04f5df3 path: Add gsk_path_is_convex
Add a function to compute whether a path is convex.
2022-04-08 16:44:29 -04:00

287 lines
6.5 KiB
C

#include "config.h"
#include "gskconvexityprivate.h"
#include "gskcontourprivate.h"
typedef enum
{
GSK_DIR_CHANGE_UNKNOWN,
GSK_DIR_CHANGE_LEFT,
GSK_DIR_CHANGE_RIGHT,
GSK_DIR_CHANGE_STRAIGHT,
GSK_DIR_CHANGE_REVERSE,
GSK_DIR_CHANGE_INVALID,
} GskDirChange;
typedef enum
{
GSK_PATH_DIRECTION_UNKNOWN,
GSK_PATH_DIRECTION_CLOCKWISE,
GSK_PATH_DIRECTION_COUNTERCLOCKWISE,
} GskPathDirection;
typedef struct _GskConvexityChecker GskConvexityChecker;
struct _GskConvexityChecker
{
graphene_point_t first_point;
graphene_vec2_t first_vec;
graphene_point_t last_point;
graphene_vec2_t last_vec;
GskDirChange expected_direction;
GskPathDirection first_direction;
int reversals;
gboolean finite;
GskConvexity convexity;
int xsign;
int ysign;
int dx;
int dy;
};
static float
cross_product (graphene_vec2_t *a,
graphene_vec2_t *b)
{
float fa[2];
float fb[2];
graphene_vec2_to_float (a, fa);
graphene_vec2_to_float (b, fb);
return fa[0] * fb[1] - fa[1] * fb[0];
}
static GskDirChange
direction_change (GskConvexityChecker *c,
graphene_vec2_t *v)
{
float cross = cross_product (&(c->last_vec), v);
if (!finite (cross))
return GSK_DIR_CHANGE_UNKNOWN;
if (cross == 0)
return graphene_vec2_dot (&(c->last_vec), v) < 0
? GSK_DIR_CHANGE_REVERSE
: GSK_DIR_CHANGE_STRAIGHT;
return cross > 0
? GSK_DIR_CHANGE_RIGHT
: GSK_DIR_CHANGE_LEFT;
}
static gboolean
add_vec (GskConvexityChecker *c,
graphene_vec2_t *v)
{
GskDirChange dir;
int sign;
dir = direction_change (c, v);
switch (dir)
{
case GSK_DIR_CHANGE_LEFT:
case GSK_DIR_CHANGE_RIGHT:
if (c->expected_direction == GSK_DIR_CHANGE_INVALID)
{
c->expected_direction = dir;
c->first_direction = (dir == GSK_DIR_CHANGE_RIGHT)
? GSK_PATH_DIRECTION_CLOCKWISE
: GSK_PATH_DIRECTION_COUNTERCLOCKWISE;
}
else if (c->expected_direction != dir)
{
c->first_direction = GSK_PATH_DIRECTION_UNKNOWN;
c->convexity = GSK_CONVEXITY_CONCAVE;
return FALSE;
}
graphene_vec2_init_from_vec2 (&c->last_vec, v);
break;
case GSK_DIR_CHANGE_STRAIGHT:
break;
case GSK_DIR_CHANGE_REVERSE:
graphene_vec2_init_from_vec2 (&c->last_vec, v);
c->reversals++;
if (c->reversals > 2)
c->convexity = GSK_CONVEXITY_CONCAVE;
return c->reversals < 3;
case GSK_DIR_CHANGE_UNKNOWN:
c->finite = FALSE;
return FALSE;
case GSK_DIR_CHANGE_INVALID:
default:
g_assert_not_reached ();
}
if (graphene_vec2_get_x (v) > 0)
sign = 1;
else if (graphene_vec2_get_x (v) < 0)
sign = -1;
else
sign = 0;
if (sign != 0)
{
if (c->xsign != 42)
{
if (c->xsign != sign)
c->dx++;
if (c->dx > 2)
{
c->convexity = GSK_CONVEXITY_CONCAVE;
return FALSE;
}
}
c->xsign = sign;
}
if (graphene_vec2_get_y (v) > 0)
sign = 1;
else if (graphene_vec2_get_y (v) < 0)
sign = -1;
else
sign = 0;
if (sign != 0)
{
if (c->ysign != 42)
{
if (c->ysign != sign)
c->dy++;
if (c->dy > 2)
{
c->convexity = GSK_CONVEXITY_CONCAVE;
return FALSE;
}
}
c->ysign = sign;
}
return TRUE;
}
static void
gsk_convexity_checker_init (GskConvexityChecker *c)
{
c->first_point = GRAPHENE_POINT_INIT(0,0);
c->last_point = GRAPHENE_POINT_INIT(0,0);
graphene_vec2_init (&c->first_vec, 0, 0);
graphene_vec2_init (&c->last_vec, 0, 0);
c->expected_direction = GSK_DIR_CHANGE_INVALID;
c->first_direction = GSK_PATH_DIRECTION_UNKNOWN;
c->reversals = 0;
c->finite = TRUE;
c->convexity = GSK_CONVEXITY_UNKNOWN;
c->xsign = 42;
c->ysign = 42;
c->dx = 0;
c->dy = 0;
}
static void
gsk_convexity_checker_move (GskConvexityChecker *c,
const graphene_point_t *p)
{
c->first_point = c->last_point = *p;
c->expected_direction = GSK_DIR_CHANGE_INVALID;
c->convexity = GSK_CONVEXITY_CONVEX;
}
static gboolean
gsk_convexity_checker_add_point (GskConvexityChecker *c,
const graphene_point_t *p)
{
graphene_vec2_t v;
if (graphene_point_equal (&c->last_point, p))
return TRUE;
graphene_vec2_init (&v,
p->x - c->last_point.x,
p->y - c->last_point.y);
if (graphene_point_equal (&c->first_point, &c->last_point) &&
c->expected_direction == GSK_DIR_CHANGE_INVALID)
{
graphene_vec2_init_from_vec2 (&c->last_vec, &v);
graphene_vec2_init_from_vec2 (&c->first_vec, &v);
}
else if (!add_vec (c, &v))
{
c->convexity = GSK_CONVEXITY_CONCAVE;
return FALSE;
}
c->last_point = *p;
return TRUE;
}
static gboolean
gsk_convexity_checker_close (GskConvexityChecker *c)
{
if (!(gsk_convexity_checker_add_point (c, &c->first_point) &&
add_vec (c, &c->first_vec)))
{
c->convexity = GSK_CONVEXITY_CONCAVE;
return FALSE;
}
return TRUE;
}
static gboolean
convex_cb (GskPathOperation op,
const graphene_point_t *pts,
gsize n_pts,
float weight,
gpointer user_data)
{
GskConvexityChecker *c = user_data;
switch (op)
{
case GSK_PATH_MOVE:
gsk_convexity_checker_move (c, &pts[0]);
break;
case GSK_PATH_CLOSE:
if (!gsk_convexity_checker_close (c))
return FALSE;
break;
case GSK_PATH_LINE:
if (!gsk_convexity_checker_add_point (c, &pts[1]))
return FALSE;
break;
case GSK_PATH_CURVE:
if (!gsk_convexity_checker_add_point (c, &pts[1]) ||
!gsk_convexity_checker_add_point (c, &pts[2]) ||
!gsk_convexity_checker_add_point (c, &pts[3]))
return FALSE;
break;
case GSK_PATH_CONIC:
if (!gsk_convexity_checker_add_point (c, &pts[1]) ||
!gsk_convexity_checker_add_point (c, &pts[3]))
return FALSE;
break;
default:
g_assert_not_reached ();
}
return TRUE;
}
GskConvexity
gsk_contour_compute_convexity (const GskContour *contour)
{
GskConvexityChecker c;
gsk_convexity_checker_init (&c);
gsk_contour_foreach (contour, 0.001, convex_cb, &c);
g_assert (c.convexity != GSK_CONVEXITY_UNKNOWN);
return c.convexity;
}