os/graphics/m3g/m3gcore11/src/m3g_group.c
author sl@SLION-WIN7.fritz.box
Fri, 15 Jun 2012 03:10:57 +0200
changeset 0 bde4ae8d615e
permissions -rw-r--r--
First public contribution.
sl@0
     1
/*
sl@0
     2
* Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies).
sl@0
     3
* All rights reserved.
sl@0
     4
* This component and the accompanying materials are made available
sl@0
     5
* under the terms of the License "Eclipse Public License v1.0"
sl@0
     6
* which accompanies this distribution, and is available
sl@0
     7
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
sl@0
     8
*
sl@0
     9
* Initial Contributors:
sl@0
    10
* Nokia Corporation - initial contribution.
sl@0
    11
*
sl@0
    12
* Contributors:
sl@0
    13
*
sl@0
    14
* Description: Group implementation
sl@0
    15
*
sl@0
    16
*/
sl@0
    17
sl@0
    18
sl@0
    19
/*!
sl@0
    20
 * \internal
sl@0
    21
 * \file
sl@0
    22
 * \brief Group implementation
sl@0
    23
 */
sl@0
    24
sl@0
    25
#ifndef M3G_CORE_INCLUDE
sl@0
    26
#   error included by m3g_core.c; do not compile separately.
sl@0
    27
#endif
sl@0
    28
sl@0
    29
#include "m3g_group.h"
sl@0
    30
#include "m3g_memory.h"
sl@0
    31
sl@0
    32
/*----------------------------------------------------------------------
sl@0
    33
 * Internal functions
sl@0
    34
 *--------------------------------------------------------------------*/
sl@0
    35
sl@0
    36
/*!
sl@0
    37
 * \internal
sl@0
    38
 * \brief Links a new child into the child list of this node.
sl@0
    39
 *
sl@0
    40
 * This assumes that all error checking has been done prior to calling
sl@0
    41
 * the function, and the operation is a valid one.
sl@0
    42
 *
sl@0
    43
 * \param child Node object
sl@0
    44
 * \param group Group object
sl@0
    45
 */
sl@0
    46
static void m3gLinkChild(Node *child, Group *group)
sl@0
    47
{
sl@0
    48
    M3G_VALIDATE_OBJECT(child);
sl@0
    49
    M3G_VALIDATE_OBJECT(group);
sl@0
    50
    
sl@0
    51
	if (group->firstChild == NULL) {
sl@0
    52
		group->firstChild = child;
sl@0
    53
		child->left = child;
sl@0
    54
		child->right = child;
sl@0
    55
	}
sl@0
    56
	else {
sl@0
    57
        Node *linkChild = group->firstChild;
sl@0
    58
        
sl@0
    59
		child->left = linkChild->left;
sl@0
    60
		linkChild->left->right = child;
sl@0
    61
        
sl@0
    62
		child->right = linkChild;
sl@0
    63
		linkChild->left = child;
sl@0
    64
	}
sl@0
    65
    m3gSetParent(child, (Node *) group);    
sl@0
    66
}
sl@0
    67
sl@0
    68
/*!
sl@0
    69
 * \internal
sl@0
    70
 * \brief Removes a child from the child list of this node.
sl@0
    71
 *
sl@0
    72
 * This assumes that all error checking has been done prior to calling
sl@0
    73
 * the function, and the operation is a valid one.
sl@0
    74
 *
sl@0
    75
 * \param child Node object
sl@0
    76
 * \param group Group object
sl@0
    77
 */
sl@0
    78
static void m3gDetachChild(Node *child, Group *group)
sl@0
    79
{
sl@0
    80
	Node *n;
sl@0
    81
    M3G_VALIDATE_OBJECT(child);
sl@0
    82
    M3G_VALIDATE_OBJECT(group);
sl@0
    83
sl@0
    84
    n = group->firstChild;
sl@0
    85
    
sl@0
    86
	do {
sl@0
    87
		if (n == child) {
sl@0
    88
            M3G_VALIDATE_OBJECT(child->right);
sl@0
    89
            M3G_VALIDATE_OBJECT(child->left);
sl@0
    90
            
sl@0
    91
			n->right->left = n->left;
sl@0
    92
			n->left->right = n->right;
sl@0
    93
sl@0
    94
			if (group->firstChild == n) {
sl@0
    95
				group->firstChild = (n->right != n) ? n->right : NULL;
sl@0
    96
			}
sl@0
    97
            
sl@0
    98
            n->left = NULL;
sl@0
    99
            n->right = NULL;
sl@0
   100
            m3gSetParent(n, NULL);
sl@0
   101
			return;
sl@0
   102
		}
sl@0
   103
        n = n->right;
sl@0
   104
	} while (n != group->firstChild);
sl@0
   105
}
sl@0
   106
sl@0
   107
/*!
sl@0
   108
 * \internal
sl@0
   109
 * \brief Destroys this Group object.
sl@0
   110
 *
sl@0
   111
 * \param obj Group object
sl@0
   112
 */
sl@0
   113
static void m3gDestroyGroup(Object *obj)
sl@0
   114
{
sl@0
   115
    /* Release child references so they can be deleted */
sl@0
   116
    
sl@0
   117
	Group *group = (Group *) obj;
sl@0
   118
	while (group->firstChild != NULL) {
sl@0
   119
        m3gDetachChild(group->firstChild, group);
sl@0
   120
	}
sl@0
   121
#   if defined(M3G_ENABLE_VF_CULLING)
sl@0
   122
    if (group->bbox) {
sl@0
   123
        m3gFree(M3G_INTERFACE(group), group->bbox);
sl@0
   124
        m3gIncStat(M3G_INTERFACE(group), M3G_STAT_BOUNDING_BOXES, -1);
sl@0
   125
    }
sl@0
   126
#   endif
sl@0
   127
    m3gDestroyNode(obj);
sl@0
   128
}
sl@0
   129
sl@0
   130
/*!
sl@0
   131
 * \internal
sl@0
   132
 * \brief Overloaded Node method.
sl@0
   133
 *
sl@0
   134
 * \param self Group object
sl@0
   135
 * \param refNode alignment reference Node object
sl@0
   136
 *
sl@0
   137
 * \retval M3G_TRUE continue align
sl@0
   138
 * \retval M3G_FALSE abort align
sl@0
   139
 */
sl@0
   140
static M3Gbool m3gGroupAlign(Node *self, const Node *refNode)
sl@0
   141
{
sl@0
   142
	Group *group = (Group *)self;
sl@0
   143
	Node *child = group->firstChild;
sl@0
   144
sl@0
   145
    if (!m3gNodeAlign(self, refNode)) {
sl@0
   146
        return M3G_FALSE;
sl@0
   147
    }
sl@0
   148
sl@0
   149
    if (child) {
sl@0
   150
        do {
sl@0
   151
            if (!M3G_VFUNC(Node, child, align)(child, refNode)) {
sl@0
   152
                return M3G_FALSE;
sl@0
   153
            }
sl@0
   154
            child = child->right;
sl@0
   155
        } while (child != group->firstChild);
sl@0
   156
	}
sl@0
   157
sl@0
   158
    return M3G_TRUE;
sl@0
   159
}
sl@0
   160
sl@0
   161
/*!
sl@0
   162
 * \internal
sl@0
   163
 * \brief Overloaded Node method.
sl@0
   164
 *
sl@0
   165
 * Setup group rendering by calling child
sl@0
   166
 * nodes' render setup.
sl@0
   167
 *
sl@0
   168
 * \param self Group object
sl@0
   169
 * \param toCamera transform to camera
sl@0
   170
 * \param alphaFactor total alpha factor
sl@0
   171
 * \param caller caller node
sl@0
   172
 * \param renderQueue RenderQueue
sl@0
   173
 *
sl@0
   174
 * \retval M3G_TRUE continue render setup
sl@0
   175
 * \retval M3G_FALSE abort render setup
sl@0
   176
 */
sl@0
   177
static M3Gbool m3gGroupSetupRender(Node *self,
sl@0
   178
                                   const Node *caller,
sl@0
   179
                                   SetupRenderState *s,
sl@0
   180
                                   RenderQueue *renderQueue)
sl@0
   181
{
sl@0
   182
	Group *group = (Group *)self;
sl@0
   183
    M3Gbool enabled, success = M3G_TRUE;
sl@0
   184
sl@0
   185
    /* Check whether we're going up or down, and optimize the
sl@0
   186
     * rendering-enabled and visibility checking based on that */
sl@0
   187
sl@0
   188
    enabled = (self->enableBits & NODE_RENDER_BIT) != 0;
sl@0
   189
    if (caller != self->parent) {
sl@0
   190
        enabled = m3gHasEnabledPath(self, renderQueue->root);
sl@0
   191
        s->cullMask = CULLMASK_ALL;
sl@0
   192
    }
sl@0
   193
    M3G_ASSERT(!self->dirtyBits || !enabled);
sl@0
   194
    
sl@0
   195
	/* First do the child nodes, unless disabled (inheritable, so
sl@0
   196
     * children would be, too) */
sl@0
   197
    
sl@0
   198
    if (enabled && (group->numNonCullables > 0 || group->numRenderables > 0)) {
sl@0
   199
        
sl@0
   200
        Node *child = group->firstChild;
sl@0
   201
        if (child) {
sl@0
   202
sl@0
   203
            /* Check the bounding box if we have one */
sl@0
   204
            
sl@0
   205
#           if defined(M3G_ENABLE_VF_CULLING)
sl@0
   206
            if (group->bbox) {
sl@0
   207
                m3gValidateAABB(group->bbox);
sl@0
   208
                m3gUpdateCullingMask(s, renderQueue->camera, group->bbox);
sl@0
   209
            }
sl@0
   210
#           endif
sl@0
   211
            
sl@0
   212
            /* If we're not culled, or if we carry lights, we really
sl@0
   213
             * need to recurse into each child node */
sl@0
   214
            
sl@0
   215
            if (s->cullMask || group->numNonCullables > 0) {
sl@0
   216
                do {
sl@0
   217
                    if (child != caller) {
sl@0
   218
                        SetupRenderState cs;
sl@0
   219
                        cs.cullMask = s->cullMask;
sl@0
   220
                        
sl@0
   221
                        M3G_BEGIN_PROFILE(M3G_INTERFACE(group),
sl@0
   222
                                          M3G_PROFILE_SETUP_TRANSFORMS);
sl@0
   223
                        m3gGetCompositeNodeTransform(child, &cs.toCamera);
sl@0
   224
                        m3gPreMultiplyMatrix(&cs.toCamera, &s->toCamera);
sl@0
   225
                        M3G_END_PROFILE(M3G_INTERFACE(group),
sl@0
   226
                                        M3G_PROFILE_SETUP_TRANSFORMS);
sl@0
   227
                        
sl@0
   228
                        if (!M3G_VFUNC(Node, child, setupRender)(
sl@0
   229
                                child, self, &cs, renderQueue)) {
sl@0
   230
                            return M3G_FALSE;
sl@0
   231
                        }
sl@0
   232
                    }
sl@0
   233
                    child = child->right;
sl@0
   234
                } while (child != group->firstChild);
sl@0
   235
            }
sl@0
   236
            else {
sl@0
   237
                M3GInterface m3g = M3G_INTERFACE(group);
sl@0
   238
                M3Gint n = group->numRenderables;
sl@0
   239
                m3gIncStat(m3g, M3G_STAT_RENDER_NODES, n);
sl@0
   240
                m3gIncStat(m3g, M3G_STAT_RENDER_NODES_CULLED, n);
sl@0
   241
            }
sl@0
   242
        }
sl@0
   243
    }
sl@0
   244
sl@0
   245
	/* Then do the parent node if we're going up the tree.  Again, we
sl@0
   246
     * can discard the old traversal state at this point. */
sl@0
   247
    
sl@0
   248
	if (self != renderQueue->root) {
sl@0
   249
	    Node *parent = self->parent;
sl@0
   250
	    
sl@0
   251
	    if (parent != caller && parent != NULL) {
sl@0
   252
            Matrix t;
sl@0
   253
sl@0
   254
            M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS);
sl@0
   255
            if (!m3gGetInverseNodeTransform(self, &t)) {
sl@0
   256
                return M3G_FALSE;
sl@0
   257
            }
sl@0
   258
			m3gMulMatrix(&s->toCamera, &t);
sl@0
   259
            M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS);
sl@0
   260
sl@0
   261
            success = M3G_VFUNC(Node, parent, setupRender)(parent,
sl@0
   262
                                                           self,
sl@0
   263
                                                           s,
sl@0
   264
                                                           renderQueue);
sl@0
   265
	    }
sl@0
   266
	}
sl@0
   267
sl@0
   268
    return success;
sl@0
   269
}
sl@0
   270
sl@0
   271
/*!
sl@0
   272
 * \internal
sl@0
   273
 * \brief Overloaded Object3D method.
sl@0
   274
 *
sl@0
   275
 * \param self Group object
sl@0
   276
 * \param time current world time
sl@0
   277
 * \return minimum validity
sl@0
   278
 */
sl@0
   279
static M3Gint m3gGroupApplyAnimation(Object *self, M3Gint time)
sl@0
   280
{
sl@0
   281
    M3Gint validity, minValidity;
sl@0
   282
	Node *child;
sl@0
   283
    Group *group = (Group *)self;
sl@0
   284
    M3G_VALIDATE_OBJECT(group);
sl@0
   285
sl@0
   286
    minValidity = m3gObjectApplyAnimation(self, time);
sl@0
   287
    
sl@0
   288
    child = group->firstChild;
sl@0
   289
    if (child && minValidity > 0) {
sl@0
   290
        do {
sl@0
   291
            validity = M3G_VFUNC(Object, child, applyAnimation)(
sl@0
   292
                (Object *)child, time);
sl@0
   293
            minValidity = validity < minValidity ? validity : minValidity;
sl@0
   294
            child = child->right;
sl@0
   295
        } while (minValidity > 0 && child != group->firstChild);
sl@0
   296
    }
sl@0
   297
    return minValidity;
sl@0
   298
}
sl@0
   299
sl@0
   300
/*!
sl@0
   301
 * \internal
sl@0
   302
 * \brief Overloaded Node method.
sl@0
   303
 *
sl@0
   304
 * Call child nodes' ray intersect.
sl@0
   305
 *
sl@0
   306
 * \param self      Group object
sl@0
   307
 * \param mask      pick scope mask
sl@0
   308
 * \param ray       pick ray
sl@0
   309
 * \param ri        RayIntersection object
sl@0
   310
 * \param toGroup   transform to originating group
sl@0
   311
 * \retval          M3G_TRUE    continue pick
sl@0
   312
 * \retval          M3G_FALSE   abort pick
sl@0
   313
 */
sl@0
   314
static M3Gbool m3gGroupRayIntersect(Node *self,
sl@0
   315
                                    M3Gint mask,
sl@0
   316
                                    M3Gfloat *ray,
sl@0
   317
                                    RayIntersection *ri,
sl@0
   318
                                    Matrix *toGroup)
sl@0
   319
{
sl@0
   320
    Group *group = (Group *)self;
sl@0
   321
    Node *child;
sl@0
   322
    Matrix t, nt;
sl@0
   323
sl@0
   324
    m3gIdentityMatrix(&t);
sl@0
   325
    m3gIdentityMatrix(&nt);
sl@0
   326
sl@0
   327
    child = group->firstChild;
sl@0
   328
    if (child) {
sl@0
   329
        do {
sl@0
   330
            if (m3gHasPickablePath(child, ri->root)) {
sl@0
   331
                m3gCopyMatrix(&t, toGroup);
sl@0
   332
                m3gGetCompositeNodeTransform(child, &nt);
sl@0
   333
                m3gRightMulMatrix(&t, &nt);
sl@0
   334
                
sl@0
   335
                if (!M3G_VFUNC(Node, child, rayIntersect)(
sl@0
   336
                        child, mask, ray, ri, &t)) {
sl@0
   337
                    return M3G_FALSE;
sl@0
   338
                }
sl@0
   339
            }    
sl@0
   340
            child = child->right;
sl@0
   341
        } while (child != group->firstChild);
sl@0
   342
    }
sl@0
   343
sl@0
   344
    return M3G_TRUE;
sl@0
   345
}
sl@0
   346
sl@0
   347
/*!
sl@0
   348
 * \internal
sl@0
   349
 * \brief Initializes pick traversing.
sl@0
   350
 *
sl@0
   351
 * \param ri        RayIntersection object
sl@0
   352
 * \param root      Root node for the traversing
sl@0
   353
 * \param camera    Camera object used in pick (2D pick only)
sl@0
   354
 * \param x         viewport x (2D pick only)
sl@0
   355
 * \param y         viewport y (2D pick only)
sl@0
   356
 */
sl@0
   357
static void m3gInitPick(RayIntersection *ri, Node *root, Camera *camera, M3Gfloat x, M3Gfloat y)
sl@0
   358
{
sl@0
   359
    m3gZero(ri, sizeof(*ri));
sl@0
   360
sl@0
   361
    ri->root = root;
sl@0
   362
    ri->camera = camera;
sl@0
   363
    ri->x = x;
sl@0
   364
    ri->y = y;
sl@0
   365
    ri->tMin = M3G_MAX_POSITIVE_FLOAT;
sl@0
   366
}
sl@0
   367
sl@0
   368
/*!
sl@0
   369
 * \internal
sl@0
   370
 * \brief Fills Java side RayIntersection result.
sl@0
   371
 *
sl@0
   372
 * \param ri        RayIntersection object
sl@0
   373
 * \param ray       Ray used in pick
sl@0
   374
 * \param result    Java side float array
sl@0
   375
 */
sl@0
   376
static void m3gFillPickResult(RayIntersection *ri, M3Gfloat *ray, M3Gfloat *result)
sl@0
   377
{
sl@0
   378
    if (ri->intersected != NULL) {
sl@0
   379
        Vec3 n;
sl@0
   380
sl@0
   381
        /* Fill in the values */
sl@0
   382
        result[0] = ri->distance;
sl@0
   383
        result[1] = (M3Gfloat)ri->submeshIndex;
sl@0
   384
        result[2] = ri->textureS[0];
sl@0
   385
        result[3] = ri->textureS[1];
sl@0
   386
        result[4] = ri->textureT[0];
sl@0
   387
        result[5] = ri->textureT[1];
sl@0
   388
sl@0
   389
        /* Normalize normal */
sl@0
   390
        n.x = ri->normal[0];
sl@0
   391
        n.y = ri->normal[1];
sl@0
   392
        n.z = ri->normal[2];
sl@0
   393
        m3gNormalizeVec3(&n);
sl@0
   394
sl@0
   395
        result[6] = n.x;
sl@0
   396
        result[7] = n.y;
sl@0
   397
        result[8] = n.z;
sl@0
   398
sl@0
   399
        result[9] = ray[0];
sl@0
   400
        result[10] = ray[1];
sl@0
   401
        result[11] = ray[2];
sl@0
   402
        result[12] = m3gSub(ray[3], ray[0]);
sl@0
   403
        result[13] = m3gSub(ray[4], ray[1]);
sl@0
   404
        result[14] = m3gSub(ray[5], ray[2]);
sl@0
   405
    }
sl@0
   406
}
sl@0
   407
sl@0
   408
/*!
sl@0
   409
 * \internal
sl@0
   410
 * \brief Overloaded Object3D method.
sl@0
   411
 *
sl@0
   412
 * \param self Group object
sl@0
   413
 * \param references array of reference objects
sl@0
   414
 * \return number of references
sl@0
   415
 */
sl@0
   416
static M3Gint m3gGroupDoGetReferences(Object *self, Object **references)
sl@0
   417
{
sl@0
   418
    Group *group = (Group *)self;
sl@0
   419
    M3Gint num = m3gObjectDoGetReferences(self, references);
sl@0
   420
    Node *child = group->firstChild;
sl@0
   421
    if (child) {
sl@0
   422
        do {
sl@0
   423
            if (references != NULL)
sl@0
   424
                references[num] = (Object *)child;
sl@0
   425
            child = child->right;
sl@0
   426
            num++;
sl@0
   427
        } while (child != group->firstChild);
sl@0
   428
    }
sl@0
   429
    return num;
sl@0
   430
}
sl@0
   431
sl@0
   432
/*!
sl@0
   433
 * \internal
sl@0
   434
 * \brief Overloaded Object3D method.
sl@0
   435
 *
sl@0
   436
 * \param self Group object
sl@0
   437
 * \param references array of reference objects
sl@0
   438
 * \return number of references
sl@0
   439
 */
sl@0
   440
static Object *m3gGroupFindID(Object *self, M3Gint userID)
sl@0
   441
{
sl@0
   442
    Group *group = (Group *)self;
sl@0
   443
    Object *found = m3gObjectFindID(self, userID);
sl@0
   444
    
sl@0
   445
    Node *child = group->firstChild;
sl@0
   446
    if (child && !found) {
sl@0
   447
        do {
sl@0
   448
            found = m3gFindID((Object*) child, userID);
sl@0
   449
            child = child->right;
sl@0
   450
        } while (!found && child != group->firstChild);
sl@0
   451
    }
sl@0
   452
    return found;
sl@0
   453
}
sl@0
   454
sl@0
   455
/*!
sl@0
   456
 * \internal
sl@0
   457
 * \brief Overloaded Object3D method.
sl@0
   458
 *
sl@0
   459
 * \param originalObj original Group object
sl@0
   460
 * \param cloneObj pointer to cloned Group object
sl@0
   461
 * \param pairs array for all object-duplicate pairs
sl@0
   462
 * \param numPairs number of pairs
sl@0
   463
 */
sl@0
   464
static M3Gbool m3gGroupDuplicate(const Object *originalObj,
sl@0
   465
                                 Object **cloneObj,
sl@0
   466
                                 Object **pairs,
sl@0
   467
                                 M3Gint *numPairs)
sl@0
   468
{
sl@0
   469
    Node *child;
sl@0
   470
    Group *original = (Group *)originalObj;
sl@0
   471
    Group *clone;
sl@0
   472
sl@0
   473
    /* Create the clone object, unless already created in a derived
sl@0
   474
     * class function */
sl@0
   475
    
sl@0
   476
    if (*cloneObj == NULL) {
sl@0
   477
        clone = (Group *)m3gCreateGroup(originalObj->interface);
sl@0
   478
        if (!clone) {
sl@0
   479
            return M3G_FALSE; /* out of memory */
sl@0
   480
        }
sl@0
   481
        *cloneObj = (Object *)clone;
sl@0
   482
    }
sl@0
   483
    else {
sl@0
   484
        clone = (Group *)*cloneObj;
sl@0
   485
    }
sl@0
   486
sl@0
   487
    /* Call base class function to duplicate base class data */
sl@0
   488
    
sl@0
   489
    if (!m3gNodeDuplicate(originalObj, cloneObj, pairs, numPairs)) {
sl@0
   490
        return M3G_FALSE; /* out of memory; caller will delete us */
sl@0
   491
    }
sl@0
   492
sl@0
   493
    /* Duplicate child nodes. */
sl@0
   494
    
sl@0
   495
    child = original->firstChild;
sl@0
   496
    if (child) {
sl@0
   497
        do {
sl@0
   498
            Node *temp = NULL;
sl@0
   499
            if (!M3G_VFUNC(Object, child, duplicate)(
sl@0
   500
                    (Object *)child, (Object**)&temp, pairs, numPairs)) {
sl@0
   501
                m3gDeleteObject((Object*) temp); /* we have the only reference */
sl@0
   502
                return M3G_FALSE;
sl@0
   503
            }
sl@0
   504
            m3gAddChild(clone, temp);
sl@0
   505
            child = child->right;
sl@0
   506
        } while (child != original->firstChild);
sl@0
   507
    }
sl@0
   508
    
sl@0
   509
    return M3G_TRUE;
sl@0
   510
}
sl@0
   511
sl@0
   512
/*!
sl@0
   513
 * \internal
sl@0
   514
 * \brief Overloaded Node method
sl@0
   515
 */
sl@0
   516
static M3Gint m3gGroupGetBBox(Node *self, AABB *bbox)
sl@0
   517
{
sl@0
   518
    Group *group = (Group*) self;
sl@0
   519
    
sl@0
   520
    /* Quick exit for empty volumes */
sl@0
   521
    
sl@0
   522
    if (!group->firstChild || !self->hasRenderables) {
sl@0
   523
        return 0;
sl@0
   524
    }
sl@0
   525
sl@0
   526
    /* Assume our existing bbox is ok, but compute a new one if it
sl@0
   527
     * isn't */
sl@0
   528
    
sl@0
   529
    if (group->bbox && !(self->dirtyBits & NODE_BBOX_BIT)) {
sl@0
   530
        *bbox = *group->bbox;
sl@0
   531
    }
sl@0
   532
    else {
sl@0
   533
sl@0
   534
        /* Compute local bounding box by recursively merging the
sl@0
   535
         * bounding boxes of all renderable child nodes */
sl@0
   536
    
sl@0
   537
        Node *child = group->firstChild;
sl@0
   538
        M3Gint groupYield = 0;
sl@0
   539
        
sl@0
   540
        do {
sl@0
   541
            if (child->hasRenderables && child->enableBits) {
sl@0
   542
                
sl@0
   543
                /* Get the transformation for the child node, then
sl@0
   544
                 * update our existing state with its bounding box */
sl@0
   545
                
sl@0
   546
                AABB childBBox;
sl@0
   547
                M3Gint childYield;
sl@0
   548
                Matrix t;
sl@0
   549
                
sl@0
   550
                childYield = m3gGetNodeBBox(child, &childBBox);
sl@0
   551
                if (childYield > 0) {
sl@0
   552
                    m3gGetCompositeNodeTransform(child, &t);
sl@0
   553
                    m3gTransformAABB(&childBBox, &t);
sl@0
   554
                    
sl@0
   555
                    if (groupYield) {
sl@0
   556
                        m3gFitAABB(bbox, &childBBox, bbox);
sl@0
   557
                    }
sl@0
   558
                    else {
sl@0
   559
                        *bbox = childBBox;
sl@0
   560
                    }
sl@0
   561
                    groupYield += childYield;
sl@0
   562
                }
sl@0
   563
            }
sl@0
   564
            child = child->right;
sl@0
   565
        } while (child != group->firstChild);
sl@0
   566
        
sl@0
   567
        /* Store the updated bbox locally if we have one, or return
sl@0
   568
         * the combined child yield factor if we don't */
sl@0
   569
        
sl@0
   570
        if (group->bbox) {
sl@0
   571
            *group->bbox = *bbox;
sl@0
   572
        }
sl@0
   573
        else {
sl@0
   574
            return (groupYield > 0) ? groupYield + VFC_NODE_OVERHEAD : 0;
sl@0
   575
        }
sl@0
   576
    }
sl@0
   577
    return VFC_BBOX_COST + VFC_NODE_OVERHEAD;
sl@0
   578
}
sl@0
   579
sl@0
   580
/*!
sl@0
   581
 * \internal
sl@0
   582
 * \brief Overloaded Node method
sl@0
   583
 */
sl@0
   584
static M3Gbool m3gGroupValidate(Node *self, M3Gbitmask stateBits, M3Gint scope)
sl@0
   585
{
sl@0
   586
    Group *group = (Group*) self;
sl@0
   587
sl@0
   588
    if (stateBits & self->enableBits) {
sl@0
   589
        
sl@0
   590
        /* First validate child nodes to ensure we don't skip anything,
sl@0
   591
         * and allow children to invalidate our state */
sl@0
   592
    
sl@0
   593
        Node *child = group->firstChild;
sl@0
   594
        if (child) {
sl@0
   595
            do {
sl@0
   596
                if (!m3gValidateNode(child, stateBits, scope)) {
sl@0
   597
                    return M3G_FALSE;
sl@0
   598
                }
sl@0
   599
                child = child->right;
sl@0
   600
            } while (child != group->firstChild);
sl@0
   601
        }
sl@0
   602
sl@0
   603
        /* Re-evaluate our local bounding box if necessary */
sl@0
   604
sl@0
   605
        if (self->hasRenderables && self->dirtyBits & NODE_BBOX_BIT) {
sl@0
   606
            AABB bbox;
sl@0
   607
            M3Gint yield;
sl@0
   608
            M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_VFC_UPDATE);
sl@0
   609
            
sl@0
   610
            yield = m3gGetNodeBBox(self, &bbox);
sl@0
   611
        
sl@0
   612
            /* Think about adding a bounding box if we don't yet have one,
sl@0
   613
             * or removing the current one if it doesn't seem worth it */
sl@0
   614
sl@0
   615
            if (!group->bbox) {
sl@0
   616
                if (yield > (3*VFC_BBOX_COST) >> 1) {
sl@0
   617
                    group->bbox = m3gAlloc(M3G_INTERFACE(group),
sl@0
   618
                                           sizeof(*group->bbox));
sl@0
   619
                    if (group->bbox) {
sl@0
   620
                        m3gIncStat(M3G_INTERFACE(group),
sl@0
   621
                                   M3G_STAT_BOUNDING_BOXES, 1);
sl@0
   622
                        *group->bbox = bbox;
sl@0
   623
                    }
sl@0
   624
                    else {
sl@0
   625
                        return M3G_FALSE;
sl@0
   626
                    }
sl@0
   627
                }
sl@0
   628
            }
sl@0
   629
            else if (yield <= VFC_BBOX_COST) {
sl@0
   630
                m3gFree(M3G_INTERFACE(group), group->bbox);
sl@0
   631
                group->bbox = NULL;
sl@0
   632
                m3gIncStat(M3G_INTERFACE(group), M3G_STAT_BOUNDING_BOXES, -1);
sl@0
   633
            }
sl@0
   634
            M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_VFC_UPDATE);
sl@0
   635
        }
sl@0
   636
        return m3gNodeValidate(self, stateBits, scope);
sl@0
   637
    }
sl@0
   638
    return M3G_TRUE;
sl@0
   639
}
sl@0
   640
sl@0
   641
/*!
sl@0
   642
 * \internal
sl@0
   643
 * \brief Overloaded Node method.
sl@0
   644
 *
sl@0
   645
 * \param self Group object
sl@0
   646
 * \param pairs array for all object-duplicate pairs
sl@0
   647
 * \param numPairs number of pairs
sl@0
   648
 */
sl@0
   649
static void m3gGroupUpdateDuplicateReferences(Node *self, Object **pairs, M3Gint numPairs)
sl@0
   650
{
sl@0
   651
    Group *group = (Group *)self;
sl@0
   652
    Node *child = group->firstChild;
sl@0
   653
    
sl@0
   654
    m3gNodeUpdateDuplicateReferences(self, pairs, numPairs);
sl@0
   655
    
sl@0
   656
    if (child) {
sl@0
   657
        do {
sl@0
   658
            M3G_VFUNC(Node, child, updateDuplicateReferences)(
sl@0
   659
                child, pairs, numPairs);
sl@0
   660
            child = child->right;
sl@0
   661
        } while (child != group->firstChild);
sl@0
   662
    }
sl@0
   663
}
sl@0
   664
sl@0
   665
/*!
sl@0
   666
 * \internal
sl@0
   667
 * \brief Initializes a Group object. See specification
sl@0
   668
 * for default values.
sl@0
   669
 *
sl@0
   670
 * \param m3g           M3G interface
sl@0
   671
 * \param group         Group object
sl@0
   672
 * \param vfTable       virtual function table
sl@0
   673
 */
sl@0
   674
static void m3gInitGroup(Interface *m3g, Group *group, M3GClass classID)
sl@0
   675
{
sl@0
   676
	/* Group is derived from Node */
sl@0
   677
	m3gInitNode(m3g, &group->node, classID);
sl@0
   678
}
sl@0
   679
sl@0
   680
/*----------------------------------------------------------------------
sl@0
   681
 * Virtual function table
sl@0
   682
 *--------------------------------------------------------------------*/
sl@0
   683
sl@0
   684
static const NodeVFTable m3gvf_Group = {
sl@0
   685
    {
sl@0
   686
        {
sl@0
   687
            m3gGroupApplyAnimation,
sl@0
   688
            m3gNodeIsCompatible,
sl@0
   689
            m3gNodeUpdateProperty,
sl@0
   690
            m3gGroupDoGetReferences,
sl@0
   691
            m3gGroupFindID,
sl@0
   692
            m3gGroupDuplicate,
sl@0
   693
            m3gDestroyGroup
sl@0
   694
        }
sl@0
   695
    },
sl@0
   696
    m3gGroupAlign,
sl@0
   697
    NULL, /* pure virtual m3gNodeDoRender */
sl@0
   698
    m3gGroupGetBBox,
sl@0
   699
    m3gGroupRayIntersect,
sl@0
   700
    m3gGroupSetupRender,
sl@0
   701
    m3gGroupUpdateDuplicateReferences,
sl@0
   702
    m3gGroupValidate
sl@0
   703
};
sl@0
   704
sl@0
   705
sl@0
   706
/*----------------------------------------------------------------------
sl@0
   707
 * Public API functions
sl@0
   708
 *--------------------------------------------------------------------*/
sl@0
   709
sl@0
   710
/*!
sl@0
   711
 * \brief Creates a Group object.
sl@0
   712
 *
sl@0
   713
 * \param interface     M3G interface
sl@0
   714
 * \retval Group new Group object
sl@0
   715
 * \retval NULL Group creating failed
sl@0
   716
 */
sl@0
   717
M3G_API M3GGroup m3gCreateGroup(M3GInterface interface)
sl@0
   718
{
sl@0
   719
    Interface *m3g = (Interface *) interface;
sl@0
   720
    M3G_VALIDATE_INTERFACE(m3g);
sl@0
   721
sl@0
   722
	{
sl@0
   723
		Group *group =  m3gAllocZ(m3g, sizeof(Group));
sl@0
   724
	
sl@0
   725
        if (group != NULL) {
sl@0
   726
    		m3gInitGroup(m3g, group, M3G_CLASS_GROUP);
sl@0
   727
        }
sl@0
   728
sl@0
   729
		return (M3GGroup) group;
sl@0
   730
	}
sl@0
   731
}
sl@0
   732
sl@0
   733
/*!
sl@0
   734
 * \brief Adds a node to this group.
sl@0
   735
 *
sl@0
   736
 * \param handle        Group object
sl@0
   737
 * \param hNode         Node object
sl@0
   738
 */
sl@0
   739
M3G_API void m3gAddChild(M3GGroup handle, M3GNode hNode)
sl@0
   740
{
sl@0
   741
    Group *group = (Group *) handle;
sl@0
   742
	Node *child = (Node *) hNode;
sl@0
   743
    
sl@0
   744
    M3G_VALIDATE_OBJECT(group);
sl@0
   745
sl@0
   746
    if (child == NULL) {
sl@0
   747
        m3gRaiseError(M3G_INTERFACE(group), M3G_NULL_POINTER);
sl@0
   748
        return;
sl@0
   749
    }
sl@0
   750
sl@0
   751
    if (child == (Node *)group   ||
sl@0
   752
        (child->parent != NULL && child->parent != (Node *)group) ||
sl@0
   753
        m3gIsChildOf(child, (Node *)group) ||
sl@0
   754
        m3gGetClass((Object *) child) == M3G_CLASS_WORLD) {
sl@0
   755
        m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_VALUE);
sl@0
   756
        return;
sl@0
   757
    }
sl@0
   758
sl@0
   759
    if (child->parent == NULL) {
sl@0
   760
        m3gLinkChild(child, group);
sl@0
   761
    }
sl@0
   762
}
sl@0
   763
sl@0
   764
/*!
sl@0
   765
 * \brief Removes a node from this group.
sl@0
   766
 *
sl@0
   767
 * \param handle        Group object
sl@0
   768
 * \param hNode         Node object
sl@0
   769
 */
sl@0
   770
M3G_API void m3gRemoveChild(M3GGroup handle, M3GNode hNode)
sl@0
   771
{
sl@0
   772
    Group *group = (Group *) handle;
sl@0
   773
	Node *child = (Node *)hNode;
sl@0
   774
    M3G_VALIDATE_OBJECT(group);
sl@0
   775
sl@0
   776
    if (child == NULL) {
sl@0
   777
        return;
sl@0
   778
    }
sl@0
   779
sl@0
   780
    if (child->hasBones == M3G_TRUE) {
sl@0
   781
        m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_VALUE);
sl@0
   782
        return;
sl@0
   783
    }
sl@0
   784
    
sl@0
   785
    if (group->firstChild == NULL) {
sl@0
   786
        return;
sl@0
   787
    }
sl@0
   788
sl@0
   789
    m3gDetachChild(child, group);
sl@0
   790
}
sl@0
   791
sl@0
   792
/*!
sl@0
   793
 * \brief Performs 3D pick.
sl@0
   794
 *
sl@0
   795
 * \param handle        Group object
sl@0
   796
 * \param mask          pick scope mask
sl@0
   797
 * \param ray           pick ray
sl@0
   798
 * \arg ray[0]          origin X
sl@0
   799
 * \arg ray[1]          origin Y
sl@0
   800
 * \arg ray[2]          origin Z
sl@0
   801
 * \arg ray[3]          direction X
sl@0
   802
 * \arg ray[4]          direction Y
sl@0
   803
 * \arg ray[5]          direction Z
sl@0
   804
 * \param result        java side RayIntersection result
sl@0
   805
 * \arg result[0]       distance
sl@0
   806
 * \arg result[1]       submesh index
sl@0
   807
 * \arg result[2]       textureS[0]
sl@0
   808
 * \arg result[3]       textureS[1]
sl@0
   809
 * \arg result[4]       textureT[0]
sl@0
   810
 * \arg result[5]       textureT[1]
sl@0
   811
 * \arg result[6]       normal X
sl@0
   812
 * \arg result[7]       normal Y
sl@0
   813
 * \arg result[8]       normal Z
sl@0
   814
 * \arg result[9]       ray ox
sl@0
   815
 * \arg result[10]      ray oy
sl@0
   816
 * \arg result[11]      ray oz
sl@0
   817
 * \arg result[12]      ray dx
sl@0
   818
 * \arg result[13]      ray dy
sl@0
   819
 * \arg result[14]      ray dz
sl@0
   820
 * \return              intersected Node object
sl@0
   821
 */
sl@0
   822
sl@0
   823
#ifdef M3G_ENABLE_PROFILING
sl@0
   824
static M3GNode m3gPick3DInternal(M3GGroup handle,
sl@0
   825
                          M3Gint mask,
sl@0
   826
                          M3Gfloat *ray,
sl@0
   827
                          M3Gfloat *result);
sl@0
   828
sl@0
   829
M3G_API M3GNode m3gPick3D(M3GGroup handle,
sl@0
   830
                          M3Gint mask,
sl@0
   831
                          M3Gfloat *ray,
sl@0
   832
                          M3Gfloat *result)
sl@0
   833
{
sl@0
   834
    M3GNode pickResult;
sl@0
   835
    M3G_BEGIN_PROFILE(M3G_INTERFACE(handle), M3G_PROFILE_PICK);
sl@0
   836
    pickResult = m3gPick3DInternal(handle, mask, ray, result);
sl@0
   837
    M3G_END_PROFILE(M3G_INTERFACE(handle), M3G_PROFILE_PICK);
sl@0
   838
    return pickResult;
sl@0
   839
}
sl@0
   840
sl@0
   841
static M3GNode m3gPick3DInternal(M3GGroup handle,
sl@0
   842
                          M3Gint mask,
sl@0
   843
                          M3Gfloat *ray,
sl@0
   844
                          M3Gfloat *result)
sl@0
   845
#else
sl@0
   846
M3G_API M3GNode m3gPick3D(M3GGroup handle,
sl@0
   847
                          M3Gint mask,
sl@0
   848
                          M3Gfloat *ray,
sl@0
   849
                          M3Gfloat *result)
sl@0
   850
sl@0
   851
#endif
sl@0
   852
{
sl@0
   853
    RayIntersection ri;
sl@0
   854
    Matrix toGroup;
sl@0
   855
    Group *group = (Group *) handle;
sl@0
   856
    M3G_VALIDATE_OBJECT(group);
sl@0
   857
sl@0
   858
    M3G_LOG1(M3G_LOG_STAGES, "Picking group 0x%08X\n", (unsigned) group);
sl@0
   859
    
sl@0
   860
    /* Check for errors */
sl@0
   861
    if (ray[3] == 0 && ray[4] == 0 && ray[5] == 0) {
sl@0
   862
        m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_VALUE);
sl@0
   863
        return NULL;
sl@0
   864
    }
sl@0
   865
    if (!m3gValidateNode((Node*) group, NODE_PICK_BIT, mask)) {
sl@0
   866
        return NULL;
sl@0
   867
    }
sl@0
   868
        
sl@0
   869
    m3gInitPick(&ri, (Node *)group, NULL, 0, 0);
sl@0
   870
    m3gIdentityMatrix(&toGroup);
sl@0
   871
sl@0
   872
    ray[3] = m3gAdd(ray[3], ray[0]);
sl@0
   873
    ray[4] = m3gAdd(ray[4], ray[1]);
sl@0
   874
    ray[5] = m3gAdd(ray[5], ray[2]);
sl@0
   875
sl@0
   876
    M3G_VFUNC(Node, group, rayIntersect)(   (Node *)group,
sl@0
   877
                                            mask,
sl@0
   878
                                            ray,
sl@0
   879
                                            &ri,
sl@0
   880
                                            &toGroup);
sl@0
   881
    m3gFillPickResult(&ri, ray, result);
sl@0
   882
    return ri.intersected;
sl@0
   883
}
sl@0
   884
sl@0
   885
/*!
sl@0
   886
 * \brief Performs 2D pick.
sl@0
   887
 *
sl@0
   888
 * \param handle        Group object
sl@0
   889
 * \param mask          pick scope mask
sl@0
   890
 * \param x             viewport x
sl@0
   891
 * \param y             viewport y
sl@0
   892
 * \param hCamera       Camera object
sl@0
   893
 * \param result        java side RayIntersection result, see m3gPick3D
sl@0
   894
 * \return              intersected Node object
sl@0
   895
 */
sl@0
   896
sl@0
   897
#ifdef M3G_ENABLE_PROFILING
sl@0
   898
static M3GNode m3gPick2DInternal(M3GGroup handle,
sl@0
   899
                          M3Gint mask,
sl@0
   900
                          M3Gfloat x, M3Gfloat y,
sl@0
   901
                          M3GCamera hCamera,
sl@0
   902
                          M3Gfloat *result);
sl@0
   903
sl@0
   904
M3G_API M3GNode m3gPick2D(M3GGroup handle,
sl@0
   905
                          M3Gint mask,
sl@0
   906
                          M3Gfloat x, M3Gfloat y,
sl@0
   907
                          M3GCamera hCamera,
sl@0
   908
                          M3Gfloat *result)
sl@0
   909
{
sl@0
   910
    M3GNode pickResult;
sl@0
   911
    M3G_BEGIN_PROFILE(M3G_INTERFACE(handle), M3G_PROFILE_PICK);
sl@0
   912
    pickResult = m3gPick2DInternal(handle, mask, x, y, hCamera, result);
sl@0
   913
    M3G_END_PROFILE(M3G_INTERFACE(handle), M3G_PROFILE_PICK);
sl@0
   914
    return pickResult;
sl@0
   915
}
sl@0
   916
sl@0
   917
static M3GNode m3gPick2DInternal(M3GGroup handle,
sl@0
   918
                          M3Gint mask,
sl@0
   919
                          M3Gfloat x, M3Gfloat y,
sl@0
   920
                          M3GCamera hCamera,
sl@0
   921
                          M3Gfloat *result)
sl@0
   922
#else
sl@0
   923
M3G_API M3GNode m3gPick2D(M3GGroup handle,
sl@0
   924
                          M3Gint mask,
sl@0
   925
                          M3Gfloat x, M3Gfloat y,
sl@0
   926
                          M3GCamera hCamera,
sl@0
   927
                          M3Gfloat *result)
sl@0
   928
#endif
sl@0
   929
{
sl@0
   930
    Vec4 farp, nearp;
sl@0
   931
    RayIntersection ri;
sl@0
   932
    Matrix toGroup;
sl@0
   933
    M3Gfloat ray[6 + 2];    /* Extra floats to store near and far plane z */
sl@0
   934
    Node *root;
sl@0
   935
    Group *group = (Group *) handle;    
sl@0
   936
sl@0
   937
    M3G_LOG2(M3G_LOG_STAGES, "Picking group 0x%08X via camera 0x%08X\n",
sl@0
   938
             (unsigned) group, (unsigned) hCamera);
sl@0
   939
    
sl@0
   940
    M3G_VALIDATE_OBJECT(group);
sl@0
   941
sl@0
   942
    if (hCamera == 0) {
sl@0
   943
        m3gRaiseError(M3G_INTERFACE(group), M3G_NULL_POINTER);
sl@0
   944
        return NULL;
sl@0
   945
    }
sl@0
   946
sl@0
   947
    root = m3gGetRoot((Node *)hCamera);
sl@0
   948
sl@0
   949
    if (root != m3gGetRoot(&group->node)) {
sl@0
   950
        m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_OPERATION);
sl@0
   951
        return NULL;
sl@0
   952
    }
sl@0
   953
    if (!m3gValidateNode(root, NODE_PICK_BIT, mask)) {
sl@0
   954
        return NULL;
sl@0
   955
    }
sl@0
   956
sl@0
   957
    farp.x = m3gSub(m3gMul(2, x), 1.f);
sl@0
   958
    farp.y = m3gSub(1.f, m3gMul(2, y));
sl@0
   959
    farp.z = 1.f;
sl@0
   960
    farp.w = 1.f;
sl@0
   961
sl@0
   962
    nearp.x = farp.x;
sl@0
   963
    nearp.y = farp.y;
sl@0
   964
    nearp.z = -1.f;
sl@0
   965
    nearp.w =  1.f;
sl@0
   966
sl@0
   967
    m3gCopyMatrix(&toGroup, m3gProjectionMatrix((Camera *)hCamera));
sl@0
   968
sl@0
   969
    M3G_BEGIN_PROFILE(M3G_INTERFACE(group), M3G_PROFILE_TRANSFORM_INVERT);
sl@0
   970
    if (!m3gInvertMatrix(&toGroup)) {
sl@0
   971
        m3gRaiseError(M3G_INTERFACE(group), M3G_ARITHMETIC_ERROR);
sl@0
   972
        return NULL;
sl@0
   973
    }
sl@0
   974
    M3G_END_PROFILE(M3G_INTERFACE(group), M3G_PROFILE_TRANSFORM_INVERT);
sl@0
   975
sl@0
   976
    m3gTransformVec4(&toGroup, &nearp);
sl@0
   977
    m3gTransformVec4(&toGroup, &farp);
sl@0
   978
sl@0
   979
    m3gScaleVec4(&nearp, m3gRcp(nearp.w));
sl@0
   980
    m3gScaleVec4(&farp, m3gRcp(farp.w));
sl@0
   981
sl@0
   982
    /* Store near and far plane z for sprite picking */
sl@0
   983
    ray[6] = nearp.z;
sl@0
   984
    ray[7] = farp.z;
sl@0
   985
sl@0
   986
    if (!m3gGetTransformTo((M3GNode) hCamera, (Node *) group, &toGroup)) {
sl@0
   987
        return NULL;
sl@0
   988
    }
sl@0
   989
sl@0
   990
    m3gTransformVec4(&toGroup, &nearp);
sl@0
   991
    m3gTransformVec4(&toGroup, &farp);
sl@0
   992
sl@0
   993
    m3gScaleVec4(&nearp, m3gRcp(nearp.w));
sl@0
   994
    m3gScaleVec4(&farp, m3gRcp(farp.w));
sl@0
   995
sl@0
   996
    ray[0] = nearp.x;
sl@0
   997
    ray[1] = nearp.y;
sl@0
   998
    ray[2] = nearp.z;
sl@0
   999
    ray[3] = farp.x;
sl@0
  1000
    ray[4] = farp.y;
sl@0
  1001
    ray[5] = farp.z;
sl@0
  1002
sl@0
  1003
sl@0
  1004
    m3gInitPick(&ri, (Node *)group, (Camera *)hCamera, x, y);
sl@0
  1005
    m3gIdentityMatrix(&toGroup);
sl@0
  1006
sl@0
  1007
    M3G_VFUNC(Node, group, rayIntersect)((Node *)group, mask, ray, &ri, &toGroup);
sl@0
  1008
sl@0
  1009
    m3gFillPickResult(&ri, ray, result);
sl@0
  1010
    return ri.intersected;
sl@0
  1011
}
sl@0
  1012
sl@0
  1013
/*!
sl@0
  1014
 * \brief Gets a child.
sl@0
  1015
 *
sl@0
  1016
 * \param handle        Group object
sl@0
  1017
 * \param idx           child index
sl@0
  1018
 * \return              Node object
sl@0
  1019
 */
sl@0
  1020
M3G_API M3GNode m3gGetChild(M3GGroup handle, M3Gint idx)
sl@0
  1021
{
sl@0
  1022
    Node *n;
sl@0
  1023
    Group *group = (Group *) handle;
sl@0
  1024
    M3G_VALIDATE_OBJECT(group);
sl@0
  1025
sl@0
  1026
    if (idx < 0) {
sl@0
  1027
        goto InvalidIndex;
sl@0
  1028
    }
sl@0
  1029
sl@0
  1030
	n = group->firstChild;
sl@0
  1031
sl@0
  1032
    while (idx-- > 0) {
sl@0
  1033
        n = n->right;
sl@0
  1034
        if (n == group->firstChild) {
sl@0
  1035
            goto InvalidIndex;
sl@0
  1036
        }
sl@0
  1037
    }
sl@0
  1038
    return n;
sl@0
  1039
sl@0
  1040
    InvalidIndex:
sl@0
  1041
    m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_INDEX);
sl@0
  1042
    return NULL;
sl@0
  1043
}
sl@0
  1044
sl@0
  1045
/*!
sl@0
  1046
 * \brief Gets children count.
sl@0
  1047
 *
sl@0
  1048
 * \param handle        Group object
sl@0
  1049
 * \return              children count
sl@0
  1050
 */
sl@0
  1051
M3G_API M3Gint m3gGetChildCount(M3GGroup handle)
sl@0
  1052
{
sl@0
  1053
    Group *group = (Group *) handle;
sl@0
  1054
    M3G_VALIDATE_OBJECT(group);
sl@0
  1055
    {
sl@0
  1056
        M3Gint count = 0;
sl@0
  1057
        const Node *child = group->firstChild;
sl@0
  1058
        if (child) {
sl@0
  1059
            do {
sl@0
  1060
                ++count;
sl@0
  1061
                child = child->right;
sl@0
  1062
            } while (child != group->firstChild);
sl@0
  1063
        }
sl@0
  1064
        return count;
sl@0
  1065
    }
sl@0
  1066
}
sl@0
  1067