actor: Add hit testing on ClutterActor

We should have a way to determine whether a point, defined in
actor-relative coordinates, is contained by an actor or not, as well as
determining the innermost child of a ClutterActor that contains the
point.

This requires some minimal matrix calculations to unproject a point
using the modelview and projection matrix tied to an actor, as well as a
new virtual function to allow actors to define non-rectangular shapes
for themselves.
This commit is contained in:
Emmanuele Bassi 2012-02-23 08:08:10 +00:00 committed by Emmanuele Bassi
parent 55c4d7ab67
commit 8c7dc4ea38
11 changed files with 314 additions and 37 deletions

View File

@ -5466,6 +5466,30 @@ clutter_actor_real_destroy (ClutterActor *actor)
g_object_thaw_notify (G_OBJECT (actor));
}
static gboolean
clutter_actor_real_contains_point (ClutterActor *self,
const ClutterPoint *point)
{
ClutterActorPrivate *priv = self->priv;
ClutterRect bounds;
CLUTTER_NOTE (HIT_TEST, "Point { x:%.2f, y:%.2f } -> '%s' { %.2f, %.2f, %.2f, %.2f }",
_clutter_actor_get_debug_name (self),
point->x, point->y,
priv->allocation.x1,
priv->allocation.y1,
priv->allocation.x2,
priv->allocation.y2);
clutter_rect_init (&bounds,
0.f,
0.f,
priv->allocation.x2 - priv->allocation.x1,
priv->allocation.y2 - priv->allocation.y1);
return clutter_rect_contains_point (&bounds, point);
}
static GObject *
clutter_actor_constructor (GType gtype,
guint n_props,
@ -5527,6 +5551,7 @@ clutter_actor_class_init (ClutterActorClass *klass)
klass->has_overlaps = clutter_actor_real_has_overlaps;
klass->paint = clutter_actor_real_paint;
klass->destroy = clutter_actor_real_destroy;
klass->contains_point = clutter_actor_real_contains_point;
g_type_class_add_private (klass, sizeof (ClutterActorPrivate));
@ -18536,3 +18561,145 @@ clutter_actor_needs_expand (ClutterActor *self,
return FALSE;
}
/**
* clutter_actor_contains_point:
* @self: a #ClutterActor
* @point: a #ClutterPoint, in actor-relative coordinates
*
* Calls #ClutterActorClass.contains_point() to check if @self contains
* the given @point.
*
* Return value: %TRUE if the #ClutterActor contains the point
*
* Since: 1.12
*/
gboolean
clutter_actor_contains_point (ClutterActor *self,
const ClutterPoint *point)
{
g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE);
g_return_val_if_fail (point != NULL, FALSE);
return CLUTTER_ACTOR_GET_CLASS (self)->contains_point (self, point);
}
static gboolean
actor_is_transformed (ClutterActor *actor)
{
const ClutterTransformInfo *info;
info = _clutter_actor_get_transform_info_or_defaults (actor);
if (info->depth != 0.f)
return TRUE;
if (info->rx_angle || info->ry_angle || info->rz_angle)
return TRUE;
if (info->scale_x != 1.f || info->scale_y != 1.f)
return TRUE;
if (!clutter_anchor_coord_is_zero (&info->anchor))
return TRUE;
return FALSE;
}
/**
* clutter_actor_hit_test:
* @self: a #ClutterActor
* @point: a #ClutterPoint, in actor-relative coordinates
*
* Finds the innermost #ClutterActor that contains the given @point.
*
* This function will recurse through the actor tree starting from @self
* and will implicitly call #ClutterActorClass.contains_point() to check
* if an actor contains the given point.
*
* Return value: (transfer none): a #ClutterActor, or %NULL
*
* Since: 1.12
*/
ClutterActor *
clutter_actor_hit_test (ClutterActor *self,
const ClutterPoint *point)
{
ClutterActor *retval, *child;
ClutterActorIter iter;
const CoglMatrix *projection;
float viewport[4];
ClutterStage *stage;
g_return_val_if_fail (CLUTTER_IS_ACTOR (self), NULL);
g_return_val_if_fail (point != NULL, NULL);
if (!clutter_actor_contains_point (self, point))
{
CLUTTER_NOTE (HIT_TEST,
"The actor '%s' does not contain { x:%.2f, y:%.2f }",
_clutter_actor_get_debug_name (self),
point->x, point->y);
return NULL;
}
stage = (ClutterStage *) _clutter_actor_get_stage_internal (self);
if (stage == NULL)
return NULL;
/* XXX - need peek() versions of this */
projection = _clutter_stage_peek_projection_matrix (stage);
/* the viewport is defined to be the allocation of the actor */
viewport[0] = 0.f;
viewport[1] = 0.f;
viewport[2] = self->priv->allocation.x2 - self->priv->allocation.x1;
viewport[3] = self->priv->allocation.y2 - self->priv->allocation.y1;
clutter_actor_iter_init (&iter, self);
while (clutter_actor_iter_next (&iter, &child))
{
ClutterPoint child_point;
if (!actor_is_transformed (child))
{
/* fast path: just offset the coordinates */
child_point.x = point->x - child->priv->allocation.x1;
child_point.y = point->y - child->priv->allocation.y1;
}
else
{
ClutterVertex out_vertex = CLUTTER_VERTEX_INIT (0.f, 0.f, 0.f);
ClutterVertex in_vertex = CLUTTER_VERTEX_INIT (point->x, point->y, 0.f);
/* not so fast path: unproject the point */
if (!_clutter_util_unproject (&in_vertex,
&child->priv->transform,
projection,
viewport,
&out_vertex))
{
CLUTTER_NOTE (HIT_TEST, "Skipping '%s': unproject failed",
_clutter_actor_get_debug_name (child));
continue;
}
child_point.x = out_vertex.x - child->priv->allocation.x1;
child_point.y = out_vertex.y - child->priv->allocation.y1;
}
retval = clutter_actor_hit_test (child, &child_point);
if (retval != NULL)
{
CLUTTER_NOTE (HIT_TEST,
"Hit test success: '%s'",
_clutter_actor_get_debug_name (retval));
return retval;
}
}
CLUTTER_NOTE (HIT_TEST,
"Hit test success: '%s'",
_clutter_actor_get_debug_name (self));
return self;
}

View File

@ -262,10 +262,12 @@ struct _ClutterActorClass
void (* paint_node) (ClutterActor *self,
ClutterPaintNode *root);
gboolean (* contains_point) (ClutterActor *self,
const ClutterPoint *point);
/*< private >*/
/* padding for future expansion */
gpointer _padding_dummy[27];
gpointer _padding_dummy[26];
};
/**
@ -527,6 +529,10 @@ gboolean clutter_actor_event
ClutterEvent *event,
gboolean capture);
gboolean clutter_actor_has_pointer (ClutterActor *self);
gboolean clutter_actor_contains_point (ClutterActor *self,
const ClutterPoint *point);
ClutterActor * clutter_actor_hit_test (ClutterActor *self,
const ClutterPoint *point);
/* Text */
PangoContext * clutter_actor_get_pango_context (ClutterActor *self);

View File

@ -895,8 +895,8 @@ clutter_rect_get_center (ClutterRect *rect,
* Since: 1.12
*/
gboolean
clutter_rect_contains_point (ClutterRect *rect,
ClutterPoint *point)
clutter_rect_contains_point (ClutterRect *rect,
const ClutterPoint *point)
{
g_return_val_if_fail (rect != NULL, FALSE);
g_return_val_if_fail (point != NULL, FALSE);

View File

@ -23,7 +23,8 @@ typedef enum {
CLUTTER_DEBUG_PICK = 1 << 13,
CLUTTER_DEBUG_EVENTLOOP = 1 << 14,
CLUTTER_DEBUG_CLIPPING = 1 << 15,
CLUTTER_DEBUG_OOB_TRANSFORMS = 1 << 16
CLUTTER_DEBUG_OOB_TRANSFORMS = 1 << 16,
CLUTTER_DEBUG_HIT_TEST = 1 << 17
} ClutterDebugFlag;
typedef enum {

View File

@ -192,6 +192,7 @@ static const GDebugKey clutter_debug_keys[] = {
{ "layout", CLUTTER_DEBUG_LAYOUT },
{ "clipping", CLUTTER_DEBUG_CLIPPING },
{ "oob-transforms", CLUTTER_DEBUG_OOB_TRANSFORMS },
{ "hit-test", CLUTTER_DEBUG_HIT_TEST },
};
#endif /* CLUTTER_ENABLE_DEBUG */

View File

@ -253,6 +253,12 @@ void _clutter_util_rectangle_union (const cairo_rectangle_int_t *src1,
const cairo_rectangle_int_t *src2,
cairo_rectangle_int_t *dest);
gboolean _clutter_util_unproject (const ClutterVertex *in_vertex,
const CoglMatrix *modelview,
const CoglMatrix *projection,
const float *viewport,
ClutterVertex *out_vertex);
typedef struct _ClutterPlane
{
float v0[3];

View File

@ -44,6 +44,7 @@ void _clutter_stage_set_window (ClutterStage
ClutterStageWindow *_clutter_stage_get_window (ClutterStage *stage);
void _clutter_stage_get_projection_matrix (ClutterStage *stage,
CoglMatrix *projection);
const CoglMatrix * _clutter_stage_peek_projection_matrix (ClutterStage *stage);
void _clutter_stage_dirty_projection (ClutterStage *stage);
void _clutter_stage_set_viewport (ClutterStage *stage,
float x,

View File

@ -2471,6 +2471,12 @@ _clutter_stage_get_projection_matrix (ClutterStage *stage,
*projection = stage->priv->projection;
}
const CoglMatrix *
_clutter_stage_peek_projection_matrix (ClutterStage *stage)
{
return &stage->priv->projection;
}
/* This simply provides a simple mechanism for us to ensure that
* the projection matrix gets re-asserted before painting.
*

View File

@ -297,56 +297,56 @@ GType clutter_rect_get_type (void) G_GNUC_CONST;
CLUTTER_AVAILABLE_IN_1_12
ClutterRect * clutter_rect_alloc (void);
CLUTTER_AVAILABLE_IN_1_12
ClutterRect * clutter_rect_init (ClutterRect *rect,
float x,
float y,
float width,
float height);
ClutterRect * clutter_rect_init (ClutterRect *rect,
float x,
float y,
float width,
float height);
CLUTTER_AVAILABLE_IN_1_12
ClutterRect * clutter_rect_copy (const ClutterRect *rect);
ClutterRect * clutter_rect_copy (const ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
void clutter_rect_free (ClutterRect *rect);
void clutter_rect_free (ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
gboolean clutter_rect_equals (ClutterRect *a,
ClutterRect *b);
gboolean clutter_rect_equals (ClutterRect *a,
ClutterRect *b);
CLUTTER_AVAILABLE_IN_1_12
ClutterRect * clutter_rect_normalize (ClutterRect *rect);
ClutterRect * clutter_rect_normalize (ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
void clutter_rect_get_center (ClutterRect *rect,
ClutterPoint *center);
void clutter_rect_get_center (ClutterRect *rect,
ClutterPoint *center);
CLUTTER_AVAILABLE_IN_1_12
gboolean clutter_rect_contains_point (ClutterRect *rect,
ClutterPoint *point);
gboolean clutter_rect_contains_point (ClutterRect *rect,
const ClutterPoint *point);
CLUTTER_AVAILABLE_IN_1_12
gboolean clutter_rect_contains_rect (ClutterRect *a,
ClutterRect *b);
gboolean clutter_rect_contains_rect (ClutterRect *a,
ClutterRect *b);
CLUTTER_AVAILABLE_IN_1_12
void clutter_rect_union (ClutterRect *a,
ClutterRect *b,
ClutterRect *res);
void clutter_rect_union (ClutterRect *a,
ClutterRect *b,
ClutterRect *res);
CLUTTER_AVAILABLE_IN_1_12
gboolean clutter_rect_intersection (ClutterRect *a,
ClutterRect *b,
ClutterRect *res);
gboolean clutter_rect_intersection (ClutterRect *a,
ClutterRect *b,
ClutterRect *res);
CLUTTER_AVAILABLE_IN_1_12
void clutter_rect_offset (ClutterRect *rect,
float d_x,
float d_y);
void clutter_rect_offset (ClutterRect *rect,
float d_x,
float d_y);
CLUTTER_AVAILABLE_IN_1_12
void clutter_rect_inset (ClutterRect *rect,
float d_x,
float d_y);
void clutter_rect_inset (ClutterRect *rect,
float d_x,
float d_y);
CLUTTER_AVAILABLE_IN_1_12
void clutter_rect_clamp_to_pixel (ClutterRect *rect);
void clutter_rect_clamp_to_pixel (ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
float clutter_rect_get_x (ClutterRect *rect);
float clutter_rect_get_x (ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
float clutter_rect_get_y (ClutterRect *rect);
float clutter_rect_get_y (ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
float clutter_rect_get_width (ClutterRect *rect);
float clutter_rect_get_width (ClutterRect *rect);
CLUTTER_AVAILABLE_IN_1_12
float clutter_rect_get_height (ClutterRect *rect);
float clutter_rect_get_height (ClutterRect *rect);
/**
* ClutterVertex:

View File

@ -182,6 +182,71 @@ _clutter_util_rectangle_union (const cairo_rectangle_int_t *src1,
dest->y = dest_y;
}
/*< private >
* _clutter_util_unproject:
* @in_vertex: a #ClutterVertex with the point to unproject
* @modelview: the modelview matrix used to unproject
* @projection: the projection matrix used to unproject
* @viewport: the viewport, as an array of floats with the origin in the
* first two elements and the width and height in the last two
* @out_vertex: (out caller-allocates): return location for the unprojected
* vertex
*
* Unprojects @in_vertex according to the provided modelview and projection
* matrices, and relative to the viewport.
*
* Return value: %TRUE if the vertex was successfully unprojected
*/
gboolean
_clutter_util_unproject (const ClutterVertex *in_vertex,
const CoglMatrix *modelview,
const CoglMatrix *projection,
const float *viewport,
ClutterVertex *out_vertex)
{
float in_v[4], out_v[4];
const float *matrix;
CoglMatrix final;
int i;
cogl_matrix_multiply (&final, modelview, projection);
if (!cogl_matrix_get_inverse (&final, &final))
return FALSE;
in_v[0] = in_vertex->x;
in_v[1] = in_vertex->y;
in_v[2] = in_vertex->z;
in_v[3] = 1.0f;
/* normalize in GL <-1, 1> space */
in_v[0] = (in_v[0] - viewport[0]) / viewport[2];
in_v[1] = (in_v[1] - viewport[1]) / viewport[3];
in_v[0] = in_v[0] * 2 - 1;
in_v[1] = in_v[1] * 2 - 1;
in_v[2] = in_v[2] * 2 - 1;
/* multiply the matrix with the vector */
matrix = cogl_matrix_get_array (&final);
for (i = 0; i < 4; i++)
{
out_v[i] = in_v[0] * matrix[0 * 4 + i]
+ in_v[1] * matrix[1 * 4 + i]
+ in_v[2] * matrix[2 * 4 + i]
+ in_v[3] * matrix[3 * 4 + i];
}
if (out_v[3] == 0.f)
return FALSE;
/* denormalize using the viewport */
out_vertex->x = MTX_GL_SCALE_X (out_v[0], out_v[3], viewport[2], viewport[0]);
out_vertex->y = MTX_GL_SCALE_Y (out_v[1], out_v[3], viewport[3], viewport[1]);
out_vertex->z = MTX_GL_SCALE_Z (out_v[2], out_v[3], viewport[2], viewport[0]);
return TRUE;
}
typedef struct
{
GType value_type;

View File

@ -83,6 +83,28 @@ animate_rotation (ClutterActor *actor,
return CLUTTER_EVENT_STOP;
}
static gboolean
on_capture (ClutterActor *stage,
ClutterEvent *event)
{
ClutterActor *hit;
ClutterPoint point = { 0, };
if (clutter_event_type (event) != CLUTTER_MOTION)
return CLUTTER_EVENT_PROPAGATE;
clutter_event_get_position (event, &point);
hit = clutter_actor_hit_test (stage, &point);
if (hit != NULL)
g_print ("HIT TEST: %s (at x:%.2f, y:%.2f)\n",
clutter_actor_get_name (hit),
point.x,
point.y);
return CLUTTER_EVENT_PROPAGATE;
}
int
main (int argc, char *argv[])
{
@ -93,9 +115,11 @@ main (int argc, char *argv[])
return EXIT_FAILURE;
stage = clutter_stage_new ();
g_signal_connect (stage, "captured-event", G_CALLBACK (on_capture), NULL);
g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);
clutter_stage_set_title (CLUTTER_STAGE (stage), "Three Flowers in a Vase");
clutter_stage_set_user_resizable (CLUTTER_STAGE (stage), TRUE);
clutter_actor_set_name (stage, "Stage");
/* there are three flowers in a vase */
vase = clutter_actor_new ();