1 /*
2  * $RCSfile: WakeupOnCollisionEntry.java,v $
3  *
4  * Copyright 1997-2008 Sun Microsystems, Inc.  All Rights Reserved.
5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
6  *
7  * This code is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License version 2 only, as
9  * published by the Free Software Foundation.  Sun designates this
10  * particular file as subject to the "Classpath" exception as provided
11  * by Sun in the LICENSE file that accompanied this code.
12  *
13  * This code is distributed in the hope that it will be useful, but WITHOUT
14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16  * version 2 for more details (a copy is included in the LICENSE file that
17  * accompanied this code).
18  *
19  * You should have received a copy of the GNU General Public License version
20  * 2 along with this work; if not, write to the Free Software Foundation,
21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
22  *
23  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
24  * CA 95054 USA or visit www.sun.com if you need additional information or
25  * have any questions.
26  *
27  * $Revision: 1.5 $
28  * $Date: 2008/02/28 20:17:33 $
29  * $State: Exp $
30  */
31 
32 package javax.media.j3d;
33 
34 import java.util.*;
35 
36 /**
37  * Class specifying a wakeup when the specified object
38  * collides with any other object in the scene graph.
39  *
40  */
41 public final class WakeupOnCollisionEntry extends WakeupCriterion {
42 
43   // different types of WakeupIndexedList that use in GeometryStructure
44     static final int COND_IN_GS_LIST = 0;
45     static final int COLLIDEENTRY_IN_BS_LIST = 1;
46 
47     // total number of different IndexedUnorderedSet types
48     static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2;
49 
50     /**
51      * Use geometry in computing collisions.
52      */
53     public static final int USE_GEOMETRY = 10;
54 
55     /**
56      * Use geometric bounds as an approximation in computing collisions.
57      */
58     public static final int USE_BOUNDS = 11;
59 
60     static final int GROUP = NodeRetained.GROUP;
61     static final int BOUNDINGLEAF = NodeRetained.BOUNDINGLEAF;
62     static final int SHAPE = NodeRetained.SHAPE;
63     static final int MORPH = NodeRetained.MORPH;
64     static final int ORIENTEDSHAPE3D = NodeRetained.ORIENTEDSHAPE3D;
65     static final int BOUND = 0;
66 
67     /**
68      * Accuracy mode one of USE_GEOMETRY or USE_BOUNDS
69      */
70     int accuracyMode;
71 
72     // Cached the arming Node being used when it is not BOUND
73     NodeRetained armingNode;
74 
75     // A transformed Bounds of Group/Bounds, use by
76     // BOUND, GROUP
77     Bounds vwcBounds = null;
78 
79     // Use by BoundingLeaf, point to mirror BoundingLeaf
80     // transformedRegion under this leaf is used.
81     BoundingLeafRetained boundingLeaf = null;
82 
83     /**
84      * Geometry atoms that this wakeup condition refer to.
85      * Only use by SHAPE, MORPH, GROUP, ORIENTEDSHAPE
86      */
87     UnorderList geometryAtoms = null;
88 
89     // one of GROUP, BOUNDINGLEAF, SHAPE, MORPH, BOUND
90     int nodeType;
91 
92     SceneGraphPath armingPath = null;
93     Bounds armingBounds = null;
94 
95     // the following two references are set only after a collision
96     // has occurred
97     Bounds collidingBounds = null;
98     SceneGraphPath collidingPath = null;
99 
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      */
WakeupOnCollisionEntry(SceneGraphPath armingPath)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      */
WakeupOnCollisionEntry(SceneGraphPath armingPath, int speedHint)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      */
WakeupOnCollisionEntry(Node armingNode)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      */
WakeupOnCollisionEntry(Node armingNode, int speedHint)154     public WakeupOnCollisionEntry(Node armingNode, int speedHint) {
155 	this(new SceneGraphPath(null, armingNode), speedHint, null);
156     }
157 
158 
159     /**
160      * Constructs a new WakeupOnCollisionEntry criterion.
161      * @param armingBounds the bounds object used to <em>arm</em> collision
162      * detection
163      */
WakeupOnCollisionEntry(Bounds armingBounds)164     public WakeupOnCollisionEntry(Bounds armingBounds) {
165 	this(null, USE_BOUNDS,  (Bounds) armingBounds.clone());
166     }
167 
168     /**
169      * Constructs a new WakeupOnCollisionEntry criterion.
170      * @param armingPath the path used to <em>arm</em> collision
171      * detection
172      * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how
173      * accurately Java 3D will perform collision detection
174      * @param armingBounds the bounds object used to <em>arm</em> collision
175      * detection
176      * @exception IllegalArgumentException if hint is not one of
177      * USE_GEOMETRY or USE_BOUNDS.
178      * @exception IllegalArgumentException if object associated with the
179      * SceneGraphPath is other than a Group, Shape3D, Morph, or
180      * BoundingLeaf node.
181      */
WakeupOnCollisionEntry(SceneGraphPath armingPath, int speedHint, Bounds armingBounds)182     WakeupOnCollisionEntry(SceneGraphPath armingPath,
183 			   int speedHint, Bounds armingBounds) {
184 	if (armingPath != null) {
185 	    this.armingNode = (NodeRetained) armingPath.getObject().retained;
186 	    nodeType = getNodeType(armingNode, armingPath,
187 				   "WakeupOnCollisionEntry");
188 	    this.armingPath = armingPath;
189 	    validateSpeedHint(speedHint, "WakeupOnCollisionEntry4");
190 	} else {
191 	    this.armingBounds = armingBounds;
192 	    nodeType = BOUND;
193 	}
194 	accuracyMode = speedHint;
195 	WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES);
196     }
197 
198     /**
199      * Returns the path used in specifying the collision condition.
200      * @return the SceneGraphPath object generated when arming this
201      * criterion---null implies that a bounds object armed this criteria
202      */
getArmingPath()203     public SceneGraphPath getArmingPath() {
204 	return (armingPath != null ?
205 		new SceneGraphPath(armingPath) : null);
206     }
207 
208     /**
209      * Returns the bounds object used in specifying the collision condition.
210      * @return the Bounds object generated when arming this
211      * criterion---null implies that a SceneGraphPath armed this criteria
212      */
getArmingBounds()213     public Bounds getArmingBounds() {
214 	return (armingBounds != null ?
215 		(Bounds)armingBounds.clone() : null);
216     }
217 
218     /**
219      * Retrieves the path describing the object causing the collision.
220      * @return the SceneGraphPath that describes the triggering object.
221      * @exception IllegalStateException if not called from within the
222      * a behavior's processStimulus method which was awoken by a collision.
223      */
getTriggeringPath()224     public SceneGraphPath getTriggeringPath() {
225 	if (behav == null) {
226 	    throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionEntry5"));
227 	}
228 
229 	synchronized (behav) {
230 	    if (!behav.inCallback) {
231 		throw new IllegalStateException
232 		    (J3dI18N.getString("WakeupOnCollisionEntry5"));
233 	    }
234 	}
235 	return (collidingPath != null ?
236 		new SceneGraphPath(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      */
getTriggeringBounds()245     public Bounds getTriggeringBounds() {
246 	if (behav == null) {
247 	    throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionEntry6"));
248 	}
249 
250 	synchronized (behav) {
251 	    if (!behav.inCallback) {
252 		throw new IllegalStateException
253 		    (J3dI18N.getString("WakeupOnCollisionEntry6"));
254 	    }
255 	}
256 	return (collidingBounds != null ?
257 		(Bounds)(collidingBounds.clone()): null);
258     }
259 
260 
261     /**
262      * Node legality checker
263      * throw Exception if node is not legal.
264      * @return nodeType
265      */
getNodeType(NodeRetained armingNode, SceneGraphPath armingPath, String s)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 + "7"));
275 	}
276 
277 	if (armingNode.inBackgroundGroup) {
278 	    throw new IllegalArgumentException(J3dI18N.getString(s + "1"));
279 	}
280 
281 	// This should come before Shape3DRetained check
282 	if (armingNode instanceof OrientedShape3DRetained) {
283 	    return ORIENTEDSHAPE3D;
284 	}
285 
286 	if (armingNode instanceof Shape3DRetained) {
287 	    return SHAPE;
288 	}
289 
290 	if (armingNode instanceof MorphRetained) {
291 	    return MORPH;
292 	}
293 
294 	if (armingNode instanceof GroupRetained) {
295 	    return GROUP;
296 	}
297 
298 	if (armingNode instanceof BoundingLeafRetained) {
299 	    return BOUNDINGLEAF;
300 	}
301 
302 	throw new IllegalArgumentException(J3dI18N.getString(s + "0"));
303     }
304 
305     /**
306      * speedHint legality checker
307      * throw Exception if speedHint is not legal
308      */
validateSpeedHint(int speedHint, String s)309     static void validateSpeedHint(int speedHint, String s)
310 	throws IllegalArgumentException {
311 	if ((speedHint != USE_GEOMETRY) && (speedHint != USE_BOUNDS)) {
312 	    throw new IllegalArgumentException(J3dI18N.getString(s));
313 	}
314 
315     }
316 
317 
318     /**
319      * This is a callback from BehaviorStructure. It is
320      * used to add wakeupCondition to behavior structure.
321      */
addBehaviorCondition(BehaviorStructure bs)322     void addBehaviorCondition(BehaviorStructure bs) {
323 
324 	switch (nodeType) {
325 	  case SHAPE:  // Use geometryAtoms[].collisionBounds
326 	  case ORIENTEDSHAPE3D:
327 	      if (!armingNode.source.isLive()) {
328 		  return;
329 	      }
330 	      if (geometryAtoms == null) {
331 		  geometryAtoms = new UnorderList(1, GeometryAtom.class);
332 	      }
333 	      Shape3DRetained shape = (Shape3DRetained) armingNode;
334 	      geometryAtoms.add(Shape3DRetained.getGeomAtom(shape.getMirrorShape(armingPath)));
335 	      break;
336 	  case MORPH:  // Use geometryAtoms[].collisionBounds
337 	      if (!armingNode.source.isLive()) {
338 		  return;
339 	      }
340 	      if (geometryAtoms == null) {
341 		  geometryAtoms = new UnorderList(1, GeometryAtom.class);
342 	      }
343 	      MorphRetained morph = (MorphRetained) armingNode;
344 	      geometryAtoms.add(Shape3DRetained.getGeomAtom(morph.getMirrorShape(armingPath)));
345 	      break;
346  	  case BOUNDINGLEAF:  // use BoundingLeaf.transformedRegion
347 	      if (!armingNode.source.isLive()) {
348 		  return;
349 	      }
350 	      this.boundingLeaf = ((BoundingLeafRetained)  armingNode).mirrorBoundingLeaf;
351 	      break;
352 	  case BOUND: // use this.vwcBounds
353 	      vwcBounds = (Bounds) armingBounds.clone();
354 	      this.armingNode = behav;
355 	      break;
356 	  case GROUP:
357 	      if (!armingNode.source.isLive()) {
358 		  return;
359 	      }
360 	      if (accuracyMode == USE_GEOMETRY) {
361 		  if (geometryAtoms == null) {
362 		      geometryAtoms = new UnorderList(1, GeometryAtom.class);
363 		  }
364 		  ((GroupRetained) armingNode).searchGeometryAtoms(geometryAtoms);
365 	      }
366 	      // else use this.vwcBounds
367 	  default:
368 	}
369 
370 	behav.universe.geometryStructure.addWakeupOnCollision(this);
371     }
372 
373     /**
374      * This is a callback from BehaviorStructure. It is
375      * used to remove wakeupCondition from behavior structure.
376      */
removeBehaviorCondition(BehaviorStructure bs)377     void removeBehaviorCondition(BehaviorStructure bs) {
378 	vwcBounds = null;
379 	if (geometryAtoms != null) {
380 	    geometryAtoms.clear();
381 	}
382 	boundingLeaf = null;
383 	behav.universe.geometryStructure.removeWakeupOnCollision(this);
384     }
385 
386 
387     // Set collidingPath & collidingBounds
setTarget(BHLeafInterface leaf)388     void setTarget(BHLeafInterface leaf) {
389 	SceneGraphPath path;
390 	Bounds bound;
391 
392 	if (leaf instanceof GeometryAtom) {
393 	    // Find the triggered Path & Bounds for this geometry Atom
394 	    GeometryAtom geomAtom = (GeometryAtom) leaf;
395 	    Shape3DRetained shape = geomAtom.source;
396 
397 	    path = getSceneGraphPath(shape.sourceNode,
398 				     shape.key,
399 				     shape.getCurrentLocalToVworld(0));
400 	    bound = getTriggeringBounds(shape);
401 
402 	} else {
403 	    // Find the triggered Path & Bounds for this alternative
404 	    // collision target
405 	    GroupRetained  group = (GroupRetained) leaf;
406 	    path = getSceneGraphPath(group);
407 	    bound = getTriggeringBounds(group);
408 	}
409 
410 	if (path != null) {
411 	    // colliding path may be null when branch detach before
412 	    // user behavior retrieve the previous colliding path
413 	    collidingPath = path;
414 	    collidingBounds = bound;
415 	}
416     }
417 
418 
419     // Invoke from GeometryStructure  to update vwcBounds of GROUP
updateCollisionBounds(boolean reEvaluateGAs)420     void updateCollisionBounds(boolean reEvaluateGAs){
421 	if (nodeType == GROUP) {
422 	    GroupRetained group = (GroupRetained) armingNode;
423 	    if (group.collisionBound != null) {
424 		vwcBounds = (Bounds) group.collisionBound.clone();
425 	    } else {
426 		// this may involve recursive tree traverse if
427 		// BoundsAutoCompute is true, we can't avoid
428 		// since the bound under it may change by transform
429 		vwcBounds = group.getEffectiveBounds();
430 	    }
431 	    group.transformBounds(armingPath, vwcBounds);
432 	} else if (nodeType == BOUND) {
433 	    vwcBounds.transform(armingBounds, behav.getCurrentLocalToVworld());
434 	}
435 
436 	if (reEvaluateGAs &&
437 	    (nodeType == GROUP) &&
438 	    (accuracyMode == USE_GEOMETRY)) {
439 	    geometryAtoms.clear();
440 	    ((GroupRetained) armingNode).searchGeometryAtoms(geometryAtoms);
441 	}
442     }
443 
444 
445     /**
446      * Return the TriggeringBounds for node
447      */
getTriggeringBounds(Shape3DRetained mirrorShape)448     static Bounds getTriggeringBounds(Shape3DRetained mirrorShape) {
449 	NodeRetained node = mirrorShape.sourceNode;
450 
451 	if (node instanceof Shape3DRetained) {
452 	    Shape3DRetained shape = (Shape3DRetained) node;
453 	    if (shape.collisionBound == null) {
454 		// TODO: get bounds by copy
455 		return shape.getEffectiveBounds();
456 	    }
457 	    return shape.collisionBound;
458 	}
459 
460 
461 	MorphRetained morph = (MorphRetained) node;
462 	if (morph.collisionBound == null) {
463 	    // TODO: get bounds by copy
464 	    return morph.getEffectiveBounds();
465 	}
466 	return morph.collisionBound;
467     }
468 
469 
470     /**
471      * Return the TriggeringBounds for node
472      */
getTriggeringBounds(GroupRetained group)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 
getSceneGraphPath(GroupRetained group)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      */
getSceneGraphPath(NodeRetained startNode, HashKey key, Transform3D transform)507     static SceneGraphPath getSceneGraphPath(NodeRetained startNode,
508 					    HashKey key,
509 					    Transform3D transform) {
510 	synchronized (startNode.universe.sceneGraphLock) {
511 	    NodeRetained target = startNode;
512 
513 	    UnorderList path = new UnorderList(5, Node.class);
514 	    NodeRetained nodeR = target;
515 	    Locale locale = nodeR.locale;
516 	    String nodeId;
517 	    Vector parents;
518 	    NodeRetained linkR;
519 
520 	    if (nodeR.inSharedGroup) {
521 		// getlastNodeId() will destroy this key
522 		if (key != null) {
523 		    key = new HashKey(key);
524 		} else {
525 		    key = new HashKey(startNode.localToVworldKeys[0]);
526 		}
527 	    }
528 
529 	    do {
530 		if (nodeR.source.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,
574 						       nodes,
575 						       (Node) target.source);
576 	    sgpath.setTransform(transform);
577 	    return sgpath;
578 	}
579     }
580 
581 
setTriggered()582     void setTriggered(){
583 	// if path not set, probably the branch is just detach.
584 	if (collidingPath != null) {
585 	    super.setTriggered();
586 	}
587     }
588 
589 
590     /**
591      * Perform task in addBehaviorCondition() that has to be
592      * set every time the condition met.
593      */
resetBehaviorCondition(BehaviorStructure bs)594     void resetBehaviorCondition(BehaviorStructure bs) {
595 	// The reference geometryAtom will not change once
596 	// Shape3D create so there is no need to set this.
597     }
598 }
599