001: /*
002: * $RCSfile: SceneGraphPath.java,v $
003: *
004: * Copyright 1997-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.6 $
028: * $Date: 2008/02/28 20:17:29 $
029: * $State: Exp $
030: */
031:
032: package javax.media.j3d;
033:
034: import javax.vecmath.Point3d;
035: import javax.vecmath.Point4d;
036:
037: /**
038: * A SceneGraphPath object represents the path from a Locale to a
039: * terminal node in the scene graph. This path consists of a Locale, a
040: * terminal node, and an array of internal nodes that are in the path
041: * from the Locale to the terminal node. The terminal node may be
042: * either a Leaf node or a Group node. A valid SceneGraphPath must
043: * uniquely identify a specific instance of the terminal node. For
044: * nodes that are not under a SharedGroup, the minimal SceneGraphPath
045: * consists of the Locale and the terminal node itself. For nodes that
046: * are under a SharedGroup, the minimal SceneGraphPath consists of the
047: * Locale, the terminal node, and a list of all Link nodes in the path
048: * from the Locale to the terminal node. A SceneGraphPath may optionally
049: * contain other interior nodes that are in the path.
050: * A SceneGraphPath is verified for correctness and uniqueness when
051: * it is sent as an argument to other methods of Java 3D.
052: * <p>
053: * In the array of internal nodes, the node at index 0 is the node
054: * closest to the Locale. The indices increase along the path to the
055: * terminal node, with the node at index length-1 being the node closest
056: * to the terminal node. The array of nodes does not contain either the
057: * Locale (which is not a node) or the terminal node.
058: * <p>
059: * When a SceneGraphPath is returned from the picking or collision
060: * methods of Java 3D, it will also contain the value of the
061: * LocalToVworld transform of the terminal node that was in effect at
062: * the time the pick or collision occurred.
063: * Note that ENABLE_PICK_REPORTING and ENABLE_COLLISION_REPORTING are
064: * disabled by default. This means that the picking and collision
065: * methods will return the minimal SceneGraphPath by default.
066: *
067: * @see Node#ENABLE_PICK_REPORTING
068: * @see Node#ENABLE_COLLISION_REPORTING
069: * @see BranchGroup#pickAll
070: * @see BranchGroup#pickAllSorted
071: * @see BranchGroup#pickClosest
072: * @see BranchGroup#pickAny
073: */
074:
075: public class SceneGraphPath {
076:
077: Locale root = null;
078: Node[] interior = null;
079: Node item = null;
080: Transform3D transform = new Transform3D();
081:
082: // Intersect Point for item when picked
083: Point3d intersectPoint = new Point3d();
084:
085: double pickDistance; // distance to pick location
086:
087: /**
088: * Constructs a SceneGraphPath object with default parameters.
089: * The default values are as follows:
090: * <ul>
091: * root : null<br>
092: * object : null<br>
093: * list of (interior) nodes : null<br>
094: * transform : identity<br>
095: * </ul>
096: */
097: public SceneGraphPath() {
098: // Just use defaults
099: }
100:
101: /**
102: * Constructs a new SceneGraphPath object.
103: * @param root the Locale object of this path
104: * @param object the terminal node of this path
105: */
106: public SceneGraphPath(Locale root, Node object) {
107:
108: this .item = object;
109: this .root = root;
110: }
111:
112: /**
113: * Constructs a new SceneGraphPath object.
114: * @param root the Locale object of this path
115: * @param nodes an array of node objects in the path from
116: * the Locale to the terminal node
117: * @param object the terminal node of this path
118: */
119: public SceneGraphPath(Locale root, Node nodes[], Node object) {
120:
121: this .item = object;
122: this .root = root;
123: this .interior = new Node[nodes.length];
124: for (int i = 0; i < nodes.length; i++)
125: this .interior[i] = nodes[i];
126: }
127:
128: /**
129: * Constructs a new SceneGraphPath object
130: * @param sgp the SceneGraphPath to copy from
131: */
132: SceneGraphPath(SceneGraphPath sgp) {
133: set(sgp);
134: }
135:
136: /**
137: * Sets this path's values to that of the specified path.
138: * @param newPath the SceneGraphPath to copy
139: */
140: public final void set(SceneGraphPath newPath) {
141: this .root = newPath.root;
142: this .item = newPath.item;
143: this .transform.set(newPath.transform);
144: if (newPath.interior != null && newPath.interior.length > 0) {
145: interior = new Node[newPath.interior.length];
146: for (int i = 0; i < interior.length; i++)
147: this .interior[i] = newPath.interior[i];
148: } else
149: interior = null;
150: }
151:
152: /**
153: * Sets this path's Locale to the specified Locale.
154: * @param newLocale The new Locale
155: */
156: public final void setLocale(Locale newLocale) {
157: root = newLocale;
158: }
159:
160: /**
161: * Sets this path's terminal node to the specified node object.
162: * @param object the new terminal node
163: */
164: public final void setObject(Node object) {
165: this .item = object;
166: }
167:
168: /**
169: * Sets this path's node objects to the specified node objects.
170: * @param nodes an array of node objects in the path from
171: * the Locale to the terminal node
172: */
173: public final void setNodes(Node nodes[]) {
174:
175: if (nodes != null && nodes.length > 0) {
176: interior = new Node[nodes.length];
177: for (int i = 0; i < nodes.length; i++)
178: this .interior[i] = nodes[i];
179: } else
180: interior = null;
181: }
182:
183: /**
184: * Replaces the node at the specified index with newNode.
185: * @param index the index of the node to replace
186: * @param newNode the new node
187: * @exception NullPointerException if the node array pointer is null.
188: *
189: */
190: public final void setNode(int index, Node newNode) {
191: if (interior == null)
192: throw new NullPointerException(J3dI18N
193: .getString("SceneGraphPath0"));
194:
195: interior[index] = newNode;
196: }
197:
198: /**
199: * Sets the transform component of this SceneGraphPath to the value of
200: * the passed transform.
201: * @param trans the transform to be copied. trans should be the
202: * localToVworld matrix of this SceneGraphPath object.
203: */
204: public final void setTransform(Transform3D trans) {
205: transform.set(trans);
206: }
207:
208: /**
209: * Returns a copy of the transform associated with this SceneGraphPath;
210: * returns null if there is no transform associated.
211: * If this SceneGraphPath was returned by a Java 3D picking or
212: * collision method, the local coordinate to virtual world
213: * coordinate transform for this scene graph object at the
214: * time of the pick or collision is recorded.
215: * @return the local to VWorld transform
216: */
217: public final Transform3D getTransform() {
218: return new Transform3D(transform);
219: }
220:
221: /**
222: * Retrieves the path's Locale
223: * @return this path's Locale
224: */
225: public final Locale getLocale() {
226: return this .root;
227: }
228:
229: /**
230: * Retrieves the path's terminal node object.
231: * @return the terminal node
232: */
233: public final Node getObject() {
234: return this .item;
235: }
236:
237: /**
238: * Retrieves the number of nodes in this path. The number of nodes
239: * does not include the Locale or the terminal node object itself.
240: * @return a count of the number of nodes in this path
241: */
242: public final int nodeCount() {
243: if (interior == null)
244: return 0;
245: return interior.length;
246: }
247:
248: /**
249: * Retrieves the node at the specified index.
250: * @param index the index specifying which node to retrieve
251: * @return the specified node
252: */
253: public final Node getNode(int index) {
254: if (interior == null)
255: throw new ArrayIndexOutOfBoundsException(J3dI18N
256: .getString("SceneGraphPath1"));
257: return interior[index];
258: }
259:
260: /**
261: * Returns true if all of the data members of path testPath are
262: * equal to the corresponding data members in this SceneGraphPath and
263: * if the values of the transforms is equal.
264: * @param testPath the path we will compare this object's path against.
265: * @return true or false
266: */
267: public boolean equals(SceneGraphPath testPath) {
268: boolean result = true;
269: try {
270:
271: if (testPath == null || root != testPath.root
272: || item != testPath.item)
273: return false;
274:
275: result = transform.equals(testPath.transform);
276:
277: if (result == false)
278: return false;
279:
280: if (interior == null || testPath.interior == null) {
281: if (interior != testPath.interior)
282: return false;
283: else
284: result = (root == testPath.root && item == testPath.item);
285:
286: } else {
287: if (interior.length == testPath.interior.length) {
288: for (int i = 0; i < interior.length; i++)
289: if (interior[i] != testPath.interior[i]) {
290: return false;
291: }
292: } else
293: return false;
294: }
295:
296: } catch (NullPointerException e2) {
297: return false;
298: }
299:
300: return result;
301: }
302:
303: /**
304: * Returns true if the Object o1 is of type SceneGraphPath and all of the
305: * data members of o1 are equal to the corresponding data members in
306: * this SceneGraphPath and if the values of the transforms is equal.
307: * @param o1 the object we will compare this SceneGraphPath's path against.
308: * @return true or false
309: */
310: public boolean equals(Object o1) {
311: boolean result = true;
312:
313: try {
314: SceneGraphPath testPath = (SceneGraphPath) o1;
315: if (testPath == null || root != testPath.root
316: || item != testPath.item)
317: return false;
318:
319: result = transform.equals(testPath.transform);
320:
321: if (result == false)
322: return false;
323:
324: if (interior == null || testPath.interior == null) {
325: if (interior != testPath.interior)
326: return false;
327: else
328: result = (root == testPath.root && item == testPath.item);
329:
330: } else {
331: if (interior.length == testPath.interior.length) {
332: for (int i = 0; i < interior.length; i++)
333: if (interior[i] != testPath.interior[i]) {
334: return false;
335: }
336: } else
337: return false;
338: }
339:
340: return result;
341: } catch (NullPointerException e2) {
342: return false;
343: } catch (ClassCastException e1) {
344: return false;
345: }
346: }
347:
348: /**
349: * Returns a hash number based on the data values in this
350: * object. Two different SceneGraphPath objects with identical data
351: * values (ie, returns true for trans.equals(SceneGraphPath) ) will
352: * return the same hash number. Two Paths with different data members
353: * may return the same hash value, although this is not likely.
354: * @return the integer hash value
355: */
356: public int hashCode() {
357: HashKey key = new HashKey(250);
358: // NOTE: Needed to add interior != null because this method is called
359: // by object.toString() when interior is null.
360: if (interior != null && item != null) {
361: for (int i = 0; i < interior.length; i++) {
362: key.append(LinkRetained.plus).append(item.toString());
363: }
364: }
365: return (key.hashCode() + transform.hashCode());
366: }
367:
368: /**
369: * Determines whether two SceneGraphPath objects represent the same
370: * path in the scene graph; either object might include a different
371: * subset of internal nodes; only the internal link nodes, the Locale,
372: * and the Node itself are compared. The paths are not validated for
373: * correctness or uniqueness.
374: * @param testPath the SceneGraphPath to be compared to this SceneGraphPath
375: * @return true or false
376: */
377: public final boolean isSamePath(SceneGraphPath testPath) {
378: int count = 0, i;
379:
380: if (testPath == null || testPath.item != this .item
381: || root != testPath.root)
382: return false;
383:
384: if (interior != null && testPath.interior != null) {
385: for (i = 0; i < interior.length; i++) {
386: if (interior[i] instanceof Link) {
387: // found Link in this, now check for matching in testPath
388: while (count < testPath.interior.length) {
389: if (testPath.interior[count] instanceof Link) {
390: if (testPath.interior[count] != interior[i]) {
391: return false;
392: }
393: count++;
394: break;
395: }
396: count++;
397: // if true, this had an extra Link
398: if (count == testPath.interior.length)
399: return false;
400: }
401: }
402: }
403: // make sure testPath doesn't have any extra Links
404: while (count < testPath.interior.length) {
405: if (testPath.interior[count] instanceof Link)
406: return false;
407: count++;
408: }
409: } else if (interior != testPath.interior) // ==> they are not both null
410: return false;
411:
412: return true;
413: }
414:
415: /**
416: * Returns a string representation of this object;
417: * the string contains the class names of all Nodes in the SceneGraphPath,
418: * the toString() method of any associated user data provided by
419: * SceneGraphObject.getUserData(), and also prints out the transform,
420: * if it is not null.
421: * @return String representation of this object
422: */
423: public String toString() {
424:
425: StringBuffer str = new StringBuffer();
426: Object obj;
427:
428: if (root == null && interior == null && item == null)
429: return (super .toString());
430:
431: if (root != null)
432: str.append(root + " : ");
433:
434: if (interior != null) {
435: for (int i = 0; i < interior.length; i++) {
436:
437: str.append(interior[i].getClass().getName());
438: obj = interior[i].getUserData();
439: if (obj == null)
440: str.append(" : ");
441: else
442: str.append(", " + obj + " : ");
443: }
444: }
445:
446: if (item != null) {
447: // str.append( item + ", "+ item.getUserData() + "--"+intersectPoint );
448: str.append(item.getClass().getName());
449: obj = item.getUserData();
450: if (obj != null)
451: str.append(", " + obj);
452:
453: try {
454: if (item.getClass().getName().equals(
455: "javax.media.j3d.Shape3D"))
456: str.append(((Shape3D) item).getGeometry());
457: } catch (CapabilityNotSetException e) {
458: }
459: }
460:
461: str.append("\n" + "LocalToVworld Transform:\n" + transform);
462:
463: return new String(str);
464: }
465:
466: /**
467: * Determine if this SceneGraphPath is unique and valid
468: * The graph don't have to be live for this checking.
469: * Set Locale when it is null.
470: * Only the essential link node which led to the Locale
471: * is validated.
472: */
473: boolean validate() {
474: NodeRetained node = (NodeRetained) item.retained;
475:
476: Locale locale = node.locale;
477:
478: if (root != null) {
479: if (item.isLive()) {
480: if (locale != root) {
481: return false;
482: }
483: }
484: } else {
485: root = locale;
486: }
487:
488: int idx = (interior == null ? 0 : interior.length);
489:
490: do {
491: if (node instanceof SharedGroupRetained) {
492: if (interior == null)
493: return false;
494: while (--idx > 0) {
495: if (((SharedGroupRetained) node).parents
496: .contains(interior[idx].retained)) {
497: break;
498: }
499: }
500: if (idx < 0) {
501: return false;
502: }
503: node = (NodeRetained) interior[idx].retained;
504: } else {
505: node = node.parent;
506: }
507: } while (node != null);
508:
509: return true;
510: }
511:
512: // return key of this path or null is not in SharedGroup
513: void getHashKey(HashKey key) {
514: if (interior != null) {
515: key.reset();
516: key.append(root.nodeId);
517: for (int i = 0; i < interior.length; i++) {
518: Node node = interior[i];
519:
520: if (!node.isLive()) {
521: throw new RuntimeException(J3dI18N
522: .getString("SceneGraphPath3"));
523: }
524:
525: NodeRetained nodeR = (NodeRetained) node.retained;
526: if (nodeR.nodeType == NodeRetained.LINK) {
527: key.append("+").append(nodeR.nodeId);
528: }
529: }
530: }
531: }
532:
533: /**
534: * Determines whether this SceneGraphPath is unique and valid. The
535: * verification determines that all of the nodes are live, that the
536: * specified path is unique, that the correct Locale is specified, and
537: * that there is a Node specified.
538: */
539: boolean validate(HashKey key) {
540:
541: int i;
542:
543: // verify that there is at least a Locale and Node specified
544: if (root == null)
545: throw new IllegalArgumentException(J3dI18N
546: .getString("SceneGraphPath2"));
547:
548: if (item == null)
549: throw new IllegalArgumentException(J3dI18N
550: .getString("SceneGraphPath10"));
551:
552: // verify liveness
553: if (!item.isLive())
554: throw new IllegalArgumentException(J3dI18N
555: .getString("SceneGraphPath3"));
556:
557: try {
558: getHashKey(key);
559: } catch (RuntimeException ex) {
560: throw new IllegalArgumentException(ex.getMessage());
561: }
562:
563: // The rest of the code verifies uniqueness; it traverses the retained
564: // hierarchy of the scene graph. This could be problematic later in
565: // when certain compile mode optimizations are added. */
566:
567: NodeRetained bottomNR, currentNR, nextNR = null;
568: Node currentNode;
569: int count = 0;
570:
571: // Need to traverse the retained hierarchy on a live scene graph
572: // from bottom to top
573: //
574: // bottomNR = last verified node; as nodes are verified, bottomNR
575: // moves up the scen graph
576: // nextNR = Next node that the user has specified after bottomNR
577: // currentNR = current node; is changing as it covers all the
578: // nodes from bottomNR to nextNR
579:
580: // If the parent of a NodeRetained is null, we know that the parent
581: // is either a BranchGroupRetained at the top of a scene graph or
582: // it is a SharedGroupRetained, potentially with multiple parents.
583:
584: bottomNR = (NodeRetained) (item.retained);
585:
586: if (interior != null) {
587: for (i = interior.length - 1; i >= 0; i--) {
588: nextNR = (NodeRetained) (interior[i].retained);
589: currentNR = bottomNR.parent;
590: if (currentNR == null
591: && bottomNR instanceof SharedGroupRetained) {
592: if (((SharedGroupRetained) (bottomNR)).parents
593: .contains(nextNR))
594: currentNR = nextNR;
595: else
596: throw new IllegalArgumentException(J3dI18N
597: .getString("SceneGraphPath5"));
598:
599: }
600:
601: while (currentNR != nextNR) {
602: if (currentNR == null) {
603: throw new IllegalArgumentException(J3dI18N
604: .getString("SceneGraphPath11"));
605: }
606:
607: if (currentNR instanceof SharedGroupRetained) {
608: if (((SharedGroupRetained) (currentNR)).parents
609: .contains(nextNR))
610: currentNR = nextNR;
611: else
612: throw new IllegalArgumentException(J3dI18N
613: .getString("SceneGraphPath5"));
614:
615: } else {
616: currentNR = currentNR.parent;
617: }
618: }
619: bottomNR = currentNR;
620: }
621: }
622:
623: // Now go from bottomNR to Locale
624: currentNR = bottomNR.parent;
625: if (currentNR == null
626: && bottomNR instanceof SharedGroupRetained) {
627: throw new IllegalArgumentException(J3dI18N
628: .getString("SceneGraphPath5"));
629: }
630:
631: while (currentNR != null) {
632: if (currentNR instanceof LinkRetained) {
633: throw new IllegalArgumentException(J3dI18N
634: .getString("SceneGraphPath5"));
635: }
636:
637: bottomNR = currentNR;
638: currentNR = currentNR.parent;
639: if (currentNR == null
640: && bottomNR instanceof SharedGroupRetained) {
641: throw new IllegalArgumentException(J3dI18N
642: .getString("SceneGraphPath5"));
643: }
644: }
645:
646: // get the real BranchGroup from the BranchGroupRetained
647: currentNode = (Node) (bottomNR.source);
648: // now bottomNR should be a BranchGroup -- should try an assert here
649: if (!root.branchGroups.contains(currentNode)) {
650: throw new IllegalArgumentException(J3dI18N
651: .getString("SceneGraphPath9"));
652: }
653:
654: return true;
655: }
656:
657: /**
658: * Returns the distance from the intersectPoint for item and
659: * origin.
660: */
661: double getDistanceFrom(Point3d origin) {
662: return intersectPoint.distance(origin);
663: }
664:
665: /**
666: * Returns the distance of the pick
667: */
668: double getDistance() {
669: return pickDistance;
670: }
671:
672: final void setIntersectPoint(Point3d point) {
673: intersectPoint.set(point);
674: }
675:
676: final void setIntersectPointDis(Point4d pickLocation) {
677: // System.err.println( "setIntersectPointDis pickLocation= "+pickLocation);
678: intersectPoint.x = pickLocation.x;
679: intersectPoint.y = pickLocation.y;
680: intersectPoint.z = pickLocation.z;
681: pickDistance = pickLocation.w;
682: }
683:
684: final Point3d getIntersectPoint() {
685: return intersectPoint;
686: }
687: }
|