001: /*
002: * $RCSfile: WakeupOnCollisionEntry.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.5 $
028: * $Date: 2008/02/28 20:17:33 $
029: * $State: Exp $
030: */
031:
032: package javax.media.j3d;
033:
034: import java.util.*;
035:
036: /**
037: * Class specifying a wakeup when the specified object
038: * collides with any other object in the scene graph.
039: *
040: */
041: public final class WakeupOnCollisionEntry extends WakeupCriterion {
042:
043: // different types of WakeupIndexedList that use in GeometryStructure
044: static final int COND_IN_GS_LIST = 0;
045: static final int COLLIDEENTRY_IN_BS_LIST = 1;
046:
047: // total number of different IndexedUnorderedSet types
048: static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2;
049:
050: /**
051: * Use geometry in computing collisions.
052: */
053: public static final int USE_GEOMETRY = 10;
054:
055: /**
056: * Use geometric bounds as an approximation in computing collisions.
057: */
058: public static final int USE_BOUNDS = 11;
059:
060: static final int GROUP = NodeRetained.GROUP;
061: static final int BOUNDINGLEAF = NodeRetained.BOUNDINGLEAF;
062: static final int SHAPE = NodeRetained.SHAPE;
063: static final int MORPH = NodeRetained.MORPH;
064: static final int ORIENTEDSHAPE3D = NodeRetained.ORIENTEDSHAPE3D;
065: static final int BOUND = 0;
066:
067: /**
068: * Accuracy mode one of USE_GEOMETRY or USE_BOUNDS
069: */
070: int accuracyMode;
071:
072: // Cached the arming Node being used when it is not BOUND
073: NodeRetained armingNode;
074:
075: // A transformed Bounds of Group/Bounds, use by
076: // BOUND, GROUP
077: Bounds vwcBounds = null;
078:
079: // Use by BoundingLeaf, point to mirror BoundingLeaf
080: // transformedRegion under this leaf is used.
081: BoundingLeafRetained boundingLeaf = null;
082:
083: /**
084: * Geometry atoms that this wakeup condition refer to.
085: * Only use by SHAPE, MORPH, GROUP, ORIENTEDSHAPE
086: */
087: UnorderList geometryAtoms = null;
088:
089: // one of GROUP, BOUNDINGLEAF, SHAPE, MORPH, BOUND
090: int nodeType;
091:
092: SceneGraphPath armingPath = null;
093: Bounds armingBounds = null;
094:
095: // the following two references are set only after a collision
096: // has occurred
097: Bounds collidingBounds = null;
098: SceneGraphPath collidingPath = null;
099:
100: /**
101: * Constructs a new WakeupOnCollisionEntry criterion with
102: * USE_BOUNDS for a speed hint.
103: * @param armingPath the path used to <em>arm</em> collision
104: * detection
105: * @exception IllegalArgumentException if object associated with the
106: * SceneGraphPath is other than a Group, Shape3D, Morph, or
107: * BoundingLeaf node.
108: */
109: public WakeupOnCollisionEntry(SceneGraphPath armingPath) {
110: this (armingPath, USE_BOUNDS);
111: }
112:
113: /**
114: * Constructs a new WakeupOnCollisionEntry criterion.
115: * @param armingPath the path used to <em>arm</em> collision
116: * detection
117: * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how
118: * accurately Java 3D will perform collision detection
119: * @exception IllegalArgumentException if hint is not one of
120: * USE_GEOMETRY or USE_BOUNDS.
121: * @exception IllegalArgumentException if object associated with the
122: * SceneGraphPath is other than a Group, Shape3D, Morph, or
123: * BoundingLeaf node.
124: */
125: public WakeupOnCollisionEntry(SceneGraphPath armingPath,
126: int speedHint) {
127: this (new SceneGraphPath(armingPath), speedHint, null);
128: }
129:
130: /**
131: * Constructs a new WakeupOnCollisionEntry criterion.
132: * @param armingNode the Group, Shape, or Morph node used to
133: * <em>arm</em> collision detection
134: * @exception IllegalArgumentException if object is under a
135: * SharedGroup node or object is other than a Group, Shape3D,
136: * Morph or BoundingLeaf node.
137: */
138: public WakeupOnCollisionEntry(Node armingNode) {
139: this (armingNode, USE_BOUNDS);
140: }
141:
142: /**
143: * Constructs a new WakeupOnCollisionEntry criterion.
144: * @param armingNode the Group, Shape, or Morph node used to
145: * <em>arm</em> collision detection
146: * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how
147: * accurately Java 3D will perform collision detection
148: * @exception IllegalArgumentException if hint is not one of
149: * USE_GEOMETRY or USE_BOUNDS.
150: * @exception IllegalArgumentException if object is under a
151: * SharedGroup node or object is other than a Group, Shape3D,
152: * Morph or BoundingLeaf node.
153: */
154: public WakeupOnCollisionEntry(Node armingNode, int speedHint) {
155: this (new SceneGraphPath(null, armingNode), speedHint, null);
156: }
157:
158: /**
159: * Constructs a new WakeupOnCollisionEntry criterion.
160: * @param armingBounds the bounds object used to <em>arm</em> collision
161: * detection
162: */
163: public WakeupOnCollisionEntry(Bounds armingBounds) {
164: this (null, USE_BOUNDS, (Bounds) armingBounds.clone());
165: }
166:
167: /**
168: * Constructs a new WakeupOnCollisionEntry criterion.
169: * @param armingPath the path used to <em>arm</em> collision
170: * detection
171: * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how
172: * accurately Java 3D will perform collision detection
173: * @param armingBounds the bounds object used to <em>arm</em> collision
174: * detection
175: * @exception IllegalArgumentException if hint is not one of
176: * USE_GEOMETRY or USE_BOUNDS.
177: * @exception IllegalArgumentException if object associated with the
178: * SceneGraphPath is other than a Group, Shape3D, Morph, or
179: * BoundingLeaf node.
180: */
181: WakeupOnCollisionEntry(SceneGraphPath armingPath, int speedHint,
182: Bounds armingBounds) {
183: if (armingPath != null) {
184: this .armingNode = (NodeRetained) armingPath.getObject().retained;
185: nodeType = getNodeType(armingNode, armingPath,
186: "WakeupOnCollisionEntry");
187: this .armingPath = armingPath;
188: validateSpeedHint(speedHint, "WakeupOnCollisionEntry4");
189: } else {
190: this .armingBounds = armingBounds;
191: nodeType = BOUND;
192: }
193: accuracyMode = speedHint;
194: WakeupIndexedList.init(this , TOTAL_INDEXED_UNORDER_SET_TYPES);
195: }
196:
197: /**
198: * Returns the path used in specifying the collision condition.
199: * @return the SceneGraphPath object generated when arming this
200: * criterion---null implies that a bounds object armed this criteria
201: */
202: public SceneGraphPath getArmingPath() {
203: return (armingPath != null ? new SceneGraphPath(armingPath)
204: : null);
205: }
206:
207: /**
208: * Returns the bounds object used in specifying the collision condition.
209: * @return the Bounds object generated when arming this
210: * criterion---null implies that a SceneGraphPath armed this criteria
211: */
212: public Bounds getArmingBounds() {
213: return (armingBounds != null ? (Bounds) armingBounds.clone()
214: : null);
215: }
216:
217: /**
218: * Retrieves the path describing the object causing the collision.
219: * @return the SceneGraphPath that describes the triggering object.
220: * @exception IllegalStateException if not called from within the
221: * a behavior's processStimulus method which was awoken by a collision.
222: */
223: public SceneGraphPath getTriggeringPath() {
224: if (behav == null) {
225: throw new IllegalStateException(J3dI18N
226: .getString("WakeupOnCollisionEntry5"));
227: }
228:
229: synchronized (behav) {
230: if (!behav.inCallback) {
231: throw new IllegalStateException(J3dI18N
232: .getString("WakeupOnCollisionEntry5"));
233: }
234: }
235: return (collidingPath != null ? new SceneGraphPath(
236: collidingPath) : null);
237: }
238:
239: /**
240: * Retrieves the Bounds object that caused the collision
241: * @return the colliding Bounds object.
242: * @exception IllegalStateException if not called from within the
243: * a behavior's processStimulus method which was awoken by a collision.
244: */
245: public Bounds getTriggeringBounds() {
246: if (behav == null) {
247: throw new IllegalStateException(J3dI18N
248: .getString("WakeupOnCollisionEntry6"));
249: }
250:
251: synchronized (behav) {
252: if (!behav.inCallback) {
253: throw new IllegalStateException(J3dI18N
254: .getString("WakeupOnCollisionEntry6"));
255: }
256: }
257: return (collidingBounds != null ? (Bounds) (collidingBounds
258: .clone()) : null);
259: }
260:
261: /**
262: * Node legality checker
263: * throw Exception if node is not legal.
264: * @return nodeType
265: */
266: static int getNodeType(NodeRetained armingNode,
267: SceneGraphPath armingPath, String s)
268: throws IllegalArgumentException {
269:
270: // check if SceneGraphPath is unique
271: // Note that graph may not live at this point so we
272: // can't use node.inSharedGroup.
273: if (!armingPath.validate()) {
274: throw new IllegalArgumentException(J3dI18N.getString(s
275: + "7"));
276: }
277:
278: if (armingNode.inBackgroundGroup) {
279: throw new IllegalArgumentException(J3dI18N.getString(s
280: + "1"));
281: }
282:
283: // This should come before Shape3DRetained check
284: if (armingNode instanceof OrientedShape3DRetained) {
285: return ORIENTEDSHAPE3D;
286: }
287:
288: if (armingNode instanceof Shape3DRetained) {
289: return SHAPE;
290: }
291:
292: if (armingNode instanceof MorphRetained) {
293: return MORPH;
294: }
295:
296: if (armingNode instanceof GroupRetained) {
297: return GROUP;
298: }
299:
300: if (armingNode instanceof BoundingLeafRetained) {
301: return BOUNDINGLEAF;
302: }
303:
304: throw new IllegalArgumentException(J3dI18N.getString(s + "0"));
305: }
306:
307: /**
308: * speedHint legality checker
309: * throw Exception if speedHint is not legal
310: */
311: static void validateSpeedHint(int speedHint, String s)
312: throws IllegalArgumentException {
313: if ((speedHint != USE_GEOMETRY) && (speedHint != USE_BOUNDS)) {
314: throw new IllegalArgumentException(J3dI18N.getString(s));
315: }
316:
317: }
318:
319: /**
320: * This is a callback from BehaviorStructure. It is
321: * used to add wakeupCondition to behavior structure.
322: */
323: void addBehaviorCondition(BehaviorStructure bs) {
324:
325: switch (nodeType) {
326: case SHAPE: // Use geometryAtoms[].collisionBounds
327: case ORIENTEDSHAPE3D:
328: if (!armingNode.source.isLive()) {
329: return;
330: }
331: if (geometryAtoms == null) {
332: geometryAtoms = new UnorderList(1, GeometryAtom.class);
333: }
334: Shape3DRetained shape = (Shape3DRetained) armingNode;
335: geometryAtoms.add(Shape3DRetained.getGeomAtom(shape
336: .getMirrorShape(armingPath)));
337: break;
338: case MORPH: // Use geometryAtoms[].collisionBounds
339: if (!armingNode.source.isLive()) {
340: return;
341: }
342: if (geometryAtoms == null) {
343: geometryAtoms = new UnorderList(1, GeometryAtom.class);
344: }
345: MorphRetained morph = (MorphRetained) armingNode;
346: geometryAtoms.add(Shape3DRetained.getGeomAtom(morph
347: .getMirrorShape(armingPath)));
348: break;
349: case BOUNDINGLEAF: // use BoundingLeaf.transformedRegion
350: if (!armingNode.source.isLive()) {
351: return;
352: }
353: this .boundingLeaf = ((BoundingLeafRetained) armingNode).mirrorBoundingLeaf;
354: break;
355: case BOUND: // use this.vwcBounds
356: vwcBounds = (Bounds) armingBounds.clone();
357: this .armingNode = behav;
358: break;
359: case GROUP:
360: if (!armingNode.source.isLive()) {
361: return;
362: }
363: if (accuracyMode == USE_GEOMETRY) {
364: if (geometryAtoms == null) {
365: geometryAtoms = new UnorderList(1,
366: GeometryAtom.class);
367: }
368: ((GroupRetained) armingNode)
369: .searchGeometryAtoms(geometryAtoms);
370: }
371: // else use this.vwcBounds
372: default:
373: }
374:
375: behav.universe.geometryStructure.addWakeupOnCollision(this );
376: }
377:
378: /**
379: * This is a callback from BehaviorStructure. It is
380: * used to remove wakeupCondition from behavior structure.
381: */
382: void removeBehaviorCondition(BehaviorStructure bs) {
383: vwcBounds = null;
384: if (geometryAtoms != null) {
385: geometryAtoms.clear();
386: }
387: boundingLeaf = null;
388: behav.universe.geometryStructure.removeWakeupOnCollision(this );
389: }
390:
391: // Set collidingPath & collidingBounds
392: void setTarget(BHLeafInterface leaf) {
393: SceneGraphPath path;
394: Bounds bound;
395:
396: if (leaf instanceof GeometryAtom) {
397: // Find the triggered Path & Bounds for this geometry Atom
398: GeometryAtom geomAtom = (GeometryAtom) leaf;
399: Shape3DRetained shape = geomAtom.source;
400:
401: path = getSceneGraphPath(shape.sourceNode, shape.key, shape
402: .getCurrentLocalToVworld(0));
403: bound = getTriggeringBounds(shape);
404:
405: } else {
406: // Find the triggered Path & Bounds for this alternative
407: // collision target
408: GroupRetained group = (GroupRetained) leaf;
409: path = getSceneGraphPath(group);
410: bound = getTriggeringBounds(group);
411: }
412:
413: if (path != null) {
414: // colliding path may be null when branch detach before
415: // user behavior retrieve the previous colliding path
416: collidingPath = path;
417: collidingBounds = bound;
418: }
419: }
420:
421: // Invoke from GeometryStructure to update vwcBounds of GROUP
422: void updateCollisionBounds(boolean reEvaluateGAs) {
423: if (nodeType == GROUP) {
424: GroupRetained group = (GroupRetained) armingNode;
425: if (group.collisionBound != null) {
426: vwcBounds = (Bounds) group.collisionBound.clone();
427: } else {
428: // this may involve recursive tree traverse if
429: // BoundsAutoCompute is true, we can't avoid
430: // since the bound under it may change by transform
431: vwcBounds = group.getEffectiveBounds();
432: }
433: group.transformBounds(armingPath, vwcBounds);
434: } else if (nodeType == BOUND) {
435: vwcBounds.transform(armingBounds, behav
436: .getCurrentLocalToVworld());
437: }
438:
439: if (reEvaluateGAs && (nodeType == GROUP)
440: && (accuracyMode == USE_GEOMETRY)) {
441: geometryAtoms.clear();
442: ((GroupRetained) armingNode)
443: .searchGeometryAtoms(geometryAtoms);
444: }
445: }
446:
447: /**
448: * Return the TriggeringBounds for node
449: */
450: static Bounds getTriggeringBounds(Shape3DRetained mirrorShape) {
451: NodeRetained node = mirrorShape.sourceNode;
452:
453: if (node instanceof Shape3DRetained) {
454: Shape3DRetained shape = (Shape3DRetained) node;
455: if (shape.collisionBound == null) {
456: // TODO: get bounds by copy
457: return shape.getEffectiveBounds();
458: }
459: return shape.collisionBound;
460: }
461:
462: MorphRetained morph = (MorphRetained) node;
463: if (morph.collisionBound == null) {
464: // TODO: get bounds by copy
465: return morph.getEffectiveBounds();
466: }
467: return morph.collisionBound;
468: }
469:
470: /**
471: * Return the TriggeringBounds for node
472: */
473: static Bounds getTriggeringBounds(GroupRetained group) {
474: if (group.collisionBound == null) {
475: // TODO: get bounds by copy
476: return group.getEffectiveBounds();
477: }
478: return group.collisionBound;
479: }
480:
481: static SceneGraphPath getSceneGraphPath(GroupRetained group) {
482: // Find the transform base on the key
483: Transform3D transform = null;
484: GroupRetained srcGroup = group.sourceNode;
485:
486: synchronized (srcGroup.universe.sceneGraphLock) {
487: if (group.key == null) {
488: transform = srcGroup.getCurrentLocalToVworld();
489: } else {
490: HashKey keys[] = srcGroup.localToVworldKeys;
491: if (keys == null) {
492: // the branch is already detach when
493: // Collision got this message
494: return null;
495: }
496: transform = srcGroup.getCurrentLocalToVworld(group.key);
497: }
498: return getSceneGraphPath(srcGroup, group.key, transform);
499: }
500:
501: }
502:
503: /**
504: * return the SceneGraphPath of the geomAtom.
505: * Find the alternative Collision target closest to the locale.
506: */
507: static SceneGraphPath getSceneGraphPath(NodeRetained startNode,
508: HashKey key, Transform3D transform) {
509: synchronized (startNode.universe.sceneGraphLock) {
510: NodeRetained target = startNode;
511:
512: UnorderList path = new UnorderList(5, Node.class);
513: NodeRetained nodeR = target;
514: Locale locale = nodeR.locale;
515: String nodeId;
516: Vector parents;
517: NodeRetained linkR;
518:
519: if (nodeR.inSharedGroup) {
520: // getlastNodeId() will destroy this key
521: if (key != null) {
522: key = new HashKey(key);
523: } else {
524: key = new HashKey(startNode.localToVworldKeys[0]);
525: }
526: }
527:
528: do {
529: if (nodeR.source
530: .getCapability(Node.ENABLE_COLLISION_REPORTING)) {
531: path.add(nodeR.source);
532: }
533:
534: if (nodeR instanceof SharedGroupRetained) {
535:
536: // retrieve the last node ID
537: nodeId = key.getLastNodeId();
538: parents = ((SharedGroupRetained) nodeR).parents;
539: NodeRetained prevNodeR = nodeR;
540: for (int i = parents.size() - 1; i >= 0; i--) {
541: linkR = (NodeRetained) parents.elementAt(i);
542: if (linkR.nodeId.equals(nodeId)) {
543: nodeR = linkR;
544: break;
545: }
546: }
547: if (nodeR == prevNodeR) {
548: // the branch is already detach when
549: // Collision got this message
550: return null;
551: }
552: } else if ((nodeR instanceof GroupRetained)
553: && ((GroupRetained) nodeR).collisionTarget) {
554: // we need to find the collision target closest to the
555: // root of tree
556: target = nodeR;
557:
558: if (key == null) {
559: transform = nodeR.getCurrentLocalToVworld(null);
560: } else {
561: transform = nodeR.getCurrentLocalToVworld(key);
562: }
563: }
564: nodeR = nodeR.parent;
565: } while (nodeR != null); // reach Locale
566:
567: Node nodes[];
568: if (target == startNode) { // in most case
569: nodes = (Node[]) path.toArray(false);
570: } else { // alternativeCollisionTarget is set
571: nodes = (Node[]) path.toArray(target);
572: }
573: SceneGraphPath sgpath = new SceneGraphPath(locale, nodes,
574: (Node) target.source);
575: sgpath.setTransform(transform);
576: return sgpath;
577: }
578: }
579:
580: void setTriggered() {
581: // if path not set, probably the branch is just detach.
582: if (collidingPath != null) {
583: super .setTriggered();
584: }
585: }
586:
587: /**
588: * Perform task in addBehaviorCondition() that has to be
589: * set every time the condition met.
590: */
591: void resetBehaviorCondition(BehaviorStructure bs) {
592: // The reference geometryAtom will not change once
593: // Shape3D create so there is no need to set this.
594: }
595: }
|