/* * Copyright (c) 2004-2008 Hypertriton, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Base vector graphics object. */ #include #include #include #include "vg.h" #include "vg_view.h" #include "icons.h" #include "icons_data.h" #include const AG_Version vgVer = { 6, 1 }; VG_NodeOps **vgNodeClasses; Uint vgNodeClassCount; void VG_InitSubsystem(void) { vgNodeClasses = NULL; vgNodeClassCount = 0; AG_RegisterNamespace("VG", "VG_", "http://libagar.org/"); if (agGUI) { AG_RegisterClass(&vgViewClass); } VG_RegisterClass(&vgPointOps); VG_RegisterClass(&vgLineOps); VG_RegisterClass(&vgPolygonOps); VG_RegisterClass(&vgCircleOps); VG_RegisterClass(&vgArcOps); VG_RegisterClass(&vgTextOps); vgIcon_Init(); } void VG_DestroySubsystem(void) { Free(vgNodeClasses); vgNodeClasses = NULL; vgNodeClassCount = 0; AG_UnregisterClass(&vgViewClass); } VG * VG_New(Uint flags) { VG *vg; vg = Malloc(sizeof(VG)); VG_Init(vg, flags); return (vg); } void VG_Init(VG *vg, Uint flags) { VG_Point *ptRoot; vg->flags = flags; vg->colors = NULL; vg->nColors = 0; vg->fillColor = VG_GetColorRGB(0,0,0); vg->selectionColor = VG_GetColorRGBA(0,200,0,150); vg->mouseoverColor = VG_GetColorRGBA(250,250,0,100); vg->layers = NULL; vg->nLayers = 0; TAILQ_INIT(&vg->nodes); AG_MutexInitRecursive(&vg->lock); vg->T = Malloc(sizeof(VG_Matrix)); vg->nT = 1; vg->T[0] = VG_MatrixIdentity(); VG_PushLayer(vg, _("Layer 0")); ptRoot = VG_PointNew(NULL, VGVECTOR(0.0f,0.0f)); vg->root = VGNODE(ptRoot); vg->root->vg = vg; vg->root->handle = 1; VG_SetColorRGB(vg->root, 0, 150, 0); } /* Delete and free a node (including its children). */ void VG_NodeDestroy(void *p) { VG_Node *vn = p; VG_Node *vnChld, *vnNext; #ifdef AG_DEBUG if (vn->vg != NULL || vn->parent != NULL) AG_FatalError("VG_NodeDetach() must precede VG_NodeDestroy()"); #endif for (vnChld = TAILQ_FIRST(&vn->cNodes); vnChld != TAILQ_END(&vn->cNodes); vnChld = vnNext) { vnNext = TAILQ_NEXT(vnChld, tree); TAILQ_REMOVE(&vnChld->vg->nodes, vnChld, list); VG_NodeDestroy(vnChld); } TAILQ_INIT(&vn->cNodes); if (vn->ops->destroy != NULL) { vn->ops->destroy(vn); } Free(vn); } /* Reinitialize the drawing. */ void VG_Clear(VG *vg) { VG_ClearNodes(vg); VG_ClearColors(vg); } /* Reinitialize the tree of entities. */ void VG_ClearNodes(VG *vg) { VG_Node *vnChld, *vnNext; if (vg->root == NULL) { return; } for (vnChld = TAILQ_FIRST(&vg->root->cNodes); vnChld != TAILQ_END(&vg->root->cNodes); vnChld = vnNext) { vnNext = TAILQ_NEXT(vnChld, tree); VG_NodeDetach(vnChld); VG_NodeDestroy(vnChld); } TAILQ_INIT(&vg->root->cNodes); TAILQ_INIT(&vg->nodes); } /* Reinitialize the color array. */ void VG_ClearColors(VG *vg) { if (vg->colors != NULL) { Free(vg->colors); vg->colors = NULL; } vg->nColors = 0; } void VG_Destroy(VG *vg) { VG_Clear(vg); Free(vg->layers); AG_MutexDestroy(&vg->lock); } void VG_RegisterClass(VG_NodeOps *vnOps) { vgNodeClasses = Realloc(vgNodeClasses, (vgNodeClassCount+1)*sizeof(VG_NodeOps *)); vgNodeClasses[vgNodeClassCount++] = vnOps; } void VG_UnregisterClass(VG_NodeOps *vnOps) { int i; for (i = 0; i < vgNodeClassCount; i++) { if (vgNodeClasses[i] == vnOps) break; } if (i == vgNodeClassCount) { return; } if (i < vgNodeClassCount-1) { memmove(&vgNodeClasses[i], &vgNodeClasses[i+1], (vgNodeClassCount-1)*sizeof(VG_NodeOps *)); } vgNodeClassCount--; } /* Lookup a node class by name. */ VG_NodeOps * VG_LookupClass(const char *name) { Uint i; for (i = 0; i < vgNodeClassCount; i++) { VG_NodeOps *vnOps = vgNodeClasses[i]; if (strcmp(vnOps->name, name) == 0) return (vnOps); } AG_SetError("Invalid node type: %s", name); return (NULL); } /* Detach and free the specified node and its children. */ int VG_Delete(void *pVn) { VG_Node *vn = pVn; VG *vg = vn->vg; #ifdef AG_DEBUG if (vg == NULL) AG_FatalError("VG_Delete() on unattached node %s%d", vn->ops->name, vn->handle); #endif VG_Lock(vg); if (vn->nDeps > 0) { AG_SetError("%s%u is in use", vn->ops->name, vn->handle); goto fail; } if (vn->ops->deleteNode != NULL) { vn->ops->deleteNode(vn); } VG_NodeDetach(vn); VG_NodeDestroy(vn); VG_Unlock(vg); return (0); fail: VG_Unlock(vg); return (-1); } static void MoveNodesRecursively(VG *vgDst, VG_Node *vn) { VG_Node *vnChld; VG_FOREACH_CHLD(vnChld, vn, vg_node) { MoveNodesRecursively(vgDst, vnChld); } vn->handle = VG_GenNodeName(vgDst, vn->ops->name); TAILQ_REMOVE(&vn->vg->nodes, vn, list); vn->vg = vgDst; TAILQ_INSERT_TAIL(&vgDst->nodes, vn, list); } /* * Move the contents of a source VG (to be discarded) to the specified * destination VG, under a given node. */ void VG_Merge(void *pVnDst, VG *vgSrc) { VG_Node *vnDst = pVnDst; VG_Node *vn = vgSrc->root; vn->vg = vnDst->vg; vn->parent = vnDst; TAILQ_INSERT_TAIL(&vnDst->vg->nodes, vn, list); TAILQ_INSERT_TAIL(&vnDst->cNodes, vn, tree); MoveNodesRecursively(vnDst->vg, vn); vgSrc->root = NULL; } /* Create a node reference to another node. */ void VG_AddRef(void *p, void *pRef) { VG_Node *vn = p; if (vn->vg != NULL) { VG_Lock(vn->vg); } vn->refs = Realloc(vn->refs, (vn->nRefs+1)*sizeof(VG_Node *)); vn->refs[vn->nRefs++] = VGNODE(pRef); VGNODE(pRef)->nDeps++; if (vn->vg != NULL) { VG_Unlock(vn->vg); } } /* Remove a node reference to another node. */ Uint VG_DelRef(void *pVn, void *pRef) { VG_Node *vn = pVn; Uint newDeps; int i; if (vn->vg != NULL) { VG_Lock(vn->vg); } for (i = 0; i < vn->nRefs; i++) { if (vn->refs[i] == VGNODE(pRef)) break; } if (i == vn->nRefs) { AG_FatalError("No such reference"); } if (i < vn->nRefs-1) { memmove(&vn->refs[i], &vn->refs[i+1], (vn->nRefs-1)*sizeof(VG_Node *)); } vn->nRefs--; newDeps = (--VGNODE(pRef)->nDeps); if (vn->vg != NULL) { VG_Unlock(vn->vg); } return (newDeps); } void VG_SetBackgroundColor(VG *vg, VG_Color c) { vg->fillColor = c; } void VG_SetSelectionColor(VG *vg, VG_Color c) { vg->selectionColor = c; } void VG_SetMouseOverColor(VG *vg, VG_Color c) { vg->mouseoverColor = c; } void VG_NodeInit(void *p, VG_NodeOps *vnOps) { VG_Node *vn = p; vn->ops = vnOps; vn->handle = 0; vn->sym[0] = '\0'; vn->flags = 0; vn->layer = 0; vn->color = VG_GetColorRGB(250,250,250); vn->parent = NULL; vn->vg = NULL; vn->refs = NULL; vn->nRefs = 0; vn->nDeps = 0; vn->T = VG_MatrixIdentity(); vn->p = NULL; TAILQ_INIT(&vn->cNodes); if (vn->ops->init != NULL) vn->ops->init(vn); } /* Generate a unique name for a node of the specified type. */ Uint32 VG_GenNodeName(VG *vg, const char *type) { Uint32 name = 1; while (VG_FindNode(vg, name, type) != NULL) { if (++name >= VG_HANDLE_MAX) AG_FatalError("Out of node names"); } return (name); } /* * Attach the specified node to a new parent. If the node has no * name assigned (handle=0), one is generated. */ void VG_NodeAttach(void *pParent, void *pNode) { VG_Node *vnParent = pParent; VG_Node *vn = pNode; VG *vg; if (vnParent == NULL) { vn->parent = NULL; vn->vg = NULL; return; } vg = vnParent->vg; VG_Lock(vg); if (vn->handle == 0) { vn->handle = VG_GenNodeName(vg, vn->ops->name); } vn->parent = vnParent; TAILQ_INSERT_TAIL(&vnParent->cNodes, vn, tree); TAILQ_INSERT_TAIL(&vg->nodes, vn, list); vn->vg = vg; VG_Unlock(vg); } /* * Detach the specified node from its current parent. The node itself * and all of its children are also detached from the VG. No reference * checking is done. */ void VG_NodeDetach(void *p) { VG_Node *vn = p; VG *vg = vn->vg; VG_Node *vnChld, *vnNext; #ifdef AG_DEBUG if (vg == NULL) AG_FatalError("VG_NodeDetach() on unattached node"); #endif VG_Lock(vg); for (vnChld = TAILQ_FIRST(&vn->cNodes); vnChld != TAILQ_END(&vn->cNodes); vnChld = vnNext) { vnNext = TAILQ_NEXT(vnChld, tree); VG_NodeDetach(vnChld); } TAILQ_INIT(&vn->cNodes); if (vn->parent != NULL) { TAILQ_REMOVE(&vn->parent->cNodes, vn, tree); vn->parent = NULL; } TAILQ_REMOVE(&vg->nodes, vn, list); vn->vg = NULL; VG_Unlock(vg); } /* Set the symbolic name of a node. */ void VG_SetSym(void *pNode, const char *fmt, ...) { VG_Node *vn = pNode; va_list args; if (vn->vg != NULL) { VG_Lock(vn->vg); } va_start(args, fmt); Vsnprintf(vn->sym, sizeof(vn->sym), fmt, args); va_end(args); if (vn->vg != NULL) { VG_Unlock(vn->vg); } } void VG_SetLayer(void *pNode, int layer) { VGNODE(pNode)->layer = layer; } void VG_SetColorv(void *pNode, const VG_Color *c) { VG_Node *vn = pNode; vn->color.r = c->r; vn->color.g = c->g; vn->color.b = c->b; vn->color.a = c->a; } void VG_SetColorRGB(void *pNode, Uint8 r, Uint8 g, Uint8 b) { VG_Node *vn = pNode; vn->color.r = r; vn->color.g = g; vn->color.b = b; } void VG_SetColorRGBA(void *pNode, Uint8 r, Uint8 g, Uint8 b, Uint8 a) { VG_Node *vn = pNode; vn->color.r = r; vn->color.g = g; vn->color.b = b; vn->color.a = a; } /* Push a new layer onto the layer stack. */ VG_Layer * VG_PushLayer(VG *vg, const char *name) { VG_Layer *vgl; VG_Lock(vg); vg->layers = Realloc(vg->layers, (vg->nLayers+1) * sizeof(VG_Layer)); vgl = &vg->layers[vg->nLayers]; vg->nLayers++; Strlcpy(vgl->name, name, sizeof(vgl->name)); vgl->visible = 1; vgl->alpha = 255; vgl->color = VG_GetColorRGB(255,255,255); VG_Unlock(vg); return (vgl); } /* Pop the highest layer off the layer stack. */ void VG_PopLayer(VG *vg) { VG_Lock(vg); if (--vg->nLayers < 1) { vg->nLayers = 1; } VG_Unlock(vg); } static void SaveMatrix(VG_Matrix *A, AG_DataSource *ds) { int m, n; for (m = 0; m < 3; m++) for (n = 0; n < 3; n++) AG_WriteFloat(ds, A->m[m][n]); } static void LoadMatrix(VG_Matrix *A, AG_DataSource *ds) { int m, n; for (m = 0; m < 3; m++) for (n = 0; n < 3; n++) A->m[m][n] = AG_ReadFloat(ds); } static void SaveNodeGeneric(VG *vg, VG_Node *vn, AG_DataSource *ds) { off_t nNodesOffs; Uint32 nNodes = 0; VG_Node *vnChld; if (vn->flags & VG_NODE_NOSAVE) return; AG_WriteString(ds, vn->ops->name); AG_WriteString(ds, vn->sym); AG_WriteUint32(ds, (Uint32)vn->handle); AG_WriteUint32(ds, (Uint32)(vn->flags & VG_NODE_SAVED_FLAGS)); AG_WriteUint32(ds, (Uint32)vn->layer); VG_WriteColor(ds, &vn->color); SaveMatrix(&vn->T, ds); /* Save the entities. */ nNodesOffs = AG_Tell(ds); AG_WriteUint32(ds, 0); VG_FOREACH_CHLD(vnChld, vn, vg_node) { SaveNodeGeneric(vg, vnChld, ds); nNodes++; } AG_WriteUint32At(ds, nNodes, nNodesOffs); } static int SaveNodeData(VG *vg, VG_Node *vn, AG_DataSource *ds) { VG_Node *vnChld; VG_FOREACH_CHLD(vnChld, vn, vg_node) { if (SaveNodeData(vg, vnChld, ds) == -1) return (-1); } if (vn->ops->save != NULL) { vn->ops->save(vn, ds); } return (0); } void VG_Save(VG *vg, AG_DataSource *ds) { off_t nNodesOffs; Uint32 nNodes = 0; VG_Node *vn; Uint i; AG_WriteVersion(ds, "Agar-VG", &vgVer); AG_WriteString(ds, "VG"); /* name */ VG_Lock(vg); AG_WriteUint32(ds, (Uint32)vg->flags); VG_WriteColor(ds, &vg->fillColor); VG_WriteColor(ds, &vg->fillColor); /* gridColor */ VG_WriteColor(ds, &vg->selectionColor); VG_WriteColor(ds, &vg->mouseoverColor); AG_WriteFloat(ds, 0.0f); /* gridIval */ /* Save the layer information. */ AG_WriteUint32(ds, (Uint32)vg->nLayers); for (i = 0; i < vg->nLayers; i++) { VG_Layer *layer = &vg->layers[i]; AG_WriteString(ds, layer->name); AG_WriteUint8(ds, (Uint8)layer->visible); VG_WriteColor(ds, &layer->color); AG_WriteUint8(ds, layer->alpha); } /* Save the color table. */ AG_WriteUint32(ds, (Uint32)vg->nColors); for (i = 0; i < vg->nColors; i++) { VG_IndexedColor *vic = &vg->colors[i]; AG_WriteString(ds, vic->name); VG_WriteColor(ds, &vic->color); } /* Save the entities. */ nNodesOffs = AG_Tell(ds); AG_WriteUint32(ds, 0); VG_FOREACH_CHLD(vn, vg->root, vg_node) { SaveNodeGeneric(vg, vn, ds); nNodes++; } AG_WriteUint32At(ds, nNodes, nNodesOffs); SaveNodeData(vg, vg->root, ds); VG_Unlock(vg); } static int LoadNodeGeneric(VG *vg, VG_Node *vnParent, AG_DataSource *ds) { char type[VG_TYPE_NAME_MAX]; VG_Node *vn; VG_NodeOps *vnOps; Uint32 i, nNodes; AG_CopyString(type, ds, sizeof(type)); if ((vnOps = VG_LookupClass(type)) == NULL) { return (-1); } vn = Malloc(vnOps->size); VG_NodeInit(vn, vnOps); AG_CopyString(vn->sym, ds, sizeof(vn->sym)); vn->handle = AG_ReadUint32(ds); vn->flags = AG_ReadUint32(ds); vn->layer = (int)AG_ReadUint32(ds); vn->color = VG_ReadColor(ds); LoadMatrix(&vn->T, ds); VG_NodeAttach(vnParent, vn); nNodes = AG_ReadUint32(ds); for (i = 0; i < nNodes; i++) { if (LoadNodeGeneric(vg, vn, ds) == -1) return (-1); } return (0); } static int LoadNodeData(VG *vg, VG_Node *vn, AG_DataSource *ds, const AG_Version *dsVer) { VG_Node *vnChld; VG_FOREACH_CHLD(vnChld, vn, vg_node) { if (LoadNodeData(vg, vnChld, ds, dsVer) == -1) return (-1); } if (vn->ops->load != NULL && vn->ops->load(vn, ds, dsVer) == -1) { AG_SetError("%s%u: %s", vn->ops->name, vn->handle, AG_GetError()); return (-1); } return (0); } int VG_Load(VG *vg, AG_DataSource *ds) { char name[VG_NAME_MAX]; AG_Version dsVer; Uint32 i, nColors, nNodes; if (AG_ReadVersion(ds, "Agar-VG", &vgVer, &dsVer) != 0) { return (-1); } AG_CopyString(name, ds, sizeof(name)); /* Ignore */ VG_Lock(vg); vg->flags = AG_ReadUint32(ds); vg->fillColor = VG_ReadColor(ds); (void)VG_ReadColor(ds); /* gridColor */ vg->selectionColor = VG_ReadColor(ds); vg->mouseoverColor = VG_ReadColor(ds); (void)AG_ReadFloat(ds); /* gridIval */ /* Read the layer information. */ vg->nLayers = (Uint)AG_ReadUint32(ds); vg->layers = Realloc(vg->layers, vg->nLayers*sizeof(VG_Layer)); for (i = 0; i < vg->nLayers; i++) { VG_Layer *layer = &vg->layers[i]; AG_CopyString(layer->name, ds, sizeof(layer->name)); layer->visible = (int)AG_ReadUint8(ds); layer->color = VG_ReadColor(ds); layer->alpha = AG_ReadUint8(ds); } /* Read the color table. */ nColors = AG_ReadUint32(ds); vg->colors = Malloc(nColors*sizeof(VG_IndexedColor *)); vg->nColors = nColors; for (i = 0; i < nColors; i++) { VG_IndexedColor *vic = &vg->colors[i]; AG_CopyString(vic->name, ds, sizeof(vic->name)); vic->color = VG_ReadColor(ds); } /* Read the entities. */ VG_ClearNodes(vg); nNodes = AG_ReadUint32(ds); for (i = 0; i < nNodes; i++) { if (LoadNodeGeneric(vg, vg->root, ds) == -1) goto fail; } if (LoadNodeData(vg, vg->root, ds, &dsVer) == -1) { goto fail; } VG_Unlock(vg); return (0); fail: VG_Unlock(vg); return (-1); } void VG_WriteVector(AG_DataSource *ds, const VG_Vector *vtx) { AG_WriteFloat(ds, vtx->x); AG_WriteFloat(ds, vtx->y); } void VG_WriteColor(AG_DataSource *ds, const VG_Color *c) { AG_WriteUint8(ds, c->r); AG_WriteUint8(ds, c->g); AG_WriteUint8(ds, c->b); AG_WriteUint8(ds, c->a); } VG_Vector VG_ReadVector(AG_DataSource *ds) { VG_Vector v; v.x = AG_ReadFloat(ds); v.y = AG_ReadFloat(ds); return (v); } VG_Color VG_ReadColor(AG_DataSource *ds) { VG_Color c; c.r = AG_ReadUint8(ds); c.g = AG_ReadUint8(ds); c.b = AG_ReadUint8(ds); c.a = AG_ReadUint8(ds); c.idx = -1; return (c); } /* Serialize a node->node reference. */ void VG_WriteRef(AG_DataSource *ds, void *p) { VG_Node *vn = p; AG_WriteString(ds, vn->ops->name); AG_WriteUint32(ds, vn->handle); } /* Deserialize a node->node reference. */ void * VG_ReadRef(AG_DataSource *ds, void *pNode, const char *expType) { VG_Node *vn = pNode; char rType[VG_TYPE_NAME_MAX]; Uint32 handle; void *vnFound; AG_CopyString(rType, ds, sizeof(rType)); handle = AG_ReadUint32(ds); if (expType != NULL) { if (strcmp(rType, expType) != 0) { AG_SetError("Unexpected reference type: %s " "(expecting %s)", rType, expType); return (NULL); } } if ((vnFound = VG_FindNode(vn->vg, handle, rType)) == NULL) { AG_SetError("Reference to unexisting item: %s%u", rType, (Uint)handle); return (NULL); } VG_AddRef(vn, vnFound); return (vnFound); } /* Return the element closest to the given point. */ void * VG_PointProximity(VG_View *vv, const char *type, const VG_Vector *vPt, VG_Vector *vC, void *ignoreNode) { VG *vg = vv->vg; VG_Node *vn, *vnClosest = NULL; float distClosest = AG_FLT_MAX, p; VG_Vector v, vClosest = VGVECTOR(AG_FLT_MAX,AG_FLT_MAX); VG_FOREACH_NODE(vn, vg, vg_node) { if (vn == ignoreNode || vn->ops->pointProximity == NULL) { continue; } if (type != NULL && strcmp(vn->ops->name, type) != 0) { continue; } v = *vPt; p = vn->ops->pointProximity(vn, vv, &v); if (p < distClosest) { distClosest = p; vnClosest = vn; vClosest = v; } } if (vC != NULL) { *vC = vClosest; } return (vnClosest); } /* * Return the element closest to the given point, ignoring all elements * beyond a specified distance. */ void * VG_PointProximityMax(VG_View *vv, const char *type, const VG_Vector *vPt, VG_Vector *vC, void *ignoreNode, float distMax) { VG *vg = vv->vg; VG_Node *vn, *vnClosest = NULL; float distClosest = AG_FLT_MAX, p; VG_Vector v, vClosest = VGVECTOR(AG_FLT_MAX,AG_FLT_MAX); VG_FOREACH_NODE(vn, vg, vg_node) { if (vn == ignoreNode || vn->ops->pointProximity == NULL) { continue; } if (type != NULL && strcmp(vn->ops->name, type) != 0) { continue; } v = *vPt; p = vn->ops->pointProximity(vn, vv, &v); if (p < distMax && p < distClosest) { distClosest = p; vnClosest = vn; vClosest = v; } } if (vC != NULL) { *vC = vClosest; } return (vnClosest); } /* * Compute the product of the transform matrices of the given node and its * parents in order. T is initialized to identity. */ void VG_NodeTransform(void *p, VG_Matrix *T) { VG_Node *node = p; VG_Node *cNode = node; TAILQ_HEAD(,vg_node) rNodes = TAILQ_HEAD_INITIALIZER(rNodes); while (cNode != NULL) { TAILQ_INSERT_HEAD(&rNodes, cNode, reverse); if (cNode->parent == NULL) { break; } cNode = cNode->parent; } *T = VG_MatrixIdentity(); TAILQ_FOREACH(cNode, &rNodes, reverse) VG_MultMatrix(T, &cNode->T); } /* Compute the inverse of a VG transformation matrix. */ VG_Matrix VG_MatrixInvert(VG_Matrix A) { VG_Matrix B; float det, detInv; int i, j; B.m[0][0] = A.m[1][1]*A.m[2][2] - A.m[1][2]*A.m[2][1]; B.m[0][1] = A.m[0][2]*A.m[2][1] - A.m[0][1]*A.m[2][2]; B.m[0][2] = A.m[0][1]*A.m[1][2] - A.m[0][2]*A.m[1][1]; B.m[1][0] = A.m[1][2]*A.m[2][0] - A.m[1][0]*A.m[2][2]; B.m[1][1] = A.m[0][0]*A.m[2][2] - A.m[0][2]*A.m[2][0]; B.m[1][2] = A.m[0][2]*A.m[1][0] - A.m[0][0]*A.m[1][2]; B.m[2][0] = A.m[1][0]*A.m[2][1] - A.m[1][1]*A.m[2][0]; B.m[2][1] = A.m[0][1]*A.m[2][0] - A.m[0][0]*A.m[2][1]; B.m[2][2] = A.m[0][0]*A.m[1][1] - A.m[0][1]*A.m[1][0]; det = A.m[0][0]*B.m[0][0] + A.m[0][1]*B.m[1][0] + A.m[0][2]*B.m[2][0]; if (Fabs(det) <= 1e-6f) AG_FatalError("Singular matrix"); detInv = 1.0/det; for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) B.m[i][j] *= detInv; } return (B); }