aboutsummaryrefslogtreecommitdiffstats
path: root/src/javax/media/j3d/WakeupOnCollisionEntry.java
blob: 27fcd63f2238d526254f65c12af1c3eab7a84953 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
/*
 * Copyright 1997-2008 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 *
 */

package javax.media.j3d;

import java.util.Vector;

/**
 * Class specifying a wakeup when the specified object
 * collides with any other object in the scene graph.
 *
 */
public final class WakeupOnCollisionEntry extends WakeupCriterion {

  // different types of WakeupIndexedList that use in GeometryStructure
    static final int COND_IN_GS_LIST = 0;
    static final int COLLIDEENTRY_IN_BS_LIST = 1;

    // total number of different IndexedUnorderedSet types
    static final int TOTAL_INDEXED_UNORDER_SET_TYPES = 2;

    /**
     * Use geometry in computing collisions.
     */
    public static final int USE_GEOMETRY = 10;

    /**
     * Use geometric bounds as an approximation in computing collisions.
     */
    public static final int USE_BOUNDS = 11;

    static final int GROUP = NodeRetained.GROUP;
    static final int BOUNDINGLEAF = NodeRetained.BOUNDINGLEAF;
    static final int SHAPE = NodeRetained.SHAPE;
    static final int MORPH = NodeRetained.MORPH;
    static final int ORIENTEDSHAPE3D = NodeRetained.ORIENTEDSHAPE3D;
    static final int BOUND = 0;

    /**
     * Accuracy mode one of USE_GEOMETRY or USE_BOUNDS
     */
    int accuracyMode;

    // Cached the arming Node being used when it is not BOUND
    NodeRetained armingNode;

    // A transformed Bounds of Group/Bounds, use by
    // BOUND, GROUP
    Bounds vwcBounds = null;

    // Use by BoundingLeaf, point to mirror BoundingLeaf
    // transformedRegion under this leaf is used.
    BoundingLeafRetained boundingLeaf = null;

    /**
     * Geometry atoms that this wakeup condition refer to.
     * Only use by SHAPE, MORPH, GROUP, ORIENTEDSHAPE
     */
    UnorderList geometryAtoms = null;

    // one of GROUP, BOUNDINGLEAF, SHAPE, MORPH, BOUND
    int nodeType;

    SceneGraphPath armingPath = null;
    Bounds armingBounds = null;

    // the following two references are set only after a collision
    // has occurred
    Bounds collidingBounds = null;
    SceneGraphPath collidingPath = null;

    /**
     * Constructs a new WakeupOnCollisionEntry criterion with
     * USE_BOUNDS for a speed hint.
     * @param armingPath the path used to <em>arm</em> collision
     * detection
     * @exception IllegalArgumentException if object associated with the
     * SceneGraphPath is other than a Group, Shape3D, Morph, or
     * BoundingLeaf node.
     */
    public WakeupOnCollisionEntry(SceneGraphPath armingPath) {
	this(armingPath, USE_BOUNDS);
    }

    /**
     * Constructs a new WakeupOnCollisionEntry criterion.
     * @param armingPath the path used to <em>arm</em> collision
     * detection
     * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how
     * accurately Java 3D will perform collision detection
     * @exception IllegalArgumentException if hint is not one of
     * USE_GEOMETRY or USE_BOUNDS.
     * @exception IllegalArgumentException if object associated with the
     * SceneGraphPath is other than a Group, Shape3D, Morph, or
     * BoundingLeaf node.
     */
    public WakeupOnCollisionEntry(SceneGraphPath armingPath,
				  int speedHint) {
	this(new SceneGraphPath(armingPath), speedHint, null);
    }

    /**
     * Constructs a new WakeupOnCollisionEntry criterion.
     * @param armingNode the Group, Shape, or Morph node used to
     * <em>arm</em> collision detection
     * @exception IllegalArgumentException if object is under a
     * SharedGroup node or object is other than a Group, Shape3D,
     * Morph or BoundingLeaf node.
     */
    public WakeupOnCollisionEntry(Node armingNode) {
	this(armingNode, USE_BOUNDS);
    }

    /**
     * Constructs a new WakeupOnCollisionEntry criterion.
     * @param armingNode the Group, Shape, or Morph node used to
     * <em>arm</em> collision detection
     * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how
     * accurately Java 3D will perform collision detection
     * @exception IllegalArgumentException if hint is not one of
     * USE_GEOMETRY or USE_BOUNDS.
     * @exception IllegalArgumentException if object is under a
     * SharedGroup node or object is other than a Group, Shape3D,
     * Morph or BoundingLeaf node.
     */
    public WakeupOnCollisionEntry(Node armingNode, int speedHint) {
	this(new SceneGraphPath(null, armingNode), speedHint, null);
    }


    /**
     * Constructs a new WakeupOnCollisionEntry criterion.
     * @param armingBounds the bounds object used to <em>arm</em> collision
     * detection
     */
    public WakeupOnCollisionEntry(Bounds armingBounds) {
	this(null, USE_BOUNDS,  (Bounds) armingBounds.clone());
    }

    /**
     * Constructs a new WakeupOnCollisionEntry criterion.
     * @param armingPath the path used to <em>arm</em> collision
     * detection
     * @param speedHint one of USE_GEOMETRY or USE_BOUNDS, specifies how
     * accurately Java 3D will perform collision detection
     * @param armingBounds the bounds object used to <em>arm</em> collision
     * detection
     * @exception IllegalArgumentException if hint is not one of
     * USE_GEOMETRY or USE_BOUNDS.
     * @exception IllegalArgumentException if object associated with the
     * SceneGraphPath is other than a Group, Shape3D, Morph, or
     * BoundingLeaf node.
     */
    WakeupOnCollisionEntry(SceneGraphPath armingPath,
			   int speedHint, Bounds armingBounds) {
	if (armingPath != null) {
	    this.armingNode = (NodeRetained) armingPath.getObject().retained;
	    nodeType = getNodeType(armingNode, armingPath,
				   "WakeupOnCollisionEntry");
	    this.armingPath = armingPath;
	    validateSpeedHint(speedHint, "WakeupOnCollisionEntry4");
	} else {
	    this.armingBounds = armingBounds;
	    nodeType = BOUND;
	}
	accuracyMode = speedHint;
	WakeupIndexedList.init(this, TOTAL_INDEXED_UNORDER_SET_TYPES);
    }

    /**
     * Returns the path used in specifying the collision condition.
     * @return the SceneGraphPath object generated when arming this
     * criterion---null implies that a bounds object armed this criteria
     */
    public SceneGraphPath getArmingPath() {
	return (armingPath != null ?
		new SceneGraphPath(armingPath) : null);
    }

    /**
     * Returns the bounds object used in specifying the collision condition.
     * @return the Bounds object generated when arming this
     * criterion---null implies that a SceneGraphPath armed this criteria
     */
    public Bounds getArmingBounds() {
	return (armingBounds != null ?
		(Bounds)armingBounds.clone() : null);
    }

    /**
     * Retrieves the path describing the object causing the collision.
     * @return the SceneGraphPath that describes the triggering object.
     * @exception IllegalStateException if not called from within the
     * a behavior's processStimulus method which was awoken by a collision.
     */
    public SceneGraphPath getTriggeringPath() {
	if (behav == null) {
	    throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionEntry5"));
	}

	synchronized (behav) {
	    if (!behav.inCallback) {
		throw new IllegalStateException
		    (J3dI18N.getString("WakeupOnCollisionEntry5"));
	    }
	}
	return (collidingPath != null ?
		new SceneGraphPath(collidingPath): null);
    }

    /**
     * Retrieves the Bounds object that caused the collision
     * @return the colliding Bounds object.
     * @exception IllegalStateException if not called from within the
     * a behavior's processStimulus method which was awoken by a collision.
     */
    public Bounds getTriggeringBounds() {
	if (behav == null) {
	    throw new IllegalStateException(J3dI18N.getString("WakeupOnCollisionEntry6"));
	}

	synchronized (behav) {
	    if (!behav.inCallback) {
		throw new IllegalStateException
		    (J3dI18N.getString("WakeupOnCollisionEntry6"));
	    }
	}
	return (collidingBounds != null ?
		(Bounds)(collidingBounds.clone()): null);
    }


    /**
     * Node legality checker
     * throw Exception if node is not legal.
     * @return nodeType
     */
    static int getNodeType(NodeRetained armingNode,
			   SceneGraphPath armingPath, String s)
	throws IllegalArgumentException {

	// check if SceneGraphPath is unique
	// Note that graph may not live at this point so we
	// can't use node.inSharedGroup.
	if (!armingPath.validate()) {
	    throw new IllegalArgumentException(J3dI18N.getString(s + "7"));
	}

	if (armingNode.inBackgroundGroup) {
	    throw new IllegalArgumentException(J3dI18N.getString(s + "1"));
	}

	// This should come before Shape3DRetained check
	if (armingNode instanceof OrientedShape3DRetained) {
	    return ORIENTEDSHAPE3D;
	}

	if (armingNode instanceof Shape3DRetained) {
	    return SHAPE;
	}

	if (armingNode instanceof MorphRetained) {
	    return MORPH;
	}

	if (armingNode instanceof GroupRetained) {
	    return GROUP;
	}

	if (armingNode instanceof BoundingLeafRetained) {
	    return BOUNDINGLEAF;
	}

	throw new IllegalArgumentException(J3dI18N.getString(s + "0"));
    }

    /**
     * speedHint legality checker
     * throw Exception if speedHint is not legal
     */
    static void validateSpeedHint(int speedHint, String s)
	throws IllegalArgumentException {
	if ((speedHint != USE_GEOMETRY) && (speedHint != USE_BOUNDS)) {
	    throw new IllegalArgumentException(J3dI18N.getString(s));
	}

    }


    /**
     * This is a callback from BehaviorStructure. It is
     * used to add wakeupCondition to behavior structure.
     */
    @Override
    void addBehaviorCondition(BehaviorStructure bs) {

	switch (nodeType) {
	  case SHAPE:  // Use geometryAtoms[].collisionBounds
	  case ORIENTEDSHAPE3D:
	      if (!armingNode.source.isLive()) {
		  return;
	      }
	      if (geometryAtoms == null) {
		  geometryAtoms = new UnorderList(1, GeometryAtom.class);
	      }
	      Shape3DRetained shape = (Shape3DRetained) armingNode;
	      geometryAtoms.add(Shape3DRetained.getGeomAtom(shape.getMirrorShape(armingPath)));
	      break;
	  case MORPH:  // Use geometryAtoms[].collisionBounds
	      if (!armingNode.source.isLive()) {
		  return;
	      }
	      if (geometryAtoms == null) {
		  geometryAtoms = new UnorderList(1, GeometryAtom.class);
	      }
	      MorphRetained morph = (MorphRetained) armingNode;
	      geometryAtoms.add(Shape3DRetained.getGeomAtom(morph.getMirrorShape(armingPath)));
	      break;
 	  case BOUNDINGLEAF:  // use BoundingLeaf.transformedRegion
	      if (!armingNode.source.isLive()) {
		  return;
	      }
	      this.boundingLeaf = ((BoundingLeafRetained)  armingNode).mirrorBoundingLeaf;
	      break;
	  case BOUND: // use this.vwcBounds
	      vwcBounds = (Bounds) armingBounds.clone();
	      this.armingNode = behav;
	      break;
	  case GROUP:
	      if (!armingNode.source.isLive()) {
		  return;
	      }
	      if (accuracyMode == USE_GEOMETRY) {
		  if (geometryAtoms == null) {
		      geometryAtoms = new UnorderList(1, GeometryAtom.class);
		  }
		  ((GroupRetained) armingNode).searchGeometryAtoms(geometryAtoms);
	      }
	      // else use this.vwcBounds
	  default:
	}

	behav.universe.geometryStructure.addWakeupOnCollision(this);
    }

    /**
     * This is a callback from BehaviorStructure. It is
     * used to remove wakeupCondition from behavior structure.
     */
    @Override
    void removeBehaviorCondition(BehaviorStructure bs) {
	vwcBounds = null;
	if (geometryAtoms != null) {
	    geometryAtoms.clear();
	}
	boundingLeaf = null;
	behav.universe.geometryStructure.removeWakeupOnCollision(this);
    }


    // Set collidingPath & collidingBounds
    void setTarget(BHLeafInterface leaf) {
	SceneGraphPath path;
	Bounds bound;

	if (leaf instanceof GeometryAtom) {
	    // Find the triggered Path & Bounds for this geometry Atom
	    GeometryAtom geomAtom = (GeometryAtom) leaf;
	    Shape3DRetained shape = geomAtom.source;

	    path = getSceneGraphPath(shape.sourceNode,
				     shape.key,
				     shape.getCurrentLocalToVworld(0));
	    bound = getTriggeringBounds(shape);

	} else {
	    // Find the triggered Path & Bounds for this alternative
	    // collision target
	    GroupRetained  group = (GroupRetained) leaf;
	    path = getSceneGraphPath(group);
	    bound = getTriggeringBounds(group);
	}

	if (path != null) {
	    // colliding path may be null when branch detach before
	    // user behavior retrieve the previous colliding path
	    collidingPath = path;
	    collidingBounds = bound;
	}
    }


    // Invoke from GeometryStructure  to update vwcBounds of GROUP
    void updateCollisionBounds(boolean reEvaluateGAs){
	if (nodeType == GROUP) {
	    GroupRetained group = (GroupRetained) armingNode;
	    if (group.collisionBound != null) {
		vwcBounds = (Bounds) group.collisionBound.clone();
	    } else {
		// this may involve recursive tree traverse if
		// BoundsAutoCompute is true, we can't avoid
		// since the bound under it may change by transform
		vwcBounds = group.getEffectiveBounds();
	    }
	    group.transformBounds(armingPath, vwcBounds);
	} else if (nodeType == BOUND) {
	    vwcBounds.transform(armingBounds, behav.getCurrentLocalToVworld());
	}

	if (reEvaluateGAs &&
	    (nodeType == GROUP) &&
	    (accuracyMode == USE_GEOMETRY)) {
	    geometryAtoms.clear();
	    ((GroupRetained) armingNode).searchGeometryAtoms(geometryAtoms);
	}
    }


    /**
     * Return the TriggeringBounds for node
     */
    static Bounds getTriggeringBounds(Shape3DRetained mirrorShape) {
	NodeRetained node = mirrorShape.sourceNode;

	if (node instanceof Shape3DRetained) {
	    Shape3DRetained shape = (Shape3DRetained) node;
	    if (shape.collisionBound == null) {
		// TODO: get bounds by copy
		return shape.getEffectiveBounds();
	    }
	    return shape.collisionBound;
	}


	MorphRetained morph = (MorphRetained) node;
	if (morph.collisionBound == null) {
	    // TODO: get bounds by copy
	    return morph.getEffectiveBounds();
	}
	return morph.collisionBound;
    }


    /**
     * Return the TriggeringBounds for node
     */
    static Bounds getTriggeringBounds(GroupRetained group) {
	if (group.collisionBound == null) {
	    // TODO: get bounds by copy
	    return group.getEffectiveBounds();
	}
	return group.collisionBound;
    }

    static SceneGraphPath getSceneGraphPath(GroupRetained group) {
	// Find the transform base on the key
	Transform3D transform = null;
	GroupRetained srcGroup = group.sourceNode;

	synchronized (srcGroup.universe.sceneGraphLock) {
	    if (group.key == null) {
		transform = srcGroup.getCurrentLocalToVworld();
	    } else {
		HashKey keys[] = srcGroup.localToVworldKeys;
		if (keys == null) {
		    // the branch is already detach when
		    // Collision got this message
		    return null;
		}
		transform = srcGroup.getCurrentLocalToVworld(group.key);
	    }
	    return getSceneGraphPath(srcGroup, group.key, transform);
	}

    }

    /**
     * return the SceneGraphPath of the geomAtom.
     * Find the alternative Collision target closest to the locale.
     */
    static SceneGraphPath getSceneGraphPath(NodeRetained startNode,
					    HashKey key,
					    Transform3D transform) {
	synchronized (startNode.universe.sceneGraphLock) {
	    NodeRetained target = startNode;

	    UnorderList path = new UnorderList(5, Node.class);
	    NodeRetained nodeR = target;
	    Locale locale = nodeR.locale;
	    String nodeId;

	    if (nodeR.inSharedGroup) {
		// getlastNodeId() will destroy this key
		if (key != null) {
		    key = new HashKey(key);
		} else {
		    key = new HashKey(startNode.localToVworldKeys[0]);
		}
	    }

	    do {
		if (nodeR.source.getCapability(Node.ENABLE_COLLISION_REPORTING)){
		    path.add(nodeR.source);
		}

		if (nodeR instanceof SharedGroupRetained) {

		    // retrieve the last node ID
		    nodeId = key.getLastNodeId();
			Vector<NodeRetained> parents = ((SharedGroupRetained)nodeR).parents;
		    NodeRetained prevNodeR = nodeR;
		    for(int i=parents.size()-1; i >=0; i--) {
			NodeRetained linkR = parents.get(i);
			if (linkR.nodeId.equals(nodeId)) {
			    nodeR = linkR;
			    break;
			}
		    }
		    if (nodeR == prevNodeR) {
			// the branch is already detach when
			// Collision got this message
			return null;
		    }
		} else if ((nodeR instanceof GroupRetained) &&
			   ((GroupRetained) nodeR).collisionTarget) {
		    // we need to find the collision target closest to the
		    // root of tree
		    target = nodeR;

		    if (key == null) {
			transform = nodeR.getCurrentLocalToVworld(null);
		    } else {
			transform = nodeR.getCurrentLocalToVworld(key);
		    }
		}
		nodeR = nodeR.parent;
	    } while (nodeR != null); // reach Locale

	    Node nodes[];
	    if (target == startNode) { // in most case
		nodes = (Node []) path.toArray(false);
	    } else { // alternativeCollisionTarget is set
		nodes = (Node []) path.toArray(target);
	    }
	    SceneGraphPath sgpath = new SceneGraphPath(locale,
						       nodes,
						       (Node) target.source);
	    sgpath.setTransform(transform);
	    return sgpath;
	}
    }


    @Override
    void setTriggered(){
	// if path not set, probably the branch is just detach.
	if (collidingPath != null) {
	    super.setTriggered();
	}
    }


    /**
     * Perform task in addBehaviorCondition() that has to be
     * set every time the condition met.
     */
    @Override
    void resetBehaviorCondition(BehaviorStructure bs) {
	// The reference geometryAtom will not change once
	// Shape3D create so there is no need to set this.
    }
}