mutter/clutter/clutter-behaviour-bspline.c
Emmanuele Bassi 454e493197 Fix various compiler warnings
Most of these fixes are simple symbol shadowing issues, like index and the
braindead y0 and y1 extern symbols exported by math.h on GNU libc systems.

There is a masking issue in ClutterTexture which should be checked; I ran
the tests and everything looked fine.

The rest are just unused variables.
2007-07-26 11:04:04 +00:00

1143 lines
30 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-fixed.h"
#include "clutter-marshal.h"
#include "clutter-behaviour-bspline.h"
#include "clutter-debug.h"
#include "clutter-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 *
* *
****************************************************************************/
G_DEFINE_TYPE (ClutterBehaviourBspline,
clutter_behaviour_bspline,
CLUTTER_TYPE_BEHAVIOUR);
#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;
}
/**
* 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);
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;
}
}