m3g/m3gcore11/src/m3g_group.c
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Wed, 31 Mar 2010 23:34:07 +0300
branchRCL_3
changeset 26 15986eb6c500
parent 0 5d03bc08d59c
permissions -rw-r--r--
Revision: 201010 Kit: 201013

/*
* 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;
    }
}