001: /*
002: * $RCSfile: TransformStructure.java,v $
003: *
004: * Copyright 1999-2008 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
006: *
007: * This code is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU General Public License version 2 only, as
009: * published by the Free Software Foundation. Sun designates this
010: * particular file as subject to the "Classpath" exception as provided
011: * by Sun in the LICENSE file that accompanied this code.
012: *
013: * This code is distributed in the hope that it will be useful, but WITHOUT
014: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
015: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
016: * version 2 for more details (a copy is included in the LICENSE file that
017: * accompanied this code).
018: *
019: * You should have received a copy of the GNU General Public License version
020: * 2 along with this work; if not, write to the Free Software Foundation,
021: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
022: *
023: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
024: * CA 95054 USA or visit www.sun.com if you need additional information or
025: * have any questions.
026: *
027: * $Revision: 1.9 $
028: * $Date: 2008/02/28 20:17:32 $
029: * $State: Exp $
030: */
031:
032: package javax.media.j3d;
033:
034: import java.util.*;
035:
036: /**
037: * A transform update is a object that manages TransformGroups
038: */
039:
040: class TransformStructure extends J3dStructure implements ObjectUpdate {
041:
042: /**
043: * A set of TransformGroups and associated Transform3Ds to traverse
044: */
045: private HashSet<TransformData> transformSet = new HashSet<TransformData>();
046:
047: private ArrayList objectList = new ArrayList();
048:
049: /**
050: * arraylist of the bounding leaf users affected by the transform
051: */
052: private ArrayList blUsers = new ArrayList();
053:
054: // to gather transform targets
055: private UpdateTargets targets = new UpdateTargets();
056:
057: /**
058: * An arrayList of nodes that need collisionBounds updates
059: */
060: private ArrayList collisionObjectList = new ArrayList();
061:
062: // List of dirty TransformGroups
063: private ArrayList dirtyTransformGroups = new ArrayList();
064:
065: // Associated Keys with the dirtyNodeGroup
066: private ArrayList keySet = new ArrayList();
067:
068: // the active list contains changed TransformGroup minus those that
069: // have been switched-off, plus those that have been changed but
070: // just switched-on
071: private ArrayList<TransformGroupRetained> activeTraverseList = new ArrayList<TransformGroupRetained>();
072:
073: // contains TG that have been previously changed but just switched-on
074: private ArrayList switchDirtyTgList = new ArrayList(1);
075:
076: private boolean lazyUpdate = false;
077:
078: // ArrayList of switches that have changed, use for lastSwitchOn updates
079: private ArrayList switchChangedList = new ArrayList();
080:
081: // true if already in MasterControl's update object list
082: private boolean inUpdateObjectList = false;
083:
084: /**
085: * This constructor does nothing
086: */
087: TransformStructure(VirtualUniverse u) {
088: super (u, J3dThread.UPDATE_TRANSFORM);
089: }
090:
091: void processMessages(long referenceTime) {
092: J3dMessage[] messages = getMessages(referenceTime);
093: int nMsg = getNumMessage();
094: J3dMessage m;
095: int i;
096:
097: if (nMsg <= 0) {
098: return;
099: }
100:
101: targets.clearNodes();
102: objectList.clear();
103: blUsers.clear();
104: inUpdateObjectList = false;
105:
106: synchronized (universe.sceneGraphLock) {
107: // first compact the TRANSFORM_CHANGED messages by going
108: // backwards through the messages
109: for (i = (nMsg - 1); i >= 0; i--) {
110: m = messages[i];
111: if (m.type == J3dMessage.TRANSFORM_CHANGED) {
112: // Add the TG and associated transform. Since this is a
113: // set, duplicates will be culled.
114: transformSet.add(new TransformData(
115: (TransformGroupRetained) m.args[1],
116: (Transform3D) m.args[2]));
117: }
118: }
119:
120: for (i = 0; i < nMsg; i++) {
121: m = messages[i];
122:
123: switch (m.type) {
124: case J3dMessage.INSERT_NODES:
125: objectList.add(m.args[0]);
126: if (m.args[1] != null) {
127: TargetsInterface ti = (TargetsInterface) m.args[1];
128: ti.updateCachedTargets(
129: TargetsInterface.TRANSFORM_TARGETS,
130: (CachedTargets[]) m.args[2]);
131: }
132: break;
133: case J3dMessage.REMOVE_NODES:
134: removeNodes(m);
135: break;
136: case J3dMessage.SWITCH_CHANGED:
137: processSwitchChanged(m);
138: break;
139: case J3dMessage.SHAPE3D_CHANGED:
140: objectList.add(m.args[3]);
141: if (m.args[4] != null) {
142: TargetsInterface ti = (TargetsInterface) m.args[4];
143: ti.updateCachedTargets(
144: TargetsInterface.TRANSFORM_TARGETS,
145: (CachedTargets[]) m.args[5]);
146: }
147: break;
148: case J3dMessage.GEOMETRY_CHANGED:
149: objectList.add(m.args[0]);
150: break;
151: case J3dMessage.MORPH_CHANGED:
152: objectList.add(m.args[3]);
153: break;
154: case J3dMessage.TEXT3D_DATA_CHANGED:
155: objectList.add(m.args[1]);
156: Object tiArr[] = (Object[]) m.args[2];
157: if (tiArr != null) {
158: Object newCtArr[] = (Object[]) m.args[3];
159: for (int j = 0; j < tiArr.length; j++) {
160: TargetsInterface ti = (TargetsInterface) tiArr[j];
161: ti.updateCachedTargets(
162: TargetsInterface.TRANSFORM_TARGETS,
163: (CachedTargets[]) newCtArr[j]);
164: }
165: }
166: break;
167: case J3dMessage.TEXT3D_TRANSFORM_CHANGED:
168: objectList.add(m.args[0]);
169: break;
170: case J3dMessage.BOUNDS_AUTO_COMPUTE_CHANGED:
171: processBoundsAutoComputeChanged(m);
172: break;
173: case J3dMessage.REGION_BOUND_CHANGED:
174: processRegionBoundChanged(m);
175: break;
176: case J3dMessage.COLLISION_BOUND_CHANGED:
177: processCollisionBoundChanged(m);
178: break;
179: }
180: m.decRefcount();
181: }
182: processCurrentLocalToVworld();
183:
184: // XXXX: temporary -- processVwcBounds will be
185: // done in GeometryStructure
186: if (objectList.size() > 0) {
187: processGeometryAtomVwcBounds();
188: }
189: processVwcBounds();
190: }
191:
192: // Issue 434: clear references to objects that have been processed
193: objectList.clear();
194:
195: Arrays.fill(messages, 0, nMsg, null);
196: }
197:
198: void processCurrentLocalToVworld() {
199: int i, j, tSize, sSize;
200: TransformGroupRetained tg;
201: BranchGroupRetained bgr;
202: Transform3D t;
203: TransformGroupData data;
204:
205: lazyUpdate = false;
206:
207: tSize = transformSet.size();
208: sSize = switchDirtyTgList.size();
209: if (tSize <= 0 && sSize <= 0) {
210: return;
211: }
212:
213: // process TG with setTransform changes
214: // update Transform3D, switchDirty and lToVwDrity flags
215: if (tSize > 0) {
216: Iterator<TransformData> it = transformSet.iterator();
217: while (it.hasNext()) {
218: TransformData lData = it.next();
219: tg = lData.getTransformGroupRetained();
220: tg.currentTransform.set(lData.getTransform3D());
221:
222: synchronized (tg) { // synchronized with tg.set/clearLive
223: if (tg.perPathData != null) {
224: if (!tg.inSharedGroup) {
225: data = tg.perPathData[0];
226: if (!data.switchState.inSwitch) {
227: // always add to activetraverseList if not in switch
228: activeTraverseList.add(tg);
229: data.markedDirty = true;
230: data.switchDirty = false;
231: } else {
232: // if in switch, add to activetraverseList only if it is
233: // currently switched on, otherwise, mark it as
234: // switchDirty
235: if (data.switchState.currentSwitchOn) {
236: activeTraverseList.add(tg);
237: data.switchDirty = false;
238: data.markedDirty = true;
239: } else {
240: data.switchDirty = true;
241: data.markedDirty = false;
242: }
243: }
244: } else {
245: int npaths = tg.perPathData.length;
246: boolean added = false;
247:
248: for (int k = 0; k < npaths; k++) {
249: data = tg.perPathData[k];
250: if (!data.switchState.inSwitch) {
251: if (!added) {
252: // add to activetraverseList if not in switch
253: added = true;
254: activeTraverseList.add(tg);
255: }
256: data.markedDirty = true;
257: data.switchDirty = false;
258: } else {
259: // if in switch, add to activetraverseList only if
260: // it is currently switched on, otherwise,
261: // mark it as switchDirty
262: if (data.switchState.currentSwitchOn) {
263: if (!added) {
264: added = true;
265: activeTraverseList.add(tg);
266: }
267: data.switchDirty = false;
268: data.markedDirty = true;
269: } else {
270: data.switchDirty = true;
271: data.markedDirty = false;
272: }
273: }
274: }
275: }
276: }
277: }
278: }
279: }
280:
281: // merge switchDirty into activeTraverseList
282: if (sSize > 0) {
283: activeTraverseList.addAll(switchDirtyTgList);
284: switchDirtyTgList.clear();
285: lazyUpdate = true;
286: }
287:
288: // activeTraverseList contains switched-on tg as well
289: tSize = activeTraverseList.size();
290: TransformGroupRetained[] tgs = (TransformGroupRetained[]) activeTraverseList
291: .toArray(new TransformGroupRetained[tSize]);
292:
293: // process active TGs
294: if (tSize > 0) {
295:
296: sortTransformGroups(tSize, tgs);
297:
298: // update lToVw and gather targets
299: for (i = 0; i < tSize; i++) {
300: tgs[i].processChildLocalToVworld(dirtyTransformGroups,
301: keySet, targets, blUsers);
302: }
303: if (!inUpdateObjectList) {
304: VirtualUniverse.mc.addMirrorObject(this );
305: inUpdateObjectList = true;
306: }
307: }
308:
309: transformSet.clear();
310: activeTraverseList.clear();
311: }
312:
313: private void sortTransformGroups(int size,
314: TransformGroupRetained[] tgs) {
315: if (size < 7) {
316: insertSort(size, tgs);
317: } else {
318: quicksort(0, size - 1, tgs);
319: }
320: }
321:
322: // Insertion sort on smallest arrays
323: private void insertSort(int size, TransformGroupRetained[] tgs) {
324: for (int i = 0; i < size; i++) {
325: for (int j = i; j > 0
326: && (tgs[j - 1].maxTransformLevel > tgs[j].maxTransformLevel); j--) {
327: TransformGroupRetained tmptg = tgs[j];
328: tgs[j] = tgs[j - 1];
329: tgs[j - 1] = tmptg;
330: }
331: }
332: }
333:
334: private void quicksort(int l, int r, TransformGroupRetained[] tgs) {
335: int i = l;
336: int j = r;
337: double k = tgs[(l + r) / 2].maxTransformLevel;
338: do {
339: while (tgs[i].maxTransformLevel < k)
340: i++;
341: while (k < tgs[j].maxTransformLevel)
342: j--;
343: if (i <= j) {
344: TransformGroupRetained tmptg = tgs[i];
345: tgs[i] = tgs[j];
346: tgs[j] = tmptg;
347:
348: i++;
349: j--;
350: }
351: } while (i <= j);
352:
353: if (l < j)
354: quicksort(l, j, tgs);
355: if (l < r)
356: quicksort(i, r, tgs);
357: }
358:
359: public void updateObject() {
360: processLastLocalToVworld();
361: processLastSwitchOn();
362: }
363:
364: void processLastSwitchOn() {
365: int size = switchChangedList.size();
366: if (size > 0) {
367: SwitchState switchState;
368:
369: for (int i = 0; i < size; i++) {
370: switchState = (SwitchState) switchChangedList.get(i);
371: switchState.updateLastSwitchOn();
372: }
373: switchChangedList.clear();
374: }
375: }
376:
377: void processLastLocalToVworld() {
378: int i, j, k;
379: TransformGroupRetained tg;
380: HashKey key;
381:
382: int dTGSize = dirtyTransformGroups.size();
383: if (J3dDebug.devPhase && J3dDebug.debug) {
384: J3dDebug.doDebug(J3dDebug.transformStructure,
385: J3dDebug.LEVEL_5,
386: "processLastLocalToVworld(): dTGSize= " + dTGSize
387: + "\n");
388: }
389:
390: for (i = 0, k = 0; i < dTGSize; i++) {
391: tg = (TransformGroupRetained) dirtyTransformGroups.get(i);
392: // Check if the transformGroup is still alive
393:
394: // XXXX: This is a hack, should be fixed after EA
395: // Null pointer checking should be removed!
396: // should call trans = tg.getCurrentChildLocalToVworld(key);
397: synchronized (tg) {
398: if (tg.childLocalToVworld != null) {
399: if (tg.inSharedGroup) {
400: key = (HashKey) keySet.get(k++);
401: for (j = 0; j < tg.localToVworldKeys.length; j++) {
402: if (tg.localToVworldKeys[j].equals(key)) {
403: break;
404: }
405: }
406: if (j < tg.localToVworldKeys.length) {
407: // last index = current index
408: tg.childLocalToVworldIndex[j][NodeRetained.LAST_LOCAL_TO_VWORLD] = tg.childLocalToVworldIndex[j][NodeRetained.CURRENT_LOCAL_TO_VWORLD];
409: }
410: } else {
411: // last index = current index
412: tg.childLocalToVworldIndex[0][NodeRetained.LAST_LOCAL_TO_VWORLD] = tg.childLocalToVworldIndex[0][NodeRetained.CURRENT_LOCAL_TO_VWORLD];
413: }
414: }
415:
416: }
417:
418: }
419: dirtyTransformGroups.clear();
420: keySet.clear();
421:
422: }
423:
424: void processGeometryAtomVwcBounds() {
425:
426: Shape3DRetained ms;
427: GeometryAtom ga;
428:
429: //int num_locales = universe.listOfLocales.size();
430: int oSize = objectList.size();
431: for (int i = 0; i < oSize; i++) {
432: Object[] nodes = (Object[]) objectList.get(i);
433: if (J3dDebug.devPhase && J3dDebug.debug) {
434: J3dDebug.doDebug(J3dDebug.transformStructure,
435: J3dDebug.LEVEL_5,
436: "vwcBounds computed this frame = "
437: + nodes.length + "\n");
438: }
439: for (int j = 0; j < nodes.length; j++) {
440: // If the list has geometry atoms, update the vwc bounds
441: synchronized (nodes[j]) {
442: if (nodes[j] instanceof GeometryAtom) {
443: ga = (GeometryAtom) nodes[j];
444: ms = ga.source;
445:
446: // update mirrorShape's vwcBounds if in use
447: // shape with multiple geometries only needed to be
448: // updated once
449:
450: synchronized (ms.bounds) {
451: ms.vwcBounds.transform(ms.bounds, ms
452: .getCurrentLocalToVworld(0));
453: }
454: if (ms.collisionBound != null) {
455: ms.collisionVwcBound
456: .transform(ms.collisionBound, ms
457: .getCurrentLocalToVworld(0));
458: }
459: ga.centroidIsDirty = true;
460: } else if (nodes[j] instanceof GroupRetained) {
461: // Update collisionVwcBounds of mirror GroupRetained
462: GroupRetained g = (GroupRetained) nodes[j];
463: Bounds bound = (g.sourceNode.collisionBound != null ? g.sourceNode.collisionBound
464: : g.sourceNode.getEffectiveBounds());
465: g.collisionVwcBounds.transform(bound, g
466: .getCurrentLocalToVworld());
467: }
468: }
469: }
470: }
471: // process collision bounds only update
472: for (int i = 0; i < collisionObjectList.size(); i++) {
473: Object[] nodes = (Object[]) collisionObjectList.get(i);
474: for (int j = 0; j < nodes.length; j++) {
475: synchronized (nodes[j]) {
476: if (nodes[j] instanceof GeometryAtom) {
477: ga = (GeometryAtom) nodes[j];
478: ms = ga.source;
479:
480: if (ms.collisionVwcBound != null) {
481: ms.collisionVwcBound
482: .transform(ms.collisionBound, ms
483: .getCurrentLocalToVworld(0));
484: }
485: }
486: }
487: }
488: }
489: collisionObjectList.clear();
490: }
491:
492: void processVwcBounds() {
493:
494: int size;
495: int i, j;
496: GeometryAtom ga;
497: Shape3DRetained ms;
498: Object nodes[], nodesArr[];
499:
500: UnorderList arrList = targets.targetList[Targets.GEO_TARGETS];
501: if (arrList != null) {
502: size = arrList.size();
503: nodesArr = arrList.toArray(false);
504:
505: for (i = 0; i < size; i++) {
506: nodes = (Object[]) nodesArr[i];
507: for (j = 0; j < nodes.length; j++) {
508: synchronized (nodes[j]) {
509: ga = (GeometryAtom) nodes[j];
510: ms = ga.source;
511: synchronized (ms.bounds) {
512: ms.vwcBounds.transform(ms.bounds, ms
513: .getCurrentLocalToVworld(0));
514: }
515: if (ms.collisionBound != null) {
516: ms.collisionVwcBound
517: .transform(ms.collisionBound, ms
518: .getCurrentLocalToVworld(0));
519: }
520: ga.centroidIsDirty = true;
521: }
522: }
523: }
524: }
525:
526: arrList = targets.targetList[Targets.GRP_TARGETS];
527: if (arrList != null) {
528: size = arrList.size();
529: nodesArr = arrList.toArray(false);
530:
531: for (i = 0; i < size; i++) {
532: nodes = (Object[]) nodesArr[i];
533: for (j = 0; j < nodes.length; j++) {
534: // Update collisionVwcBounds of mirror GroupRetained
535: GroupRetained g = (GroupRetained) nodes[j];
536: Bounds bound = (g.sourceNode.collisionBound != null ? g.sourceNode.collisionBound
537: : g.sourceNode.getEffectiveBounds());
538: g.collisionVwcBounds.transform(bound, g
539: .getCurrentLocalToVworld());
540: }
541: }
542: }
543:
544: // process collision bounds only update
545: for (i = 0; i < collisionObjectList.size(); i++) {
546: nodes = (Object[]) collisionObjectList.get(i);
547: for (j = 0; j < nodes.length; j++) {
548: synchronized (nodes[j]) {
549: if (nodes[j] instanceof GeometryAtom) {
550: ga = (GeometryAtom) nodes[j];
551: ms = ga.source;
552:
553: if (ms.collisionVwcBound != null) {
554: ms.collisionVwcBound
555: .transform(ms.collisionBound, ms
556: .getCurrentLocalToVworld(0));
557: }
558: }
559: }
560: }
561: }
562: collisionObjectList.clear();
563: }
564:
565: void processRegionBoundChanged(J3dMessage m) {
566: // need to update mirrorShape's bounds
567: processBoundsChanged((Object[]) m.args[0], (Bounds) m.args[1]);
568: }
569:
570: void processBoundsChanged(Object[] gaArray, Bounds updateBounds) {
571: int i;
572: GeometryAtom ga;
573: Shape3DRetained ms;
574:
575: for (i = 0; i < gaArray.length; i++) {
576: ga = (GeometryAtom) gaArray[i];
577: ms = ga.source;
578:
579: // update mirrorShape's bound objects
580: // since boundsAutoCompute is false and user specified a bound
581: ms.bounds = updateBounds;
582: if (ms.collisionBound == null) {
583: ms.collisionVwcBound = ms.vwcBounds;
584: }
585: }
586: objectList.add(gaArray);
587: }
588:
589: void processCollisionBoundChanged(J3dMessage m) {
590: int i;
591: Shape3DRetained ms;
592: Bounds collisionBound = (Bounds) m.args[1];
593:
594: if (m.args[0] instanceof GroupRetained) {
595: GroupRetained g = (GroupRetained) m.args[0];
596: if (g.mirrorGroup != null) {
597: objectList.add(g.mirrorGroup);
598: }
599: } else {
600: Object[] gaArray = (Object[]) m.args[0];
601: GeometryAtom ga;
602:
603: for (i = 0; i < gaArray.length; i++) {
604: ga = (GeometryAtom) gaArray[i];
605: ms = ga.source;
606:
607: ms.collisionBound = collisionBound;
608:
609: if (ms.collisionBound != null) {
610: // may be previously points to ms.vwcBounds, therefore
611: // needs to create one
612: ms.collisionVwcBound = (Bounds) ms.collisionBound
613: .clone();
614: } else {
615: ms.collisionVwcBound = ms.vwcBounds;
616: }
617: }
618: collisionObjectList.add(gaArray);
619: }
620: }
621:
622: void processBoundsAutoComputeChanged(J3dMessage m) {
623: // need to update mirrorShape's bounds
624: processBoundsChanged((Object[]) m.args[0], (Bounds) m.args[1]);
625: }
626:
627: void processSwitchChanged(J3dMessage m) {
628: ArrayList switchList = (ArrayList) m.args[2];
629:
630: int size = switchList.size();
631: if (size > 0) {
632: // update SwitchState's CurrentSwitchOn flag
633: SwitchState switchState;
634: for (int j = 0; j < size; j++) {
635: switchState = (SwitchState) switchList.get(j);
636: switchState.updateCurrentSwitchOn();
637: }
638:
639: // process switch dirty TranformGroups
640: UpdateTargets targets = (UpdateTargets) m.args[0];
641: UnorderList arrList = targets.targetList[Targets.GRP_TARGETS];
642:
643: if (arrList != null) {
644:
645: Object[] nodes;
646: Object[] nodesArr = arrList.toArray(false);
647: int aSize = arrList.size();
648: int nPaths;
649: boolean added;
650:
651: TransformGroupRetained tg;
652: TransformGroupData data;
653:
654: for (int j = 0; j < aSize; j++) {
655: nodes = (Object[]) nodesArr[j];
656:
657: for (int i = 0; i < nodes.length; i++) {
658: added = false;
659: tg = (TransformGroupRetained) nodes[i];
660:
661: synchronized (tg) { // synchronized with tg.set/clearLive
662: if (tg.perPathData != null) {
663: nPaths = tg.perPathData.length;
664:
665: for (int k = 0; k < nPaths; k++) {
666: data = tg.perPathData[k];
667: if (data.switchState.currentSwitchOn
668: && data.switchDirty) {
669: if (!added) {
670: // only needed to add once
671: switchDirtyTgList.add(tg);
672: added = true;
673: }
674: data.switchDirty = false;
675: data.markedDirty = true;
676: }
677: }
678: }
679: }
680: }
681: }
682: }
683:
684: // gather a list of SwitchState for lastSwitchOn update
685: switchChangedList.addAll(switchList);
686:
687: if (!inUpdateObjectList) {
688: VirtualUniverse.mc.addMirrorObject(this );
689: inUpdateObjectList = true;
690: }
691: }
692: }
693:
694: UpdateTargets getTargetList() {
695: return targets;
696: }
697:
698: ArrayList getBlUsers() {
699: return blUsers;
700: }
701:
702: boolean getLazyUpdate() {
703: return lazyUpdate;
704: }
705:
706: void removeNodes(J3dMessage m) {
707: if (m.args[1] != null) {
708: TargetsInterface ti = (TargetsInterface) m.args[1];
709: ti.updateCachedTargets(TargetsInterface.TRANSFORM_TARGETS,
710: (CachedTargets[]) m.args[2]);
711: }
712: }
713:
714: void cleanup() {
715: }
716:
717: // Wrapper for a (TransformGroupRetained, Transform3D) pair
718: // TransformGroupRetained is effectively used as the key in the
719: // HashSet
720: private class TransformData {
721: private TransformGroupRetained transformGroupRetained;
722: private Transform3D transform3D;
723:
724: TransformData(TransformGroupRetained tgr, Transform3D t3d) {
725: transformGroupRetained = tgr;
726: transform3D = t3d;
727: }
728:
729: // Hashcode and equals test only evaluate TransformGroupRetained
730: @Override
731: public int hashCode() {
732: return transformGroupRetained.hashCode();
733: }
734:
735: // Hashcode and equals test only evaluate TransformGroupRetained
736: @Override
737: public boolean equals(Object o) {
738: if (!(o instanceof TransformData)) {
739: return false;
740: }
741:
742: return transformGroupRetained.equals(((TransformData) o)
743: .getTransformGroupRetained());
744: }
745:
746: TransformGroupRetained getTransformGroupRetained() {
747: return transformGroupRetained;
748: }
749:
750: Transform3D getTransform3D() {
751: return transform3D;
752: }
753:
754: }
755:
756: }
|