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