mirror of
https://github.com/brl/mutter.git
synced 2024-11-26 01:50:42 -05:00
368dc48372
These aren't path methods so aren't consistent with the cogl_object_method naming style we are aiming for.
1245 lines
31 KiB
C
1245 lines
31 KiB
C
/*
|
|
* Cogl
|
|
*
|
|
* An object oriented GL/GLES Abstraction/Utility Layer
|
|
*
|
|
* Copyright (C) 2007,2008,2009,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/>.
|
|
*
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "cogl.h"
|
|
#include "cogl-object.h"
|
|
#include "cogl-internal.h"
|
|
#include "cogl-context.h"
|
|
#include "cogl-journal-private.h"
|
|
#include "cogl-material-private.h"
|
|
#include "cogl-framebuffer-private.h"
|
|
#include "cogl-path-private.h"
|
|
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#define _COGL_MAX_BEZ_RECURSE_DEPTH 16
|
|
|
|
#ifdef HAVE_COGL_GL
|
|
#define glClientActiveTexture ctx->drv.pf_glClientActiveTexture
|
|
#endif
|
|
|
|
static void _cogl_path_free (CoglPath *path);
|
|
|
|
COGL_OBJECT_DEFINE (Path, path);
|
|
|
|
static void
|
|
_cogl_path_data_unref (CoglPathData *data)
|
|
{
|
|
if (--data->ref_count <= 0)
|
|
{
|
|
g_array_free (data->path_nodes, TRUE);
|
|
|
|
g_slice_free (CoglPathData, data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_cogl_path_modify (CoglPath *path)
|
|
{
|
|
/* This needs to be called whenever the path is about to be modified
|
|
to implement copy-on-write semantics */
|
|
|
|
/* If there is more than one path using the data then we need to
|
|
copy the data instead */
|
|
if (path->data->ref_count != 1)
|
|
{
|
|
CoglPathData *old_data = path->data;
|
|
|
|
path->data = g_slice_dup (CoglPathData, old_data);
|
|
path->data->path_nodes = g_array_new (FALSE, FALSE,
|
|
sizeof (CoglPathNode));
|
|
g_array_append_vals (path->data->path_nodes,
|
|
old_data->path_nodes->data,
|
|
old_data->path_nodes->len);
|
|
|
|
path->data->ref_count = 1;
|
|
|
|
_cogl_path_data_unref (old_data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_cogl_path_add_node (gboolean new_sub_path,
|
|
float x,
|
|
float y)
|
|
{
|
|
CoglPathNode new_node;
|
|
CoglPath *path;
|
|
CoglPathData *data;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
path = COGL_PATH (ctx->current_path);
|
|
|
|
_cogl_path_modify (path);
|
|
|
|
data = path->data;
|
|
|
|
new_node.x = x;
|
|
new_node.y = y;
|
|
new_node.path_size = 0;
|
|
|
|
if (new_sub_path || data->path_nodes->len == 0)
|
|
data->last_path = data->path_nodes->len;
|
|
|
|
g_array_append_val (data->path_nodes, new_node);
|
|
|
|
g_array_index (data->path_nodes, CoglPathNode, data->last_path).path_size++;
|
|
|
|
if (data->path_nodes->len == 1)
|
|
{
|
|
data->path_nodes_min.x = data->path_nodes_max.x = x;
|
|
data->path_nodes_min.y = data->path_nodes_max.y = y;
|
|
}
|
|
else
|
|
{
|
|
if (x < data->path_nodes_min.x) data->path_nodes_min.x = x;
|
|
if (x > data->path_nodes_max.x) data->path_nodes_max.x = x;
|
|
if (y < data->path_nodes_min.y) data->path_nodes_min.y = y;
|
|
if (y > data->path_nodes_max.y) data->path_nodes_max.y = y;
|
|
}
|
|
}
|
|
|
|
static void
|
|
_cogl_path_stroke_nodes (void)
|
|
{
|
|
unsigned int path_start = 0;
|
|
unsigned long enable_flags = COGL_ENABLE_VERTEX_ARRAY;
|
|
CoglPathData *data;
|
|
CoglMaterialFlushOptions options;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
data = COGL_PATH (ctx->current_path)->data;
|
|
|
|
_cogl_journal_flush ();
|
|
|
|
/* NB: _cogl_framebuffer_flush_state may disrupt various state (such
|
|
* as the material state) when flushing the clip stack, so should
|
|
* always be done first when preparing to draw. */
|
|
_cogl_framebuffer_flush_state (_cogl_get_framebuffer (), 0);
|
|
|
|
enable_flags |= _cogl_material_get_cogl_enable_flags (ctx->source_material);
|
|
_cogl_enable (enable_flags);
|
|
|
|
options.flags = COGL_MATERIAL_FLUSH_DISABLE_MASK;
|
|
/* disable all texture layers */
|
|
options.disable_layers = (guint32)~0;
|
|
|
|
_cogl_material_flush_gl_state (ctx->source_material, &options);
|
|
|
|
while (path_start < data->path_nodes->len)
|
|
{
|
|
CoglPathNode *node = &g_array_index (data->path_nodes, CoglPathNode,
|
|
path_start);
|
|
|
|
GE( glVertexPointer (2, GL_FLOAT, sizeof (CoglPathNode), &node->x) );
|
|
GE( glDrawArrays (GL_LINE_STRIP, 0, node->path_size) );
|
|
|
|
path_start += node->path_size;
|
|
}
|
|
}
|
|
|
|
static void
|
|
_cogl_path_get_bounds (floatVec2 nodes_min,
|
|
floatVec2 nodes_max,
|
|
float *bounds_x,
|
|
float *bounds_y,
|
|
float *bounds_w,
|
|
float *bounds_h)
|
|
{
|
|
*bounds_x = nodes_min.x;
|
|
*bounds_y = nodes_min.y;
|
|
*bounds_w = nodes_max.x - *bounds_x;
|
|
*bounds_h = nodes_max.y - *bounds_y;
|
|
}
|
|
|
|
void
|
|
_cogl_add_path_to_stencil_buffer (CoglPath *path,
|
|
gboolean merge,
|
|
gboolean need_clear)
|
|
{
|
|
unsigned int path_start = 0;
|
|
float bounds_x;
|
|
float bounds_y;
|
|
float bounds_w;
|
|
float bounds_h;
|
|
unsigned long enable_flags = COGL_ENABLE_VERTEX_ARRAY;
|
|
CoglHandle prev_source;
|
|
CoglFramebuffer *framebuffer = _cogl_get_framebuffer ();
|
|
CoglMatrixStack *modelview_stack =
|
|
_cogl_framebuffer_get_modelview_stack (framebuffer);
|
|
CoglMatrixStack *projection_stack =
|
|
_cogl_framebuffer_get_projection_stack (framebuffer);
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
/* We don't track changes to the stencil buffer in the journal
|
|
* so we need to flush any batched geometry first */
|
|
_cogl_journal_flush ();
|
|
|
|
/* NB: _cogl_framebuffer_flush_state may disrupt various state (such
|
|
* as the material state) when flushing the clip stack, so should
|
|
* always be done first when preparing to draw. */
|
|
_cogl_framebuffer_flush_state (framebuffer, 0);
|
|
|
|
/* Just setup a simple material that doesn't use texturing... */
|
|
prev_source = cogl_object_ref (ctx->source_material);
|
|
cogl_set_source (ctx->stencil_material);
|
|
|
|
_cogl_material_flush_gl_state (ctx->source_material, NULL);
|
|
|
|
enable_flags |=
|
|
_cogl_material_get_cogl_enable_flags (ctx->source_material);
|
|
_cogl_enable (enable_flags);
|
|
|
|
_cogl_path_get_bounds (path->data->path_nodes_min, path->data->path_nodes_max,
|
|
&bounds_x, &bounds_y, &bounds_w, &bounds_h);
|
|
|
|
GE( glEnable (GL_STENCIL_TEST) );
|
|
|
|
GE( glColorMask (FALSE, FALSE, FALSE, FALSE) );
|
|
GE( glDepthMask (FALSE) );
|
|
|
|
if (merge)
|
|
{
|
|
GE (glStencilMask (2));
|
|
GE (glStencilFunc (GL_LEQUAL, 0x2, 0x6));
|
|
}
|
|
else
|
|
{
|
|
/* If we're not using the stencil buffer for clipping then we
|
|
don't need to clear the whole stencil buffer, just the area
|
|
that will be drawn */
|
|
if (need_clear)
|
|
cogl_clear (NULL, COGL_BUFFER_BIT_STENCIL);
|
|
else
|
|
{
|
|
/* Just clear the bounding box */
|
|
GE( glStencilMask (~(GLuint) 0) );
|
|
GE( glStencilOp (GL_ZERO, GL_ZERO, GL_ZERO) );
|
|
cogl_rectangle (bounds_x, bounds_y,
|
|
bounds_x + bounds_w, bounds_y + bounds_h);
|
|
/* Make sure the rectangle hits the stencil buffer before
|
|
* directly changing other GL state. */
|
|
_cogl_journal_flush ();
|
|
/* NB: The journal flushing may trash the modelview state and
|
|
* enable flags */
|
|
_cogl_matrix_stack_flush_to_gl (modelview_stack,
|
|
COGL_MATRIX_MODELVIEW);
|
|
_cogl_enable (enable_flags);
|
|
}
|
|
GE (glStencilMask (1));
|
|
GE (glStencilFunc (GL_LEQUAL, 0x1, 0x3));
|
|
}
|
|
|
|
GE (glStencilOp (GL_INVERT, GL_INVERT, GL_INVERT));
|
|
|
|
/* Disable all client texture coordinate arrays */
|
|
_cogl_bitmask_clear_all (&ctx->temp_bitmask);
|
|
_cogl_disable_other_texcoord_arrays (&ctx->temp_bitmask);
|
|
|
|
while (path_start < path->data->path_nodes->len)
|
|
{
|
|
CoglPathNode *node =
|
|
&g_array_index (path->data->path_nodes, CoglPathNode, path_start);
|
|
|
|
GE (glVertexPointer (2, GL_FLOAT, sizeof (CoglPathNode), &node->x));
|
|
GE (glDrawArrays (GL_TRIANGLE_FAN, 0, node->path_size));
|
|
|
|
path_start += node->path_size;
|
|
}
|
|
|
|
if (merge)
|
|
{
|
|
/* Now we have the new stencil buffer in bit 1 and the old
|
|
stencil buffer in bit 0 so we need to intersect them */
|
|
GE (glStencilMask (3));
|
|
GE (glStencilFunc (GL_NEVER, 0x2, 0x3));
|
|
GE (glStencilOp (GL_DECR, GL_DECR, GL_DECR));
|
|
/* Decrement all of the bits twice so that only pixels where the
|
|
value is 3 will remain */
|
|
|
|
_cogl_matrix_stack_push (projection_stack);
|
|
_cogl_matrix_stack_load_identity (projection_stack);
|
|
_cogl_matrix_stack_flush_to_gl (projection_stack,
|
|
COGL_MATRIX_PROJECTION);
|
|
|
|
_cogl_matrix_stack_push (modelview_stack);
|
|
_cogl_matrix_stack_load_identity (modelview_stack);
|
|
_cogl_matrix_stack_flush_to_gl (modelview_stack,
|
|
COGL_MATRIX_MODELVIEW);
|
|
|
|
cogl_rectangle (-1.0, -1.0, 1.0, 1.0);
|
|
cogl_rectangle (-1.0, -1.0, 1.0, 1.0);
|
|
/* Make sure these rectangles hit the stencil buffer before we
|
|
* restore the stencil op/func. */
|
|
_cogl_journal_flush ();
|
|
|
|
_cogl_matrix_stack_pop (modelview_stack);
|
|
_cogl_matrix_stack_pop (projection_stack);
|
|
}
|
|
|
|
GE (glStencilMask (~(GLuint) 0));
|
|
GE (glDepthMask (TRUE));
|
|
GE (glColorMask (TRUE, TRUE, TRUE, TRUE));
|
|
|
|
GE (glStencilFunc (GL_EQUAL, 0x1, 0x1));
|
|
GE (glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP));
|
|
|
|
/* restore the original material */
|
|
cogl_set_source (prev_source);
|
|
cogl_object_unref (prev_source);
|
|
}
|
|
|
|
static int
|
|
compare_ints (gconstpointer a,
|
|
gconstpointer b)
|
|
{
|
|
return GPOINTER_TO_INT(a)-GPOINTER_TO_INT(b);
|
|
}
|
|
|
|
static void
|
|
_cogl_path_fill_nodes_scanlines (CoglPathNode *path,
|
|
unsigned int path_size,
|
|
int bounds_x,
|
|
int bounds_y,
|
|
unsigned int bounds_w,
|
|
unsigned int bounds_h)
|
|
{
|
|
/* This is our edge list it stores intersections between our
|
|
* curve and scanlines, it should probably be implemented with a
|
|
* data structure that has smaller overhead for inserting the
|
|
* curve/scanline intersections.
|
|
*/
|
|
GSList **scanlines = g_alloca (bounds_h * sizeof (GSList *));
|
|
|
|
int i;
|
|
int prev_x;
|
|
int prev_y;
|
|
int first_x;
|
|
int first_y;
|
|
int lastdir = -2; /* last direction we vere moving */
|
|
int lastline = -1; /* the previous scanline we added to */
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
/* We are going to use GL to draw directly so make sure any
|
|
* previously batched geometry gets to GL before we start...
|
|
*/
|
|
_cogl_journal_flush ();
|
|
|
|
/* NB: _cogl_framebuffer_flush_state may disrupt various state (such
|
|
* as the material state) when flushing the clip stack, so should
|
|
* always be done first when preparing to draw. */
|
|
_cogl_framebuffer_flush_state (_cogl_get_framebuffer (), 0);
|
|
|
|
_cogl_material_flush_gl_state (ctx->source_material, NULL);
|
|
|
|
_cogl_enable (COGL_ENABLE_VERTEX_ARRAY
|
|
| (ctx->color_alpha < 255 ? COGL_ENABLE_BLEND : 0));
|
|
|
|
/* clear scanline intersection lists */
|
|
for (i = 0; i < bounds_h; i++)
|
|
scanlines[i]=NULL;
|
|
|
|
first_x = prev_x = path->x;
|
|
first_y = prev_y = path->y;
|
|
|
|
/* create scanline intersection list */
|
|
for (i=1; i < path_size; i++)
|
|
{
|
|
int dest_x = path[i].x;
|
|
int dest_y = path[i].y;
|
|
int ydir;
|
|
int dx;
|
|
int dy;
|
|
int y;
|
|
|
|
fill_close:
|
|
dx = dest_x - prev_x;
|
|
dy = dest_y - prev_y;
|
|
|
|
if (dy < 0)
|
|
ydir = -1;
|
|
else if (dy > 0)
|
|
ydir = 1;
|
|
else
|
|
ydir = 0;
|
|
|
|
/* do linear interpolation between vertices */
|
|
for (y = prev_y; y != dest_y; y += ydir)
|
|
{
|
|
|
|
/* only add a point if the scanline has changed and we're
|
|
* within bounds.
|
|
*/
|
|
if (y - bounds_y >= 0 &&
|
|
y - bounds_y < bounds_h &&
|
|
lastline != y)
|
|
{
|
|
int x = prev_x + (dx * (y-prev_y)) / dy;
|
|
|
|
scanlines[ y - bounds_y ]=
|
|
g_slist_insert_sorted (scanlines[ y - bounds_y],
|
|
GINT_TO_POINTER(x),
|
|
compare_ints);
|
|
|
|
if (ydir != lastdir && /* add a double entry when changing */
|
|
lastdir != -2) /* vertical direction */
|
|
scanlines[ y - bounds_y ]=
|
|
g_slist_insert_sorted (scanlines[ y - bounds_y],
|
|
GINT_TO_POINTER(x),
|
|
compare_ints);
|
|
lastdir = ydir;
|
|
lastline = y;
|
|
}
|
|
}
|
|
|
|
prev_x = dest_x;
|
|
prev_y = dest_y;
|
|
|
|
/* if we're on the last knot, fake the first vertex being a
|
|
next one */
|
|
if (path_size == i+1)
|
|
{
|
|
dest_x = first_x;
|
|
dest_y = first_y;
|
|
i++; /* to make the loop finally end */
|
|
goto fill_close;
|
|
}
|
|
}
|
|
|
|
{
|
|
int spans = 0;
|
|
int span_no;
|
|
GLfloat *coords;
|
|
|
|
/* count number of spans */
|
|
for (i = 0; i < bounds_h; i++)
|
|
{
|
|
GSList *iter = scanlines[i];
|
|
while (iter)
|
|
{
|
|
GSList *next = iter->next;
|
|
if (!next)
|
|
{
|
|
break;
|
|
}
|
|
/* draw the segments that should be visible */
|
|
spans ++;
|
|
iter = next->next;
|
|
}
|
|
}
|
|
coords = g_malloc0 (spans * sizeof (GLfloat) * 3 * 2 * 2);
|
|
|
|
span_no = 0;
|
|
/* build list of triangles */
|
|
for (i = 0; i < bounds_h; i++)
|
|
{
|
|
GSList *iter = scanlines[i];
|
|
while (iter)
|
|
{
|
|
GSList *next = iter->next;
|
|
GLfloat x_0, x_1;
|
|
GLfloat y_0, y_1;
|
|
if (!next)
|
|
break;
|
|
|
|
x_0 = GPOINTER_TO_INT (iter->data);
|
|
x_1 = GPOINTER_TO_INT (next->data);
|
|
y_0 = bounds_y + i;
|
|
y_1 = bounds_y + i + 1.0625f;
|
|
/* render scanlines 1.0625 high to avoid gaps when
|
|
transformed */
|
|
|
|
coords[span_no * 12 + 0] = x_0;
|
|
coords[span_no * 12 + 1] = y_0;
|
|
coords[span_no * 12 + 2] = x_1;
|
|
coords[span_no * 12 + 3] = y_0;
|
|
coords[span_no * 12 + 4] = x_1;
|
|
coords[span_no * 12 + 5] = y_1;
|
|
coords[span_no * 12 + 6] = x_0;
|
|
coords[span_no * 12 + 7] = y_0;
|
|
coords[span_no * 12 + 8] = x_0;
|
|
coords[span_no * 12 + 9] = y_1;
|
|
coords[span_no * 12 + 10] = x_1;
|
|
coords[span_no * 12 + 11] = y_1;
|
|
span_no ++;
|
|
iter = next->next;
|
|
}
|
|
}
|
|
for (i = 0; i < bounds_h; i++)
|
|
g_slist_free (scanlines[i]);
|
|
|
|
/* render triangles */
|
|
GE (glVertexPointer (2, GL_FLOAT, 0, coords ));
|
|
GE (glDrawArrays (GL_TRIANGLES, 0, spans * 2 * 3));
|
|
g_free (coords);
|
|
}
|
|
}
|
|
|
|
static void
|
|
_cogl_path_fill_nodes (void)
|
|
{
|
|
CoglPathData *data;
|
|
float bounds_x;
|
|
float bounds_y;
|
|
float bounds_w;
|
|
float bounds_h;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
data = COGL_PATH (ctx->current_path)->data;
|
|
|
|
_cogl_path_get_bounds (data->path_nodes_min, data->path_nodes_max,
|
|
&bounds_x, &bounds_y, &bounds_w, &bounds_h);
|
|
|
|
if (G_LIKELY (!(cogl_debug_flags & COGL_DEBUG_FORCE_SCANLINE_PATHS)) &&
|
|
cogl_features_available (COGL_FEATURE_STENCIL_BUFFER))
|
|
{
|
|
CoglFramebuffer *framebuffer;
|
|
CoglClipState *clip_state;
|
|
|
|
_cogl_journal_flush ();
|
|
|
|
framebuffer = _cogl_get_framebuffer ();
|
|
clip_state = _cogl_framebuffer_get_clip_state (framebuffer);
|
|
|
|
_cogl_add_path_to_stencil_buffer (ctx->current_path,
|
|
clip_state->stencil_used,
|
|
FALSE);
|
|
|
|
cogl_rectangle (bounds_x, bounds_y,
|
|
bounds_x + bounds_w, bounds_y + bounds_h);
|
|
|
|
/* The stencil buffer now contains garbage so the clip area needs to
|
|
* be rebuilt.
|
|
*
|
|
* NB: We only ever try and update the clip state during
|
|
* _cogl_journal_init (when we flush the framebuffer state) which is
|
|
* only called when the journal first gets something logged in it; so
|
|
* we call cogl_flush() to emtpy the journal.
|
|
*/
|
|
cogl_flush ();
|
|
_cogl_clip_state_dirty (clip_state);
|
|
}
|
|
else
|
|
{
|
|
unsigned int path_start = 0;
|
|
|
|
while (path_start < data->path_nodes->len)
|
|
{
|
|
CoglPathNode *node = &g_array_index (data->path_nodes, CoglPathNode,
|
|
path_start);
|
|
|
|
_cogl_path_fill_nodes_scanlines (node, node->path_size,
|
|
bounds_x, bounds_y,
|
|
bounds_w, bounds_h);
|
|
|
|
path_start += node->path_size;
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
cogl_path_fill (void)
|
|
{
|
|
cogl_path_fill_preserve ();
|
|
|
|
cogl_path_new ();
|
|
}
|
|
|
|
void
|
|
cogl_path_fill_preserve (void)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
if (COGL_PATH (ctx->current_path)->data->path_nodes->len == 0)
|
|
return;
|
|
|
|
_cogl_path_fill_nodes ();
|
|
}
|
|
|
|
void
|
|
cogl_path_stroke (void)
|
|
{
|
|
cogl_path_stroke_preserve ();
|
|
|
|
cogl_path_new ();
|
|
}
|
|
|
|
void
|
|
cogl_path_stroke_preserve (void)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
if (COGL_PATH (ctx->current_path)->data->path_nodes->len == 0)
|
|
return;
|
|
|
|
_cogl_path_stroke_nodes ();
|
|
}
|
|
|
|
void
|
|
cogl_path_move_to (float x,
|
|
float y)
|
|
{
|
|
CoglPathData *data;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
_cogl_path_add_node (TRUE, x, y);
|
|
|
|
data = COGL_PATH (ctx->current_path)->data;
|
|
|
|
data->path_start.x = x;
|
|
data->path_start.y = y;
|
|
|
|
data->path_pen = data->path_start;
|
|
}
|
|
|
|
void
|
|
cogl_path_rel_move_to (float x,
|
|
float y)
|
|
{
|
|
CoglPathData *data;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
data = COGL_PATH (ctx->current_path)->data;
|
|
|
|
cogl_path_move_to (data->path_pen.x + x,
|
|
data->path_pen.y + y);
|
|
}
|
|
|
|
void
|
|
cogl_path_line_to (float x,
|
|
float y)
|
|
{
|
|
CoglPathData *data;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
_cogl_path_add_node (FALSE, x, y);
|
|
|
|
data = COGL_PATH (ctx->current_path)->data;
|
|
|
|
data->path_pen.x = x;
|
|
data->path_pen.y = y;
|
|
}
|
|
|
|
void
|
|
cogl_path_rel_line_to (float x,
|
|
float y)
|
|
{
|
|
CoglPathData *data;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
data = COGL_PATH (ctx->current_path)->data;
|
|
|
|
cogl_path_line_to (data->path_pen.x + x,
|
|
data->path_pen.y + y);
|
|
}
|
|
|
|
void
|
|
cogl_path_close (void)
|
|
{
|
|
CoglPath *path;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
path = COGL_PATH (ctx->current_path);
|
|
|
|
_cogl_path_add_node (FALSE, path->data->path_start.x,
|
|
path->data->path_start.y);
|
|
|
|
path->data->path_pen = path->data->path_start;
|
|
}
|
|
|
|
void
|
|
cogl_path_new (void)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
cogl_object_unref (ctx->current_path);
|
|
ctx->current_path = _cogl_path_new ();
|
|
}
|
|
|
|
void
|
|
cogl_path_line (float x_1,
|
|
float y_1,
|
|
float x_2,
|
|
float y_2)
|
|
{
|
|
cogl_path_move_to (x_1, y_1);
|
|
cogl_path_line_to (x_2, y_2);
|
|
}
|
|
|
|
void
|
|
cogl_path_polyline (float *coords,
|
|
int num_points)
|
|
{
|
|
int c = 0;
|
|
|
|
cogl_path_move_to (coords[0], coords[1]);
|
|
|
|
for (c = 1; c < num_points; ++c)
|
|
cogl_path_line_to (coords[2*c], coords[2*c+1]);
|
|
}
|
|
|
|
void
|
|
cogl_path_polygon (float *coords,
|
|
int num_points)
|
|
{
|
|
cogl_path_polyline (coords, num_points);
|
|
cogl_path_close ();
|
|
}
|
|
|
|
void
|
|
cogl_path_rectangle (float x_1,
|
|
float y_1,
|
|
float x_2,
|
|
float y_2)
|
|
{
|
|
cogl_path_move_to (x_1, y_1);
|
|
cogl_path_line_to (x_2, y_1);
|
|
cogl_path_line_to (x_2, y_2);
|
|
cogl_path_line_to (x_1, y_2);
|
|
cogl_path_close ();
|
|
}
|
|
|
|
static void
|
|
_cogl_path_arc (float center_x,
|
|
float center_y,
|
|
float radius_x,
|
|
float radius_y,
|
|
float angle_1,
|
|
float angle_2,
|
|
float angle_step,
|
|
unsigned int move_first)
|
|
{
|
|
float a = 0x0;
|
|
float cosa = 0x0;
|
|
float sina = 0x0;
|
|
float px = 0x0;
|
|
float py = 0x0;
|
|
|
|
/* Fix invalid angles */
|
|
|
|
if (angle_1 == angle_2 || angle_step == 0x0)
|
|
return;
|
|
|
|
if (angle_step < 0x0)
|
|
angle_step = -angle_step;
|
|
|
|
/* Walk the arc by given step */
|
|
|
|
a = angle_1;
|
|
while (a != angle_2)
|
|
{
|
|
cosa = cosf (a * (G_PI/180.0));
|
|
sina = sinf (a * (G_PI/180.0));
|
|
|
|
px = center_x + (cosa * radius_x);
|
|
py = center_y + (sina * radius_y);
|
|
|
|
if (a == angle_1 && move_first)
|
|
cogl_path_move_to (px, py);
|
|
else
|
|
cogl_path_line_to (px, py);
|
|
|
|
if (G_LIKELY (angle_2 > angle_1))
|
|
{
|
|
a += angle_step;
|
|
if (a > angle_2)
|
|
a = angle_2;
|
|
}
|
|
else
|
|
{
|
|
a -= angle_step;
|
|
if (a < angle_2)
|
|
a = angle_2;
|
|
}
|
|
}
|
|
|
|
/* Make sure the final point is drawn */
|
|
|
|
cosa = cosf (angle_2 * (G_PI/180.0));
|
|
sina = sinf (angle_2 * (G_PI/180.0));
|
|
|
|
px = center_x + (cosa * radius_x);
|
|
py = center_y + (sina * radius_y);
|
|
|
|
cogl_path_line_to (px, py);
|
|
}
|
|
|
|
void
|
|
cogl_path_arc (float center_x,
|
|
float center_y,
|
|
float radius_x,
|
|
float radius_y,
|
|
float angle_1,
|
|
float angle_2)
|
|
{
|
|
float angle_step = 10;
|
|
/* it is documented that a move to is needed to create a freestanding
|
|
* arc
|
|
*/
|
|
_cogl_path_arc (center_x, center_y,
|
|
radius_x, radius_y,
|
|
angle_1, angle_2,
|
|
angle_step, 0 /* no move */);
|
|
}
|
|
|
|
|
|
static void
|
|
_cogl_path_rel_arc (float center_x,
|
|
float center_y,
|
|
float radius_x,
|
|
float radius_y,
|
|
float angle_1,
|
|
float angle_2,
|
|
float angle_step)
|
|
{
|
|
CoglPathData *data;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
data = COGL_PATH (ctx->current_path)->data;
|
|
|
|
_cogl_path_arc (data->path_pen.x + center_x,
|
|
data->path_pen.y + center_y,
|
|
radius_x, radius_y,
|
|
angle_1, angle_2,
|
|
angle_step, 0 /* no move */);
|
|
}
|
|
|
|
void
|
|
cogl_path_ellipse (float center_x,
|
|
float center_y,
|
|
float radius_x,
|
|
float radius_y)
|
|
{
|
|
float angle_step = 10;
|
|
|
|
/* FIXME: if shows to be slow might be optimized
|
|
* by mirroring just a quarter of it */
|
|
|
|
_cogl_path_arc (center_x, center_y,
|
|
radius_x, radius_y,
|
|
0, 360,
|
|
angle_step, 1 /* move first */);
|
|
|
|
cogl_path_close();
|
|
}
|
|
|
|
void
|
|
cogl_path_round_rectangle (float x_1,
|
|
float y_1,
|
|
float x_2,
|
|
float y_2,
|
|
float radius,
|
|
float arc_step)
|
|
{
|
|
CoglPath *path;
|
|
float inner_width = x_2 - x_1 - radius * 2;
|
|
float inner_height = y_2 - y_1 - radius * 2;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
path = COGL_PATH (ctx->current_path);
|
|
|
|
cogl_path_move_to (x_1, y_1 + radius);
|
|
_cogl_path_rel_arc (radius, 0,
|
|
radius, radius,
|
|
180,
|
|
270,
|
|
arc_step);
|
|
|
|
cogl_path_line_to (path->data->path_pen.x + inner_width,
|
|
path->data->path_pen.y);
|
|
_cogl_path_rel_arc (0, radius,
|
|
radius, radius,
|
|
-90,
|
|
0,
|
|
arc_step);
|
|
|
|
cogl_path_line_to (path->data->path_pen.x,
|
|
path->data->path_pen.y + inner_height);
|
|
|
|
_cogl_path_rel_arc (-radius, 0,
|
|
radius, radius,
|
|
0,
|
|
90,
|
|
arc_step);
|
|
|
|
cogl_path_line_to (path->data->path_pen.x - inner_width,
|
|
path->data->path_pen.y);
|
|
_cogl_path_rel_arc (0, -radius,
|
|
radius, radius,
|
|
90,
|
|
180,
|
|
arc_step);
|
|
|
|
cogl_path_close ();
|
|
}
|
|
|
|
|
|
static void
|
|
_cogl_path_bezier3_sub (CoglBezCubic *cubic)
|
|
{
|
|
CoglBezCubic cubics[_COGL_MAX_BEZ_RECURSE_DEPTH];
|
|
CoglBezCubic *cleft;
|
|
CoglBezCubic *cright;
|
|
CoglBezCubic *c;
|
|
floatVec2 dif1;
|
|
floatVec2 dif2;
|
|
floatVec2 mm;
|
|
floatVec2 c1;
|
|
floatVec2 c2;
|
|
floatVec2 c3;
|
|
floatVec2 c4;
|
|
floatVec2 c5;
|
|
int cindex;
|
|
|
|
/* Put first curve on stack */
|
|
cubics[0] = *cubic;
|
|
cindex = 0;
|
|
|
|
while (cindex >= 0)
|
|
{
|
|
c = &cubics[cindex];
|
|
|
|
|
|
/* Calculate distance of control points from their
|
|
* counterparts on the line between end points */
|
|
dif1.x = (c->p2.x * 3) - (c->p1.x * 2) - c->p4.x;
|
|
dif1.y = (c->p2.y * 3) - (c->p1.y * 2) - c->p4.y;
|
|
dif2.x = (c->p3.x * 3) - (c->p4.x * 2) - c->p1.x;
|
|
dif2.y = (c->p3.y * 3) - (c->p4.y * 2) - c->p1.y;
|
|
|
|
if (dif1.x < 0)
|
|
dif1.x = -dif1.x;
|
|
if (dif1.y < 0)
|
|
dif1.y = -dif1.y;
|
|
if (dif2.x < 0)
|
|
dif2.x = -dif2.x;
|
|
if (dif2.y < 0)
|
|
dif2.y = -dif2.y;
|
|
|
|
|
|
/* Pick the greatest of two distances */
|
|
if (dif1.x < dif2.x) dif1.x = dif2.x;
|
|
if (dif1.y < dif2.y) dif1.y = dif2.y;
|
|
|
|
/* Cancel if the curve is flat enough */
|
|
if (dif1.x + dif1.y <= 1.0 ||
|
|
cindex == _COGL_MAX_BEZ_RECURSE_DEPTH-1)
|
|
{
|
|
/* Add subdivision point (skip last) */
|
|
if (cindex == 0)
|
|
return;
|
|
|
|
_cogl_path_add_node (FALSE, c->p4.x, c->p4.y);
|
|
|
|
--cindex;
|
|
|
|
continue;
|
|
}
|
|
|
|
/* Left recursion goes on top of stack! */
|
|
cright = c; cleft = &cubics[++cindex];
|
|
|
|
/* Subdivide into 2 sub-curves */
|
|
c1.x = ((c->p1.x + c->p2.x) / 2);
|
|
c1.y = ((c->p1.y + c->p2.y) / 2);
|
|
mm.x = ((c->p2.x + c->p3.x) / 2);
|
|
mm.y = ((c->p2.y + c->p3.y) / 2);
|
|
c5.x = ((c->p3.x + c->p4.x) / 2);
|
|
c5.y = ((c->p3.y + c->p4.y) / 2);
|
|
|
|
c2.x = ((c1.x + mm.x) / 2);
|
|
c2.y = ((c1.y + mm.y) / 2);
|
|
c4.x = ((mm.x + c5.x) / 2);
|
|
c4.y = ((mm.y + c5.y) / 2);
|
|
|
|
c3.x = ((c2.x + c4.x) / 2);
|
|
c3.y = ((c2.y + c4.y) / 2);
|
|
|
|
/* Add left recursion to stack */
|
|
cleft->p1 = c->p1;
|
|
cleft->p2 = c1;
|
|
cleft->p3 = c2;
|
|
cleft->p4 = c3;
|
|
|
|
/* Add right recursion to stack */
|
|
cright->p1 = c3;
|
|
cright->p2 = c4;
|
|
cright->p3 = c5;
|
|
cright->p4 = c->p4;
|
|
}
|
|
}
|
|
|
|
void
|
|
cogl_path_curve_to (float x_1,
|
|
float y_1,
|
|
float x_2,
|
|
float y_2,
|
|
float x_3,
|
|
float y_3)
|
|
{
|
|
CoglBezCubic cubic;
|
|
CoglPath *path;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
path = COGL_PATH (ctx->current_path);
|
|
|
|
/* Prepare cubic curve */
|
|
cubic.p1 = path->data->path_pen;
|
|
cubic.p2.x = x_1;
|
|
cubic.p2.y = y_1;
|
|
cubic.p3.x = x_2;
|
|
cubic.p3.y = y_2;
|
|
cubic.p4.x = x_3;
|
|
cubic.p4.y = y_3;
|
|
|
|
/* Run subdivision */
|
|
_cogl_path_bezier3_sub (&cubic);
|
|
|
|
/* Add last point */
|
|
_cogl_path_add_node (FALSE, cubic.p4.x, cubic.p4.y);
|
|
path->data->path_pen = cubic.p4;
|
|
}
|
|
|
|
void
|
|
cogl_path_rel_curve_to (float x_1,
|
|
float y_1,
|
|
float x_2,
|
|
float y_2,
|
|
float x_3,
|
|
float y_3)
|
|
{
|
|
CoglPathData *data;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
data = COGL_PATH (ctx->current_path)->data;
|
|
|
|
cogl_path_curve_to (data->path_pen.x + x_1,
|
|
data->path_pen.y + y_1,
|
|
data->path_pen.x + x_2,
|
|
data->path_pen.y + y_2,
|
|
data->path_pen.x + x_3,
|
|
data->path_pen.y + y_3);
|
|
}
|
|
|
|
CoglPath *
|
|
_cogl_path_new (void)
|
|
{
|
|
CoglPath *path;
|
|
CoglPathData *data;
|
|
|
|
path = g_slice_new (CoglPath);
|
|
data = path->data = g_slice_new (CoglPathData);
|
|
|
|
data->ref_count = 1;
|
|
data->path_nodes = g_array_new (FALSE, FALSE, sizeof (CoglPathNode));
|
|
data->last_path = 0;
|
|
|
|
return _cogl_path_object_new (path);
|
|
}
|
|
|
|
CoglPath *
|
|
cogl_path_copy (CoglPath *old_path)
|
|
{
|
|
CoglPath *new_path;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NULL);
|
|
|
|
if (!cogl_is_path (old_path))
|
|
return NULL;
|
|
|
|
new_path = g_slice_new (CoglPath);
|
|
new_path->data = old_path->data;
|
|
new_path->data->ref_count++;
|
|
|
|
return _cogl_path_object_new (new_path);
|
|
}
|
|
|
|
static void
|
|
_cogl_path_free (CoglPath *path)
|
|
{
|
|
_cogl_path_data_unref (path->data);
|
|
g_slice_free (CoglPath, path);
|
|
}
|
|
|
|
/* If second order beziers were needed the following code could
|
|
* be re-enabled:
|
|
*/
|
|
#if 0
|
|
|
|
static void
|
|
_cogl_path_bezier2_sub (CoglBezQuad *quad)
|
|
{
|
|
CoglBezQuad quads[_COGL_MAX_BEZ_RECURSE_DEPTH];
|
|
CoglBezQuad *qleft;
|
|
CoglBezQuad *qright;
|
|
CoglBezQuad *q;
|
|
floatVec2 mid;
|
|
floatVec2 dif;
|
|
floatVec2 c1;
|
|
floatVec2 c2;
|
|
floatVec2 c3;
|
|
int qindex;
|
|
|
|
/* Put first curve on stack */
|
|
quads[0] = *quad;
|
|
qindex = 0;
|
|
|
|
/* While stack is not empty */
|
|
while (qindex >= 0)
|
|
{
|
|
|
|
q = &quads[qindex];
|
|
|
|
/* Calculate distance of control point from its
|
|
* counterpart on the line between end points */
|
|
mid.x = ((q->p1.x + q->p3.x) / 2);
|
|
mid.y = ((q->p1.y + q->p3.y) / 2);
|
|
dif.x = (q->p2.x - mid.x);
|
|
dif.y = (q->p2.y - mid.y);
|
|
if (dif.x < 0) dif.x = -dif.x;
|
|
if (dif.y < 0) dif.y = -dif.y;
|
|
|
|
/* Cancel if the curve is flat enough */
|
|
if (dif.x + dif.y <= 1.0 ||
|
|
qindex == _COGL_MAX_BEZ_RECURSE_DEPTH - 1)
|
|
{
|
|
/* Add subdivision point (skip last) */
|
|
if (qindex == 0) return;
|
|
_cogl_path_add_node (FALSE, q->p3.x, q->p3.y);
|
|
--qindex; continue;
|
|
}
|
|
|
|
/* Left recursion goes on top of stack! */
|
|
qright = q; qleft = &quads[++qindex];
|
|
|
|
/* Subdivide into 2 sub-curves */
|
|
c1.x = ((q->p1.x + q->p2.x) / 2);
|
|
c1.y = ((q->p1.y + q->p2.y) / 2);
|
|
c3.x = ((q->p2.x + q->p3.x) / 2);
|
|
c3.y = ((q->p2.y + q->p3.y) / 2);
|
|
c2.x = ((c1.x + c3.x) / 2);
|
|
c2.y = ((c1.y + c3.y) / 2);
|
|
|
|
/* Add left recursion onto stack */
|
|
qleft->p1 = q->p1;
|
|
qleft->p2 = c1;
|
|
qleft->p3 = c2;
|
|
|
|
/* Add right recursion onto stack */
|
|
qright->p1 = c2;
|
|
qright->p2 = c3;
|
|
qright->p3 = q->p3;
|
|
}
|
|
}
|
|
|
|
void
|
|
cogl_path_curve2_to (float x_1,
|
|
float y_1,
|
|
float x_2,
|
|
float y_2)
|
|
{
|
|
CoglPath *path;
|
|
CoglBezQuad quad;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
path = COGL_PATH (ctx->current_path);
|
|
|
|
/* Prepare quadratic curve */
|
|
quad.p1 = path->data->path_pen;
|
|
quad.p2.x = x_1;
|
|
quad.p2.y = y_1;
|
|
quad.p3.x = x_2;
|
|
quad.p3.y = y_2;
|
|
|
|
/* Run subdivision */
|
|
_cogl_path_bezier2_sub (&quad);
|
|
|
|
/* Add last point */
|
|
_cogl_path_add_node (FALSE, quad.p3.x, quad.p3.y);
|
|
path->data->path_pen = quad.p3;
|
|
}
|
|
|
|
void
|
|
cogl_rel_curve2_to (float x_1,
|
|
float y_1,
|
|
float x_2,
|
|
float y_2)
|
|
{
|
|
CoglPathData *data;
|
|
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
data = COGL_PATH (ctx->current_path)->data;
|
|
|
|
cogl_path_curve2_to (data->path_pen.x + x_1,
|
|
data->path_pen.y + y_1,
|
|
data->path_pen.x + x_2,
|
|
data->path_pen.y + y_2);
|
|
}
|
|
|
|
#endif
|
|
|
|
CoglPath *
|
|
cogl_get_path (void)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NULL);
|
|
|
|
return ctx->current_path;
|
|
}
|
|
|
|
void
|
|
cogl_set_path (CoglPath *path)
|
|
{
|
|
_COGL_GET_CONTEXT (ctx, NO_RETVAL);
|
|
|
|
if (!cogl_is_path (path))
|
|
return;
|
|
|
|
/* Reference the new object first in case it is the same as the old
|
|
object */
|
|
cogl_object_ref (path);
|
|
cogl_object_unref (ctx->current_path);
|
|
ctx->current_path = path;
|
|
}
|
|
|