001 package physics3d; 002 003 /** 004 * <p> 005 * 006 * The Geometry3D library contains procedural abstractions which are useful in 007 * modeling the physical interactions between various 3 dimensional objects. 008 * 009 * <p> 010 * The library is described in terms of these concepts: <br> 011 * <ul> 012 * <li> object - a ball or a bouncer 013 * <li> ball - a circle with position and velocity 014 * <li> bouncer - a plane polygon, a plane circle, a sphere, the outside of a 015 * lateral cylinder, and a torus 016 * </ul> 017 * 018 * <p> 019 * The intended use of the Geometry3D library is as follows: 020 * 021 * <p> 022 * <ol> 023 * <li> The client calls the timeUntilCollision() methods to calculate the times 024 * at which the ball(s) will collide with each of the bouncers or with another 025 * ball. The minimum of all these times (call it "mintime") is the time of the 026 * next collision. 027 * 028 * <li> The client updates the position of the ball(s) and the bouncers to 029 * account for mintime passing. At this point, the ball and the object it is 030 * about to hit are exactly adjacent to one another. 031 * 032 * <li> The client calls the appropriate reflect() method to calculate the 033 * change in the ball's velocity. 034 * 035 * <li>The client updates the ball's velocity and repeats back to step 1. 036 * 037 * </ol> 038 * 039 * <p> 040 * <a name="constant_velocity"></a> 041 * 042 * <p> 043 * The timeUntilCollision() methods assume constant ball velocity. That is, no 044 * force will be acting on the ball, so it will follow a straight-line path. 045 * Therefore, if external forces (such as gravity or friction) need to be 046 * accounted for, the client must do so before or after the of the "time until / 047 * update position / reflect" series of steps - never inbetween those three 048 * steps. 049 * 050 * <p> 051 * <a name="endpoint_effects"></a> 052 * 053 * <b>Important note</b>: The methods which deal with plane polygon, plane 054 * circle and lateral cylinder bouncers do NOT deal with the edges of these 055 * surfaces. To ensure realistic behavior, shapes should be constructed from a 056 * combination of plane polygons, plane circles, lateral cylinders, spheres and 057 * tori. To connect plane polygons together, have zero radius cylinders at the 058 * edges and zero radius spheres on the points. To connect plane circles to 059 * lateral cylinders, put a zero tube raius torus on the ring. 060 * 061 * <p> 062 * For example: A ball is located at (0,0,1.5) and is moving in the (1,1,0) 063 * direction towards a unit cube parallel to all 3 axes with minimum (x,y,z) = 064 * (1,1,1) The ball will hit the ends of both faces at a 45 degree angle and 065 * something REALLY WEIRD will happen. However, if a cylinder with zero radius 066 * is placed along the edges of the cube, then the ball will bounce off the 067 * cylinder in the expected manner. Likewise for points and tori. 068 * </p> 069 * 070 * @specfield maximumForesight : double // maximum time used to search for a 071 * collision. Default is .03 072 * @specfield numSubIntervals : int // # of subintervals used to search an 073 * interval for a zero. More accurate, but slower for higher values. 074 * Default is 10. 075 */ 076 public strictfp class Geometry3D { 077 protected double maximumForesight; 078 079 protected int numSubIntervals; 080 081 // 082 // Abstraction function maximumForesight = this.maximumForesight 083 // numSubIntervals = this.numSubIntervals 084 // 085 086 // 087 // rep invariant maximumForesight > 0 numSubIntervals >= 1 088 // 089 @SuppressWarnings("unused") 090 private void checkRep() { 091 if (maximumForesight <= 0 || numSubIntervals < 1) { 092 throw new RuntimeException(); 093 } 094 } 095 096 /** 097 * @effects Constructs a Geometry3D with the default tuning parameters as 098 * described in the class overview. 099 */ 100 public Geometry3D() { 101 this(.03, 10); 102 } 103 104 /** 105 * @requires (maximumForesight >= 0.0) && (numSubIntervals >= 1) 106 * 107 * @effects Constructs a Geometry3D with the specified tuning parameters as 108 * described in the class overview. 109 */ 110 public Geometry3D(double maximumForesight, int numSubIntervals) { 111 if (!(maximumForesight >= 0.0)) { 112 throw new IllegalArgumentException(); 113 } 114 if (!(numSubIntervals >= 1)) { 115 throw new IllegalArgumentException(); 116 } 117 this.maximumForesight = maximumForesight; 118 this.numSubIntervals = numSubIntervals; 119 } 120 121 /** 122 * Modifies the behavior of this class to use the specified 123 * <code>maximumForesight & numSubIntervals</code>. 124 * 125 * @param maximumForesight 126 * The maximal time in the future that a collision will be searched 127 * for. Collisions may still be returned that happen farther than 128 * <code>maximumForesight</code> in the future, but no extra effort 129 * will be made to find them. 130 * 131 * @param numSubIntervals 132 * used to find roots of polynomials. Finding roots is linear in time 133 * with the number of sub intervals, but too few intervals could lead 134 * to missing a root. 135 */ 136 public void setForesightAndSubintervals(double maximumForesight, 137 int numSubIntervals) { 138 this.maximumForesight = maximumForesight; 139 this.numSubIntervals = numSubIntervals; 140 } 141 142 /** 143 * @return the maximum foresight measured in seconds, used for geometry 144 * calculations 145 */ 146 public double getMaximumForesight() { 147 return this.maximumForesight; 148 } 149 150 /** 151 * @return the number of subintervals used when searching an interval for a 152 * collision. This affects all rotating methods and the stationary 153 * torus collision. 154 */ 155 public int getNumSubIntervals() { 156 return this.numSubIntervals; 157 } 158 159 public double minQuadraticSolution(double a, double b, double c) { 160 if (a == 0.0) { 161 if (b == 0.0) { 162 return Double.NaN; 163 } else { 164 return -c / b; 165 } 166 } else { 167 double discriminant = (b * b) - (4.0 * a * c); 168 if (discriminant < 0.0) { 169 return Double.NaN; 170 } else { 171 double sqrt = Math.sqrt(discriminant); 172 double twoA = 2.0 * a; 173 double lesserNum = -b - sqrt; 174 double greaterNum = -b + sqrt; 175 if (a > 0) { 176 return lesserNum / twoA; 177 } else { 178 return greaterNum / twoA; 179 } 180 } 181 } 182 } 183 184 /** 185 * Accounts for the effects of inelastic collisions given the intial and 186 * resulting velocities of the collision assuming elasticity. 187 * 188 * @requires <code>rCoeff</code> >= 0 189 * 190 * @effects given an initial velocity, <code>incidentVect</code>, and the 191 * velocity resulting from an elastic collision, 192 * <code>reflectedVect</code>, and a reflection coefficient, 193 * <code>rCoeff</code>, returns the resulting velocity of the 194 * collision had it been inelastic with the given reflection 195 * coefficient. If the reflection coefficient is 1.0, the resulting 196 * velocity will be equal to <code>reflectedVect</code>. A 197 * reflection coefficient of 0 implies that the collision will absorb 198 * any energy that was reflected in the elastic case. 199 * 200 * @param incidentVect 201 * the intial velocity of the ball 202 * @param reflectedVect 203 * the resulting velocity after the collision assuming elasticity. 204 * @param rCoeff 205 * the reflection coefficent. 206 * 207 * @return the resulting velocity after an inelastic collision. 208 */ 209 public Vect3 applyReflectionCoeff(Vect3 incidentVect, Vect3 reflectedVect, 210 double rCoeff) { 211 return incidentVect.plus(reflectedVect.minus(incidentVect).times( 212 0.5 + 0.5 * rCoeff)); 213 } 214 215 /***************************************************************************** 216 * 217 * METHODS FOR STATIONARY OBJECTS 218 * 219 ****************************************************************************/ 220 221 public Vect3 pointWherePlaneIntersectsLine(Plane plane, Line line) { 222 // pointOnLine 223 Vect3 pointOnLine = line.getPointOnLine(); 224 // get normal 225 Vect3 normal = plane.getNormal(); 226 Vect3 obliqueVect = pointOnLine.minus(plane.getPointOnPlane()); 227 // get normal pointing to our side 228 Vect3 normalWeWant = obliqueVect.projectOnToB(normal).unitSize(); 229 // direction pointing towards plane 230 Vect3 directionWeWant = line.getDirection(); 231 if (directionWeWant.dot(normalWeWant) > 0) { 232 directionWeWant = directionWeWant.neg().unitSize(); 233 } 234 // acute angle between us and plane 235 Angle theta = directionWeWant.neg().angleBetween(normalWeWant); 236 double distance = plane.perpDistanceFromPlaneToPoint(pointOnLine); 237 if (Vect3.isAbout(theta.cos(), 0)) { 238 return null; 239 } 240 Vect3 totalDistance = directionWeWant.times(distance / theta.cos()); 241 return totalDistance.plus(pointOnLine); 242 } 243 244 /***************************************************************************** 245 * 246 * METHODS FOR STATIONARY PLANE POLYGONS 247 * 248 ****************************************************************************/ 249 250 /** 251 * Computes the time until a ball represented by a sphere, travelling at a 252 * specified velocity collides with a specified plane polygon. 253 * 254 * @requires ball.radius > 0 255 * 256 * @effects computes the time until a ball represented by a sphere, travelling 257 * at a specified velocity collides with a specified plane polygon. 258 * If no collision will occur <tt>POSITIVE_INFINITY</tt> is 259 * returned. This method assumes the ball travels with constant 260 * velocity until impact. 261 * 262 * @param polygon 263 * a plane polygon representing the circle with which the ball may 264 * collide 265 * 266 * @param ball 267 * a sphere representing the size and initial location of the ball 268 * 269 * @param velocity 270 * the velocity of the ball before impact 271 * 272 * @return the time until collision or <tt>POSITIVE_INFINITY</tt> if the 273 * collision will not occur 274 * 275 * @see Double#POSITIVE_INFINITY 276 */ 277 public double timeUntilPlanePolygonCollision(PlanePolygon polygon, 278 Sphere ball, Vect3 velocity) { 279 280 if(velocity.equals(Vect3.ZERO)) 281 return Double.POSITIVE_INFINITY; 282 283 // get normal 284 Vect3 normal = polygon.getNormal(); 285 Vect3 obliqueVect = ball.getCenter().minus(polygon.getVertices().next()); 286 // get normal pointing to our side 287 Vect3 normalWeWant = obliqueVect.projectOnToB(normal).unitSize(); 288 // make it length radius 289 Vect3 scaledNormal = normalWeWant.times(ball.getRadius()); 290 // find where our point of collision is on the sphere 291 Vect3 collisionPointOnSphere = ball.getCenter().minus(scaledNormal); 292 // find where this point intersects the plane 293 Line line = new Line(velocity, collisionPointOnSphere); 294 Vect3 collisionPointOnPlane = pointWherePlaneIntersectsLine(polygon 295 .planeContainingObject(), line); 296 // if parallel, return pos inf 297 if (collisionPointOnPlane == null) { 298 return Double.POSITIVE_INFINITY; 299 } 300 Vect3 pDVect = collisionPointOnPlane.minus(ball.getCenter()).projectOnToB( 301 normal); 302 Vect3 pVVect = velocity.projectOnToB(normal); 303 if (normalWeWant.dot(velocity) > 0 304 || pDVect.rho() * pDVect.rho() - ball.getRadius() * pDVect.rho() > pVVect 305 .dot(pDVect) 306 * maximumForesight) { 307 return Double.POSITIVE_INFINITY; 308 } 309 310 // if polygon contains this point AND we would hit it, return 311 // the time it would take 312 if (polygon.containsPoint(collisionPointOnPlane)) { 313 if (pDVect.rho() - ball.getRadius() < 0){ 314 if(polygon.minDistanceToObjectFromP(ball.getCenter()) < ball.getRadius()) 315 return 0; 316 else 317 return Double.POSITIVE_INFINITY; 318 } 319 else 320 return collisionPointOnPlane.minus(collisionPointOnSphere).rho() 321 / velocity.rho(); 322 } 323 // otherwise return infinity 324 return Double.POSITIVE_INFINITY; 325 } 326 327 /** 328 * Computes the new velocity of a ball reflecting off of a plane polygon. 329 * 330 * @requires <code>reflectionCoeff</code> >= 0 331 * 332 * @effects computes the new velocity of a ball reflecting off of a plane 333 * polygon. The velocity resulting from this method corresponds to a 334 * collision against a surface with the given reflection coefficient. 335 * A reflection coefficient of 1 indicates a perfectly elastic 336 * collision. This method assumes that the ball is at the point of 337 * impact. 338 * 339 * @param polygon 340 * the plane polygon which is being hit 341 * 342 * @param ball 343 * the ball 344 * 345 * @param velocity 346 * the velocity of the ball before impact 347 * 348 * @param reflectionCoeff 349 * the reflection coefficient 350 * 351 * @return the velocity of the ball after impacting the given plane polygon 352 */ 353 public Vect3 reflectPlanePolygon(PlanePolygon polygon, Sphere ball, 354 Vect3 velocity, double reflectionCoeff) { 355 return applyReflectionCoeff(velocity, reflectPlanePolygon(polygon, ball, 356 velocity), reflectionCoeff); 357 } 358 359 /** 360 * Computes the new velocity of a ball reflecting off of a plane polygon. 361 * 362 * @effects computes the new velocity of a ball reflecting off of a plane 363 * polygon. The velocity resulting from this method corresponds to a 364 * perfectly elastic collision. This method assumes that the ball is 365 * at the point of impact. 366 * 367 * @param polygon 368 * the plane polygon which is being hit 369 * 370 * @param ball 371 * the ball 372 * 373 * @param velocity 374 * the velocity of the ball before impact 375 * 376 * @return the velocity of the ball after impacting the given plane polygon 377 */ 378 public Vect3 reflectPlanePolygon(PlanePolygon polygon, Sphere ball, 379 Vect3 velocity) { 380 // get normal 381 Vect3 normal = polygon.getNormal(); 382 // rotate, flip, and return 383 velocity = (velocity.rotateAroundVect(normal, Angle.DEG_180)).neg(); 384 return velocity; 385 } 386 387 /***************************************************************************** 388 * 389 * METHODS FOR STATIONARY PLANE CIRCLES 390 * 391 ****************************************************************************/ 392 393 /** 394 * Computes the time until a ball represented by a sphere, travelling at a 395 * specified velocity collides with a specified plane circle. 396 * 397 * @requires ball.radius > 0 398 * 399 * @effects computes the time until a ball represented by a sphere, travelling 400 * at a specified velocity collides with a specified plane circle. If 401 * no collision will occur <tt>POSITIVE_INFINITY</tt> is returned. 402 * This method assumes the ball travels with constant velocity until 403 * impact. 404 * 405 * @param circle 406 * a plane circle representing the circle with which the ball may 407 * collide 408 * 409 * @param ball 410 * a sphere representing the size and initial location of the ball 411 * 412 * @param velocity 413 * the velocity of the ball before impact 414 * 415 * @return the time until collision or <tt>POSITIVE_INFINITY</tt> if the 416 * collision will not occur 417 * 418 * @see Double#POSITIVE_INFINITY 419 */ 420 public double timeUntilPlaneCircleCollision(PlaneCircle circle, Sphere ball, 421 Vect3 velocity) { 422 if(velocity.equals(Vect3.ZERO)) 423 return Double.POSITIVE_INFINITY; 424 Vect3 normal = circle.getNormal(); 425 Vect3 obliqueVect = ball.getCenter().minus(circle.getCenter()); 426 // get normal pointing to our side 427 Vect3 normalWeWant = obliqueVect.projectOnToB(normal).unitSize(); 428 // make it length radius 429 Vect3 scaledNormal = normalWeWant.times(ball.getRadius()); 430 // find where our point of collision is on the sphere 431 Vect3 collisionPointOnSphere = ball.getCenter().minus(scaledNormal); 432 // find where this point intersects the plane 433 Line line = new Line(velocity, collisionPointOnSphere); 434 Vect3 collisionPointOnPlane = pointWherePlaneIntersectsLine(circle 435 .planeContainingObject(), line); 436 // if parallel, return pos inf 437 if (collisionPointOnPlane == null) { 438 return Double.POSITIVE_INFINITY; 439 } 440 Vect3 pDVect = collisionPointOnPlane.minus(ball.getCenter()).projectOnToB(normal); 441 Vect3 pVVect = velocity.projectOnToB(normal); 442 if (normalWeWant.dot(velocity) > 0 || 443 pDVect.rho() * pDVect.rho() - ball.getRadius()*pDVect.rho() > 444 pVVect.dot(pDVect)*maximumForesight) { 445 return Double.POSITIVE_INFINITY; 446 } 447 448 if(circle.containsPoint(collisionPointOnPlane)){ 449 if(pDVect.rho() - ball.getRadius() < 0){ 450 if(circle.minDistanceToObjectFromP(ball.getCenter()) < ball.getRadius()) 451 return 0; 452 else 453 return Double.POSITIVE_INFINITY; 454 } 455 else 456 return collisionPointOnPlane.minus(collisionPointOnSphere).rho()/velocity.rho(); 457 } 458 459 // otherwise return infinity 460 return Double.POSITIVE_INFINITY; 461 } 462 463 /** 464 * Computes the new velocity of a ball reflecting off of a plane circle. 465 * 466 * @requires <code>reflectionCoeff</code> >= 0 467 * 468 * @effects computes the new velocity of a ball reflecting off of a plane 469 * circle. The velocity resulting from this method corresponds to a 470 * collision against a surface with the given reflection coefficient. 471 * A reflection coefficient of 1 indicates a perfectly elastic 472 * collision. This method assumes that the ball is at the point of 473 * impact. 474 * 475 * @param circle 476 * the plane circle which is being hit 477 * 478 * @param ball 479 * the ball 480 * 481 * @param velocity 482 * the velocity of the ball before impact 483 * 484 * @param reflectionCoeff 485 * the reflection coefficient 486 * 487 * @return the velocity of the ball after impacting the given plane circle 488 */ 489 public Vect3 reflectPlaneCircle(PlaneCircle circle, Sphere ball, 490 Vect3 velocity, double reflectionCoeff) { 491 return applyReflectionCoeff(velocity, reflectPlaneCircle(circle, ball, 492 velocity), reflectionCoeff); 493 } 494 495 /** 496 * Computes the new velocity of a ball reflecting off of a plane circle. 497 * 498 * @effects computes the new velocity of a ball reflecting off of a plane 499 * circle. The velocity resulting from this method corresponds to a 500 * perfectly elastic collision. This method assumes that the ball is 501 * at the point of impact. 502 * 503 * @param circle 504 * the plane circle which is being hit 505 * 506 * @param ball 507 * the ball 508 * 509 * @param velocity 510 * the velocity of the ball before impact 511 * 512 * @return the velocity of the ball after impacting the given plane circle 513 */ 514 public Vect3 reflectPlaneCircle(PlaneCircle circle, Sphere ball, 515 Vect3 velocity) { 516 // get normal 517 Vect3 normal = circle.getNormal(); 518 // rotate, flip, and return 519 velocity = (velocity.rotateAroundVect(normal, Angle.DEG_180)).neg(); 520 return velocity; 521 } 522 523 /***************************************************************************** 524 * 525 * METHODS FOR STATIONARY SPHERES 526 * 527 ****************************************************************************/ 528 529 /** 530 * Computes the time until a ball represented by a sphere, travelling at a 531 * specified velocity collides with a specified sphere. 532 * 533 * @requires ball.radius > 0 534 * 535 * @effects computes the time until a ball represented by a sphere, travelling 536 * at a specified velocity collides with a specified sphere. If no 537 * collision will occur <tt>POSITIVE_INFINITY</tt> is returned. 538 * This method assumes the ball travels with constant velocity until 539 * impact. 540 * 541 * @param sphere 542 * a sphere representing the circle with which the ball may collide 543 * 544 * @param ball 545 * a sphere representing the size and initial location of the ball 546 * 547 * @param velocity 548 * the velocity of the ball before impact 549 * 550 * @return the time until collision or <tt>POSITIVE_INFINITY</tt> if the 551 * collision will not occur 552 * 553 * @see Double#POSITIVE_INFINITY 554 */ 555 public double timeUntilSphereCollision(Sphere sphere, Sphere ball, 556 Vect3 velocity) { 557 if(velocity.equals(Vect3.ZERO)) 558 return Double.POSITIVE_INFINITY; 559 // get variables 560 Vect3 xyz = sphere.getCenter(), abc = ball.getCenter(); 561 double radius1 = sphere.getRadius(), radius2 = ball.getRadius(); 562 double distance = radius1 + radius2; 563 564 Vect3 dims = abc.minus(xyz); 565 566 if (dims.dot(dims) - distance * dims.rho() > -1 * velocity.dot(dims) 567 * maximumForesight) { 568 return Double.POSITIVE_INFINITY; 569 } 570 571 double A = velocity.dot(velocity); 572 double B = 2.0 * velocity.dot(dims); 573 ; 574 double C = dims.dot(dims) - (distance * distance); 575 /* 576 * double A = ((va * va) + (vb * vb) + (vc * vc)); double B = 2.0 * ((va * 577 * width) + (vb * height) + (vc * depth)); double C = (width * width) + 578 * (height * height) + (depth * depth)- (distance * distance); 579 */ 580 double d = minQuadraticSolution(A, B, C); 581 582 if (Double.isNaN(d)) { 583 return Double.POSITIVE_INFINITY; 584 } else if (d > 0) { 585 // ans > 0 && ans <= +inf 586 return d; 587 } else if (B < 0 && (C < 0 || Vect3.isAbout(d,0))){ 588 return 0; 589 } 590 return Double.POSITIVE_INFINITY; 591 } 592 593 /** 594 * @requires sphere != null, ball != null, sphere and ball have different 595 * centers. 596 * @return the unit vector that points from the center of the sphere to the 597 * center of the ball. 598 */ 599 public Vect3 findSphereNormalToBall(Sphere sphere, Sphere ball) { 600 return ball.getCenter().minus(sphere.getCenter()).unitSize(); 601 } 602 603 /** 604 * Computes the new velocity of a ball reflecting off of a sphere. 605 * 606 * @requires <code>reflectionCoeff</code> >= 0 607 * 608 * @effects computes the new velocity of a ball reflecting off of a sphere. 609 * The velocity resulting from this method corresponds to a collision 610 * against a surface with the given reflection coefficient. A 611 * reflection coefficient of 1 indicates a perfectly elastic 612 * collision. This method assumes that the ball is at the point of 613 * impact. 614 * 615 * @param sphere 616 * the sphere which is being hit 617 * 618 * @param ball 619 * the ball 620 * 621 * @param velocity 622 * the velocity of the ball before impact 623 * 624 * @param reflectionCoeff 625 * the reflection coefficient 626 * 627 * @return the velocity of the ball after impacting the given sphere 628 */ 629 public Vect3 reflectSphere(Sphere sphere, Sphere ball, Vect3 velocity, 630 double reflectionCoeff) { 631 return applyReflectionCoeff(velocity, 632 reflectSphere(sphere, ball, velocity), reflectionCoeff); 633 } 634 635 /** 636 * Computes the new velocity of a ball reflecting off of a sphere. 637 * 638 * @effects computes the new velocity of a ball reflecting off of a sphere. 639 * The velocity resulting from this method corresponds to a perfectly 640 * elastic collision. This method assumes that the ball is at the 641 * point of impact. 642 * 643 * @param sphere 644 * the sphere which is being hit 645 * 646 * @param ball 647 * the ball 648 * 649 * @param velocity 650 * the velocity of the ball before impact 651 * 652 * @return the velocity of the ball after impacting the given sphere 653 */ 654 public Vect3 reflectSphere(Sphere sphere, Sphere ball, Vect3 velocity) { 655 // find normal 656 Vect3 normal = findSphereNormalToBall(sphere, ball); 657 // rotate, flip, and return 658 velocity = (velocity.rotateAroundVect(normal, Angle.DEG_180)).neg(); 659 return velocity; 660 } 661 662 /***************************************************************************** 663 * 664 * METHODS FOR STATIONARY LATERAL CYLINDERS 665 * 666 ****************************************************************************/ 667 668 /** 669 * Computes the time until a ball represented by a sphere, travelling at a 670 * specified velocity collides with a specified lateral cylinder. 671 * 672 * @requires ball.radius > 0 673 * 674 * @effects computes the time until a ball represented by a sphere, travelling 675 * at a specified velocity collides with a specified lateral 676 * cylinder. If no collision will occur <tt>POSITIVE_INFINITY</tt> 677 * is returned. This method assumes the ball travels with constant 678 * velocity until impact. 679 * 680 * @param cyl 681 * a lateral cylinder representing the circle with which the ball may 682 * collide 683 * 684 * @param ball 685 * a sphere representing the size and initial location of the ball 686 * 687 * @param velocity 688 * the velocity of the ball before impact 689 * 690 * @return the time until collision or <tt>POSITIVE_INFINITY</tt> if the 691 * collision will not occur 692 * 693 * @see Double#POSITIVE_INFINITY 694 */ 695 public double timeUntilLateralCylinderCollision(LateralCylinder cyl, 696 Sphere ball, Vect3 velocity) { 697 if(velocity.equals(Vect3.ZERO)) 698 return Double.POSITIVE_INFINITY; 699 700 // the strategy here is to find when the ball is within 701 // cylradius + ballradius from the infinite line that is the 702 // axis of the cylinder 703 704 // then we see if the point of contact would actually be on the 705 // cylinder 706 707 Vect3 p2 = ball.getCenter(); 708 Vect3 p1 = cyl.getTopCenter(); 709 Vect3 d1 = cyl.getDirection(); 710 double distance = cyl.getRadius() + ball.getRadius(); 711 712 // p2(t) = p2 + velocity * t 713 // r(t) = p2(t) - p1 = p2 - p1 + velocity * t 714 // r_perp = r(t) - r(t) dot d1 / d1 dot d1 * d1 715 // find when |r_perp| - distance = 0 716 // r_perp = p2 - p1 + velocity * t 717 // - (p2 - p1 + velocity * t) dot d1 / d1 dot d1 * d1 718 // r_perp = p2 - p1 - (p2 - p1) dot d1 / d1 dot d1 * d1 719 // + velocity * t - (velocity * t)dot d1/ d1 dot d1 * d1 720 721 // call k = p2 - p1 - (p2 - p1) dot d1 / d1 dot d1 * d1 722 // 723 // r_perp dot r_perp = k dot k + velocity dot velocty t^2 724 // + (2 k dot velocity - 2 (velocity dot d1) (d1 dot k) / (d1 dot d1)) t 725 // + (velocity dot dl)^2 / dl dot dl t^2 + 726 // - 2 (velocity dot d1)^2 / (d1 dot d1) t^2 727 728 Vect3 k = (p2.minus(p1)).minus(d1 729 .times((p2.minus(p1)).dot(d1) / d1.dot(d1))); 730 double C = k.dot(k) - distance * distance; 731 double B = 2 * (k.dot(velocity)) - 2 * (d1.dot(velocity)) * (d1.dot(k)) 732 / (d1.dot(d1)); 733 double A = velocity.dot(velocity) - (velocity.dot(d1)) * (velocity.dot(d1)) 734 / (d1.dot(d1)); 735 double d = minQuadraticSolution(A, B, C); 736 737 // we just solved if/when the sphere intersects infinitely long 738 // cylinder. Now we find teh point of intersection and see 739 // if it is indeed on the cylinder 740 if (Double.isNaN(d)) { 741 return Double.POSITIVE_INFINITY; 742 } 743 Vect3 center = ball.getCenter(); 744 Vect3 newCenter = ball.getCenter().plus(velocity.times(d)); 745 Line line = new Line(cyl.getDirection(), cyl.getTopCenter()); 746 Vect3 pOnLine = line.getPointOnLineClosestToP(center); 747 Vect3 newPOnLine = line.getPointOnLineClosestToP(newCenter); 748 Vect3 inward = newPOnLine.minus(newCenter).unitSize().times(ball.getRadius()); 749 Vect3 point = newCenter.plus(inward); 750 Vect3 normal = center.minus(pOnLine); 751 752 if (d >= 0 && cyl.containsPoint(point)) { 753 return d; 754 } else if (cyl.minDistanceToObjectFromP(center) < ball.getRadius() 755 && normal.dot(velocity) < 0) { 756 return 0; 757 } 758 return Double.POSITIVE_INFINITY; 759 } 760 761 /** 762 * @requires cyl != null, ball != null, center of ball does not lie on the 763 * axis of the cylinder. 764 * @return the outward pointing unit vector perpendicular to the axis of cyl 765 * to the center of the ball (regardless of whether that point is 766 * contained within the cylinder) 767 */ 768 public Vect3 findCylinderNormalToBall(LateralCylinder cyl, Sphere ball) { 769 // make line that goes through centers of cylinders 770 Vect3 dir = cyl.getDirection(); 771 Vect3 point = cyl.getBottomCenter(); 772 Line line = new Line(dir, point); 773 // find point on line closest to our point 774 Vect3 pointOnNormal = line.getPointOnLineClosestToP(ball.getCenter()); 775 // make normal to surface 776 Vect3 normal = ball.getCenter().minus(pointOnNormal).unitSize(); 777 return normal; 778 } 779 780 /** 781 * Computes the new velocity of a ball reflecting off of a lateral cylinder. 782 * 783 * @requires <code>reflectionCoeff</code> >= 0 784 * 785 * @effects computes the new velocity of a ball reflecting off of a lateral 786 * cylinder. The velocity resulting from this method corresponds to a 787 * collision against a surface with the given reflection coefficient. 788 * A reflection coefficient of 1 indicates a perfectly elastic 789 * collision. This method assumes that the ball is at the point of 790 * impact. 791 * 792 * @param cyl 793 * the lateral cylinder which is being hit 794 * 795 * @param ball 796 * the ball 797 * 798 * @param velocity 799 * the velocity of the ball before impact 800 * 801 * @param reflectionCoeff 802 * the reflection coefficient 803 * 804 * @return the velocity of the ball after impacting the given lateral cylinder 805 */ 806 public Vect3 reflectLateralCylinder(LateralCylinder cyl, Sphere ball, 807 Vect3 velocity, double reflectionCoeff) { 808 return applyReflectionCoeff(velocity, reflectLateralCylinder(cyl, ball, 809 velocity), reflectionCoeff); 810 } 811 812 /** 813 * Computes the new velocity of a ball reflecting off of a lateral cylinder. 814 * 815 * @effects computes the new velocity of a ball reflecting off of a lateral 816 * cylinder. The velocity resulting from this method corresponds to a 817 * perfectly elastic collision. This method assumes that the ball is 818 * at the point of impact. 819 * 820 * @param cyl 821 * the lateral cylinder which is being hit 822 * 823 * @param ball 824 * the ball 825 * 826 * @param velocity 827 * the velocity of the ball before impact 828 * 829 * @return the velocity of the ball after impacting the given lateral cylinder 830 */ 831 public Vect3 reflectLateralCylinder(LateralCylinder cyl, Sphere ball, 832 Vect3 velocity) { 833 // make normal to surface 834 Vect3 normal = findCylinderNormalToBall(cyl, ball); 835 // rotate, flip, and return 836 velocity = (velocity.rotateAroundVect(normal, Angle.DEG_180)).neg(); 837 return velocity; 838 } 839 840 /***************************************************************************** 841 * 842 * METHODS FOR STATIONARY TORI 843 * 844 ****************************************************************************/ 845 846 private PlaneCircle circleThroughTorus(Torus torus) { 847 return new PlaneCircle(torus.getCenterPoint(), torus.getOrientation(), 848 torus.getRadiusFromCenter()); 849 } 850 851 private Vect3 pointOnRingClosestToP(PlaneCircle ring, Vect3 p) { 852 Vect3 obliqueVect = p.minus(ring.getCenter()); 853 Vect3 perpToPlane = obliqueVect.projectOnToB(ring.getNormal()); 854 Vect3 parallelToPlane = obliqueVect.minus(perpToPlane); 855 if (Vect3.isAbout(parallelToPlane.rho(), 0.0)) { 856 return null; 857 } 858 parallelToPlane = parallelToPlane.unitSize(); 859 parallelToPlane = parallelToPlane.times(ring.getRadius()); 860 return parallelToPlane.plus(ring.getCenter()); 861 } 862 863 /** 864 * Computes the time until a ball represented by a sphere, travelling at a 865 * specified velocity collides with a specified torus. 866 * 867 * @requires ball.radius > 0 868 * 869 * @effects computes the time until a ball represented by a sphere, travelling 870 * at a specified velocity collides with a specified torus. If no 871 * collision will occur <tt>POSITIVE_INFINITY</tt> is returned. 872 * This method assumes the ball travels with constant velocity until 873 * impact. 874 * 875 * @param torus 876 * a torus representing the circle with which the ball may collide 877 * 878 * @param ball 879 * a sphere representing the size and initial location of the ball 880 * 881 * @param velocity 882 * the velocity of the ball before impact 883 * 884 * @return the time until collision or <tt>POSITIVE_INFINITY</tt> if the 885 * collision will not occur 886 * 887 * @see Double#POSITIVE_INFINITY 888 */ 889 public double timeUntilTorusCollision(Torus torus, Sphere ball, Vect3 velocity) { 890 // the strategy here is to find when the ball is within 891 // tuberadius + ballradius from the circle within the middle 892 // of the torus 893 if(velocity.equals(Vect3.ZERO)) 894 return Double.POSITIVE_INFINITY; 895 final Vect3 p = ball.getCenter(); 896 final Vect3 v = velocity; 897 final Vect3 c = torus.getCenterPoint(); 898 final Vect3 n = torus.getOrientation(); 899 final double radiusOfTube = torus.getRadiusOfTube(); 900 final double radiusFromCenter = torus.getRadiusFromCenter(); 901 final double radiusOfSphere = ball.getRadius(); 902 final double distance = (radiusOfSphere + radiusOfTube); 903 class torusDistance implements ZeroFinder.Function { 904 public double evaluate(double t) { 905 Vect3 p_t = p.plus(v.times(t)); 906 Vect3 r_t = p_t.minus(c); 907 Vect3 rpar_t = r_t.minus(r_t.projectOnToB(n)); 908 Vect3 z_t = rpar_t.unitSize().times(radiusFromCenter); 909 Vect3 w_t = r_t.minus(z_t); 910 return w_t.rho() - distance; 911 } 912 } 913 ZeroFinder.Function function = new torusDistance(); 914 915 Double zero = ZeroFinder.findRoot(function, 0, maximumForesight, 916 numSubIntervals); 917 double f0 = function.evaluate(0); 918 double fprime = (function.evaluate(0 + .00000000001) - function.evaluate(0)) / (.00000000001); 919 920 if (zero > 0) { 921 return zero; 922 } else if ((Vect3.isAbout(zero, 0) || f0 < 0) && fprime < 0) { 923 return 0; 924 } 925 926 return Double.POSITIVE_INFINITY; 927 } 928 929 /** 930 * @requires torus != null, ball != null, the center of ball does not lie on 931 * the circle of radius torus.radiusFromCenter contained in the 932 * plane that is perpendicular to torus.orientation 933 * @return if(ball.getCenter()) lies on the line determined by the torus 934 * centerPoint and orientation, then returns a unit vector with the 935 * same direction as the torus's orientation. Otherwise returns the 936 * outward pointing unit vector from the closest point on torus to the 937 * center of the ball 938 */ 939 public Vect3 findTorusNormalToBall(Torus torus, Sphere ball) { 940 // get point on circle inside torus closest to our ball 941 Vect3 pointOnNormal = pointOnRingClosestToP(circleThroughTorus(torus), ball 942 .getCenter()); 943 // if ball is directy overhead, won't work 944 if (pointOnNormal == null) { 945 return torus.getOrientation().unitSize(); 946 } 947 // make normal 948 Vect3 normal = ball.getCenter().minus(pointOnNormal); 949 return normal.unitSize(); 950 } 951 952 /** 953 * Computes the new velocity of a ball reflecting off of a torus. 954 * 955 * @requires <code>reflectionCoeff</code> >= 0 956 * 957 * @effects computes the new velocity of a ball reflecting off of a torus. The 958 * velocity resulting from this method corresponds to a collision 959 * against a surface with the given reflection coefficient. A 960 * reflection coefficient of 1 indicates a perfectly elastic 961 * collision. This method assumes that the ball is at the point of 962 * impact. 963 * 964 * @param torus 965 * the torus which is being hit 966 * 967 * @param ball 968 * the ball 969 * 970 * @param velocity 971 * the velocity of the ball before impact 972 * 973 * @param reflectionCoeff 974 * the reflection coefficient 975 * 976 * @return the velocity of the ball after impacting the given torus 977 */ 978 public Vect3 reflectTorus(Torus torus, Sphere ball, Vect3 velocity, 979 double reflectionCoeff) { 980 return applyReflectionCoeff(velocity, reflectTorus(torus, ball, velocity), 981 reflectionCoeff); 982 } 983 984 /** 985 * Computes the new velocity of a ball reflecting off of a torus. 986 * 987 * @effects computes the new velocity of a ball reflecting off of a torus. The 988 * velocity resulting from this method corresponds to a perfectly 989 * elastic collision. This method assumes that the ball is at the 990 * point of impact. 991 * 992 * @param torus 993 * the torus which is being hit 994 * 995 * @param ball 996 * the ball 997 * 998 * @param velocity 999 * the velocity of the ball before impact 1000 * 1001 * @return the velocity of the ball after impacting the given torus 1002 */ 1003 public Vect3 reflectTorus(Torus torus, Sphere ball, Vect3 velocity) { 1004 // make normal 1005 Vect3 normal = findTorusNormalToBall(torus, ball); 1006 // rotate by 180, flip, and return 1007 velocity = (velocity.rotateAroundVect(normal, Angle.DEG_180)).neg(); 1008 return velocity; 1009 } 1010 1011 /***************************************************************************** 1012 * 1013 * METHODS FOR ROTATING OBJECTS 1014 * 1015 ****************************************************************************/ 1016 1017 /***************************************************************************** 1018 * 1019 * METHODS FOR ROTATING PLANE POLYGON 1020 * 1021 ****************************************************************************/ 1022 1023 /** 1024 * Computes the time until a ball travelling at a specified velocity collides 1025 * with a rotating plane polygon. 1026 * 1027 * @effects computes the time until a spherical ball travelling at a specified 1028 * velocity collides with a specified plane polygon that is rotating 1029 * about a given center of rotation at a given angular velocity. If 1030 * no collision will occurr <tt>POSITIVE_INFINITY</tt> is returned. 1031 * This method assumes the ball will travel with constant velocity 1032 * until impact. 1033 * 1034 * <p> 1035 * <img src="doc-files/rotate_circle.gif"> 1036 * 1037 * @param polygon 1038 * a plane polygon representing the initial location and size of the 1039 * rotating plane polygon 1040 * 1041 * @param center 1042 * the point around which the plane polygon is rotating 1043 * 1044 * @param angularVelocity 1045 * the angular velocity with which <code>polygon</code> is rotating 1046 * about <code>center</code>, where the length is measured in 1047 * radians per second, and the direction is perpendicular to the axis 1048 * of rotation using the right hand rule (the standard convention). 1049 * 1050 * @param ball 1051 * a sphere representing the size and initial position of the ball 1052 * 1053 * @param velocity 1054 * the velocity of the ball before impact 1055 * 1056 * @see Double#POSITIVE_INFINITY 1057 */ 1058 public double timeUntilRotatingPlanePolygonCollision(PlanePolygon polygon, 1059 Vect3 center, Vect3 angularVelocity, Sphere ball, Vect3 velocity) { 1060 if (angularVelocity.isAbout(Vect3.ZERO)) { 1061 return timeUntilPlanePolygonCollision(polygon, ball, velocity); 1062 } 1063 1064 // ball stuff 1065 final Vect3 vOfBall = velocity; 1066 final Sphere theBall = ball; 1067 // rotational stuff 1068 final Vect3 angV = angularVelocity; 1069 final double omega = angV.rho(); 1070 final Vect3 centerOfRotation = center; 1071 1072 // polygon stuff 1073 final PlanePolygon poly = polygon; 1074 1075 class rotatingPolygonDistance implements ZeroFinder.Function { 1076 public double evaluate(double t) { 1077 Angle rotAngle = new Angle(t * omega); 1078 PlanePolygon newPoly = poly.rotateAboutCwithAxisAandAngleT( 1079 centerOfRotation, angV, rotAngle); 1080 Plane p = newPoly.planeContainingObject(); 1081 1082 Sphere newBall = new Sphere(theBall.getCenter().plus(vOfBall.times(t)), 1083 theBall.getRadius()); 1084 1085 double d = p.perpDistanceFromPlaneToPoint(newBall.getCenter()); 1086 return d - newBall.getRadius(); 1087 } 1088 } 1089 ZeroFinder.Function function = new rotatingPolygonDistance(); 1090 1091 Double zero = ZeroFinder.findRoot(function, 0.0, maximumForesight, 1092 numSubIntervals); 1093 1094 if (Double.isNaN(zero)) { 1095 return Double.POSITIVE_INFINITY; 1096 } 1097 1098 double f0 = function.evaluate(0); 1099 double f0prime = (function.evaluate(0.00001) - f0) / .00001; 1100 1101 double fprime = (function.evaluate(zero + .0001) - function 1102 .evaluate(zero - .0001)) 1103 / (2 * .0001); 1104 1105 Angle rotAngle = new Angle(zero * omega); 1106 PlanePolygon newPoly = poly.rotateAboutCwithAxisAandAngleT( 1107 centerOfRotation, angV, rotAngle); 1108 // translate ball 1109 Sphere newBall = ball.translateByT(velocity.times(zero)); 1110 1111 Vect3 newNormal = newPoly.getNormal(); 1112 Vect3 obliqueVect = newBall.getCenter().minus(newPoly.getVertices().next()); 1113 // get normal pointing to our side 1114 Vect3 normalWeWant = obliqueVect.projectOnToB(newNormal).unitSize(); 1115 // make it length radius 1116 Vect3 scaledNormal = normalWeWant.times(newBall.getRadius()); 1117 // find where our point of collision is on the sphere 1118 Vect3 collisionPointOnSphere = newBall.getCenter().minus(scaledNormal); 1119 1120 // if polygon contains this point AND we would hit it, return 1121 // the time it would take 1122 1123 Vect3 oldNormal = poly.getNormal(); 1124 Vect3 oldobliqueVect = theBall.getCenter().minus(poly.getVertices().next()); 1125 // get normal pointing to our side 1126 Vect3 oldnormalWeWant = oldobliqueVect.projectOnToB(oldNormal).unitSize(); 1127 // make it length radius 1128 Vect3 oldscaledNormal = oldnormalWeWant.times(poly.planeContainingObject() 1129 .perpDistanceFromPlaneToPoint(theBall.getCenter())); 1130 // find where our point of collision is on the sphere 1131 Vect3 oldcollisionPointOnSphere = theBall.getCenter() 1132 .minus(oldscaledNormal); 1133 1134 if (f0 < 0 && f0prime < 0 && poly.containsPoint(oldcollisionPointOnSphere)) { 1135 return 0; 1136 } 1137 1138 if (newPoly.containsPoint(collisionPointOnSphere) && fprime < 0) // && 1139 // normalWeWant.dot(velocity) < 0) 1140 { 1141 if (zero > 0) { 1142 return zero; 1143 } else if (Vect3.isAbout(zero, 0)) { 1144 return 0; 1145 } 1146 } 1147 // otherwise return infinity 1148 return Double.POSITIVE_INFINITY; 1149 1150 } 1151 1152 /** 1153 * Computes the new velocity of a sphere reflected off of a rotating plane 1154 * polygon. 1155 * 1156 * @requires the sphere is at the point of impact 1157 * 1158 * @effects computes the new velocity of a sphere reflected off of a plane 1159 * polygon which is rotating with constant angular velocity around a 1160 * point. The velocity resulting from this method corresponds to a 1161 * perfectly elastic collision. 1162 * 1163 * @param polygon 1164 * the rotating plane polygon 1165 * 1166 * @param center 1167 * the point about which <code>polygon</code> is rotating 1168 * 1169 * @param angularVelocity 1170 * the angular velocity with which <code>polygon</code> is rotating 1171 * about <code>center</code>, where the length is measured in 1172 * radians per second, and the direction is perpendicular to the axis 1173 * of rotation using the right hand rule (the standard convention). 1174 * 1175 * @param ball 1176 * the size and position of the sphere before impact 1177 * 1178 * @param velocity 1179 * the velocity of the sphere before impact 1180 * 1181 * @return the velocity of the sphere after impacting the rotating plane 1182 * polygon 1183 */ 1184 public Vect3 reflectRotatingPlanePolygon(PlanePolygon polygon, Vect3 center, 1185 Vect3 angularVelocity, Sphere ball, Vect3 velocity) { 1186 return reflectRotatingPlanePolygon(polygon, center, angularVelocity, ball, 1187 velocity, 1.0); 1188 } 1189 1190 /** 1191 * Computes the new velocity of a sphere reflected off of a rotating plane 1192 * polygon. 1193 * 1194 * @requires the sphere is at the point of impact 1195 * 1196 * @effects computes the new velocity of a sphere reflected off of a plane 1197 * polygon which is rotating with constant angular velocity around a 1198 * point. The velocity resulting from this method corresponds to a 1199 * collision against a surface with the given reflection coefficient. 1200 * A reflection coefficient of 1.0 indicates a perfectly elastic 1201 * collision. 1202 * 1203 * @param polygon 1204 * the rotating plane polygon 1205 * 1206 * @param center 1207 * the point about which <code>polygon</code> is rotating 1208 * 1209 * @param angularVelocity 1210 * the angular velocity with which <code>polygon</code> is rotating 1211 * about <code>center</code>, where the length is measured in 1212 * radians per second, and the direction is perpendicular to the axis 1213 * of rotation using the right hand rule (the standard convention). 1214 * 1215 * @param ball 1216 * the size and position of the sphere before impact 1217 * 1218 * @param velocity 1219 * the velocity of the sphere before impact 1220 * 1221 * @param reflectionCoeff 1222 * the reflection coefficient 1223 * 1224 * @return the velocity of the sphere after impacting the rotating plane 1225 * polygon 1226 */ 1227 public Vect3 reflectRotatingPlanePolygon(PlanePolygon polygon, Vect3 center, 1228 Vect3 angularVelocity, Sphere ball, Vect3 velocity, double reflectionCoeff) { 1229 if (angularVelocity.isAbout(Vect3.ZERO)) { 1230 return reflectPlanePolygon(polygon, ball, velocity, reflectionCoeff); 1231 } 1232 1233 // calculate point of collision 1234 Vect3 planeToPointPerp = (polygon.planeContainingObject()) 1235 .perpVectorFromPlaneToPoint(ball.getCenter()); 1236 Vect3 pointOfCollision = ball.getCenter().minus(planeToPointPerp); 1237 1238 Line lineOfRotation = new Line(angularVelocity, center); 1239 Vect3 closeToCollisionPoint = lineOfRotation 1240 .getPointOnLineClosestToP(pointOfCollision); 1241 1242 // translate point 1243 pointOfCollision = pointOfCollision.minus(closeToCollisionPoint); 1244 // translate polygon 1245 PlanePolygon newPolygon = polygon.translateByT(closeToCollisionPoint.neg()); 1246 // translate ball 1247 Sphere newBall = ball.translateByT(closeToCollisionPoint); 1248 1249 // velocity of the plane polygon at that point 1250 Vect3 myVel = angularVelocity.cross(pointOfCollision); 1251 // translate into refernce frame of moving plane polygon 1252 Vect3 relativeV = velocity.minus(myVel); 1253 // reflect 1254 Vect3 reflectV = reflectPlanePolygon(newPolygon, newBall, relativeV, 1255 reflectionCoeff); 1256 // translate back 1257 Vect3 absoluteV = myVel.plus(reflectV); 1258 return absoluteV; 1259 } 1260 1261 /***************************************************************************** 1262 * 1263 * METHODS FOR ROTATING PLANE CIRCLES 1264 * 1265 ****************************************************************************/ 1266 1267 /** 1268 * Computes the time until a ball travelling at a specified velocity collides 1269 * with a rotating plane circle. 1270 * 1271 * @effects computes the time until a spherical ball travelling at a specified 1272 * velocity collides with a specified plane circle that is rotating 1273 * about a given center of rotation at a given angular velocity. If 1274 * no collision will occurr <tt>POSITIVE_INFINITY</tt> is returned. 1275 * This method assumes the ball will travel with constant velocity 1276 * until impact. 1277 * 1278 * <p> 1279 * <img src="doc-files/rotate_circle.gif"> 1280 * 1281 * @param circle 1282 * a plane circle representing the initial location and size of the 1283 * rotating plane circle 1284 * 1285 * @param center 1286 * the point around which the plane circle is rotating 1287 * 1288 * @param angularVelocity 1289 * the angular velocity with which <code>circle</code> is rotating 1290 * about <code>center</code>, where the length is measured in 1291 * radians per second, and the direction is perpendicular to the axis 1292 * of rotation using the right hand rule (the standard convention). 1293 * 1294 * @param ball 1295 * a sphere representing the size and initial position of the ball 1296 * 1297 * @param velocity 1298 * the velocity of the ball before impact 1299 * 1300 * @see Double#POSITIVE_INFINITY 1301 */ 1302 public double timeUntilRotatingPlaneCircleCollision(PlaneCircle circle, 1303 Vect3 center, Vect3 angularVelocity, Sphere ball, Vect3 velocity) { 1304 if (angularVelocity.isAbout(Vect3.ZERO) 1305 || (angularVelocity.cross(circle.getNormal()).isAbout(Vect3.ZERO) && angularVelocity 1306 .cross(center.minus(circle.getCenter())).isAbout(Vect3.ZERO))) { 1307 return timeUntilPlaneCircleCollision(circle, ball, velocity); 1308 } 1309 1310 // ball stuff 1311 final Vect3 vOfBall = velocity; 1312 final Sphere theBall = ball; 1313 1314 // rotational stuff 1315 final Vect3 angV = angularVelocity; 1316 final double omega = angV.rho(); 1317 final Vect3 centerOfRotation = center; 1318 1319 // circle stuff 1320 final PlaneCircle theCircle = circle; 1321 1322 class rotatingCircleDistance implements ZeroFinder.Function { 1323 public double evaluate(double t) { 1324 Angle rotAngle = new Angle(t * omega); 1325 PlaneCircle newCircle = theCircle.rotateAboutCwithAxisAandAngleT( 1326 centerOfRotation, angV, rotAngle); 1327 Plane p = newCircle.planeContainingObject(); 1328 Sphere newBall = new Sphere(theBall.getCenter().plus(vOfBall.times(t)), 1329 theBall.getRadius()); 1330 double d = p.perpDistanceFromPlaneToPoint(newBall.getCenter()); 1331 return d - newBall.getRadius(); 1332 } 1333 } 1334 ZeroFinder.Function function = new rotatingCircleDistance(); 1335 1336 Double zero = ZeroFinder.findRoot(function, 0.0, maximumForesight, 1337 numSubIntervals); 1338 1339 if (Double.isNaN(zero)) { 1340 return Double.POSITIVE_INFINITY; 1341 } 1342 double f0 = function.evaluate(0); 1343 double f0prime = (function.evaluate(0.00001) - f0) / .00001; 1344 double fprime = (function.evaluate(zero + .0001) - function 1345 .evaluate(zero - .0001)) 1346 / (2 * .0001); 1347 1348 Angle rotAngle = new Angle(zero * omega); 1349 PlaneCircle newCircle = theCircle.rotateAboutCwithAxisAandAngleT( 1350 centerOfRotation, angV, rotAngle); 1351 // translate ball 1352 Sphere newBall = ball.translateByT(velocity.times(zero)); 1353 1354 Vect3 newNormal = newCircle.getNormal(); 1355 Vect3 obliqueVect = newBall.getCenter().minus(newCircle.getCenter()); 1356 // get normal pointing to our side 1357 Vect3 normalWeWant = obliqueVect.projectOnToB(newNormal).unitSize(); 1358 // make it length radius 1359 Vect3 scaledNormal = normalWeWant.times(newBall.getRadius()); 1360 // find where our point of collision is on the sphere 1361 Vect3 collisionPointOnSphere = newBall.getCenter().minus(scaledNormal); 1362 1363 // if polygon contains this point AND we would hit it, return 1364 // the time it would take 1365 1366 Vect3 oldNormal = circle.getNormal(); 1367 Vect3 oldobliqueVect = theBall.getCenter().minus(circle.getCenter()); 1368 // get normal pointing to our side 1369 Vect3 oldnormalWeWant = oldobliqueVect.projectOnToB(oldNormal).unitSize(); 1370 // make it length radius 1371 Vect3 oldscaledNormal = oldnormalWeWant.times(circle 1372 .planeContainingObject().perpDistanceFromPlaneToPoint( 1373 theBall.getCenter())); 1374 // find where our point of collision is on the sphere 1375 Vect3 oldcollisionPointOnSphere = theBall.getCenter() 1376 .minus(oldscaledNormal); 1377 1378 if (f0 < 0 && f0prime < 0 1379 && circle.containsPoint(oldcollisionPointOnSphere)) { 1380 return 0; 1381 } 1382 1383 // if polygon contains this point AND we would hit it, return 1384 // the time it would take 1385 if (newCircle.containsPoint(collisionPointOnSphere) && fprime < 0) // && 1386 // normalWeWant.dot(velocity) < 0) 1387 { 1388 if (zero > 0) { 1389 return zero; 1390 } else if (Vect3.isAbout(zero, 0)) { 1391 return 0; 1392 } 1393 } 1394 // otherwise return infinity 1395 return Double.POSITIVE_INFINITY; 1396 1397 } 1398 1399 /** 1400 * Computes the new velocity of a sphere reflected off of a rotating plane 1401 * circle. 1402 * 1403 * @requires the sphere is at the point of impact 1404 * 1405 * @effects computes the new velocity of a sphere reflected off of a plane 1406 * circle which is rotating with constant angular velocity around a 1407 * point. The velocity resulting from this method corresponds to a 1408 * perfectly elastic collision. 1409 * 1410 * @param circle 1411 * the rotating plane circle 1412 * 1413 * @param center 1414 * the point about which <code>circle</code> is rotating 1415 * 1416 * @param angularVelocity 1417 * the angular velocity with which <code>circle</code> is rotating 1418 * about <code>center</code>, where the length is measured in 1419 * radians per second, and the direction is perpendicular to the axis 1420 * of rotation using the right hand rule (the standard convention). 1421 * 1422 * @param ball 1423 * the size and position of the sphere before impact 1424 * 1425 * @param velocity 1426 * the velocity of the sphere before impact 1427 * 1428 * @return the velocity of the sphere after impacting the rotating plane 1429 * circle 1430 */ 1431 public Vect3 reflectRotatingPlaneCircle(PlaneCircle circle, Vect3 center, 1432 Vect3 angularVelocity, Sphere ball, Vect3 velocity) { 1433 return reflectRotatingPlaneCircle(circle, center, angularVelocity, ball, 1434 velocity, 1.0); 1435 } 1436 1437 /** 1438 * Computes the new velocity of a sphere reflected off of a rotating plane 1439 * circle. 1440 * 1441 * @requires the sphere is at the point of impact 1442 * 1443 * @effects computes the new velocity of a sphere reflected off of a plane 1444 * circle which is rotating with constant angular velocity around a 1445 * point. The velocity resulting from this method corresponds to a 1446 * collision against a surface with the given reflection coefficient. 1447 * A reflection coefficient of 1.0 indicates a perfectly elastic 1448 * collision. 1449 * 1450 * @param circle 1451 * the rotating plane circle 1452 * 1453 * @param center 1454 * the point about which <code>circle</code> is rotating 1455 * 1456 * @param angularVelocity 1457 * the angular velocity with which <code>circle</code> is rotating 1458 * about <code>center</code>, where the length is measured in 1459 * radians per second, and the direction is perpendicular to the axis 1460 * of rotation using the right hand rule (the standard convention). 1461 * 1462 * @param ball 1463 * the size and position of the sphere before impact 1464 * 1465 * @param velocity 1466 * the velocity of the sphere before impact 1467 * 1468 * @param reflectionCoeff 1469 * the reflection coefficient 1470 * 1471 * @return the velocity of the sphere after impacting the rotating plane 1472 * circle 1473 */ 1474 public Vect3 reflectRotatingPlaneCircle(PlaneCircle circle, Vect3 center, 1475 Vect3 angularVelocity, Sphere ball, Vect3 velocity, double reflectionCoeff) { 1476 if (angularVelocity.isAbout(Vect3.ZERO)) { 1477 return reflectPlaneCircle(circle, ball, velocity, reflectionCoeff); 1478 } 1479 1480 // calculate point of collision 1481 Vect3 planeToPointPerp = circle.planeContainingObject() 1482 .perpVectorFromPlaneToPoint(ball.getCenter()); 1483 Vect3 pointOfCollision = ball.getCenter().minus(planeToPointPerp); 1484 1485 if (!circle.containsPoint(pointOfCollision)) { 1486 // return velocity; 1487 } 1488 1489 Line lineOfRotation = new Line(angularVelocity, center); 1490 Vect3 closeToCollisionPoint = lineOfRotation 1491 .getPointOnLineClosestToP(pointOfCollision); 1492 1493 // translate point of collision 1494 pointOfCollision = pointOfCollision.minus(closeToCollisionPoint); 1495 // translate circle 1496 PlaneCircle newCircle = circle.translateByT(closeToCollisionPoint.neg()); 1497 // translate ball 1498 Sphere newBall = ball.translateByT(closeToCollisionPoint.neg()); 1499 1500 // velocity of the plane polygon at that point 1501 Vect3 myVel = angularVelocity.cross(pointOfCollision); 1502 // translate into refernce frame of moving plane polygon 1503 Vect3 relativeV = velocity.minus(myVel); 1504 // reflect 1505 Vect3 reflectV = reflectPlaneCircle(newCircle, newBall, relativeV, 1506 reflectionCoeff); 1507 // translate back 1508 Vect3 absoluteV = myVel.plus(reflectV); 1509 return absoluteV; 1510 } 1511 1512 /***************************************************************************** 1513 * 1514 * METHODS FOR ROTATING SPHERES 1515 * 1516 ****************************************************************************/ 1517 1518 /** 1519 * Computes the time until a ball travelling at a specified velocity collides 1520 * with a rotating sphere. 1521 * 1522 * @effects computes the time until a spherical ball travelling at a specified 1523 * velocity collides with a specified sphere that is rotating about a 1524 * given center of rotation at a given angular velocity. If no 1525 * collision will occurr <tt>POSITIVE_INFINITY</tt> is returned. 1526 * This method assumes the ball will travel with constant velocity 1527 * until impact. 1528 * 1529 * <p> 1530 * <img src="doc-files/rotate_circle.gif"> 1531 * 1532 * @param sphere 1533 * a sphere representing the initial location and size of the 1534 * rotating sphere 1535 * 1536 * @param center 1537 * the point around which the sphere is rotating 1538 * 1539 * @param angularVelocity 1540 * the angular velocity with which <code>sphere</code> is rotating 1541 * about <code>center</code>, where the length is measured in 1542 * radians per second, and the direction is perpendicular to the axis 1543 * of rotation using the right hand rule (the standard convention). 1544 * 1545 * @param ball 1546 * a sphere representing the size and initial position of the ball 1547 * 1548 * @param velocity 1549 * the velocity of the ball before impact 1550 * 1551 * @see Double#POSITIVE_INFINITY 1552 */ 1553 public double timeUntilRotatingSphereCollision(Sphere sphere, Vect3 center, 1554 Vect3 angularVelocity, Sphere ball, Vect3 velocity) { 1555 if (angularVelocity.isAbout(Vect3.ZERO) 1556 || angularVelocity.cross(center.minus(sphere.getCenter())).isAbout( 1557 Vect3.ZERO)) { 1558 return timeUntilSphereCollision(sphere, ball, velocity); 1559 } 1560 1561 // ball stuff 1562 final Vect3 vOfBall = velocity; 1563 final Sphere theBall = ball; 1564 1565 // rotational stuff 1566 final Vect3 angV = angularVelocity; 1567 final double omega = angV.rho(); 1568 final Vect3 centerOfRotation = center; 1569 1570 // sphere stuff 1571 final Sphere theSphere = sphere; 1572 1573 class rotatingSphereDistance implements ZeroFinder.Function { 1574 public double evaluate(double t) { 1575 Angle rotAngle = new Angle(t * omega); 1576 Sphere newSphere = theSphere.rotateAboutCwithAxisAandAngleT( 1577 centerOfRotation, angV, rotAngle); 1578 Sphere newBall = new Sphere(theBall.getCenter().plus(vOfBall.times(t)), 1579 theBall.getRadius()); 1580 double d = newSphere.getCenter().minus(newBall.getCenter()).rho() 1581 - newSphere.getRadius() - newBall.getRadius(); 1582 return d; 1583 } 1584 } 1585 ZeroFinder.Function function = new rotatingSphereDistance(); 1586 1587 Double zero = ZeroFinder.findRoot(function, 0, maximumForesight, 1588 numSubIntervals); 1589 if (Double.isNaN(zero)) { 1590 return Double.POSITIVE_INFINITY; 1591 } 1592 double fprime = (function.evaluate(0 + .0001) - function 1593 .evaluate(0 - .0001)) 1594 / (2 * .0001); 1595 double f0 = function.evaluate(0); 1596 1597 // 1598 // Sphere newSphere = 1599 // theSphere.rotateAboutCwithAxisAandAngleT(centerOfRotation, angV, new 1600 // Angle(zero*omega)); Sphere newBall = new 1601 // Sphere(theBall.getCenter().plus(vOfBall.times(zero)), 1602 // theBall.getRadius()); Vect3 outwardNormal = 1603 // newBall.getCenter().minus(newSphere.getCenter()); 1604 // 1605 1606 if (zero > 0) { 1607 return zero; 1608 } else if ((zero == 0 || f0 < 0) && fprime < 0) { 1609 return 0; 1610 } 1611 1612 return Double.POSITIVE_INFINITY; 1613 } 1614 1615 /** 1616 * Computes the new velocity of a sphere reflected off of a rotating sphere. 1617 * 1618 * @requires the sphere is at the point of impact 1619 * 1620 * @effects computes the new velocity of a sphere reflected off of a sphere 1621 * which is rotating with constant angular velocity around a point. 1622 * The velocity resulting from this method corresponds to a perfectly 1623 * elastic collision. 1624 * 1625 * @param sphere 1626 * the rotating sphere 1627 * 1628 * @param center 1629 * the point about which <code>sphere</code> is rotating 1630 * 1631 * @param angularVelocity 1632 * the angular velocity with which <code>sphere</code> is rotating 1633 * about <code>center</code>, where the length is measured in 1634 * radians per second, and the direction is perpendicular to the axis 1635 * of rotation using the right hand rule (the standard convention). 1636 * 1637 * @param ball 1638 * the size and position of the sphere before impact 1639 * 1640 * @param velocity 1641 * the velocity of the sphere before impact 1642 * 1643 * @return the velocity of the sphere after impacting the rotating sphere 1644 */ 1645 public Vect3 reflectRotatingSphere(Sphere sphere, Vect3 center, 1646 Vect3 angularVelocity, Sphere ball, Vect3 velocity) { 1647 return reflectRotatingSphere(sphere, center, angularVelocity, ball, 1648 velocity, 1.0); 1649 } 1650 1651 /** 1652 * Computes the new velocity of a sphere reflected off of a rotating sphere. 1653 * 1654 * @requires the sphere is at the point of impact 1655 * 1656 * @effects computes the new velocity of a sphere reflected off of a sphere 1657 * which is rotating with constant angular velocity around a point. 1658 * The velocity resulting from this method corresponds to a collision 1659 * against a surface with the given reflection coefficient. A 1660 * reflection coefficient of 1.0 indicates a perfectly elastic 1661 * collision. 1662 * 1663 * @param sphere 1664 * the rotating sphere 1665 * 1666 * @param center 1667 * the point about which <code>sphere</code> is rotating 1668 * 1669 * @param angularVelocity 1670 * the angular velocity with which <code>sphere</code> is rotating 1671 * about <code>center</code>, where the length is measured in 1672 * radians per second, and the direction is perpendicular to the axis 1673 * of rotation using the right hand rule (the standard convention). 1674 * 1675 * @param ball 1676 * the size and position of the sphere before impact 1677 * 1678 * @param velocity 1679 * the velocity of the sphere before impact 1680 * 1681 * @param reflectionCoeff 1682 * the reflection coefficient 1683 * 1684 * @return the velocity of the sphere after impacting the rotating sphere 1685 */ 1686 public Vect3 reflectRotatingSphere(Sphere sphere, Vect3 center, 1687 Vect3 angularVelocity, Sphere ball, Vect3 velocity, double reflectionCoeff) { 1688 if (angularVelocity.isAbout(Vect3.ZERO)) { 1689 return reflectSphere(sphere, ball, velocity, reflectionCoeff); 1690 } 1691 // calculate point of collision 1692 Vect3 pointOfCollision = ball.getCenter().minus( 1693 ball.getCenter().minus(sphere.getCenter()).unitSize().times( 1694 ball.getRadius())); 1695 1696 Line lineOfRotation = new Line(angularVelocity, center); 1697 // translate point of collision 1698 Vect3 closeToCollisionPoint = lineOfRotation 1699 .getPointOnLineClosestToP(pointOfCollision); 1700 // translate sphere 1701 Sphere newSphere = sphere.translateByT(closeToCollisionPoint.neg()); 1702 // translate ball 1703 Sphere newBall = ball.translateByT(closeToCollisionPoint.neg()); 1704 pointOfCollision = pointOfCollision.minus(closeToCollisionPoint); 1705 1706 // velocity of the plane polygon at that point 1707 Vect3 myVel = angularVelocity.cross(pointOfCollision); 1708 // translate into refernce frame of moving plane polygon 1709 Vect3 relativeV = velocity.minus(myVel); 1710 // reflect 1711 Vect3 reflectV = reflectSphere(newSphere, newBall, relativeV, 1712 reflectionCoeff); 1713 // translate back 1714 Vect3 absoluteV = myVel.plus(reflectV); 1715 return absoluteV; 1716 } 1717 1718 /***************************************************************************** 1719 * 1720 * METHODS FOR ROTATING LATERAL CYLINDERS 1721 * 1722 ****************************************************************************/ 1723 1724 /** 1725 * Computes the time until a ball travelling at a specified velocity collides 1726 * with a rotating lateral cylinder. 1727 * 1728 * @effects computes the time until a spherical ball travelling at a specified 1729 * velocity collides with a specified lateral cylinder that is 1730 * rotating about a given center of rotation at a given angular 1731 * velocity. If no collision will occurr <tt>POSITIVE_INFINITY</tt> 1732 * is returned. This method assumes the ball will travel with 1733 * constant velocity until impact. 1734 * 1735 * <p> 1736 * <img src="doc-files/rotate_circle.gif"> 1737 * 1738 * @param cyl 1739 * a lateral cylinder representing the initial location and size of 1740 * the rotating lateral cylinder 1741 * 1742 * @param center 1743 * the point around which the lateral cylinder is rotating 1744 * 1745 * @param angularVelocity 1746 * the angular velocity with which <code>cyl</code> is rotating 1747 * about <code>center</code>, where the length is measured in 1748 * radians per second, and the direction is perpendicular to the axis 1749 * of rotation using the right hand rule (the standard convention). 1750 * 1751 * @param ball 1752 * a sphere representing the size and initial position of the ball 1753 * 1754 * @param velocity 1755 * the velocity of the ball before impact 1756 * 1757 * @see Double#POSITIVE_INFINITY 1758 */ 1759 public double timeUntilRotatingLateralCylinderCollision(LateralCylinder cyl, 1760 Vect3 center, Vect3 angularVelocity, Sphere ball, Vect3 velocity) { 1761 if (angularVelocity.isAbout(Vect3.ZERO) 1762 || (angularVelocity.cross(cyl.getDirection()).isAbout(Vect3.ZERO) && angularVelocity 1763 .cross(center.minus(cyl.getTopCenter())).isAbout(Vect3.ZERO))) { 1764 return timeUntilLateralCylinderCollision(cyl, ball, velocity); 1765 } 1766 1767 // ball stuff 1768 final Vect3 vOfBall = velocity; 1769 final Sphere theBall = ball; 1770 1771 // rotational stuff 1772 final Vect3 angV = angularVelocity; 1773 final double omega = angV.rho(); 1774 final Vect3 centerOfRotation = center; 1775 1776 // cylinder stuff 1777 final LateralCylinder theCyl = cyl; 1778 1779 class rotatingCylinderDistance implements ZeroFinder.Function { 1780 public double evaluate(double t) { 1781 Angle rotAngle = new Angle(t * omega); 1782 LateralCylinder newCyl = theCyl.rotateAboutCwithAxisAandAngleT( 1783 centerOfRotation, angV, rotAngle); 1784 Sphere newBall = new Sphere(theBall.getCenter().plus(vOfBall.times(t)), 1785 theBall.getRadius()); 1786 Vect3 p1 = newCyl.getTopCenter(); 1787 Vect3 d1 = newCyl.getDirection(); 1788 Line line = new Line(d1, p1); 1789 double distance = line.getPointOnLineClosestToP(newBall.getCenter()) 1790 .minus(newBall.getCenter()).rho() 1791 - newCyl.getRadius() - newBall.getRadius(); 1792 return distance; 1793 } 1794 } 1795 1796 ZeroFinder.Function function = new rotatingCylinderDistance(); 1797 1798 Double zero = ZeroFinder.findRoot(function, 0, maximumForesight, 10); 1799 double f0 = function.evaluate(0); 1800 1801 if (Double.isNaN(zero)) { 1802 return Double.POSITIVE_INFINITY; 1803 } 1804 1805 double fprime0 = (function.evaluate(0 + .0001) - function 1806 .evaluate(0 - .0001)) 1807 / (2 * .0001); 1808 1809 Angle rotAngle = new Angle(zero * omega); 1810 LateralCylinder newCyl = cyl.rotateAboutCwithAxisAandAngleT( 1811 centerOfRotation, angV, rotAngle); 1812 Sphere newBall = new Sphere(theBall.getCenter().plus(vOfBall.times(zero)), 1813 theBall.getRadius()); 1814 1815 Vect3 p1 = newCyl.getTopCenter(); 1816 Vect3 d1 = newCyl.getDirection(); 1817 Line line = new Line(d1, p1); 1818 1819 Vect3 outwardNormal = newBall.getCenter().minus( 1820 line.getPointOnLineClosestToP(newBall.getCenter())); 1821 1822 if (Vect3.isAbout(newCyl.minDistanceToObjectFromP(newBall.getCenter()), 1823 newBall.getRadius()) 1824 && outwardNormal.dot(velocity) < 0) { 1825 if (zero > 0) 1826 return zero; 1827 else if ((zero == 0 || f0 < 0) && fprime0 < 0) 1828 return 0; 1829 } 1830 1831 return Double.POSITIVE_INFINITY; 1832 } 1833 1834 /** 1835 * Computes the new velocity of a sphere reflected off of a rotating lateral 1836 * cylinder. 1837 * 1838 * @requires the sphere is at the point of impact 1839 * 1840 * @effects computes the new velocity of a sphere reflected off of a lateral 1841 * cylinder which is rotating with constant angular velocity around a 1842 * point. The velocity resulting from this method corresponds to a 1843 * perfectly elastic collision. 1844 * 1845 * @param cyl 1846 * the rotating lateral cylinder 1847 * 1848 * @param center 1849 * the point about which <code>cyl</code> is rotating 1850 * 1851 * @param angularVelocity 1852 * the angular velocity with which <code>cyl</code> is rotating 1853 * about <code>center</code>, where the length is measured in 1854 * radians per second, and the direction is perpendicular to the axis 1855 * of rotation using the right hand rule (the standard convention). 1856 * 1857 * @param ball 1858 * the size and position of the sphere before impact 1859 * 1860 * @param velocity 1861 * the velocity of the sphere before impact 1862 * 1863 * @return the velocity of the sphere after impacting the rotating lateral 1864 * cylinder 1865 */ 1866 public Vect3 reflectRotatingLateralCylinder(LateralCylinder cyl, 1867 Vect3 center, Vect3 angularVelocity, Sphere ball, Vect3 velocity) { 1868 return reflectRotatingLateralCylinder(cyl, center, angularVelocity, ball, 1869 velocity, 1.0); 1870 } 1871 1872 /** 1873 * Computes the new velocity of a sphere reflected off of a rotating lateral 1874 * cylinder. 1875 * 1876 * @requires the sphere is at the point of impact 1877 * 1878 * @effects computes the new velocity of a sphere reflected off of a lateral 1879 * cylinder which is rotating with constant angular velocity around a 1880 * point. The velocity resulting from this method corresponds to a 1881 * collision against a surface with the given reflection coefficient. 1882 * A reflection coefficient of 1.0 indicates a perfectly elastic 1883 * collision. 1884 * 1885 * @param cyl 1886 * the rotating lateral cylinder 1887 * 1888 * @param center 1889 * the point about which <code>cyl</code> is rotating 1890 * 1891 * @param angularVelocity 1892 * the angular velocity with which <code>cyl</code> is rotating 1893 * about <code>center</code>, where the length is measured in 1894 * radians per second, and the direction is perpendicular to the axis 1895 * of rotation using the right hand rule (the standard convention). 1896 * 1897 * @param ball 1898 * the size and position of the sphere before impact 1899 * 1900 * @param velocity 1901 * the velocity of the sphere before impact 1902 * 1903 * @param reflectionCoeff 1904 * the reflection coefficient 1905 * 1906 * @return the velocity of the sphere after impacting the rotating lateral 1907 * cylinder 1908 */ 1909 public Vect3 reflectRotatingLateralCylinder(LateralCylinder cyl, 1910 Vect3 center, Vect3 angularVelocity, Sphere ball, Vect3 velocity, 1911 double reflectionCoeff) { 1912 if (angularVelocity.isAbout(Vect3.ZERO)) { 1913 return reflectLateralCylinder(cyl, ball, velocity, reflectionCoeff); 1914 } 1915 // calculate point of collision 1916 Vect3 dir = cyl.getDirection(); 1917 Vect3 point = cyl.getBottomCenter(); 1918 Line line = new Line(dir, point); 1919 // find point on line closest to our point 1920 Vect3 pointOnNormal = line.getPointOnLineClosestToP(ball.getCenter()); 1921 // make normal to surface 1922 Vect3 normal = ball.getCenter().minus(pointOnNormal); 1923 1924 Vect3 pointOfCollision = pointOnNormal.plus((normal.unitSize()).times(cyl 1925 .getRadius())); 1926 1927 Line lineOfRotation = new Line(angularVelocity, center); 1928 Vect3 closeToCollisionPoint = lineOfRotation 1929 .getPointOnLineClosestToP(pointOfCollision); 1930 1931 // translate point of collision 1932 pointOfCollision = pointOfCollision.minus(closeToCollisionPoint); 1933 // translate cylinder 1934 LateralCylinder newCyl = cyl.translateByT(closeToCollisionPoint.neg()); 1935 // translate ball 1936 Sphere newBall = ball.translateByT(closeToCollisionPoint.neg()); 1937 1938 // velocity of the plane polygon at that point 1939 Vect3 myVel = angularVelocity.cross(pointOfCollision); 1940 // translate into refernce frame of moving plane polygon 1941 Vect3 relativeV = velocity.minus(myVel); 1942 // reflect 1943 Vect3 reflectV = reflectLateralCylinder(newCyl, newBall, relativeV, 1944 reflectionCoeff); 1945 // translate back 1946 Vect3 absoluteV = myVel.plus(reflectV); 1947 return absoluteV; 1948 } 1949 1950 /***************************************************************************** 1951 * 1952 * METHODS FOR ROTATING TORI 1953 * 1954 ****************************************************************************/ 1955 1956 /** 1957 * Computes the time until a ball travelling at a specified velocity collides 1958 * with a rotating torus. 1959 * 1960 * @effects computes the time until a spherical ball travelling at a specified 1961 * velocity collides with a specified torus that is rotating about a 1962 * given center of rotation at a given angular velocity. If no 1963 * collision will occurr <tt>POSITIVE_INFINITY</tt> is returned. 1964 * This method assumes the ball will travel with constant velocity 1965 * until impact. 1966 * 1967 * <p> 1968 * <img src="doc-files/rotate_circle.gif"> 1969 * 1970 * @param torus 1971 * a torus representing the initial location and size of the rotating 1972 * torus 1973 * 1974 * @param center 1975 * the point around which the torus is rotating 1976 * 1977 * @param angularVelocity 1978 * the angular velocity with which <code>torus</code> is rotating 1979 * about <code>center</code>, where the length is measured in 1980 * radians per second, and the direction is perpendicular to the axis 1981 * of rotation using the right hand rule (the standard convention). 1982 * 1983 * @param ball 1984 * a sphere representing the size and initial position of the ball 1985 * 1986 * @param velocity 1987 * the velocity of the ball before impact 1988 * 1989 * @see Double#POSITIVE_INFINITY 1990 */ 1991 public double timeUntilRotatingTorusCollision(Torus torus, Vect3 center, 1992 Vect3 angularVelocity, Sphere ball, Vect3 velocity) { 1993 if (angularVelocity.isAbout(Vect3.ZERO) 1994 || (angularVelocity.cross(torus.getOrientation()).isAbout(Vect3.ZERO) && angularVelocity 1995 .cross(center.minus(torus.getCenterPoint())).isAbout(Vect3.ZERO))) { 1996 return timeUntilTorusCollision(torus, ball, velocity); 1997 } 1998 1999 // ball stuff 2000 final Vect3 vOfBall = velocity; 2001 final Sphere theBall = ball; 2002 2003 // rotational stuff 2004 final Vect3 angV = angularVelocity; 2005 final double omega = angV.rho(); 2006 final Vect3 centerOfRotation = center; 2007 2008 // torus stuff 2009 final Torus theTorus = torus; 2010 2011 class rotatingTorusDistance implements ZeroFinder.Function { 2012 public double evaluate(double t) { 2013 Angle rotAngle = new Angle(t * omega); 2014 Torus newTorus = theTorus.rotateAboutCwithAxisAandAngleT( 2015 centerOfRotation, angV, rotAngle); 2016 Sphere newBall = new Sphere(theBall.getCenter().plus(vOfBall.times(t)), 2017 theBall.getRadius()); 2018 Vect3 r_t = newBall.getCenter().minus(newTorus.getCenterPoint()); 2019 Vect3 rpar_t = r_t.minus(r_t.projectOnToB(newTorus.getOrientation())); 2020 Vect3 z_t = rpar_t.unitSize().times(newTorus.getRadiusFromCenter()); 2021 Vect3 w_t = r_t.minus(z_t); 2022 return w_t.rho() - newTorus.getRadiusOfTube() - newBall.getRadius(); 2023 } 2024 } 2025 ZeroFinder.Function function = new rotatingTorusDistance(); 2026 Double zero = ZeroFinder.findRoot(function, 0, maximumForesight, 2027 numSubIntervals); 2028 double f0 = function.evaluate(0); 2029 double fprime = (function.evaluate(0 + .0001) - function 2030 .evaluate(0 - .0001)) 2031 / (2 * .0001); 2032 2033 if (zero > 0) { 2034 return zero; 2035 } else if ((zero == 0 || f0 < 0) && fprime < 0) { 2036 return 0; 2037 } 2038 2039 return Double.POSITIVE_INFINITY; 2040 } 2041 2042 /** 2043 * Computes the new velocity of a sphere reflected off of a rotating torus. 2044 * 2045 * @requires the sphere is at the point of impact 2046 * 2047 * @effects computes the new velocity of a sphere reflected off of a torus 2048 * which is rotating with constant angular velocity around a point. 2049 * The velocity resulting from this method corresponds to a perfectly 2050 * elastic collision. 2051 * 2052 * @param torus 2053 * the rotating torus 2054 * 2055 * @param center 2056 * the point about which <code>torus</code> is rotating 2057 * 2058 * @param angularVelocity 2059 * the angular velocity with which <code>torus</code> is rotating 2060 * about <code>center</code>, where the length is measured in 2061 * radians per second, and the direction is perpendicular to the axis 2062 * of rotation using the right hand rule (the standard convention). 2063 * 2064 * @param ball 2065 * the size and position of the sphere before impact 2066 * 2067 * @param velocity 2068 * the velocity of the sphere before impact 2069 * 2070 * @return the velocity of the sphere after impacting the rotating torus 2071 */ 2072 public Vect3 reflectRotatingTorus(Torus torus, Vect3 center, 2073 Vect3 angularVelocity, Sphere ball, Vect3 velocity) { 2074 return reflectRotatingTorus(torus, center, angularVelocity, ball, velocity, 2075 1.0); 2076 } 2077 2078 /** 2079 * Computes the new velocity of a sphere reflected off of a rotating torus. 2080 * 2081 * @requires the sphere is at the point of impact 2082 * 2083 * @effects computes the new velocity of a sphere reflected off of a torus 2084 * which is rotating with constant angular velocity around a point. 2085 * The velocity resulting from this method corresponds to a collision 2086 * against a surface with the given reflection coefficient. A 2087 * reflection coefficient of 1.0 indicates a perfectly elastic 2088 * collision. 2089 * 2090 * @param torus 2091 * the rotating torus 2092 * 2093 * @param center 2094 * the point about which <code>torus</code> is rotating 2095 * 2096 * @param angularVelocity 2097 * the angular velocity with which <code>torus</code> is rotating 2098 * about <code>center</code>, where the length is measured in 2099 * radians per second, and the direction is perpendicular to the axis 2100 * of rotation using the right hand rule (the standard convention). 2101 * 2102 * @param ball 2103 * the size and position of the sphere before impact 2104 * 2105 * @param velocity 2106 * the velocity of the sphere before impact 2107 * 2108 * @param reflectionCoeff 2109 * the reflection coefficient 2110 * 2111 * @return the velocity of the sphere after impacting the rotating torus 2112 */ 2113 public Vect3 reflectRotatingTorus(Torus torus, Vect3 center, 2114 Vect3 angularVelocity, Sphere ball, Vect3 velocity, double reflectionCoeff) { 2115 if (angularVelocity.isAbout(Vect3.ZERO)) { 2116 return reflectTorus(torus, ball, velocity, reflectionCoeff); 2117 } 2118 // calculate point of collision 2119 Vect3 pointOfCollision; 2120 // get point on circle inside torus closest to our ball 2121 Vect3 pointOnNormal = pointOnRingClosestToP(circleThroughTorus(torus), ball 2122 .getCenter()); 2123 // if ball is directy overhead, won't work 2124 if (pointOnNormal == null) { 2125 // calculate point of collision 2126 pointOfCollision = torus.getCenterPoint(); 2127 } else { 2128 // make normal 2129 Vect3 normal = ball.getCenter().minus(pointOnNormal); 2130 // calculate point of collision 2131 pointOfCollision = pointOnNormal.plus((normal.unitSize()).times(torus 2132 .getRadiusOfTube())); 2133 } 2134 2135 Line lineOfRotation = new Line(angularVelocity, center); 2136 Vect3 closeToCollisionPoint = lineOfRotation 2137 .getPointOnLineClosestToP(pointOfCollision); 2138 2139 // translate point of collision 2140 pointOfCollision = pointOfCollision.minus(closeToCollisionPoint); 2141 // translate torus 2142 Torus newTorus = torus.translateByT(closeToCollisionPoint.neg()); 2143 // translate ball 2144 Sphere newBall = ball.translateByT(closeToCollisionPoint.neg()); 2145 2146 // velocity of the torus at that point 2147 Vect3 myVel = angularVelocity.cross(pointOfCollision); 2148 // translate into refernce frame of moving ball 2149 Vect3 relativeV = velocity.minus(myVel); 2150 // reflect 2151 Vect3 reflectV = reflectTorus(newTorus, newBall, relativeV, reflectionCoeff); 2152 // translate back 2153 Vect3 absoluteV = myVel.plus(reflectV); 2154 return absoluteV; 2155 } 2156 2157 /***************************************************************************** 2158 * 2159 * METHODS FOR MULTI-BALL SIMULATIONS 2160 * 2161 ****************************************************************************/ 2162 2163 /** 2164 * Computes the time until two spheres collide. 2165 * 2166 * @effects computes the time until two spheres, travelling at specified 2167 * constant velocities, collide. If no collision will occur 2168 * <tt>POSITIVE_INFINITY</tt> is returned. This method assumes that 2169 * both spheres will travel at constant velocity until impact. 2170 * 2171 * @param sphere1 2172 * a sphere representing the size and initial position of the first 2173 * sphere. 2174 * 2175 * @param vel1 2176 * the velocity of the first sphere before impact 2177 * 2178 * @param sphere2 2179 * a sphere representing the size and initial position of the second 2180 * sphere. 2181 * 2182 * @param vel2 2183 * the velocity of the second sphere before impact 2184 * 2185 * @return the time until collision or <tt>POSITIVE_INFINITY</tt> if the 2186 * collision will not occur 2187 * 2188 * @see Double#POSITIVE_INFINITY 2189 */ 2190 public double timeUntilSphereSphereCollision(Sphere sphere1, Vect3 vel1, 2191 Sphere sphere2, Vect3 vel2) { 2192 Vect3 pos1 = sphere1.getCenter(); 2193 Vect3 pos2 = sphere2.getCenter(); 2194 double sizes = sphere1.getRadius() + sphere2.getRadius(); 2195 double initPosXDelta = pos1.x() - pos2.x(); 2196 double initPosYDelta = pos1.y() - pos2.y(); 2197 double initPosZDelta = pos1.z() - pos2.z(); 2198 double velXDelta = vel1.x() - vel2.x(); 2199 double velYDelta = vel1.y() - vel2.y(); 2200 double velZDelta = vel1.z() - vel2.z(); 2201 double sizes2 = sizes * sizes; 2202 double initPosXDelta2 = initPosXDelta * initPosXDelta; 2203 double initPosYDelta2 = initPosYDelta * initPosYDelta; 2204 double initPosZDelta2 = initPosZDelta * initPosZDelta; 2205 double initGap2 = initPosXDelta2 + initPosYDelta2 + initPosZDelta2; 2206 2207 double a = velXDelta * velXDelta + velYDelta * velYDelta + velZDelta 2208 * velZDelta; 2209 double b = 2 * initPosXDelta * velXDelta + 2 * initPosYDelta * velYDelta 2210 + 2 * initPosZDelta * velZDelta; 2211 double c = initGap2 - sizes2; 2212 2213 double t = minQuadraticSolution(a, b, c); 2214 if (Double.isNaN(t)) 2215 return Double.POSITIVE_INFINITY; 2216 if (t > 0) { 2217 return t; 2218 } else if (t <= 0 && pos1.minus(pos2).dot(vel1.minus(vel2)) < 0) { 2219 return 0; 2220 } 2221 return Double.POSITIVE_INFINITY; 2222 } 2223 2224 /** 2225 * Computes the resulting velocities of two spheres which collide. 2226 * 2227 * @requires mass1 > 0 && mass2 > 0 && the distance between the two spheres is 2228 * approximately equal to the sum of their radii; that is, the 2229 * spheres are positioned at the point of impact. 2230 * 2231 * @effects computes the resulting velocities of two spheres which collide. 2232 * 2233 * @param sphere1 2234 * the first sphere 2235 * 2236 * @param mass1 2237 * the mass of the first sphere 2238 * 2239 * @param velocity1 2240 * the velocity of the first sphere before impact 2241 * 2242 * @param sphere2 2243 * the second sphere 2244 * 2245 * @param mass2 2246 * the mass of the second sphere 2247 * 2248 * @param velocity2 2249 * the velocity of the second sphere before impact 2250 * 2251 * @return a <code>Vect3Pair</code>, where the first <code>Vect3</code> 2252 * is the velocity of the first sphere after the collision and the 2253 * second <code>Vect3</code> is the velocity of the second sphere 2254 * after the collision. 2255 */ 2256 public Vect3Pair reflectSpheres(Sphere sphere1, double mass1, 2257 Vect3 velocity1, Sphere sphere2, double mass2, Vect3 velocity2) { 2258 2259 double m1 = mass1; 2260 double m2 = mass2; 2261 double m = m1 / m2; 2262 2263 Vect3 tHat = sphere1.getCenter().minus(sphere2.getCenter()).unitSize(); 2264 double vx1 = velocity1.x(); 2265 double vx2 = velocity2.x(); 2266 2267 double vy1 = velocity1.y(); 2268 double vy2 = velocity2.y(); 2269 2270 double vz1 = velocity1.z(); 2271 double vz2 = velocity2.z(); 2272 2273 double tx = tHat.x(); 2274 double ty = tHat.y(); 2275 double tz = tHat.z(); 2276 2277 double gamma = (-2 * (vx1 * tx * m1 + vy1 * ty * m1 + vz1 * tz * m1 - vx2 2278 * tx * m * m2 - vy2 * ty * m * m2 - vz2 * tz * m * m2)) 2279 / (tx * tx * m1 + ty * ty * m1 + tz * tz * m1 + m * m * tx * tx * m2 2280 + m * m * ty * ty * m2 + m * m * tz * tz * m2); 2281 2282 return new Vect3Pair(velocity1.plus(tHat.times(gamma)), velocity2.plus(tHat 2283 .neg().times(gamma * m))); 2284 } 2285 2286 /** convenience method: calls the appropriate timeUntilCollision method **/ 2287 public double timeUntilCollision(PhysicsShape nextShape, Sphere ball, Vect3 velocity) { 2288 ShapeClassification sh = nextShape.getShapeClassification(); 2289 if (sh.equals(ShapeClassification.LATERAL_CYLINDER)) 2290 return this.timeUntilLateralCylinderCollision((LateralCylinder)nextShape, ball, velocity); 2291 else if (sh.equals(ShapeClassification.PLANE_CIRCLE)) 2292 return this.timeUntilPlaneCircleCollision((PlaneCircle)nextShape, ball, velocity); 2293 else if (sh.equals(ShapeClassification.PLANE_POLYGON)) 2294 return this.timeUntilPlanePolygonCollision((PlanePolygon)nextShape, ball, velocity); 2295 else if (sh.equals(ShapeClassification.SPHERE)) 2296 return this.timeUntilSphereCollision((Sphere)nextShape, ball, velocity); 2297 else if (sh.equals(ShapeClassification.TORUS)) 2298 return this.timeUntilTorusCollision((Torus)nextShape, ball, velocity); 2299 else 2300 throw new IllegalArgumentException("unknown shape nextShape="+nextShape); 2301 } 2302 2303 /** convenience method: calls the appropriate reflect method **/ 2304 public Vect3 reflect(PhysicsShape nextShape, Sphere ball, Vect3 velocity, double coref) { 2305 ShapeClassification sh = nextShape.getShapeClassification(); 2306 if (sh.equals(ShapeClassification.LATERAL_CYLINDER)) 2307 return this.reflectLateralCylinder((LateralCylinder)nextShape, ball, velocity, coref); 2308 else if (sh.equals(ShapeClassification.PLANE_CIRCLE)) 2309 return this.reflectPlaneCircle((PlaneCircle)nextShape, ball, velocity, coref); 2310 else if (sh.equals(ShapeClassification.PLANE_POLYGON)) 2311 return this.reflectPlanePolygon((PlanePolygon)nextShape, ball, velocity, coref); 2312 else if (sh.equals(ShapeClassification.SPHERE)) 2313 return this.reflectSphere((Sphere)nextShape, ball, velocity, coref); 2314 else if (sh.equals(ShapeClassification.TORUS)) 2315 return this.reflectTorus((Torus)nextShape, ball, velocity, coref); 2316 else 2317 throw new IllegalArgumentException("unknown shape nextShape="+nextShape); 2318 } 2319 2320 /** convenience method: calls the appropriate reflect method **/ 2321 public Vect3 reflectRotating(PhysicsShape nextShape, Vect3 center, Vect3 angularVel, Sphere ball, Vect3 velocity, double coref) { 2322 ShapeClassification sh = nextShape.getShapeClassification(); 2323 if (sh.equals(ShapeClassification.LATERAL_CYLINDER)) 2324 return this.reflectRotatingLateralCylinder((LateralCylinder)nextShape, center, angularVel, ball, velocity, coref); 2325 else if (sh.equals(ShapeClassification.PLANE_CIRCLE)) 2326 return this.reflectRotatingPlaneCircle((PlaneCircle)nextShape, center, angularVel, ball, velocity, coref); 2327 else if (sh.equals(ShapeClassification.PLANE_POLYGON)) 2328 return this.reflectRotatingPlanePolygon((PlanePolygon)nextShape, center, angularVel, ball, velocity, coref); 2329 else if (sh.equals(ShapeClassification.SPHERE)) 2330 return this.reflectRotatingSphere((Sphere)nextShape, center, angularVel, ball, velocity, coref); 2331 else if (sh.equals(ShapeClassification.TORUS)) 2332 return this.reflectRotatingTorus((Torus)nextShape, center, angularVel, ball, velocity, coref); 2333 else 2334 throw new IllegalArgumentException("unknown shape nextShape="+nextShape); 2335 } 2336 2337 public double timeUntilRotatingCollision(PhysicsShape shape, Vect3 center, Vect3 angularVel, Sphere ballShape, Vect3 velocity) { 2338 ShapeClassification sh = shape.getShapeClassification(); 2339 if (sh.equals(ShapeClassification.LATERAL_CYLINDER)) 2340 return this.timeUntilRotatingLateralCylinderCollision((LateralCylinder)shape, center, angularVel, ballShape, velocity); 2341 else if (sh.equals(ShapeClassification.PLANE_CIRCLE)) 2342 return this.timeUntilRotatingPlaneCircleCollision((PlaneCircle)shape, center, angularVel, ballShape, velocity); 2343 else if (sh.equals(ShapeClassification.PLANE_POLYGON)) 2344 return this.timeUntilRotatingPlanePolygonCollision((PlanePolygon)shape, center, angularVel, ballShape, velocity); 2345 else if (sh.equals(ShapeClassification.SPHERE)) 2346 return this.timeUntilRotatingSphereCollision((Sphere)shape, center, angularVel, ballShape, velocity); 2347 else if (sh.equals(ShapeClassification.TORUS)) 2348 return this.timeUntilRotatingTorusCollision((Torus)shape, center, angularVel, ballShape, velocity); 2349 else 2350 throw new IllegalArgumentException("unknown shape shape="+shape); 2351 2352 } 2353 }