View Javadoc
1   package com.jed.core;
2   
3   import com.jed.actor.AbstractEntity;
4   import com.jed.util.Vector2f;
5   import org.colapietro.number.util.Doubles;
6   import com.jed.actor.Boundary;
7   import com.jed.state.MapTile;
8   
9   import javax.annotation.Nonnull;
10  
11  /**
12   * 
13   * @author jlinde, Peter Colapietro
14   *
15   */
16  public class Collision implements Comparable<Collision> {
17  
18      /**
19       * 
20       */
21      private static final int NONE = 0;
22      
23      /**
24       * 
25       */
26      private static final int SAT = 1;
27      
28      /**
29       * 
30       */
31      private static final int SWEPT_X = 2;
32      
33      /**
34       * 
35       */
36      private static final int SWEPT_Y = 3;
37  
38      /**
39       * 
40       */
41      private int collisionType = NONE;
42  
43      /**
44       * 
45       */
46      private final AbstractEntity a;
47  
48      /**
49       *
50       */
51      private final AbstractEntity b;
52      
53      /**
54       * 
55       */
56      private double minXDistance, minYDistance;
57      
58      /**
59       * 
60       */
61      private double smallestDisplacement;
62      
63      /**
64       * 
65       */
66      private MinMax xEntityMinMax, xSEntityMinMax, yEntityMinMax, ySEntityMinMax;
67      
68      /**
69       * 
70       */
71      private final boolean isDebugViewEnabled;
72  
73      /**
74       * 
75       * Use {@link #Collision(AbstractEntity, AbstractEntity, boolean)}.
76       * 
77       * @param a entity a.
78       * @param b entity b.
79       * 
80       */
81      @Deprecated
82      public Collision(AbstractEntity a, AbstractEntity b) {
83          this.a = a;
84          this.b = b;
85          this.isDebugViewEnabled = false;
86      }
87      
88      /**
89       * @since 0.1.8
90       * 
91       * @param a entity a.
92       * @param b entity b.
93       * @param isDebugViewEnabled isDebugViewEnabled
94       */
95      public Collision(AbstractEntity a, AbstractEntity b, boolean isDebugViewEnabled) {
96          this.a = a;
97          this.b = b;
98          this.isDebugViewEnabled = isDebugViewEnabled ;
99      }
100 
101     /**
102      * 
103      * @return the smallest displacement.
104      */
105     public double smallestDisplacement() {
106         return smallestDisplacement;
107     }
108 
109     /**
110      * 
111      * @return if a collision was detected.
112      */
113     public boolean detectCollision() {
114 
115         //TODO: must take into account relative motion vector when dealing w/ 2 moving objects for swept test!!!
116 
117         Vector2f xAxis = new Vector2f(1, 0);
118         Vector2f yAxis = new Vector2f(0, 1);
119 
120         xEntityMinMax = new MinMax(a.getBounds(), xAxis);
121         xSEntityMinMax = new MinMax(b.getBounds(), xAxis);
122         minXDistance = Math.abs(xEntityMinMax.getIntervalDistance(xSEntityMinMax));
123 
124         yEntityMinMax = new MinMax(a.getBounds(), yAxis);
125         ySEntityMinMax = new MinMax(b.getBounds(), yAxis);
126         minYDistance = Math.abs(yEntityMinMax.getIntervalDistance(ySEntityMinMax));
127 
128         boolean separateX =
129                 xEntityMinMax.max < xSEntityMinMax.min ||
130                         xSEntityMinMax.max < xEntityMinMax.min;
131 
132         boolean separateY =
133                 yEntityMinMax.max < ySEntityMinMax.min ||
134                         ySEntityMinMax.max < yEntityMinMax.min;
135 
136         //Separating AXIS Theorem
137         if (!separateX && !separateY) {
138             collisionType = SAT;
139         } else
140 
141             //Swept Separating Axis Theorem
142             if (a.getMovement().x != 0 || a.getMovement().y != 0) {
143                 if (Math.abs(a.getMovement().dotProduct(yAxis)) > minYDistance &&
144                         !(xEntityMinMax.max <= xSEntityMinMax.min || xSEntityMinMax.max <= xEntityMinMax.min)) {
145 
146                     collisionType = SWEPT_Y;
147                 } else if (Math.abs(a.getMovement().dotProduct(xAxis)) > minXDistance &&
148                         !(yEntityMinMax.max <= ySEntityMinMax.min || ySEntityMinMax.max <= yEntityMinMax.min)) {
149 
150                     collisionType = SWEPT_X;
151                 }
152             }
153 
154         smallestDisplacement = minXDistance < minYDistance ? minXDistance : minYDistance;
155 
156         //TODO: Temporary!
157         if(isDebugViewEnabled) {
158             if (collisionType != NONE) {
159                 ((MapTile) b).setColliding(true);
160             } else {
161                 ((MapTile) b).setEvaluating(true);
162             }
163         }
164 
165         return collisionType != NONE;
166 
167     }
168 
169     /**
170      * 
171      */
172     public void resolveCollision() {
173 
174         //OVERLAPS
175         if (collisionType == SAT) {
176 
177             if (minYDistance != 0) {
178                 /**
179                  * Resolve Wall Collision if this is the 1st frame
180                  * (i.e. player is holding over against the wall minX is = to acceleration)
181                  *  Or
182                  * if the x axis overlap is smaller push out
183                  */
184 
185                 //FIXME test
186                 if (Doubles.compareDoubles(minXDistance, a.getAcceleration()) || minXDistance < minYDistance) {
187                     if (xEntityMinMax.min > xSEntityMinMax.min) {
188                         a.getPosition().x += minXDistance;
189                     } else {
190                         a.getPosition().x -= minXDistance;
191                     }
192 
193                     a.getMovement().x = 0;
194                 }
195                 //Resolve Ceiling / Floor Collisions
196                 else {
197                     if (yEntityMinMax.min > ySEntityMinMax.min) {
198                         a.getPosition().y += minYDistance;
199                     } else {
200                         a.getPosition().y -= minYDistance;
201                         a.collideDown(b);
202                     }
203                     a.getMovement().y = 0;
204 
205                 }
206             }
207 
208             //Notify Entity they're standing on a platform and don't have to start falling...
209             if (minYDistance == 0 && a.getMovement().y == 0 && yEntityMinMax.min < ySEntityMinMax.min) {
210                 a.collideDown(b);
211             }
212         }
213         //Y Collision on the next game update - accounts for fast moving objects
214         else if (collisionType == SWEPT_Y) {
215 
216             if (yEntityMinMax.min > ySEntityMinMax.min) {
217                 //Closest Edge is above the entity, only collide if moving towards it
218                 if (a.getMovement().y <= 0 && minXDistance != 0) {
219                     a.getPosition().y -= minYDistance;
220                     a.getMovement().y = 0;
221                 }
222             } else {
223                 //Closest Edge is below the entity, only collide if moving towards it
224                 if (a.getMovement().y >= 0 && minXDistance != 0) {
225                     a.getMovement().y = 0;
226                     a.getPosition().y += minYDistance;
227                     a.collideDown(b);
228                 }
229             }
230 
231         }
232         //X Collision on the next game update - accounts for fast moving objects
233         else if (collisionType == SWEPT_X) {
234             if (xEntityMinMax.min > xSEntityMinMax.min) {
235                 a.getPosition().x -= minXDistance;
236             } else {
237                 a.getPosition().x += minXDistance;
238             }
239         }
240 
241     }
242 
243     /**
244      * 
245      * @author jlinde, Peter Colapietro
246      *
247      */
248     private class MinMax {
249 
250         /**
251          * 
252          */
253         public double min, max;
254 
255         /**
256          * 
257          * @param boundary boundary
258          * @param axis axis
259          */
260         public MinMax(@Nonnull Boundary boundary, Vector2f axis) {
261             max = boundary.vertices[0].add(boundary.getWorldPosition()).dotProduct(axis);
262             min = boundary.vertices[0].add(boundary.getWorldPosition()).dotProduct(axis);
263 
264             double current;
265             for (int i = 1; i < boundary.vertices.length; i++) {
266                 current = boundary.vertices[i].add(boundary.getWorldPosition()).dotProduct(axis);
267                 if (min > current) {
268                     min = current;
269                 }
270 
271                 if (current > max) {
272                     max = current;
273                 }
274             }
275         }
276 
277         /**
278          * 
279          * @param b another minmax
280          * @return interval distance
281          */
282         public double getIntervalDistance(@Nonnull MinMax b) {
283             if (this.min < b.min) {
284                 return b.min - this.max;
285             } else {
286                 return this.min - b.max;
287             }
288         }
289 
290     }
291 
292     @Override
293     public int compareTo(@Nonnull Collision c) {
294         if (collisionType == SAT) {
295             if (c.collisionType != SAT) {
296                 return -1;
297             } else {
298                 return
299                         Doubles.compareDoubles(smallestDisplacement, c.smallestDisplacement) ? 0 :
300                                 smallestDisplacement < c.smallestDisplacement ? -1 : 1;//FIXME test
301             }
302         } else if (collisionType == SWEPT_Y) {
303             if (c.collisionType == SAT) {
304                 return 1;
305             } else if (c.collisionType == SWEPT_X) {
306                 return -1;
307             } else {
308                 return
309                         Doubles.compareDoubles(minYDistance,c.minYDistance) ? 0 :
310                                 minYDistance < c.minYDistance ? -1 : 1;//FIXME test
311             }
312         } else if (collisionType == SWEPT_X) {
313             if (c.collisionType != SWEPT_X) {
314                 return 1;
315             } else {
316                 return
317                         Doubles.compareDoubles(minXDistance,c.minXDistance) ? 0 :
318                                 minXDistance < c.minXDistance ? -1 : 1; //FIXME test
319             }
320 
321         }
322         return 0;
323     }
324 }