sl@0: /* sl@0: * Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies). sl@0: * All rights reserved. sl@0: * This component and the accompanying materials are made available sl@0: * under the terms of the License "Eclipse Public License v1.0" sl@0: * which accompanies this distribution, and is available sl@0: * at the URL "http://www.eclipse.org/legal/epl-v10.html". sl@0: * sl@0: * Initial Contributors: sl@0: * Nokia Corporation - initial contribution. sl@0: * sl@0: * Contributors: sl@0: * sl@0: * Description: Group implementation sl@0: * sl@0: */ sl@0: sl@0: sl@0: /*! sl@0: * \internal sl@0: * \file sl@0: * \brief Group implementation sl@0: */ sl@0: sl@0: #ifndef M3G_CORE_INCLUDE sl@0: # error included by m3g_core.c; do not compile separately. sl@0: #endif sl@0: sl@0: #include "m3g_group.h" sl@0: #include "m3g_memory.h" sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Internal functions sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Links a new child into the child list of this node. sl@0: * sl@0: * This assumes that all error checking has been done prior to calling sl@0: * the function, and the operation is a valid one. sl@0: * sl@0: * \param child Node object sl@0: * \param group Group object sl@0: */ sl@0: static void m3gLinkChild(Node *child, Group *group) sl@0: { sl@0: M3G_VALIDATE_OBJECT(child); sl@0: M3G_VALIDATE_OBJECT(group); sl@0: sl@0: if (group->firstChild == NULL) { sl@0: group->firstChild = child; sl@0: child->left = child; sl@0: child->right = child; sl@0: } sl@0: else { sl@0: Node *linkChild = group->firstChild; sl@0: sl@0: child->left = linkChild->left; sl@0: linkChild->left->right = child; sl@0: sl@0: child->right = linkChild; sl@0: linkChild->left = child; sl@0: } sl@0: m3gSetParent(child, (Node *) group); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Removes a child from the child list of this node. sl@0: * sl@0: * This assumes that all error checking has been done prior to calling sl@0: * the function, and the operation is a valid one. sl@0: * sl@0: * \param child Node object sl@0: * \param group Group object sl@0: */ sl@0: static void m3gDetachChild(Node *child, Group *group) sl@0: { sl@0: Node *n; sl@0: M3G_VALIDATE_OBJECT(child); sl@0: M3G_VALIDATE_OBJECT(group); sl@0: sl@0: n = group->firstChild; sl@0: sl@0: do { sl@0: if (n == child) { sl@0: M3G_VALIDATE_OBJECT(child->right); sl@0: M3G_VALIDATE_OBJECT(child->left); sl@0: sl@0: n->right->left = n->left; sl@0: n->left->right = n->right; sl@0: sl@0: if (group->firstChild == n) { sl@0: group->firstChild = (n->right != n) ? n->right : NULL; sl@0: } sl@0: sl@0: n->left = NULL; sl@0: n->right = NULL; sl@0: m3gSetParent(n, NULL); sl@0: return; sl@0: } sl@0: n = n->right; sl@0: } while (n != group->firstChild); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Destroys this Group object. sl@0: * sl@0: * \param obj Group object sl@0: */ sl@0: static void m3gDestroyGroup(Object *obj) sl@0: { sl@0: /* Release child references so they can be deleted */ sl@0: sl@0: Group *group = (Group *) obj; sl@0: while (group->firstChild != NULL) { sl@0: m3gDetachChild(group->firstChild, group); sl@0: } sl@0: # if defined(M3G_ENABLE_VF_CULLING) sl@0: if (group->bbox) { sl@0: m3gFree(M3G_INTERFACE(group), group->bbox); sl@0: m3gIncStat(M3G_INTERFACE(group), M3G_STAT_BOUNDING_BOXES, -1); sl@0: } sl@0: # endif sl@0: m3gDestroyNode(obj); sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Node method. sl@0: * sl@0: * \param self Group object sl@0: * \param refNode alignment reference Node object sl@0: * sl@0: * \retval M3G_TRUE continue align sl@0: * \retval M3G_FALSE abort align sl@0: */ sl@0: static M3Gbool m3gGroupAlign(Node *self, const Node *refNode) sl@0: { sl@0: Group *group = (Group *)self; sl@0: Node *child = group->firstChild; sl@0: sl@0: if (!m3gNodeAlign(self, refNode)) { sl@0: return M3G_FALSE; sl@0: } sl@0: sl@0: if (child) { sl@0: do { sl@0: if (!M3G_VFUNC(Node, child, align)(child, refNode)) { sl@0: return M3G_FALSE; sl@0: } sl@0: child = child->right; sl@0: } while (child != group->firstChild); sl@0: } sl@0: sl@0: return M3G_TRUE; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Node method. sl@0: * sl@0: * Setup group rendering by calling child sl@0: * nodes' render setup. sl@0: * sl@0: * \param self Group object sl@0: * \param toCamera transform to camera sl@0: * \param alphaFactor total alpha factor sl@0: * \param caller caller node sl@0: * \param renderQueue RenderQueue sl@0: * sl@0: * \retval M3G_TRUE continue render setup sl@0: * \retval M3G_FALSE abort render setup sl@0: */ sl@0: static M3Gbool m3gGroupSetupRender(Node *self, sl@0: const Node *caller, sl@0: SetupRenderState *s, sl@0: RenderQueue *renderQueue) sl@0: { sl@0: Group *group = (Group *)self; sl@0: M3Gbool enabled, success = M3G_TRUE; sl@0: sl@0: /* Check whether we're going up or down, and optimize the sl@0: * rendering-enabled and visibility checking based on that */ sl@0: sl@0: enabled = (self->enableBits & NODE_RENDER_BIT) != 0; sl@0: if (caller != self->parent) { sl@0: enabled = m3gHasEnabledPath(self, renderQueue->root); sl@0: s->cullMask = CULLMASK_ALL; sl@0: } sl@0: M3G_ASSERT(!self->dirtyBits || !enabled); sl@0: sl@0: /* First do the child nodes, unless disabled (inheritable, so sl@0: * children would be, too) */ sl@0: sl@0: if (enabled && (group->numNonCullables > 0 || group->numRenderables > 0)) { sl@0: sl@0: Node *child = group->firstChild; sl@0: if (child) { sl@0: sl@0: /* Check the bounding box if we have one */ sl@0: sl@0: # if defined(M3G_ENABLE_VF_CULLING) sl@0: if (group->bbox) { sl@0: m3gValidateAABB(group->bbox); sl@0: m3gUpdateCullingMask(s, renderQueue->camera, group->bbox); sl@0: } sl@0: # endif sl@0: sl@0: /* If we're not culled, or if we carry lights, we really sl@0: * need to recurse into each child node */ sl@0: sl@0: if (s->cullMask || group->numNonCullables > 0) { sl@0: do { sl@0: if (child != caller) { sl@0: SetupRenderState cs; sl@0: cs.cullMask = s->cullMask; sl@0: sl@0: M3G_BEGIN_PROFILE(M3G_INTERFACE(group), sl@0: M3G_PROFILE_SETUP_TRANSFORMS); sl@0: m3gGetCompositeNodeTransform(child, &cs.toCamera); sl@0: m3gPreMultiplyMatrix(&cs.toCamera, &s->toCamera); sl@0: M3G_END_PROFILE(M3G_INTERFACE(group), sl@0: M3G_PROFILE_SETUP_TRANSFORMS); sl@0: sl@0: if (!M3G_VFUNC(Node, child, setupRender)( sl@0: child, self, &cs, renderQueue)) { sl@0: return M3G_FALSE; sl@0: } sl@0: } sl@0: child = child->right; sl@0: } while (child != group->firstChild); sl@0: } sl@0: else { sl@0: M3GInterface m3g = M3G_INTERFACE(group); sl@0: M3Gint n = group->numRenderables; sl@0: m3gIncStat(m3g, M3G_STAT_RENDER_NODES, n); sl@0: m3gIncStat(m3g, M3G_STAT_RENDER_NODES_CULLED, n); sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* Then do the parent node if we're going up the tree. Again, we sl@0: * can discard the old traversal state at this point. */ sl@0: sl@0: if (self != renderQueue->root) { sl@0: Node *parent = self->parent; sl@0: sl@0: if (parent != caller && parent != NULL) { sl@0: Matrix t; sl@0: sl@0: M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS); sl@0: if (!m3gGetInverseNodeTransform(self, &t)) { sl@0: return M3G_FALSE; sl@0: } sl@0: m3gMulMatrix(&s->toCamera, &t); sl@0: M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS); sl@0: sl@0: success = M3G_VFUNC(Node, parent, setupRender)(parent, sl@0: self, sl@0: s, sl@0: renderQueue); sl@0: } sl@0: } sl@0: sl@0: return success; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param self Group object sl@0: * \param time current world time sl@0: * \return minimum validity sl@0: */ sl@0: static M3Gint m3gGroupApplyAnimation(Object *self, M3Gint time) sl@0: { sl@0: M3Gint validity, minValidity; sl@0: Node *child; sl@0: Group *group = (Group *)self; sl@0: M3G_VALIDATE_OBJECT(group); sl@0: sl@0: minValidity = m3gObjectApplyAnimation(self, time); sl@0: sl@0: child = group->firstChild; sl@0: if (child && minValidity > 0) { sl@0: do { sl@0: validity = M3G_VFUNC(Object, child, applyAnimation)( sl@0: (Object *)child, time); sl@0: minValidity = validity < minValidity ? validity : minValidity; sl@0: child = child->right; sl@0: } while (minValidity > 0 && child != group->firstChild); sl@0: } sl@0: return minValidity; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Node method. sl@0: * sl@0: * Call child nodes' ray intersect. sl@0: * sl@0: * \param self Group object sl@0: * \param mask pick scope mask sl@0: * \param ray pick ray sl@0: * \param ri RayIntersection object sl@0: * \param toGroup transform to originating group sl@0: * \retval M3G_TRUE continue pick sl@0: * \retval M3G_FALSE abort pick sl@0: */ sl@0: static M3Gbool m3gGroupRayIntersect(Node *self, sl@0: M3Gint mask, sl@0: M3Gfloat *ray, sl@0: RayIntersection *ri, sl@0: Matrix *toGroup) sl@0: { sl@0: Group *group = (Group *)self; sl@0: Node *child; sl@0: Matrix t, nt; sl@0: sl@0: m3gIdentityMatrix(&t); sl@0: m3gIdentityMatrix(&nt); sl@0: sl@0: child = group->firstChild; sl@0: if (child) { sl@0: do { sl@0: if (m3gHasPickablePath(child, ri->root)) { sl@0: m3gCopyMatrix(&t, toGroup); sl@0: m3gGetCompositeNodeTransform(child, &nt); sl@0: m3gRightMulMatrix(&t, &nt); sl@0: sl@0: if (!M3G_VFUNC(Node, child, rayIntersect)( sl@0: child, mask, ray, ri, &t)) { sl@0: return M3G_FALSE; sl@0: } sl@0: } sl@0: child = child->right; sl@0: } while (child != group->firstChild); sl@0: } sl@0: sl@0: return M3G_TRUE; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Initializes pick traversing. sl@0: * sl@0: * \param ri RayIntersection object sl@0: * \param root Root node for the traversing sl@0: * \param camera Camera object used in pick (2D pick only) sl@0: * \param x viewport x (2D pick only) sl@0: * \param y viewport y (2D pick only) sl@0: */ sl@0: static void m3gInitPick(RayIntersection *ri, Node *root, Camera *camera, M3Gfloat x, M3Gfloat y) sl@0: { sl@0: m3gZero(ri, sizeof(*ri)); sl@0: sl@0: ri->root = root; sl@0: ri->camera = camera; sl@0: ri->x = x; sl@0: ri->y = y; sl@0: ri->tMin = M3G_MAX_POSITIVE_FLOAT; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Fills Java side RayIntersection result. sl@0: * sl@0: * \param ri RayIntersection object sl@0: * \param ray Ray used in pick sl@0: * \param result Java side float array sl@0: */ sl@0: static void m3gFillPickResult(RayIntersection *ri, M3Gfloat *ray, M3Gfloat *result) sl@0: { sl@0: if (ri->intersected != NULL) { sl@0: Vec3 n; sl@0: sl@0: /* Fill in the values */ sl@0: result[0] = ri->distance; sl@0: result[1] = (M3Gfloat)ri->submeshIndex; sl@0: result[2] = ri->textureS[0]; sl@0: result[3] = ri->textureS[1]; sl@0: result[4] = ri->textureT[0]; sl@0: result[5] = ri->textureT[1]; sl@0: sl@0: /* Normalize normal */ sl@0: n.x = ri->normal[0]; sl@0: n.y = ri->normal[1]; sl@0: n.z = ri->normal[2]; sl@0: m3gNormalizeVec3(&n); sl@0: sl@0: result[6] = n.x; sl@0: result[7] = n.y; sl@0: result[8] = n.z; sl@0: sl@0: result[9] = ray[0]; sl@0: result[10] = ray[1]; sl@0: result[11] = ray[2]; sl@0: result[12] = m3gSub(ray[3], ray[0]); sl@0: result[13] = m3gSub(ray[4], ray[1]); sl@0: result[14] = m3gSub(ray[5], ray[2]); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param self Group object sl@0: * \param references array of reference objects sl@0: * \return number of references sl@0: */ sl@0: static M3Gint m3gGroupDoGetReferences(Object *self, Object **references) sl@0: { sl@0: Group *group = (Group *)self; sl@0: M3Gint num = m3gObjectDoGetReferences(self, references); sl@0: Node *child = group->firstChild; sl@0: if (child) { sl@0: do { sl@0: if (references != NULL) sl@0: references[num] = (Object *)child; sl@0: child = child->right; sl@0: num++; sl@0: } while (child != group->firstChild); sl@0: } sl@0: return num; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param self Group object sl@0: * \param references array of reference objects sl@0: * \return number of references sl@0: */ sl@0: static Object *m3gGroupFindID(Object *self, M3Gint userID) sl@0: { sl@0: Group *group = (Group *)self; sl@0: Object *found = m3gObjectFindID(self, userID); sl@0: sl@0: Node *child = group->firstChild; sl@0: if (child && !found) { sl@0: do { sl@0: found = m3gFindID((Object*) child, userID); sl@0: child = child->right; sl@0: } while (!found && child != group->firstChild); sl@0: } sl@0: return found; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Object3D method. sl@0: * sl@0: * \param originalObj original Group object sl@0: * \param cloneObj pointer to cloned Group object sl@0: * \param pairs array for all object-duplicate pairs sl@0: * \param numPairs number of pairs sl@0: */ sl@0: static M3Gbool m3gGroupDuplicate(const Object *originalObj, sl@0: Object **cloneObj, sl@0: Object **pairs, sl@0: M3Gint *numPairs) sl@0: { sl@0: Node *child; sl@0: Group *original = (Group *)originalObj; sl@0: Group *clone; sl@0: sl@0: /* Create the clone object, unless already created in a derived sl@0: * class function */ sl@0: sl@0: if (*cloneObj == NULL) { sl@0: clone = (Group *)m3gCreateGroup(originalObj->interface); sl@0: if (!clone) { sl@0: return M3G_FALSE; /* out of memory */ sl@0: } sl@0: *cloneObj = (Object *)clone; sl@0: } sl@0: else { sl@0: clone = (Group *)*cloneObj; sl@0: } sl@0: sl@0: /* Call base class function to duplicate base class data */ sl@0: sl@0: if (!m3gNodeDuplicate(originalObj, cloneObj, pairs, numPairs)) { sl@0: return M3G_FALSE; /* out of memory; caller will delete us */ sl@0: } sl@0: sl@0: /* Duplicate child nodes. */ sl@0: sl@0: child = original->firstChild; sl@0: if (child) { sl@0: do { sl@0: Node *temp = NULL; sl@0: if (!M3G_VFUNC(Object, child, duplicate)( sl@0: (Object *)child, (Object**)&temp, pairs, numPairs)) { sl@0: m3gDeleteObject((Object*) temp); /* we have the only reference */ sl@0: return M3G_FALSE; sl@0: } sl@0: m3gAddChild(clone, temp); sl@0: child = child->right; sl@0: } while (child != original->firstChild); sl@0: } sl@0: sl@0: return M3G_TRUE; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Node method sl@0: */ sl@0: static M3Gint m3gGroupGetBBox(Node *self, AABB *bbox) sl@0: { sl@0: Group *group = (Group*) self; sl@0: sl@0: /* Quick exit for empty volumes */ sl@0: sl@0: if (!group->firstChild || !self->hasRenderables) { sl@0: return 0; sl@0: } sl@0: sl@0: /* Assume our existing bbox is ok, but compute a new one if it sl@0: * isn't */ sl@0: sl@0: if (group->bbox && !(self->dirtyBits & NODE_BBOX_BIT)) { sl@0: *bbox = *group->bbox; sl@0: } sl@0: else { sl@0: sl@0: /* Compute local bounding box by recursively merging the sl@0: * bounding boxes of all renderable child nodes */ sl@0: sl@0: Node *child = group->firstChild; sl@0: M3Gint groupYield = 0; sl@0: sl@0: do { sl@0: if (child->hasRenderables && child->enableBits) { sl@0: sl@0: /* Get the transformation for the child node, then sl@0: * update our existing state with its bounding box */ sl@0: sl@0: AABB childBBox; sl@0: M3Gint childYield; sl@0: Matrix t; sl@0: sl@0: childYield = m3gGetNodeBBox(child, &childBBox); sl@0: if (childYield > 0) { sl@0: m3gGetCompositeNodeTransform(child, &t); sl@0: m3gTransformAABB(&childBBox, &t); sl@0: sl@0: if (groupYield) { sl@0: m3gFitAABB(bbox, &childBBox, bbox); sl@0: } sl@0: else { sl@0: *bbox = childBBox; sl@0: } sl@0: groupYield += childYield; sl@0: } sl@0: } sl@0: child = child->right; sl@0: } while (child != group->firstChild); sl@0: sl@0: /* Store the updated bbox locally if we have one, or return sl@0: * the combined child yield factor if we don't */ sl@0: sl@0: if (group->bbox) { sl@0: *group->bbox = *bbox; sl@0: } sl@0: else { sl@0: return (groupYield > 0) ? groupYield + VFC_NODE_OVERHEAD : 0; sl@0: } sl@0: } sl@0: return VFC_BBOX_COST + VFC_NODE_OVERHEAD; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Node method sl@0: */ sl@0: static M3Gbool m3gGroupValidate(Node *self, M3Gbitmask stateBits, M3Gint scope) sl@0: { sl@0: Group *group = (Group*) self; sl@0: sl@0: if (stateBits & self->enableBits) { sl@0: sl@0: /* First validate child nodes to ensure we don't skip anything, sl@0: * and allow children to invalidate our state */ sl@0: sl@0: Node *child = group->firstChild; sl@0: if (child) { sl@0: do { sl@0: if (!m3gValidateNode(child, stateBits, scope)) { sl@0: return M3G_FALSE; sl@0: } sl@0: child = child->right; sl@0: } while (child != group->firstChild); sl@0: } sl@0: sl@0: /* Re-evaluate our local bounding box if necessary */ sl@0: sl@0: if (self->hasRenderables && self->dirtyBits & NODE_BBOX_BIT) { sl@0: AABB bbox; sl@0: M3Gint yield; sl@0: M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_VFC_UPDATE); sl@0: sl@0: yield = m3gGetNodeBBox(self, &bbox); sl@0: sl@0: /* Think about adding a bounding box if we don't yet have one, sl@0: * or removing the current one if it doesn't seem worth it */ sl@0: sl@0: if (!group->bbox) { sl@0: if (yield > (3*VFC_BBOX_COST) >> 1) { sl@0: group->bbox = m3gAlloc(M3G_INTERFACE(group), sl@0: sizeof(*group->bbox)); sl@0: if (group->bbox) { sl@0: m3gIncStat(M3G_INTERFACE(group), sl@0: M3G_STAT_BOUNDING_BOXES, 1); sl@0: *group->bbox = bbox; sl@0: } sl@0: else { sl@0: return M3G_FALSE; sl@0: } sl@0: } sl@0: } sl@0: else if (yield <= VFC_BBOX_COST) { sl@0: m3gFree(M3G_INTERFACE(group), group->bbox); sl@0: group->bbox = NULL; sl@0: m3gIncStat(M3G_INTERFACE(group), M3G_STAT_BOUNDING_BOXES, -1); sl@0: } sl@0: M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_VFC_UPDATE); sl@0: } sl@0: return m3gNodeValidate(self, stateBits, scope); sl@0: } sl@0: return M3G_TRUE; sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Overloaded Node method. sl@0: * sl@0: * \param self Group object sl@0: * \param pairs array for all object-duplicate pairs sl@0: * \param numPairs number of pairs sl@0: */ sl@0: static void m3gGroupUpdateDuplicateReferences(Node *self, Object **pairs, M3Gint numPairs) sl@0: { sl@0: Group *group = (Group *)self; sl@0: Node *child = group->firstChild; sl@0: sl@0: m3gNodeUpdateDuplicateReferences(self, pairs, numPairs); sl@0: sl@0: if (child) { sl@0: do { sl@0: M3G_VFUNC(Node, child, updateDuplicateReferences)( sl@0: child, pairs, numPairs); sl@0: child = child->right; sl@0: } while (child != group->firstChild); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \internal sl@0: * \brief Initializes a Group object. See specification sl@0: * for default values. sl@0: * sl@0: * \param m3g M3G interface sl@0: * \param group Group object sl@0: * \param vfTable virtual function table sl@0: */ sl@0: static void m3gInitGroup(Interface *m3g, Group *group, M3GClass classID) sl@0: { sl@0: /* Group is derived from Node */ sl@0: m3gInitNode(m3g, &group->node, classID); sl@0: } sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Virtual function table sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: static const NodeVFTable m3gvf_Group = { sl@0: { sl@0: { sl@0: m3gGroupApplyAnimation, sl@0: m3gNodeIsCompatible, sl@0: m3gNodeUpdateProperty, sl@0: m3gGroupDoGetReferences, sl@0: m3gGroupFindID, sl@0: m3gGroupDuplicate, sl@0: m3gDestroyGroup sl@0: } sl@0: }, sl@0: m3gGroupAlign, sl@0: NULL, /* pure virtual m3gNodeDoRender */ sl@0: m3gGroupGetBBox, sl@0: m3gGroupRayIntersect, sl@0: m3gGroupSetupRender, sl@0: m3gGroupUpdateDuplicateReferences, sl@0: m3gGroupValidate sl@0: }; sl@0: sl@0: sl@0: /*---------------------------------------------------------------------- sl@0: * Public API functions sl@0: *--------------------------------------------------------------------*/ sl@0: sl@0: /*! sl@0: * \brief Creates a Group object. sl@0: * sl@0: * \param interface M3G interface sl@0: * \retval Group new Group object sl@0: * \retval NULL Group creating failed sl@0: */ sl@0: M3G_API M3GGroup m3gCreateGroup(M3GInterface interface) sl@0: { sl@0: Interface *m3g = (Interface *) interface; sl@0: M3G_VALIDATE_INTERFACE(m3g); sl@0: sl@0: { sl@0: Group *group = m3gAllocZ(m3g, sizeof(Group)); sl@0: sl@0: if (group != NULL) { sl@0: m3gInitGroup(m3g, group, M3G_CLASS_GROUP); sl@0: } sl@0: sl@0: return (M3GGroup) group; sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \brief Adds a node to this group. sl@0: * sl@0: * \param handle Group object sl@0: * \param hNode Node object sl@0: */ sl@0: M3G_API void m3gAddChild(M3GGroup handle, M3GNode hNode) sl@0: { sl@0: Group *group = (Group *) handle; sl@0: Node *child = (Node *) hNode; sl@0: sl@0: M3G_VALIDATE_OBJECT(group); sl@0: sl@0: if (child == NULL) { sl@0: m3gRaiseError(M3G_INTERFACE(group), M3G_NULL_POINTER); sl@0: return; sl@0: } sl@0: sl@0: if (child == (Node *)group || sl@0: (child->parent != NULL && child->parent != (Node *)group) || sl@0: m3gIsChildOf(child, (Node *)group) || sl@0: m3gGetClass((Object *) child) == M3G_CLASS_WORLD) { sl@0: m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_VALUE); sl@0: return; sl@0: } sl@0: sl@0: if (child->parent == NULL) { sl@0: m3gLinkChild(child, group); sl@0: } sl@0: } sl@0: sl@0: /*! sl@0: * \brief Removes a node from this group. sl@0: * sl@0: * \param handle Group object sl@0: * \param hNode Node object sl@0: */ sl@0: M3G_API void m3gRemoveChild(M3GGroup handle, M3GNode hNode) sl@0: { sl@0: Group *group = (Group *) handle; sl@0: Node *child = (Node *)hNode; sl@0: M3G_VALIDATE_OBJECT(group); sl@0: sl@0: if (child == NULL) { sl@0: return; sl@0: } sl@0: sl@0: if (child->hasBones == M3G_TRUE) { sl@0: m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_VALUE); sl@0: return; sl@0: } sl@0: sl@0: if (group->firstChild == NULL) { sl@0: return; sl@0: } sl@0: sl@0: m3gDetachChild(child, group); sl@0: } sl@0: sl@0: /*! sl@0: * \brief Performs 3D pick. sl@0: * sl@0: * \param handle Group object sl@0: * \param mask pick scope mask sl@0: * \param ray pick ray sl@0: * \arg ray[0] origin X sl@0: * \arg ray[1] origin Y sl@0: * \arg ray[2] origin Z sl@0: * \arg ray[3] direction X sl@0: * \arg ray[4] direction Y sl@0: * \arg ray[5] direction Z sl@0: * \param result java side RayIntersection result sl@0: * \arg result[0] distance sl@0: * \arg result[1] submesh index sl@0: * \arg result[2] textureS[0] sl@0: * \arg result[3] textureS[1] sl@0: * \arg result[4] textureT[0] sl@0: * \arg result[5] textureT[1] sl@0: * \arg result[6] normal X sl@0: * \arg result[7] normal Y sl@0: * \arg result[8] normal Z sl@0: * \arg result[9] ray ox sl@0: * \arg result[10] ray oy sl@0: * \arg result[11] ray oz sl@0: * \arg result[12] ray dx sl@0: * \arg result[13] ray dy sl@0: * \arg result[14] ray dz sl@0: * \return intersected Node object sl@0: */ sl@0: sl@0: #ifdef M3G_ENABLE_PROFILING sl@0: static M3GNode m3gPick3DInternal(M3GGroup handle, sl@0: M3Gint mask, sl@0: M3Gfloat *ray, sl@0: M3Gfloat *result); sl@0: sl@0: M3G_API M3GNode m3gPick3D(M3GGroup handle, sl@0: M3Gint mask, sl@0: M3Gfloat *ray, sl@0: M3Gfloat *result) sl@0: { sl@0: M3GNode pickResult; sl@0: M3G_BEGIN_PROFILE(M3G_INTERFACE(handle), M3G_PROFILE_PICK); sl@0: pickResult = m3gPick3DInternal(handle, mask, ray, result); sl@0: M3G_END_PROFILE(M3G_INTERFACE(handle), M3G_PROFILE_PICK); sl@0: return pickResult; sl@0: } sl@0: sl@0: static M3GNode m3gPick3DInternal(M3GGroup handle, sl@0: M3Gint mask, sl@0: M3Gfloat *ray, sl@0: M3Gfloat *result) sl@0: #else sl@0: M3G_API M3GNode m3gPick3D(M3GGroup handle, sl@0: M3Gint mask, sl@0: M3Gfloat *ray, sl@0: M3Gfloat *result) sl@0: sl@0: #endif sl@0: { sl@0: RayIntersection ri; sl@0: Matrix toGroup; sl@0: Group *group = (Group *) handle; sl@0: M3G_VALIDATE_OBJECT(group); sl@0: sl@0: M3G_LOG1(M3G_LOG_STAGES, "Picking group 0x%08X\n", (unsigned) group); sl@0: sl@0: /* Check for errors */ sl@0: if (ray[3] == 0 && ray[4] == 0 && ray[5] == 0) { sl@0: m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_VALUE); sl@0: return NULL; sl@0: } sl@0: if (!m3gValidateNode((Node*) group, NODE_PICK_BIT, mask)) { sl@0: return NULL; sl@0: } sl@0: sl@0: m3gInitPick(&ri, (Node *)group, NULL, 0, 0); sl@0: m3gIdentityMatrix(&toGroup); sl@0: sl@0: ray[3] = m3gAdd(ray[3], ray[0]); sl@0: ray[4] = m3gAdd(ray[4], ray[1]); sl@0: ray[5] = m3gAdd(ray[5], ray[2]); sl@0: sl@0: M3G_VFUNC(Node, group, rayIntersect)( (Node *)group, sl@0: mask, sl@0: ray, sl@0: &ri, sl@0: &toGroup); sl@0: m3gFillPickResult(&ri, ray, result); sl@0: return ri.intersected; sl@0: } sl@0: sl@0: /*! sl@0: * \brief Performs 2D pick. sl@0: * sl@0: * \param handle Group object sl@0: * \param mask pick scope mask sl@0: * \param x viewport x sl@0: * \param y viewport y sl@0: * \param hCamera Camera object sl@0: * \param result java side RayIntersection result, see m3gPick3D sl@0: * \return intersected Node object sl@0: */ sl@0: sl@0: #ifdef M3G_ENABLE_PROFILING sl@0: static M3GNode m3gPick2DInternal(M3GGroup handle, sl@0: M3Gint mask, sl@0: M3Gfloat x, M3Gfloat y, sl@0: M3GCamera hCamera, sl@0: M3Gfloat *result); sl@0: sl@0: M3G_API M3GNode m3gPick2D(M3GGroup handle, sl@0: M3Gint mask, sl@0: M3Gfloat x, M3Gfloat y, sl@0: M3GCamera hCamera, sl@0: M3Gfloat *result) sl@0: { sl@0: M3GNode pickResult; sl@0: M3G_BEGIN_PROFILE(M3G_INTERFACE(handle), M3G_PROFILE_PICK); sl@0: pickResult = m3gPick2DInternal(handle, mask, x, y, hCamera, result); sl@0: M3G_END_PROFILE(M3G_INTERFACE(handle), M3G_PROFILE_PICK); sl@0: return pickResult; sl@0: } sl@0: sl@0: static M3GNode m3gPick2DInternal(M3GGroup handle, sl@0: M3Gint mask, sl@0: M3Gfloat x, M3Gfloat y, sl@0: M3GCamera hCamera, sl@0: M3Gfloat *result) sl@0: #else sl@0: M3G_API M3GNode m3gPick2D(M3GGroup handle, sl@0: M3Gint mask, sl@0: M3Gfloat x, M3Gfloat y, sl@0: M3GCamera hCamera, sl@0: M3Gfloat *result) sl@0: #endif sl@0: { sl@0: Vec4 farp, nearp; sl@0: RayIntersection ri; sl@0: Matrix toGroup; sl@0: M3Gfloat ray[6 + 2]; /* Extra floats to store near and far plane z */ sl@0: Node *root; sl@0: Group *group = (Group *) handle; sl@0: sl@0: M3G_LOG2(M3G_LOG_STAGES, "Picking group 0x%08X via camera 0x%08X\n", sl@0: (unsigned) group, (unsigned) hCamera); sl@0: sl@0: M3G_VALIDATE_OBJECT(group); sl@0: sl@0: if (hCamera == 0) { sl@0: m3gRaiseError(M3G_INTERFACE(group), M3G_NULL_POINTER); sl@0: return NULL; sl@0: } sl@0: sl@0: root = m3gGetRoot((Node *)hCamera); sl@0: sl@0: if (root != m3gGetRoot(&group->node)) { sl@0: m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_OPERATION); sl@0: return NULL; sl@0: } sl@0: if (!m3gValidateNode(root, NODE_PICK_BIT, mask)) { sl@0: return NULL; sl@0: } sl@0: sl@0: farp.x = m3gSub(m3gMul(2, x), 1.f); sl@0: farp.y = m3gSub(1.f, m3gMul(2, y)); sl@0: farp.z = 1.f; sl@0: farp.w = 1.f; sl@0: sl@0: nearp.x = farp.x; sl@0: nearp.y = farp.y; sl@0: nearp.z = -1.f; sl@0: nearp.w = 1.f; sl@0: sl@0: m3gCopyMatrix(&toGroup, m3gProjectionMatrix((Camera *)hCamera)); sl@0: sl@0: M3G_BEGIN_PROFILE(M3G_INTERFACE(group), M3G_PROFILE_TRANSFORM_INVERT); sl@0: if (!m3gInvertMatrix(&toGroup)) { sl@0: m3gRaiseError(M3G_INTERFACE(group), M3G_ARITHMETIC_ERROR); sl@0: return NULL; sl@0: } sl@0: M3G_END_PROFILE(M3G_INTERFACE(group), M3G_PROFILE_TRANSFORM_INVERT); sl@0: sl@0: m3gTransformVec4(&toGroup, &nearp); sl@0: m3gTransformVec4(&toGroup, &farp); sl@0: sl@0: m3gScaleVec4(&nearp, m3gRcp(nearp.w)); sl@0: m3gScaleVec4(&farp, m3gRcp(farp.w)); sl@0: sl@0: /* Store near and far plane z for sprite picking */ sl@0: ray[6] = nearp.z; sl@0: ray[7] = farp.z; sl@0: sl@0: if (!m3gGetTransformTo((M3GNode) hCamera, (Node *) group, &toGroup)) { sl@0: return NULL; sl@0: } sl@0: sl@0: m3gTransformVec4(&toGroup, &nearp); sl@0: m3gTransformVec4(&toGroup, &farp); sl@0: sl@0: m3gScaleVec4(&nearp, m3gRcp(nearp.w)); sl@0: m3gScaleVec4(&farp, m3gRcp(farp.w)); sl@0: sl@0: ray[0] = nearp.x; sl@0: ray[1] = nearp.y; sl@0: ray[2] = nearp.z; sl@0: ray[3] = farp.x; sl@0: ray[4] = farp.y; sl@0: ray[5] = farp.z; sl@0: sl@0: sl@0: m3gInitPick(&ri, (Node *)group, (Camera *)hCamera, x, y); sl@0: m3gIdentityMatrix(&toGroup); sl@0: sl@0: M3G_VFUNC(Node, group, rayIntersect)((Node *)group, mask, ray, &ri, &toGroup); sl@0: sl@0: m3gFillPickResult(&ri, ray, result); sl@0: return ri.intersected; sl@0: } sl@0: sl@0: /*! sl@0: * \brief Gets a child. sl@0: * sl@0: * \param handle Group object sl@0: * \param idx child index sl@0: * \return Node object sl@0: */ sl@0: M3G_API M3GNode m3gGetChild(M3GGroup handle, M3Gint idx) sl@0: { sl@0: Node *n; sl@0: Group *group = (Group *) handle; sl@0: M3G_VALIDATE_OBJECT(group); sl@0: sl@0: if (idx < 0) { sl@0: goto InvalidIndex; sl@0: } sl@0: sl@0: n = group->firstChild; sl@0: sl@0: while (idx-- > 0) { sl@0: n = n->right; sl@0: if (n == group->firstChild) { sl@0: goto InvalidIndex; sl@0: } sl@0: } sl@0: return n; sl@0: sl@0: InvalidIndex: sl@0: m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_INDEX); sl@0: return NULL; sl@0: } sl@0: sl@0: /*! sl@0: * \brief Gets children count. sl@0: * sl@0: * \param handle Group object sl@0: * \return children count sl@0: */ sl@0: M3G_API M3Gint m3gGetChildCount(M3GGroup handle) sl@0: { sl@0: Group *group = (Group *) handle; sl@0: M3G_VALIDATE_OBJECT(group); sl@0: { sl@0: M3Gint count = 0; sl@0: const Node *child = group->firstChild; sl@0: if (child) { sl@0: do { sl@0: ++count; sl@0: child = child->right; sl@0: } while (child != group->firstChild); sl@0: } sl@0: return count; sl@0: } sl@0: } sl@0: