View Javadoc
1   package com.jed.state;
2   
3   import com.jed.actor.AbstractEntity;
4   import com.jed.actor.Ball;
5   import com.jed.actor.CircleBoundary;
6   import com.jed.core.MotherBrainConstants;
7   import com.jed.core.QuadTree;
8   import com.jed.util.Rectangle;
9   import com.jed.util.Vector2f;
10  import org.slf4j.Logger;
11  import org.slf4j.LoggerFactory;
12  
13  import javax.annotation.Nonnull;
14  import java.util.ArrayList;
15  import java.util.List;
16  import java.util.Random;
17  import java.util.Stack;
18  
19  /**
20   * 
21   * @author jlinde, Peter Colapietro
22   *
23   * TODO Decouple from com.jed.core.MotherBrain / com.jed.core.MotherBrainConstants
24   *
25   */
26  public class DiscoState extends AbstractGameState {
27  
28      /**
29       * 
30       */
31      private static final Logger LOGGER = LoggerFactory.getLogger(DiscoState.class);
32  
33      /**
34       * 
35       */
36      private QuadTree quadTree;
37  
38      /**
39       * 
40       */
41      private Stack<Ball> scene;
42  
43      /**
44       * 
45       */
46      private int width;
47      
48      /**
49       * 
50       */
51      private int height;
52  
53      @Override
54      public void entered() {
55          width = MotherBrainConstants.WIDTH;
56          height = MotherBrainConstants.HEIGHT;
57  
58          scene = new GameEntityStack<>();
59          quadTree = new QuadTree(new Vector2f(0, 0), 0, new Rectangle(width, height), this);
60  
61          Random rand = new Random();
62  
63          int randW, randH, randR;
64          float randXS, randYS, randRed, randGreen, randBlue;
65  
66          for (int i = 0; i < 25; i++) {
67              randW = rand.nextInt(1024) + 1;
68              randH = rand.nextInt(768) + 1;
69              randR = rand.nextInt(25) + 1;
70              randXS = rand.nextFloat() * 2;
71              randYS = rand.nextFloat() * 2;
72              randRed = rand.nextFloat();
73              randGreen = rand.nextFloat();
74              randBlue = rand.nextFloat();
75  
76              Ball newBall = new Ball(
77                      new Vector2f(randW, randH),
78                      new Vector2f(randXS, randYS),
79                      new CircleBoundary(randR),
80                      25,
81                      randRed, randGreen, randBlue);
82              scene.push(newBall);
83          }
84  
85          /*
86          Ball ball1 = new Ball(new Vector(50,50), new Vector(.05f,.03f), 30, 25);
87          scene.push(ball1);
88  
89          Ball ball2 = new Ball(new Vector(100,100), new Vector(-.04f,0.05f), 10, 25);
90          scene.push(ball2);
91  
92          Ball ball3 = new Ball(new Vector(800,300), new Vector(.04f,0.07f), 100, 25);
93          scene.push(ball3);
94  
95          Ball ball4 = new Ball(new Vector(200,50), new Vector(.05f,.03f), 10, 25);
96          scene.push(ball4);
97  
98          Ball ball5 = new Ball(new Vector(50,400), new Vector(-.04f,0.05f), 10, 25);
99          scene.push(ball5);
100 
101         Ball ball6 = new Ball(new Vector(1000,20), new Vector(.4f,0.07f), 5, 25);
102         scene.push(ball6);
103 
104         Ball ball7 = new Ball(new Vector(500,35), new Vector(.05f,.03f), 15, 25);
105         scene.push(ball7);
106 
107         Ball ball8 = new Ball(new Vector(600,100), new Vector(-.04f,0.05f), 10, 25);
108         scene.push(ball8);
109 
110         Ball ball9 = new Ball(new Vector(270,300), new Vector(.04f,0.07f), 100, 25);
111         scene.push(ball9);
112 
113         Ball ball10 = new Ball(new Vector(890,50), new Vector(.05f,.03f), 10, 25);
114         scene.push(ball10);
115 
116         Ball ball11 = new Ball(new Vector(65,400), new Vector(-.04f,0.05f), 10, 25);
117         scene.push(ball11);
118 
119         Ball ball12 = new Ball(new Vector(88,220), new Vector(.04f,0.07f), 10, 25);
120         scene.push(ball12);
121 
122         Ball ball13 = new Ball(new Vector(18,20), new Vector(.4f,0.07f), 5, 25);
123         scene.push(ball13);
124 
125         Ball ball14 = new Ball(new Vector(500,20), new Vector(.4f,0.07f), 5, 25);
126         scene.push(ball14);
127 
128         Ball ball15 = new Ball(new Vector(600,20), new Vector(.4f,0.07f), 5, 25);
129         scene.push(ball15);
130 
131         Ball ball16 = new Ball(new Vector(700,20), new Vector(.4f,0.07f), 5, 25);
132         scene.push(ball16);
133 
134         Ball ball17 = new Ball(new Vector(900,20), new Vector(.4f,0.07f), 5, 25);
135         scene.push(ball17);
136 
137         Ball ball18 = new Ball(new Vector(601,20), new Vector(.4f,0.07f), 5, 25);
138         scene.push(ball18);*/
139     }
140 
141     @Override
142     public void update() {
143         quadTree.clear();
144         scene.forEach(quadTree::insert);
145         handleCollisions();
146     }
147 
148     @Override
149     public void render() {
150         quadTree.render();
151         scene.forEach(Ball::render);
152     }
153 
154     /**
155      *
156      */
157     private void handleCollisions() {//TODO Test handleCollisions().
158         boolean collide = false;
159         final List<AbstractEntity> returnObjects = new ArrayList<>();
160         for (Ball aScene : scene) {
161             quadTree.retrieve(returnObjects, aScene);
162             for (AbstractEntity returnObject : returnObjects) {
163                 if (!returnObject.equals(aScene)) {
164                     final Ball p2 = (Ball) returnObject;
165                     if (detectCollision(aScene, p2)) {
166                         if (!collide) {
167                             LOGGER.debug("Handling Collisions");
168                             collide = true;
169                         }
170                         collide(aScene, p2);
171                     }
172                 }
173             }
174             returnObjects.clear();
175         }
176 
177         //Boundary collisions
178         for (Ball each : scene) {
179             double yPosition = each.getPosition().y;
180             double xPosition = each.getPosition().x;
181             float radius = each.getRadius();
182 
183             //Alter the movement vector, move the ball in the opposite
184             //direction, then
185             //Adjust the position vector so that the ball
186             //does not get stuck in the wall
187             if (yPosition + radius >= height) {
188                 each.getMovement().y = each.getMovement().y * -1;
189                 each.getPosition().y = height - each.getRadius();
190             } else if (yPosition - radius <= 0) {
191                 each.getMovement().y = each.getMovement().y * -1;
192                 each.getPosition().y = each.getRadius();
193             }
194 
195             if (xPosition + radius >= width) {
196                 each.getMovement().x = each.getMovement().x * -1;
197                 each.getPosition().x = width - each.getRadius();
198             } else if (xPosition - radius <= 0) {
199                 each.getMovement().x = each.getMovement().x * -1;
200                 each.getPosition().x = each.getRadius();
201             }
202         }
203     }
204 
205     /**
206      * 
207      * @param p1 ball one
208      * @param p2 ball two
209      * @return if ball one and two collided or not
210      */
211     private boolean detectCollision(@Nonnull Ball p1, @Nonnull Ball p2) {
212         /**
213          * Subtract p2's movement vector from p1 the resultant vector
214          * Represents where the two balls will collide, if they do
215          * by assuming p2 is static
216          */
217         Vector2f mv = p1.getMovement().subtract(p2.getMovement());
218 
219         /**
220          * The movement vector must be at least the distance between
221          * the centers of the circles minus the radius of each.
222          * If it is not, then there is no way that the circles
223          * will collide.
224          */
225         double dist = p1.getPosition().distance(p2.getPosition());
226         double sumRadii = p1.getRadius() + p2.getRadius();
227         dist -= sumRadii;
228         double mvMagnitude = mv.magnitude();
229         if (mvMagnitude < dist) {
230             return false;
231         }
232 
233         /**
234          * Find c, the vector from the center of p1 to the center of p2
235          */
236         Vector2f c = p2.getPosition().subtract(p1.getPosition());
237 
238         /**
239          * Normalize the movement vector to determine if p1 is moving towards p2
240          */
241         Vector2f mvN = mv.normalize();
242 
243         /**
244          * Dot product of the normalized movement vector and the difference
245          * between the position vectors, this will tell how far along the
246          * movement vector the ball will collide with the other, if
247          * the result is zero or less, the balls will never collide
248          */
249         double d = mvN.dotProduct(c);
250         if (d <= 0) {
251             return false;
252         }
253 
254 
255         /**
256          * f is the distance between the center of p1 and the movement vector
257          * if this distance is greater than the radii squared, the balls
258          * will never touch
259          */
260         double lengthC = c.magnitude();
261         double f = (lengthC * lengthC) - (d * d);
262 
263         double sumRadiiSquared = sumRadii * sumRadii;
264         if (f >= sumRadiiSquared) {
265             return false;
266         }
267 
268         /**
269          * Math#sqrt(double) represents the distance between the 90 degree intersection
270          * of the point on the movement vector and the ball minus the
271          * 2 radii of the balls. So it's the square root of the distance along the movement
272          * vector where the balls WILL intersect
273          */
274         double t = sumRadiiSquared - f;
275 
276         /**
277          * If there is no such right triangle with sides length of
278          * sumRadii and Math#sqrt(double), T will probably be less than 0.
279          * Better to check now than perform a square root of a
280          * negative number.
281          */
282         if (t < 0) {
283             return false;
284         }
285 
286         /**
287          * The distance the circle has to travel along
288          *    move vector is D - Math#sqrt(double)
289          */
290         double mvDistance = d - Math.sqrt(t);
291 
292         /**
293          * Finally, make sure that the distance A has to move
294          * to touch B is not greater than the magnitude of the
295          * movement vector.
296          */
297         if (mvMagnitude < mvDistance) {
298             return false;
299         }
300 
301         //TODO: fix this to adjust the ball
302         /**
303          * Adjust the displacement of p1 so that it doesn't become "entwined"
304          * with the other ball. Place it right where the collision would have occurred
305          */
306         LOGGER.debug("result = " + mv.magnitude() / p1.getMovement().magnitude());
307         return true;
308     }
309 
310     /**
311      * 
312      * @param p1 ball one
313      * @param p2 ball two
314      */
315     private void collide(@Nonnull Ball p1, @Nonnull Ball p2) {
316         // First, find the normalized vector n from the center of
317         // circle1 to the center of circle2
318         Vector2f n = (p1.getPosition().subtract(p2.getPosition())).normalize();
319 
320         // Find the length of the component of each of the movement
321         // vectors along n.
322         // a1 = v1 . n
323         // a2 = v2 . n
324         double a1 = p1.getMovement().dotProduct(n);
325         double a2 = p2.getMovement().dotProduct(n);
326 
327         double optimizedP = (2.0 * (a1 - a2)) / (p1.mass() + p2.mass());
328 
329         // Calculate v1', the new movement vector of circle1
330         // v1' = v1 - optimizedP * m2 * n
331         Vector2f v1 = p1.getMovement().subtract(n.scale((float) (optimizedP * p2.mass())));
332 
333         // Calculate v1', the new movement vector of circle1
334         // v2' = v2 + optimizedP * m1 * n
335         Vector2f v2 = p2.getMovement().add(n.scale((float) (optimizedP * p1.mass())));
336 
337         p1.setMovement(v1);
338         p2.setMovement(v2);
339     }
340 }