mutter/clutter/clutter-behaviour-bspline.c
Emmanuele Bassi 7b242370f0 2007-10-25 Emmanuele Bassi <ebassi@openedhand.com>
* clutter/clutter-behaviour-bspline.c: Implement the
	ClutterScriptableIface to parse the custom "knots" property.
2007-10-25 15:05:19 +00:00

1228 lines
33 KiB
C

/* -*- 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 class interpolating actors along a path
* defined by bezier spline.
*
* #ClutterBehaviourBspline interpolates actors along a defined bspline 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 use
* 14.18 fixed format to improve precission and simplify POW3 calculation.
*/
#define CBZ_T_Q 18
#define CBZ_T_ONE (1 << CBZ_T_Q)
#define CBZ_T_POW2(x) ((x >> 9) * (x >> 9))
#define CBZ_T_POW3(x) ((x >> 12) * (x >> 12) * (x >> 12))
#define CBZ_T_MUL(x,y) ((x >> 9) * (y >> 9))
#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, 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:
* @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.4
*/
void
clutter_behaviour_bspline_append (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;
}
}