001    package physics3d;
002    
003    import java.util.ArrayList;
004    import java.util.List;
005    import java.util.Set;
006    
007    import components.Ball;
008    import components.Flipper;
009    import components.GameObject;
010    
011    /** 
012     *  GamePhysics is an immutable class with static methods that does the physics calculations
013     *  
014     *  @specfield geometry : Geometry3D   // the object that does calculations
015     */
016    
017    /*
018     * Rep Invariant: 
019     * geometry != null
020     */
021    
022    public strictfp class GamePhysics {
023    
024        //fields
025        private static Geometry3D geometry = new Geometry3D(); //the brains of the operation
026        
027        ////// THE CRUX OF THE MATTER /////
028    
029        /** Move the given ball. *
030         * @throws FallBackException */
031        public static List<GameObject> moveBall(Ball ball) {
032            try {
033                    List<GameObject> collisions = new ArrayList<GameObject>(); //to be returned
034                    if (ball.isFrozen())
035                            return collisions;
036                    
037                    //System.err.println("entering moveBall with "+ ball);
038                    geometry.setForesightAndSubintervals(1.0/(double)ball.getGameSpace().getSettings().getFPS(), 20);
039                    
040                    //some local variables
041                    double timeleft = geometry.getMaximumForesight();
042                    //System.err.println("foresight/timeleft: "+timeleft);
043                    Collision coll = new Collision(ball);
044                    
045                    while (timeleft > 0.0) {
046                            //finds the next collision and sets its parameters: time till coll, object, shape
047                            coll = findNext(ball, timeleft);
048                    
049                            if (coll == null) {
050                                    freeMove(ball, timeleft);
051                                    return collisions;
052                            } else {
053                                    collisions.add(coll.object());
054                                    constantVelMove(ball, coll.time());
055                                    reflect(coll);
056                                    //freeMove(ball, 0.0001); //just a smidgen  
057                                    Collision c2 = findNext(ball, timeleft);
058                                    if (c2 != null && c2.time() < GameConstants.TOLERANCE) {
059                                            //damn itttttttt
060                                            //ball.setFrozen(true);
061                                            System.err.println("stupid collision!"+c2);
062            //                      try {
063            //                                              Thread.currentThread().sleep(1000);
064            //                                      } catch (InterruptedException e) {
065            //                                              e.printStackTrace();
066            //                                      }
067                                            throw new NumberFormatException("Crap!");
068            //                              return collisions;
069                                            //throw new RuntimeException("stupid collision! "+c2);
070                                    }
071                                    
072                                    //freeMove(ball, timeleft-coll.time());
073                                    timeleft = timeleft - coll.time(); // - 0.0001;
074                            }       
075                    } //loop while still in this frame
076            
077                    //System.err.println("exiting moveBall");
078                    return collisions;
079                    }
080                    catch (NumberFormatException e) { //hackish -- an exception not used elsewhere
081                            //System.out.println("Crap, that was bad, lets try the old version!");
082                            return OldGamePhysics.moveBall(ball);
083                    }
084        } //moveBall
085        
086        /** finds information about the next collision **/
087        private static Collision findNext(Ball ball, double timeleft) {
088            //some local variables
089            Set<GameObject> allObjects = ball.getGameSpace().getObjects();
090            Collision next = new Collision(ball);
091            Sphere ballShape = (Sphere)(world(ball.getBounds(), ball));
092            
093            
094            // get the possible collisions using bounding spheres???
095            List<GameObject> candidates = willCollideWithBound(ball, allObjects, timeleft);
096            if (candidates.size() == 0) {
097                    return null; //leave time/object/shape in their initial/null states
098            }
099            else {
100                    //System.out.println("candidates! " + candidates);
101            }
102           
103            ///* i live on the wild side Set<GameObject> candidates = allObjects;
104            
105            // now check only those possible collisions
106            for (GameObject g : candidates) {
107                //check each specific physics3d shape
108                    if (g instanceof Flipper) {
109                            for (PhysicsShape s : g.getShape().getParts()) {
110                                    PhysicsShape sShape = world(s, g);
111                                    Vect3 center = g.getCenter();
112                                    Vect3 angularVel = ((Flipper)g).getAngularVelocity();
113                                    double time = -1; //will always be set
114                                    if (angularVel.isAbout(Vect3.ZERO))
115                                            time = geometry.timeUntilCollision(sShape, ballShape, ball.getVelocity());
116                                    else { //TODO, also move flipper to nextTIme
117                                            time = geometry.timeUntilRotatingCollision(sShape, center, angularVel, ballShape, ball.getVelocity());
118                                    }
119                                            
120                                    if (time < next.time()) {
121                                            next.set(time, g, sShape);
122                                            //System.out.println("time until ball "+ ballShape  + " collides with " + sShape + ": "+ time);
123                            }
124                            }
125                    } else if (g instanceof Ball) {
126                            Sphere sphere1 = ballShape;
127                            Vect3 vel1 = ball.getVelocity();
128                            Sphere sphere2 = (Sphere) world(g.getBounds(), g);
129                            Vect3 vel2 = g.getVelocity();
130                            double time = geometry.timeUntilSphereSphereCollision(sphere1, vel1, sphere2, vel2);
131                            if (time < next.time()) {
132                                    next.set(time, g, sphere2);
133                                    //System.out.println("time until ball "+ ballShape  + " collides with " + sphere2 + ": "+ time);
134                            }
135                    } else {
136                        for (PhysicsShape s : g.getShape().getParts()) {
137                            PhysicsShape sShape = world(s, g);
138                            double time = geometry.timeUntilCollision(sShape, ballShape, ball.getVelocity());
139                            if (time < next.time()) {
140                                    //System.out.println("time until ball "+ ballShape  + " collides with " + sShape + ": "+ time);
141                                            next.set(time, g, sShape);
142                                    }
143                        }
144                    }
145            }
146            if (next.time() < timeleft) {
147                    //System.out.println("collision! " + next);
148                    return next; //return the collision
149            }
150            else
151                    return null; //not in this frame, baby
152        } //findNext
153    
154        /** Returns those GameObjects where the ball will collide with their bounds in the next
155         * timeleft seconds. */
156        public static List<GameObject> willCollideWithBound(Ball ball, Set<GameObject> objects, double timeleft) {
157            //System.err.println("entering willCollideWithBound");
158            List<GameObject> candidates = new ArrayList<GameObject>();
159            Sphere ballShape = (Sphere)(world(ball.getBounds(), ball));
160            
161            for (GameObject g : objects) {
162                    PhysicsShape gShape = world(g.getShape().getBound(), g);
163                    ////System.out.println(gShape + ", " 
164                    //              + ballShape + ", " + ball.getVelocity());
165                double time = geometry.timeUntilCollision(gShape, ballShape, ball.getVelocity());
166                ////System.out.println("time until ball "+ ballShape  + "with velocity "+ ball.getVelocity() + " collides with bound " + gShape + ": "+ time);
167                
168                //does ballShape overlap with gShape?
169                boolean overlaps = false;
170                if (gShape instanceof Sphere) {
171                    double distance = ((Sphere)gShape).getCenter().minus(ballShape.getCenter()).rho();
172                    overlaps = distance < ((Sphere)gShape).getRadius() + ballShape.getRadius();
173                } else if (gShape instanceof PlanePolygon) { //walls
174                    double distance = ((PlanePolygon)gShape).minDistanceToObjectFromP(ballShape.getCenter());
175                    overlaps = distance < ballShape.getRadius();
176                } else
177                    throw new RuntimeException("unknown bound: "+gShape);
178                
179                if (time < timeleft || overlaps)
180                    candidates.add(g);
181            }
182            return candidates;
183        }
184        
185        /** Move the given ball for the specified amount of time, under only the
186         * influence of gravity and air resistance
187         * @effects ball.velocity, ball.position
188         */
189        public static void freeMove(Ball ball, double time) {
190            Vect3 vel = ball.getVelocity();
191            double mass = 1.0; //ball.getMass();
192            Vect3 pos = ball.getCenter();
193            
194            //f_d = -bv, b = mu + mu2*|v|
195            double b = ball.getGameSpace().getSettings().getMu() + ball.getGameSpace().getSettings().getMu2()*vel.rho();
196            Vect3 f_drag = vel.times(-1.3 * b);
197            //f_g = mg
198            Vect3 f_gravity = ball.getGameSpace().getSettings().getGravity().times(mass); //TODO, REMOVE THE FUDGE
199            //f_net = f_d + f_g
200            Vect3 f_net = f_drag.plus(f_gravity);
201            
202            //where the ball should move
203            //x_f = x_i + t*v_i + 1/2at^2  //assumes constant acceleration
204            Vect3 next_pos = pos.plus( vel.times(time) ).plus ( f_net.times(1/mass).times(0.5).times(time*time));
205            //v_f = v_i + f_net/mass * t
206            Vect3 next_vel = vel.plus(f_net.times(1/mass).times(time));
207            
208            ball.setCenter(next_pos);
209            ball.setVelocity(next_vel);
210        } //freeMove
211        
212        /** move the ball at its current velocity for time time **/
213        public static void constantVelMove(Ball b
214                    , double time) {
215            //x_f = x_i + v*dt
216            b.setCenter(b.getCenter().plus(b.getVelocity().times(time))); 
217        }
218        
219        
220        /** return p in the worldspace of o **/
221        public static PhysicsShape world(PhysicsShape p, GameObject o) {
222            ////System.out.println("world: p="+p+", o="+o+", pos="+o.getPosition());
223            return p.translateByT(o.getCenter()).rotateAboutCwithAxisAandAngleT(o.getCenter(), o.getOrientVector(), o.getOrientAngle());
224            //return p.rotateAboutCwithAxisAandAngleT(o.getCenter(), o.getOrientVector(), o.getOrientAngle()).translateByT(o.getCenter());
225        }
226        
227            /** reflect the collisioner off the collisionee
228             * @require fields not null
229             * @require collisioner and collisionee are adjacent
230             */
231            private static void reflect(Collision coll) {
232                    Ball b = coll.ball();
233                    //System.out.print("reflect! p="+b.getCenter()+", v_before="+b.getVelocity());
234                    GameObject obj = coll.object();
235                    Sphere bShape = (Sphere)b.getBounds();
236                    //reflect appropriately
237                    if (obj instanceof Flipper) { //rotating reflect
238                            Vect3 center = obj.getCenter();
239                            Vect3 angularVel = ((Flipper)obj).getAngularVelocity();
240                            if (angularVel.isAbout(Vect3.ZERO)) 
241                                    b.setVelocity( geometry.reflect(coll.shape(), bShape, b.getVelocity(), obj.getCoRef()));
242                            else
243                                    b.setVelocity( geometry.reflectRotating(coll.shape(), center, angularVel, bShape, b.getVelocity(), obj.getCoRef()));
244                    } else if (obj instanceof Ball) { //reflecting off another ball o_o
245                            Sphere sphere1 = (Sphere) world(b.getBounds(), b);
246                            Vect3 velocity1 = b.getVelocity();
247                            Sphere sphere2 = (Sphere)coll.shape();
248                            Vect3 velocity2 = obj.getVelocity();
249                            Vect3Pair newVel = geometry.reflectSpheres(sphere1, 1.0, velocity1, sphere2, 1.0, velocity2);
250                            b.setVelocity(newVel.v1);
251                            obj.setVelocity(newVel.v2);
252                            
253                    } else { //regular reflect
254                            b.setVelocity( geometry.reflect(coll.shape(), bShape, b.getVelocity(), obj.getCoRef()));
255                    }
256                    //System.out.println(", v_after="+b.getVelocity());
257            } //reflect
258    } //GamePhysics