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 }