From e9d9715dcfa1f186c81f34ac785cd35c5d456fff Mon Sep 17 00:00:00 2001 From: Tomas Frydrych Date: Wed, 5 Dec 2007 14:54:15 +0000 Subject: [PATCH] 2007-12-05 Tomas Frydrych * clutter/clutter-actor.c: * clutter/clutter-actor.h: * tests/Makefile.am: * tests/test-unproject: (clutter_actor_transform_stage_point): Added new function for translating stage coordinates into local actor coordinates. --- ChangeLog | 12 ++- clutter/clutter-actor.c | 199 ++++++++++++++++++++++++++++++++++++++-- clutter/clutter-actor.h | 6 ++ tests/Makefile.am | 10 +- tests/test-unproject.c | 147 +++++++++++++++++++++++++++++ 5 files changed, 362 insertions(+), 12 deletions(-) create mode 100644 tests/test-unproject.c diff --git a/ChangeLog b/ChangeLog index 79515df9a..f9716ccd2 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,13 @@ +2007-12-05 Tomas Frydrych + + * clutter/clutter-actor.c: + * clutter/clutter-actor.h: + * tests/Makefile.am: + * tests/test-unproject: + (clutter_actor_transform_stage_point): + Added new function for translating stage coordinates into local + actor coordinates. + 2007-12-04 Emmanuele Bassi * clutter/clutter-score.c: Better document ClutterScore @@ -39,7 +49,7 @@ (clutter_stage_glx_unrealize): unbind all shaders on stage unrealize. * clutter/Makefile.am: added clutter-shader.[ch] - * clutter/clutter-actor.[ch]: adding shader capability to + * clutter/clutter-actor.[ch]: adding shader capability to actors. * clutter/clutter-feature.h: added CLUTTER_FEATURE_SHADERS_GLSL * clutter/clutter-private.h: added stack of shaders to context. diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c index 8e48d651e..5488a5b58 100644 --- a/clutter/clutter-actor.c +++ b/clutter/clutter-actor.c @@ -928,8 +928,6 @@ clutter_actor_paint (ClutterActor *self) cogl_pop_matrix(); } -#undef M - static void clutter_actor_real_request_coords (ClutterActor *self, ClutterActorBox *box) @@ -3870,7 +3868,7 @@ parse_units (ClutterActor *self, else if (G_VALUE_HOLDS (&value, G_TYPE_DOUBLE)) { gint val; - + if (CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IS_TOPLEVEL) { g_warning ("Unable to set percentage of %s on a top-level " @@ -3948,6 +3946,191 @@ clutter_scriptable_iface_init (ClutterScriptableIface *iface) iface->parse_custom_node = clutter_actor_parse_custom_node; } +/** + * clutter_actor_transform_stage_point + * @self: A #ClutterActor + * @x: x screen coordiance of point to unproject, in #ClutterUnit + * @y: y screen coordiance of point to unproject, in #ClutterUnit + * @x: x_out location where to store the unprojected x coordinance, in + * #ClutterUnit. + * @y: y_out location where to store the unprojected y coordinance, in + * #ClutterUnit. + * + * Return value: TRUE if conversion was successful. + * + * The function translates point with screen coordinates x,y to coordinates + * relative to the actor, i.e., it can be used, to translate screen events + * from global screen coordinates into local coordinates. + * + * The conversion can fail, notably if the transform stack results in the + * actor being projected on the screen as a mere line. + * + * The conversion should not be expected to be pixel-perfect due to the nature + * of the operation. In general the error grows when the skewing of the actor + * rectangle on screen increases. + * + * WARNING: This function is fairly computationally intensive. + * + * Since: 0.6 + */ +gboolean +clutter_actor_transform_stage_point (ClutterActor *self, + ClutterUnit x, + ClutterUnit y, + ClutterUnit *x_out, + ClutterUnit *y_out) +{ + ClutterVertex v[4]; + ClutterFixed ST[3][3]; + ClutterFixed RQ[3][3]; + int du, dv, xi, yi; + ClutterFixed xf, yf, wf, px, py, det; + ClutterActorPrivate *priv; + + g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE); + + priv = self->priv; + + /* + * This implementation is based on the quad -> quad projection algorithm + * described by Paul Heckbert in + * + * http://www.cs.cmu.edu/~ph/texfund/texfund.pdf + * + * and the sample implementaion at http://www.cs.cmu.edu/~ph/src/texfund/. + * + * Our texture is a rectangle with origin [0,0], so we are mapping from quad + * to rectangle only, which significantly simplifies things; the function + * calls have been unrolled, and most of the math is done in fixed point. + */ + + clutter_actor_get_vertices (self, v); + + /* + * Keeping these as ints simplifies the multiplication (no significant loss + * of precission here). + */ + du = CLUTTER_UNITS_TO_DEVICE (priv->coords.x2 - priv->coords.x1); + dv = CLUTTER_UNITS_TO_DEVICE (priv->coords.y2 - priv->coords.y1); + + if (!du || !dv) + return FALSE; + +#define FP2FX CLUTTER_FLOAT_TO_FIXED +#define FX2FP CLUTTER_FIXED_TO_DOUBLE +#define FP2INT CLUTTER_FLOAT_TO_INT +#define DET2X(a,b, c,d) (CFX_QMUL(a,d) - CFX_QMUL(b,c)) + + /* + * First, find mapping from unit uv square to xy quadrilateral; this + * equivalent to the pmap_square_quad() functions in the sample + * implementation, which we can simplify, since our target is always + * a rectangle. + */ + px = v[0].x - v[1].x + v[3].x - v[2].x; + py = v[0].y - v[1].y + v[3].y - v[2].y; + + if (!px && !py) + { /* affine transform */ + RQ[0][0] = v[1].x - v[0].x; + RQ[1][0] = v[3].x - v[1].x; + RQ[2][0] = v[0].x; + RQ[0][1] = v[1].y - v[0].y; + RQ[1][1] = v[3].y - v[1].y; + RQ[2][1] = v[0].y; + RQ[0][2] = 0; + RQ[1][2] = 0; + RQ[2][2] = CFX_ONE; + } + else + { /* projective transform */ + ClutterFixed dx1, dx2, dy1, dy2, del; + + dx1 = v[1].x - v[3].x; + dx2 = v[2].x - v[3].x; + dy1 = v[1].y - v[3].y; + dy2 = v[2].y - v[3].y; + + del = DET2X (dx1,dx2, dy1,dy2); + + if (!del) + return FALSE; + + /* + * The division here needs to be done in floating point for + * precisions reasons. + */ + RQ[0][2] = FP2FX (FX2FP (DET2X (px,dx2, py,dy2) / FX2FP (del))); + RQ[1][2] = FP2FX (FX2FP (DET2X (dx1,px, dy1,py) / FX2FP (del))); + RQ[1][2] = CFX_DIV (DET2X(dx1,px, dy1,py), del); + RQ[2][2] = CFX_ONE; + RQ[0][0] = v[1].x - v[0].x + CFX_QMUL (RQ[0][2], v[1].x); + RQ[1][0] = v[2].x - v[0].x + CFX_QMUL (RQ[1][2], v[2].x); + RQ[2][0] = v[0].x; + RQ[0][1] = v[1].y - v[0].y + CFX_QMUL (RQ[0][2], v[1].y); + RQ[1][1] = v[2].y - v[0].y + CFX_QMUL (RQ[1][2], v[2].y); + RQ[2][1] = v[0].y; + } + + /* + * Now combine with transform from our rectangle (u0,v0,u1,v1) to unit + * square. Since our rectangle is based at 0,0 we only need to scale. + */ + RQ[0][0] /= du; + RQ[1][0] /= dv; + RQ[0][1] /= du; + RQ[1][1] /= dv; + RQ[0][2] /= du; + RQ[1][2] /= dv; + + /* + * Now RQ is transform from uv rectangle to xy quadrilateral; we need an + * inverse of that. + */ + ST[0][0] = DET2X(RQ[1][1], RQ[1][2], RQ[2][1], RQ[2][2]); + ST[1][0] = DET2X(RQ[1][2], RQ[1][0], RQ[2][2], RQ[2][0]); + ST[2][0] = DET2X(RQ[1][0], RQ[1][1], RQ[2][0], RQ[2][1]); + ST[0][1] = DET2X(RQ[2][1], RQ[2][2], RQ[0][1], RQ[0][2]); + ST[1][1] = DET2X(RQ[2][2], RQ[2][0], RQ[0][2], RQ[0][0]); + ST[2][1] = DET2X(RQ[2][0], RQ[2][1], RQ[0][0], RQ[0][1]); + ST[0][2] = DET2X(RQ[0][1], RQ[0][2], RQ[1][1], RQ[1][2]); + ST[1][2] = DET2X(RQ[0][2], RQ[0][0], RQ[1][2], RQ[1][0]); + ST[2][2] = DET2X(RQ[0][0], RQ[0][1], RQ[1][0], RQ[1][1]); + + /* + * Check the resutling martix is OK. + */ + det = CFX_QMUL (RQ[0][0], ST[0][0]) + CFX_QMUL (RQ[0][1], ST[0][1]) + + CFX_QMUL (RQ[0][2], ST[0][2]); + + if (!det) + return FALSE; + + /* + * Now transform our point with the ST matrix; the notional w coordiance + * is 1, hence the last part is simply added. + */ + xi = CLUTTER_UNITS_TO_DEVICE (x); + yi = CLUTTER_UNITS_TO_DEVICE (y); + + xf = xi*ST[0][0] + yi*ST[1][0] + ST[2][0]; + yf = xi*ST[0][1] + yi*ST[1][1] + ST[2][1]; + wf = xi*ST[0][2] + yi*ST[1][2] + ST[2][2]; + + /* + * The division needs to be done in floating point for precission reasons. + */ + *x_out = CLUTTER_UNITS_FROM_FLOAT (FX2FP (xf) / FX2FP (wf)); + *y_out = CLUTTER_UNITS_FROM_FLOAT (FX2FP (yf) / FX2FP (wf)); + +#undef FP2FX +#undef FX2FP +#undef FP2INT +#undef DET2X + + return TRUE; +} + /* * ClutterGeometry */ @@ -4097,7 +4280,7 @@ gboolean clutter_actor_apply_shader (ClutterActor *self, ShaderData *shader_data; g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE); - + actor_priv = self->priv; shader_data = actor_priv->shader_data; @@ -4143,7 +4326,7 @@ clutter_actor_shader_pre_paint (ClutterActor *actor, ClutterMainContext *context; g_return_if_fail (CLUTTER_IS_ACTOR (actor)); - + actor_priv = actor->priv; shader_data = actor_priv->shader_data; @@ -4175,7 +4358,7 @@ clutter_actor_shader_post_paint (ClutterActor *actor) ClutterMainContext *context; g_return_if_fail (CLUTTER_IS_ACTOR (actor)); - + actor_priv = actor->priv; shader_data = actor_priv->shader_data; @@ -4211,7 +4394,7 @@ clutter_actor_set_shader_param (ClutterActor *actor, BoxedFloat *box; g_return_if_fail (CLUTTER_IS_ACTOR (actor)); - + actor_priv = actor->priv; shader_data = actor_priv->shader_data; @@ -4222,3 +4405,5 @@ clutter_actor_set_shader_param (ClutterActor *actor, box->value = value; g_hash_table_insert (shader_data->float1f_hash, g_strdup (param), box); } + +#undef M diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h index 96769b04e..cee73b3a2 100644 --- a/clutter/clutter-actor.h +++ b/clutter/clutter-actor.h @@ -415,6 +415,12 @@ void clutter_actor_get_anchor_pointu (ClutterActor *se void clutter_actor_set_anchor_point_from_gravity (ClutterActor *self, ClutterGravity gravity); +gboolean clutter_actor_transform_stage_point (ClutterActor *self, + ClutterUnit x, + ClutterUnit y, + ClutterUnit *x_out, + ClutterUnit *y_out); + G_END_DECLS #endif /* _HAVE_CLUTTER_ACTOR_H */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 8aef6bec8..b43156334 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -2,12 +2,13 @@ noinst_PROGRAMS = test-textures test-events test-offscreen test-scale \ test-actors test-behave test-text test-entry test-project \ test-perspective test-rotate test-depth \ test-threads test-timeline test-score test-script \ - test-model test-grab test-effects test-fullscreen test-shader + test-model test-grab test-effects test-fullscreen \ + test-shader test-unproject INCLUDES = -I$(top_srcdir)/ LDADD = $(top_builddir)/clutter/libclutter-@CLUTTER_FLAVOUR@-@CLUTTER_MAJORMINOR@.la AM_CFLAGS = $(CLUTTER_CFLAGS) -AM_LDFLAGS = $(CLUTTER_LIBS) +AM_LDFLAGS = $(CLUTTER_LIBS) test_textures_SOURCES = test-textures.c test_events_SOURCES = test-events.c @@ -15,10 +16,11 @@ test_offscreen_SOURCES = test-offscreen.c test_scale_SOURCES = test-scale.c test_actors_SOURCES = test-actors.c test_grab_SOURCES = test-grab.c -test_behave_SOURCES = test-behave.c -test_text_SOURCES = test-text.c +test_behave_SOURCES = test-behave.c +test_text_SOURCES = test-text.c test_entry_SOURCES = test-entry.c test_project_SOURCES = test-project.c +test_unproject_SOURCES = test-unproject.c test_perspective_SOURCES = test-perspective.c test_rotate_SOURCES = test-rotate.c test_depth_SOURCES = test-depth.c diff --git a/tests/test-unproject.c b/tests/test-unproject.c new file mode 100644 index 000000000..4c937e28a --- /dev/null +++ b/tests/test-unproject.c @@ -0,0 +1,147 @@ +#include +#include +#include + +ClutterActor *label; + +#define RECT_L 200 +#define RECT_T 150 +#define RECT_W 320 +#define RECT_H 240 + +void +on_event (ClutterStage *stage, + ClutterEvent *event, + gpointer user_data) +{ + static ClutterActor * dragging = NULL; + + switch (event->type) + { + case CLUTTER_BUTTON_PRESS: + { + gint x, y; + ClutterActor * actor; + ClutterUnit xu2, yu2; + + clutter_event_get_coords (event, &x, &y); + + actor = clutter_stage_get_actor_at_pos (stage, x, y); + + + if (clutter_actor_transform_stage_point (actor, + CLUTTER_UNITS_FROM_DEVICE (x), + CLUTTER_UNITS_FROM_DEVICE (y), + &xu2, &yu2)) + { + gchar *txt; + + if (actor != stage) + txt = g_strdup_printf ("Click on rectangle\n" + "Screen coords: [%d, %d]\n" + "Local coords : [%d, %d]", + x, y, + CLUTTER_UNITS_TO_DEVICE (xu2), + CLUTTER_UNITS_TO_DEVICE (yu2)); + else + txt = g_strdup_printf ("Click on stage\n" + "Screen coords: [%d, %d]\n" + "Local coords : [%d, %d]", + x, y, + CLUTTER_UNITS_TO_DEVICE (xu2), + CLUTTER_UNITS_TO_DEVICE (yu2)); + + clutter_label_set_text (CLUTTER_LABEL (label), txt); + g_free (txt); + } + else + clutter_label_set_text (CLUTTER_LABEL (label), + "Unprojection failed."); + } + break; + + default: + break; + } +} + + +int +main (int argc, char *argv[]) +{ + gchar *txt; + ClutterActor *rect, *stage, *label0; + int i, rotate_x = 0, rotate_y = 60, rotate_z = 0; + ClutterColor stage_clr = { 0x0, 0x0, 0x0, 0xff }, + white = { 0xff, 0xff, 0xff, 0xff }, + blue = { 0, 0xff, 0xff, 0xff }; + + for (i = 0; i < argc; ++i) + { + if (!strncmp (argv[i], "--rotate-x", 10)) + { + rotate_x = atoi (argv[i] + 11); + } + else if (!strncmp (argv[i], "--rotate-y", 10)) + { + rotate_y = atoi (argv[i] + 11); + } + else if (!strncmp (argv[i], "--rotate-z", 10)) + { + rotate_z = atoi (argv[i] + 11); + } + else if (!strncmp (argv[i], "--help", 6)) + { + printf ("%s [--rotage-x=degrees] [--rotage-y=degrees] " + "[--rotage-z=degrees]\n", + argv[0]); + + exit (0); + } + } + + clutter_init (&argc, &argv); + + stage = clutter_stage_get_default (); + + clutter_stage_set_color (CLUTTER_STAGE (stage), &stage_clr); + clutter_actor_set_size (stage, 640, 480); + + rect = clutter_rectangle_new_with_color (&white); + clutter_actor_set_size (rect, RECT_W, RECT_H); + clutter_actor_set_position (rect, RECT_L, RECT_T); + clutter_actor_set_rotation (rect, CLUTTER_X_AXIS, rotate_x, 0, 0, 0); + clutter_actor_set_rotation (rect, CLUTTER_Y_AXIS, rotate_y, 0, 0, 0); + clutter_actor_set_rotation (rect, CLUTTER_Z_AXIS, rotate_z, 0, 0, 0); + clutter_group_add (CLUTTER_GROUP (stage), rect); + + txt = g_strdup_printf ("Rectangle: L %d, R %d, T %d, B %d\n" + "Rotation : x %d, y %d, z %d", + RECT_L, RECT_L + RECT_W, + RECT_T, RECT_T + RECT_H, + rotate_x, rotate_y, rotate_z); + + label0 = clutter_label_new_with_text ("Mono 8pt", txt); + clutter_label_set_color (CLUTTER_LABEL (label0), &white); + + clutter_actor_set_position (label0, 10, 10); + clutter_group_add (CLUTTER_GROUP (stage), label0); + + g_free (txt); + + label = + clutter_label_new_with_text ("Mono 8pt", "Click around!"); + + clutter_label_set_color (CLUTTER_LABEL (label), &blue); + + clutter_actor_set_position (label, 10, 50); + clutter_group_add (CLUTTER_GROUP (stage), label); + + clutter_actor_show_all (stage); + + g_signal_connect (stage, "event", G_CALLBACK (on_event), NULL); + + clutter_main(); + + return EXIT_SUCCESS; +}