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    }