mutter/doc/cookbook/effects.xml

1300 lines
47 KiB
XML
Raw Normal View History

<!DOCTYPE chapter PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
<chapter id="effects" xmlns:xi="http://www.w3.org/2003/XInclude">
<title>Effects</title>
<epigraph>
<attribution>Roger Zelazny, from <citetitle>Prince of Chaos</citetitle>
</attribution>
<para>Don't wake me for the end of the world unless it has very
good special effects</para>
</epigraph>
<section id="effects-introduction">
<title>Introduction</title>
<para>Effects modify an actor's appearance, such
as how it is positioned, colored and textured.</para>
<para>The Clutter API for effects contains several
abstract classes you can subclass to create your own effects.
It also contains several built-in effects you can use to
modify the visual appearance of actors in a variety of ways.</para>
<para>The recipes in this section of the cookbook cover how to create
your own effects as well as how to apply Clutter's effects.</para>
<section>
<title>Creating effects using the abstract effect classes</title>
<tip>
<para>One of the original design goals of Clutter was to abstract
the complexity of GL. However, the effects API partially circumvents
these abstractions, to give you finer-grained access to the
graphics pipeline. Therefore, if you want to write your own effects,
some understanding of Cogl, OpenGL, and general graphics programming
is essential.</para>
</tip>
<para>Each abstract effect class is tailored to modifying different
aspects of an actor, as explained below:</para>
<itemizedlist>
<listitem>
<formalpara>
<title><type>ClutterEffect</type></title>
<para>If you're just using the Clutter and Cogl APIs to
decorate an actor, this is simplest type of effect to
implement.</para>
</formalpara>
<para>Subclassing <type>ClutterEffect</type> enables you to
"wrap" how an actor is painted, by injecting some code before
and/or after the actor's own <function>paint()</function>
implementation.</para>
<note>
<para>This is the preferred way to modify how an actor is
painted, short of creating your own actor subclass.</para>
</note>
<para><emphasis>Subclasses of
<type>ClutterEffect</type></emphasis>:</para>
<itemizedlist>
<listitem>
<formalpara>
<title><type>ClutterOffscreenEffect</type></title>
<para>Use this class as a basis if you need GL textures
for your effect.</para>
</formalpara>
<para>GL textures are required for effects which need
an offscreen framebuffer. The offscreen framebuffer is
used to store a modified rendering of an actor (e.g.
with its colors altered or with deformed geometry).
This buffer is then redirected to a texture in the
stage window.</para>
<para>An example is <type>ClutterBlurEffect</type>,
which uses a GLSL fragment shader to blur an
actor's appearance in an offscreen framebuffer.</para>
<para><emphasis>Subclasses of
<type>ClutterOffscreenEffect</type></emphasis>:</para>
<itemizedlist>
<listitem>
<formalpara>
<title><type>ClutterDeformEffect</type></title>
<para>Use this base class if you want to modify
an actor's geometry, at the level of individual
vertices.</para>
</formalpara>
<para><type>ClutterDeformEffect</type> removes the
complexity of dealing with vertex-based deformations
at the OpenGL level, instead enabling you to easily plug
a deformation callback into the graphics pipeline.</para>
<para>If you are writing your own deform effects,
a good example to work from is
<type>ClutterPageTurnEffect</type>.</para>
<para>There is also a
<link linkend="effects-custom-deform">recipe which
explains how to implement a simple custom deform
effect</link> (a page fold).</para>
</listitem>
<listitem>
<formalpara>
<title><type>ClutterShaderEffect</type></title>
<para>Use this if you want to apply custom
GLSL vertex or fragment shaders to your actors.</para>
</formalpara>
<para>Writing <type>ClutterShaderEffects</type> gives
you very fine-grained control over the GL pipeline.
However, this makes them the most complex
effects to implement.</para>
<tip>
<para>If you want to write your own GLSL shaders, the
<ulink url="http://www.opengl.org/documentation/glsl/">GLSL
specification</ulink> is a good starting point.</para>
</tip>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
</section>
<section id="effects-introduction-using-the-built-in-effects">
<title>Using the built-in effects</title>
<para>Clutter comes with a number of built-in effects
which can easily be applied to your actors. This section
explains how to do this.</para>
<para>First, create an actor. For this
example, we use a texture loaded with an image:</para>
<informalexample>
<programlisting>
/* filename could be set from command line or constant */
gchar *filename;
/* create a texture */
ClutterActor *texture = clutter_texture_new ();
/* ...set texture size, keep aspect ratio etc... */
/* NB ignoring missing file errors here for brevity */
clutter_texture_set_from_file (CLUTTER_TEXTURE (texture),
filename,
NULL);
/* ...add texture to the stage... */
</programlisting>
</informalexample>
<para>Next, create an instance of an effect; here, we're
creating a <type>ClutterColorizeEffect</type> with a pink tint:</para>
<informalexample>
<programlisting>
ClutterColor *pink = clutter_color_new (230, 187, 210, 255);
ClutterEffect *effect = clutter_colorize_effect_new (pink);
</programlisting>
</informalexample>
<para>Finally, apply the effect to the actor:</para>
<informalexample>
<programlisting>
clutter_actor_add_effect (texture, effect);
</programlisting>
</informalexample>
<para>The result in this case is an image colorized with
a pink tint, like this:</para>
<screenshot>
<mediaobject>
<imageobject>
<imagedata format="PNG"
fileref="images/effects-built-in.png" />
</imageobject>
<alt>
<para>Applying a <type>ClutterColorizeEffect</type>
to a texture loaded with an image (drawing by
Madeleine Smith)</para>
</alt>
</mediaobject>
</screenshot>
<para>The same set of steps applies for any of the built-in
Clutter effects. Your own custom effects classes should also
behave in a similar way: constructors should return
<type>ClutterEffect</type> instances so your effect can
be added to an actor through the standard API.</para>
<para>One further thing worth mentioning is that because an
effect is a GObject, any properties you expose for your effect
can be animated via implicit animations,
<type>ClutterAnimator</type> or <type>ClutterState</type>. For
example, the <type>ClutterPageTurnEffect</type> can be animated
by manipulating its <varname>period</varname> property. An example
of how to do this for your own effect is given in the
<link linkend="effects-custom-deform">custom deform effect
recipe</link>.</para>
<para>The full code for the <type>ClutterColorizeEffect</type>
example is below.</para>
<example id="effects-introduction-example-1">
<title>Applying a <type>ClutterColorizeEffect</type> to
a texture loaded with an image</title>
<programlisting>
<xi:include href="examples/effects-built-in.c" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
</section>
</section>
<section id="effects-basic">
<title>Changing an actor's paint sequence using
<type>ClutterEffect</type></title>
<section>
<title>Problem</title>
<para>You want to paint on top of or under an actor in a generic
way, without editing the actor's <function>paint()</function>
implementation. Example use cases are:</para>
<itemizedlist>
<listitem>
<para>Adding a border on top of an actor.</para>
</listitem>
<listitem>
<para>Drawing a background for an actor.</para>
</listitem>
</itemizedlist>
<para>A quick way to achieve the same thing (though not
readily portable between actors) is to connect a callback
before or after an actor's <emphasis>paint</emphasis> signal.
See <link linkend="actors-paint-wrappers">this recipe</link> for
more details. However, using a <type>ClutterEffect</type>
implementation, as explained in this recipe, is the preferred
approach.</para>
</section>
<section>
<title>Solution</title>
<para>Create a subclass of the <type>ClutterEffect</type> abstract
class; then implement the <function>pre_paint()</function> and/or
<function>post_paint()</function> virtual functions. When the
effect is applied to an actor, these functions will paint
before and after the actor's own <function>paint()</function>
implementation.</para>
<note>
<para>For this solution, we implement a simple
<type>CbBackgroundEffect</type> which draws a gray rectangle
under an actor. The full source is in
<link linkend="effects-basic-example-cbbackgroundeffect">this
section</link>. To keep it simple, the effect has no properties
and isn't configurable (the background is always gray); see the
<link linkend="effects-basic-example-cbbordereffect">border
effect</link> for a more detailed implementation with GObject
trimmings.</para>
</note>
<para>First, create a <type>ClutterEffect</type> subclass. This
requires the trappings of a GObject class; in particular,
it needs a private struct to hold the effect's state. This
should include any <type>CoglMaterials</type>,
<type>CoglColors</type> or other private member variables
you intend to use to draw the effect.</para>
<para>In the case of the background effect, we have a background
<type>CoglMaterial</type> and a <type>CoglColor</type> for that
material:</para>
<informalexample>
<programlisting>
struct _CbBackgroundEffectPrivate
{
CoglMaterial *background;
CoglColor *color;
};
</programlisting>
</informalexample>
<para>In the <function>init()</function> function for objects of
your class, create any Cogl resources which you need to draw the
effect. In the case of the background effect,
we need to create the <type>CoglMaterial</type> and
<type>CoglColor</type> for the private struct:</para>
<informalexample>
<programlisting>
<![CDATA[
static void
cb_background_effect_init (CbBackgroundEffect *self)
{
/* get the private struct for the object
CbBackgroundEffectPrivate *priv;
priv = self->priv = CB_BACKGROUND_EFFECT_GET_PRIVATE (self);
/* create the background material */
priv->background = cogl_material_new ();
/* gray color for filling the background material */
priv->color = cogl_color_new ();
cogl_color_init_from_4ub (priv->color, 122, 122, 122, 255);
/* set the color on the material; NB this isn't configurable
* for this effect, and is always gray
*/
cogl_material_set_color (priv->background, priv->color);
}
]]>
</programlisting>
</informalexample>
<para>Optionally, you can create GObject properties for
the class, if you want a configurable effect: see
<link linkend="effects-basic-discussion-properties">this
section</link> for details.</para>
<para>The <function>dispose()</function> function for your effect
should clean up any Cogl resources:</para>
<informalexample>
<programlisting>
<![CDATA[
static void
cb_background_effect_dispose (GObject *gobject)
{
CbBackgroundEffectPrivate *priv = CB_BACKGROUND_EFFECT (gobject)->priv;
if (priv->background != COGL_INVALID_HANDLE)
{
cogl_handle_unref (priv->background);
priv->background = COGL_INVALID_HANDLE;
}
if (priv->color != NULL)
{
cogl_color_free (priv->color);
priv->color = NULL;
}
G_OBJECT_CLASS (cb_background_effect_parent_class)->dispose (gobject);
}
]]>
</programlisting>
</informalexample>
<para>Now, the important part: implement <function>pre_paint()</function>
and/or <function>post_paint()</function>, using Cogl to draw on the
material(s) set up for the effect.</para>
<para>For the background effect, we implement <function>pre_paint()</function>,
to draw a gray rectangle under the actor:</para>
<informalexample>
<programlisting>
<![CDATA[
/* note that if pre_paint() returns FALSE
* any post_paint() defined for the effect will not be called
*/
static gboolean
cb_background_effect_pre_paint (ClutterEffect *self)
{
ClutterActor *actor;
gfloat width;
gfloat height;
CbBackgroundEffectPrivate *priv;
priv = CB_BACKGROUND_EFFECT (self)->priv;
/* get the associated actor's dimensions */
actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self));
clutter_actor_get_size (actor, &width, &height);
/* draw a Cogl rectangle in the background using the default color */
cogl_set_source (priv->background);
/* the rectangle has the same dimensions as the actor */
cogl_rectangle (0, 0, width, height);
return TRUE;
}
]]>
</programlisting>
</informalexample>
<para>Now, in the <function>init()</function> function for the
effect <emphasis>class</emphasis>, assign your implementations to the
virtual methods of the <type>ClutterEffect</type> abstract class:</para>
<informalexample>
<programlisting>
<![CDATA[
static void
cb_background_effect_class_init (CbBackgroundEffectClass *klass)
{
ClutterEffectClass *effect_class = CLUTTER_EFFECT_CLASS (klass);
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
effect_class->pre_paint = cb_background_effect_pre_paint;
gobject_class->dispose = cb_background_effect_dispose;
g_type_class_add_private (klass, sizeof (CbBackgroundEffectPrivate));
}
]]>
</programlisting>
</informalexample>
<para>If you intend to make your effect reusable, provide
a public constructor (as is done for the example effects in this
recipe):</para>
<informalexample>
<programlisting>
ClutterEffect *
cb_background_effect_new ()
{
return g_object_new (CB_TYPE_BACKGROUND_EFFECT,
NULL);
}
</programlisting>
</informalexample>
<para>The effect is now ready to be used. The application code
for applying your effect to an actor is the same as for any
other effect:</para>
<informalexample>
<programlisting>
ClutterActor *texture;
ClutterEffect *background_effect;
/* ...initialize texture, load image file etc... */
/* create a gray background effect */
background_effect = cb_background_effect_new ();
/* apply the effect to the actor */
clutter_actor_add_effect (texture, background_effect);
</programlisting>
</informalexample>
<para>Below is an example of applying this effect to a texture loaded
with an image; the image has a transparent background, so the
background is visible through it. The screenshot is from
the <link linkend="effects-basic-example-5">example
application</link>:</para>
<screenshot>
<mediaobject>
<imageobject>
<imagedata format="PNG"
fileref="images/effects-basic-background.png" />
</imageobject>
<alt>
<para>Applying <type>CbBackgroundEffect</type>
to a texture loaded with an image that has a transparent
background</para>
</alt>
</mediaobject>
</screenshot>
</section>
<section>
<title>Discussion</title>
<para>A basic <type>ClutterEffect</type> is particularly useful for
amending the appearance of an actor on the fly: for example,
to highlight an actor in response to a button presses. This
<emphasis>could</emphasis> be done by creating a custom widget
whose appearance could be toggled. But what if you wanted to make
an arbitrary actor's appearance "togglable"? A generic effect
in the style of the border effect in this recipe can be applied
to any actor, and easily toggled by enabling/disabling the
effect.</para>
<para><type>ClutterEffect</type> works best where
you want to overlay or underlay the actor with Cogl paths or
primitives, without changing the actor's geometry. If you want
to do complicated geometry transformations, or other subtle
manipulations of an actor's appearance, it is better to use
a <type>ClutterEffect</type> subclass like
<type>ClutterOffscreenEffect</type>, <type>ClutterDeformEffect</type>,
or <type>ClutterShaderEffect</type>.</para>
<para>In a similar vein, when a <type>ClutterEffect</type> is
applied to an actor, the effect shouldn't paint outside the actor's
allocation. However, if the effect provides a
<function>get_paint_volume()</function> implementation which
returns a volume larger than the actor's allocation, the effect
<emphasis>can</emphasis> paint anywhere within that volume. Though
in most cases, creating a custom paint volume is only going to be
useful for offscreen effects, where you are changing the
actor's geometry.</para>
<section id="effects-basic-discussion-properties">
<title>Effect properties</title>
<para>If your effect has GObject properties, you should
ensure that an actor associated with the effect is queued
for a redraw when those properties change. (You only need to
do this for properties which change the effect's appearance;
but this is likely to include most of an effect's properties.)</para>
<para>In most cases, you're likely define standard GObject
properties for the class; for example,
<link linkend="effects-basic-example-2"><type>CbBorderEffect</type></link>
defines a <varname>width</varname> property like this:</para>
<informalexample>
<programlisting>
<![CDATA[
static void
cb_border_effect_class_init (CbBorderEffectClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GParamSpec *pspec;
/* ...more class initialization code here... */
pspec = g_param_spec_float ("width",
"Width",
"The width of the border (in pixels)",
1.0, 100.0,
10.0,
G_PARAM_READWRITE);
obj_props[PROP_WIDTH] = pspec;
g_object_class_install_property (gobject_class, PROP_WIDTH, pspec);
/* ...more property definitions...*/
}
]]>
</programlisting>
</informalexample>
<para>It also defines a standard GObject
<function>set_property()</function> function for
<varname>width</varname>:</para>
<informalexample>
<programlisting>
<![CDATA[
static void
cb_border_effect_set_property (GObject *gobject,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
CbBorderEffect *effect = CB_BORDER_EFFECT (gobject);
switch (prop_id)
{
/* ...other cases here ... */
case PROP_WIDTH:
cb_border_effect_set_width (effect, g_value_get_float (value));
break;
/* ...default case ... */
}
}
]]>
</programlisting>
</informalexample>
<para>Note that this calls
<function>cb_border_effect_set_width()</function>, which is
also exposed in the public API. This is where the
<varname>width</varname> member variable is actually set in
the private struct; and also where the redraw for the actor
associated with the effect should be queued:</para>
<informalexample>
<programlisting>
<![CDATA[
/* queues a redraw of the actor associated with the effect, if there is one */
static void
cb_border_effect_update (CbBorderEffect *self)
{
ClutterActor *actor = clutter_actor_meta_get_actor (CLUTTER_ACTOR_META (self));
/* this guard is necessary as an effect's properties can be manipulated
* before it has an actor associated with it
*/
if (actor != NULL)
clutter_actor_queue_redraw (actor);
}
/* public setter for the width property, which calls the update function */
void
cb_border_effect_set_width (CbBorderEffect *self,
gfloat width)
{
CbBorderEffectPrivate *priv;
g_return_if_fail (CB_IS_BORDER_EFFECT (self));
priv = CB_BORDER_EFFECT_GET_PRIVATE (self);
priv->width = width;
/* the property has been updated, so queue a redraw of the actor (if set) */
cb_border_effect_update (self);
}
]]>
</programlisting>
</informalexample>
<para>Any other property setters which affect the associated
actor's appearance (i.e. color in the case of
<type>CbBorderEffect</type>) should also call the update
function after setting the property.</para>
<tip>
<para>If your effect exposes GObject properties in this way,
it can also be animated with the Clutter animation API as usual.
For example, you could animate the border effect in this recipe
so that the border gradually becomes thinner or thicker.</para>
</tip>
</section>
</section>
<section id="effects-basic-example">
<title>Full example</title>
<para>The example application applies two effects to a
group of <type>ClutterTextures</type>:</para>
<itemizedlist>
<listitem>
<para>A <type>CbBackgroundEffect</type> which draws a gray
background under each actor. The effect is implemented in
<link linkend="effects-basic-example-1">a header
file</link> and <link linkend="effects-basic-example-2">a C
code file</link>.</para>
</listitem>
<listitem>
<para>A <type>CbBorderEffect</type> which draws a
red border on top of an actor; this is toggled by clicking
on the actor. The effect is implemented in
<link linkend="effects-basic-example-3">a header
file</link> and <link linkend="effects-basic-example-4">a C
code file</link>.</para>
</listitem>
</itemizedlist>
<para>The <link linkend="effects-basic-example-5">application</link>
creates textures from the file paths specified
on the command line then applies both of these effects to
each texture. In the case of the <type>CbBorderEffect</type>,
a 5 pixel red border is applied; this is also disabled by default,
and enabled when a texture is clicked.</para>
<para>Here is an example of the output when the application is loaded
with four images:</para>
<screenshot>
<mediaobject>
<imageobject>
<imagedata format="PNG"
fileref="images/effects-basic.png" />
</imageobject>
<alt>
<para>Applying <type>CbBackgroundEffect</type>
and a togglable <type>CbBorderEffect</type>
to a several textures</para>
</alt>
</mediaobject>
</screenshot>
<section id="effects-basic-example-cbbackgroundeffect">
<title><type>CbBackgroundEffect</type></title>
<example id="effects-basic-example-1">
<title><filename>cb-background-effect.h</filename> (header file)</title>
<programlisting>
<xi:include href="examples/cb-background-effect.h" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
<example id="effects-basic-example-2">
<title><filename>cb-background-effect.c</filename> (code file)</title>
<programlisting>
<xi:include href="examples/cb-background-effect.c" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
</section>
<section id="effects-basic-example-cbbordereffect">
<title><type>CbBorderEffect</type></title>
<para>This is a more sophisticated effect with configurable
border color and width.</para>
<example id="effects-basic-example-3">
<title><filename>cb-border-effect.h</filename> (header file)</title>
<programlisting>
<xi:include href="examples/cb-border-effect.h" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
<example id="effects-basic-example-4">
<title><filename>cb-border-effect.c</filename> (code file)</title>
<programlisting>
<xi:include href="examples/cb-border-effect.c" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
</section>
<section id="effects-basic-example-application">
<title>Application</title>
<example id="effects-basic-example-5">
<title>Application which applies <type>CbBorderEffect</type>
and <type>CbBackgroundEffect</type> to a group of
<type>ClutterTextures</type>.</title>
<programlisting>
<xi:include href="examples/effects-basic.c" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
</section>
</section>
</section>
<section id="effects-custom-deform">
<title>Creating and animating a custom <type>ClutterDeformEffect</type></title>
<section>
<title>Problem</title>
<para>You want to deform an actor's geometry: for example,
to make it appear stretched, twisted or folded.</para>
<para>This recipe demonstrates how to do this with a simple page
fold effect, which folds one half of the actor over its other half.</para>
</section>
<section id="effects-custom-deform-solution">
<title>Solution</title>
<para>Subclass <type>ClutterDeformEffect</type> and
implement a <function>deform_vertex()</function> function
to modify the actor's vertices.</para>
<para>The signature for <function>deform_vertex()</function>
is:</para>
<informalexample>
<programlisting>
void
deform_vertex (ClutterDeformEffect *effect,
gfloat width,
gfloat height,
CoglTextureVertex *vertex);
</programlisting>
</informalexample>
<para>The <varname>width</varname> and <varname>height</varname>
are the width and height of the target material, stored in
the offscreen buffer. Usually the target material's size will
match the actor's transformed size; however, if the effect
implements <function>create_texture()</function>, the target
material's size may differ from the actor's transformed size.</para>
<para>The <varname>vertex</varname> contains the position
and color of a vertex, to be deformed by your effect.
Your <function>deform_vertex()</function>
function should modify the member variables of this
<type>CoglTextureVertex</type> in place. Usually, this will
mean modifying the <varname>x</varname>, <varname>y</varname>
and <varname>y</varname> member variables of the vertex,
which describe its position in 3D space.</para>
<para>The example function below, taken from
<link linkend="effects-custom-deform-example-2">the
full example</link>, applies a transformation to vertices falling
in the "right-hand" half of the actor (i.e. vertices with an
<varname>x</varname> value greater than or equal to half the
width of the actor).</para>
<informalexample>
<programlisting>
static void
cb_page_fold_effect_deform_vertex (ClutterDeformEffect *effect,
gfloat width,
gfloat height,
CoglTextureVertex *vertex)
{
CbPageFoldEffectPrivate *priv = CB_PAGE_FOLD_EFFECT (effect)->priv;
/* the rotation angle is modified by the percentage progress of the fold,
* as represented by the period variable
*/
gfloat radians = (priv->angle * priv->period) / (180.0f / G_PI);
/* rotate from the center of the actor on the y axis */
gfloat adjusted_x = vertex->x - (width / 2);
/* only rotate vertices to the right of the middle of the actor */
if (adjusted_x >= 0.0)
{
vertex->x = (vertex->z * sin (radians))
+ (adjusted_x * cos (radians))
+ width / 2;
/* NB add 1 to z to prevent "z fighting"; otherwise, when fully-folded
* the image has "stripes" where vertices from the folded part
* of the actor interfere with vertices from the unfolded part
*/
vertex->z = (vertex->z * cos (radians))
+ (adjusted_x * sin (radians))
+ 1;
}
/* adjust depth of all vertices so they fit inside the actor while folding;
* this has the effect of making the image smaller within the texture,
* but does produce a cleaner fold animation
*/
vertex->z -= width / 2;
}
</programlisting>
</informalexample>
<para>Note that this effect has two properties set in its
constructor or through setters:</para>
<orderedlist>
<listitem>
<para><varname>angle</varname>, representing the angle of
the full fold; for the actor to fully fold in half, this
would be set to 180.0</para>
</listitem>
<listitem>
<para><varname>period</varname>, representing the percentage
of the fold to apply</para>
</listitem>
</orderedlist>
<para>As well as rotating the vertex, the
<function>deform_vertex()</function> function also shifts
the <varname>z</varname> coordinate "up" by 1
(towards the viewpoint) for vertices on the right-hand side of the
actor. This is so that the "folded over" vertices
are above vertices on the left-hand side. Without this small
shift, the vertices interfere with each other, which can cause striping
artefacts.</para>
<para><emphasis>All</emphasis> vertices are also shifted "down",
so that the the folding part of the actor remains within the texture.
Otherwise the part which is folding may be clipped to the allocation of
the actor.</para>
<para>This effect can now be applied to an actor, using the
approach
<link linkend="effects-introduction-using-the-built-in-effects">outlined
in the introduction</link>. The result looks like this when
<varname>period</varname> is set to 0.25 and <varname>angle</varname>
to 180.0 (i.e. the page is folded by 45 degrees):</para>
<screenshot>
<mediaobject>
<imageobject>
<imagedata format="PNG"
fileref="images/effects-custom-deform.png" />
</imageobject>
<alt>
<para>Applying a custom <type>ClutterDeformEffect</type>
to a texture loaded with an image</para>
</alt>
</mediaobject>
</screenshot>
<para>Because the effect is a GObject which exposes its
properties, it can easily be animated, as described in
<link linkend="effects-custom-deform-discussion-animating">the
discussion section</link>.</para>
</section>
<section id="effects-custom-deform-discussion">
<title>Discussion</title>
<para>A deform effect processes an actor as follows:</para>
<itemizedlist>
<listitem>
<para>The actor is divided into a series of
triangular tiles. The number of
horizontal and vertical tiles is configurable;
more tiles implies more vertices. See
<link linkend="effects-custom-deform-discussion-tiles">this
section</link> for more details about tiles.</para>
</listitem>
<listitem>
<para>The position of each vertex of each
tile is then modified (or not) by the
<function>deform_vertex()</function> function. In this
function, you can change the vertex's position
(<varname>x</varname>, <varname>y</varname>,
<varname>z</varname> coordinates). You can also
modify the color at the vertex if desired.</para>
<para>The resulting deformed vertices are stored
in an offscreen buffer.</para>
</listitem>
<listitem>
<para>Once the deformation has been applied to
all vertices, the content of the offscreen buffer
is painted at the onscreen position of the actor.</para>
</listitem>
</itemizedlist>
<para>You may find it useful to visualise this process by imagining
your actor's surface as a net, composed of triangles. (Something
like a fishing net, not a mathematical one.) At each corner of
each triangle is a marble; and between each pair of corners
is an infinitely flexible length of elastic. Moving a marble
doesn't change the position of its neighbours; it just stretches
or relaxes the elastic.</para>
<para>In this analogy, the marbles are the vertices; and the
surfaces between the marbles, bordered by triangles of
elastic, are the tiles. More triangles (tiles) means more
marbles (vertices).</para>
<para>When you create a <type>ClutterDeformEffect</type>,
think of it as specifying movements of marbles in the net.
Changing the position of a vertex corresponds to moving a marble
up/down (-/+ <varname>y</varname> position), left/right
(-/+ <varname>x</varname> position) or away/towards
you (-/+ <varname>z</varname> position) (ignoring color for the
moment).</para>
<para>Now imagine that you are asked to fold the whole net of
marbles; but you can't just grab the edge of the net and pull
it over: you can only move one marble at a time. However, once moved,
each marble magically stays where you put it in 3D space.</para>
<para>To do this, you could project where each marble would be if
you could fold the whole sheet in one go; then move the
marbles one by one to their projected positions. Even though
you'd be moving the marbles one at a time, it would eventually
look as though you'd folded the whole net with a single movement.</para>
<para>When you write a <type>ClutterDeformEffect</type>, you have
to accomplish a similar feat: change the shape of an actor
by individually modifying the positions of points on its surface. In
most cases, your <function>deform_vertex()</function> implementation
can take advantage of an existing geometric transformation
method to achieve this. (For example, the page fold in this recipe
is based on equations from p.412 of <citetitle pubwork="book">Computer
Graphics (C Version), 2nd Edition</citetitle> by Hearn and
Baker, 1996.)</para>
<section>
<title>Customising the back material</title>
<para>When you set up a deform effect, you
can optionally specify a material to use for the "back" of
any actor it is applied to.</para>
<para>If you think of an actor as a sheet of paper with a
picture on it, specifying a back is similar to turning the
sheet of paper over (rotating it around the
<varname>y</varname> axis) and drawing another picture on
the other side. If you then folded or twisted the paper,
you would be able to see parts of the pictures on both the
front and back of the paper.</para>
<para>Similarly, during deformation of an actor, if any
vertices of the actor are deformed such that the actor's surface
is folded or twisted over itself, parts of its back
become visible. If you set a back material, you will see parts
of that where the surface is folded over. If you don't set a back
material, you will instead see mirror images of parts of the actor's
front: as if the actor was flexible stained glass, rather than paper.
You can see this if you watch the animation in
<link linkend="effects-custom-deform-discussion-animating">this
section</link>.</para>
<para>The back material should be an instance of
<type>CoglMaterial</type>. You can either create this via
the Cogl API directly; or indirectly through the Clutter API
(for example, by getting the material from a
<type>ClutterTexture</type>). The code below gives an example
of how to do the latter:</para>
<informalexample>
<programlisting>
<![CDATA[
/* create a texture */
ClutterActor *back = clutter_texture_new ();
clutter_texture_set_keep_aspect_ratio (CLUTTER_TEXTURE (back), TRUE);
clutter_actor_set_width (back, 400);
/* load image into texture (ignoring errors for brevity) */
clutter_texture_set_from_file (CLUTTER_TEXTURE (back),
back_image_file,
NULL);
/* get a handle to the texture's Cogl material */
CoglHandle material = clutter_texture_get_cogl_material (CLUTTER_TEXTURE (back));
/* cast the effect to ClutterDeformEffect and set its back material
* to the handle
*/
clutter_deform_effect_set_back_material (CLUTTER_DEFORM_EFFECT (effect),
material);
]]>
</programlisting>
</informalexample>
<para>See the <type>ClutterDeformEffect</type> API reference
for more details about back materials.</para>
<para>Here's a screenshot of the
<link linkend="effects-custom-deform-example-3">example</link>
with the addition of a back material, folded at an angle
of 60 degrees:</para>
<screenshot>
<mediaobject>
<imageobject>
<imagedata format="PNG"
fileref="images/effects-custom-deform-back-material.png" />
</imageobject>
<alt>
<para>Applying a custom <type>ClutterDeformEffect</type>
to a texture loaded with an image</para>
</alt>
</mediaobject>
</screenshot>
</section>
<section id="effects-custom-deform-discussion-animating">
<title>Animating a custom deform effect</title>
<para>Clutter's animation API can animate any GObject which
exposes its properties. In the case of the page fold effect,
we can expose the <varname>period</varname> property using
standard GObject property installation:</para>
<informalexample>
<programlisting>
/* GObject class init */
static void
cb_page_fold_effect_class_init (CbPageFoldEffectClass *klass)
{
GParamSpec *pspec;
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
/* ...other class setup code... */
/* expose the period as a GObject property */
pspec = g_param_spec_double ("period",
"Period",
"The period of the page fold",
0.0, 1.0,
0.0,
G_PARAM_READWRITE);
obj_props[PROP_PERIOD] = pspec;
g_object_class_install_property (gobject_class, PROP_PERIOD, pspec);
/* ...install other properties... */
}
</programlisting>
</informalexample>
<para>We also add a <function>get_property()</function>
implementation, as well as a setter (see
<link linkend="effects-custom-deform-example-2">the full
GObject implementation</link> for details).</para>
<para>Then set up an animation for the property; in this case,
using a <type>ClutterState</type>:</para>
<informalexample>
<programlisting>
ClutterEffect *effect = cb_page_fold_effect_new (180.0, 0.0);
ClutterState *transitions = clutter_state_new ();
clutter_state_set_duration (transitions, NULL, NULL, 500);
clutter_state_set (transitions, NULL, "unfolded",
effect, "period", CLUTTER_LINEAR, 0.0,
NULL);
clutter_state_set (transitions, NULL, "folded",
effect, "period", CLUTTER_LINEAR, 1.0,
NULL);
</programlisting>
</informalexample>
<para>To start the animation, warp the <type>ClutterState</type>
into its <emphasis>"unfolded"</emphasis> state, then set it to
<emphasis>"folded"</emphasis>:</para>
<informalexample>
<programlisting>
/* this changes state instantaneously */
clutter_state_warp_to_state (transitions, "unfolded");
/* this starts an animation to the state */
clutter_state_set_state (transitions, "folded");
</programlisting>
</informalexample>
<para>Note that the
<link linkend="effects-custom-deform-example-3">full code
sample</link> is slightly more complex, as it triggers state
changes when a mouse button is pressed on the texture. There is
also a third "partially folded" state (used to create
the screenshot for the
<link linkend="effects-custom-deform-solution">previous
section</link>).</para>
<para>Here's what the resulting animation looks like:</para>
<inlinemediaobject>
<videoobject>
<videodata fileref="videos/effects-custom-deform.ogv"/>
</videoobject>
<alt>
<para>Video showing animation of a custom deform effect
on a texture</para>
</alt>
</inlinemediaobject>
</section>
<section id="effects-custom-deform-discussion-tiles">
<title>Tiles</title>
<para>A <type>ClutterDeformEffect</type> divides the actor
being deformed into a number of tiles: the larger the number
of tiles, the larger the number of vertices to be manipulated
by the effect. Increasing the number of tiles increases the number of
vertex computations required, which can slow down animations;
at the same time, finer-grained tiles can make an effect appear
smoother, particularly when animated.</para>
<para>Most of the time, the default number
of tiles in the <varname>x</varname> and <varname>y</varname>
axes should suffice. You can get the current number of
tiles associated with an effect with:</para>
<informalexample>
<programlisting>
<![CDATA[
guint x_tiles;
guint y_tiles;
/* effect must be a subclass of ClutterDeformEffect */
clutter_deform_effect_get_n_tiles (CLUTTER_DEFORM_EFFECT (effect),
&x_tiles,
&y_tiles);
]]>
</programlisting>
</informalexample>
<para>However, if an effect produces jerky or fragmented output,
you want to tweak the number of tiles. Use the
<function>clutter_deform_effect_set_n_tiles()</function> function
to do this:</para>
<informalexample>
<programlisting>
/* 64 tiles in both axes */
guint x_tiles = 64;
guint y_tiles = 64;
clutter_deform_effect_set_n_tiles (CLUTTER_DEFORM_EFFECT (effect),
x_tiles,
y_tiles);
</programlisting>
</informalexample>
</section>
</section>
<section>
<title>Full example</title>
<para>This example consists of three files:</para>
<itemizedlist>
<listitem>
<para><link linkend="effects-custom-deform-example-1">A header
file</link> for the <type>CbPageFoldEffect</type> GObject.</para>
</listitem>
<listitem>
<para><link linkend="effects-custom-deform-example-2">The
code file</link> implementing <type>CbPageFoldEffect</type>.</para>
</listitem>
<listitem>
<para><link linkend="effects-custom-deform-example-3">A short
sample application</link> which applies a <type>CbPageFoldEffect</type>
instance to an actor and animates the fold when the actor is
clicked.</para>
</listitem>
</itemizedlist>
<para>As Clutter effect subclasses are written using GObject,
you might find <link linkend="actors-composite">this recipe</link>
(which goes into GObject in more detail) a useful introduction.</para>
<example id="effects-custom-deform-example-1">
<title><filename>cb-page-fold-effect.h</filename> (header file)</title>
<programlisting>
<xi:include href="examples/cb-page-fold-effect.h" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
<example id="effects-custom-deform-example-2">
<title><filename>cb-page-fold-effect.c</filename> (code file)</title>
<programlisting>
<xi:include href="examples/cb-page-fold-effect.c" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
<example id="effects-custom-deform-example-3">
<title>Application which uses <type>CbPageFoldEffect</type>
to do animated folding of a <type>ClutterTexture</type></title>
<programlisting>
<xi:include href="examples/effects-custom-deform.c" parse="text">
<xi:fallback>a code sample should be here... but isn't</xi:fallback>
</xi:include>
</programlisting>
</example>
</section>
</section>
</chapter>