Bug 1252 - Merge ClutterBehaviourPath and ClutterBehaviourBspline
authorNeil Roberts <neil@openedhand.com>
Fri, 5 Dec 2008 13:13:37 +0000 (13:13 +0000)
committerNeil Roberts <neil@openedhand.com>
Fri, 5 Dec 2008 13:13:37 +0000 (13:13 +0000)
* 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

24 files changed:
.gitignore
ChangeLog
clutter/Makefile.am
clutter/clutter-behaviour-bspline.c [deleted file]
clutter/clutter-behaviour-bspline.h [deleted file]
clutter/clutter-behaviour-path.c
clutter/clutter-behaviour-path.h
clutter/clutter-bezier.c [new file with mode: 0644]
clutter/clutter-bezier.h [new file with mode: 0644]
clutter/clutter-effect.c
clutter/clutter-marshal.list
clutter/clutter-path.c [new file with mode: 0644]
clutter/clutter-path.h [new file with mode: 0644]
clutter/clutter.h
doc/reference/clutter/clutter-animation-tutorial.xml
doc/reference/clutter/clutter-docs.xml
doc/reference/clutter/clutter-sections.txt
doc/reference/clutter/clutter.types
tests/conform/Makefile.am
tests/conform/test-conform-main.c
tests/conform/test-path.c [new file with mode: 0644]
tests/interactive/test-behave.c
tests/interactive/test-script.c
tests/interactive/test-threads.c

index df7d68b..4c42d31 100644 (file)
@@ -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
index a2c59f8..0451108 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,53 @@
+2008-12-05  Neil Roberts  <neil@linux.intel.com>
+
+       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  <neil@linux.intel.com>
 
        Bug 1297 - Bring back support for GL_ARB_texture_rectangle
index 6bf2c5c..ee6c19a 100644 (file)
@@ -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 (file)
index 53c44d1..0000000
+++ /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  <tf@openedhand.com>
- *
- * 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 <stdlib.h>
-#include <memory.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
- */
-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 (file)
index 644b588..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Clutter.
- *
- * An OpenGL based 'interactive canvas' library.
- *
- * Authored By Tomas Frydrych  <tf@openedhand.com>
- *
- * 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 <http://www.gnu.org/licenses/>.
- */
-
-#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
-#error "Only <clutter/clutter.h> can be included directly."
-#endif
-
-#ifndef __CLUTTER_BEHAVIOUR_BSPLINE_H__
-#define __CLUTTER_BEHAVIOUR_BSPLINE_H__
-
-#include <clutter/clutter-alpha.h>
-#include <clutter/clutter-actor.h>
-#include <clutter/clutter-behaviour.h>
-
-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__ */
index 7dcf292..ccbd20c 100644 (file)
 
 /**
  * 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
+ *    }
+ * }
+ * ]|
  *
  * <note>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
 #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 <math.h>
 
@@ -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,73 +106,10 @@ 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,
                           gpointer          data)
@@ -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;
-
-  /* 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)
-    {
-      /* 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;
-    }
+  ClutterBehaviourPath *pathb = CLUTTER_BEHAVIOUR_PATH (behave);
+  ClutterBehaviourPathPrivate *priv = pathb->priv;
+  ClutterKnot position;
+  guint knot_num;
 
-  if (offset == total_len)
+  if (priv->path)
+    knot_num = clutter_path_get_position (priv->path, alpha_value, &position);
+  else
     {
-      /* 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;
+      memset (&position, 0, sizeof (position));
+      knot_num = 0;
     }
 
-  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 (behave,
+                                    actor_apply_knot_foreach,
+                                    &position);
 
-              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;
+  if (knot_num != priv->last_knot_passed)
+    {
+      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);
@@ -296,33 +188,38 @@ 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;
-
-  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);
+  return g_object_new (CLUTTER_TYPE_BEHAVIOUR_PATH,
+                       "alpha", alpha,
+                       "path", clutter_path_new_with_description (desc),
+                       NULL);
 }
 
 /**
- * clutter_behaviour_path_append_knot:
- * @pathb: a #ClutterBehvaiourPath
- * @knot:  a #ClutterKnot to append.
+ * clutter_behaviour_path_new_with_knots:
+ * @alpha: a #ClutterAlpha
+ * @knots: an array of #ClutterKnot<!-- -->s
+ * @n_knots: number of entries in @knots
  *
- * Appends a #ClutterKnot to the path
+ * Creates a new path behaviour that will make the actors visit all of
+ * the given knots in order with straight lines in between.
  *
- * 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.
+ * 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_TO<!-- -->s.
  *
- * 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.
+ * Return value: a #ClutterBehaviour
  *
- * Since: 0.2
+ * Since: 1.0
  */
-void
-clutter_behaviour_path_insert_knot (ClutterBehaviourPath  *pathb,
-                                   guint                  offset,
-                                   const ClutterKnot     *knot)
+ClutterBehaviour *
+clutter_behaviour_path_new_with_knots (ClutterAlpha      *alpha,
+                                       const ClutterKnot *knots,
+                                       guint              n_knots)
 {
-  ClutterBehaviourPathPrivate *priv;
+  ClutterPath *path = clutter_path_new ();
+  guint i;
 
-  g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb));
-  g_return_if_fail (knot != NULL);
+  if (n_knots > 0)
+    {
+      clutter_path_add_move_to (path, knots[0].x, knots[0].y);
 
-  priv = pathb->priv;
-  priv->knots = g_slist_insert (priv->knots, clutter_knot_copy (knot), offset);
+      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_remove_knot:
- * @pathb: a #ClutterBehvaiourPath
- * @offset: position in path to remove knot.
+ * clutter_behaviour_path_set_path:
+ * @pathb: the path behaviour
+ * @path: the new path to follow
  *
- * Removes a #ClutterKnot in the path at specified offset.
+ * 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: 0.2
+ * Since: 1.0
  */
 void
-clutter_behaviour_path_remove_knot (ClutterBehaviourPath  *pathb,
-                                   guint                  offset)
+clutter_behaviour_path_set_path (ClutterBehaviourPath *pathb,
+                                 ClutterPath          *path)
 {
   ClutterBehaviourPathPrivate *priv;
-  GSList                      *togo;
 
   g_return_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb));
 
   priv = pathb->priv;
 
-  togo = g_slist_nth (priv->knots, offset);
+  if (path)
+    g_object_ref_sink (path);
 
-  if (togo)
-    {
-      clutter_knot_free ((ClutterKnot*)togo->data);
-      priv->knots = g_slist_delete_link (priv->knots, togo);
-    }
-}
+  if (priv->path)
+    g_object_unref (priv->path);
 
-static void
-clutter_behaviour_path_append_knots_valist (ClutterBehaviourPath *pathb,
-                                           const ClutterKnot    *first_knot,
-                                           va_list               args)
-{
-  const ClutterKnot * knot;
+  priv->path = path;
 
-  knot = first_knot;
-  while (knot)
-    {
-      clutter_behaviour_path_append_knot (pathb, knot);
-      knot = va_arg (args, ClutterKnot*);
-    }
+  g_object_notify (G_OBJECT (pathb), "path");
 }
 
 /**
- * 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.
+ * clutter_behaviour_path_get_path:
+ * @pathb: a #ClutterBehaviourPath instance
  *
- * 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
+ * Get the current path of the behaviour
  *
- * Removes all knots from a path
+ * Return value: the path
  *
- * Since: 0.2
+ * Since 1.0:
  */
-void
-clutter_behaviour_path_clear (ClutterBehaviourPath *pathb)
+ClutterPath *
+clutter_behaviour_path_get_path (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);
+  g_return_val_if_fail (CLUTTER_IS_BEHAVIOUR_PATH (pathb), NULL);
 
-  pathb->priv->knots = NULL;
+  return pathb->priv->path;
 }
index 8eb425f..eab16ef 100644 (file)
@@ -32,6 +32,7 @@
 
 #include <clutter/clutter-alpha.h>
 #include <clutter/clutter-behaviour.h>
+#include <clutter/clutter-path.h>
 
 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 (file)
index 0000000..6a47626
--- /dev/null
@@ -0,0 +1,426 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Tomas Frydrych  <tf@openedhand.com>
+ *
+ * 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 <glib.h>
+#include <string.h>
+#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 (file)
index 0000000..df9f617
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Tomas Frydrych  <tf@openedhand.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __CLUTTER_BEZIER_H__
+#define __CLUTTER_BEZIER_H__
+
+#include <glib.h>
+#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__ */
index fa8a7be..64fd25b 100644 (file)
@@ -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;
 
+  path = clutter_path_new ();
+
   if (n_knots)
-    clutter_actor_set_position (actor, knots[0].x, knots[0].y);
+    {
+      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, knots, n_knots);
+  c->behave = clutter_behaviour_path_new (c->alpha, path);
 
   clutter_behaviour_apply (c->behave, actor);
   clutter_timeline_start (c->timeline);
index b83857f..3c1ac97 100644 (file)
@@ -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 (file)
index 0000000..76481f4
--- /dev/null
@@ -0,0 +1,1414 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Matthew Allum  <mallum@openedhand.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * 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:
+ *
+ * <variablelist>
+ * <varlistentry><term>%CLUTTER_PATH_MOVE_TO</term>
+ * <listitem><para>
+ * 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.
+ * </para></listitem></varlistentry>
+ * <varlistentry><term>%CLUTTER_PATH_LINE_TO</term>
+ * <listitem><para>
+ * Creates a straight line from the previous point to the given point.
+ * </para></listitem></varlistentry>
+ * <varlistentry><term>%CLUTTER_PATH_CURVE_TO</term>
+ * <listitem><para>
+ * 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.
+ * </para></listitem></varlistentry>
+ * <varlistentry><term>%CLUTTER_PATH_CLOSE</term>
+ * <listitem><para>
+ * 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.
+ * </para></listitem></varlistentry>
+ * </variablelist>
+ *
+ * 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 <glib-object.h>
+#include <string.h>
+#include <stdarg.h>
+
+#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:
+ *
+ * <variablelist>
+ * <varlistentry><term>M</term>
+ * <listitem><para>
+ * Adds a %CLUTTER_PATH_MOVE_TO node. Takes one pair of coordinates.
+ * </para></listitem></varlistentry>
+ * <varlistentry><term>L</term>
+ * <listitem><para>
+ * Adds a %CLUTTER_PATH_LINE_TO node. Takes one pair of coordinates.
+ * </para></listitem></varlistentry>
+ * <varlistentry><term>C</term>
+ * <listitem><para>
+ * Adds a %CLUTTER_PATH_CURVE_TO node. Takes three pairs of coordinates.
+ * </para></listitem></varlistentry>
+ * <varlistentry><term>z</term>
+ * <listitem><para>
+ * Adds a %CLUTTER_PATH_CLOSE node. No coordinates are needed.
+ * </para></listitem></varlistentry>
+ * </variablelist>
+ *
+ * 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:
+ *
+ * <informalexample>
+ *  <programlisting>
+ *   M 250,350 l 0 -100 L 350,250 l 0 100 z
+ *  </programlisting>
+ * </informalexample>
+ *
+ * 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 #ClutterPathNode<!-- -->s. 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 (file)
index 0000000..08ab77f
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * Clutter.
+ *
+ * An OpenGL based 'interactive canvas' library.
+ *
+ * Authored By Matthew Allum  <mallum@openedhand.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#if !defined(__CLUTTER_H_INSIDE__) && !defined(CLUTTER_COMPILATION)
+#error "Only <clutter/clutter.h> can be included directly."
+#endif
+
+#ifndef __CLUTTER_PATH_H__
+#define __CLUTTER_PATH_H__
+
+#include <glib-object.h>
+#include <clutter/clutter-types.h>
+
+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__ */
index df07f84..25e8ded 100644 (file)
@@ -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"
index cb3797d..c12b641 100644 (file)
@@ -410,10 +410,6 @@ main (int argc, char *argv[])
 
   <variablelist>
     <varlistentry>
-      <term>#ClutterBehaviourBspline</term>
-      <listitem><simpara>Moves actors along a B-spline path</simpara></listitem>
-    </varlistentry>
-    <varlistentry>
       <term>#ClutterBehaviourDepth</term>
       <listitem><simpara>Changes the depth of actors</simpara></listitem>
     </varlistentry>
index ff98eb8..829cde4 100644 (file)
     <chapter>
       <title>Behaviours</title>
 
-      <xi:include href="xml/clutter-behaviour-bspline.xml"/>
       <xi:include href="xml/clutter-behaviour-depth.xml"/>
       <xi:include href="xml/clutter-behaviour-ellipse.xml"/>
       <xi:include href="xml/clutter-behaviour-opacity.xml"/>
       <xi:include href="xml/clutter-behaviour-path.xml"/>
+      <xi:include href="xml/clutter-path.xml"/>
       <xi:include href="xml/clutter-behaviour-rotate.xml"/>
       <xi:include href="xml/clutter-behaviour-scale.xml"/>
     </chapter>
index c41f493..9d5a291 100644 (file)
@@ -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
 <SUBSECTION>
 ClutterKnot
 clutter_knot_copy
@@ -688,6 +685,44 @@ clutter_behaviour_path_get_type
 </SECTION>
 
 <SECTION>
+<FILE>clutter-path</FILE>
+<TITLE>ClutterPath</TITLE>
+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
+
+<SUBSECTION>
+ClutterPathNode
+clutter_path_node_copy
+clutter_path_node_free
+clutter_path_node_equal
+</SECTION>
+
+<SECTION>
 <FILE>clutter-behaviour-opacity</FILE>
 <TITLE>ClutterBehaviourOpacity</TITLE>
 ClutterBehaviourOpacity
@@ -763,33 +798,6 @@ clutter_behaviour_scale_get_type
 </SECTION>
 
 <SECTION>
-<FILE>clutter-behaviour-bspline</FILE>
-<TITLE>ClutterBehaviourBspline</TITLE>
-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
-<SUBSECTION Standard>
-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
-<SUBSECTION Private>
-ClutterBehaviourBsplinePrivate
-clutter_behaviour_bspline_get_type
-</SECTION>
-
-<SECTION>
 <FILE>clutter-behaviour-ellipse</FILE>
 <TITLE>ClutterBehaviourEllipse</TITLE>
 ClutterBehaviourEllipse
index 7846d1c..2e66076 100644 (file)
@@ -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
index 9706b8a..ed6dd00 100644 (file)
@@ -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 \
index 1f6e2c8..611bb48 100644 (file)
@@ -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 (file)
index 0000000..0933917
--- /dev/null
@@ -0,0 +1,609 @@
+#include <clutter/clutter.h>
+#include <string.h>
+#include <math.h>
+
+#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);
+}
+
index 2f1e46a..6ecf255 100644 (file)
@@ -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;
     }
 
index b457bb4..5e6464d 100644 (file)
@@ -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\""
index b21acbe..86eb6cf 100644 (file)
@@ -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,