Update contrib.
2 * Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies).
4 * This component and the accompanying materials are made available
5 * under the terms of the License "Eclipse Public License v1.0"
6 * which accompanies this distribution, and is available
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
9 * Initial Contributors:
10 * Nokia Corporation - initial contribution.
14 * Description: Group implementation
22 * \brief Group implementation
25 #ifndef M3G_CORE_INCLUDE
26 # error included by m3g_core.c; do not compile separately.
29 #include "m3g_group.h"
30 #include "m3g_memory.h"
32 /*----------------------------------------------------------------------
34 *--------------------------------------------------------------------*/
38 * \brief Links a new child into the child list of this node.
40 * This assumes that all error checking has been done prior to calling
41 * the function, and the operation is a valid one.
43 * \param child Node object
44 * \param group Group object
46 static void m3gLinkChild(Node *child, Group *group)
48 M3G_VALIDATE_OBJECT(child);
49 M3G_VALIDATE_OBJECT(group);
51 if (group->firstChild == NULL) {
52 group->firstChild = child;
57 Node *linkChild = group->firstChild;
59 child->left = linkChild->left;
60 linkChild->left->right = child;
62 child->right = linkChild;
63 linkChild->left = child;
65 m3gSetParent(child, (Node *) group);
70 * \brief Removes a child from the child list of this node.
72 * This assumes that all error checking has been done prior to calling
73 * the function, and the operation is a valid one.
75 * \param child Node object
76 * \param group Group object
78 static void m3gDetachChild(Node *child, Group *group)
81 M3G_VALIDATE_OBJECT(child);
82 M3G_VALIDATE_OBJECT(group);
84 n = group->firstChild;
88 M3G_VALIDATE_OBJECT(child->right);
89 M3G_VALIDATE_OBJECT(child->left);
91 n->right->left = n->left;
92 n->left->right = n->right;
94 if (group->firstChild == n) {
95 group->firstChild = (n->right != n) ? n->right : NULL;
100 m3gSetParent(n, NULL);
104 } while (n != group->firstChild);
109 * \brief Destroys this Group object.
111 * \param obj Group object
113 static void m3gDestroyGroup(Object *obj)
115 /* Release child references so they can be deleted */
117 Group *group = (Group *) obj;
118 while (group->firstChild != NULL) {
119 m3gDetachChild(group->firstChild, group);
121 # if defined(M3G_ENABLE_VF_CULLING)
123 m3gFree(M3G_INTERFACE(group), group->bbox);
124 m3gIncStat(M3G_INTERFACE(group), M3G_STAT_BOUNDING_BOXES, -1);
132 * \brief Overloaded Node method.
134 * \param self Group object
135 * \param refNode alignment reference Node object
137 * \retval M3G_TRUE continue align
138 * \retval M3G_FALSE abort align
140 static M3Gbool m3gGroupAlign(Node *self, const Node *refNode)
142 Group *group = (Group *)self;
143 Node *child = group->firstChild;
145 if (!m3gNodeAlign(self, refNode)) {
151 if (!M3G_VFUNC(Node, child, align)(child, refNode)) {
154 child = child->right;
155 } while (child != group->firstChild);
163 * \brief Overloaded Node method.
165 * Setup group rendering by calling child
166 * nodes' render setup.
168 * \param self Group object
169 * \param toCamera transform to camera
170 * \param alphaFactor total alpha factor
171 * \param caller caller node
172 * \param renderQueue RenderQueue
174 * \retval M3G_TRUE continue render setup
175 * \retval M3G_FALSE abort render setup
177 static M3Gbool m3gGroupSetupRender(Node *self,
180 RenderQueue *renderQueue)
182 Group *group = (Group *)self;
183 M3Gbool enabled, success = M3G_TRUE;
185 /* Check whether we're going up or down, and optimize the
186 * rendering-enabled and visibility checking based on that */
188 enabled = (self->enableBits & NODE_RENDER_BIT) != 0;
189 if (caller != self->parent) {
190 enabled = m3gHasEnabledPath(self, renderQueue->root);
191 s->cullMask = CULLMASK_ALL;
193 M3G_ASSERT(!self->dirtyBits || !enabled);
195 /* First do the child nodes, unless disabled (inheritable, so
196 * children would be, too) */
198 if (enabled && (group->numNonCullables > 0 || group->numRenderables > 0)) {
200 Node *child = group->firstChild;
203 /* Check the bounding box if we have one */
205 # if defined(M3G_ENABLE_VF_CULLING)
207 m3gValidateAABB(group->bbox);
208 m3gUpdateCullingMask(s, renderQueue->camera, group->bbox);
212 /* If we're not culled, or if we carry lights, we really
213 * need to recurse into each child node */
215 if (s->cullMask || group->numNonCullables > 0) {
217 if (child != caller) {
219 cs.cullMask = s->cullMask;
221 M3G_BEGIN_PROFILE(M3G_INTERFACE(group),
222 M3G_PROFILE_SETUP_TRANSFORMS);
223 m3gGetCompositeNodeTransform(child, &cs.toCamera);
224 m3gPreMultiplyMatrix(&cs.toCamera, &s->toCamera);
225 M3G_END_PROFILE(M3G_INTERFACE(group),
226 M3G_PROFILE_SETUP_TRANSFORMS);
228 if (!M3G_VFUNC(Node, child, setupRender)(
229 child, self, &cs, renderQueue)) {
233 child = child->right;
234 } while (child != group->firstChild);
237 M3GInterface m3g = M3G_INTERFACE(group);
238 M3Gint n = group->numRenderables;
239 m3gIncStat(m3g, M3G_STAT_RENDER_NODES, n);
240 m3gIncStat(m3g, M3G_STAT_RENDER_NODES_CULLED, n);
245 /* Then do the parent node if we're going up the tree. Again, we
246 * can discard the old traversal state at this point. */
248 if (self != renderQueue->root) {
249 Node *parent = self->parent;
251 if (parent != caller && parent != NULL) {
254 M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS);
255 if (!m3gGetInverseNodeTransform(self, &t)) {
258 m3gMulMatrix(&s->toCamera, &t);
259 M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_SETUP_TRANSFORMS);
261 success = M3G_VFUNC(Node, parent, setupRender)(parent,
273 * \brief Overloaded Object3D method.
275 * \param self Group object
276 * \param time current world time
277 * \return minimum validity
279 static M3Gint m3gGroupApplyAnimation(Object *self, M3Gint time)
281 M3Gint validity, minValidity;
283 Group *group = (Group *)self;
284 M3G_VALIDATE_OBJECT(group);
286 minValidity = m3gObjectApplyAnimation(self, time);
288 child = group->firstChild;
289 if (child && minValidity > 0) {
291 validity = M3G_VFUNC(Object, child, applyAnimation)(
292 (Object *)child, time);
293 minValidity = validity < minValidity ? validity : minValidity;
294 child = child->right;
295 } while (minValidity > 0 && child != group->firstChild);
302 * \brief Overloaded Node method.
304 * Call child nodes' ray intersect.
306 * \param self Group object
307 * \param mask pick scope mask
308 * \param ray pick ray
309 * \param ri RayIntersection object
310 * \param toGroup transform to originating group
311 * \retval M3G_TRUE continue pick
312 * \retval M3G_FALSE abort pick
314 static M3Gbool m3gGroupRayIntersect(Node *self,
320 Group *group = (Group *)self;
324 m3gIdentityMatrix(&t);
325 m3gIdentityMatrix(&nt);
327 child = group->firstChild;
330 if (m3gHasPickablePath(child, ri->root)) {
331 m3gCopyMatrix(&t, toGroup);
332 m3gGetCompositeNodeTransform(child, &nt);
333 m3gRightMulMatrix(&t, &nt);
335 if (!M3G_VFUNC(Node, child, rayIntersect)(
336 child, mask, ray, ri, &t)) {
340 child = child->right;
341 } while (child != group->firstChild);
349 * \brief Initializes pick traversing.
351 * \param ri RayIntersection object
352 * \param root Root node for the traversing
353 * \param camera Camera object used in pick (2D pick only)
354 * \param x viewport x (2D pick only)
355 * \param y viewport y (2D pick only)
357 static void m3gInitPick(RayIntersection *ri, Node *root, Camera *camera, M3Gfloat x, M3Gfloat y)
359 m3gZero(ri, sizeof(*ri));
365 ri->tMin = M3G_MAX_POSITIVE_FLOAT;
370 * \brief Fills Java side RayIntersection result.
372 * \param ri RayIntersection object
373 * \param ray Ray used in pick
374 * \param result Java side float array
376 static void m3gFillPickResult(RayIntersection *ri, M3Gfloat *ray, M3Gfloat *result)
378 if (ri->intersected != NULL) {
381 /* Fill in the values */
382 result[0] = ri->distance;
383 result[1] = (M3Gfloat)ri->submeshIndex;
384 result[2] = ri->textureS[0];
385 result[3] = ri->textureS[1];
386 result[4] = ri->textureT[0];
387 result[5] = ri->textureT[1];
389 /* Normalize normal */
393 m3gNormalizeVec3(&n);
402 result[12] = m3gSub(ray[3], ray[0]);
403 result[13] = m3gSub(ray[4], ray[1]);
404 result[14] = m3gSub(ray[5], ray[2]);
410 * \brief Overloaded Object3D method.
412 * \param self Group object
413 * \param references array of reference objects
414 * \return number of references
416 static M3Gint m3gGroupDoGetReferences(Object *self, Object **references)
418 Group *group = (Group *)self;
419 M3Gint num = m3gObjectDoGetReferences(self, references);
420 Node *child = group->firstChild;
423 if (references != NULL)
424 references[num] = (Object *)child;
425 child = child->right;
427 } while (child != group->firstChild);
434 * \brief Overloaded Object3D method.
436 * \param self Group object
437 * \param references array of reference objects
438 * \return number of references
440 static Object *m3gGroupFindID(Object *self, M3Gint userID)
442 Group *group = (Group *)self;
443 Object *found = m3gObjectFindID(self, userID);
445 Node *child = group->firstChild;
446 if (child && !found) {
448 found = m3gFindID((Object*) child, userID);
449 child = child->right;
450 } while (!found && child != group->firstChild);
457 * \brief Overloaded Object3D method.
459 * \param originalObj original Group object
460 * \param cloneObj pointer to cloned Group object
461 * \param pairs array for all object-duplicate pairs
462 * \param numPairs number of pairs
464 static M3Gbool m3gGroupDuplicate(const Object *originalObj,
470 Group *original = (Group *)originalObj;
473 /* Create the clone object, unless already created in a derived
476 if (*cloneObj == NULL) {
477 clone = (Group *)m3gCreateGroup(originalObj->interface);
479 return M3G_FALSE; /* out of memory */
481 *cloneObj = (Object *)clone;
484 clone = (Group *)*cloneObj;
487 /* Call base class function to duplicate base class data */
489 if (!m3gNodeDuplicate(originalObj, cloneObj, pairs, numPairs)) {
490 return M3G_FALSE; /* out of memory; caller will delete us */
493 /* Duplicate child nodes. */
495 child = original->firstChild;
499 if (!M3G_VFUNC(Object, child, duplicate)(
500 (Object *)child, (Object**)&temp, pairs, numPairs)) {
501 m3gDeleteObject((Object*) temp); /* we have the only reference */
504 m3gAddChild(clone, temp);
505 child = child->right;
506 } while (child != original->firstChild);
514 * \brief Overloaded Node method
516 static M3Gint m3gGroupGetBBox(Node *self, AABB *bbox)
518 Group *group = (Group*) self;
520 /* Quick exit for empty volumes */
522 if (!group->firstChild || !self->hasRenderables) {
526 /* Assume our existing bbox is ok, but compute a new one if it
529 if (group->bbox && !(self->dirtyBits & NODE_BBOX_BIT)) {
530 *bbox = *group->bbox;
534 /* Compute local bounding box by recursively merging the
535 * bounding boxes of all renderable child nodes */
537 Node *child = group->firstChild;
538 M3Gint groupYield = 0;
541 if (child->hasRenderables && child->enableBits) {
543 /* Get the transformation for the child node, then
544 * update our existing state with its bounding box */
550 childYield = m3gGetNodeBBox(child, &childBBox);
551 if (childYield > 0) {
552 m3gGetCompositeNodeTransform(child, &t);
553 m3gTransformAABB(&childBBox, &t);
556 m3gFitAABB(bbox, &childBBox, bbox);
561 groupYield += childYield;
564 child = child->right;
565 } while (child != group->firstChild);
567 /* Store the updated bbox locally if we have one, or return
568 * the combined child yield factor if we don't */
571 *group->bbox = *bbox;
574 return (groupYield > 0) ? groupYield + VFC_NODE_OVERHEAD : 0;
577 return VFC_BBOX_COST + VFC_NODE_OVERHEAD;
582 * \brief Overloaded Node method
584 static M3Gbool m3gGroupValidate(Node *self, M3Gbitmask stateBits, M3Gint scope)
586 Group *group = (Group*) self;
588 if (stateBits & self->enableBits) {
590 /* First validate child nodes to ensure we don't skip anything,
591 * and allow children to invalidate our state */
593 Node *child = group->firstChild;
596 if (!m3gValidateNode(child, stateBits, scope)) {
599 child = child->right;
600 } while (child != group->firstChild);
603 /* Re-evaluate our local bounding box if necessary */
605 if (self->hasRenderables && self->dirtyBits & NODE_BBOX_BIT) {
608 M3G_BEGIN_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_VFC_UPDATE);
610 yield = m3gGetNodeBBox(self, &bbox);
612 /* Think about adding a bounding box if we don't yet have one,
613 * or removing the current one if it doesn't seem worth it */
616 if (yield > (3*VFC_BBOX_COST) >> 1) {
617 group->bbox = m3gAlloc(M3G_INTERFACE(group),
618 sizeof(*group->bbox));
620 m3gIncStat(M3G_INTERFACE(group),
621 M3G_STAT_BOUNDING_BOXES, 1);
629 else if (yield <= VFC_BBOX_COST) {
630 m3gFree(M3G_INTERFACE(group), group->bbox);
632 m3gIncStat(M3G_INTERFACE(group), M3G_STAT_BOUNDING_BOXES, -1);
634 M3G_END_PROFILE(M3G_INTERFACE(self), M3G_PROFILE_VFC_UPDATE);
636 return m3gNodeValidate(self, stateBits, scope);
643 * \brief Overloaded Node method.
645 * \param self Group object
646 * \param pairs array for all object-duplicate pairs
647 * \param numPairs number of pairs
649 static void m3gGroupUpdateDuplicateReferences(Node *self, Object **pairs, M3Gint numPairs)
651 Group *group = (Group *)self;
652 Node *child = group->firstChild;
654 m3gNodeUpdateDuplicateReferences(self, pairs, numPairs);
658 M3G_VFUNC(Node, child, updateDuplicateReferences)(
659 child, pairs, numPairs);
660 child = child->right;
661 } while (child != group->firstChild);
667 * \brief Initializes a Group object. See specification
668 * for default values.
670 * \param m3g M3G interface
671 * \param group Group object
672 * \param vfTable virtual function table
674 static void m3gInitGroup(Interface *m3g, Group *group, M3GClass classID)
676 /* Group is derived from Node */
677 m3gInitNode(m3g, &group->node, classID);
680 /*----------------------------------------------------------------------
681 * Virtual function table
682 *--------------------------------------------------------------------*/
684 static const NodeVFTable m3gvf_Group = {
687 m3gGroupApplyAnimation,
689 m3gNodeUpdateProperty,
690 m3gGroupDoGetReferences,
697 NULL, /* pure virtual m3gNodeDoRender */
699 m3gGroupRayIntersect,
701 m3gGroupUpdateDuplicateReferences,
706 /*----------------------------------------------------------------------
707 * Public API functions
708 *--------------------------------------------------------------------*/
711 * \brief Creates a Group object.
713 * \param interface M3G interface
714 * \retval Group new Group object
715 * \retval NULL Group creating failed
717 M3G_API M3GGroup m3gCreateGroup(M3GInterface interface)
719 Interface *m3g = (Interface *) interface;
720 M3G_VALIDATE_INTERFACE(m3g);
723 Group *group = m3gAllocZ(m3g, sizeof(Group));
726 m3gInitGroup(m3g, group, M3G_CLASS_GROUP);
729 return (M3GGroup) group;
734 * \brief Adds a node to this group.
736 * \param handle Group object
737 * \param hNode Node object
739 M3G_API void m3gAddChild(M3GGroup handle, M3GNode hNode)
741 Group *group = (Group *) handle;
742 Node *child = (Node *) hNode;
744 M3G_VALIDATE_OBJECT(group);
747 m3gRaiseError(M3G_INTERFACE(group), M3G_NULL_POINTER);
751 if (child == (Node *)group ||
752 (child->parent != NULL && child->parent != (Node *)group) ||
753 m3gIsChildOf(child, (Node *)group) ||
754 m3gGetClass((Object *) child) == M3G_CLASS_WORLD) {
755 m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_VALUE);
759 if (child->parent == NULL) {
760 m3gLinkChild(child, group);
765 * \brief Removes a node from this group.
767 * \param handle Group object
768 * \param hNode Node object
770 M3G_API void m3gRemoveChild(M3GGroup handle, M3GNode hNode)
772 Group *group = (Group *) handle;
773 Node *child = (Node *)hNode;
774 M3G_VALIDATE_OBJECT(group);
780 if (child->hasBones == M3G_TRUE) {
781 m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_VALUE);
785 if (group->firstChild == NULL) {
789 m3gDetachChild(child, group);
793 * \brief Performs 3D pick.
795 * \param handle Group object
796 * \param mask pick scope mask
797 * \param ray pick ray
798 * \arg ray[0] origin X
799 * \arg ray[1] origin Y
800 * \arg ray[2] origin Z
801 * \arg ray[3] direction X
802 * \arg ray[4] direction Y
803 * \arg ray[5] direction Z
804 * \param result java side RayIntersection result
805 * \arg result[0] distance
806 * \arg result[1] submesh index
807 * \arg result[2] textureS[0]
808 * \arg result[3] textureS[1]
809 * \arg result[4] textureT[0]
810 * \arg result[5] textureT[1]
811 * \arg result[6] normal X
812 * \arg result[7] normal Y
813 * \arg result[8] normal Z
814 * \arg result[9] ray ox
815 * \arg result[10] ray oy
816 * \arg result[11] ray oz
817 * \arg result[12] ray dx
818 * \arg result[13] ray dy
819 * \arg result[14] ray dz
820 * \return intersected Node object
823 #ifdef M3G_ENABLE_PROFILING
824 static M3GNode m3gPick3DInternal(M3GGroup handle,
829 M3G_API M3GNode m3gPick3D(M3GGroup handle,
835 M3G_BEGIN_PROFILE(M3G_INTERFACE(handle), M3G_PROFILE_PICK);
836 pickResult = m3gPick3DInternal(handle, mask, ray, result);
837 M3G_END_PROFILE(M3G_INTERFACE(handle), M3G_PROFILE_PICK);
841 static M3GNode m3gPick3DInternal(M3GGroup handle,
846 M3G_API M3GNode m3gPick3D(M3GGroup handle,
855 Group *group = (Group *) handle;
856 M3G_VALIDATE_OBJECT(group);
858 M3G_LOG1(M3G_LOG_STAGES, "Picking group 0x%08X\n", (unsigned) group);
860 /* Check for errors */
861 if (ray[3] == 0 && ray[4] == 0 && ray[5] == 0) {
862 m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_VALUE);
865 if (!m3gValidateNode((Node*) group, NODE_PICK_BIT, mask)) {
869 m3gInitPick(&ri, (Node *)group, NULL, 0, 0);
870 m3gIdentityMatrix(&toGroup);
872 ray[3] = m3gAdd(ray[3], ray[0]);
873 ray[4] = m3gAdd(ray[4], ray[1]);
874 ray[5] = m3gAdd(ray[5], ray[2]);
876 M3G_VFUNC(Node, group, rayIntersect)( (Node *)group,
881 m3gFillPickResult(&ri, ray, result);
882 return ri.intersected;
886 * \brief Performs 2D pick.
888 * \param handle Group object
889 * \param mask pick scope mask
890 * \param x viewport x
891 * \param y viewport y
892 * \param hCamera Camera object
893 * \param result java side RayIntersection result, see m3gPick3D
894 * \return intersected Node object
897 #ifdef M3G_ENABLE_PROFILING
898 static M3GNode m3gPick2DInternal(M3GGroup handle,
900 M3Gfloat x, M3Gfloat y,
904 M3G_API M3GNode m3gPick2D(M3GGroup handle,
906 M3Gfloat x, M3Gfloat y,
911 M3G_BEGIN_PROFILE(M3G_INTERFACE(handle), M3G_PROFILE_PICK);
912 pickResult = m3gPick2DInternal(handle, mask, x, y, hCamera, result);
913 M3G_END_PROFILE(M3G_INTERFACE(handle), M3G_PROFILE_PICK);
917 static M3GNode m3gPick2DInternal(M3GGroup handle,
919 M3Gfloat x, M3Gfloat y,
923 M3G_API M3GNode m3gPick2D(M3GGroup handle,
925 M3Gfloat x, M3Gfloat y,
933 M3Gfloat ray[6 + 2]; /* Extra floats to store near and far plane z */
935 Group *group = (Group *) handle;
937 M3G_LOG2(M3G_LOG_STAGES, "Picking group 0x%08X via camera 0x%08X\n",
938 (unsigned) group, (unsigned) hCamera);
940 M3G_VALIDATE_OBJECT(group);
943 m3gRaiseError(M3G_INTERFACE(group), M3G_NULL_POINTER);
947 root = m3gGetRoot((Node *)hCamera);
949 if (root != m3gGetRoot(&group->node)) {
950 m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_OPERATION);
953 if (!m3gValidateNode(root, NODE_PICK_BIT, mask)) {
957 farp.x = m3gSub(m3gMul(2, x), 1.f);
958 farp.y = m3gSub(1.f, m3gMul(2, y));
967 m3gCopyMatrix(&toGroup, m3gProjectionMatrix((Camera *)hCamera));
969 M3G_BEGIN_PROFILE(M3G_INTERFACE(group), M3G_PROFILE_TRANSFORM_INVERT);
970 if (!m3gInvertMatrix(&toGroup)) {
971 m3gRaiseError(M3G_INTERFACE(group), M3G_ARITHMETIC_ERROR);
974 M3G_END_PROFILE(M3G_INTERFACE(group), M3G_PROFILE_TRANSFORM_INVERT);
976 m3gTransformVec4(&toGroup, &nearp);
977 m3gTransformVec4(&toGroup, &farp);
979 m3gScaleVec4(&nearp, m3gRcp(nearp.w));
980 m3gScaleVec4(&farp, m3gRcp(farp.w));
982 /* Store near and far plane z for sprite picking */
986 if (!m3gGetTransformTo((M3GNode) hCamera, (Node *) group, &toGroup)) {
990 m3gTransformVec4(&toGroup, &nearp);
991 m3gTransformVec4(&toGroup, &farp);
993 m3gScaleVec4(&nearp, m3gRcp(nearp.w));
994 m3gScaleVec4(&farp, m3gRcp(farp.w));
1004 m3gInitPick(&ri, (Node *)group, (Camera *)hCamera, x, y);
1005 m3gIdentityMatrix(&toGroup);
1007 M3G_VFUNC(Node, group, rayIntersect)((Node *)group, mask, ray, &ri, &toGroup);
1009 m3gFillPickResult(&ri, ray, result);
1010 return ri.intersected;
1014 * \brief Gets a child.
1016 * \param handle Group object
1017 * \param idx child index
1018 * \return Node object
1020 M3G_API M3GNode m3gGetChild(M3GGroup handle, M3Gint idx)
1023 Group *group = (Group *) handle;
1024 M3G_VALIDATE_OBJECT(group);
1030 n = group->firstChild;
1034 if (n == group->firstChild) {
1041 m3gRaiseError(M3G_INTERFACE(group), M3G_INVALID_INDEX);
1046 * \brief Gets children count.
1048 * \param handle Group object
1049 * \return children count
1051 M3G_API M3Gint m3gGetChildCount(M3GGroup handle)
1053 Group *group = (Group *) handle;
1054 M3G_VALIDATE_OBJECT(group);
1057 const Node *child = group->firstChild;
1061 child = child->right;
1062 } while (child != group->firstChild);