/* * Copyright (c) 2005-2023 Julien Nadeau Carriere * * 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 class for any node in a SG(3) scene graph. */ #include #include #include #include static void OnAttach(AG_Event *_Nonnull event) { SG_Node *node = SG_NODE_SELF(); AG_Object *parent = AG_OBJECT_PTR(1); SG *sg = NULL; if (SG_ISA(parent)) { sg = (SG *)parent; } else if (SG_NODE_ISA(parent)) { sg = SGNODE(parent)->sg; } else { AG_FatalError("Invalid SG_Node parent"); } AG_ObjectLock(sg); TAILQ_INSERT_TAIL(&sg->nodes, node, nodes); node->sg = sg; AG_ObjectUnlock(sg); } static void OnDetach(AG_Event *_Nonnull event) { SG_Node *node = SG_NODE_SELF(); void *parent = AG_OBJECT_PTR(1); SG *sg = NULL; if (SG_ISA(parent)) { sg = (SG *)parent; } else if (SG_NODE_ISA(parent)) { sg = SGNODE(parent)->sg; } else { AG_FatalError("Invalid SG_Node parent"); } AG_ObjectLock(sg); TAILQ_REMOVE(&sg->nodes, node, nodes); node->sg = NULL; AG_ObjectUnlock(sg); } static void Init(void *_Nonnull obj) { SG_Node *node = obj; node->flags = 0; M_MatIdentity44v(&node->T); TAILQ_INIT(&node->actions); AG_SetEvent(node, "attached", OnAttach, NULL); AG_SetEvent(node, "detached", OnDetach, NULL); } static void Reset(void *_Nonnull obj) { SG_Node *node = obj; SG_Action *act, *actNext; for (act = TAILQ_FIRST(&node->actions); act != TAILQ_END(&node->actions); act = actNext) { actNext = TAILQ_NEXT(act, actions); Free(act); } TAILQ_INIT(&node->actions); } static int Load(void *_Nonnull obj, AG_DataSource *_Nonnull buf, const AG_Version *_Nonnull ver) { SG_Node *node = obj; node->flags = (Uint)AG_ReadUint32(buf); M_ReadMatrix44v(buf, &node->T); return (0); } static int Save(void *_Nonnull obj, AG_DataSource *_Nonnull buf) { SG_Node *node = obj; AG_WriteUint32(buf, (Uint32)node->flags); M_WriteMatrix44(buf, &node->T); return (0); } static void *_Nullable Edit(void *_Nonnull obj, SG_View *_Nullable sgv) { SG_Node *node = obj; AG_Box *box; box = AG_BoxNewVert(NULL, AG_BOX_HFILL); AG_LabelNew(box, 0, _("Node Name: %s"), AGOBJECT(node)->name); return (box); } /* Generic edit() operation for node objects. */ void *_Nonnull SG_NodeEdit(void *obj) { SG_Node *node = obj; AG_Window *win; void *wEdit; AG_ObjectClass **hier; int i, nHier; if ((win = AG_WindowNew(0)) == NULL) { AG_FatalError(NULL); } AG_WindowSetCaptionS(win, OBJECT(node)->name); if (AG_ObjectGetInheritHier(node, &hier, &nHier) != 0) { AG_FatalError(NULL); } for (i = 0; i < nHier; i++) { SG_NodeClass *nc = (SG_NodeClass *)hier[i]; if (!AG_ClassIsNamed(hier[i], "SG_Node:*") || nc->edit == NULL) continue; wEdit = nc->edit(node, NULL); if (wEdit != NULL && AG_WIDGET_ISA(wEdit)) AG_ObjectAttach(win, wEdit); } Free(hier); return (win); } /* * Compute the product of the transform matrices of the given node * and its parents. */ void SG_GetNodeTransform(void *obj, M_Matrix44 *T) { SG_Node *node = obj; #ifdef AG_THREADS SG *sg = node->sg; #endif SG_Node *chld = node; TAILQ_HEAD_(sg_node) rnodes = TAILQ_HEAD_INITIALIZER(rnodes); M_MatIdentity44v(T); AG_ObjectLock(sg); while (chld != NULL) { TAILQ_INSERT_TAIL(&rnodes, chld, rnodes); if (OBJECT(chld)->parent == NULL || !SG_NODE_ISA(OBJECT(chld)->parent)) { break; } chld = OBJECT(chld)->parent; } TAILQ_FOREACH(chld, &rnodes, rnodes) { M_MatMult44v(T, &chld->T); } AG_ObjectUnlock(sg); } /* * Compute the product of the inverse transform matrices of the given node * and its parents. */ void SG_GetNodeTransformInverse(void *obj, M_Matrix44 *T) { SG_Node *node = obj; SG_Node *chld = node; #ifdef AG_THREADS SG *sg = node->sg; #endif TAILQ_HEAD_(sg_node) rnodes = TAILQ_HEAD_INITIALIZER(rnodes); M_MatIdentity44v(T); AG_ObjectLock(sg); while (chld != NULL) { TAILQ_INSERT_TAIL(&rnodes, chld, rnodes); if (OBJECT(chld)->parent == NULL || !SG_NODE_ISA(OBJECT(chld)->parent)) { break; } chld = OBJECT(chld)->parent; } TAILQ_FOREACH(chld, &rnodes, rnodes) { M_Matrix44 Tinv; Tinv = M_MatInvert44(chld->T); M_MatMult44v(T, &Tinv); } AG_ObjectUnlock(sg); } /* * Graphically render a node and its children. * The SG must be locked. */ void SG_NodeDraw(SG *sg, SG_Node *node, SG_View *view) { AG_ObjectClass **hier; int i, nHier; M_Matrix44 Tsave, T; SG_Node *chld; GL_FetchMatrixv(GL_MODELVIEW_MATRIX, &Tsave); T = M_MatTranspose44p(&node->T); /* OpenGL is column-major */ GL_MultMatrixv(&T); AG_ObjectLock(node); /* Render this node. */ if (AG_ObjectGetInheritHier(node, &hier, &nHier) != 0) { AG_FatalError(NULL); } for (i = nHier-1; i >= 0; i--) { SG_NodeClass *nc = (SG_NodeClass *)hier[i]; if (!AG_ClassIsNamed(hier[i], "SG_Node:*") || nc->draw == NULL) { continue; } nc->draw(node, view); break; } /* Render child nodes. */ OBJECT_FOREACH_CLASS(chld, node, sg_node, "SG_Node:*") { SG_NodeDraw(sg, chld, view); } AG_ObjectUnlock(node); GL_LoadMatrixv(&Tsave); Free(hier); } /* Save node data (and child nodes) to a data source. */ int SG_NodeSave(SG *sg, AG_DataSource *ds, SG_Node *node) { off_t countOffs, skipSizeOffs; Uint32 count; SG_Node *chldNode; AG_ObjectLock(node); /* Save object metadata */ AG_WriteString(ds, OBJECT(node)->name); if (OBJECT_CLASS(node)->libs[0] != '\0') { char s[AG_OBJECT_TYPE_MAX]; Strlcpy(s, OBJECT_CLASS(node)->hier, sizeof(s)); Strlcat(s, "@", sizeof(s)); Strlcat(s, OBJECT_CLASS(node)->libs, sizeof(s)); AG_WriteString(ds, s); } else { AG_WriteString(ds, OBJECT_CLASS(node)->hier); } skipSizeOffs = AG_Tell(ds); AG_WriteUint32(ds, 0); /* Save object dataset */ if (AG_ObjectSerialize(node, ds) == -1) goto fail; /* Save child nodes */ countOffs = AG_Tell(ds); AG_WriteUint32(ds, 0); count = 0; OBJECT_FOREACH_CHILD(chldNode, node, sg_node) { if (SG_NodeSave(sg, ds, chldNode) == -1) { goto fail; } count++; } AG_WriteUint32At(ds, count, countOffs); AG_WriteUint32At(ds, AG_Tell(ds)-skipSizeOffs, skipSizeOffs); AG_ObjectUnlock(node); return (0); fail: AG_ObjectUnlock(node); return (-1); } /* Load node data (and children) from data source. */ int SG_NodeLoad(SG *sg, AG_DataSource *ds, SG_Node *parentNode) { Uint count, i; char nodeName[AG_OBJECT_NAME_MAX]; char classSpec[AG_OBJECT_TYPE_MAX]; AG_ObjectClass *nodeClass; /* Uint32 skipSize; */ SG_Node *node; /* Load object metadata */ AG_CopyString(nodeName, ds, sizeof(nodeName)); AG_CopyString(classSpec, ds, sizeof(classSpec)); /* skipSize = AG_ReadUint32(ds); */ (void)AG_ReadUint32(ds); /* Allocate node instance; load DSOs as needed. */ if ((nodeClass = AG_LoadClass(classSpec)) == NULL) { return (-1); } if ((node = AG_TryMalloc(nodeClass->size)) == NULL) { return (-1); } AG_ObjectInit(node, nodeClass); AG_ObjectSetNameS(node, nodeName); /* Load object dataset */ if (AG_ObjectUnserialize(node, ds) == -1) goto fail; /* Attach node to parent. */ if (parentNode != NULL) { AG_ObjectAttach(parentNode, node); } else { AG_ObjectAttach(sg, node); sg->root = node; } /* Load child nodes */ count = (Uint)AG_ReadUint32(ds); for (i = 0; i < count; i++) { if (SG_NodeLoad(sg, ds, node) == -1) goto fail_detach; } return (0); fail_detach: if (node == sg->root) { sg->root = NULL; } fail: AG_ObjectDestroy(node); return (-1); } SG_NodeClass sgNodeClass = { { "SG_Node", sizeof(SG_Node), { 1,0, AGC_SG_NODE, 0xE026 }, Init, Reset, NULL, /* destroy */ Load, Save, SG_NodeEdit }, NULL, /* menuInstance */ NULL, /* menuClass */ NULL, /* draw */ NULL, /* intersect */ Edit };