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