mutter/clutter/clutter-paint-volume.c

816 lines
21 KiB
C
Raw Normal View History

/*
* Clutter.
*
* An OpenGL based 'interactive canvas' library.
*
* Copyright (C) 2010 Intel Corporation.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Authors:
* Robert Bragg <robert@linux.intel.com>
* Emmanuele Bassi <ebassi@linux.intel.com>
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <glib-object.h>
#include "clutter-private.h"
GType
clutter_paint_volume_get_type (void)
{
static GType our_type = 0;
if (G_UNLIKELY (our_type == 0))
our_type =
g_boxed_type_register_static (I_("ClutterPaintVolume"),
(GBoxedCopyFunc) clutter_paint_volume_copy,
(GBoxedFreeFunc) clutter_paint_volume_free);
return our_type;
}
/*<private>
* _clutter_paint_volume_new:
* @actor: a #ClutterActor
*
* Creates a new #ClutterPaintVolume for the given @actor.
*
* Return value: the newly allocated #ClutterPaintVolume. Use
* clutter_paint_volume_free() to free the resources it uses
*
* Since: 1.4
*/
ClutterPaintVolume *
_clutter_paint_volume_new (ClutterActor *actor)
{
ClutterPaintVolume *pv;
g_return_val_if_fail (actor != NULL, NULL);
pv = g_slice_new (ClutterPaintVolume);
pv->actor = g_object_ref (actor);
memset (pv->vertices, 0, 8 * sizeof (ClutterVertex));
pv->is_static = FALSE;
pv->is_empty = TRUE;
pv->is_axis_aligned = TRUE;
pv->is_complete = TRUE;
pv->is_2d = TRUE;
return pv;
}
/* Since paint volumes are used so heavily in a typical paint
* traversal of a Clutter scene graph and since paint volumes often
* have a very short life cycle that maps well to stack allocation we
* allow initializing a static ClutterPaintVolume variable to avoid
* hammering the slice allocator.
*
* We were seeing slice allocation take about 1% cumulative CPU time
* for some very simple clutter tests which although it isn't a *lot*
* this is an easy way to basically drop that to 0%.
*
* The PaintVolume will be internally marked as static and
* clutter_paint_volume_free should still be used to "free" static
* volumes. This allows us to potentially store dynamically allocated
* data inside paint volumes in the future since we would be able to
* free it during _paint_volume_free().
*/
void
_clutter_paint_volume_init_static (ClutterActor *actor,
ClutterPaintVolume *pv)
{
g_return_if_fail (actor != NULL);
pv->actor = g_object_ref (actor);
memset (pv->vertices, 0, 8 * sizeof (ClutterVertex));
pv->is_static = TRUE;
pv->is_empty = TRUE;
pv->is_axis_aligned = TRUE;
pv->is_complete = TRUE;
pv->is_2d = TRUE;
}
void
_clutter_paint_volume_copy_static (const ClutterPaintVolume *src_pv,
ClutterPaintVolume *dst_pv)
{
g_return_if_fail (src_pv != NULL && dst_pv != NULL);
memcpy (dst_pv, src_pv, sizeof (ClutterPaintVolume));
g_object_ref (dst_pv->actor);
dst_pv->is_static = TRUE;
}
/**
* clutter_paint_volume_copy:
* @pv: a #ClutterPaintVolume
*
* Copies @pv into a new #ClutterPaintVolume
*
* Return value: a newly allocated copy of a #ClutterPaintVolume
*
* Since: 1.4
*/
ClutterPaintVolume *
clutter_paint_volume_copy (const ClutterPaintVolume *pv)
{
ClutterPaintVolume *copy;
g_return_val_if_fail (pv != NULL, NULL);
copy = g_slice_dup (ClutterPaintVolume, pv);
if (copy->actor)
g_object_ref (copy->actor);
copy->is_static = FALSE;
return copy;
}
/**
* clutter_paint_volume_free:
* @pv: a #ClutterPaintVolume
*
* Frees the resources allocated by @pv
*
* Since: 1.4
*/
void
clutter_paint_volume_free (ClutterPaintVolume *pv)
{
g_return_if_fail (pv != NULL);
g_object_unref (pv->actor);
if (G_LIKELY (pv->is_static))
return;
g_slice_free (ClutterPaintVolume, pv);
}
/**
* clutter_paint_volume_set_origin:
* @pv: a #ClutterPaintVolume
* @origin: a #ClutterVertex
*
* Sets the origin of the paint volume.
*
* The origin is defined as the X, Y and Z coordinates of the top-left
* corner of an actor's paint volume, in actor coordinates.
*
* The default is origin is assumed at: (0, 0, 0)
*
* Since: 1.4
*/
void
clutter_paint_volume_set_origin (ClutterPaintVolume *pv,
const ClutterVertex *origin)
{
float dx = origin->x - pv->vertices[0].x;
float dy = origin->y - pv->vertices[0].y;
float dz = origin->z - pv->vertices[0].z;
int key_vertices[4] = {0, 1, 3, 4};
int i;
g_return_if_fail (pv != NULL);
g_return_if_fail (pv->is_axis_aligned);
/* If we change the origin then all the key vertices of the paint
* volume need to be shifted too... */
for (i = 0; i < 4; i++)
{
pv->vertices[key_vertices[i]].x += dx;
pv->vertices[key_vertices[i]].y += dy;
pv->vertices[key_vertices[i]].z += dz;
}
pv->is_complete = FALSE;
}
/**
* clutter_paint_volume_get_origin:
* @pv: a #ClutterPaintVolume
* @vertex: (out): the return location for a #ClutterVertex
*
* Retrieves the origin of the #ClutterPaintVolume.
*
* Since: 1.4
*/
void
clutter_paint_volume_get_origin (const ClutterPaintVolume *pv,
ClutterVertex *vertex)
{
g_return_if_fail (pv != NULL);
g_return_if_fail (vertex != NULL);
*vertex = pv->vertices[0];
}
static void
_clutter_paint_volume_update_is_empty (ClutterPaintVolume *pv)
{
if (pv->vertices[0].x == pv->vertices[1].x &&
pv->vertices[0].y == pv->vertices[3].y &&
pv->vertices[0].z == pv->vertices[4].z)
pv->is_empty = TRUE;
else
pv->is_empty = FALSE;
}
/**
* clutter_paint_volume_set_width:
* @pv: a #ClutterPaintVolume
* @width: the width of the paint volume, in pixels
*
* Sets the width of the paint volume.
*
* Since: 1.4
*/
void
clutter_paint_volume_set_width (ClutterPaintVolume *pv,
gfloat width)
{
gfloat right_xpos;
g_return_if_fail (pv != NULL);
g_return_if_fail (pv->is_axis_aligned);
g_return_if_fail (width >= 0.0f);
/* If the volume is currently empty then only the origin is
* currently valid */
if (pv->is_empty)
pv->vertices[1] = pv->vertices[3] = pv->vertices[4] = pv->vertices[0];
right_xpos = pv->vertices[0].x + width;
/* Move the right vertices of the paint box relative to the
* origin... */
pv->vertices[1].x = right_xpos;
/* pv->vertices[2].x = right_xpos; NB: updated lazily */
/* pv->vertices[5].x = right_xpos; NB: updated lazily */
/* pv->vertices[6].x = right_xpos; NB: updated lazily */
pv->is_complete = FALSE;
_clutter_paint_volume_update_is_empty (pv);
}
/**
* clutter_paint_volume_get_width:
* @pv: a #ClutterPaintVolume
*
* Retrieves the width set using clutter_paint_volume_get_width()
*
* Return value: the width, in pixels
*
* Since: 1.4
*/
gfloat
clutter_paint_volume_get_width (const ClutterPaintVolume *pv)
{
g_return_val_if_fail (pv != NULL, 0.0);
g_return_val_if_fail (pv->is_axis_aligned, 0);
if (pv->is_empty)
return 0;
else
return pv->vertices[1].x - pv->vertices[0].x;
}
/**
* clutter_paint_volume_set_height:
* @pv: a #ClutterPaintVolume
* @height: the height of the paint volume, in pixels
*
* Sets the height of the paint volume.
*
* Since: 1.4
*/
void
clutter_paint_volume_set_height (ClutterPaintVolume *pv,
gfloat height)
{
gfloat height_ypos;
g_return_if_fail (pv != NULL);
g_return_if_fail (pv->is_axis_aligned);
g_return_if_fail (height >= 0.0f);
/* If the volume is currently empty then only the origin is
* currently valid */
if (pv->is_empty)
pv->vertices[1] = pv->vertices[3] = pv->vertices[4] = pv->vertices[0];
height_ypos = pv->vertices[0].y + height;
/* Move the bottom vertices of the paint box relative to the
* origin... */
/* pv->vertices[2].y = height_ypos; NB: updated lazily */
pv->vertices[3].y = height_ypos;
/* pv->vertices[6].y = height_ypos; NB: updated lazily */
/* pv->vertices[7].y = height_ypos; NB: updated lazily */
pv->is_complete = FALSE;
_clutter_paint_volume_update_is_empty (pv);
}
/**
* clutter_paint_volume_get_height:
* @pv: a #ClutterPaintVolume
*
* Retrieves the height of the paint volume set using
* clutter_paint_volume_get_height()
*
* Return value: the height of the paint volume, in pixels
*
* Since: 1.4
*/
gfloat
clutter_paint_volume_get_height (const ClutterPaintVolume *pv)
{
g_return_val_if_fail (pv != NULL, 0.0);
g_return_val_if_fail (pv->is_axis_aligned, 0);
if (pv->is_empty)
return 0;
else
return pv->vertices[3].y - pv->vertices[0].y;
}
/**
* clutter_paint_volume_set_depth:
* @pv: a #ClutterPaintVolume
* @depth: the depth of the paint volume, in pixels
*
* Sets the depth of the paint volume.
*
* Since: 1.4
*/
void
clutter_paint_volume_set_depth (ClutterPaintVolume *pv,
gfloat depth)
{
gfloat depth_zpos;
g_return_if_fail (pv != NULL);
g_return_if_fail (pv->is_axis_aligned);
g_return_if_fail (depth >= 0.0f);
/* If the volume is currently empty then only the origin is
* currently valid */
if (pv->is_empty)
pv->vertices[1] = pv->vertices[3] = pv->vertices[4] = pv->vertices[0];
depth_zpos = pv->vertices[0].z + depth;
/* Move the back vertices of the paint box relative to the
* origin... */
pv->vertices[4].z = depth_zpos;
/* pv->vertices[5].z = depth_zpos; NB: updated lazily */
/* pv->vertices[6].z = depth_zpos; NB: updated lazily */
/* pv->vertices[7].z = depth_zpos; NB: updated lazily */
pv->is_complete = FALSE;
pv->is_2d = depth ? FALSE : TRUE;
_clutter_paint_volume_update_is_empty (pv);
}
/**
* clutter_paint_volume_get_depth:
* @pv: a #ClutterPaintVolume
*
* Retrieves the depth of the paint volume set using
* clutter_paint_volume_get_depth()
*
* Return value: the depth
*
* Since: 1.4
*/
gfloat
clutter_paint_volume_get_depth (const ClutterPaintVolume *pv)
{
g_return_val_if_fail (pv != NULL, 0.0);
g_return_val_if_fail (pv->is_axis_aligned, 0);
if (pv->is_empty)
return 0;
else
return pv->vertices[4].z - pv->vertices[0].z;
}
/**
* clutter_paint_volume_union:
* @pv: The first #ClutterPaintVolume and destination for resulting
* union
* @another_pv: A second #ClutterPaintVolume to union with @pv
*
* Updates the geometry of @pv to be the union bounding box that
* encompases @pv and @another_pv.
*
* Since: 1.4
*/
void
clutter_paint_volume_union (ClutterPaintVolume *pv,
const ClutterPaintVolume *another_pv)
{
int key_vertices[4] = {0, 1, 3, 4};
g_return_if_fail (pv != NULL);
g_return_if_fail (pv->is_axis_aligned);
g_return_if_fail (another_pv != NULL);
g_return_if_fail (another_pv->is_axis_aligned);
/* NB: we only have to update vertices 0, 1, 3 and 4
* (See the ClutterPaintVolume typedef for more details) */
/* We special case empty volumes because otherwise we'd end up
* calculating a bounding box that would enclose the origin of
* the empty volume which isn't desired.
*/
if (another_pv->is_empty)
return;
if (pv->is_empty)
{
int i;
for (i = 0; i < 4; i++)
pv->vertices[key_vertices[i]] = another_pv->vertices[key_vertices[i]];
pv->is_2d = another_pv->is_2d;
goto done;
}
/* grow left*/
/* left vertices 0, 3, 4, 7 */
if (another_pv->vertices[0].x < pv->vertices[0].x)
{
int min_x = another_pv->vertices[0].x;
pv->vertices[0].x = min_x;
pv->vertices[3].x = min_x;
pv->vertices[4].x = min_x;
/* pv->vertices[7].x = min_x; */
}
/* grow right */
/* right vertices 1, 2, 5, 6 */
if (another_pv->vertices[1].x > pv->vertices[1].x)
{
int max_x = another_pv->vertices[1].x;
pv->vertices[1].x = max_x;
/* pv->vertices[2].x = max_x; */
/* pv->vertices[5].x = max_x; */
/* pv->vertices[6].x = max_x; */
}
/* grow up */
/* top vertices 0, 1, 4, 5 */
if (another_pv->vertices[0].y < pv->vertices[0].y)
{
int min_y = another_pv->vertices[0].y;
pv->vertices[0].y = min_y;
pv->vertices[1].y = min_y;
pv->vertices[4].y = min_y;
/* pv->vertices[5].y = min_y; */
}
/* grow down */
/* bottom vertices 2, 3, 6, 7 */
if (another_pv->vertices[3].y > pv->vertices[3].y)
{
int may_y = another_pv->vertices[3].y;
/* pv->vertices[2].y = may_y; */
pv->vertices[3].y = may_y;
/* pv->vertices[6].y = may_y; */
/* pv->vertices[7].y = may_y; */
}
/* grow forward */
/* front vertices 0, 1, 2, 3 */
if (another_pv->vertices[0].z < pv->vertices[0].z)
{
int min_z = another_pv->vertices[0].z;
pv->vertices[0].z = min_z;
pv->vertices[1].z = min_z;
/* pv->vertices[2].z = min_z; */
pv->vertices[3].z = min_z;
}
/* grow backward */
/* back vertices 4, 5, 6, 7 */
if (another_pv->vertices[4].z > pv->vertices[4].z)
{
int maz_z = another_pv->vertices[4].z;
pv->vertices[4].z = maz_z;
/* pv->vertices[5].z = maz_z; */
/* pv->vertices[6].z = maz_z; */
/* pv->vertices[7].z = maz_z; */
}
if (pv->vertices[4].z == pv->vertices[0].z)
pv->is_2d = TRUE;
else
pv->is_2d = FALSE;
done:
pv->is_empty = FALSE;
pv->is_complete = FALSE;
}
/* The paint_volume setters only update vertices 0, 1, 3 and
* 4 since the others can be drived from them.
*
* This will set pv->completed = TRUE;
*/
void
_clutter_paint_volume_complete (ClutterPaintVolume *pv)
{
if (pv->is_complete || pv->is_empty)
return;
/* TODO: it is possible to complete non axis aligned volumes too. */
g_return_if_fail (pv->is_axis_aligned);
/* front-bottom-right */
pv->vertices[2].x = pv->vertices[1].x;
pv->vertices[2].y = pv->vertices[3].y;
pv->vertices[2].z = pv->vertices[0].z;
if (G_UNLIKELY (!pv->is_2d))
{
/* back-top-right */
pv->vertices[5].x = pv->vertices[1].x;
pv->vertices[5].y = pv->vertices[0].y;
pv->vertices[5].z = pv->vertices[4].z;
/* back-bottom-right */
pv->vertices[6].x = pv->vertices[1].x;
pv->vertices[6].y = pv->vertices[3].y;
pv->vertices[6].z = pv->vertices[4].z;
/* back-bottom-left */
pv->vertices[7].x = pv->vertices[0].x;
pv->vertices[7].y = pv->vertices[3].y;
pv->vertices[7].z = pv->vertices[4].z;
}
pv->is_complete = TRUE;
}
/*<private>
* _clutter_paint_volume_get_box:
* @pv: a #ClutterPaintVolume
* @box: a pixel aligned #ClutterGeometry
*
* Transforms a 3D paint volume into a 2D bounding box in the
* same coordinate space as the 3D paint volume.
*
* To get an actors "paint box" you should first project
* the paint volume into window coordinates before getting
* the 2D bounding box.
*
* <note>The coordinates of the returned box are not clamped to
* integer pixel values, if you need them to be clamped you can use
* clutter_actor_box_clamp_to_pixel()</note>
*
* Since: 1.4
*/
void
_clutter_paint_volume_get_bounding_box (ClutterPaintVolume *pv,
ClutterActorBox *box)
{
gfloat x_min, y_min, x_max, y_max;
ClutterVertex *vertices;
int count;
gint i;
g_return_if_fail (pv != NULL);
g_return_if_fail (box != NULL);
if (pv->is_empty)
{
box->x1 = box->x2 = pv->vertices[0].x;
box->y1 = box->y2 = pv->vertices[0].y;
return;
}
/* Updates the vertices we calculate lazily
* (See ClutterPaintVolume typedef for more details) */
_clutter_paint_volume_complete (pv);
vertices = pv->vertices;
x_min = x_max = vertices[0].x;
y_min = y_max = vertices[0].y;
/* Most actors are 2D so we only have to look at the front 4
* vertices of the paint volume... */
if (G_LIKELY (pv->is_2d))
count = 4;
else
count = 8;
for (i = 1; i < count; i++)
{
if (vertices[i].x < x_min)
x_min = vertices[i].x;
else if (vertices[i].x > x_max)
x_max = vertices[i].x;
if (vertices[i].y < y_min)
y_min = vertices[i].y;
else if (vertices[i].y > y_max)
y_max = vertices[i].y;
}
box->x1 = x_min;
box->y1 = y_min;
box->x2 = x_max;
box->y2 = y_max;
}
void
_clutter_paint_volume_project (ClutterPaintVolume *pv,
const CoglMatrix *modelview,
const CoglMatrix *projection,
const int *viewport)
{
int transform_count;
if (pv->is_empty)
{
/* Just transform the origin... */
_clutter_util_fully_transform_vertices (modelview,
projection,
viewport,
pv->vertices,
pv->vertices,
1);
return;
}
/* All the vertices must be up to date, since after the projection
* it wont be trivial to derive the other vertices. */
_clutter_paint_volume_complete (pv);
/* Most actors are 2D so we only have to transform the front 4
* vertices of the paint volume... */
if (G_LIKELY (pv->is_2d))
transform_count = 4;
else
transform_count = 8;
_clutter_util_fully_transform_vertices (modelview,
projection,
viewport,
pv->vertices,
pv->vertices,
transform_count);
pv->is_axis_aligned = FALSE;
}
void
_clutter_paint_volume_transform (ClutterPaintVolume *pv,
const CoglMatrix *matrix)
{
int transform_count;
int i;
if (pv->is_empty)
{
gfloat w = 1;
/* Just transform the origin */
cogl_matrix_transform_point (matrix,
&pv->vertices[0].x,
&pv->vertices[0].y,
&pv->vertices[0].z,
&w);
return;
}
/* All the vertices must be up to date, since after the transform
* it wont be trivial to derive the other vertices. */
_clutter_paint_volume_complete (pv);
/* Most actors are 2D so we only have to transform the front 4
* vertices of the paint volume... */
if (G_LIKELY (pv->is_2d))
transform_count = 4;
else
transform_count = 8;
for (i = 0; i < transform_count; i++)
{
gfloat w = 1;
cogl_matrix_transform_point (matrix,
&pv->vertices[i].x,
&pv->vertices[i].y,
&pv->vertices[i].z,
&w);
}
pv->is_axis_aligned = FALSE;
}
/* Given a paint volume that has been transformed by an arbitrary
* modelview and is no longer axis aligned, this derives a replacement
* that is axis aligned. */
void
_clutter_paint_volume_axis_align (ClutterPaintVolume *pv)
{
int count;
int i;
ClutterVertex origin;
float max_x;
float max_y;
float max_z;
if (pv->is_empty)
return;
g_return_if_fail (pv->is_complete);
if (G_LIKELY (pv->is_axis_aligned))
return;
if (G_LIKELY (pv->vertices[0].x == pv->vertices[1].x &&
pv->vertices[0].y == pv->vertices[3].y &&
pv->vertices[0].z == pv->vertices[4].y))
{
pv->is_axis_aligned = TRUE;
return;
}
origin = pv->vertices[0];
max_x = pv->vertices[0].x;
max_y = pv->vertices[0].y;
max_z = pv->vertices[0].z;
count = pv->is_2d ? 4 : 8;
for (i = 1; i < count; i++)
{
if (pv->vertices[i].x < origin.x)
origin.x = pv->vertices[i].x;
else if (pv->vertices[i].x > max_x)
max_x = pv->vertices[i].x;
if (pv->vertices[i].y < origin.y)
origin.y = pv->vertices[i].y;
else if (pv->vertices[i].y > max_y)
max_y = pv->vertices[i].y;
if (pv->vertices[i].z < origin.z)
origin.z = pv->vertices[i].z;
else if (pv->vertices[i].z > max_z)
max_z = pv->vertices[i].z;
}
pv->vertices[0] = origin;
pv->vertices[1].x = max_x;
pv->vertices[1].y = origin.y;
pv->vertices[1].z = origin.z;
pv->vertices[3].x = origin.x;
pv->vertices[3].y = max_y;
pv->vertices[3].z = origin.z;
pv->vertices[4].x = origin.x;
pv->vertices[4].y = origin.y;
pv->vertices[4].z = max_z;
pv->is_complete = FALSE;
pv->is_axis_aligned = TRUE;
if (pv->vertices[4].z == pv->vertices[0].z)
pv->is_2d = TRUE;
else
pv->is_2d = FALSE;
}