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