1 /*
2 * Clutter.
3 *
4 * An OpenGL based 'interactive canvas' library.
5 *
6 * Copyright (C) 2010 Intel Corporation.
7 *
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
12 *
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public
19 * License along with this library. If not, see
20 * <http://www.gnu.org/licenses/>.
21 *
22 * Authors:
23 * Robert Bragg <robert@linux.intel.com>
24 * Emmanuele Bassi <ebassi@linux.intel.com>
25 */
26
27 #ifdef HAVE_CONFIG_H
28 #include "config.h"
29 #endif
30
31 #include <string.h>
32
33 #include <glib-object.h>
34 #include <math.h>
35
36 #include "clutter-actor-private.h"
37 #include "clutter-paint-volume-private.h"
38 #include "clutter-private.h"
39 #include "clutter-stage-private.h"
40
41 G_DEFINE_BOXED_TYPE (ClutterPaintVolume, clutter_paint_volume,
42 clutter_paint_volume_copy,
43 clutter_paint_volume_free);
44
45 /*<private>
46 * _clutter_paint_volume_new:
47 * @actor: a #ClutterActor
48 *
49 * Creates a new #ClutterPaintVolume for the given @actor.
50 *
51 * Return value: the newly allocated #ClutterPaintVolume. Use
52 * clutter_paint_volume_free() to free the resources it uses
53 *
54 * Since: 1.6
55 */
56 ClutterPaintVolume *
_clutter_paint_volume_new(ClutterActor * actor)57 _clutter_paint_volume_new (ClutterActor *actor)
58 {
59 ClutterPaintVolume *pv;
60
61 g_return_val_if_fail (actor != NULL, NULL);
62
63 pv = g_slice_new (ClutterPaintVolume);
64
65 pv->actor = actor;
66
67 memset (pv->vertices, 0, 8 * sizeof (ClutterVertex));
68
69 pv->is_static = FALSE;
70 pv->is_empty = TRUE;
71 pv->is_axis_aligned = TRUE;
72 pv->is_complete = TRUE;
73 pv->is_2d = TRUE;
74
75 return pv;
76 }
77
78 /* Since paint volumes are used so heavily in a typical paint
79 * traversal of a Clutter scene graph and since paint volumes often
80 * have a very short life cycle that maps well to stack allocation we
81 * allow initializing a static ClutterPaintVolume variable to avoid
82 * hammering the slice allocator.
83 *
84 * We were seeing slice allocation take about 1% cumulative CPU time
85 * for some very simple clutter tests which although it isn't a *lot*
86 * this is an easy way to basically drop that to 0%.
87 *
88 * The PaintVolume will be internally marked as static and
89 * clutter_paint_volume_free should still be used to "free" static
90 * volumes. This allows us to potentially store dynamically allocated
91 * data inside paint volumes in the future since we would be able to
92 * free it during _paint_volume_free().
93 */
94 void
_clutter_paint_volume_init_static(ClutterPaintVolume * pv,ClutterActor * actor)95 _clutter_paint_volume_init_static (ClutterPaintVolume *pv,
96 ClutterActor *actor)
97 {
98 pv->actor = actor;
99
100 memset (pv->vertices, 0, 8 * sizeof (ClutterVertex));
101
102 pv->is_static = TRUE;
103 pv->is_empty = TRUE;
104 pv->is_axis_aligned = TRUE;
105 pv->is_complete = TRUE;
106 pv->is_2d = TRUE;
107 }
108
109 void
_clutter_paint_volume_copy_static(const ClutterPaintVolume * src_pv,ClutterPaintVolume * dst_pv)110 _clutter_paint_volume_copy_static (const ClutterPaintVolume *src_pv,
111 ClutterPaintVolume *dst_pv)
112 {
113
114 g_return_if_fail (src_pv != NULL && dst_pv != NULL);
115
116 memcpy (dst_pv, src_pv, sizeof (ClutterPaintVolume));
117 dst_pv->is_static = TRUE;
118 }
119
120 /**
121 * clutter_paint_volume_copy:
122 * @pv: a #ClutterPaintVolume
123 *
124 * Copies @pv into a new #ClutterPaintVolume
125 *
126 * Return value: a newly allocated copy of a #ClutterPaintVolume
127 *
128 * Since: 1.6
129 */
130 ClutterPaintVolume *
clutter_paint_volume_copy(const ClutterPaintVolume * pv)131 clutter_paint_volume_copy (const ClutterPaintVolume *pv)
132 {
133 ClutterPaintVolume *copy;
134
135 g_return_val_if_fail (pv != NULL, NULL);
136
137 copy = g_slice_dup (ClutterPaintVolume, pv);
138 copy->is_static = FALSE;
139
140 return copy;
141 }
142
143 void
_clutter_paint_volume_set_from_volume(ClutterPaintVolume * pv,const ClutterPaintVolume * src)144 _clutter_paint_volume_set_from_volume (ClutterPaintVolume *pv,
145 const ClutterPaintVolume *src)
146 {
147 gboolean is_static = pv->is_static;
148 memcpy (pv, src, sizeof (ClutterPaintVolume));
149 pv->is_static = is_static;
150 }
151
152 /**
153 * clutter_paint_volume_free:
154 * @pv: a #ClutterPaintVolume
155 *
156 * Frees the resources allocated by @pv
157 *
158 * Since: 1.6
159 */
160 void
clutter_paint_volume_free(ClutterPaintVolume * pv)161 clutter_paint_volume_free (ClutterPaintVolume *pv)
162 {
163 g_return_if_fail (pv != NULL);
164
165 if (G_LIKELY (pv->is_static))
166 return;
167
168 g_slice_free (ClutterPaintVolume, pv);
169 }
170
171 /**
172 * clutter_paint_volume_set_origin:
173 * @pv: a #ClutterPaintVolume
174 * @origin: a #ClutterVertex
175 *
176 * Sets the origin of the paint volume.
177 *
178 * The origin is defined as the X, Y and Z coordinates of the top-left
179 * corner of an actor's paint volume, in actor coordinates.
180 *
181 * The default is origin is assumed at: (0, 0, 0)
182 *
183 * Since: 1.6
184 */
185 void
clutter_paint_volume_set_origin(ClutterPaintVolume * pv,const ClutterVertex * origin)186 clutter_paint_volume_set_origin (ClutterPaintVolume *pv,
187 const ClutterVertex *origin)
188 {
189 static const int key_vertices[4] = { 0, 1, 3, 4 };
190 float dx, dy, dz;
191 int i;
192
193 g_return_if_fail (pv != NULL);
194
195 dx = origin->x - pv->vertices[0].x;
196 dy = origin->y - pv->vertices[0].y;
197 dz = origin->z - pv->vertices[0].z;
198
199 /* If we change the origin then all the key vertices of the paint
200 * volume need to be shifted too... */
201 for (i = 0; i < 4; i++)
202 {
203 pv->vertices[key_vertices[i]].x += dx;
204 pv->vertices[key_vertices[i]].y += dy;
205 pv->vertices[key_vertices[i]].z += dz;
206 }
207
208 pv->is_complete = FALSE;
209 }
210
211 /**
212 * clutter_paint_volume_get_origin:
213 * @pv: a #ClutterPaintVolume
214 * @vertex: (out): the return location for a #ClutterVertex
215 *
216 * Retrieves the origin of the #ClutterPaintVolume.
217 *
218 * Since: 1.6
219 */
220 void
clutter_paint_volume_get_origin(const ClutterPaintVolume * pv,ClutterVertex * vertex)221 clutter_paint_volume_get_origin (const ClutterPaintVolume *pv,
222 ClutterVertex *vertex)
223 {
224 g_return_if_fail (pv != NULL);
225 g_return_if_fail (vertex != NULL);
226
227 *vertex = pv->vertices[0];
228 }
229
230 static void
_clutter_paint_volume_update_is_empty(ClutterPaintVolume * pv)231 _clutter_paint_volume_update_is_empty (ClutterPaintVolume *pv)
232 {
233 if (pv->vertices[0].x == pv->vertices[1].x &&
234 pv->vertices[0].y == pv->vertices[3].y &&
235 pv->vertices[0].z == pv->vertices[4].z)
236 pv->is_empty = TRUE;
237 else
238 pv->is_empty = FALSE;
239 }
240
241 /**
242 * clutter_paint_volume_set_width:
243 * @pv: a #ClutterPaintVolume
244 * @width: the width of the paint volume, in pixels
245 *
246 * Sets the width of the paint volume. The width is measured along
247 * the x axis in the actor coordinates that @pv is associated with.
248 *
249 * Since: 1.6
250 */
251 void
clutter_paint_volume_set_width(ClutterPaintVolume * pv,gfloat width)252 clutter_paint_volume_set_width (ClutterPaintVolume *pv,
253 gfloat width)
254 {
255 gfloat right_xpos;
256
257 g_return_if_fail (pv != NULL);
258 g_return_if_fail (width >= 0.0f);
259
260 /* If the volume is currently empty then only the origin is
261 * currently valid */
262 if (pv->is_empty)
263 pv->vertices[1] = pv->vertices[3] = pv->vertices[4] = pv->vertices[0];
264
265 if (!pv->is_axis_aligned)
266 _clutter_paint_volume_axis_align (pv);
267
268 right_xpos = pv->vertices[0].x + width;
269
270 /* Move the right vertices of the paint box relative to the
271 * origin... */
272 pv->vertices[1].x = right_xpos;
273 /* pv->vertices[2].x = right_xpos; NB: updated lazily */
274 /* pv->vertices[5].x = right_xpos; NB: updated lazily */
275 /* pv->vertices[6].x = right_xpos; NB: updated lazily */
276
277 pv->is_complete = FALSE;
278
279 _clutter_paint_volume_update_is_empty (pv);
280 }
281
282 /**
283 * clutter_paint_volume_get_width:
284 * @pv: a #ClutterPaintVolume
285 *
286 * Retrieves the width of the volume's, axis aligned, bounding box.
287 *
288 * In other words; this takes into account what actor's coordinate
289 * space @pv belongs too and conceptually fits an axis aligned box
290 * around the volume. It returns the size of that bounding box as
291 * measured along the x-axis.
292 *
293 * If, for example, clutter_actor_get_transformed_paint_volume()
294 * is used to transform a 2D child actor that is 100px wide, 100px
295 * high and 0px deep into container coordinates then the width might
296 * not simply be 100px if the child actor has a 3D rotation applied to
297 * it.
298 *
299 * Remember: if clutter_actor_get_transformed_paint_volume() is
300 * used then a transformed child volume will be defined relative to the
301 * ancestor container actor and so a 2D child actor can have a 3D
302 * bounding volume.
303 *
304 * There are no accuracy guarantees for the reported width,
305 * except that it must always be greater than, or equal to, the
306 * actor's width. This is because actors may report simple, loose
307 * fitting paint volumes for efficiency.
308
309 * Return value: the width, in units of @pv's local coordinate system.
310 *
311 * Since: 1.6
312 */
313 gfloat
clutter_paint_volume_get_width(const ClutterPaintVolume * pv)314 clutter_paint_volume_get_width (const ClutterPaintVolume *pv)
315 {
316 g_return_val_if_fail (pv != NULL, 0.0);
317
318 if (pv->is_empty)
319 return 0;
320 else if (!pv->is_axis_aligned)
321 {
322 ClutterPaintVolume tmp;
323 float width;
324 _clutter_paint_volume_copy_static (pv, &tmp);
325 _clutter_paint_volume_axis_align (&tmp);
326 width = tmp.vertices[1].x - tmp.vertices[0].x;
327 clutter_paint_volume_free (&tmp);
328 return width;
329 }
330 else
331 return pv->vertices[1].x - pv->vertices[0].x;
332 }
333
334 /**
335 * clutter_paint_volume_set_height:
336 * @pv: a #ClutterPaintVolume
337 * @height: the height of the paint volume, in pixels
338 *
339 * Sets the height of the paint volume. The height is measured along
340 * the y axis in the actor coordinates that @pv is associated with.
341 *
342 * Since: 1.6
343 */
344 void
clutter_paint_volume_set_height(ClutterPaintVolume * pv,gfloat height)345 clutter_paint_volume_set_height (ClutterPaintVolume *pv,
346 gfloat height)
347 {
348 gfloat height_ypos;
349
350 g_return_if_fail (pv != NULL);
351 g_return_if_fail (height >= 0.0f);
352
353 /* If the volume is currently empty then only the origin is
354 * currently valid */
355 if (pv->is_empty)
356 pv->vertices[1] = pv->vertices[3] = pv->vertices[4] = pv->vertices[0];
357
358 if (!pv->is_axis_aligned)
359 _clutter_paint_volume_axis_align (pv);
360
361 height_ypos = pv->vertices[0].y + height;
362
363 /* Move the bottom vertices of the paint box relative to the
364 * origin... */
365 /* pv->vertices[2].y = height_ypos; NB: updated lazily */
366 pv->vertices[3].y = height_ypos;
367 /* pv->vertices[6].y = height_ypos; NB: updated lazily */
368 /* pv->vertices[7].y = height_ypos; NB: updated lazily */
369 pv->is_complete = FALSE;
370
371 _clutter_paint_volume_update_is_empty (pv);
372 }
373
374 /**
375 * clutter_paint_volume_get_height:
376 * @pv: a #ClutterPaintVolume
377 *
378 * Retrieves the height of the volume's, axis aligned, bounding box.
379 *
380 * In other words; this takes into account what actor's coordinate
381 * space @pv belongs too and conceptually fits an axis aligned box
382 * around the volume. It returns the size of that bounding box as
383 * measured along the y-axis.
384 *
385 * If, for example, clutter_actor_get_transformed_paint_volume()
386 * is used to transform a 2D child actor that is 100px wide, 100px
387 * high and 0px deep into container coordinates then the height might
388 * not simply be 100px if the child actor has a 3D rotation applied to
389 * it.
390 *
391 * Remember: if clutter_actor_get_transformed_paint_volume() is
392 * used then a transformed child volume will be defined relative to the
393 * ancestor container actor and so a 2D child actor
394 * can have a 3D bounding volume.
395 *
396 * There are no accuracy guarantees for the reported height,
397 * except that it must always be greater than, or equal to, the actor's
398 * height. This is because actors may report simple, loose fitting paint
399 * volumes for efficiency.
400 *
401 * Return value: the height, in units of @pv's local coordinate system.
402 *
403 * Since: 1.6
404 */
405 gfloat
clutter_paint_volume_get_height(const ClutterPaintVolume * pv)406 clutter_paint_volume_get_height (const ClutterPaintVolume *pv)
407 {
408 g_return_val_if_fail (pv != NULL, 0.0);
409
410 if (pv->is_empty)
411 return 0;
412 else if (!pv->is_axis_aligned)
413 {
414 ClutterPaintVolume tmp;
415 float height;
416 _clutter_paint_volume_copy_static (pv, &tmp);
417 _clutter_paint_volume_axis_align (&tmp);
418 height = tmp.vertices[3].y - tmp.vertices[0].y;
419 clutter_paint_volume_free (&tmp);
420 return height;
421 }
422 else
423 return pv->vertices[3].y - pv->vertices[0].y;
424 }
425
426 /**
427 * clutter_paint_volume_set_depth:
428 * @pv: a #ClutterPaintVolume
429 * @depth: the depth of the paint volume, in pixels
430 *
431 * Sets the depth of the paint volume. The depth is measured along
432 * the z axis in the actor coordinates that @pv is associated with.
433 *
434 * Since: 1.6
435 */
436 void
clutter_paint_volume_set_depth(ClutterPaintVolume * pv,gfloat depth)437 clutter_paint_volume_set_depth (ClutterPaintVolume *pv,
438 gfloat depth)
439 {
440 gfloat depth_zpos;
441
442 g_return_if_fail (pv != NULL);
443 g_return_if_fail (depth >= 0.0f);
444
445 /* If the volume is currently empty then only the origin is
446 * currently valid */
447 if (pv->is_empty)
448 pv->vertices[1] = pv->vertices[3] = pv->vertices[4] = pv->vertices[0];
449
450 if (!pv->is_axis_aligned)
451 _clutter_paint_volume_axis_align (pv);
452
453 depth_zpos = pv->vertices[0].z + depth;
454
455 /* Move the back vertices of the paint box relative to the
456 * origin... */
457 pv->vertices[4].z = depth_zpos;
458 /* pv->vertices[5].z = depth_zpos; NB: updated lazily */
459 /* pv->vertices[6].z = depth_zpos; NB: updated lazily */
460 /* pv->vertices[7].z = depth_zpos; NB: updated lazily */
461
462 pv->is_complete = FALSE;
463 pv->is_2d = depth ? FALSE : TRUE;
464 _clutter_paint_volume_update_is_empty (pv);
465 }
466
467 /**
468 * clutter_paint_volume_get_depth:
469 * @pv: a #ClutterPaintVolume
470 *
471 * Retrieves the depth of the volume's, axis aligned, bounding box.
472 *
473 * In other words; this takes into account what actor's coordinate
474 * space @pv belongs too and conceptually fits an axis aligned box
475 * around the volume. It returns the size of that bounding box as
476 * measured along the z-axis.
477 *
478 * If, for example, clutter_actor_get_transformed_paint_volume()
479 * is used to transform a 2D child actor that is 100px wide, 100px
480 * high and 0px deep into container coordinates then the depth might
481 * not simply be 0px if the child actor has a 3D rotation applied to
482 * it.
483 *
484 * Remember: if clutter_actor_get_transformed_paint_volume() is
485 * used then the transformed volume will be defined relative to the
486 * container actor and in container coordinates a 2D child actor
487 * can have a 3D bounding volume.
488 *
489 * There are no accuracy guarantees for the reported depth,
490 * except that it must always be greater than, or equal to, the actor's
491 * depth. This is because actors may report simple, loose fitting paint
492 * volumes for efficiency.
493 *
494 * Return value: the depth, in units of @pv's local coordinate system.
495 *
496 * Since: 1.6
497 */
498 gfloat
clutter_paint_volume_get_depth(const ClutterPaintVolume * pv)499 clutter_paint_volume_get_depth (const ClutterPaintVolume *pv)
500 {
501 g_return_val_if_fail (pv != NULL, 0.0);
502
503 if (pv->is_empty)
504 return 0;
505 else if (!pv->is_axis_aligned)
506 {
507 ClutterPaintVolume tmp;
508 float depth;
509 _clutter_paint_volume_copy_static (pv, &tmp);
510 _clutter_paint_volume_axis_align (&tmp);
511 depth = tmp.vertices[4].z - tmp.vertices[0].z;
512 clutter_paint_volume_free (&tmp);
513 return depth;
514 }
515 else
516 return pv->vertices[4].z - pv->vertices[0].z;
517 }
518
519 /**
520 * clutter_paint_volume_union:
521 * @pv: The first #ClutterPaintVolume and destination for resulting
522 * union
523 * @another_pv: A second #ClutterPaintVolume to union with @pv
524 *
525 * Updates the geometry of @pv to encompass @pv and @another_pv.
526 *
527 * There are no guarantees about how precisely the two volumes
528 * will be unioned.
529 *
530 * Since: 1.6
531 */
532 void
clutter_paint_volume_union(ClutterPaintVolume * pv,const ClutterPaintVolume * another_pv)533 clutter_paint_volume_union (ClutterPaintVolume *pv,
534 const ClutterPaintVolume *another_pv)
535 {
536 ClutterPaintVolume aligned_pv;
537
538 g_return_if_fail (pv != NULL);
539 g_return_if_fail (another_pv != NULL);
540
541 /* Both volumes have to belong to the same local coordinate space */
542 g_return_if_fail (pv->actor == another_pv->actor);
543
544 /* NB: we only have to update vertices 0, 1, 3 and 4
545 * (See the ClutterPaintVolume typedef for more details) */
546
547 /* We special case empty volumes because otherwise we'd end up
548 * calculating a bounding box that would enclose the origin of
549 * the empty volume which isn't desired.
550 */
551 if (another_pv->is_empty)
552 return;
553
554 if (pv->is_empty)
555 {
556 _clutter_paint_volume_set_from_volume (pv, another_pv);
557 goto done;
558 }
559
560 if (!pv->is_axis_aligned)
561 _clutter_paint_volume_axis_align (pv);
562
563 if (!another_pv->is_axis_aligned)
564 {
565 _clutter_paint_volume_copy_static (another_pv, &aligned_pv);
566 _clutter_paint_volume_axis_align (&aligned_pv);
567 another_pv = &aligned_pv;
568 }
569
570 /* grow left*/
571 /* left vertices 0, 3, 4, 7 */
572 if (another_pv->vertices[0].x < pv->vertices[0].x)
573 {
574 int min_x = another_pv->vertices[0].x;
575 pv->vertices[0].x = min_x;
576 pv->vertices[3].x = min_x;
577 pv->vertices[4].x = min_x;
578 /* pv->vertices[7].x = min_x; */
579 }
580
581 /* grow right */
582 /* right vertices 1, 2, 5, 6 */
583 if (another_pv->vertices[1].x > pv->vertices[1].x)
584 {
585 int max_x = another_pv->vertices[1].x;
586 pv->vertices[1].x = max_x;
587 /* pv->vertices[2].x = max_x; */
588 /* pv->vertices[5].x = max_x; */
589 /* pv->vertices[6].x = max_x; */
590 }
591
592 /* grow up */
593 /* top vertices 0, 1, 4, 5 */
594 if (another_pv->vertices[0].y < pv->vertices[0].y)
595 {
596 int min_y = another_pv->vertices[0].y;
597 pv->vertices[0].y = min_y;
598 pv->vertices[1].y = min_y;
599 pv->vertices[4].y = min_y;
600 /* pv->vertices[5].y = min_y; */
601 }
602
603 /* grow down */
604 /* bottom vertices 2, 3, 6, 7 */
605 if (another_pv->vertices[3].y > pv->vertices[3].y)
606 {
607 int may_y = another_pv->vertices[3].y;
608 /* pv->vertices[2].y = may_y; */
609 pv->vertices[3].y = may_y;
610 /* pv->vertices[6].y = may_y; */
611 /* pv->vertices[7].y = may_y; */
612 }
613
614 /* grow forward */
615 /* front vertices 0, 1, 2, 3 */
616 if (another_pv->vertices[0].z < pv->vertices[0].z)
617 {
618 int min_z = another_pv->vertices[0].z;
619 pv->vertices[0].z = min_z;
620 pv->vertices[1].z = min_z;
621 /* pv->vertices[2].z = min_z; */
622 pv->vertices[3].z = min_z;
623 }
624
625 /* grow backward */
626 /* back vertices 4, 5, 6, 7 */
627 if (another_pv->vertices[4].z > pv->vertices[4].z)
628 {
629 int maz_z = another_pv->vertices[4].z;
630 pv->vertices[4].z = maz_z;
631 /* pv->vertices[5].z = maz_z; */
632 /* pv->vertices[6].z = maz_z; */
633 /* pv->vertices[7].z = maz_z; */
634 }
635
636 if (pv->vertices[4].z == pv->vertices[0].z)
637 pv->is_2d = TRUE;
638 else
639 pv->is_2d = FALSE;
640
641 done:
642 pv->is_empty = FALSE;
643 pv->is_complete = FALSE;
644 }
645
646 /**
647 * clutter_paint_volume_union_box:
648 * @pv: a #ClutterPaintVolume
649 * @box: a #ClutterActorBox to union to @pv
650 *
651 * Unions the 2D region represented by @box to a #ClutterPaintVolume.
652 *
653 * This function is similar to clutter_paint_volume_union(), but it is
654 * specific for 2D regions.
655 *
656 * Since: 1.10
657 */
658 void
clutter_paint_volume_union_box(ClutterPaintVolume * pv,const ClutterActorBox * box)659 clutter_paint_volume_union_box (ClutterPaintVolume *pv,
660 const ClutterActorBox *box)
661 {
662 ClutterPaintVolume volume;
663 ClutterVertex origin;
664
665 g_return_if_fail (pv != NULL);
666 g_return_if_fail (box != NULL);
667
668 _clutter_paint_volume_init_static (&volume, pv->actor);
669
670 origin.x = box->x1;
671 origin.y = box->y1;
672 origin.z = 0.f;
673 clutter_paint_volume_set_origin (&volume, &origin);
674 clutter_paint_volume_set_width (&volume, box->x2 - box->x1);
675 clutter_paint_volume_set_height (&volume, box->y2 - box->y1);
676
677 clutter_paint_volume_union (pv, &volume);
678
679 clutter_paint_volume_free (&volume);
680 }
681
682 /* The paint_volume setters only update vertices 0, 1, 3 and
683 * 4 since the others can be drived from them.
684 *
685 * This will set pv->completed = TRUE;
686 */
687 void
_clutter_paint_volume_complete(ClutterPaintVolume * pv)688 _clutter_paint_volume_complete (ClutterPaintVolume *pv)
689 {
690 float dx_l2r, dy_l2r, dz_l2r;
691 float dx_t2b, dy_t2b, dz_t2b;
692
693 if (pv->is_empty)
694 return;
695
696 if (pv->is_complete)
697 return;
698
699 /* Find the vector that takes us from any vertex on the left face to
700 * the corresponding vertex on the right face. */
701 dx_l2r = pv->vertices[1].x - pv->vertices[0].x;
702 dy_l2r = pv->vertices[1].y - pv->vertices[0].y;
703 dz_l2r = pv->vertices[1].z - pv->vertices[0].z;
704
705 /* Find the vector that takes us from any vertex on the top face to
706 * the corresponding vertex on the bottom face. */
707 dx_t2b = pv->vertices[3].x - pv->vertices[0].x;
708 dy_t2b = pv->vertices[3].y - pv->vertices[0].y;
709 dz_t2b = pv->vertices[3].z - pv->vertices[0].z;
710
711 /* front-bottom-right */
712 pv->vertices[2].x = pv->vertices[3].x + dx_l2r;
713 pv->vertices[2].y = pv->vertices[3].y + dy_l2r;
714 pv->vertices[2].z = pv->vertices[3].z + dz_l2r;
715
716 if (G_UNLIKELY (!pv->is_2d))
717 {
718 /* back-top-right */
719 pv->vertices[5].x = pv->vertices[4].x + dx_l2r;
720 pv->vertices[5].y = pv->vertices[4].y + dy_l2r;
721 pv->vertices[5].z = pv->vertices[4].z + dz_l2r;
722
723 /* back-bottom-right */
724 pv->vertices[6].x = pv->vertices[5].x + dx_t2b;
725 pv->vertices[6].y = pv->vertices[5].y + dy_t2b;
726 pv->vertices[6].z = pv->vertices[5].z + dz_t2b;
727
728 /* back-bottom-left */
729 pv->vertices[7].x = pv->vertices[4].x + dx_t2b;
730 pv->vertices[7].y = pv->vertices[4].y + dy_t2b;
731 pv->vertices[7].z = pv->vertices[4].z + dz_t2b;
732 }
733
734 pv->is_complete = TRUE;
735 }
736
737 /*<private>
738 * _clutter_paint_volume_get_box:
739 * @pv: a #ClutterPaintVolume
740 * @box: a pixel aligned #ClutterActorBox
741 *
742 * Transforms a 3D paint volume into a 2D bounding box in the
743 * same coordinate space as the 3D paint volume.
744 *
745 * To get an actors "paint box" you should first project
746 * the paint volume into window coordinates before getting
747 * the 2D bounding box.
748 *
749 * The coordinates of the returned box are not clamped to
750 * integer pixel values; if you need them to be rounded to the
751 * nearest integer pixel values, you can use the
752 * clutter_actor_box_clamp_to_pixel() function.
753 *
754 * Since: 1.6
755 */
756 void
_clutter_paint_volume_get_bounding_box(ClutterPaintVolume * pv,ClutterActorBox * box)757 _clutter_paint_volume_get_bounding_box (ClutterPaintVolume *pv,
758 ClutterActorBox *box)
759 {
760 gfloat x_min, y_min, x_max, y_max;
761 ClutterVertex *vertices;
762 int count;
763 gint i;
764
765 g_return_if_fail (pv != NULL);
766 g_return_if_fail (box != NULL);
767
768 if (pv->is_empty)
769 {
770 box->x1 = box->x2 = pv->vertices[0].x;
771 box->y1 = box->y2 = pv->vertices[0].y;
772 return;
773 }
774
775 /* Updates the vertices we calculate lazily
776 * (See ClutterPaintVolume typedef for more details) */
777 _clutter_paint_volume_complete (pv);
778
779 vertices = pv->vertices;
780
781 x_min = x_max = vertices[0].x;
782 y_min = y_max = vertices[0].y;
783
784 /* Most actors are 2D so we only have to look at the front 4
785 * vertices of the paint volume... */
786 if (G_LIKELY (pv->is_2d))
787 count = 4;
788 else
789 count = 8;
790
791 for (i = 1; i < count; i++)
792 {
793 if (vertices[i].x < x_min)
794 x_min = vertices[i].x;
795 else if (vertices[i].x > x_max)
796 x_max = vertices[i].x;
797
798 if (vertices[i].y < y_min)
799 y_min = vertices[i].y;
800 else if (vertices[i].y > y_max)
801 y_max = vertices[i].y;
802 }
803
804 box->x1 = x_min;
805 box->y1 = y_min;
806 box->x2 = x_max;
807 box->y2 = y_max;
808 }
809
810 void
_clutter_paint_volume_project(ClutterPaintVolume * pv,const CoglMatrix * modelview,const CoglMatrix * projection,const float * viewport)811 _clutter_paint_volume_project (ClutterPaintVolume *pv,
812 const CoglMatrix *modelview,
813 const CoglMatrix *projection,
814 const float *viewport)
815 {
816 int transform_count;
817
818 if (pv->is_empty)
819 {
820 /* Just transform the origin... */
821 _clutter_util_fully_transform_vertices (modelview,
822 projection,
823 viewport,
824 pv->vertices,
825 pv->vertices,
826 1);
827 return;
828 }
829
830 /* All the vertices must be up to date, since after the projection
831 * it wont be trivial to derive the other vertices. */
832 _clutter_paint_volume_complete (pv);
833
834 /* Most actors are 2D so we only have to transform the front 4
835 * vertices of the paint volume... */
836 if (G_LIKELY (pv->is_2d))
837 transform_count = 4;
838 else
839 transform_count = 8;
840
841 _clutter_util_fully_transform_vertices (modelview,
842 projection,
843 viewport,
844 pv->vertices,
845 pv->vertices,
846 transform_count);
847
848 pv->is_axis_aligned = FALSE;
849 }
850
851 void
_clutter_paint_volume_transform(ClutterPaintVolume * pv,const CoglMatrix * matrix)852 _clutter_paint_volume_transform (ClutterPaintVolume *pv,
853 const CoglMatrix *matrix)
854 {
855 int transform_count;
856
857 if (pv->is_empty)
858 {
859 gfloat w = 1;
860 /* Just transform the origin */
861 cogl_matrix_transform_point (matrix,
862 &pv->vertices[0].x,
863 &pv->vertices[0].y,
864 &pv->vertices[0].z,
865 &w);
866 return;
867 }
868
869 /* All the vertices must be up to date, since after the transform
870 * it wont be trivial to derive the other vertices. */
871 _clutter_paint_volume_complete (pv);
872
873 /* Most actors are 2D so we only have to transform the front 4
874 * vertices of the paint volume... */
875 if (G_LIKELY (pv->is_2d))
876 transform_count = 4;
877 else
878 transform_count = 8;
879
880 cogl_matrix_transform_points (matrix,
881 3,
882 sizeof (ClutterVertex),
883 pv->vertices,
884 sizeof (ClutterVertex),
885 pv->vertices,
886 transform_count);
887
888 pv->is_axis_aligned = FALSE;
889 }
890
891
892 /* Given a paint volume that has been transformed by an arbitrary
893 * modelview and is no longer axis aligned, this derives a replacement
894 * that is axis aligned. */
895 void
_clutter_paint_volume_axis_align(ClutterPaintVolume * pv)896 _clutter_paint_volume_axis_align (ClutterPaintVolume *pv)
897 {
898 int count;
899 int i;
900 ClutterVertex origin;
901 float max_x;
902 float max_y;
903 float max_z;
904
905 g_return_if_fail (pv != NULL);
906
907 if (pv->is_empty)
908 return;
909
910 if (G_LIKELY (pv->is_axis_aligned))
911 return;
912
913 if (G_LIKELY (pv->vertices[0].x == pv->vertices[1].x &&
914 pv->vertices[0].y == pv->vertices[3].y &&
915 pv->vertices[0].z == pv->vertices[4].z))
916 {
917 pv->is_axis_aligned = TRUE;
918 return;
919 }
920
921 if (!pv->is_complete)
922 _clutter_paint_volume_complete (pv);
923
924 origin = pv->vertices[0];
925 max_x = pv->vertices[0].x;
926 max_y = pv->vertices[0].y;
927 max_z = pv->vertices[0].z;
928
929 count = pv->is_2d ? 4 : 8;
930 for (i = 1; i < count; i++)
931 {
932 if (pv->vertices[i].x < origin.x)
933 origin.x = pv->vertices[i].x;
934 else if (pv->vertices[i].x > max_x)
935 max_x = pv->vertices[i].x;
936
937 if (pv->vertices[i].y < origin.y)
938 origin.y = pv->vertices[i].y;
939 else if (pv->vertices[i].y > max_y)
940 max_y = pv->vertices[i].y;
941
942 if (pv->vertices[i].z < origin.z)
943 origin.z = pv->vertices[i].z;
944 else if (pv->vertices[i].z > max_z)
945 max_z = pv->vertices[i].z;
946 }
947
948 pv->vertices[0] = origin;
949
950 pv->vertices[1].x = max_x;
951 pv->vertices[1].y = origin.y;
952 pv->vertices[1].z = origin.z;
953
954 pv->vertices[3].x = origin.x;
955 pv->vertices[3].y = max_y;
956 pv->vertices[3].z = origin.z;
957
958 pv->vertices[4].x = origin.x;
959 pv->vertices[4].y = origin.y;
960 pv->vertices[4].z = max_z;
961
962 pv->is_complete = FALSE;
963 pv->is_axis_aligned = TRUE;
964
965 if (pv->vertices[4].z == pv->vertices[0].z)
966 pv->is_2d = TRUE;
967 else
968 pv->is_2d = FALSE;
969 }
970
971 /*<private>
972 * _clutter_actor_set_default_paint_volume:
973 * @self: a #ClutterActor
974 * @check_gtype: if not %G_TYPE_INVALID, match the type of @self against
975 * this type
976 * @volume: the #ClutterPaintVolume to set
977 *
978 * Sets the default paint volume for @self.
979 *
980 * This function should be called by #ClutterActor sub-classes that follow
981 * the default assumption that their paint volume is defined by their
982 * allocation.
983 *
984 * If @check_gtype is not %G_TYPE_INVALID, this function will check the
985 * type of @self and only compute the paint volume if the type matches;
986 * this can be used to avoid computing the paint volume for sub-classes
987 * of an actor class
988 *
989 * Return value: %TRUE if the paint volume was set, and %FALSE otherwise
990 */
991 gboolean
_clutter_actor_set_default_paint_volume(ClutterActor * self,GType check_gtype,ClutterPaintVolume * volume)992 _clutter_actor_set_default_paint_volume (ClutterActor *self,
993 GType check_gtype,
994 ClutterPaintVolume *volume)
995 {
996 ClutterActorBox box;
997
998 if (check_gtype != G_TYPE_INVALID)
999 {
1000 if (G_OBJECT_TYPE (self) != check_gtype)
1001 return FALSE;
1002 }
1003
1004 /* calling clutter_actor_get_allocation_* can potentially be very
1005 * expensive, as it can result in a synchronous full stage relayout
1006 * and redraw
1007 */
1008 if (!clutter_actor_has_allocation (self))
1009 return FALSE;
1010
1011 clutter_actor_get_allocation_box (self, &box);
1012
1013 /* we only set the width and height, as the paint volume is defined
1014 * to be relative to the actor's modelview, which means that the
1015 * allocation's origin has already been applied
1016 */
1017 clutter_paint_volume_set_width (volume, box.x2 - box.x1);
1018 clutter_paint_volume_set_height (volume, box.y2 - box.y1);
1019
1020 return TRUE;
1021 }
1022
1023 /**
1024 * clutter_paint_volume_set_from_allocation:
1025 * @pv: a #ClutterPaintVolume
1026 * @actor: a #ClutterActor
1027 *
1028 * Sets the #ClutterPaintVolume from the allocation of @actor.
1029 *
1030 * This function should be used when overriding the
1031 * #ClutterActorClass.get_paint_volume() by #ClutterActor sub-classes
1032 * that do not paint outside their allocation.
1033 *
1034 * A typical example is:
1035 *
1036 * |[
1037 * static gboolean
1038 * my_actor_get_paint_volume (ClutterActor *self,
1039 * ClutterPaintVolume *volume)
1040 * {
1041 * return clutter_paint_volume_set_from_allocation (volume, self);
1042 * }
1043 * ]|
1044 *
1045 * Return value: %TRUE if the paint volume was successfully set, and %FALSE
1046 * otherwise
1047 *
1048 * Since: 1.6
1049 */
1050 gboolean
clutter_paint_volume_set_from_allocation(ClutterPaintVolume * pv,ClutterActor * actor)1051 clutter_paint_volume_set_from_allocation (ClutterPaintVolume *pv,
1052 ClutterActor *actor)
1053 {
1054 g_return_val_if_fail (pv != NULL, FALSE);
1055 g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), FALSE);
1056
1057 return _clutter_actor_set_default_paint_volume (actor, G_TYPE_INVALID, pv);
1058 }
1059
1060 /* Currently paint volumes are defined relative to a given actor, but
1061 * in some cases it is desireable to be able to change the actor that
1062 * a volume relates too (For instance for ClutterClone actors where we
1063 * need to masquarade the source actors volume as the volume for the
1064 * clone). */
1065 void
_clutter_paint_volume_set_reference_actor(ClutterPaintVolume * pv,ClutterActor * actor)1066 _clutter_paint_volume_set_reference_actor (ClutterPaintVolume *pv,
1067 ClutterActor *actor)
1068 {
1069 g_return_if_fail (pv != NULL);
1070
1071 pv->actor = actor;
1072 }
1073
1074 ClutterCullResult
_clutter_paint_volume_cull(ClutterPaintVolume * pv,const ClutterPlane * planes)1075 _clutter_paint_volume_cull (ClutterPaintVolume *pv,
1076 const ClutterPlane *planes)
1077 {
1078 int vertex_count;
1079 ClutterVertex *vertices = pv->vertices;
1080 gboolean partial = FALSE;
1081 int i;
1082 int j;
1083
1084 if (pv->is_empty)
1085 return CLUTTER_CULL_RESULT_OUT;
1086
1087 /* We expect the volume to already be transformed into eye coordinates
1088 */
1089 g_return_val_if_fail (pv->is_complete == TRUE, CLUTTER_CULL_RESULT_IN);
1090 g_return_val_if_fail (pv->actor == NULL, CLUTTER_CULL_RESULT_IN);
1091
1092 /* Most actors are 2D so we only have to transform the front 4
1093 * vertices of the paint volume... */
1094 if (G_LIKELY (pv->is_2d))
1095 vertex_count = 4;
1096 else
1097 vertex_count = 8;
1098
1099 for (i = 0; i < 4; i++)
1100 {
1101 int out = 0;
1102 for (j = 0; j < vertex_count; j++)
1103 {
1104 ClutterVertex p;
1105 float distance;
1106
1107 /* XXX: for perspective projections this can be optimized
1108 * out because all the planes should pass through the origin
1109 * so (0,0,0) is a valid v0. */
1110 p.x = vertices[j].x - planes[i].v0[0];
1111 p.y = vertices[j].y - planes[i].v0[1];
1112 p.z = vertices[j].z - planes[i].v0[2];
1113
1114 distance = (planes[i].n[0] * p.x +
1115 planes[i].n[1] * p.y +
1116 planes[i].n[2] * p.z);
1117
1118 if (distance < 0)
1119 out++;
1120 }
1121
1122 if (out == vertex_count)
1123 return CLUTTER_CULL_RESULT_OUT;
1124 else if (out != 0)
1125 partial = TRUE;
1126 }
1127
1128 if (partial)
1129 return CLUTTER_CULL_RESULT_PARTIAL;
1130 else
1131 return CLUTTER_CULL_RESULT_IN;
1132 }
1133
1134 void
_clutter_paint_volume_get_stage_paint_box(ClutterPaintVolume * pv,ClutterStage * stage,ClutterActorBox * box)1135 _clutter_paint_volume_get_stage_paint_box (ClutterPaintVolume *pv,
1136 ClutterStage *stage,
1137 ClutterActorBox *box)
1138 {
1139 ClutterPaintVolume projected_pv;
1140 CoglMatrix modelview;
1141 CoglMatrix projection;
1142 float viewport[4];
1143 float width;
1144 float height;
1145
1146 _clutter_paint_volume_copy_static (pv, &projected_pv);
1147
1148 cogl_matrix_init_identity (&modelview);
1149
1150 /* If the paint volume isn't already in eye coordinates... */
1151 if (pv->actor)
1152 _clutter_actor_apply_relative_transformation_matrix (pv->actor, NULL,
1153 &modelview);
1154
1155 _clutter_stage_get_projection_matrix (stage, &projection);
1156 _clutter_stage_get_viewport (stage,
1157 &viewport[0],
1158 &viewport[1],
1159 &viewport[2],
1160 &viewport[3]);
1161
1162 _clutter_paint_volume_project (&projected_pv,
1163 &modelview,
1164 &projection,
1165 viewport);
1166
1167 _clutter_paint_volume_get_bounding_box (&projected_pv, box);
1168
1169 /* The aim here is that for a given rectangle defined with floating point
1170 * coordinates we want to determine a stable quantized size in pixels
1171 * that doesn't vary due to the original box's sub-pixel position.
1172 *
1173 * The reason this is important is because effects will use this
1174 * API to determine the size of offscreen framebuffers and so for
1175 * a fixed-size object that may be animated accross the screen we
1176 * want to make sure that the stage paint-box has an equally stable
1177 * size so that effects aren't made to continuously re-allocate
1178 * a corresponding fbo.
1179 *
1180 * The other thing we consider is that the calculation of this box is
1181 * subject to floating point precision issues that might be slightly
1182 * different to the precision issues involved with actually painting the
1183 * actor, which might result in painting slightly leaking outside the
1184 * user's calculated paint-volume. For this we simply aim to pad out the
1185 * paint-volume by at least half a pixel all the way around.
1186 */
1187 width = box->x2 - box->x1;
1188 height = box->y2 - box->y1;
1189 width = CLUTTER_NEARBYINT (width);
1190 height = CLUTTER_NEARBYINT (height);
1191 /* XXX: NB the width/height may now be up to 0.5px too small so we
1192 * must also pad by 0.25px all around to account for this. In total we
1193 * must padd by at least 0.75px around all sides. */
1194
1195 /* XXX: The furthest that we can overshoot the bottom right corner by
1196 * here is 1.75px in total if you consider that the 0.75 padding could
1197 * just cross an integer boundary and so ceil will effectively add 1.
1198 */
1199 box->x2 = ceilf (box->x2 + 0.75);
1200 box->y2 = ceilf (box->y2 + 0.75);
1201
1202 /* Now we redefine the top-left relative to the bottom right based on the
1203 * rounded width/height determined above + a constant so that the overall
1204 * size of the box will be stable and not dependant on the box's
1205 * position.
1206 *
1207 * Adding 3px to the width/height will ensure we cover the maximum of
1208 * 1.75px padding on the bottom/right and still ensure we have > 0.75px
1209 * padding on the top/left.
1210 */
1211 box->x1 = box->x2 - width - 3;
1212 box->y1 = box->y2 - height - 3;
1213
1214 clutter_paint_volume_free (&projected_pv);
1215 }
1216
1217 void
_clutter_paint_volume_transform_relative(ClutterPaintVolume * pv,ClutterActor * relative_to_ancestor)1218 _clutter_paint_volume_transform_relative (ClutterPaintVolume *pv,
1219 ClutterActor *relative_to_ancestor)
1220 {
1221 CoglMatrix matrix;
1222 ClutterActor *actor;
1223
1224 actor = pv->actor;
1225
1226 g_return_if_fail (actor != NULL);
1227
1228 _clutter_paint_volume_set_reference_actor (pv, relative_to_ancestor);
1229
1230 cogl_matrix_init_identity (&matrix);
1231 _clutter_actor_apply_relative_transformation_matrix (actor,
1232 relative_to_ancestor,
1233 &matrix);
1234
1235 _clutter_paint_volume_transform (pv, &matrix);
1236 }
1237