/* * Triangles.c -- Implementation of Triangle fan/strips item. * * Authors : Patrick Lecoanet. * Creation date : Tue Dec 11 10:52:01 2001 * * $Id$ */ /* * Copyright (c) 1993 - 2001 CENA, Patrick Lecoanet -- * * This code is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This code 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 * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this code; if not, write to the Free * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ #include "Draw.h" #include "Item.h" #include "Geo.h" #include "Types.h" #include "WidgetInfo.h" #include "Image.h" #include "Color.h" #include #include static const char rcsid[] = "$Id"; static const char compile_id[]="$Compile: " __FILE__ " " __DATE__ " " __TIME__ " $"; /* * Bit offset of flags. */ #define FAN_BIT 1<<0 /* Tell if the triangles are arranged in fan or strips. */ /* ********************************************************************************** * * Specific Triangles item record * ********************************************************************************** */ typedef struct _TrianglesItemStruct { ItemStruct header; /* Public data */ ZnList points; unsigned short flags; ZnList colors; /* Private data */ ZnTriStrip dev_points; } TrianglesItemStruct, *TrianglesItem; static ZnAttrConfig tr_attrs[] = { { ZN_CONFIG_GRADIENT_LIST, "-colors", NULL, Tk_Offset(TrianglesItemStruct, colors), 0, ZN_DRAW_FLAG, False }, { ZN_CONFIG_BOOL, "-composealpha", NULL, Tk_Offset(TrianglesItemStruct, header.flags), COMPOSE_ALPHA_BIT, ZN_DRAW_FLAG, False }, { ZN_CONFIG_BOOL, "-composerotation", NULL, Tk_Offset(TrianglesItemStruct, header.flags), COMPOSE_ROTATION_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_BOOL, "-composescale", NULL, Tk_Offset(TrianglesItemStruct, header.flags), COMPOSE_SCALE_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_BOOL, "-fan", NULL, Tk_Offset(TrianglesItemStruct, flags), FAN_BIT, ZN_COORDS_FLAG, False }, { ZN_CONFIG_PRI, "-priority", NULL, Tk_Offset(TrianglesItemStruct, header.priority), 0, ZN_DRAW_FLAG|ZN_REPICK_FLAG, False }, { ZN_CONFIG_BOOL, "-sensitive", NULL, Tk_Offset(TrianglesItemStruct, header.flags), SENSITIVE_BIT, ZN_REPICK_FLAG, False }, { ZN_CONFIG_TAG_LIST, "-tags", NULL, Tk_Offset(TrianglesItemStruct, header.tags), 0, 0, False }, { ZN_CONFIG_BOOL, "-visible", NULL, Tk_Offset(TrianglesItemStruct, header.flags), VISIBLE_BIT, ZN_DRAW_FLAG|ZN_REPICK_FLAG|ZN_VIS_FLAG, False }, { ZN_CONFIG_END, NULL, NULL, 0, 0, 0 } }; /* ********************************************************************************** * * Init -- * ********************************************************************************** */ static int Init(Item item, int *argc, Tcl_Obj *CONST *args[]) { WidgetInfo *wi = item->wi; TrianglesItem tr = (TrianglesItem) item; Tcl_Obj **elems; int i, num_elems; ZnPoint p; ZnGradient **grads; tr->dev_points.num_strips = 0; /* Init attributes */ SET(item->flags, VISIBLE_BIT); SET(item->flags, SENSITIVE_BIT); SET(item->flags, COMPOSE_ALPHA_BIT); SET(item->flags, COMPOSE_ROTATION_BIT); SET(item->flags, COMPOSE_SCALE_BIT); item->priority = DEFAULT_TRIANGLES_PRIORITY; if (*argc < 1) { Tcl_AppendResult(wi->interp, " triangles coords expected", NULL); return ZN_ERROR; } if ((Tcl_ListObjGetElements(wi->interp, (*args)[0], &num_elems, &elems) == ZN_ERROR) || ((num_elems % 2) != 0) || (num_elems < 6)) { tr_error: Tcl_AppendResult(wi->interp, " malformed triangles coords", NULL); return ZN_ERROR; } tr->points = ZnListNew(num_elems/2, sizeof(ZnPoint)); for (i = 0; i < num_elems; i += 2) { if (Tcl_GetDoubleFromObj(wi->interp, elems[i], &p.x) == ZN_ERROR) { tr_error2: ZnListFree(tr->points); tr->points = NULL; goto tr_error; } if (Tcl_GetDoubleFromObj(wi->interp, elems[i+1], &p.y) == ZN_ERROR) { goto tr_error2; } ZnListAdd(tr->points, &p, ZnListTail); } (*args)++; (*argc)--; CLEAR(tr->flags, FAN_BIT); tr->colors = ZnListNew(1, sizeof(ZnGradient *)); ZnListAssertSize(tr->colors, 1); grads = ZnListArray(tr->colors); *grads = ZnGetGradientByValue(wi->fore_color); return ZN_OK; } /* ********************************************************************************** * * Clone -- * ********************************************************************************** */ static void Clone(Item item) { TrianglesItem tr = (TrianglesItem) item; if (tr->colors) { int i, num_grads; ZnGradient **grads; tr->colors = ZnListDuplicate(tr->colors); num_grads = ZnListSize(tr->colors); grads = ZnListArray(tr->colors); for (i = 0; i < num_grads; i++, grads++) { *grads = ZnGetGradientByValue(*grads); } } tr->dev_points.num_strips = 0; if (tr->points) { tr->points = ZnListDuplicate(tr->points); } } /* ********************************************************************************** * * Destroy -- * ********************************************************************************** */ static void Destroy(Item item) { TrianglesItem tr = (TrianglesItem) item; if (tr->points) { ZnListFree(tr->points); } if (tr->dev_points.num_strips) { ZnFree(tr->dev_points.strips->points); } if (tr->colors) { int i, num_grads; ZnGradient **grads; num_grads = ZnListSize(tr->colors); grads = ZnListArray(tr->colors); for (i = 0; i < num_grads; i++, grads++) { ZnFreeGradient(*grads); } ZnListFree(tr->colors); } } /* ********************************************************************************** * * Configure -- * ********************************************************************************** */ static int Configure(Item item, int argc, Tcl_Obj *CONST argv[], int *flags) { int status = ZN_OK; status = ZnConfigureAttributes(item->wi, item, tr_attrs, argc, argv, flags); return status; } /* ********************************************************************************** * * Query -- * ********************************************************************************** */ static int Query(Item item, int argc, Tcl_Obj *CONST argv[]) { if (ZnQueryAttribute(item->wi, item, tr_attrs, argv[0]) == ZN_ERROR) { return ZN_ERROR; } return ZN_OK; } /* ********************************************************************************** * * ComputeCoordinates -- * ********************************************************************************** */ static void ComputeCoordinates(Item item, ZnBool force) { WidgetInfo *wi = item->wi; TrianglesItem tr = (TrianglesItem) item; ZnPoint *points; ZnPoint *dev_points; int num_points; ResetBBox(&item->item_bounding_box); if (tr->points == NULL) { return; } points = (ZnPoint *) ZnListArray(tr->points); num_points = ZnListSize(tr->points); /* * Allocate space for devices coordinates */ if (tr->dev_points.num_strips == 0) { dev_points = ZnMalloc(num_points * sizeof(ZnPoint)); } else { dev_points = tr->dev_points.strips->points; if (tr->dev_points.strips->num_points < num_points) { dev_points = ZnRealloc(dev_points, num_points * sizeof(ZnPoint)); } } TRI_STRIP1(&tr->dev_points, dev_points, num_points, ISSET(tr->flags, FAN_BIT)); /* * Compute device coordinates. */ ZnTransformPoints(wi->current_transfo, points, dev_points, num_points); /* * Compute the bounding box. */ AddPointsToBBox(&item->item_bounding_box, dev_points, num_points); /* * Expand the bounding box by one pixel in all * directions to take care of rounding errors. */ item->item_bounding_box.orig.x -= 1; item->item_bounding_box.orig.y -= 1; item->item_bounding_box.corner.x += 1; item->item_bounding_box.corner.y += 1; } /* ********************************************************************************** * * ToArea -- * Tell if the object is entirely outside (-1), * entirely inside (1) or in between (0). * ********************************************************************************** */ static int ToArea(Item item, ZnToArea ta) { TrianglesItem tr = (TrianglesItem) item; ZnPoint *points; int i, num_points, result=-1, result2; ZnBBox *area = ta->area; if (tr->dev_points.num_strips == 0) { return -1; } points = tr->dev_points.strips->points; num_points = tr->dev_points.strips->num_points; if (ISCLEAR(tr->flags, FAN_BIT)) { result = PolygonInBBox(points, 3, area, NULL); if (result == 0) { return 0; } points++; for (i = 0; i < num_points-3; i++, points++) { result2 = PolygonInBBox(points, 3, area, NULL); if (result2 != result) { return 0; } } } else { ZnPoint tri[3]; tri[0] = points[0]; tri[1] = points[1]; tri[2] = points[2]; result = PolygonInBBox(points, num_points, area, NULL); if (result == 0) { return 0; } points += 3; for (i = 0; i < num_points-3; i++, points++) { tri[1] = tri[2]; tri[2] = *points; result2 = PolygonInBBox(points, num_points, area, NULL); if (result2 != result) { return 0; } } } return result; } /* ********************************************************************************** * * Draw -- * ********************************************************************************** */ static void Draw(Item item) { WidgetInfo *wi = item->wi; TrianglesItem tr = (TrianglesItem) item; int i, num_points, last_color_index; ZnPoint *points; ZnGradient **grads; if (tr->dev_points.num_strips == 0) { return; } points = tr->dev_points.strips->points; num_points = tr->dev_points.strips->num_points; grads = ZnListArray(tr->colors); last_color_index = ZnListSize(tr->colors)-1; XSetFillStyle(wi->dpy, wi->gc, FillSolid); if (ISCLEAR(tr->flags, FAN_BIT)) { XPoint *xpoints; ZnListAssertSize(wi->work_xpts, num_points); xpoints = ZnListArray(wi->work_xpts); for (i = 0; i < num_points; i++) { xpoints[i].x = REAL_TO_INT(points[i].x); xpoints[i].y = REAL_TO_INT(points[i].y); } for (i = 0; i < num_points-2; i++, xpoints++) { if (i <= last_color_index) { XSetForeground(wi->dpy, wi->gc, ZnPixel(ZnGetGradientColor(grads[i], 0, NULL))); } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, xpoints, 3, Convex, CoordModeOrigin); } } else { XPoint tri[3]; tri[0].x = REAL_TO_INT(points[0].x); tri[0].y = REAL_TO_INT(points[0].y); tri[1].x = REAL_TO_INT(points[1].x); tri[1].y = REAL_TO_INT(points[1].y); tri[2].x = REAL_TO_INT(points[2].x); tri[2].y = REAL_TO_INT(points[2].y); points += 3; for (i = 0; i < num_points-2; i++, points++) { if (i <= last_color_index) { XSetForeground(wi->dpy, wi->gc, ZnPixel(ZnGetGradientColor(grads[i], 0.0, NULL))); } XFillPolygon(wi->dpy, wi->draw_buffer, wi->gc, tri, 3, Convex, CoordModeOrigin); tri[1] = tri[2]; tri[2].x = REAL_TO_INT(points->x); tri[2].y = REAL_TO_INT(points->y); } } } /* ********************************************************************************** * * Render -- * ********************************************************************************** */ static void Render(Item item) { #ifdef GLX WidgetInfo *wi = item->wi; TrianglesItem tr = (TrianglesItem) item; int i, num_points, last_color_index; ZnPoint *points; ZnGradient **grads; int alpha; XColor *color; if (tr->dev_points.num_strips == 0) { return; } points = tr->dev_points.strips->points; num_points = tr->dev_points.strips->num_points; grads = ZnListArray(tr->colors); last_color_index = ZnListSize(tr->colors)-1; glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); if (ISCLEAR(tr->flags, FAN_BIT)) { glBegin(GL_TRIANGLE_STRIP); } else { glBegin(GL_TRIANGLE_FAN); } for (i = 0; i < num_points; i++, points++) { if (i <= last_color_index) { color = ZnGetGradientColor(grads[i], 0.0, &alpha); alpha = ZnComposeAlpha(alpha, wi->alpha); glColor4us(color->red, color->green, color->blue, alpha); } glVertex2f(points->x, points->y); } glEnd(); #endif } /* ********************************************************************************** * * IsSensitive -- * ********************************************************************************** */ static ZnBool IsSensitive(Item item, int item_part) { return (ISSET(item->flags, SENSITIVE_BIT) && item->parent->class->IsSensitive(item->parent, ZN_NO_PART)); } /* ********************************************************************************** * * Pick -- * ********************************************************************************** */ static double Pick(Item item, ZnPick ps) { TrianglesItem tr = (TrianglesItem) item; double dist=1.0e40, new_dist; ZnPoint *points, *p = ps->point; int i, num_points; if (tr->dev_points.num_strips == 0) { return dist; } points = tr->dev_points.strips->points; num_points = tr->dev_points.strips->num_points; if (ISCLEAR(tr->flags, FAN_BIT)) { for (i = 0; i < num_points-2; i++, points++) { new_dist = PolygonToPointDist(points, 3, p); if (new_dist <= 0.0) { return 0.0; } if (new_dist < dist) { dist = new_dist; } } } else { ZnPoint tri[3]; tri[0] = points[0]; tri[1] = points[1]; tri[2] = points[2]; for (i = 0; i < num_points-2; i++, points++) { new_dist = PolygonToPointDist(tri, 3, p); if (new_dist <= 0.0) { return 0.0; } if (new_dist < dist) { dist = new_dist; } tri[1] = tri[2]; tri[2] = *points; } } return dist; } /* ********************************************************************************** * * PostScript -- * ********************************************************************************** */ static void PostScript(Item item, PostScriptInfo ps_info) { } /* ********************************************************************************** * * GetClipVertices -- * Get the clipping shape. * Never ever call TRI_FREE on the tristrip returned by GetClipVertices. * ********************************************************************************** */ static ZnBool GetClipVertices(Item item, ZnTriStrip *tristrip) { TrianglesItem tr = (TrianglesItem) item; if (tr->dev_points.num_strips == 0) { tristrip->num_strips = 0; return True; } TRI_STRIP1(tristrip, tr->dev_points.strips->points, tr->dev_points.strips->num_points, tr->dev_points.strips[0].fan); return False; } /* ********************************************************************************** * * GetContours -- * Get the external contour(s). * Never ever call POLY_FREE on the poly returned by GetContours. * ********************************************************************************** */ static ZnBool GetContours(Item item, ZnPoly *poly) { TrianglesItem tr = (TrianglesItem) item; WidgetInfo *wi = item->wi; ZnPoint *points; int i, j, num_points; if (tr->dev_points.num_strips == 0) { poly->num_contours = 0; return True; } num_points = tr->dev_points.strips->num_points; if (ISCLEAR(tr->flags, FAN_BIT)) { ZnListAssertSize(wi->work_pts, num_points); points = ZnListArray(wi->work_pts); for (i = 1, j = 0; i < num_points; i += 2, j++) { points[j] = tr->dev_points.strips->points[i]; } i = num_points - 1; if (num_points % 2 == 0) { i--; } for ( ; i >= 0; i -= 2, j++) { points[j] = tr->dev_points.strips->points[i]; } POLY_CONTOUR1(poly, points, num_points); } else { POLY_CONTOUR1(poly, tr->dev_points.strips->points, num_points); } poly->contour1.cw = !TestCCW(poly->contour1.points, poly->contour1.num_points); poly->contour1.controls = NULL; return False; } /* ********************************************************************************** * * Coords -- * Return or edit the item vertices. * ********************************************************************************** */ static int Coords(Item item, int contour, int index, int cmd, ZnPoint **pts, char **controls, int *num_pts) { TrianglesItem tr = (TrianglesItem) item; int num_points, i; ZnPoint *points; if ((cmd == COORDS_REPLACE) || (cmd == COORDS_REPLACE_ALL)) { if (*num_pts == 0) { Tcl_AppendResult(item->wi->interp, " coords command need at least 1 point on beziers", NULL); return ZN_ERROR; } if (cmd == COORDS_REPLACE_ALL) { ZnList tmp; replace_all: tmp = ZnListFromArray(*pts, *num_pts, sizeof(ZnPoint)); if (!tr->points) { ZnListEmpty(tr->points); } else { tr->points = ZnListNew(*num_pts, sizeof(ZnPoint)); } ZnListAppend(tr->points, tmp); ZnListFree(tmp); } else { if (!tr->points) { edit_err: Tcl_AppendResult(item->wi->interp, " coords command cannot edit empty beziers", NULL); return ZN_ERROR; } points = ZnListArray(tr->points); num_points = ZnListSize(tr->points); if (index < 0) { index += num_points; } if ((index < 0) || (index >= num_points)) { range_err: Tcl_AppendResult(item->wi->interp, " coord index out of range", NULL); return ZN_ERROR; } points[index] = (*pts)[0]; } ITEM.Invalidate(item, ZN_COORDS_FLAG); } else if ((cmd == COORDS_READ) || (cmd == COORDS_READ_ALL)) { if (!tr->points) { *num_pts = 0; *pts = NULL; return ZN_OK; } points = ZnListArray(tr->points); num_points = ZnListSize(tr->points); if (cmd == COORDS_READ_ALL) { *num_pts = num_points; *pts = points; } else { if (index < 0) { index += num_points; } if ((index < 0) || (index >= num_points)) { goto range_err; } *num_pts = 1; *pts = &points[index]; } } else if ((cmd == COORDS_ADD) || (cmd == COORDS_ADD_LAST)) { if (!tr->points) { goto replace_all; } else if (cmd == COORDS_ADD) { num_points = ZnListSize(tr->points); if (index < 0) { index += num_points; } if ((index < 0) || (index >= num_points)) { goto range_err; } for (i = 0; i < *num_pts; i++, index++) { ZnListAdd(tr->points, &(*pts)[i], index); } } else { ZnList tmp; tmp = ZnListFromArray(*pts, *num_pts, sizeof(ZnPoint)); ZnListAppend(tr->points, tmp); ZnListFree(tmp); } ITEM.Invalidate(item, ZN_COORDS_FLAG); } else if (cmd == COORDS_REMOVE) { if (!tr->points) { goto edit_err; } points = ZnListArray(tr->points); num_points = ZnListSize(tr->points); if (index < 0) { index += num_points; } if ((index < 0) || (index >= num_points)) { goto range_err; } ZnListDelete(tr->points, index); ITEM.Invalidate(item, ZN_COORDS_FLAG); } return ZN_OK; } /* ********************************************************************************** * * PickVertex -- * Return in 'vertex' the vertex closest to p and in 'o_vertex' the * opposite vertex on the closest edge, if such an edge exists or -1 * in the other case. * ********************************************************************************** */ static void PickVertex(Item item, ZnPoint *p, int *contour, int *vertex, int *o_vertex) { TrianglesItem tr = (TrianglesItem) item; int i, k, num_points; ZnPoint *points; ZnReal dist=1.0e40, new_dist, dist2; *contour = *vertex = *o_vertex = -1; points = tr->dev_points.strips->points; num_points = tr->dev_points.strips->num_points; for (i = 0; i < num_points; i++) { new_dist = hypot(points[i].x - p->x, points[i].y - p->y); if (new_dist < dist) { dist = new_dist; *contour = 0; *vertex = i; } } /* * Update the opposite vertex. */ i = (*vertex+1) % num_points; new_dist = LineToPointDist(&points[*vertex], &points[i], p); k = ((unsigned)(*vertex-1)) % num_points; dist2 = LineToPointDist(&points[*vertex], &points[k], p); if (dist2 < new_dist) { *o_vertex = k; } else { *o_vertex = i; } } /* ********************************************************************************** * * Exported functions struct -- * ********************************************************************************** */ static ItemClassStruct TRIANGLES_ITEM_CLASS = { sizeof(TrianglesItemStruct), 0, /* num_parts */ False, /* has_anchors */ "triangles", tr_attrs, Init, Clone, Destroy, Configure, Query, NULL, NULL, GetClipVertices, GetContours, Coords, NULL, /* InsertChars */ NULL, /* DeleteChars */ NULL, /* Cursor */ NULL, /* Index */ NULL, /* Part */ NULL, /* Selection */ NULL, /* Contour */ ComputeCoordinates, ToArea, Draw, Render, IsSensitive, Pick, PickVertex, /* PickVertex */ PostScript }; ZnItemClassId ZnTriangles = (ZnItemClassId) &TRIANGLES_ITEM_CLASS;