Blob Blame History Raw
<html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8"><title>10. Animating an actor along a curved path</title><link rel="stylesheet" type="text/css" href="style.css"><meta name="generator" content="DocBook XSL Stylesheets Vsnapshot"><link rel="home" href="index.html" title="The Clutter Cookbook"><link rel="up" href="animations.html" title="Chapter 5. Animations"><link rel="prev" href="animations-scaling.html" title="9. Animated scaling"><link rel="next" href="text.html" title="Chapter 6. Text"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">10. Animating an actor along a curved path</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="animations-scaling.html">Prev</a> </td><th width="60%" align="center">Chapter 5. Animations</th><td width="20%" align="right"> <a accesskey="n" href="text.html">Next</a></td></tr></table><hr></div><div class="section"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="animations-path"></a>10. Animating an actor along a curved path</h2></div></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="idm140200507122976"></a>10.1. Problem</h3></div></div></div><p>You want to animate an actor along a curved path: for
      example, to move an actor in a circle or spiral.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="idm140200507121696"></a>10.2. Solution</h3></div></div></div><p>Create a <span class="type">ClutterPath</span> to describe the
      path the actor should move along; then create a
      <span class="type">ClutterPathConstraint</span> based on that path:</p><div class="informalexample"><pre class="programlisting">ClutterPath *path;
ClutterConstraint *constraint;

/* create the path */
path = clutter_path_new ();

/* first node is at 30,60 */
clutter_path_add_move_to (path, 30, 60);

/* add a curve to the top-right of the stage, with control
 * points relative to the start point at 30,60
 */
clutter_path_add_rel_curve_to (path,
                               120, 180,
                               180, 120,
                               240, 0);

/* create a constraint based on the path */
constraint = clutter_path_constraint_new (path, 0.0);</pre></div><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>For more on the types of curve and line segment available,
        see the <span class="type">ClutterPath</span> API documentation.</p></div><p>Next, add the constraint to an actor; in this case, the
      actor is a red rectangle:</p><div class="informalexample"><pre class="programlisting">ClutterActor *rectangle;
ClutterActor *stage = clutter_stage_new ();

/* ...set size stage, color, etc... */

const ClutterColor *red_color = clutter_color_new (255, 0, 0, 255);

rectangle = clutter_rectangle_new_with_color (red_color);
clutter_actor_set_size (rectangle, 60, 60);

/* add the constraint to the rectangle; note that this
 * puts the rectangle at the start of the path, i.e. at position 30,60;
 * we also give the constraint a name, so we can use it from an implicit
 * animation
 */
clutter_actor_add_constraint_with_name (rectangle, "path", constraint);

/* add the rectangle to the stage */
clutter_actor_add_child (stage, rectangle);</pre></div><p>Note how the constraint has to be assigned a name (here, "path")
      to make it accessible via implicit animations.</p><p>Finally, animate the constraint's <code class="varname">offset</code>
      property; which in turn moves the actor along the path:</p><div class="informalexample"><pre class="programlisting">ClutterTimeline *timeline;

/* create a timeline with 1000 milliseconds duration, which loops
 * indefinitely and reverses its direction each time it completes
 */
timeline = clutter_timeline_new (1000);
clutter_timeline_set_repeat_count (timeline, -1);
clutter_timeline_set_auto_reverse (timeline, TRUE);

/* animate the offset property on the constraint from 0.0 to 1.0;
 * note the syntax used to refer to the constraints metadata for the
 * rectangle actor:
 *
 *   "@constraints.&lt;constraint name&gt;.&lt;property&gt;"
 */
clutter_actor_animate_with_timeline (rectangle, CLUTTER_LINEAR, timeline,
                                     "@constraints.path.offset", 1.0,
                                     NULL);

/* ...show the stage, run the mainloop, free memory on exit... */</pre></div><p>The <a class="link" href="animations-path.html#animations-path-example-1" title="Example 5.16. Using a ClutterPathConstraint with implicit animations to move an actor along a curved path">full
      example</a> shows how these fragments fit together.
      The animation produced by this example looks
      like this:</p><p><video controls="controls" src="videos/animations-path.ogv"><a href="videos/animations-path.ogv">
          <p>Video showing animation of an actor along a curved
          path using <span class="type">ClutterPathConstraint</span></p>
        </a></video></p><p>The <a class="link" href="animations-path.html#animations-path-example-2" title="Example 5.17. Using a ClutterPathConstraint with ClutterAnimator to animate an actor on a simulated circular path">second full
      example</a> animates an actor around a simulated circle
      using a more complex <span class="type">ClutterPath</span>.</p></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="idm140200507106528"></a>10.3. Discussion</h3></div></div></div><p>Animating an actor using <span class="type">ClutterPathConstraint</span>
      is the recommended way to animate actors along curved paths. It
      replaces the older <span class="type">ClutterBehaviourPath</span>.</p><p>A <span class="type">ClutterPathConstraint</span> constrains an
      actor's <code class="varname">x</code> and <code class="varname">y</code> properties
      to a position along such a <span class="type">ClutterPath</span>: a path through
      2D space. The <span class="type">ClutterPath</span> itself is composed of nodes
      (x,y positions in 2D space), connected by straight lines or (cubic)
      <a class="ulink" href="http://en.wikipedia.org/wiki/B%C3%A9zier_curve" target="_top">Bézier
      curves</a>.</p><div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p><span class="type">ClutterPath</span> doesn't have to be used in animations:
        it can also be used in drawing (see the
        <a class="link" href="actors-non-rectangular.html" title="6. Creating an actor with a non-rectangular shape">non-rectangular actor
        recipe</a>).</p></div><p>The actor's position along the path is determined by the constraint's
      <code class="varname">offset</code> property, which has a
      value between 0.0 and 1.0. When the offset is 0.0, the actor
      is at the beginning of the path; when the actor is at 1.0, the
      actor is at the end of the path. Between 0.0 and 1.0, the actor
      is some fraction of the way along the path.</p><p>If you immediately set the <code class="varname">offset</code> for the
      constraint (e.g. to <code class="code">0.5</code>), the actor is instantly placed
      at that position along the path: for <code class="code">offset = 0.5</code>,
      at the halfway point.</p><p>By contrast, to animate an actor along a path, you
      <span class="emphasis"><em>animate</em></span> the offset property of a
      <span class="type">ClutterPathConstraint</span>. The actor's position
      along the path is dependent on the progress of the animation:
      when the animation starts, the actor is at the beginning of the path;
      by the end of the animation, it will have reached its end.</p><p>If you animate the constraint using a linear easing mode,
      the progress of the animation matches progress along the path: at
      half-way through the animation, the actor will be half-way along
      the path.</p><p>However, if you are using a non-linear easing mode
      (e.g. a quintic or cubic mode), the offset along the path and
      progress through the animation may differ. This is because the
      offset along the path is computed from the alpha value at that
      point in the animation; this in turn depends on the alpha function
      applied by the animation. (See the
      <a class="link" href="animations.html#animations-introduction" title="1. Introduction">animations introduction</a>
      for more details about alphas.)</p><p>One way to think about this is to imagine the actor
      making a journey along the path. The alpha function governs the
      actor's speed, including how it speeds up and slows down
      during its journey. The actor's speed may be constant
      (as in a linear easing mode). Alternatively, the actor's speed
      may not be constant: it might start out fast then slow down
      (ease out); or start slow and speed up (ease in); or start and
      end fast, but slow down in the middle (ease in and ease out); or
      some other more complex arrangement (as in the bounce and elastic
      easing modes). So where the actor is on the path at a particular
      time doesn't directly relate to how long it's been travelling:
      the position is determined both by how long it's been travelling,
      and changes in its speed throughout the journey.</p><div class="section"><div class="titlepage"><div><div><h4 class="title"><a name="idm140200507092256"></a>10.3.1. Other ways to animate along a path</h4></div></div></div><p><span class="type">ClutterPathConstraint</span> is the only
        decent way of animating along curves in a predictable
        and manageable fashion. It can also be used to animate along
        paths composed of straight lines, though this isn't essential: you
        can do straight line animations directly with <span class="type">ClutterAnimator</span>,
        <span class="type">ClutterState</span> or implicit animations. But if
        you need to animate between more than a half a dozen sets of
        points joined by straight lines, <span class="type">ClutterPathConstraint</span>
        makes sense then too.</p><p>It is also possible to animate actors over very simple, non-Bézier
        curves without using <span class="type">ClutterPathConstraint</span>. This
        can be done by animating the actor's position properties using
        a non-linear easing mode (see the <span class="type">ClutterAlpha</span>
        documentation for available modes, or write your own custom
        alpha function). <a class="link" href="animations-path.html#animations-path-example-3" title="Example 5.18. Animating actors on curved paths using easing modes">This
        example</a> shows how to animate two actors on
        curved paths around each other without
        <span class="type">ClutterPathConstraint</span>.</p><p>However, it is difficult to precisely calculate paths
        with this approach. It is also only practical where you have a
        very simple curve: if you want to chain together several curved
        motions (as in the <a class="link" href="animations-path.html#animations-path-example-2" title="Example 5.17. Using a ClutterPathConstraint with ClutterAnimator to animate an actor on a simulated circular path">circle
        example</a>), this quickly becomes unwieldy.</p><div class="tip" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Tip</h3><p>
          If you want physics-based animation, look at
          <a class="ulink" href="http://git.clutter-project.org/clutter-box2d/" target="_top">clutter-box2d</a>.
          </p></div></div></div><div class="section"><div class="titlepage"><div><div><h3 class="title"><a name="animations-path-examples"></a>10.4. Full examples</h3></div></div></div><div class="example"><a name="animations-path-example-1"></a><p class="title"><b>Example 5.16. Using a <span class="type">ClutterPathConstraint</span> with
        implicit animations to move an actor along a curved path</b></p><div class="example-contents"><pre class="programlisting">#include &lt;stdlib.h&gt;
#include &lt;clutter/clutter.h&gt;

int
main (int   argc,
      char *argv[])
{
  ClutterActor *stage;
  ClutterPath *path;
  ClutterConstraint *constraint;
  ClutterActor *rectangle;
  ClutterTimeline *timeline;

  const ClutterColor *stage_color = clutter_color_new (51, 51, 85, 255);
  const ClutterColor *red_color = clutter_color_new (255, 0, 0, 255);

  if (clutter_init (&amp;argc, &amp;argv) != CLUTTER_INIT_SUCCESS)
    return 1;

  stage = clutter_stage_new ();
  clutter_actor_set_size (stage, 360, 300);
  clutter_stage_set_color (CLUTTER_STAGE (stage), stage_color);
  g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);

  /* create the path */
  path = clutter_path_new ();
  clutter_path_add_move_to (path, 30, 60);

  /* add a curve round to the top-right of the stage */
  clutter_path_add_rel_curve_to (path,
                                 120, 180,
                                 180, 120,
                                 240, 0);

  /* create a constraint based on the path */
  constraint = clutter_path_constraint_new (path, 0.0);

  /* put a rectangle at the start of the path */
  rectangle = clutter_rectangle_new_with_color (red_color);
  clutter_actor_set_size (rectangle, 60, 60);

  /* add the constraint to the rectangle */
  clutter_actor_add_constraint_with_name (rectangle, "path", constraint);

  /* add the rectangle to the stage */
  clutter_container_add_actor (CLUTTER_CONTAINER (stage), rectangle);

  /* set up the timeline */
  timeline = clutter_timeline_new (1000);
  clutter_timeline_set_repeat_count (timeline, -1);
  clutter_timeline_set_auto_reverse (timeline, TRUE);

  clutter_actor_animate_with_timeline (rectangle, CLUTTER_LINEAR, timeline,
                                       "@constraints.path.offset", 1.0,
                                       NULL);

  clutter_actor_show (stage);

  clutter_main ();

  return EXIT_SUCCESS;
}
</pre></div></div><br class="example-break"><div class="example"><a name="animations-path-example-2"></a><p class="title"><b>Example 5.17. Using a <span class="type">ClutterPathConstraint</span> with
        <span class="type">ClutterAnimator</span> to animate an actor on
        a simulated circular path</b></p><div class="example-contents"><pre class="programlisting">#include &lt;stdlib.h&gt;
#include &lt;clutter/clutter.h&gt;

#define STAGE_SIDE 400.0

static const ClutterColor stage_color = { 0x33, 0x33, 0x55, 0xff };
static const ClutterColor red_color = { 0xff, 0x00, 0x00, 0xff };

/* Build a "circular" path out of 4 Bezier curves
 *
 * code modified from
 * http://git.clutter-project.org/dax/tree/dax/dax-traverser-clutter.c#n328
 *
 * see http://www.whizkidtech.redprince.net/bezier/circle/
 * for further explanation
 */
static ClutterPath *
build_circular_path (gfloat cx,
                     gfloat cy,
                     gfloat r)
{
  ClutterPath *path;
  static gfloat kappa = 4 * (G_SQRT2 - 1) / 3;

  path = clutter_path_new ();

  clutter_path_add_move_to (path, cx + r, cy);
  clutter_path_add_curve_to (path,
                             cx + r, cy + r * kappa,
                             cx + r * kappa, cy + r,
                             cx, cy + r);
  clutter_path_add_curve_to (path,
                             cx - r * kappa, cy + r,
                             cx - r, cy + r * kappa,
                             cx - r, cy);
  clutter_path_add_curve_to (path,
                             cx - r, cy - r * kappa,
                             cx - r * kappa, cy - r,
                             cx, cy - r);
  clutter_path_add_curve_to (path,
                             cx + r * kappa, cy - r,
                             cx + r, cy - r * kappa,
                             cx + r, cy);
  clutter_path_add_close (path);

  return path;
}

static gboolean
key_pressed_cb (ClutterActor *actor,
                ClutterEvent *event,
                gpointer      user_data)
{
  ClutterTimeline *timeline = CLUTTER_TIMELINE (user_data);

  if (!clutter_timeline_is_playing (timeline))
    clutter_timeline_start (timeline);

  return TRUE;
}

int
main (int   argc,
      char *argv[])
{
  ClutterPath *path;
  ClutterConstraint *constraint;
  ClutterAnimator *animator;
  ClutterTimeline *timeline;
  ClutterActor *stage;
  ClutterActor *rectangle;

  if (clutter_init (&amp;argc, &amp;argv) != CLUTTER_INIT_SUCCESS)
    return 1;

  stage = clutter_stage_new ();
  clutter_actor_set_size (stage, STAGE_SIDE, STAGE_SIDE);
  clutter_stage_set_color (CLUTTER_STAGE (stage), &amp;stage_color);
  g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);

  rectangle = clutter_rectangle_new_with_color (&amp;red_color);
  clutter_actor_set_size (rectangle, STAGE_SIDE / 8, STAGE_SIDE / 8);
  clutter_actor_set_position (rectangle,
                              STAGE_SIDE / 2,
                              STAGE_SIDE / 2);

  clutter_container_add_actor (CLUTTER_CONTAINER (stage),
                               rectangle);

  /* set up a path and make a constraint with it */
  path = build_circular_path (STAGE_SIDE / 2,
                              STAGE_SIDE / 2,
                              STAGE_SIDE / 4);
  constraint = clutter_path_constraint_new (path, 0.0);

  /* apply the constraint to the rectangle; note that there
   * is no need to name the constraint, as we will be animating
   * the constraint's offset property directly using ClutterAnimator
   */
  clutter_actor_add_constraint (rectangle, constraint);

  /* animation to animate the path offset */
  animator = clutter_animator_new ();
  clutter_animator_set_duration (animator, 5000);

  /* use ClutterAnimator to animate the constraint directly */
  clutter_animator_set (animator,
                        constraint, "offset", CLUTTER_LINEAR, 0.0, 0.0,
                        constraint, "offset", CLUTTER_LINEAR, 1.0, 1.0,
                        NULL);

  timeline = clutter_animator_get_timeline (animator);
  clutter_timeline_set_repeat_count (timeline, -1);
  clutter_timeline_set_auto_reverse (timeline, TRUE);

  g_signal_connect (stage,
                    "key-press-event",
                    G_CALLBACK (key_pressed_cb),
                    timeline);

  clutter_actor_show (stage);

  clutter_main ();

  /* clean up */
  g_object_unref (animator);

  return EXIT_SUCCESS;
}
</pre></div></div><br class="example-break"><div class="example"><a name="animations-path-example-3"></a><p class="title"><b>Example 5.18. Animating actors on curved paths using easing modes</b></p><div class="example-contents"><pre class="programlisting">#include &lt;stdlib.h&gt;
#include &lt;clutter/clutter.h&gt;

typedef struct {
  ClutterActor *red;
  ClutterActor *green;
  ClutterTimeline *timeline;
} State;

static const ClutterColor stage_color = { 0x33, 0x33, 0x55, 0xff };
static const ClutterColor red_color = { 0xff, 0x00, 0x00, 0xff };
static const ClutterColor green_color = { 0x00, 0xff, 0x00, 0xff };

static void
reverse_timeline (ClutterTimeline *timeline)
{
  ClutterTimelineDirection dir = clutter_timeline_get_direction (timeline);

  if (dir == CLUTTER_TIMELINE_FORWARD)
    dir = CLUTTER_TIMELINE_BACKWARD;
  else
    dir = CLUTTER_TIMELINE_FORWARD;

  clutter_timeline_set_direction (timeline, dir);
}

/* a key press either starts the timeline or reverses it */
static gboolean
key_pressed_cb (ClutterActor *actor,
                ClutterEvent *event,
                gpointer      user_data)
{
  State *state = (State *) user_data;

  if (clutter_timeline_is_playing (state-&gt;timeline))
    reverse_timeline (state-&gt;timeline);
  else
    clutter_timeline_start (state-&gt;timeline);

  return TRUE;
}

int
main (int   argc,
      char *argv[])
{
  State *state = g_new0 (State, 1);

  ClutterActor *stage;
  ClutterAnimator *animator;

  if (clutter_init (&amp;argc, &amp;argv) != CLUTTER_INIT_SUCCESS)
    return 1;

  stage = clutter_stage_new ();
  clutter_actor_set_size (stage, 400, 400);
  clutter_stage_set_color (CLUTTER_STAGE (stage), &amp;stage_color);
  g_signal_connect (stage, "destroy", G_CALLBACK (clutter_main_quit), NULL);

  state-&gt;red = clutter_rectangle_new_with_color (&amp;red_color);
  clutter_actor_set_size (state-&gt;red, 100, 100);
  clutter_actor_set_position (state-&gt;red, 300, 300);

  state-&gt;green = clutter_rectangle_new_with_color (&amp;green_color);
  clutter_actor_set_size (state-&gt;green, 100, 100);
  clutter_actor_set_position (state-&gt;green, 0, 0);

  animator = clutter_animator_new ();
  clutter_animator_set_duration (animator, 1000);

  clutter_animator_set (animator,
                        state-&gt;red, "x", CLUTTER_LINEAR, 0.0, 300.0,
                        state-&gt;red, "y", CLUTTER_LINEAR, 0.0, 300.0,
                        state-&gt;red, "x", CLUTTER_LINEAR, 1.0, 0.0,
                        state-&gt;red, "y", CLUTTER_EASE_IN_QUINT, 1.0, 0.0,
                        NULL);

  clutter_animator_set (animator,
                        state-&gt;green, "x", CLUTTER_LINEAR, 0.0, 0.0,
                        state-&gt;green, "y", CLUTTER_LINEAR, 0.0, 0.0,
                        state-&gt;green, "x", CLUTTER_LINEAR, 1.0, 300.0,
                        state-&gt;green, "y", CLUTTER_EASE_IN_QUINT, 1.0, 300.0,
                        NULL);

  state-&gt;timeline = clutter_animator_get_timeline (animator);

  clutter_timeline_set_auto_reverse (state-&gt;timeline, TRUE);

  g_signal_connect (stage,
                    "key-press-event",
                    G_CALLBACK (key_pressed_cb),
                    state);

  clutter_container_add (CLUTTER_CONTAINER (stage), state-&gt;red, state-&gt;green, NULL);

  clutter_actor_show (stage);

  clutter_main ();

  g_object_unref (animator);

  g_free (state);

  return EXIT_SUCCESS;
}
</pre></div></div><br class="example-break"></div></div><div class="navfooter"><hr><table width="100%" summary="Navigation footer"><tr><td width="40%" align="left"><a accesskey="p" href="animations-scaling.html">Prev</a> </td><td width="20%" align="center"><a accesskey="u" href="animations.html">Up</a></td><td width="40%" align="right"> <a accesskey="n" href="text.html">Next</a></td></tr><tr><td width="40%" align="left" valign="top">9. Animated scaling </td><td width="20%" align="center"><a accesskey="h" href="index.html">Home</a></td><td width="40%" align="right" valign="top"> Chapter 6. Text</td></tr></table></div></body></html>