From 1a63414966c2d128be4ff3890e676fb98afb5474 Mon Sep 17 00:00:00 2001 From: Neil Roberts Date: Fri, 5 Dec 2008 13:13:37 +0000 Subject: [PATCH] Bug 1252 - Merge ClutterBehaviourPath and ClutterBehaviourBspline * clutter/clutter-path.h: * clutter/clutter-path.c: Implementation of new ClutterPath object to represent a path combining straight line and bezier curve elements. * clutter/clutter.h: Include clutter-path.h and remove clutter-behaviour-bspline.h * tests/interactive/test-threads.c (test_threads_main): * tests/interactive/test-script.c: * tests/interactive/test-behave.c (test_behave_main): Use new path API * clutter/clutter-effect.c: Use the new ClutterBehaviourPath API. * clutter/clutter-bezier.h: * clutter/clutter-bezier.c: Moved bezier curve handling code out from clutter-behaviour-bspline.c to a separate file. * clutter/clutter-behaviour-path.h: * clutter/clutter-behaviour-path.c: Reimplemented to work with a ClutterPath * clutter/clutter-behaviour-bspline.h: * clutter/clutter-behaviour-bspline.c: Removed * clutter/Makefile.am: Add clutter-path and clutter-bezier, remove clutter-behaviour-bspline. * tests/conform/test-path.c: New automatic test for ClutterPath consistency * tests/conform/test-conform-main.c (main): Add test_path * tests/conform/Makefile.am (test_conformance_SOURCES): Add test-path.c * clutter/clutter-sections.txt: Add ClutterPath docs * clutter/clutter.types: * clutter/clutter-docs.xml: * doc/reference/clutter/clutter-animation-tutorial.xml: Remove mention of ClutterBehaviourBspline * clutter/clutter-marshal.list: Add VOID:UINT --- .gitignore | 1 + ChangeLog | 50 + clutter/Makefile.am | 6 +- clutter/clutter-behaviour-bspline.c | 1228 -------------- clutter/clutter-behaviour-bspline.h | 131 -- clutter/clutter-behaviour-path.c | 645 +++----- clutter/clutter-behaviour-path.h | 27 +- clutter/clutter-bezier.c | 426 +++++ clutter/clutter-bezier.h | 65 + clutter/clutter-effect.c | 32 +- clutter/clutter-marshal.list | 1 + clutter/clutter-path.c | 1414 +++++++++++++++++ clutter/clutter-path.h | 245 +++ clutter/clutter.h | 2 +- .../clutter/clutter-animation-tutorial.xml | 4 - doc/reference/clutter/clutter-docs.xml | 2 +- doc/reference/clutter/clutter-sections.txt | 76 +- doc/reference/clutter/clutter.types | 2 +- tests/conform/Makefile.am | 1 + tests/conform/test-conform-main.c | 2 + tests/conform/test-path.c | 609 +++++++ tests/interactive/test-behave.c | 59 +- tests/interactive/test-script.c | 2 +- tests/interactive/test-threads.c | 11 +- 24 files changed, 3164 insertions(+), 1877 deletions(-) delete mode 100644 clutter/clutter-behaviour-bspline.c delete mode 100644 clutter/clutter-behaviour-bspline.h create mode 100644 clutter/clutter-bezier.c create mode 100644 clutter/clutter-bezier.h create mode 100644 clutter/clutter-path.c create mode 100644 clutter/clutter-path.h create mode 100644 tests/conform/test-path.c diff --git a/.gitignore b/.gitignore index df7d68b4d..4c42d31c4 100644 --- a/.gitignore +++ b/.gitignore @@ -132,6 +132,7 @@ stamp-h1 /tests/conform/test_initial_state /tests/conform/test_label_cache /tests/conform/test_mapped +/tests/conform/test_path /tests/conform/test_pick /tests/conform/test_realized /tests/conform/test_rect_set_color diff --git a/ChangeLog b/ChangeLog index a2c59f83e..04511087a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,53 @@ +2008-12-05 Neil Roberts + + Bug 1252 - Merge ClutterBehaviourPath and ClutterBehaviourBspline + + * clutter/clutter-path.h: + * clutter/clutter-path.c: Implementation of new ClutterPath object + to represent a path combining straight line and bezier curve + elements. + + * clutter/clutter.h: Include clutter-path.h and remove + clutter-behaviour-bspline.h + + * tests/interactive/test-threads.c (test_threads_main): + * tests/interactive/test-script.c: + * tests/interactive/test-behave.c (test_behave_main): Use new path + API + + * clutter/clutter-effect.c: Use the new ClutterBehaviourPath API. + + * clutter/clutter-bezier.h: + * clutter/clutter-bezier.c: Moved bezier curve handling code out + from clutter-behaviour-bspline.c to a separate file. + + * clutter/clutter-behaviour-path.h: + * clutter/clutter-behaviour-path.c: Reimplemented to work with a + ClutterPath + + * clutter/clutter-behaviour-bspline.h: + * clutter/clutter-behaviour-bspline.c: Removed + + * clutter/Makefile.am: Add clutter-path and clutter-bezier, remove + clutter-behaviour-bspline. + + * tests/conform/test-path.c: New automatic test for ClutterPath + consistency + + * tests/conform/test-conform-main.c (main): Add test_path + + * tests/conform/Makefile.am (test_conformance_SOURCES): Add + test-path.c + + * clutter/clutter-sections.txt: Add ClutterPath docs + + * clutter/clutter.types: + * clutter/clutter-docs.xml: + * doc/reference/clutter/clutter-animation-tutorial.xml: Remove + mention of ClutterBehaviourBspline + + * clutter/clutter-marshal.list: Add VOID:UINT + 2008-12-04 Neil Roberts Bug 1297 - Bring back support for GL_ARB_texture_rectangle diff --git a/clutter/Makefile.am b/clutter/Makefile.am index 6bf2c5caf..ee6c19a35 100644 --- a/clutter/Makefile.am +++ b/clutter/Makefile.am @@ -50,7 +50,6 @@ source_h = \ $(srcdir)/clutter-animation.h \ $(srcdir)/clutter-backend.h \ $(srcdir)/clutter-behaviour.h \ - $(srcdir)/clutter-behaviour-bspline.h \ $(srcdir)/clutter-behaviour-depth.h \ $(srcdir)/clutter-behaviour-ellipse.h \ $(srcdir)/clutter-behaviour-opacity.h \ @@ -76,6 +75,7 @@ source_h = \ $(srcdir)/clutter-main.h \ $(srcdir)/clutter-media.h \ $(srcdir)/clutter-model.h \ + $(srcdir)/clutter-path.h \ $(srcdir)/clutter-rectangle.h \ $(srcdir)/clutter-score.h \ $(srcdir)/clutter-script.h \ @@ -139,13 +139,13 @@ source_c = \ clutter-animation.c \ clutter-backend.c \ clutter-behaviour.c \ - clutter-behaviour-bspline.c \ clutter-behaviour-depth.c \ clutter-behaviour-ellipse.c \ clutter-behaviour-opacity.c \ clutter-behaviour-path.c \ clutter-behaviour-rotate.c \ clutter-behaviour-scale.c \ + clutter-bezier.c \ clutter-child-meta.c \ clutter-clone-texture.c \ clutter-color.c \ @@ -166,6 +166,7 @@ source_c = \ clutter-marshal.c \ clutter-media.c \ clutter-model.c \ + clutter-path.c \ clutter-rectangle.c \ clutter-score.c \ clutter-script.c \ @@ -184,6 +185,7 @@ source_c = \ $(NULL) source_h_priv = \ + clutter-bezier.h \ clutter-debug.h \ clutter-keysyms-table.h \ clutter-model-private.h \ diff --git a/clutter/clutter-behaviour-bspline.c b/clutter/clutter-behaviour-bspline.c deleted file mode 100644 index 53c44d1db..000000000 --- a/clutter/clutter-behaviour-bspline.c +++ /dev/null @@ -1,1228 +0,0 @@ -/* -*- mode:C; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */ -/* - * Clutter. - * - * An OpenGL based 'interactive canvas' library. - * - * Authored By Tomas Frydrych - * - * Copyright (C) 2007 OpenedHand - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the - * Free Software Foundation, Inc., 59 Temple Place - Suite 330, - * Boston, MA 02111-1307, USA. - */ - -/** - * SECTION:clutter-behaviour-bspline - * @short_description: A behaviour interpolating position along a B-Spline - * - * #ClutterBehaviourBspline interpolates actors along a defined B-Spline path. - * - * A bezier spline is a set of cubic bezier curves defined by a sequence of - * control points given when creating a new #ClutterBehaviourBspline instance. - * - * Additional bezier curves can be added to the end of the bspline using - * clutter_behaviour_bspline_append_* family of functions, control points can - * be moved using clutter_behaviour_bspline_adjust(). The bspline can be split - * into two with clutter_behaviour_bspline_split(), and bsplines can be - * concatenated using clutter_behaviour_bspline_join(). - * - * Each time the behaviour reaches a point on the path, the "knot-reached" - * signal is emitted. - * - * Since: 0.4 - */ - -#include "clutter-behaviour-bspline.h" -#include "clutter-debug.h" -#include "clutter-fixed.h" -#include "clutter-marshal.h" -#include "clutter-private.h" -#include "clutter-scriptable.h" -#include "clutter-script-private.h" - -#include -#include - -/* - * We have some experimental code here to allow for constant velocity - * movement of actors along the bezier path, this macro enables it. - */ -#undef CBZ_L2T_INTERPOLATION - -/**************************************************************************** - * ClutterBezier -- represenation of a cubic bezier curve * - * (private; a building block for the public bspline object) * - ****************************************************************************/ - -/* - * The t parameter of the bezier is from interval <0,1>, so we can use - * 14.18 format and special multiplication functions that preserve - * more of the least significant bits but would overflow if the value - * is > 1 - */ -#define CBZ_T_Q 18 -#define CBZ_T_ONE (1 << CBZ_T_Q) -#define CBZ_T_MUL(x,y) ((((x) >> 3) * ((y) >> 3)) >> 12) -#define CBZ_T_POW2(x) CBZ_T_MUL (x, x) -#define CBZ_T_POW3(x) CBZ_T_MUL (CBZ_T_POW2 (x), x) -#define CBZ_T_DIV(x,y) ((((x) << 9)/(y)) << 9) - -/* - * Constants for sampling of the bezier - */ -#define CBZ_T_SAMPLES 128 -#define CBZ_T_STEP (CBZ_T_ONE / CBZ_T_SAMPLES) -#define CBZ_L_STEP (CBZ_T_ONE / CBZ_T_SAMPLES) - -typedef gint32 _FixedT; - -/* - * This is a private type representing a single cubic bezier - */ -typedef struct _ClutterBezier -{ - /* - * bezier coefficients -- these are calculated using multiplication and - * addition from integer input, so these are also integers - */ - gint ax; - gint bx; - gint cx; - gint dx; - - gint ay; - gint by; - gint cy; - gint dy; - - /* length of the bezier */ - guint length; - -#ifdef CBZ_L2T_INTERPOLATION - /* - * coefficients for the L -> t bezier; these are calculated from fixed - * point input, and more specifically numbers that have been normalised - * to fit <0,1>, so these are also fixed point, and we can used the - * _FixedT type here. - */ - _FixedT La; - _FixedT Lb; - _FixedT Lc; - /* _FixedT Ld; == 0 */ -#endif -} ClutterBezier; - -static ClutterBezier * -clutter_bezier_new () -{ - return g_slice_new0 (ClutterBezier); -} - -static void -clutter_bezier_free (ClutterBezier * b) -{ - if (G_LIKELY (b)) - { - g_slice_free (ClutterBezier, b); - } -} - -static ClutterBezier * -clutter_bezier_clone_and_move (ClutterBezier *b, gint x, gint y) -{ - ClutterBezier * b2 = clutter_bezier_new (); - memcpy (b2, b, sizeof (ClutterBezier)); - - b2->dx += x; - b2->dy += y; - - return b2; -} - -#ifdef CBZ_L2T_INTERPOLATION -/* - * L is relative advance along the bezier curve from interval <0,1> - */ -static _FixedT -clutter_bezier_L2t (ClutterBezier *b, _FixedT L) -{ - _FixedT t = CBZ_T_MUL (b->La, CBZ_T_POW3(L)) - + CBZ_T_MUL (b->Lb, CBZ_T_POW2(L)) - + CBZ_T_MUL (b->Lc, L); - - if (t > CBZ_T_ONE) - t = CBZ_T_ONE; - else if (t < 0) - t = 0; - - return t; -} -#endif - -static gint -clutter_bezier_t2x (ClutterBezier * b, _FixedT t) -{ - /* - * NB -- the int coefficients can be at most 8192 for the multiplication - * to work in this fashion due to the limits of the 14.18 fixed. - */ - return ((b->ax*CBZ_T_POW3(t) + b->bx*CBZ_T_POW2(t) + b->cx*t) >> CBZ_T_Q) - + b->dx; -} - -static gint -clutter_bezier_t2y (ClutterBezier * b, _FixedT t) -{ - /* - * NB -- the int coefficients can be at most 8192 for the multiplication - * to work in this fashion due to the limits of the 14.18 fixed. - */ - return ((b->ay*CBZ_T_POW3(t) + b->by*CBZ_T_POW2(t) + b->cy*t) >> CBZ_T_Q) - + b->dy; -} - -/* - * Advances along the bezier to relative length L and returns the coordinances - * in knot - */ -static void -clutter_bezier_advance (ClutterBezier *b, _FixedT L, ClutterKnot * knot) -{ -#ifdef CBZ_L2T_INTERPOLATION - _FixedT t = clutter_bezier_L2t (b, L); -#else - _FixedT t = L; -#endif - - knot->x = clutter_bezier_t2x (b, t); - knot->y = clutter_bezier_t2y (b, t); - - CLUTTER_NOTE (BEHAVIOUR, "advancing to relative pt %f: t %f, {%d,%d}", - (double) L / (double) CBZ_T_ONE, - (double) t / (double) CBZ_T_ONE, - knot->x, knot->y); -} - -static void -clutter_bezier_init (ClutterBezier *b, - gint x_0, gint y_0, - gint x_1, gint y_1, - gint x_2, gint y_2, - gint x_3, gint y_3) -{ - _FixedT t; - int i; - int xp = x_0; - int yp = y_0; - _FixedT length [CBZ_T_SAMPLES + 1]; - -#ifdef CBZ_L2T_INTERPOLATION - int j, k; - _FixedT L; - _FixedT t_equalized [CBZ_T_SAMPLES + 1]; -#endif - -#if 0 - g_debug ("Initializing bezier at {{%d,%d},{%d,%d},{%d,%d},{%d,%d}}", - x0, y0, x1, y1, x2, y2, x3, y3); -#endif - - b->dx = x_0; - b->dy = y_0; - - b->cx = 3 * (x_1 - x_0); - b->cy = 3 * (y_1 - y_0); - - b->bx = 3 * (x_2 - x_1) - b->cx; - b->by = 3 * (y_2 - y_1) - b->cy; - - b->ax = x_3 - 3 * x_2 + 3 * x_1 - x_0; - b->ay = y_3 - 3 * y_2 + 3 * y_1 - y_0; - -#if 0 - g_debug ("Cooeficients {{%d,%d},{%d,%d},{%d,%d},{%d,%d}}", - b->ax, b->ay, b->bx, b->by, b->cx, b->cy, b->dx, b->dy); -#endif - - /* - * Because of the way we do the multiplication in bezeir_t2x,y - * these coefficients need to be at most 0x1fff; this should be the case, - * I think, but have added this warning to catch any problems -- if it - * triggers, we need to change those two functions a bit. - */ - if (b->ax > 0x1fff || b->bx > 0x1fff || b->cx > 0x1fff) - g_warning ("Calculated coefficents will result in multiplication " - "overflow in clutter_bezier_t2x and clutter_bezier_t2y."); - - /* - * Sample the bezier with CBZ_T_SAMPLES and calculate length at - * each point. - * - * We are working with integers here, so we use the fast sqrti function. - */ - length[0] = 0; - - for (t = CBZ_T_STEP, i = 1; i <= CBZ_T_SAMPLES; ++i, t += CBZ_T_STEP) - { - int x = clutter_bezier_t2x (b, t); - int y = clutter_bezier_t2y (b, t); - - guint l = clutter_sqrti ((y - yp)*(y - yp) + (x - xp)*(x - xp)); - - l += length[i-1]; - - length[i] = l; - - xp = x; - yp = y; - } - - b->length = length[CBZ_T_SAMPLES]; - -#if 0 - g_debug ("length %d", b->length); -#endif - -#ifdef CBZ_L2T_INTERPOLATION - /* - * Now normalize the length values, converting them into _FixedT - */ - for (i = 0; i <= CBZ_T_SAMPLES; ++i) - { - length[i] = (length[i] << CBZ_T_Q) / b->length; - } - - /* - * Now generate a L -> t table such that the L will equidistant - * over <0,1> - */ - t_equalized[0] = 0; - - for (i = 1, j = 1, L = CBZ_L_STEP; i < CBZ_T_SAMPLES; ++i, L += CBZ_L_STEP) - { - _FixedT l1, l2; - _FixedT d1, d2, d; - _FixedT t1, t2; - - /* find the band for our L */ - for (k = j; k < CBZ_T_SAMPLES; ++k) - { - if (L < length[k]) - break; - } - - /* - * Now we know that L is from (length[k-1],length[k]> - * We remember k-1 in order not to have to iterate over the - * whole length array in the next iteration of the main loop - */ - j = k - 1; - - /* - * Now interpolate equlised t as a weighted average - */ - l1 = length[k-1]; - l2 = length[k]; - d1 = l2 - L; - d2 = L - l1; - d = l2 - l1; - t1 = (k - 1) * CBZ_T_STEP; - t2 = k * CBZ_T_STEP; - - t_equalized[i] = (t1*d1 + t2*d2)/d; - - if (t_equalized[i] < t_equalized[i-1]) - g_debug ("wrong t: L %f, l1 %f, l2 %f, t1 %f, t2 %f", - (double) (L)/(double)CBZ_T_ONE, - (double) (l1)/(double)CBZ_T_ONE, - (double) (l2)/(double)CBZ_T_ONE, - (double) (t1)/(double)CBZ_T_ONE, - (double) (t2)/(double)CBZ_T_ONE); - - } - - t_equalized[CBZ_T_SAMPLES] = CBZ_T_ONE; - - /* We now fit a bezier -- at this stage, do a single fit through our values - * at 0, 1/3, 2/3 and 1 - * - * FIXME -- do we need to use a better fitting approach to choose the best - * beziere. The actual curve we acquire this way is not too bad shapwise, - * but (probably due to rounding errors) the resulting curve no longer - * satisfies the necessary condition that for L2 > L1, t2 > t1, which - * causes oscilation. - */ - -#if 0 - /* - * These are the control points we use to calculate the curve coefficients - * for bezier t(L); these are not needed directly, but are implied in the - * calculations below. - * - * (p0 is 0,0, and p3 is 1,1) - */ - p1 = (18 * t_equalized[CBZ_T_SAMPLES/3] - - 9 * t_equalized[2*CBZ_T_SAMPLES/3] + - 2 << CBZ_T_Q) / 6; - - p2 = (18 * t_equalized[2*CBZ_T_SAMPLES/3] - - 9 * t_equalized[CBZ_T_SAMPLES/3] - - (5 << CBZ_T_Q)) / 6; -#endif - - b->Lc = (18 * t_equalized[CBZ_T_SAMPLES/3] - - 9 * t_equalized[2*CBZ_T_SAMPLES/3] + - (2 << CBZ_T_Q)) >> 1; - - b->Lb = (36 * t_equalized[2*CBZ_T_SAMPLES/3] - - 45 * t_equalized[CBZ_T_SAMPLES/3] - - (9 << CBZ_T_Q)) >> 1; - - b->La = ((27 * (t_equalized[CBZ_T_SAMPLES/3] - - t_equalized[2*CBZ_T_SAMPLES/3]) + - (7 << CBZ_T_Q)) >> 1) + CBZ_T_ONE; - - g_debug ("t(1/3) %f, t(2/3) %f", - (double)t_equalized[CBZ_T_SAMPLES/3]/(double)CBZ_T_ONE, - (double)t_equalized[2*CBZ_T_SAMPLES/3]/(double)CBZ_T_ONE); - - g_debug ("L -> t coefficients: %f, %f, %f", - (double)b->La/(double)CBZ_T_ONE, - (double)b->Lb/(double)CBZ_T_ONE, - (double)b->Lc/(double)CBZ_T_ONE); - - - /* - * For debugging, you can load these values into a spreadsheet and graph - * them to see how well the approximation matches the data - */ - for (i = 0; i < CBZ_T_SAMPLES; ++i) - { - g_print ("%f, %f, %f\n", - (double)(i*CBZ_T_STEP)/(double)CBZ_T_ONE, - (double)(t_equalized[i])/(double)CBZ_T_ONE, - (double)(clutter_bezier_L2t(b,i*CBZ_T_STEP))/(double)CBZ_T_ONE); - } -#endif -} - -/* - * Moves a control point at indx to location represented by knot - */ -static void -clutter_bezier_adjust (ClutterBezier * b, ClutterKnot * knot, guint indx) -{ - guint x[4], y[4]; - - g_assert (indx < 4); - - x[0] = b->dx; - y[0] = b->dy; - - x[1] = b->cx / 3 + x[0]; - y[1] = b->cy / 3 + y[0]; - - x[2] = b->bx / 3 + b->cx + x[1]; - y[2] = b->by / 3 + b->cy + y[1]; - - x[3] = b->ax + x[0] + b->cx + b->bx; - y[3] = b->ay + y[0] + b->cy + b->by; - - x[indx] = knot->x; - y[indx] = knot->y; - - clutter_bezier_init (b, x[0], y[0], x[1], y[1], x[2], y[2], x[3], y[3]); -} - - -/**************************************************************************** - * * - * ClutterBehaviourBspline * - * * - ****************************************************************************/ - -static void clutter_scriptable_iface_init (ClutterScriptableIface *iface); - -G_DEFINE_TYPE_WITH_CODE (ClutterBehaviourBspline, - clutter_behaviour_bspline, - CLUTTER_TYPE_BEHAVIOUR, - G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE, - clutter_scriptable_iface_init)); - -#define CLUTTER_BEHAVIOUR_BSPLINE_GET_PRIVATE(obj) \ - (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \ - CLUTTER_TYPE_BEHAVIOUR_BSPLINE, \ - ClutterBehaviourBsplinePrivate)) - -enum - { - KNOT_REACHED, - - LAST_SIGNAL - }; - -static guint bspline_signals[LAST_SIGNAL] = { 0, }; - -struct _ClutterBehaviourBsplinePrivate -{ - /* - * The individual bezier curves that make up this bspline - */ - GArray * splines; - - /* - * The length of the bspline - */ - guint length; - - /* - * Bspline offsets (these allow us to move the bspline without having to - * mess about with the individual beziers). - * - * NB: this is not the actual origin, but an adjustment to the origin of - * the first bezier; it defaults to 0 unless the user explicitely changes - * the bspline offset. - */ - gint x; - gint y; - - /* - * A temporary stack of control points used by the append methods - */ - GArray * point_stack; -}; - -static void -clutter_behaviour_bspline_finalize (GObject *object) -{ - ClutterBehaviourBspline *self = CLUTTER_BEHAVIOUR_BSPLINE (object); - ClutterBehaviourBsplinePrivate *priv = self->priv; - gint i; - - for (i = 0; i < priv->splines->len; ++i) - clutter_bezier_free (g_array_index (priv->splines, ClutterBezier*, i)); - - g_array_free (priv->splines, TRUE); - - for (i = 0; i < priv->point_stack->len; ++i) - clutter_knot_free (g_array_index (priv->point_stack, ClutterKnot*, i)); - - g_array_free (priv->point_stack, TRUE); - - G_OBJECT_CLASS (clutter_behaviour_bspline_parent_class)->finalize (object); -} - -static void -actor_apply_knot_foreach (ClutterBehaviour *behaviour, - ClutterActor *actor, - gpointer data) -{ - ClutterKnot *knot = data; - - clutter_actor_set_position (actor, knot->x, knot->y); -} - -/* - * Advances to a point that is at distance 'to' along the spline; - * - * returns FALSE if the length is beyond the end of the bspline. - */ -static gboolean -clutter_behaviour_bspline_advance (ClutterBehaviourBspline *bs, - guint to) -{ - ClutterBehaviourBsplinePrivate *priv = bs->priv; - gint i; - guint length = 0; - ClutterKnot knot; - - if (to > priv->length) - return FALSE; - - for (i = 0; i < priv->splines->len; ++i) - { - ClutterBezier *b = g_array_index (priv->splines, ClutterBezier*, i); - - if (length + b->length >= to) - { - _FixedT L = ((to - length) << CBZ_T_Q) / b->length; - - clutter_bezier_advance (b, L, &knot); - - knot.x += bs->priv->x; - knot.y += bs->priv->y; - - CLUTTER_NOTE (BEHAVIOUR, "advancing to length %d: (%d, %d)", - to, knot.x, knot.y); - - clutter_behaviour_actors_foreach (CLUTTER_BEHAVIOUR (bs), - actor_apply_knot_foreach, - &knot); - - g_signal_emit (bs, bspline_signals[KNOT_REACHED], 0, &knot); - - return TRUE; - } - - length += b->length; - } - - /* should not be reached */ - return FALSE; -} - -static void -clutter_behaviour_bspline_alpha_notify (ClutterBehaviour * behave, - guint32 alpha) -{ - ClutterBehaviourBspline * bs = CLUTTER_BEHAVIOUR_BSPLINE (behave); - gint to = (alpha * bs->priv->length) / CLUTTER_ALPHA_MAX_ALPHA; - - clutter_behaviour_bspline_advance (bs, to); -} - -static void -clutter_behaviour_bspline_class_init (ClutterBehaviourBsplineClass *klass) -{ - GObjectClass * object_class = G_OBJECT_CLASS (klass); - ClutterBehaviourClass * behave_class = CLUTTER_BEHAVIOUR_CLASS (klass); - - object_class->finalize = clutter_behaviour_bspline_finalize; - - behave_class->alpha_notify = clutter_behaviour_bspline_alpha_notify; - - /** - * ClutterBehaviourBspline::knot-reached: - * @pathb: the object which received the signal - * @knot: the #ClutterKnot reached - * - * This signal is emitted at the end of each frame. - * - * Since: 0.2 - */ - bspline_signals[KNOT_REACHED] = - g_signal_new ("knot-reached", - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - G_STRUCT_OFFSET (ClutterBehaviourBsplineClass, knot_reached), - NULL, NULL, - clutter_marshal_VOID__BOXED, - G_TYPE_NONE, 1, - CLUTTER_TYPE_KNOT); - - g_type_class_add_private (klass, sizeof (ClutterBehaviourBsplinePrivate)); -} - -static void -clutter_behaviour_bspline_init (ClutterBehaviourBspline * self) -{ - ClutterBehaviourBsplinePrivate *priv; - - self->priv = priv = CLUTTER_BEHAVIOUR_BSPLINE_GET_PRIVATE (self); - - priv->splines = g_array_new (FALSE, FALSE, sizeof (ClutterBezier *)); - priv->point_stack = g_array_new (FALSE, FALSE, sizeof (ClutterKnot *)); - priv->length = 0; -} - -static void -clutter_behaviour_bspline_set_custom_property (ClutterScriptable *scriptable, - ClutterScript *script, - const gchar *name, - const GValue *value) -{ - if (strcmp (name, "knots") == 0) - { - ClutterBehaviourBspline *bspline; - GSList *knots, *l; - - if (!G_VALUE_HOLDS (value, G_TYPE_POINTER)) - return; - - bspline = CLUTTER_BEHAVIOUR_BSPLINE (scriptable); - - knots = g_value_get_pointer (value); - for (l = knots; l != NULL; l = l->next) - { - ClutterKnot *knot = l->data; - - clutter_behaviour_bspline_append_knot (bspline, knot); - clutter_knot_free (knot); - } - - g_slist_free (knots); - } - else - g_object_set_property (G_OBJECT (scriptable), name, value); -} - -static gboolean -clutter_behaviour_bspline_parse_custom_node (ClutterScriptable *scriptable, - ClutterScript *script, - GValue *value, - const gchar *name, - JsonNode *node) -{ - if (strcmp (name, "knots") == 0) - { - JsonArray *array; - guint knots_len, i; - GSList *knots = NULL; - - array = json_node_get_array (node); - knots_len = json_array_get_length (array); - - for (i = 0; i < knots_len; i++) - { - JsonNode *val = json_array_get_element (array, i); - ClutterKnot knot = { 0, }; - - if (clutter_script_parse_knot (script, val, &knot)) - { - CLUTTER_NOTE (SCRIPT, "parsed knot [ x:%d, y:%d ]", - knot.x, knot.y); - - knots = g_slist_prepend (knots, clutter_knot_copy (&knot)); - } - } - - g_value_init (value, G_TYPE_POINTER); - g_value_set_pointer (value, g_slist_reverse (knots)); - - return TRUE; - } - - return FALSE; -} - -static void -clutter_scriptable_iface_init (ClutterScriptableIface *iface) -{ - iface->parse_custom_node = clutter_behaviour_bspline_parse_custom_node; - iface->set_custom_property = clutter_behaviour_bspline_set_custom_property; -} - -/** - * clutter_behaviour_bspline_new: - * @alpha: a #ClutterAlpha, or %NULL - * @knots: a list of #ClutterKnots representing individual control points - * @n_knots: the number of control points - * - * Creates a new bezier spline behaviour. You can use this behaviour to drive - * actors along the bezier spline, described by the @knots control points. - * - * Bspline is defined by 3n + 1 points, n >=1; any trailing points passed - * into this function are stored internally and used during any subsequent - * clutter_behaviour_bspline_append_* operations. - * - * Return value: a #ClutterBehaviour - * - * Since: 0.4 - */ -ClutterBehaviour * -clutter_behaviour_bspline_new (ClutterAlpha *alpha, - const ClutterKnot *knots, - guint n_knots) -{ - ClutterBehaviourBspline *bs; - gint i; - - g_return_val_if_fail (alpha == NULL || CLUTTER_IS_ALPHA (alpha), NULL); - - bs = g_object_new (CLUTTER_TYPE_BEHAVIOUR_BSPLINE, "alpha", alpha, NULL); - - for (i = 0; i < n_knots; ++i) - clutter_behaviour_bspline_append_knot (bs, &knots[i]); - - return CLUTTER_BEHAVIOUR (bs); -} - -/* - * Appends a single spline; knots points to 4 knots if this is first - * bezier in the spline, 3 subsequently - */ -static void -clutter_behaviour_bspline_append_spline (ClutterBehaviourBspline * bs, - const ClutterKnot ** knots) -{ - ClutterBehaviourBsplinePrivate *priv; - gint i; - ClutterBezier * b; - ClutterKnot knot0; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs)); - priv = bs->priv; - - if (priv->splines->len) - { - /* Get the first point from the last curve */ - ClutterBezier *b_last; - - b_last = g_array_index (priv->splines, - ClutterBezier *, - priv->splines->len - 1); - - knot0.x = b_last->ax + b_last->bx + b_last->cx + b_last->dx; - knot0.y = b_last->ay + b_last->by + b_last->cy + b_last->dy; - - i = 0; - } - else - { - knot0.x = knots[0]->x; - knot0.y = knots[0]->y; - i = 1; - } - - b = clutter_bezier_new (); - clutter_bezier_init (b, - knot0.x, - knot0.y, - knots[i]->x, knots[i]->y, - knots[i + 1]->x, knots[i + 1]->y, - knots[i + 2]->x, knots[i + 2]->y); - - priv->splines = g_array_append_val (priv->splines, b); - - priv->length += b->length; -} - -/** - * clutter_behaviour_bspline_append_knot: - * @bs: a #ClutterBehaviourBspline - * @knot: a #ClutterKnot control point to append. - * - * Appends a #ClutterKnot control point to the bezier spline bs. Note, that - * since a bezier is defined by 4 control points, the point gets stored in - * a temporary chache, and only when there are enough control points to - * create a new bezier curve will the bspline extended. - * - * Since: 0.4 - */ -void -clutter_behaviour_bspline_append_knot (ClutterBehaviourBspline * bs, - const ClutterKnot * knot) -{ - ClutterBehaviourBsplinePrivate *priv; - ClutterKnot * k = clutter_knot_copy (knot); - guint needed = 3; - guint i; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs)); - priv = bs->priv; - - g_array_append_val (priv->point_stack, k); - - if (priv->splines->len == 0) - needed = 4; - - if (priv->point_stack->len == needed) - { - clutter_behaviour_bspline_append_spline (bs, - (const ClutterKnot**) priv->point_stack->data); - - for (i = 0; i < needed; ++i) - { - clutter_knot_free (g_array_index (priv->point_stack, - ClutterKnot *, - i)); - } - - g_array_set_size (priv->point_stack, 0); - } -} - -static void -clutter_behaviour_bspline_append_knots_valist (ClutterBehaviourBspline *bs, - const ClutterKnot *first_knot, - va_list args) -{ - const ClutterKnot * knot; - - knot = first_knot; - while (knot) - { - clutter_behaviour_bspline_append_knot (bs, knot); - knot = va_arg (args, ClutterKnot*); - } -} - -/** - * clutter_behaviour_bspline_append_knots: - * @bs: a #ClutterBehaviourBspline - * @first_knot: first #ClutterKnot - * @VarArgs: a NULL-terminated array of #ClutterKnot control points. - * - * Appends a bezier spline defined by the last control point of bezier spline - * bs and the array of #ClutterKnot control points to the orginal bezier spline - * bs. - * - * Since: 0.6 - */ -void -clutter_behaviour_bspline_append_knots (ClutterBehaviourBspline * bs, - const ClutterKnot * first_knot, - ...) -{ - va_list args; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs)); - g_return_if_fail (first_knot != NULL); - - va_start (args, first_knot); - clutter_behaviour_bspline_append_knots_valist (bs, first_knot, args); - va_end (args); -} - -/** - * clutter_behaviour_bspline_truncate: - * @bs: a #ClutterBehaviourBspline - * @offset: offset of control where the bspline should be truncated - * - * Truncates the bezier spline at the control point; if the control point at - * offset is not one of the on-curve points, the bspline will be - * truncated at the nearest preceeding on-curve point. - * - * Since: 0.4 - */ -void -clutter_behaviour_bspline_truncate (ClutterBehaviourBspline *bs, - guint offset) -{ - ClutterBehaviourBsplinePrivate *priv; - guint i; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs)); - - if (!offset) - { - clutter_behaviour_bspline_clear (bs); - return; - } - - priv = bs->priv; - - /* convert control point offset to the offset of last spline to keep */ - offset = (offset - 1) / 3; - - priv->splines = g_array_set_size (priv->splines, offset + 1); - priv->length = 0; - - for (i = 0; i < priv->splines->len; ++i) - { - ClutterBezier *b; - - b = g_array_index (priv->splines, ClutterBezier*, i); - - priv->length += b->length; - } - -} - -/** - * clutter_behaviour_bspline_clear: - * @bs: a #ClutterBehaviourBspline - * - * Empties a bspline. - * - * Since: 0.4 - */ -void -clutter_behaviour_bspline_clear (ClutterBehaviourBspline *bs) -{ - ClutterBehaviourBsplinePrivate *priv; - gint i; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs)); - - priv = bs->priv; - - for (i = 0; i < priv->splines->len; ++i) - clutter_bezier_free (g_array_index (priv->splines, ClutterBezier*, i)); - - g_array_set_size (priv->splines, 0); - - for (i = 0; i < priv->point_stack->len; ++i) - clutter_knot_free (g_array_index (priv->point_stack, ClutterKnot*, i)); - - g_array_set_size (priv->point_stack, 0); - - priv->x = 0; - priv->y = 0; - priv->length = 0; -} - -/** - * clutter_behaviour_bspline_join: - * @bs1: a #ClutterBehaviourBspline - * @bs2: a #ClutterBehaviourBspline - * - * Joins a copy of bezier spline bs2 onto the end of bezier spline bs1; bs2 is - * not modified. - * - * Since: 0.4 - */ -void -clutter_behaviour_bspline_join (ClutterBehaviourBspline *bs1, - ClutterBehaviourBspline *bs2) -{ - ClutterBehaviourBsplinePrivate *priv; - gint i, x_1, y_1; - ClutterKnot knot; - ClutterBezier *b, *b2; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs1)); - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs2)); - - clutter_behaviour_bspline_get_origin (bs2, &knot); - - priv = bs1->priv; - - b = g_array_index (priv->splines, ClutterBezier*, priv->splines->len - 1); - - x_1 = clutter_bezier_t2x (b, CBZ_T_ONE); - y_1 = clutter_bezier_t2y (b, CBZ_T_ONE); - - /* - * need to move bs2 so it joins bs1 - */ - x_1 -= knot.x; - y_1 -= knot.y; - - for (i = 0; i < priv->splines->len; ++i) - { - b = g_array_index (bs2->priv->splines, ClutterBezier*, i); - b2 = clutter_bezier_clone_and_move (b, x_1, y_1); - - priv->length += b2->length; - g_array_append_val (priv->splines, b2); - } -} - -/** - * clutter_behaviour_bspline_split: - * @bs: a #ClutterBehaviourBspline - * @offset: an offset of the control point at which to split the spline. - * - * Splits a bezier spline into two at the control point at offset; if the - * control point at offset is not one of the on-curve bezier points, the - * bspline will be split at the nearest on-curve point before the offset. - * The original bspline is shortened appropriately. - * - * Return value: new ClutterBehaviourBspline. - * - * Since: 0.4 - */ -ClutterBehaviour * -clutter_behaviour_bspline_split (ClutterBehaviourBspline *bs, - guint offset) -{ - ClutterBehaviourBsplinePrivate *priv; - ClutterBehaviourBspline * bs2 = NULL; - ClutterAlpha * alpha; - guint i, split, length2 = 0; - - g_return_val_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs), NULL); - - priv = bs->priv; - - split = offset / 3; - - if (split == 0 || split >= priv->splines->len) - return NULL; - - alpha = clutter_behaviour_get_alpha (CLUTTER_BEHAVIOUR (bs)); - - bs2 = g_object_new (CLUTTER_TYPE_BEHAVIOUR_BSPLINE, "alpha", alpha, NULL); - - bs2->priv->x = priv->x; - bs2->priv->y = priv->y; - - for (i = split; i < priv->splines->len; ++i) - { - ClutterBezier *b; - - b = g_array_index (priv->splines, ClutterBezier*, i); - g_array_append_val (bs2->priv->splines, b); - - length2 += b->length; - } - - bs2->priv->length -= length2; - bs2->priv->length = length2; - - g_array_set_size (priv->splines, split); - - return CLUTTER_BEHAVIOUR (bs2); -} - -/** - * clutter_behaviour_bspline_adjust: - * @bs: a #ClutterBehaviourBspline - * @offset: an index of control point to ajdust - * @knot: a #ClutterKnot with new coordinances for the control point. - * - * Change the coordinaces of control point at index to those represented by - * the knot. - * - * Since: 0.4 - */ -void -clutter_behaviour_bspline_adjust (ClutterBehaviourBspline *bs, - guint offset, - ClutterKnot *knot) -{ - ClutterBehaviourBsplinePrivate *priv; - ClutterBezier * b1 = NULL; - ClutterBezier * b2 = NULL; - guint p1_indx = 0; - guint p2_indx = 0; - guint old_length; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs)); - - priv = bs->priv; - - /* - * Find the bezier(s) affected by change of this control point - * and the relative position of the control point within them - */ - - if (offset == 0) - b1 = g_array_index (priv->splines, ClutterBezier*, 0); - else if (offset + 1 == priv->splines->len) - { - b2 = g_array_index (priv->splines, - ClutterBezier*, - priv->splines->len - 1); - p2_indx = 3; - } - else - { - guint mod3 = offset % 3; - guint i = offset / 3; - - if (mod3 == 0) - { - /* on-curve point, i.e., two beziers */ - b1 = g_array_index (priv->splines, ClutterBezier*, i - 1); - b2 = g_array_index (priv->splines, ClutterBezier*, i); - p1_indx = 3; - } - else - { - b1 = g_array_index (priv->splines, ClutterBezier*, i); - p1_indx = mod3; - } - } - - /* - * Adjust the bezier(s) and total bspline length - */ - if (b1) - { - old_length = b1->length; - clutter_bezier_adjust (b1, knot, p1_indx); - priv->length = priv->length - old_length + b1->length; - } - - if (b2) - { - old_length = b2->length; - clutter_bezier_adjust (b2, knot, p2_indx); - priv->length = priv->length - old_length + b2->length; - } -} - -/** - * clutter_behaviour_bspline_set_origin - * @bs: a #ClutterBehaviourBspline - * @knot: a #ClutterKnot origin for the bezier - * - * Sets the origin of the bezier to the point represented by knot. (Initially - * the origin of a bspline is given by the position of the first control point - * of the first bezier curve.) - * - * Since: 0.4 - */ -void -clutter_behaviour_bspline_set_origin (ClutterBehaviourBspline * bs, - ClutterKnot * knot) -{ - ClutterBehaviourBsplinePrivate *priv; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs)); - - priv = bs->priv; - - if (priv->splines->len == 0) - { - priv->x = knot->x; - priv->y = knot->y; - } - else - { - ClutterBezier *b; - - b = g_array_index (priv->splines, ClutterBezier*, 0); - - priv->x = knot->x - b->dx; - priv->y = knot->y - b->dy; - - CLUTTER_NOTE (BEHAVIOUR, "setting origin to (%d, %d): " - "b (%d, %d), adjustment (%d, %d)", - knot->x, knot->y, - b->dx, b->dy, - priv->x, priv->y); - } -} - -/** - * clutter_behaviour_bspline_get_origin - * @bs: a #ClutterBehaviourBspline - * @knot: a #ClutterKnot where to store the origin of the bezier - * - * Gets the origin of the bezier. - * - * Since: 0.4 - */ -void -clutter_behaviour_bspline_get_origin (ClutterBehaviourBspline *bs, - ClutterKnot *knot) -{ - ClutterBehaviourBsplinePrivate *priv; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_BSPLINE (bs)); - g_return_if_fail (knot != NULL); - - priv = bs->priv; - - if (priv->splines->len == 0) - { - knot->x = priv->x; - knot->y = priv->y; - } - else - { - ClutterBezier *b; - - b = g_array_index (priv->splines, ClutterBezier*, 0); - - knot->x = priv->x + b->dx; - knot->y = priv->y + b->dy; - } -} - diff --git a/clutter/clutter-behaviour-bspline.h b/clutter/clutter-behaviour-bspline.h deleted file mode 100644 index 644b5883e..000000000 --- a/clutter/clutter-behaviour-bspline.h +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Clutter. - * - * An OpenGL based 'interactive canvas' library. - * - * Authored By Tomas Frydrych - * - * Copyright (C) 2006, 2007 OpenedHand - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library. If not, see . - */ - -#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) -#error "Only can be included directly." -#endif - -#ifndef __CLUTTER_BEHAVIOUR_BSPLINE_H__ -#define __CLUTTER_BEHAVIOUR_BSPLINE_H__ - -#include -#include -#include - -G_BEGIN_DECLS - -#define CLUTTER_TYPE_BEHAVIOUR_BSPLINE (clutter_behaviour_bspline_get_type ()) - -#define CLUTTER_BEHAVIOUR_BSPLINE(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ - CLUTTER_TYPE_BEHAVIOUR_BSPLINE, ClutterBehaviourBspline)) - -#define CLUTTER_BEHAVIOUR_BSPLINE_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST ((klass), \ - CLUTTER_TYPE_BEHAVIOUR_BSPLINE, ClutterBehaviourBsplineClass)) - -#define CLUTTER_IS_BEHAVIOUR_BSPLINE(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ - CLUTTER_TYPE_BEHAVIOUR_BSPLINE)) - -#define CLUTTER_IS_BEHAVIOUR_BSPLINE_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE ((klass), \ - CLUTTER_TYPE_BEHAVIOUR_BSPLINE)) - -#define CLUTTER_BEHAVIOUR_BSPLINE_GET_CLASS(obj) \ - (G_TYPE_INSTANCE_GET_CLASS ((obj), \ - CLUTTER_TYPE_BEHAVIOUR_BSPLINE, ClutterBehaviourBsplineClass)) - -typedef struct _ClutterBehaviourBspline ClutterBehaviourBspline; -typedef struct _ClutterBehaviourBsplinePrivate ClutterBehaviourBsplinePrivate; -typedef struct _ClutterBehaviourBsplineClass ClutterBehaviourBsplineClass; - -/** - * ClutterBehaviourBspline: - * - * #ClutterBehaviourBspline-struct contains only private data and - * should be accessed using the functions below. - * - * Since: 0.2 - */ -struct _ClutterBehaviourBspline -{ - ClutterBehaviour parent_instance; - ClutterBehaviourBsplinePrivate *priv; -}; - -/** - * ClutterBehaviourBsplineClass: - * @knot_reached: class handler for the #ClutterBehaviourBspline::knot-reached - * signal - * - * #ClutterBehaviourBsplineClass-struct contains only private data - * and should be accessed using the functions below. - * - * Since: 0.2 - */ -struct _ClutterBehaviourBsplineClass -{ - /*< private >*/ - ClutterBehaviourClass parent_class; - - /*< public >*/ - void (*knot_reached) (ClutterBehaviourBspline *bsplineb, - const ClutterKnot *knot); - - /*< private >*/ - /* padding for future expansion */ - void (*_clutter_bspline_1) (void); - void (*_clutter_bspline_2) (void); - void (*_clutter_bspline_3) (void); - void (*_clutter_bspline_4) (void); -}; - -GType clutter_behaviour_bspline_get_type (void) G_GNUC_CONST; - -ClutterBehaviour *clutter_behaviour_bspline_new (ClutterAlpha *alpha, - const ClutterKnot *knots, - guint n_knots); -void clutter_behaviour_bspline_append_knot (ClutterBehaviourBspline *bs, - const ClutterKnot *knot); -void clutter_behaviour_bspline_append_knots (ClutterBehaviourBspline *bs, - const ClutterKnot *first_knot, - ...) G_GNUC_NULL_TERMINATED; -void clutter_behaviour_bspline_truncate (ClutterBehaviourBspline *bs, - guint offset); -void clutter_behaviour_bspline_join (ClutterBehaviourBspline *bs1, - ClutterBehaviourBspline *bs2); -ClutterBehaviour *clutter_behaviour_bspline_split (ClutterBehaviourBspline *bs, - guint offset); -void clutter_behaviour_bspline_clear (ClutterBehaviourBspline *bs); -void clutter_behaviour_bspline_adjust (ClutterBehaviourBspline *bs, - guint offset, - ClutterKnot *knot); -void clutter_behaviour_bspline_set_origin (ClutterBehaviourBspline *bs, - ClutterKnot *knot); -void clutter_behaviour_bspline_get_origin (ClutterBehaviourBspline *bs, - ClutterKnot *knot); - -G_END_DECLS - -#endif /* __CLUTTER_BEHAVIOUR_BSPLINE_H__ */ diff --git a/clutter/clutter-behaviour-path.c b/clutter/clutter-behaviour-path.c index 7dcf29244..ccbd20ca1 100644 --- a/clutter/clutter-behaviour-path.c +++ b/clutter/clutter-behaviour-path.c @@ -25,20 +25,29 @@ /** * SECTION:clutter-behaviour-path - * @short_description: A behaviour interpolating position along a path + * @short_description: A behaviour for moving actors along a #ClutterPath * * #ClutterBehaviourPath interpolates actors along a defined path. * - * A path is a set of #ClutterKnots object given when creating a new - * #ClutterBehaviourPath instance. Knots can be also added to the path - * using clutter_behaviour_path_append_knot(). The whole path can be - * cleared using clutter_behaviour_path_clear(). Each time the behaviour - * reaches a knot in the path, the "knot-reached" signal is emitted. + * A path is described by a #ClutterPath object. The path can contain + * straight line parts and bezier curves. If the path contains + * %CLUTTER_PATH_MOVE_TO parts then the actors will jump to those + * coordinates. This can be used make disjoint paths. * - * This first knot in the path is reached with the lower bound value - * provided by the #ClutterAlpha objectused by the behaviour; the last - * knot in the path is reached with the upper bound value provided by - * the #ClutterAlpha object used by the behaviour. + * When creating a path behaviour in a #ClutterScript, you can specify + * the path property directly as a string. For example: + * + * |[ + * { + * "id" : "spline-path", + * "type" : "ClutterBehaviourPath", + * "path" : "M 50 50 L 100 100", + * "alpha" : { + * "timeline" : "main-timeline", + * "function" : "ramp + * } + * } + * ]| * * If the alpha function is a periodic function, i.e. it returns to * 0 after reaching %CLUTTER_ALPHA_MAX_ALPHA, then the actors will walk @@ -54,13 +63,14 @@ #include "clutter-actor.h" #include "clutter-behaviour.h" #include "clutter-behaviour-path.h" +#include "clutter-bezier.h" #include "clutter-debug.h" #include "clutter-enum-types.h" #include "clutter-main.h" #include "clutter-marshal.h" #include "clutter-private.h" -#include "clutter-scriptable.h" #include "clutter-script-private.h" +#include "clutter-scriptable.h" #include @@ -68,14 +78,14 @@ static void clutter_scriptable_iface_init (ClutterScriptableIface *iface); G_DEFINE_TYPE_WITH_CODE (ClutterBehaviourPath, clutter_behaviour_path, - CLUTTER_TYPE_BEHAVIOUR, + CLUTTER_TYPE_BEHAVIOUR, G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE, clutter_scriptable_iface_init)); struct _ClutterBehaviourPathPrivate { - GSList *knots; - ClutterKnot *last_knot_passed; + ClutterPath *path; + guint last_knot_passed; }; #define CLUTTER_BEHAVIOUR_PATH_GET_PRIVATE(obj) \ @@ -96,72 +106,9 @@ enum { PROP_0, - PROP_KNOT + PROP_PATH }; -static void -clutter_behaviour_path_finalize (GObject *object) -{ - ClutterBehaviourPath *self = CLUTTER_BEHAVIOUR_PATH(object); - - g_slist_foreach (self->priv->knots, (GFunc) clutter_knot_free, NULL); - g_slist_free (self->priv->knots); - - G_OBJECT_CLASS (clutter_behaviour_path_parent_class)->finalize (object); -} - -static inline void -interpolate (const ClutterKnot *start, - const ClutterKnot *end, - ClutterKnot *out, - ClutterFixed t) -{ - out->x = start->x + COGL_FIXED_TO_INT (t * (end->x - start->x)); - out->y = start->y + COGL_FIXED_TO_INT (t * (end->y - start->y)); -} - -static gint -node_distance (const ClutterKnot *start, - const ClutterKnot *end) -{ - gint t; - - g_return_val_if_fail (start != NULL, 0); - g_return_val_if_fail (end != NULL, 0); - - if (clutter_knot_equal (start, end)) - return 0; - - t = (end->x - start->x) * (end->x - start->x) + - (end->y - start->y) * (end->y - start->y); - - /* - * If we are using limited precision sqrti implementation, fallback on - * clib sqrt if the precission would be less than 10% - */ -#if INT_MAX > CLUTTER_SQRTI_ARG_10_PERCENT - if (t <= COGL_SQRTI_ARG_10_PERCENT) - return cogl_sqrti (t); - else - return COGL_FLOAT_TO_INT (sqrt(t)); -#else - return cogl_sqrti (t); -#endif -} - -static gint -path_total_length (ClutterBehaviourPath *behave) -{ - GSList *l; - gint len = 0; - - for (l = behave->priv->knots; l != NULL; l = l->next) - if (l->next && l->next->data) - len += node_distance (l->data, l->next->data); - - return len; -} - static void actor_apply_knot_foreach (ClutterBehaviour *behaviour, ClutterActor *actor, @@ -175,105 +122,50 @@ actor_apply_knot_foreach (ClutterBehaviour *behaviour, } static void -path_alpha_to_position (ClutterBehaviourPath *behave, - guint32 alpha) +clutter_behaviour_path_alpha_notify (ClutterBehaviour *behave, + guint32 alpha_value) { - ClutterBehaviourPathPrivate *priv = behave->priv; - ClutterBehaviour *behaviour = CLUTTER_BEHAVIOUR (behave); - GSList *l; - gint total_len, offset, dist = 0; + ClutterBehaviourPath *pathb = CLUTTER_BEHAVIOUR_PATH (behave); + ClutterBehaviourPathPrivate *priv = pathb->priv; + ClutterKnot position; + guint knot_num; - /* FIXME: Optimise. Much of the data used here can be pre-generated - * ( total_len, dist between knots ) when knots are added/removed. - */ - - /* Calculation as follows: - * o Get total length of path - * o Find the offset on path where alpha val corresponds to - * o Figure out between which knots this offset lies. - * o Interpolate new co-ords via dist between these knots - * o Apply to actors. - */ - - total_len = path_total_length (behave); - offset = (alpha * total_len) / CLUTTER_ALPHA_MAX_ALPHA; - - CLUTTER_NOTE (BEHAVIOUR, "alpha %i vs %i, len: %i vs %i", - alpha, CLUTTER_ALPHA_MAX_ALPHA, - offset, total_len); - - if (offset == 0) + if (priv->path) + knot_num = clutter_path_get_position (priv->path, alpha_value, &position); + else { - /* first knot */ - clutter_behaviour_actors_foreach (behaviour, - actor_apply_knot_foreach, - priv->knots->data); - - priv->last_knot_passed = (ClutterKnot*)priv->knots->data; - g_signal_emit (behave, path_signals[KNOT_REACHED], 0, - priv->knots->data); - return; + memset (&position, 0, sizeof (position)); + knot_num = 0; } - if (offset == total_len) + clutter_behaviour_actors_foreach (behave, + actor_apply_knot_foreach, + &position); + + if (knot_num != priv->last_knot_passed) { - /* Special case for last knot */ - ClutterKnot *last_knot = (g_slist_last (priv->knots))->data; - - clutter_behaviour_actors_foreach (behaviour, - actor_apply_knot_foreach, - last_knot); - - priv->last_knot_passed = (ClutterKnot*)priv->knots->data; - g_signal_emit (behave, path_signals[KNOT_REACHED], 0, last_knot); - - return; - } - - for (l = priv->knots; l != NULL; l = l->next) - { - gint dist_to_next = 0; - ClutterKnot *knot = l->data; - - if (l->next) - { - ClutterKnot *next = l->next->data; - - dist_to_next = node_distance (knot, next); - - if (offset >= dist && offset < (dist + dist_to_next)) - { - ClutterKnot new; - ClutterFixed t; - - t = COGL_FIXED_FROM_INT (offset - dist) / dist_to_next; - - interpolate (knot, next, &new, t); - - clutter_behaviour_actors_foreach (behaviour, - actor_apply_knot_foreach, - &new); - - if (knot != priv->last_knot_passed) - { - /* We just passed a new Knot */ - priv->last_knot_passed = knot; - g_signal_emit (behave, path_signals[KNOT_REACHED], 0, knot); - } - - return; - } - } - - dist += dist_to_next; + g_signal_emit (behave, path_signals[KNOT_REACHED], 0, knot_num); + priv->last_knot_passed = knot_num; } } static void -clutter_behaviour_path_alpha_notify (ClutterBehaviour *behave, - guint32 alpha_value) +clutter_behaviour_path_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) { - path_alpha_to_position (CLUTTER_BEHAVIOUR_PATH (behave), alpha_value); + ClutterBehaviourPath *pathb = CLUTTER_BEHAVIOUR_PATH (gobject); + + switch (prop_id) + { + case PROP_PATH: + g_value_set_object (value, clutter_behaviour_path_get_path (pathb)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } } static void @@ -286,8 +178,8 @@ clutter_behaviour_path_set_property (GObject *gobject, switch (prop_id) { - case PROP_KNOT: - clutter_behaviour_path_append_knot (pathb, g_value_get_boxed (value)); + case PROP_PATH: + clutter_behaviour_path_set_path (pathb, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); @@ -295,34 +187,39 @@ clutter_behaviour_path_set_property (GObject *gobject, } } +static void +clutter_behaviour_path_dispose (GObject *gobject) +{ + ClutterBehaviourPath *pathb = CLUTTER_BEHAVIOUR_PATH (gobject); + + clutter_behaviour_path_set_path (pathb, NULL); + + G_OBJECT_CLASS (clutter_behaviour_path_parent_class)->dispose (gobject); +} + static void clutter_behaviour_path_class_init (ClutterBehaviourPathClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); ClutterBehaviourClass *behave_class = CLUTTER_BEHAVIOUR_CLASS (klass); + GParamSpec *pspec; + gobject_class->get_property = clutter_behaviour_path_get_property; gobject_class->set_property = clutter_behaviour_path_set_property; - gobject_class->finalize = clutter_behaviour_path_finalize; + gobject_class->dispose = clutter_behaviour_path_dispose; - /** - * ClutterBehaviourPath:knot: - * - * This property can be used to append a new knot to the path. - * - * Since: 0.2 - */ - g_object_class_install_property (gobject_class, - PROP_KNOT, - g_param_spec_boxed ("knot", - "Knot", - "Can be used to append a knot to the path", - CLUTTER_TYPE_KNOT, - CLUTTER_PARAM_WRITABLE)); + pspec = g_param_spec_object ("path", + "Path", + "The ClutterPath object representing the path " + "to animate along", + CLUTTER_TYPE_PATH, + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_PATH, pspec); /** * ClutterBehaviourPath::knot-reached: * @pathb: the object which received the signal - * @knot: the #ClutterKnot reached + * @knot_num: the index of the #ClutterPathKnot reached * * This signal is emitted each time a node defined inside the path * is reached. @@ -335,51 +232,16 @@ clutter_behaviour_path_class_init (ClutterBehaviourPathClass *klass) G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (ClutterBehaviourPathClass, knot_reached), NULL, NULL, - clutter_marshal_VOID__BOXED, + clutter_marshal_VOID__UINT, G_TYPE_NONE, 1, - CLUTTER_TYPE_KNOT); + G_TYPE_UINT); behave_class->alpha_notify = clutter_behaviour_path_alpha_notify; g_type_class_add_private (klass, sizeof (ClutterBehaviourPathPrivate)); } -static void -clutter_behaviour_path_init (ClutterBehaviourPath *self) -{ - ClutterBehaviourPathPrivate *priv; - - self->priv = priv = CLUTTER_BEHAVIOUR_PATH_GET_PRIVATE (self); -} - -static void -clutter_behaviour_path_set_custom_property (ClutterScriptable *scriptable, - ClutterScript *script, - const gchar *name, - const GValue *value) -{ - if (strcmp (name, "knots") == 0) - { - ClutterBehaviourPath *path = CLUTTER_BEHAVIOUR_PATH (scriptable); - GSList *knots, *l; - - if (!G_VALUE_HOLDS (value, G_TYPE_POINTER)) - return; - - knots = g_value_get_pointer (value); - for (l = knots; l != NULL; l = l->next) - { - ClutterKnot *knot = l->data; - - clutter_behaviour_path_append_knot (path, knot); - clutter_knot_free (knot); - } - - g_slist_free (knots); - } - else - g_object_set_property (G_OBJECT (scriptable), name, value); -} +static ClutterScriptableIface *parent_scriptable_iface = NULL; static gboolean clutter_behaviour_path_parse_custom_node (ClutterScriptable *scriptable, @@ -388,237 +250,190 @@ clutter_behaviour_path_parse_custom_node (ClutterScriptable *scriptable, const gchar *name, JsonNode *node) { - if (strcmp (name, "knots") == 0) + if (strcmp ("path", name) == 0) { - JsonArray *array; - guint knots_len, i; - GSList *knots = NULL; + ClutterPath *path; + GValue node_value = { 0 }; - array = json_node_get_array (node); - knots_len = json_array_get_length (array); + path = g_object_ref_sink (clutter_path_new ()); - for (i = 0; i < knots_len; i++) - { - JsonNode *val = json_array_get_element (array, i); - ClutterKnot knot = { 0, }; + json_node_get_value (node, &node_value); - if (clutter_script_parse_knot (script, val, &knot)) - { - CLUTTER_NOTE (SCRIPT, "parsed knot [ x:%d, y:%d ]", - knot.x, knot.y); + if (!G_VALUE_HOLDS (&node_value, G_TYPE_STRING) + || !clutter_path_set_description (path, + g_value_get_string (&node_value))) + g_warning ("Invalid path description"); - knots = g_slist_prepend (knots, clutter_knot_copy (&knot)); - } - } + g_value_unset (&node_value); - g_value_init (value, G_TYPE_POINTER); - g_value_set_pointer (value, g_slist_reverse (knots)); + g_value_init (value, G_TYPE_OBJECT); + g_value_take_object (value, path); return TRUE; } - - return FALSE; + /* chain up */ + else if (parent_scriptable_iface->parse_custom_node) + return parent_scriptable_iface->parse_custom_node (scriptable, script, + value, name, node); + else + return FALSE; } static void clutter_scriptable_iface_init (ClutterScriptableIface *iface) { + parent_scriptable_iface = g_type_interface_peek_parent (iface); + + if (!parent_scriptable_iface) + parent_scriptable_iface + = g_type_default_interface_peek (CLUTTER_TYPE_SCRIPTABLE); + iface->parse_custom_node = clutter_behaviour_path_parse_custom_node; - iface->set_custom_property = clutter_behaviour_path_set_custom_property; +} + +static void +clutter_behaviour_path_init (ClutterBehaviourPath *self) +{ + ClutterBehaviourPathPrivate *priv; + + self->priv = priv = CLUTTER_BEHAVIOUR_PATH_GET_PRIVATE (self); + + priv->path = NULL; + priv->last_knot_passed = G_MAXUINT; } /** * clutter_behaviour_path_new: * @alpha: a #ClutterAlpha, or %NULL - * @knots: a list of #ClutterKnots, or %NULL for an empty path - * @n_knots: the number of nodes in the path + * @path: a #ClutterPath or %NULL for an empty path * * Creates a new path behaviour. You can use this behaviour to drive - * actors along the nodes of a path, described by the @knots. + * actors along the nodes of a path, described by @path. + * + * This will claim the floating reference on the #ClutterPath so you + * do not need to unref if it. * * Return value: a #ClutterBehaviour * * Since: 0.2 */ ClutterBehaviour * -clutter_behaviour_path_new (ClutterAlpha *alpha, - const ClutterKnot *knots, - guint n_knots) +clutter_behaviour_path_new (ClutterAlpha *alpha, + ClutterPath *path) { - ClutterBehaviourPath *behave; - gint i; - - behave = g_object_new (CLUTTER_TYPE_BEHAVIOUR_PATH, - "alpha", alpha, - NULL); - - for (i = 0; i < n_knots; i++) - { - ClutterKnot knot = knots[i]; - - clutter_behaviour_path_append_knot (behave, &knot); - } - - return CLUTTER_BEHAVIOUR (behave); + return g_object_new (CLUTTER_TYPE_BEHAVIOUR_PATH, + "alpha", alpha, + "path", path, + NULL); } /** - * clutter_behaviour_path_get_knots: - * @pathb: a #ClutterBehvaiourPath + * clutter_behaviour_path_new_with_description: + * @alpha: a #ClutterAlpha + * @desc: a string description of the path * - * Returns a copy of the list of knots contained by @pathb + * Creates a new path behaviour using the path described by @desc. See + * clutter_path_add_string() for a description of the format. * - * Return value: a #GSList of the paths knots. + * Return value: a #ClutterBehaviour * - * Since: 0.2 + * Since: 1.0 */ -GSList * -clutter_behaviour_path_get_knots (ClutterBehaviourPath *pathb) +ClutterBehaviour * +clutter_behaviour_path_new_with_description (ClutterAlpha *alpha, + const gchar *desc) { - GSList *retval, *l; + return g_object_new (CLUTTER_TYPE_BEHAVIOUR_PATH, + "alpha", alpha, + "path", clutter_path_new_with_description (desc), + NULL); +} +/** + * clutter_behaviour_path_new_with_knots: + * @alpha: a #ClutterAlpha + * @knots: an array of #ClutterKnots + * @n_knots: number of entries in @knots + * + * Creates a new path behaviour that will make the actors visit all of + * the given knots in order with straight lines in between. + * + * A path will be created where the first knot is used in a + * %CLUTTER_PATH_MOVE_TO and the subsequent knots are used in + * %CLUTTER_PATH_LINE_TOs. + * + * Return value: a #ClutterBehaviour + * + * Since: 1.0 + */ +ClutterBehaviour * +clutter_behaviour_path_new_with_knots (ClutterAlpha *alpha, + const ClutterKnot *knots, + guint n_knots) +{ + ClutterPath *path = clutter_path_new (); + guint i; + + if (n_knots > 0) + { + clutter_path_add_move_to (path, knots[0].x, knots[0].y); + + for (i = 1; i < n_knots; i++) + clutter_path_add_line_to (path, knots[i].x, knots[i].y); + } + + return g_object_new (CLUTTER_TYPE_BEHAVIOUR_PATH, + "alpha", alpha, + "path", path, + NULL); +} + +/** + * clutter_behaviour_path_set_path: + * @pathb: the path behaviour + * @path: the new path to follow + * + * Change the path that the actors will follow. This will take the + * floating reference on the #ClutterPath so you do not need to unref + * it. + * + * Since: 1.0 + */ +void +clutter_behaviour_path_set_path (ClutterBehaviourPath *pathb, + ClutterPath *path) +{ + ClutterBehaviourPathPrivate *priv; + + g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb)); + + priv = pathb->priv; + + if (path) + g_object_ref_sink (path); + + if (priv->path) + g_object_unref (priv->path); + + priv->path = path; + + g_object_notify (G_OBJECT (pathb), "path"); +} + +/** + * clutter_behaviour_path_get_path: + * @pathb: a #ClutterBehaviourPath instance + * + * Get the current path of the behaviour + * + * Return value: the path + * + * Since 1.0: + */ +ClutterPath * +clutter_behaviour_path_get_path (ClutterBehaviourPath *pathb) +{ g_return_val_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb), NULL); - retval = NULL; - for (l = pathb->priv->knots; l != NULL; l = l->next) - retval = g_slist_prepend (retval, l->data); - - return g_slist_reverse (retval); -} - -/** - * clutter_behaviour_path_append_knot: - * @pathb: a #ClutterBehvaiourPath - * @knot: a #ClutterKnot to append. - * - * Appends a #ClutterKnot to the path - * - * Since: 0.2 - */ -void -clutter_behaviour_path_append_knot (ClutterBehaviourPath *pathb, - const ClutterKnot *knot) -{ - ClutterBehaviourPathPrivate *priv; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb)); - g_return_if_fail (knot != NULL); - - priv = pathb->priv; - priv->knots = g_slist_append (priv->knots, clutter_knot_copy (knot)); -} - -/** - * clutter_behaviour_path_insert_knot: - * @pathb: a #ClutterBehvaiourPath - * @offset: position in path to insert knot. - * @knot: a #ClutterKnot to append. - * - * Inserts a #ClutterKnot in the path at specified position. Values greater - * than total number of knots will append the knot at the end of path. - * - * Since: 0.2 - */ -void -clutter_behaviour_path_insert_knot (ClutterBehaviourPath *pathb, - guint offset, - const ClutterKnot *knot) -{ - ClutterBehaviourPathPrivate *priv; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb)); - g_return_if_fail (knot != NULL); - - priv = pathb->priv; - priv->knots = g_slist_insert (priv->knots, clutter_knot_copy (knot), offset); -} - -/** - * clutter_behaviour_path_remove_knot: - * @pathb: a #ClutterBehvaiourPath - * @offset: position in path to remove knot. - * - * Removes a #ClutterKnot in the path at specified offset. - * - * Since: 0.2 - */ -void -clutter_behaviour_path_remove_knot (ClutterBehaviourPath *pathb, - guint offset) -{ - ClutterBehaviourPathPrivate *priv; - GSList *togo; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb)); - - priv = pathb->priv; - - togo = g_slist_nth (priv->knots, offset); - - if (togo) - { - clutter_knot_free ((ClutterKnot*)togo->data); - priv->knots = g_slist_delete_link (priv->knots, togo); - } -} - -static void -clutter_behaviour_path_append_knots_valist (ClutterBehaviourPath *pathb, - const ClutterKnot *first_knot, - va_list args) -{ - const ClutterKnot * knot; - - knot = first_knot; - while (knot) - { - clutter_behaviour_path_append_knot (pathb, knot); - knot = va_arg (args, ClutterKnot*); - } -} - -/** - * clutter_behaviour_path_append_knots: - * @pathb: a #ClutterBehvaiourPath - * @first_knot: the #ClutterKnot knot to add to the path - * @Varargs: additional knots to add to the path - * - * Adds a NULL-terminated list of knots to a path. This function is - * equivalent to calling clutter_behaviour_path_append_knot() for each - * member of the list. - * - * Since: 0.2 - */ -void -clutter_behaviour_path_append_knots (ClutterBehaviourPath *pathb, - const ClutterKnot *first_knot, - ...) -{ - va_list args; - - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb)); - g_return_if_fail (first_knot != NULL); - - va_start (args, first_knot); - clutter_behaviour_path_append_knots_valist (pathb, first_knot, args); - va_end (args); -} - -/** - * clutter_behaviour_path_clear: - * @pathb: a #ClutterBehvaiourPath - * - * Removes all knots from a path - * - * Since: 0.2 - */ -void -clutter_behaviour_path_clear (ClutterBehaviourPath *pathb) -{ - g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb)); - - g_slist_foreach (pathb->priv->knots, (GFunc) clutter_knot_free, NULL); - g_slist_free (pathb->priv->knots); - - pathb->priv->knots = NULL; + return pathb->priv->path; } diff --git a/clutter/clutter-behaviour-path.h b/clutter/clutter-behaviour-path.h index 8eb425ff7..eab16efeb 100644 --- a/clutter/clutter-behaviour-path.h +++ b/clutter/clutter-behaviour-path.h @@ -32,6 +32,7 @@ #include #include +#include G_BEGIN_DECLS @@ -82,7 +83,7 @@ struct _ClutterBehaviourPathClass /*< public >*/ void (*knot_reached) (ClutterBehaviourPath *pathb, - const ClutterKnot *knot); + guint knot_num); /*< private >*/ void (*_clutter_path_1) (void); @@ -94,21 +95,21 @@ struct _ClutterBehaviourPathClass GType clutter_behaviour_path_get_type (void) G_GNUC_CONST; ClutterBehaviour *clutter_behaviour_path_new (ClutterAlpha *alpha, + ClutterPath *path); + +ClutterBehaviour *clutter_behaviour_path_new_with_description + (ClutterAlpha *alpha, + const gchar *desc); + +ClutterBehaviour *clutter_behaviour_path_new_with_knots + (ClutterAlpha *alpha, const ClutterKnot *knots, guint n_knots); -GSList * clutter_behaviour_path_get_knots (ClutterBehaviourPath *pathb); -void clutter_behaviour_path_append_knot (ClutterBehaviourPath *pathb, - const ClutterKnot *knot); -void clutter_behaviour_path_append_knots (ClutterBehaviourPath *pathb, - const ClutterKnot *first_knot, - ...) G_GNUC_NULL_TERMINATED; -void clutter_behaviour_path_insert_knot (ClutterBehaviourPath *pathb, - guint offset, - const ClutterKnot *knot); -void clutter_behaviour_path_remove_knot (ClutterBehaviourPath *pathb, - guint offset); -void clutter_behaviour_path_clear (ClutterBehaviourPath *pathb); +void clutter_behaviour_path_set_path (ClutterBehaviourPath *pathb, + ClutterPath *path); + +ClutterPath * clutter_behaviour_path_get_path (ClutterBehaviourPath *pathb); G_END_DECLS diff --git a/clutter/clutter-bezier.c b/clutter/clutter-bezier.c new file mode 100644 index 000000000..6a476268f --- /dev/null +++ b/clutter/clutter-bezier.c @@ -0,0 +1,426 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Tomas Frydrych + * + * Copyright (C) 2007 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "clutter-bezier.h" +#include "clutter-debug.h" + +/* + * We have some experimental code here to allow for constant velocity + * movement of actors along the bezier path, this macro enables it. + */ +#undef CBZ_L2T_INTERPOLATION + +/**************************************************************************** + * ClutterBezier -- represenation of a cubic bezier curve * + * (private; a building block for the public bspline object) * + ****************************************************************************/ + +/* + * The t parameter of the bezier is from interval <0,1>, so we can use + * 14.18 format and special multiplication functions that preserve + * more of the least significant bits but would overflow if the value + * is > 1 + */ +#define CBZ_T_Q 18 +#define CBZ_T_ONE (1 << CBZ_T_Q) +#define CBZ_T_MUL(x,y) ((((x) >> 3) * ((y) >> 3)) >> 12) +#define CBZ_T_POW2(x) CBZ_T_MUL (x, x) +#define CBZ_T_POW3(x) CBZ_T_MUL (CBZ_T_POW2 (x), x) +#define CBZ_T_DIV(x,y) ((((x) << 9)/(y)) << 9) + +/* + * Constants for sampling of the bezier + */ +#define CBZ_T_SAMPLES 128 +#define CBZ_T_STEP (CBZ_T_ONE / CBZ_T_SAMPLES) +#define CBZ_L_STEP (CBZ_T_ONE / CBZ_T_SAMPLES) + +typedef gint32 _FixedT; + +/* + * This is a private type representing a single cubic bezier + */ +struct _ClutterBezier +{ + /* + * bezier coefficients -- these are calculated using multiplication and + * addition from integer input, so these are also integers + */ + gint ax; + gint bx; + gint cx; + gint dx; + + gint ay; + gint by; + gint cy; + gint dy; + + /* length of the bezier */ + guint length; + +#ifdef CBZ_L2T_INTERPOLATION + /* + * coefficients for the L -> t bezier; these are calculated from fixed + * point input, and more specifically numbers that have been normalised + * to fit <0,1>, so these are also fixed point, and we can used the + * _FixedT type here. + */ + _FixedT La; + _FixedT Lb; + _FixedT Lc; + /* _FixedT Ld; == 0 */ +#endif +}; + +ClutterBezier * +_clutter_bezier_new () +{ + return g_slice_new0 (ClutterBezier); +} + +void +_clutter_bezier_free (ClutterBezier * b) +{ + if (G_LIKELY (b)) + { + g_slice_free (ClutterBezier, b); + } +} + +ClutterBezier * +_clutter_bezier_clone_and_move (const ClutterBezier *b, gint x, gint y) +{ + ClutterBezier * b2 = _clutter_bezier_new (); + memcpy (b2, b, sizeof (ClutterBezier)); + + b2->dx += x; + b2->dy += y; + + return b2; +} + +#ifdef CBZ_L2T_INTERPOLATION +/* + * L is relative advance along the bezier curve from interval <0,1> + */ +static _FixedT +_clutter_bezier_L2t (const ClutterBezier *b, _FixedT L) +{ + _FixedT t = CBZ_T_MUL (b->La, CBZ_T_POW3(L)) + + CBZ_T_MUL (b->Lb, CBZ_T_POW2(L)) + + CBZ_T_MUL (b->Lc, L); + + if (t > CBZ_T_ONE) + t = CBZ_T_ONE; + else if (t < 0) + t = 0; + + return t; +} +#endif + +static gint +_clutter_bezier_t2x (const ClutterBezier * b, _FixedT t) +{ + /* + * NB -- the int coefficients can be at most 8192 for the multiplication + * to work in this fashion due to the limits of the 14.18 fixed. + */ + return ((b->ax*CBZ_T_POW3(t) + b->bx*CBZ_T_POW2(t) + b->cx*t) >> CBZ_T_Q) + + b->dx; +} + +gint +_clutter_bezier_t2y (const ClutterBezier * b, _FixedT t) +{ + /* + * NB -- the int coefficients can be at most 8192 for the multiplication + * to work in this fashion due to the limits of the 14.18 fixed. + */ + return ((b->ay*CBZ_T_POW3(t) + b->by*CBZ_T_POW2(t) + b->cy*t) >> CBZ_T_Q) + + b->dy; +} + +/* + * Advances along the bezier to relative length L and returns the coordinances + * in knot + */ +void +_clutter_bezier_advance (const ClutterBezier *b, gint L, ClutterKnot * knot) +{ +#ifdef CBZ_L2T_INTERPOLATION + _FixedT t = clutter_bezier_L2t (b, L); +#else + _FixedT t = L; +#endif + + knot->x = _clutter_bezier_t2x (b, t); + knot->y = _clutter_bezier_t2y (b, t); + + CLUTTER_NOTE (BEHAVIOUR, "advancing to relative pt %f: t %f, {%d,%d}", + (double) L / (double) CBZ_T_ONE, + (double) t / (double) CBZ_T_ONE, + knot->x, knot->y); +} + +void +_clutter_bezier_init (ClutterBezier *b, + gint x_0, gint y_0, + gint x_1, gint y_1, + gint x_2, gint y_2, + gint x_3, gint y_3) +{ + _FixedT t; + int i; + int xp = x_0; + int yp = y_0; + _FixedT length [CBZ_T_SAMPLES + 1]; + +#ifdef CBZ_L2T_INTERPOLATION + int j, k; + _FixedT L; + _FixedT t_equalized [CBZ_T_SAMPLES + 1]; +#endif + +#if 0 + g_debug ("Initializing bezier at {{%d,%d},{%d,%d},{%d,%d},{%d,%d}}", + x0, y0, x1, y1, x2, y2, x3, y3); +#endif + + b->dx = x_0; + b->dy = y_0; + + b->cx = 3 * (x_1 - x_0); + b->cy = 3 * (y_1 - y_0); + + b->bx = 3 * (x_2 - x_1) - b->cx; + b->by = 3 * (y_2 - y_1) - b->cy; + + b->ax = x_3 - 3 * x_2 + 3 * x_1 - x_0; + b->ay = y_3 - 3 * y_2 + 3 * y_1 - y_0; + +#if 0 + g_debug ("Cooeficients {{%d,%d},{%d,%d},{%d,%d},{%d,%d}}", + b->ax, b->ay, b->bx, b->by, b->cx, b->cy, b->dx, b->dy); +#endif + + /* + * Because of the way we do the multiplication in bezeir_t2x,y + * these coefficients need to be at most 0x1fff; this should be the case, + * I think, but have added this warning to catch any problems -- if it + * triggers, we need to change those two functions a bit. + */ + if (b->ax > 0x1fff || b->bx > 0x1fff || b->cx > 0x1fff) + g_warning ("Calculated coefficents will result in multiplication " + "overflow in clutter_bezier_t2x and clutter_bezier_t2y."); + + /* + * Sample the bezier with CBZ_T_SAMPLES and calculate length at + * each point. + * + * We are working with integers here, so we use the fast sqrti function. + */ + length[0] = 0; + + for (t = CBZ_T_STEP, i = 1; i <= CBZ_T_SAMPLES; ++i, t += CBZ_T_STEP) + { + int x = _clutter_bezier_t2x (b, t); + int y = _clutter_bezier_t2y (b, t); + + guint l = clutter_sqrti ((y - yp)*(y - yp) + (x - xp)*(x - xp)); + + l += length[i-1]; + + length[i] = l; + + xp = x; + yp = y; + } + + b->length = length[CBZ_T_SAMPLES]; + +#if 0 + g_debug ("length %d", b->length); +#endif + +#ifdef CBZ_L2T_INTERPOLATION + /* + * Now normalize the length values, converting them into _FixedT + */ + for (i = 0; i <= CBZ_T_SAMPLES; ++i) + { + length[i] = (length[i] << CBZ_T_Q) / b->length; + } + + /* + * Now generate a L -> t table such that the L will equidistant + * over <0,1> + */ + t_equalized[0] = 0; + + for (i = 1, j = 1, L = CBZ_L_STEP; i < CBZ_T_SAMPLES; ++i, L += CBZ_L_STEP) + { + _FixedT l1, l2; + _FixedT d1, d2, d; + _FixedT t1, t2; + + /* find the band for our L */ + for (k = j; k < CBZ_T_SAMPLES; ++k) + { + if (L < length[k]) + break; + } + + /* + * Now we know that L is from (length[k-1],length[k]> + * We remember k-1 in order not to have to iterate over the + * whole length array in the next iteration of the main loop + */ + j = k - 1; + + /* + * Now interpolate equlised t as a weighted average + */ + l1 = length[k-1]; + l2 = length[k]; + d1 = l2 - L; + d2 = L - l1; + d = l2 - l1; + t1 = (k - 1) * CBZ_T_STEP; + t2 = k * CBZ_T_STEP; + + t_equalized[i] = (t1*d1 + t2*d2)/d; + + if (t_equalized[i] < t_equalized[i-1]) + g_debug ("wrong t: L %f, l1 %f, l2 %f, t1 %f, t2 %f", + (double) (L)/(double)CBZ_T_ONE, + (double) (l1)/(double)CBZ_T_ONE, + (double) (l2)/(double)CBZ_T_ONE, + (double) (t1)/(double)CBZ_T_ONE, + (double) (t2)/(double)CBZ_T_ONE); + + } + + t_equalized[CBZ_T_SAMPLES] = CBZ_T_ONE; + + /* We now fit a bezier -- at this stage, do a single fit through our values + * at 0, 1/3, 2/3 and 1 + * + * FIXME -- do we need to use a better fitting approach to choose the best + * beziere. The actual curve we acquire this way is not too bad shapwise, + * but (probably due to rounding errors) the resulting curve no longer + * satisfies the necessary condition that for L2 > L1, t2 > t1, which + * causes oscilation. + */ + +#if 0 + /* + * These are the control points we use to calculate the curve coefficients + * for bezier t(L); these are not needed directly, but are implied in the + * calculations below. + * + * (p0 is 0,0, and p3 is 1,1) + */ + p1 = (18 * t_equalized[CBZ_T_SAMPLES/3] - + 9 * t_equalized[2*CBZ_T_SAMPLES/3] + + 2 << CBZ_T_Q) / 6; + + p2 = (18 * t_equalized[2*CBZ_T_SAMPLES/3] - + 9 * t_equalized[CBZ_T_SAMPLES/3] - + (5 << CBZ_T_Q)) / 6; +#endif + + b->Lc = (18 * t_equalized[CBZ_T_SAMPLES/3] - + 9 * t_equalized[2*CBZ_T_SAMPLES/3] + + (2 << CBZ_T_Q)) >> 1; + + b->Lb = (36 * t_equalized[2*CBZ_T_SAMPLES/3] - + 45 * t_equalized[CBZ_T_SAMPLES/3] - + (9 << CBZ_T_Q)) >> 1; + + b->La = ((27 * (t_equalized[CBZ_T_SAMPLES/3] - + t_equalized[2*CBZ_T_SAMPLES/3]) + + (7 << CBZ_T_Q)) >> 1) + CBZ_T_ONE; + + g_debug ("t(1/3) %f, t(2/3) %f", + (double)t_equalized[CBZ_T_SAMPLES/3]/(double)CBZ_T_ONE, + (double)t_equalized[2*CBZ_T_SAMPLES/3]/(double)CBZ_T_ONE); + + g_debug ("L -> t coefficients: %f, %f, %f", + (double)b->La/(double)CBZ_T_ONE, + (double)b->Lb/(double)CBZ_T_ONE, + (double)b->Lc/(double)CBZ_T_ONE); + + + /* + * For debugging, you can load these values into a spreadsheet and graph + * them to see how well the approximation matches the data + */ + for (i = 0; i < CBZ_T_SAMPLES; ++i) + { + g_print ("%f, %f, %f\n", + (double)(i*CBZ_T_STEP)/(double)CBZ_T_ONE, + (double)(t_equalized[i])/(double)CBZ_T_ONE, + (double)(clutter_bezier_L2t(b,i*CBZ_T_STEP))/(double)CBZ_T_ONE); + } +#endif +} + +/* + * Moves a control point at indx to location represented by knot + */ +void +_clutter_bezier_adjust (ClutterBezier * b, ClutterKnot * knot, guint indx) +{ + guint x[4], y[4]; + + g_assert (indx < 4); + + x[0] = b->dx; + y[0] = b->dy; + + x[1] = b->cx / 3 + x[0]; + y[1] = b->cy / 3 + y[0]; + + x[2] = b->bx / 3 + b->cx + x[1]; + y[2] = b->by / 3 + b->cy + y[1]; + + x[3] = b->ax + x[0] + b->cx + b->bx; + y[3] = b->ay + y[0] + b->cy + b->by; + + x[indx] = knot->x; + y[indx] = knot->y; + + _clutter_bezier_init (b, x[0], y[0], x[1], y[1], x[2], y[2], x[3], y[3]); +} + +guint +_clutter_bezier_get_length (const ClutterBezier *b) +{ + return b->length; +} diff --git a/clutter/clutter-bezier.h b/clutter/clutter-bezier.h new file mode 100644 index 000000000..df9f61767 --- /dev/null +++ b/clutter/clutter-bezier.h @@ -0,0 +1,65 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Tomas Frydrych + * + * Copyright (C) 2006, 2007 OpenedHand + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#ifndef __CLUTTER_BEZIER_H__ +#define __CLUTTER_BEZIER_H__ + +#include +#include "clutter-types.h" + +G_BEGIN_DECLS + +/* This is used in _clutter_bezier_advance to represent the full + length of the bezier curve. Anything less than that represents a + fraction of the length */ +#define CLUTTER_BEZIER_MAX_LENGTH (1 << 18) + +typedef struct _ClutterBezier ClutterBezier; + +ClutterBezier *_clutter_bezier_new (); + +void _clutter_bezier_free (ClutterBezier * b); + +ClutterBezier *_clutter_bezier_clone_and_move (const ClutterBezier *b, + gint x, + gint y); + +void _clutter_bezier_advance (const ClutterBezier *b, + gint L, + ClutterKnot *knot); + +void _clutter_bezier_init (ClutterBezier *b, + gint x_0, gint y_0, + gint x_1, gint y_1, + gint x_2, gint y_2, + gint x_3, gint y_3); + +void _clutter_bezier_adjust (ClutterBezier *b, + ClutterKnot *knot, + guint indx); + +guint _clutter_bezier_get_length (const ClutterBezier *b); + +G_END_DECLS + +#endif /* __CLUTTER_BEZIER_H__ */ diff --git a/clutter/clutter-effect.c b/clutter/clutter-effect.c index fa8a7bef5..64fd25b4a 100644 --- a/clutter/clutter-effect.c +++ b/clutter/clutter-effect.c @@ -47,7 +47,6 @@ #include "clutter-marshal.h" #include "clutter-private.h" #include "clutter-debug.h" -#include "clutter-behaviour-bspline.h" #include "clutter-behaviour-depth.h" #include "clutter-behaviour-ellipse.h" #include "clutter-behaviour-opacity.h" @@ -658,7 +657,7 @@ clutter_effect_move (ClutterEffectTemplate *template_, gpointer data) { ClutterEffectClosure *c; - ClutterKnot knots[2]; + ClutterPath *path; c = clutter_effect_closure_new (template_, actor, @@ -667,13 +666,14 @@ clutter_effect_move (ClutterEffectTemplate *template_, c->completed_func = func; c->completed_data = data; - knots[0].x = clutter_actor_get_x (actor); - knots[0].y = clutter_actor_get_y (actor); + path = clutter_path_new (); - knots[1].x = x; - knots[1].y = y; + clutter_path_add_move_to (path, + clutter_actor_get_x (actor), + clutter_actor_get_y (actor)); + clutter_path_add_line_to (path, x, y); - c->behave = clutter_behaviour_path_new (c->alpha, knots, 2); + c->behave = clutter_behaviour_path_new (c->alpha, path); clutter_behaviour_apply (c->behave, actor); clutter_timeline_start (c->timeline); @@ -707,18 +707,30 @@ clutter_effect_path (ClutterEffectTemplate *template_, gpointer data) { ClutterEffectClosure *c; + ClutterPath *path; + guint i; c = clutter_effect_closure_new (template_, actor, G_CALLBACK (on_effect_complete)); + path = clutter_path_new (); + c->completed_func = func; c->completed_data = data; - if (n_knots) - clutter_actor_set_position (actor, knots[0].x, knots[0].y); + path = clutter_path_new (); - c->behave = clutter_behaviour_path_new (c->alpha, knots, n_knots); + if (n_knots) + { + clutter_actor_set_position (actor, knots[0].x, knots[0].y); + clutter_path_add_move_to (path, knots[0].x, knots[0].y); + + for (i = 1; i < n_knots; i++) + clutter_path_add_line_to (path, knots[i].x, knots[i].y); + } + + c->behave = clutter_behaviour_path_new (c->alpha, path); clutter_behaviour_apply (c->behave, actor); clutter_timeline_start (c->timeline); diff --git a/clutter/clutter-marshal.list b/clutter/clutter-marshal.list index b83857fe3..3c1ac97b1 100644 --- a/clutter/clutter-marshal.list +++ b/clutter/clutter-marshal.list @@ -10,4 +10,5 @@ VOID:OBJECT,OBJECT,PARAM VOID:OBJECT,POINTER VOID:STRING,BOOLEAN,BOOLEAN VOID:STRING,INT +VOID:UINT VOID:VOID diff --git a/clutter/clutter-path.c b/clutter/clutter-path.c new file mode 100644 index 000000000..76481f417 --- /dev/null +++ b/clutter/clutter-path.c @@ -0,0 +1,1414 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * + * Copyright (C) 2008 Intel Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +/** + * SECTION:clutter-path + * @short_description: An object describing a path with straight lines + * and bezier curves. + * + * A #ClutterPath contains a description of a path consisting of + * straight lines and bezier curves. This can be used in a + * #ClutterBehaviourPath to animate an actor moving along the path. + * + * The path consists of a series of nodes. Each node is one of the + * following four types: + * + * + * %CLUTTER_PATH_MOVE_TO + * + * Changes the position of the path to the given pair of + * coordinates. This is usually used as the first node of a path to + * mark the start position. If it is used in the middle of a path then + * the path will be disjoint and the actor will appear to jump to the + * new position when animated. + * + * %CLUTTER_PATH_LINE_TO + * + * Creates a straight line from the previous point to the given point. + * + * %CLUTTER_PATH_CURVE_TO + * + * Creates a bezier curve. The end of the last node is used as the + * first control point and the three subsequent coordinates given in + * the node as used as the other three. + * + * %CLUTTER_PATH_CLOSE + * + * Creates a straight line from the last node to the last + * %CLUTTER_PATH_MOVE_TO node. This can be used to close a path so + * that it will appear as a loop when animated. + * + * + * + * The first three types have the corresponding relative versions + * %CLUTTER_PATH_REL_MOVE_TO, %CLUTTER_PATH_REL_LINE_TO and + * %CLUTTER_PATH_REL_CURVE_TO. These are exactly the same except the + * coordinates are given relative to the previous node instead of as + * direct screen positions. + * + * You can build a path using the node adding functions such as + * clutter_path_add_line_to(). Alternatively the path can be described + * in a string using a subset of the SVG path syntax. See + * clutter_path_add_string() for details. + * + * #ClutterPath is available since Clutter 1.0 + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include "clutter-path.h" +#include "clutter-types.h" +#include "clutter-bezier.h" +#include "clutter-private.h" +#include "clutter-alpha.h" + +G_DEFINE_TYPE (ClutterPath, clutter_path, G_TYPE_INITIALLY_UNOWNED); + +#define CLUTTER_PATH_GET_PRIVATE(obj) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CLUTTER_TYPE_PATH, \ + ClutterPathPrivate)) + +#define CLUTTER_PATH_NODE_TYPE_IS_VALID(t) \ + ((((t) & ~CLUTTER_PATH_RELATIVE) >= CLUTTER_PATH_MOVE_TO \ + && ((t) & ~CLUTTER_PATH_RELATIVE) <= CLUTTER_PATH_CURVE_TO) \ + || (t) == CLUTTER_PATH_CLOSE) + +enum +{ + PROP_0, + + PROP_DESCRIPTION, + PROP_LENGTH +}; + +typedef struct _ClutterPathNodeFull ClutterPathNodeFull; + +struct _ClutterPathNodeFull +{ + ClutterPathNode k; + + ClutterBezier *bezier; + + guint length; +}; + +struct _ClutterPathPrivate +{ + GSList *nodes, *nodes_tail; + gboolean nodes_dirty; + + guint total_length; +}; + +/* Character tests that don't pay attention to the locale */ +#define clutter_path_isspace(ch) memchr (" \f\n\r\t\v", (ch), 6) +#define clutter_path_isdigit(ch) ((ch) >= '0' && (ch) <= '9') + +static ClutterPathNodeFull *clutter_path_node_full_new (void); +static void clutter_path_node_full_free (ClutterPathNodeFull *node); + +static void clutter_path_finalize (GObject *object); + +static void +clutter_path_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + ClutterPath *path = CLUTTER_PATH (gobject); + + switch (prop_id) + { + case PROP_DESCRIPTION: + g_value_take_string (value, clutter_path_get_description (path)); + break; + case PROP_LENGTH: + g_value_set_uint (value, clutter_path_get_length (path)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_path_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + ClutterPath *path = CLUTTER_PATH (gobject); + + switch (prop_id) + { + case PROP_DESCRIPTION: + if (!clutter_path_set_description (path, g_value_get_string (value))) + g_warning ("Invalid path description"); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + break; + } +} + +static void +clutter_path_class_init (ClutterPathClass *klass) +{ + GObjectClass *gobject_class = (GObjectClass *) klass; + GParamSpec *pspec; + + gobject_class->get_property = clutter_path_get_property; + gobject_class->set_property = clutter_path_set_property; + gobject_class->finalize = clutter_path_finalize; + + pspec = g_param_spec_string ("description", + "Description", + "SVG-style description of the path", + "", + CLUTTER_PARAM_READWRITE); + g_object_class_install_property (gobject_class, PROP_DESCRIPTION, pspec); + + pspec = g_param_spec_uint ("length", + "Length", + "An approximation of the total length " + "of the path.", + 0, G_MAXUINT, 0, + CLUTTER_PARAM_READABLE); + g_object_class_install_property (gobject_class, PROP_LENGTH, pspec); + + g_type_class_add_private (klass, sizeof (ClutterPathPrivate)); +} + +static void +clutter_path_init (ClutterPath *self) +{ + self->priv = CLUTTER_PATH_GET_PRIVATE (self); +} + +static void +clutter_path_finalize (GObject *object) +{ + ClutterPath *self = (ClutterPath *) object; + + clutter_path_clear (self); + + G_OBJECT_CLASS (clutter_path_parent_class)->finalize (object); +} + +/** + * clutter_path_new: + * + * Creates a new #ClutterPath instance with no nodes. + * + * The object has a floating reference so if you add it to a + * #ClutterBehaviourPath then you do not need to unref it. + * + * Return value: the newly created #ClutterPath + * + * Since: 1.0 + */ +ClutterPath * +clutter_path_new (void) +{ + ClutterPath *self = g_object_new (CLUTTER_TYPE_PATH, NULL); + + return self; +} + +/** + * clutter_path_new_with_description: + * @desc: a string describing the path + * + * Creates a new #ClutterPath instance with the nodes described in + * @desc. See clutter_path_add_string() for details of the format of + * the string. + * + * The object has a floating reference so if you add it to a + * #ClutterBehaviourPath then you do not need to unref it. + * + * Return value: the newly created #ClutterPath + * + * Since: 1.0 + */ +ClutterPath * +clutter_path_new_with_description (const gchar *desc) +{ + return g_object_new (CLUTTER_TYPE_PATH, + "description", desc, + NULL); +} + +/** + * clutter_path_clear: + * @path: a #ClutterPath + * + * Removes all nodes from the path. + * + * Since: 1.0 + */ +void +clutter_path_clear (ClutterPath *path) +{ + ClutterPathPrivate *priv = path->priv; + + g_slist_foreach (priv->nodes, (GFunc) clutter_path_node_full_free, NULL); + g_slist_free (priv->nodes); + + priv->nodes = priv->nodes_tail = NULL; + priv->nodes_dirty = TRUE; +} + +/* Takes ownership of the node */ +static void +clutter_path_add_node_full (ClutterPath *path, + ClutterPathNodeFull *node) +{ + ClutterPathPrivate *priv = path->priv; + GSList *new_node; + + new_node = g_slist_prepend (NULL, node); + + if (priv->nodes_tail == NULL) + priv->nodes = new_node; + else + priv->nodes_tail->next = new_node; + + priv->nodes_tail = new_node; + + priv->nodes_dirty = TRUE; +} + +/* Helper function to make the rest of teh add_* functions shorter */ +static void +clutter_path_add_node_helper (ClutterPath *path, + ClutterPathNodeType type, + int num_coords, + ...) +{ + ClutterPathNodeFull *node; + int i; + va_list ap; + + g_return_if_fail (CLUTTER_IS_PATH (path)); + + node = clutter_path_node_full_new (); + + node->k.type = type; + + va_start (ap, num_coords); + + for (i = 0; i < num_coords; i++) + { + node->k.points[i].x = va_arg (ap, gint); + node->k.points[i].y = va_arg (ap, gint); + } + + va_end (ap); + + clutter_path_add_node_full (path, node); +} + +/** + * clutter_path_add_move_to: + * @path: a #ClutterPath + * @x: the x coordinate + * @y: the y coordinate + * + * Adds a #CLUTTER_PATH_MOVE_TO type node to the path. This is usually + * used as the first node in a path. It can also be used in the middle + * of the path to cause the actor to jump to the new coordinate. + * + * Since: 1.0 + */ +void +clutter_path_add_move_to (ClutterPath *path, + gint x, + gint y) +{ + clutter_path_add_node_helper (path, CLUTTER_PATH_MOVE_TO, 1, x, y); +} + +/** + * clutter_path_add_rel_move_to: + * @path: a #ClutterPath + * @x: the x coordinate + * @y: the y coordinate + * + * Same as clutter_path_add_move_to() except the coordinates are + * relative to the previous node. + * + * Since: 1.0 + */ +void +clutter_path_add_rel_move_to (ClutterPath *path, + gint x, + gint y) +{ + clutter_path_add_node_helper (path, CLUTTER_PATH_REL_MOVE_TO, 1, x, y); +} + +/** + * clutter_path_add_line_to: + * @path: a #ClutterPath + * @x: the x coordinate + * @y: the y coordinate + * + * Adds a #CLUTTER_PATH_LINE_TO type node to the path. This causes the + * actor to move to the new coordinates in a straight line. + * + * Since: 1.0 + */ +void +clutter_path_add_line_to (ClutterPath *path, + gint x, + gint y) +{ + clutter_path_add_node_helper (path, CLUTTER_PATH_LINE_TO, 1, x, y); +} + +/** + * clutter_path_add_rel_line_to: + * @path: a #ClutterPath + * @x: the x coordinate + * @y: the y coordinate + * + * Same as clutter_path_add_line_to() except the coordinates are + * relative to the previous node. + * + * Since: 1.0 + */ +void +clutter_path_add_rel_line_to (ClutterPath *path, + gint x, + gint y) +{ + clutter_path_add_node_helper (path, CLUTTER_PATH_REL_LINE_TO, 1, x, y); +} + +/** + * clutter_path_add_curve_to: + * @path: a #ClutterPath + * @x1: the x coordinate of the first control point + * @y1: the y coordinate of the first control point + * @x2: the x coordinate of the second control point + * @y2: the y coordinate of the second control point + * @x3: the x coordinate of the third control point + * @y3: the y coordinate of the third control point + * + * Adds a #CLUTTER_PATH_CURVE_TO type node to the path. This causes + * the actor to follow a bezier from the last node to (x3,y3) using + * (x1,y1) and (x2,y2) as control points. + * + * Since: 1.0 + */ +void +clutter_path_add_curve_to (ClutterPath *path, + gint x1, + gint y1, + gint x2, + gint y2, + gint x3, + gint y3) +{ + clutter_path_add_node_helper (path, CLUTTER_PATH_CURVE_TO, 3, + x1, y1, x2, y2, x3, y3); +} + +/** + * clutter_path_add_rel_curve_to: + * @path: a #ClutterPath + * @x1: the x coordinate of the first control point + * @y1: the y coordinate of the first control point + * @x2: the x coordinate of the second control point + * @y2: the y coordinate of the second control point + * @x3: the x coordinate of the third control point + * @y3: the y coordinate of the third control point + * + * Same as clutter_path_add_curve_to() except the coordinates are + * relative to the previous node. + * + * Since: 1.0 + */ +void +clutter_path_add_rel_curve_to (ClutterPath *path, + gint x1, + gint y1, + gint x2, + gint y2, + gint x3, + gint y3) +{ + clutter_path_add_node_helper (path, CLUTTER_PATH_REL_CURVE_TO, 3, + x1, y1, x2, y2, x3, y3); +} + +/** + * clutter_path_add_close: + * @path: a #ClutterPath + * + * Adds a #CLUTTER_PATH_CLOSE type node to the path. This creates a + * straight line from the last node to the last #CLUTTER_PATH_MOVE_TO + * type node. + * + * Since: 1.0 + */ +void +clutter_path_add_close (ClutterPath *path) +{ + clutter_path_add_node_helper (path, CLUTTER_PATH_CLOSE, 0); +} + +static gboolean +clutter_path_parse_number (const gchar **pin, gboolean allow_comma, gint *ret) +{ + gint val = 0; + gboolean negative = FALSE; + gint digit_count = 0; + const gchar *p = *pin; + + /* Skip leading spaces */ + while (clutter_path_isspace (*p)) + p++; + + /* Optional comma */ + if (allow_comma && *p == ',') + { + p++; + while (clutter_path_isspace (*p)) + p++; + } + + /* Optional sign */ + if (*p == '+') + p++; + else if (*p == '-') + { + negative = TRUE; + p++; + } + + /* Some digits */ + while (clutter_path_isdigit (*p)) + { + val = val * 10 + *p - '0'; + digit_count++; + p++; + } + + /* We need at least one digit */ + if (digit_count < 1) + return FALSE; + + /* Optional fractional part which we ignore */ + if (*p == '.') + { + p++; + digit_count = 0; + while (clutter_path_isdigit (*p)) + { + digit_count++; + p++; + } + /* If there is a fractional part then it also needs at least one + digit */ + if (digit_count < 1) + return FALSE; + } + + *pin = p; + *ret = negative ? -val : val; + + return TRUE; +} + +static gboolean +clutter_path_parse_description (const gchar *p, GSList **ret) +{ + GSList *nodes = NULL; + ClutterPathNodeFull *node; + + while (TRUE) + { + /* Skip leading whitespace */ + while (clutter_path_isspace (*p)) + p++; + + /* It is not an error to end now */ + if (*p == '\0') + break; + + switch (*p) + { + case 'M': + case 'm': + case 'L': + case 'l': + node = clutter_path_node_full_new (); + nodes = g_slist_prepend (nodes, node); + + node->k.type = (*p == 'M' ? CLUTTER_PATH_MOVE_TO + : *p == 'm' ? CLUTTER_PATH_REL_MOVE_TO + : *p == 'L' ? CLUTTER_PATH_LINE_TO + : CLUTTER_PATH_REL_LINE_TO); + p++; + + if (!clutter_path_parse_number (&p, FALSE, &node->k.points[0].x) + || !clutter_path_parse_number (&p, TRUE, &node->k.points[0].y)) + goto fail; + break; + + case 'C': + case 'c': + node = clutter_path_node_full_new (); + nodes = g_slist_prepend (nodes, node); + + node->k.type = (*p == 'C' ? CLUTTER_PATH_CURVE_TO + : CLUTTER_PATH_REL_CURVE_TO); + p++; + + if (!clutter_path_parse_number (&p, FALSE, &node->k.points[0].x) + || !clutter_path_parse_number (&p, TRUE, &node->k.points[0].y) + || !clutter_path_parse_number (&p, TRUE, &node->k.points[1].x) + || !clutter_path_parse_number (&p, TRUE, &node->k.points[1].y) + || !clutter_path_parse_number (&p, TRUE, &node->k.points[2].x) + || !clutter_path_parse_number (&p, TRUE, &node->k.points[2].y)) + goto fail; + break; + + case 'Z': + case 'z': + node = clutter_path_node_full_new (); + nodes = g_slist_prepend (nodes, node); + p++; + + node->k.type = CLUTTER_PATH_CLOSE; + break; + + default: + goto fail; + } + } + + *ret = g_slist_reverse (nodes); + return TRUE; + + fail: + g_slist_foreach (nodes, (GFunc) clutter_path_node_full_free, NULL); + g_slist_free (nodes); + return FALSE; +} + +/* Takes ownership of the node list */ +static void +clutter_path_add_nodes (ClutterPath *path, GSList *nodes) +{ + ClutterPathPrivate *priv = path->priv; + + if (priv->nodes_tail == NULL) + priv->nodes = nodes; + else + priv->nodes_tail->next = nodes; + + while (nodes) + { + priv->nodes_tail = nodes; + nodes = nodes->next; + } + + priv->nodes_dirty = TRUE; +} + +/** + * clutter_path_add_string: + * @path: a #ClutterPath + * @str: a string describing the new nodes + * + * Adds new nodes to the end of the path as described in @str. The + * format is a subset of the SVG path format. Each node is represented + * by a letter and is followed by zero, one or three pairs of + * coordinates. The coordinates can be separated by spaces or a + * comma. The types are: + * + * + * M + * + * Adds a %CLUTTER_PATH_MOVE_TO node. Takes one pair of coordinates. + * + * L + * + * Adds a %CLUTTER_PATH_LINE_TO node. Takes one pair of coordinates. + * + * C + * + * Adds a %CLUTTER_PATH_CURVE_TO node. Takes three pairs of coordinates. + * + * z + * + * Adds a %CLUTTER_PATH_CLOSE node. No coordinates are needed. + * + * + * + * The M, L and C commands can also be specified in lower case which + * means the coordinates are relative to the previous node. + * + * For example, to move an actor in a 100 by 100 pixel square centered + * on the point 300,300 you could use the following path: + * + * + * + * M 250,350 l 0 -100 L 350,250 l 0 100 z + * + * + * + * If the path description isn't valid %FALSE will be returned and no + * nodes will be added. + * + * Return value: %TRUE is the path description was valid or %FALSE + * otherwise. + * + * Since: 1.0 + */ +gboolean +clutter_path_add_string (ClutterPath *path, const gchar *str) +{ + GSList *nodes; + + g_return_val_if_fail (CLUTTER_IS_PATH (path), FALSE); + + if (clutter_path_parse_description (str, &nodes)) + { + clutter_path_add_nodes (path, nodes); + + return TRUE; + } + else + return FALSE; +} + +/** + * clutter_path_add_node: + * @path: a #ClutterPath + * @node: a #ClutterPathNode + * + * Adds @node to the end of the path. + * + * Since: 1.0 + */ +void +clutter_path_add_node (ClutterPath *path, + const ClutterPathNode *node) +{ + ClutterPathNodeFull *node_full; + + g_return_if_fail (CLUTTER_IS_PATH (path)); + g_return_if_fail (node != NULL); + g_return_if_fail (CLUTTER_PATH_NODE_TYPE_IS_VALID (node->type)); + + node_full = clutter_path_node_full_new (); + node_full->k = *node; + + clutter_path_add_node_full (path, node_full); +} + +/** + * clutter_path_get_n_nodes: + * @path: a #ClutterPath + * + * Retrieves the number of nodes in the path. + * + * Return value: the number of nodes. + * + * Since: 1.0 + */ +guint +clutter_path_get_n_nodes (ClutterPath *path) +{ + ClutterPathPrivate *priv; + + g_return_val_if_fail (CLUTTER_IS_PATH (path), 0); + + priv = path->priv; + + return g_slist_length (priv->nodes); +} + +/** + * clutter_path_get_node: + * @path: a #ClutterPath + * @index: the node number to retrieve + * @node: a location to store a copy of the node + * + * Retrieves the node of the path indexed by @index. + * + * Since: 1.0 + */ +void +clutter_path_get_node (ClutterPath *path, + guint index, + ClutterPathNode *node) +{ + ClutterPathNodeFull *node_full; + ClutterPathPrivate *priv; + + g_return_if_fail (CLUTTER_IS_PATH (path)); + + priv = path->priv; + + node_full = g_slist_nth_data (priv->nodes, index); + + g_return_if_fail (node_full != NULL); + + *node = node_full->k; +} + +/** + * clutter_path_get_nodes: + * @path: a #ClutterPath + * + * Returns a #GSList of #ClutterPathNodes. The list should be + * freed with g_slist_free(). The nodes are owned by the path and + * should not be freed. Altering the path may cause the nodes in the + * list to become invalid so you should copy them if you want to keep + * the list. + * + * Return value: a list of nodes in the path. + * + * Since: 1.0 + */ +GSList * +clutter_path_get_nodes (ClutterPath *path) +{ + ClutterPathPrivate *priv; + + g_return_val_if_fail (CLUTTER_IS_PATH (path), NULL); + + priv = path->priv; + + return g_slist_copy (priv->nodes); +} + +/** + * clutter_path_foreach: + * @path: a #ClutterPath + * @callback: the function to call with each node + * @user_data: user data to pass to the function + * + * Calls a function for each node of the path. + * + * Since: 1.0 + */ +void +clutter_path_foreach (ClutterPath *path, + ClutterPathCallback callback, + gpointer user_data) +{ + ClutterPathPrivate *priv; + + g_return_if_fail (CLUTTER_IS_PATH (path)); + + priv = path->priv; + + g_slist_foreach (priv->nodes, (GFunc) callback, user_data); +} + +/** + * clutter_path_insert_node: + * @path: a #ClutterPath + * @index: offset of where to insert the node + * @node: the node to insert + * + * Inserts @node into the path before the node at the given offset. If + * @index is negative it will append the node to the end of the path. + * + * Since: 1.0 + */ +void +clutter_path_insert_node (ClutterPath *path, + gint index, + const ClutterPathNode *node) +{ + ClutterPathPrivate *priv; + ClutterPathNodeFull *node_full; + + g_return_if_fail (CLUTTER_IS_PATH (path)); + g_return_if_fail (node != NULL); + g_return_if_fail (CLUTTER_PATH_NODE_TYPE_IS_VALID (node->type)); + + priv = path->priv; + + node_full = clutter_path_node_full_new (); + node_full->k = *node; + + priv->nodes = g_slist_insert (priv->nodes, node_full, index); + + if (priv->nodes_tail == NULL) + priv->nodes_tail = priv->nodes; + else if (priv->nodes_tail->next) + priv->nodes_tail = priv->nodes_tail->next; + + priv->nodes_dirty = TRUE; +} + +/** + * clutter_path_remove_node: + * @path: a #ClutterPath + * @index: index of the node to remove + * + * Removes the node at the given offset from the path. + * + * Since: 1.0 + */ +void +clutter_path_remove_node (ClutterPath *path, + guint index) +{ + ClutterPathPrivate *priv; + GSList *node, *prev = NULL; + + g_return_if_fail (CLUTTER_IS_PATH (path)); + + priv = path->priv; + + for (node = priv->nodes; node && index--; node = node->next) + prev = node; + + if (node) + { + clutter_path_node_full_free (node->data); + + if (prev) + prev->next = node->next; + else + priv->nodes = node->next; + + if (node == priv->nodes_tail) + priv->nodes_tail = prev; + + g_slist_free_1 (node); + + priv->nodes_dirty = TRUE; + } +} + +/** + * clutter_path_replace_node: + * @path: a #ClutterPath + * @index: index to the existing node + * @node: the replacement node + * + * Replaces the node at offset @index with @node. + * + * Since: 1.0 + */ +void +clutter_path_replace_node (ClutterPath *path, + guint index, + const ClutterPathNode *node) +{ + ClutterPathPrivate *priv; + ClutterPathNodeFull *node_full; + + g_return_if_fail (CLUTTER_IS_PATH (path)); + g_return_if_fail (node != NULL); + g_return_if_fail (CLUTTER_PATH_NODE_TYPE_IS_VALID (node->type)); + + priv = path->priv; + + if ((node_full = g_slist_nth_data (priv->nodes, index))) + { + node_full->k = *node; + + priv->nodes_dirty = TRUE; + } +} + +/** + * clutter_path_set_description: + * @path: a #ClutterPath + * @str: a string describing the path + * + * Replaces all of the nodes in the path with nodes described by + * @str. See clutter_path_add_string() for details of the format. + * + * If the string is invalid then %FALSE is returned and the path is + * unaltered. + * + * Return value: %TRUE is the path was valid, %FALSE otherwise. + * + * Since: 1.0 + */ +gboolean +clutter_path_set_description (ClutterPath *path, const gchar *str) +{ + GSList *nodes; + + g_return_val_if_fail (CLUTTER_IS_PATH (path), FALSE); + + if (clutter_path_parse_description (str, &nodes)) + { + clutter_path_clear (path); + clutter_path_add_nodes (path, nodes); + + return TRUE; + } + else + return FALSE; +} + +/** + * clutter_path_get_description: + * @path: a #ClutterPath + * + * Returns a newly allocated string describing the path in the same + * format as used by clutter_path_add_string(). + * + * Return value: a string description of the path. Free with g_free(). + * + * Since: 1.0 + */ +gchar * +clutter_path_get_description (ClutterPath *path) +{ + ClutterPathPrivate *priv; + GString *str; + GSList *l; + + g_return_val_if_fail (CLUTTER_IS_PATH (path), FALSE); + + priv = path->priv; + + str = g_string_new (""); + + for (l = priv->nodes; l; l = l->next) + { + ClutterPathNodeFull *node = l->data; + gchar letter = '?'; + gint params = 0; + gint i; + + switch (node->k.type) + { + case CLUTTER_PATH_MOVE_TO: + letter = 'M'; + params = 1; + break; + + case CLUTTER_PATH_REL_MOVE_TO: + letter = 'm'; + params = 1; + break; + + case CLUTTER_PATH_LINE_TO: + letter = 'L'; + params = 1; + break; + + case CLUTTER_PATH_REL_LINE_TO: + letter = 'l'; + params = 1; + break; + + case CLUTTER_PATH_CURVE_TO: + letter = 'C'; + params = 3; + break; + + case CLUTTER_PATH_REL_CURVE_TO: + letter = 'c'; + params = 3; + break; + + case CLUTTER_PATH_CLOSE: + letter = 'z'; + params = 0; + break; + } + + if (str->len > 0) + g_string_append_c (str, ' '); + g_string_append_c (str, letter); + + for (i = 0; i < params; i++) + g_string_append_printf (str, " %i %i", + node->k.points[i].x, + node->k.points[i].y); + } + + return g_string_free (str, FALSE); +} + +static gint +clutter_path_node_distance (const ClutterKnot *start, + const ClutterKnot *end) +{ + gint t; + + g_return_val_if_fail (start != NULL, 0); + g_return_val_if_fail (end != NULL, 0); + + if (clutter_knot_equal (start, end)) + return 0; + + t = (end->x - start->x) * (end->x - start->x) + + (end->y - start->y) * (end->y - start->y); + + /* + * If we are using limited precision sqrti implementation, fallback on + * clib sqrt if the precission would be less than 10% + */ +#if INT_MAX > CLUTTER_SQRTI_ARG_10_PERCENT + if (t <= COGL_SQRTI_ARG_10_PERCENT) + return cogl_sqrti (t); + else + return COGL_FLOAT_TO_INT (sqrt(t)); +#else + return cogl_sqrti (t); +#endif +} + +static void +clutter_path_ensure_node_data (ClutterPath *path) +{ + ClutterPathPrivate *priv = path->priv; + + /* Recalculate the nodes data if has changed */ + if (priv->nodes_dirty) + { + GSList *l; + ClutterKnot last_position = { 0, 0 }; + ClutterKnot loop_start = { 0, 0 }; + ClutterKnot points[3]; + + priv->total_length = 0; + + for (l = priv->nodes; l; l = l->next) + { + ClutterPathNodeFull *node = l->data; + gboolean relative = (node->k.type & CLUTTER_PATH_RELATIVE) != 0; + + switch (node->k.type & ~CLUTTER_PATH_RELATIVE) + { + case CLUTTER_PATH_MOVE_TO: + node->length = 0; + + /* Store the actual position in point[1] */ + if (relative) + { + node->k.points[1].x = last_position.x + node->k.points[0].x; + node->k.points[1].y = last_position.y + node->k.points[0].y; + } + else + node->k.points[1] = node->k.points[0]; + + last_position = node->k.points[1]; + loop_start = node->k.points[1]; + break; + + case CLUTTER_PATH_LINE_TO: + /* Use point[1] as the start point and point[2] as the end + point */ + node->k.points[1] = last_position; + + if (relative) + { + node->k.points[2].x = (node->k.points[1].x + + node->k.points[0].x); + node->k.points[2].y = (node->k.points[1].y + + node->k.points[0].y); + } + else + node->k.points[2] = node->k.points[0]; + + last_position = node->k.points[2]; + + node->length = clutter_path_node_distance (node->k.points + 1, + node->k.points + 2); + break; + + case CLUTTER_PATH_CURVE_TO: + /* Convert to a bezier curve */ + if (node->bezier == NULL) + node->bezier = _clutter_bezier_new (); + + if (relative) + { + int i; + + for (i = 0; i < 3; i++) + { + points[i].x = last_position.x + node->k.points[i].x; + points[i].y = last_position.y + node->k.points[i].y; + } + } + else + memcpy (points, node->k.points, sizeof (ClutterKnot) * 3); + + _clutter_bezier_init (node->bezier, + last_position.x, last_position.y, + points[0].x, points[0].y, + points[1].x, points[1].y, + points[2].x, points[2].y); + + last_position = points[2]; + + node->length = _clutter_bezier_get_length (node->bezier); + + break; + + case CLUTTER_PATH_CLOSE: + /* Convert to a line to from last_point to loop_start */ + node->k.points[1] = last_position; + node->k.points[2] = loop_start; + last_position = node->k.points[2]; + + node->length = clutter_path_node_distance (node->k.points + 1, + node->k.points + 2); + break; + } + + priv->total_length += node->length; + } + + priv->nodes_dirty = FALSE; + } +} + +/** + * clutter_path_get_position: + * @path: a #ClutterPath + * @alpha: an alpha value + * @position: location to store the position + * + * The value in @alpha represents a position along the path where 0 is + * the beginning and %CLUTTER_ALPHA_MAX_ALPHA is the end of the + * path. An interpolated position is then stored in @position. + * + * Return value: index of the node used to calculate the position. + * + * Since: 1.0 + */ +guint +clutter_path_get_position (ClutterPath *path, + guint alpha, + ClutterKnot *position) +{ + ClutterPathPrivate *priv; + GSList *l; + guint length = 0, node_num = 0; + ClutterPathNodeFull *node; + + g_return_val_if_fail (CLUTTER_IS_PATH (path), 0); + g_return_val_if_fail (alpha <= CLUTTER_ALPHA_MAX_ALPHA, 0); + + priv = path->priv; + + clutter_path_ensure_node_data (path); + + /* Special case if the path is empty, just return 0,0 for want of + something better */ + if (priv->nodes == NULL) + { + memset (position, 0, sizeof (ClutterKnot)); + return 0; + } + + /* Convert the alpha fraction to a length along the path */ + alpha = (alpha * priv->total_length) / CLUTTER_ALPHA_MAX_ALPHA; + + /* Find the node that covers this point */ + for (l = priv->nodes; + l->next && alpha >= (((ClutterPathNodeFull *) l->data)->length + + length); + l = l->next) + { + length += ((ClutterPathNodeFull *) l->data)->length; + node_num++; + } + + node = l->data; + + /* Convert the alpha to a distance along the node */ + alpha -= length; + if (alpha > node->length) + alpha = node->length; + + switch (node->k.type & ~CLUTTER_PATH_RELATIVE) + { + case CLUTTER_PATH_MOVE_TO: + *position = node->k.points[1]; + break; + + case CLUTTER_PATH_LINE_TO: + case CLUTTER_PATH_CLOSE: + if (node->length == 0) + *position = node->k.points[1]; + else + { + position->x = (node->k.points[1].x + + ((node->k.points[2].x - node->k.points[1].x) + * (gint) alpha / (gint) node->length)); + position->y = (node->k.points[1].y + + ((node->k.points[2].y - node->k.points[1].y) + * (gint) alpha / (gint) node->length)); + } + break; + + case CLUTTER_PATH_CURVE_TO: + _clutter_bezier_advance (node->bezier, + alpha * CLUTTER_BEZIER_MAX_LENGTH + / node->length, + position); + break; + } + + return node_num; +} + +/** + * clutter_path_get_length: + * @path: a #ClutterPath + * + * Retrieves an approximation of the total length of the path. + * + * Return value: the length of the path. + * + * Since: 1.0 + */ +guint +clutter_path_get_length (ClutterPath *path) +{ + g_return_val_if_fail (CLUTTER_IS_PATH (path), 0); + + clutter_path_ensure_node_data (path); + + return path->priv->total_length; +} + +static ClutterPathNodeFull * +clutter_path_node_full_new (void) +{ + return g_slice_new0 (ClutterPathNodeFull); +} + +static void +clutter_path_node_full_free (ClutterPathNodeFull *node) +{ + if (node->bezier) + _clutter_bezier_free (node->bezier); + + g_slice_free (ClutterPathNodeFull, node); +} + +/** + * clutter_path_node_copy: + * @node: a #ClutterPathNode + * + * Makes an allocated copy of a node. + * + * Return value: the copied node. + * + * Since: 1.0 + */ +ClutterPathNode * +clutter_path_node_copy (const ClutterPathNode *node) +{ + return g_slice_dup (ClutterPathNode, node); +} + +/** + * clutter_path_node_free: + * @node: a #ClutterPathNode + * + * Frees the memory of an allocated node. + * + * Since: 1.0 + */ +void +clutter_path_node_free (ClutterPathNode *node) +{ + if (G_LIKELY (node)) + g_slice_free (ClutterPathNode, node); +} + +/** + * clutter_path_node_equal: + * @node_a: First node + * @node_b: Second node + * + * Compares two nodes and checks if they are the same type with the + * same coordinates. + * + * Return value: %TRUE if the nodes are the same. + * + * Since: 1.0 + */ +gboolean +clutter_path_node_equal (const ClutterPathNode *node_a, + const ClutterPathNode *node_b) +{ + guint n_points, i; + + g_return_val_if_fail (node_a != NULL, FALSE); + g_return_val_if_fail (node_b != NULL, FALSE); + + if (node_a->type != node_b->type) + return FALSE; + + switch (node_a->type & ~CLUTTER_PATH_RELATIVE) + { + case CLUTTER_PATH_MOVE_TO: n_points = 1; break; + case CLUTTER_PATH_LINE_TO: n_points = 1; break; + case CLUTTER_PATH_CURVE_TO: n_points = 3; break; + case CLUTTER_PATH_CLOSE: n_points = 0; break; + default: return FALSE; + } + + for (i = 0; i < n_points; i++) + if (node_a->points[i].x != node_b->points[i].x + || node_a->points[i].y != node_b->points[i].y) + return FALSE; + + return TRUE; +} + +GType +clutter_path_node_get_type (void) +{ + static GType our_type = 0; + + if (G_UNLIKELY (!our_type)) + { + our_type = + g_boxed_type_register_static (I_("ClutterPathNode"), + (GBoxedCopyFunc) clutter_path_node_copy, + (GBoxedFreeFunc) clutter_path_node_free); + } + + return our_type; +} diff --git a/clutter/clutter-path.h b/clutter/clutter-path.h new file mode 100644 index 000000000..08ab77f6b --- /dev/null +++ b/clutter/clutter-path.h @@ -0,0 +1,245 @@ +/* + * Clutter. + * + * An OpenGL based 'interactive canvas' library. + * + * Authored By Matthew Allum + * + * Copyright (C) 2008 Intel Corporation + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + */ + +#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __CLUTTER_PATH_H__ +#define __CLUTTER_PATH_H__ + +#include +#include + +G_BEGIN_DECLS + +#define CLUTTER_TYPE_PATH \ + (clutter_path_get_type()) +#define CLUTTER_PATH(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + CLUTTER_TYPE_PATH, \ + ClutterPath)) +#define CLUTTER_PATH_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + CLUTTER_TYPE_PATH, \ + ClutterPathClass)) +#define CLUTTER_IS_PATH(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + CLUTTER_TYPE_PATH)) +#define CLUTTER_IS_PATH_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), \ + CLUTTER_TYPE_PATH)) +#define CLUTTER_PATH_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + CLUTTER_TYPE_PATH, \ + ClutterPathClass)) + +#define CLUTTER_TYPE_PATH_NODE (clutter_path_node_get_type ()) + +#define CLUTTER_PATH_RELATIVE 32 + +/** + * ClutterPathNodeType: + * @CLUTTER_PATH_MOVE_TO: jump to the given position + * @CLUTTER_PATH_LINE_TO: create a line from the last node to the + * given position + * @CLUTTER_PATH_CURVE_TO: bezier curve using the last position and + * three control points. + * @CLUTTER_PATH_CLOSE: create a line from the last node to the last + * %CLUTTER_PATH_MOVE_TO node. + * @CLUTTER_PATH_REL_MOVE_TO: same as %CLUTTER_PATH_MOVE_TO but with + * coordinates relative to the last node. + * @CLUTTER_PATH_REL_LINE_TO: same as %CLUTTER_PATH_LINE_TO but with + * coordinates relative to the last node. + * @CLUTTER_PATH_REL_CURVE_TO: same as %CLUTTER_PATH_CURVE_TO but with + * coordinates relative to the last node. + */ +typedef enum { + CLUTTER_PATH_MOVE_TO = 0, + CLUTTER_PATH_LINE_TO = 1, + CLUTTER_PATH_CURVE_TO = 2, + CLUTTER_PATH_CLOSE = 3, + + CLUTTER_PATH_REL_MOVE_TO = CLUTTER_PATH_MOVE_TO | CLUTTER_PATH_RELATIVE, + CLUTTER_PATH_REL_LINE_TO = CLUTTER_PATH_LINE_TO | CLUTTER_PATH_RELATIVE, + CLUTTER_PATH_REL_CURVE_TO = CLUTTER_PATH_CURVE_TO | CLUTTER_PATH_RELATIVE +} ClutterPathNodeType; + +typedef struct _ClutterPath ClutterPath; +typedef struct _ClutterPathClass ClutterPathClass; +typedef struct _ClutterPathPrivate ClutterPathPrivate; +typedef struct _ClutterPathNode ClutterPathNode; + +/** + * ClutterPathCallback: + * @node: the node + * @data: optional data passed to the function + * + * This function is passed to clutter_path_foreach() and will be + * called for each node contained in the path. + * + * Since: 1.0 + */ +typedef void (* ClutterPathCallback) (const ClutterPathNode *node, + gpointer data); + +/** + * ClutterPathClass: + * + * The #ClutterPathClass struct contains only private data. + */ +struct _ClutterPathClass +{ + /*< private >*/ + GInitiallyUnownedClass parent_class; +}; + +/** + * ClutterPath: + * + * The #ClutterPath struct contains only private data and should + * be accessed with the functions below. + */ +struct _ClutterPath +{ + /*< private >*/ + GInitiallyUnowned parent; + + ClutterPathPrivate *priv; +}; + +/** + * ClutterPathNode: + * @type: the node's type + * @points: the coordinates of the node + * + * Represents a single node of a #ClutterPath. + * + * Some of the coordinates in @points may be unused for some node + * types. %CLUTTER_PATH_MOVE_TO and %CLUTTER_PATH_LINE_TO use only two + * pairs of coordinates, %CLUTTER_PATH_CURVE_TO uses all three and + * %CLUTTER_PATH_CLOSE uses none. + * + * Since: 1.0 + */ +struct _ClutterPathNode +{ + ClutterPathNodeType type; + + ClutterKnot points[3]; +}; + +GType clutter_path_get_type (void) G_GNUC_CONST; + +ClutterPath *clutter_path_new (void); + +ClutterPath *clutter_path_new_with_description (const gchar *desc); + +void clutter_path_add_move_to (ClutterPath *path, + gint x, + gint y); + +void clutter_path_add_rel_move_to (ClutterPath *path, + gint x, + gint y); + +void clutter_path_add_line_to (ClutterPath *path, + gint x, + gint y); + +void clutter_path_add_rel_line_to (ClutterPath *path, + gint x, + gint y); + +void clutter_path_add_curve_to (ClutterPath *path, + gint x1, + gint y1, + gint x2, + gint y2, + gint x3, + gint y3); + +void clutter_path_add_rel_curve_to (ClutterPath *path, + gint x1, + gint y1, + gint x2, + gint y2, + gint x3, + gint y3); + +void clutter_path_add_close (ClutterPath *path); + +gboolean clutter_path_add_string (ClutterPath *path, + const gchar *str); + +void clutter_path_add_node (ClutterPath *path, + const ClutterPathNode *node); + +guint clutter_path_get_n_nodes (ClutterPath *path); + +void clutter_path_get_node (ClutterPath *path, + guint index, + ClutterPathNode *node); + +GSList *clutter_path_get_nodes (ClutterPath *path); + +void clutter_path_foreach (ClutterPath *path, + ClutterPathCallback callback, + gpointer user_data); + +void clutter_path_insert_node (ClutterPath *path, + gint index, + const ClutterPathNode *node); + +void clutter_path_remove_node (ClutterPath *path, + guint index); + +void clutter_path_replace_node (ClutterPath *path, + guint index, + const ClutterPathNode *node); + +gchar *clutter_path_get_description (ClutterPath *path); + +gboolean clutter_path_set_description (ClutterPath *path, + const gchar *str); + +void clutter_path_clear (ClutterPath *path); + +guint clutter_path_get_position (ClutterPath *path, + guint alpha, + ClutterKnot *position); + +guint clutter_path_get_length (ClutterPath *path); + +ClutterPathNode *clutter_path_node_copy (const ClutterPathNode *node); + +void clutter_path_node_free (ClutterPathNode *node); + +gboolean clutter_path_node_equal (const ClutterPathNode *node_a, + const ClutterPathNode *node_b); + +GType clutter_path_node_get_type (void); + +G_END_DECLS + +#endif /* __CLUTTER_PATH_H__ */ diff --git a/clutter/clutter.h b/clutter/clutter.h index df07f844b..25e8deda5 100644 --- a/clutter/clutter.h +++ b/clutter/clutter.h @@ -35,7 +35,6 @@ #include "clutter-color.h" #include "clutter-container.h" #include "clutter-behaviour.h" -#include "clutter-behaviour-bspline.h" #include "clutter-behaviour-depth.h" #include "clutter-behaviour-ellipse.h" #include "clutter-behaviour-opacity.h" @@ -58,6 +57,7 @@ #include "clutter-main.h" #include "clutter-media.h" #include "clutter-model.h" +#include "clutter-path.h" #include "clutter-stage.h" #include "clutter-stage-manager.h" #include "clutter-texture.h" diff --git a/doc/reference/clutter/clutter-animation-tutorial.xml b/doc/reference/clutter/clutter-animation-tutorial.xml index cb3797d2a..c12b64122 100644 --- a/doc/reference/clutter/clutter-animation-tutorial.xml +++ b/doc/reference/clutter/clutter-animation-tutorial.xml @@ -409,10 +409,6 @@ main (int argc, char *argv[]) - - #ClutterBehaviourBspline - Moves actors along a B-spline path - #ClutterBehaviourDepth Changes the depth of actors diff --git a/doc/reference/clutter/clutter-docs.xml b/doc/reference/clutter/clutter-docs.xml index ff98eb8ef..829cde4d5 100644 --- a/doc/reference/clutter/clutter-docs.xml +++ b/doc/reference/clutter/clutter-docs.xml @@ -91,11 +91,11 @@ Behaviours - + diff --git a/doc/reference/clutter/clutter-sections.txt b/doc/reference/clutter/clutter-sections.txt index c41f49306..9d5a2912f 100644 --- a/doc/reference/clutter/clutter-sections.txt +++ b/doc/reference/clutter/clutter-sections.txt @@ -660,13 +660,10 @@ clutter_timeline_get_type ClutterBehaviourPath ClutterBehaviourPathClass clutter_behaviour_path_new -clutter_behaviour_path_get_knots -clutter_behaviour_path_append_knot -clutter_behaviour_path_append_knots -clutter_behaviour_path_insert_knot -clutter_behaviour_path_remove_knot -clutter_behaviour_path_clear - +clutter_behaviour_path_new_with_description +clutter_behaviour_path_new_with_knots +clutter_behaviour_path_set_path +clutter_behaviour_path_get_path ClutterKnot clutter_knot_copy @@ -687,6 +684,44 @@ clutter_knot_get_type clutter_behaviour_path_get_type +
+clutter-path +ClutterPath +ClutterPath +ClutterPathClass +ClutterPathCallback +ClutterPathNodeType +clutter_path_new +clutter_path_new_with_description +clutter_path_add_move_to +clutter_path_add_rel_move_to +clutter_path_add_line_to +clutter_path_add_rel_line_to +clutter_path_add_curve_to +clutter_path_add_rel_curve_to +clutter_path_add_close +clutter_path_add_string +clutter_path_add_node +clutter_path_get_n_nodes +clutter_path_get_node +clutter_path_get_nodes +clutter_path_foreach +clutter_path_insert_node +clutter_path_remove_node +clutter_path_replace_node +clutter_path_get_description +clutter_path_set_description +clutter_path_clear +clutter_path_get_position +clutter_path_get_length + + +ClutterPathNode +clutter_path_node_copy +clutter_path_node_free +clutter_path_node_equal +
+
clutter-behaviour-opacity ClutterBehaviourOpacity @@ -762,33 +797,6 @@ ClutterBehaviourScalePrivate clutter_behaviour_scale_get_type
-
-clutter-behaviour-bspline -ClutterBehaviourBspline -ClutterBehaviourBspline -ClutterBehaviourBsplineClass -clutter_behaviour_bspline_new -clutter_behaviour_bspline_append_knot -clutter_behaviour_bspline_append_knots -clutter_behaviour_bspline_truncate -clutter_behaviour_bspline_join -clutter_behaviour_bspline_split -clutter_behaviour_bspline_clear -clutter_behaviour_bspline_adjust -clutter_behaviour_bspline_set_origin -clutter_behaviour_bspline_get_origin - -CLUTTER_TYPE_BEHAVIOUR_BSPLINE -CLUTTER_BEHAVIOUR_BSPLINE -CLUTTER_BEHAVIOUR_BSPLINE_CLASS -CLUTTER_IS_BEHAVIOUR_BSPLINE -CLUTTER_IS_BEHAVIOUR_BSPLINE_CLASS -CLUTTER_BEHAVIOUR_BSPLINE_GET_CLASS - -ClutterBehaviourBsplinePrivate -clutter_behaviour_bspline_get_type -
-
clutter-behaviour-ellipse ClutterBehaviourEllipse diff --git a/doc/reference/clutter/clutter.types b/doc/reference/clutter/clutter.types index 7846d1cac..2e660766c 100644 --- a/doc/reference/clutter/clutter.types +++ b/doc/reference/clutter/clutter.types @@ -11,11 +11,11 @@ clutter_timeline_get_type clutter_media_get_type clutter_behaviour_get_type clutter_alpha_get_type -clutter_behaviour_bspline_get_type clutter_behaviour_depth_get_type clutter_behaviour_ellipse_get_type clutter_behaviour_opacity_get_type clutter_behaviour_path_get_type +clutter_path_get_type clutter_behaviour_rotate_get_type clutter_behaviour_scale_get_type clutter_backend_get_type diff --git a/tests/conform/Makefile.am b/tests/conform/Makefile.am index 9706b8a03..ed6dd00e7 100644 --- a/tests/conform/Makefile.am +++ b/tests/conform/Makefile.am @@ -13,6 +13,7 @@ test_conformance_SOURCES = \ test-mesh-contiguous.c \ test-mesh-interleved.c \ test-mesh-mutability.c \ + test-path.c \ test-pick.c \ test-label-cache.c \ test-clutter-entry.c \ diff --git a/tests/conform/test-conform-main.c b/tests/conform/test-conform-main.c index 1f6e2c811..611bb48e5 100644 --- a/tests/conform/test-conform-main.c +++ b/tests/conform/test-conform-main.c @@ -105,5 +105,7 @@ main (int argc, char **argv) TEST_CONFORM_SIMPLE ("/texture", test_backface_culling); + TEST_CONFORM_SIMPLE ("/path", test_path); + return g_test_run (); } diff --git a/tests/conform/test-path.c b/tests/conform/test-path.c new file mode 100644 index 000000000..0933917a4 --- /dev/null +++ b/tests/conform/test-path.c @@ -0,0 +1,609 @@ +#include +#include +#include + +#include "test-conform-common.h" + +#define MAX_NODES 128 + +#define FLOAT_FUZZ_AMOUNT 5.0f + +typedef struct _CallbackData CallbackData; + +typedef gboolean (* PathTestFunc) (CallbackData *data); + +static void compare_node (const ClutterPathNode *node, gpointer data_p); + +struct _CallbackData +{ + ClutterPath *path; + + guint n_nodes; + ClutterPathNode nodes[MAX_NODES]; + + gboolean nodes_different; + guint nodes_found; +}; + +static const char path_desc[] = + "M 21 22 m 23 24 " + "L 25 26 l 27 28 " + "C 29 30 31 32 33 34 c 35 36 37 38 39 40 " + "z"; +static const ClutterPathNode path_nodes[] = + { { CLUTTER_PATH_MOVE_TO, { { 21, 22 }, { 0, 0 }, { 0, 0 } } }, + { CLUTTER_PATH_REL_MOVE_TO, { { 23, 24 }, { 0, 0 }, { 0, 0 } } }, + { CLUTTER_PATH_LINE_TO, { { 25, 26 }, { 0, 0 }, { 0, 0 } } }, + { CLUTTER_PATH_REL_LINE_TO, { { 27, 28 }, { 0, 0 }, { 0, 0 } } }, + { CLUTTER_PATH_CURVE_TO, { { 29, 30 }, { 31, 32 }, { 33, 34 } } }, + { CLUTTER_PATH_REL_CURVE_TO, { { 35, 36 }, { 37, 38 }, { 39, 40 } } }, + { CLUTTER_PATH_CLOSE, { { 0, 0 }, { 0, 0 }, { 0, 0 } } } }; + +static gboolean +path_test_add_move_to (CallbackData *data) +{ + ClutterPathNode node; + + node.type = CLUTTER_PATH_MOVE_TO; + node.points[0].x = 1; + node.points[0].y = 2; + + clutter_path_add_move_to (data->path, node.points[0].x, node.points[0].y); + + data->nodes[data->n_nodes++] = node; + + return TRUE; +} + +static gboolean +path_test_add_line_to (CallbackData *data) +{ + ClutterPathNode node; + + node.type = CLUTTER_PATH_LINE_TO; + node.points[0].x = 3; + node.points[0].y = 4; + + clutter_path_add_line_to (data->path, node.points[0].x, node.points[0].y); + + data->nodes[data->n_nodes++] = node; + + return TRUE; +} + +static gboolean +path_test_add_curve_to (CallbackData *data) +{ + ClutterPathNode node; + + node.type = CLUTTER_PATH_CURVE_TO; + node.points[0].x = 5; + node.points[0].y = 6; + node.points[1].x = 7; + node.points[1].y = 8; + node.points[2].x = 9; + node.points[2].y = 10; + + clutter_path_add_curve_to (data->path, + node.points[0].x, node.points[0].y, + node.points[1].x, node.points[1].y, + node.points[2].x, node.points[2].y); + + data->nodes[data->n_nodes++] = node; + + return TRUE; +} + +static gboolean +path_test_add_close (CallbackData *data) +{ + ClutterPathNode node; + + node.type = CLUTTER_PATH_CLOSE; + + clutter_path_add_close (data->path); + + data->nodes[data->n_nodes++] = node; + + return TRUE; +} + +static gboolean +path_test_add_rel_move_to (CallbackData *data) +{ + ClutterPathNode node; + + node.type = CLUTTER_PATH_REL_MOVE_TO; + node.points[0].x = 11; + node.points[0].y = 12; + + clutter_path_add_rel_move_to (data->path, node.points[0].x, node.points[0].y); + + data->nodes[data->n_nodes++] = node; + + return TRUE; +} + +static gboolean +path_test_add_rel_line_to (CallbackData *data) +{ + ClutterPathNode node; + + node.type = CLUTTER_PATH_REL_LINE_TO; + node.points[0].x = 13; + node.points[0].y = 14; + + clutter_path_add_rel_line_to (data->path, node.points[0].x, node.points[0].y); + + data->nodes[data->n_nodes++] = node; + + return TRUE; +} + +static gboolean +path_test_add_rel_curve_to (CallbackData *data) +{ + ClutterPathNode node; + + node.type = CLUTTER_PATH_REL_CURVE_TO; + node.points[0].x = 15; + node.points[0].y = 16; + node.points[1].x = 17; + node.points[1].y = 18; + node.points[2].x = 19; + node.points[2].y = 20; + + clutter_path_add_rel_curve_to (data->path, + node.points[0].x, node.points[0].y, + node.points[1].x, node.points[1].y, + node.points[2].x, node.points[2].y); + + data->nodes[data->n_nodes++] = node; + + return TRUE; +} + +static gboolean +path_test_add_string (CallbackData *data) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (path_nodes); i++) + data->nodes[data->n_nodes++] = path_nodes[i]; + + clutter_path_add_string (data->path, path_desc); + + return TRUE; +} + +static gboolean +path_test_add_node_by_struct (CallbackData *data) +{ + int i; + + for (i = 0; i < G_N_ELEMENTS (path_nodes); i++) + { + data->nodes[data->n_nodes++] = path_nodes[i]; + clutter_path_add_node (data->path, path_nodes + i); + } + + return TRUE; +} + +static gboolean +path_test_get_n_nodes (CallbackData *data) +{ + return clutter_path_get_n_nodes (data->path) == data->n_nodes; +} + +static gboolean +path_test_get_node (CallbackData *data) +{ + int i; + + data->nodes_found = 0; + data->nodes_different = FALSE; + + for (i = 0; i < data->n_nodes; i++) + { + ClutterPathNode node; + + clutter_path_get_node (data->path, i, &node); + + compare_node (&node, data); + } + + return !data->nodes_different; +} + +static gboolean +path_test_get_nodes (CallbackData *data) +{ + GSList *list, *node; + + data->nodes_found = 0; + data->nodes_different = FALSE; + + list = clutter_path_get_nodes (data->path); + + for (node = list; node; node = node->next) + compare_node (node->data, data); + + g_slist_free (list); + + return !data->nodes_different && data->nodes_found == data->n_nodes; +} + +static gboolean +path_test_insert_beginning (CallbackData *data) +{ + ClutterPathNode node; + + node.type = CLUTTER_PATH_LINE_TO; + node.points[0].x = 41; + node.points[0].y = 42; + + memmove (data->nodes + 1, data->nodes, + data->n_nodes++ * sizeof (ClutterPathNode)); + data->nodes[0] = node; + + clutter_path_insert_node (data->path, 0, &node); + + return TRUE; +} + +static gboolean +path_test_insert_end (CallbackData *data) +{ + ClutterPathNode node; + + node.type = CLUTTER_PATH_LINE_TO; + node.points[0].x = 43; + node.points[0].y = 44; + + data->nodes[data->n_nodes++] = node; + + clutter_path_insert_node (data->path, -1, &node); + + return TRUE; +} + +static gboolean +path_test_insert_middle (CallbackData *data) +{ + ClutterPathNode node; + int pos = data->n_nodes / 2; + + node.type = CLUTTER_PATH_LINE_TO; + node.points[0].x = 45; + node.points[0].y = 46; + + memmove (data->nodes + pos + 1, data->nodes + pos, + (data->n_nodes - pos) * sizeof (ClutterPathNode)); + data->nodes[pos] = node; + data->n_nodes++; + + clutter_path_insert_node (data->path, pos, &node); + + return TRUE; +} + +static gboolean +path_test_clear (CallbackData *data) +{ + clutter_path_clear (data->path); + + data->n_nodes = 0; + + return TRUE; +} + +static gboolean +path_test_clear_insert (CallbackData *data) +{ + return path_test_clear (data) && path_test_insert_middle (data); +} + +static gboolean +path_test_remove_beginning (CallbackData *data) +{ + memmove (data->nodes, data->nodes + 1, + --data->n_nodes * sizeof (ClutterPathNode)); + + clutter_path_remove_node (data->path, 0); + + return TRUE; +} + +static gboolean +path_test_remove_end (CallbackData *data) +{ + clutter_path_remove_node (data->path, --data->n_nodes); + + return TRUE; +} + +static gboolean +path_test_remove_middle (CallbackData *data) +{ + int pos = data->n_nodes / 2; + + memmove (data->nodes + pos, data->nodes + pos + 1, + (--data->n_nodes - pos) * sizeof (ClutterPathNode)); + + clutter_path_remove_node (data->path, pos); + + return TRUE; +} + +static gboolean +path_test_remove_only (CallbackData *data) +{ + return path_test_clear (data) + && path_test_add_line_to (data) + && path_test_remove_beginning (data); +} + +static gboolean +path_test_replace (CallbackData *data) +{ + ClutterPathNode node; + int pos = data->n_nodes / 2; + + node.type = CLUTTER_PATH_LINE_TO; + node.points[0].x = 47; + node.points[0].y = 48; + + data->nodes[pos] = node; + + clutter_path_replace_node (data->path, pos, &node); + + return TRUE; +} + +static gboolean +path_test_set_description (CallbackData *data) +{ + data->n_nodes = G_N_ELEMENTS (path_nodes); + memcpy (data->nodes, path_nodes, sizeof (path_nodes)); + + return clutter_path_set_description (data->path, path_desc); +} + +static gboolean +path_test_get_description (CallbackData *data) +{ + char *desc1, *desc2; + gboolean ret = TRUE; + + desc1 = clutter_path_get_description (data->path); + clutter_path_clear (data->path); + if (!clutter_path_set_description (data->path, desc1)) + ret = FALSE; + desc2 = clutter_path_get_description (data->path); + + if (strcmp (desc1, desc2)) + ret = FALSE; + + g_free (desc1); + g_free (desc2); + + return ret; +} + +static gboolean +float_fuzzy_equals (float fa, float fb) +{ + return fabs (fa - fb) <= FLOAT_FUZZ_AMOUNT; +} + +static void +set_triangle_path (CallbackData *data) +{ + /* Triangular shaped path hitting (0,0), (64,64) and (128,0) in four + parts. The two curves are actually straight lines */ + static const ClutterPathNode nodes[] = + { { CLUTTER_PATH_MOVE_TO, { { 0, 0 } } }, + { CLUTTER_PATH_LINE_TO, { { 32, 32 } } }, + { CLUTTER_PATH_CURVE_TO, { { 40, 40 }, { 56, 56 }, { 64, 64 } } }, + { CLUTTER_PATH_REL_CURVE_TO, { { 8, -8 }, { 24, -24 }, { 32, -32 } } }, + { CLUTTER_PATH_REL_LINE_TO, { { 32, -32 } } } }; + gint i; + + clutter_path_clear (data->path); + + for (i = 0; i < G_N_ELEMENTS (nodes); i++) + clutter_path_add_node (data->path, nodes + i); + + memcpy (data->nodes, nodes, sizeof (nodes)); + data->n_nodes = G_N_ELEMENTS (nodes); +} + +static gboolean +path_test_get_position (CallbackData *data) +{ + static const float values[] = { 0.125f, 16.0f, 16.0f, + 0.375f, 48.0f, 48.0f, + 0.625f, 80.0f, 48.0f, + 0.875f, 112.0f, 16.0f }; + gint i; + + set_triangle_path (data); + + for (i = 0; i < G_N_ELEMENTS (values); i += 3) + { + ClutterKnot pos; + + clutter_path_get_position (data->path, + values[i] * CLUTTER_ALPHA_MAX_ALPHA, &pos); + + if (!float_fuzzy_equals (values[i + 1], pos.x) + || !float_fuzzy_equals (values[i + 2], pos.y)) + return FALSE; + } + + return TRUE; +} + +static gboolean +path_test_get_length (CallbackData *data) +{ + const float actual_length /* sqrt(64**2 + 64**2) * 2 */ = 181.019336f; + guint approx_length; + + set_triangle_path (data); + + g_object_get (data->path, "length", &approx_length, NULL); + + /* Allow 15% margin of error */ + return fabs (approx_length - actual_length) / actual_length <= 0.15f; +} + +static gboolean +path_test_boxed_type (CallbackData *data) +{ + gboolean ret = TRUE; + GSList *nodes, *l; + GValue value; + + nodes = clutter_path_get_nodes (data->path); + + memset (&value, 0, sizeof (value)); + + for (l = nodes; l; l = l->next) + { + g_value_init (&value, CLUTTER_TYPE_PATH_NODE); + + g_value_set_boxed (&value, l->data); + + if (!clutter_path_node_equal (l->data, + g_value_get_boxed (&value))) + ret = FALSE; + + g_value_unset (&value); + } + + g_slist_free (nodes); + + return ret; +} + +static const struct +{ + const char *desc; + PathTestFunc func; +} +path_tests[] = + { + { "Add line to", path_test_add_line_to }, + { "Add move to", path_test_add_move_to }, + { "Add curve to", path_test_add_curve_to }, + { "Add close", path_test_add_close }, + { "Add relative line to", path_test_add_rel_line_to }, + { "Add relative move to", path_test_add_rel_move_to }, + { "Add relative curve to", path_test_add_rel_curve_to }, + { "Add string", path_test_add_string }, + { "Add node by struct", path_test_add_node_by_struct }, + { "Get number of nodes", path_test_get_n_nodes }, + { "Get a node", path_test_get_node }, + { "Get all nodes", path_test_get_nodes }, + { "Insert at beginning", path_test_insert_beginning }, + { "Insert at end", path_test_insert_end }, + { "Insert at middle", path_test_insert_middle }, + { "Add after insert", path_test_add_line_to }, + { "Clear then insert", path_test_clear_insert }, + { "Add string again", path_test_add_string }, + { "Remove from beginning", path_test_remove_beginning }, + { "Remove from end", path_test_remove_end }, + { "Remove from middle", path_test_remove_middle }, + { "Add after remove", path_test_add_line_to }, + { "Remove only node", path_test_remove_only }, + { "Add after remove again", path_test_add_line_to }, + { "Replace a node", path_test_replace }, + { "Set description", path_test_set_description }, + { "Get description", path_test_get_description }, + { "Clear", path_test_clear }, + { "Get position", path_test_get_position }, + { "Check node boxed type", path_test_boxed_type }, + { "Get length", path_test_get_length } + }; + +static void +compare_node (const ClutterPathNode *node, gpointer data_p) +{ + CallbackData *data = data_p; + + if (data->nodes_found >= data->n_nodes) + data->nodes_different = TRUE; + else + { + guint n_points = 0, i; + const ClutterPathNode *onode = data->nodes + data->nodes_found; + + if (node->type != onode->type) + data->nodes_different = TRUE; + + switch (node->type & ~CLUTTER_PATH_RELATIVE) + { + case CLUTTER_PATH_MOVE_TO: n_points = 1; break; + case CLUTTER_PATH_LINE_TO: n_points = 1; break; + case CLUTTER_PATH_CURVE_TO: n_points = 3; break; + case CLUTTER_PATH_CLOSE: n_points = 0; break; + + default: + data->nodes_different = TRUE; + break; + } + + for (i = 0; i < n_points; i++) + if (node->points[i].x != onode->points[i].x + || node->points[i].y != onode->points[i].y) + { + data->nodes_different = TRUE; + break; + } + } + + data->nodes_found++; +} + +static gboolean +compare_nodes (CallbackData *data) +{ + data->nodes_different = FALSE; + data->nodes_found = 0; + + clutter_path_foreach (data->path, compare_node, data); + + return !data->nodes_different && data->nodes_found == data->n_nodes; +} + +void +test_path (TestConformSimpleFixture *fixture, + gconstpointer _data) +{ + CallbackData data; + gint i; + + memset (&data, 0, sizeof (data)); + + data.path = clutter_path_new (); + + for (i = 0; i < G_N_ELEMENTS (path_tests); i++) + { + gboolean succeeded; + + if (g_test_verbose ()) + g_print ("%s... ", path_tests[i].desc); + + succeeded = path_tests[i].func (&data) && compare_nodes (&data); + + if (g_test_verbose ()) + g_print ("%s\n", succeeded ? "ok" : "FAIL"); + + g_assert (succeeded); + } + + g_object_unref (data.path); +} + diff --git a/tests/interactive/test-behave.c b/tests/interactive/test-behave.c index 2f1e46a50..6ecf255f5 100644 --- a/tests/interactive/test-behave.c +++ b/tests/interactive/test-behave.c @@ -65,9 +65,6 @@ typedef enum { PATH_BSPLINE } path_t; -#define MAGIC 0.551784 -#define RADIUS 200 - G_MODULE_EXPORT int test_behave_main (int argc, char *argv[]) { @@ -81,25 +78,25 @@ test_behave_main (int argc, char *argv[]) ClutterColor rect_border_color = { 0, 0, 0, 0 }; int i; path_t path_type = PATH_POLY; - - ClutterKnot knots_poly[] = {{ 0, 0 }, { 0, 300 }, { 300, 300 }, - { 300, 0 }, {0, 0 }}; - - ClutterKnot origin = { 200, 200 }; - ClutterKnot knots_bspline[] = {{ -RADIUS, 0 }, - { -RADIUS, RADIUS*MAGIC }, - { -RADIUS*MAGIC, RADIUS }, - { 0, RADIUS }, - { RADIUS*MAGIC, RADIUS }, - { RADIUS, RADIUS*MAGIC }, - { RADIUS, 0 }, - { RADIUS, -RADIUS*MAGIC }, - { RADIUS*MAGIC, -RADIUS }, - { 0, -RADIUS }, - { -RADIUS*MAGIC, -RADIUS }, - { -RADIUS, -RADIUS*MAGIC }, - { -RADIUS, 0}}; + const char *knots_poly = ("M 0, 0 L 0, 300 L 300, 300 " + "L 300, 0 L 0, 0"); + + /* A spiral created with inkscake */ + const char *knots_bspline = + "M 34.285713,35.219326 " + "C 44.026891,43.384723 28.084874,52.378758 20.714286,51.409804 " + "C 0.7404474,48.783999 -4.6171866,23.967448 1.904757,8.0764719 " + "C 13.570984,-20.348756 49.798303,-26.746504 74.999994,-13.352108 " + "C 111.98449,6.3047056 119.56591,55.259271 99.047626,89.505034 " + "C 71.699974,135.14925 9.6251774,143.91924 -33.571422,116.17172 " + "C -87.929934,81.254291 -97.88804,5.8941057 -62.857155,-46.209236 " + "C -20.430061,-109.31336 68.300385,-120.45954 129.2857,-78.114021 " + "C 201.15479,-28.21129 213.48932,73.938876 163.80954,143.79074 " + "C 106.45226,224.43749 -9.1490153,237.96076 -87.85713,180.93363 " + "C -177.29029,116.13577 -192.00272,-12.937817 -127.61907,-100.49494 " + "C -55.390344,-198.72081 87.170553,-214.62275 183.57141,-142.87593 " + "C 290.59464,-63.223369 307.68641,92.835839 228.57145,198.07645"; for (i = 0; i < argc; ++i) { @@ -187,7 +184,11 @@ test_behave_main (int argc, char *argv[]) switch (path_type) { case PATH_POLY: - p_behave = clutter_behaviour_path_new (alpha, knots_poly, 5); + { + ClutterPath *path = clutter_path_new (); + clutter_path_set_description (path, knots_poly); + p_behave = clutter_behaviour_path_new (alpha, path); + } break; case PATH_ELLIPSE: p_behave = @@ -204,15 +205,11 @@ test_behave_main (int argc, char *argv[]) break; case PATH_BSPLINE: - origin.x = 0; - origin.y = RADIUS; - p_behave = - clutter_behaviour_bspline_new (alpha, knots_bspline, - sizeof (knots_bspline)/sizeof(ClutterKnot)); - - clutter_behaviour_bspline_set_origin ( - CLUTTER_BEHAVIOUR_BSPLINE (p_behave), - &origin); + { + ClutterPath *path = clutter_path_new (); + clutter_path_set_description (path, knots_bspline); + p_behave = clutter_behaviour_path_new (alpha, path); + } break; } diff --git a/tests/interactive/test-script.c b/tests/interactive/test-script.c index b457bb429..5e6464d4d 100644 --- a/tests/interactive/test-script.c +++ b/tests/interactive/test-script.c @@ -40,7 +40,7 @@ static const gchar *test_behaviour = " {" " \"id\" : \"path-behaviour\"," " \"type\" : \"ClutterBehaviourPath\"," -" \"knots\" : [ [ 50, 50 ], { \"x\" : 100, \"y\" : 100 } ]," +" \"path\" : \"M 50 50 L 100 100\"," " \"alpha\" : {" " \"timeline\" : \"main-timeline\"," " \"function\" : \"ramp\"" diff --git a/tests/interactive/test-threads.c b/tests/interactive/test-threads.c index b21acbe59..86eb6cfe3 100644 --- a/tests/interactive/test-threads.c +++ b/tests/interactive/test-threads.c @@ -177,6 +177,7 @@ test_threads_main (int argc, char *argv[]) ClutterColor rect_color = { 0xee, 0x55, 0x55, 0x99 }; ClutterColor progress_color = { 0x55, 0xee, 0x55, 0xbb }; ClutterBehaviour *r_behaviour, *p_behaviour; + ClutterAlpha *alpha; const ClutterKnot knots[] = { { 75, 150 }, { 400, 150 } @@ -220,11 +221,11 @@ test_threads_main (int argc, char *argv[]) 0.0, 360.0); clutter_behaviour_apply (r_behaviour, rect); - p_behaviour = clutter_behaviour_path_new (clutter_alpha_new_full (timeline, - clutter_ramp_inc_func, - NULL, NULL), - knots, - G_N_ELEMENTS (knots)); + alpha = clutter_alpha_new_full (timeline, clutter_ramp_inc_func, + NULL, NULL); + p_behaviour = clutter_behaviour_path_new_with_knots (alpha, + knots, + G_N_ELEMENTS (knots)); clutter_behaviour_apply (p_behaviour, rect); g_signal_connect (stage,