1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 
18 package com.replica.replicaisland;
19 
20 import java.util.Comparator;
21 
22 /**
23  * Handles collision against the background.  Snaps colliding objects out of collision and reports
24  * the hit to the parent game object.
25  */
26 public class BackgroundCollisionComponent extends GameComponent {
27     private Vector2 mPreviousPosition;
28     private int mWidth;
29     private int mHeight;
30     private int mHorizontalOffset;
31     private int mVerticalOffset;
32 
33     // Workspace vectors.  Allocated up front for speed.
34     private Vector2 mCurrentPosition;
35     private Vector2 mPreviousCenter;
36     private Vector2 mDelta;
37     private Vector2 mFilterDirection;
38     private Vector2 mHorizontalHitPoint;
39     private Vector2 mHorizontalHitNormal;
40     private Vector2 mVerticalHitPoint;
41     private Vector2 mVerticalHitNormal;
42     private Vector2 mRayStart;
43     private Vector2 mRayEnd;
44     private Vector2 mTestPointStart;
45     private Vector2 mTestPointEnd;
46     private Vector2 mMergedNormal;
47 
48     /**
49      * Sets up the collision bounding box.  This box may be a different size than the bounds of the
50      * sprite that this object controls.
51      * @param width  The width of the collision box.
52      * @param height  The height of the collision box.
53      * @param horzOffset  The offset of the collision box from the object's origin in the x axis.
54      * @param vertOffset  The offset of the collision box from the object's origin in the y axis.
55      */
BackgroundCollisionComponent(int width, int height, int horzOffset, int vertOffset)56     public BackgroundCollisionComponent(int width, int height, int horzOffset, int vertOffset) {
57         super();
58         setPhase(ComponentPhases.COLLISION_RESPONSE.ordinal());
59         mPreviousPosition = new Vector2();
60         mWidth = width;
61         mHeight = height;
62         mHorizontalOffset = horzOffset;
63         mVerticalOffset = vertOffset;
64 
65         mCurrentPosition = new Vector2();
66         mPreviousCenter = new Vector2();
67         mDelta = new Vector2();
68         mFilterDirection = new Vector2();
69         mHorizontalHitPoint = new Vector2();
70         mHorizontalHitNormal = new Vector2();
71         mVerticalHitPoint = new Vector2();
72         mVerticalHitNormal = new Vector2();
73         mRayStart = new Vector2();
74         mRayEnd = new Vector2();
75         mTestPointStart = new Vector2();
76         mTestPointEnd = new Vector2();
77         mMergedNormal = new Vector2();
78     }
79 
BackgroundCollisionComponent()80     public BackgroundCollisionComponent() {
81         super();
82         setPhase(ComponentPhases.COLLISION_RESPONSE.ordinal());
83         mPreviousPosition = new Vector2();
84         mCurrentPosition = new Vector2();
85         mPreviousCenter = new Vector2();
86         mDelta = new Vector2();
87         mFilterDirection = new Vector2();
88         mHorizontalHitPoint = new Vector2();
89         mHorizontalHitNormal = new Vector2();
90         mVerticalHitPoint = new Vector2();
91         mVerticalHitNormal = new Vector2();
92         mRayStart = new Vector2();
93         mRayEnd = new Vector2();
94         mTestPointStart = new Vector2();
95         mTestPointEnd = new Vector2();
96         mMergedNormal = new Vector2();
97     }
98 
99     @Override
reset()100     public void reset() {
101         mPreviousPosition.zero();
102     }
103 
setSize(int width, int height)104     public void setSize(int width, int height) {
105         mWidth = width;
106         mHeight = height;
107         // TODO: Resize might cause new collisions.
108     }
109 
setOffset(int horzOffset, int vertOffset)110     public void setOffset(int horzOffset, int vertOffset) {
111         mHorizontalOffset = horzOffset;
112         mVerticalOffset = vertOffset;
113     }
114 
115     /**
116      * This function is the meat of the collision response logic.  Our collision detection and
117      * response must be capable of dealing with arbitrary surfaces and must be frame rate
118      * independent (we must sweep the space in-between frames to find collisions reliably).  The
119      * following algorithm is used to keep the collision box out of the collision world.
120      *      1.  Cast a ray from the center point of the box at its position last frame to the edge
121      *          of the box at its current position.  If the ray intersects anything, snap the box
122      *          back to the point of intersection.
123      *      2.  Perform Step 1 twice: once looking for surfaces opposing horizontal movement and
124      *          again for surfaces opposing vertical movement.  These two ray tests approximate the
125      *          movement of the box between the previous frame and this one.
126      *      3.  Since most collisions are collisions with the ground, more precision is required for
127      *          vertical intersections.  Perform another ray test, this time from the top of the
128      *          box's position (after snapping in Step 2) to the bottom.  Snap out of any vertical
129      *          surfaces that the ray encounters.  This will ensure consistent snapping behavior on
130      *          incline surfaces.
131      *      4.  Add the normals of the surfaces that were hit up and normalize the result to produce
132      *          a direction describing the average slope of the surfaces that the box is resting on.
133      *          Physics will use this value as a normal to resolve collisions with the background.
134      */
135     @Override
update(float timeDelta, BaseObject parent)136     public void update(float timeDelta, BaseObject parent) {
137         GameObject parentObject = (GameObject) parent;
138         parentObject.setBackgroundCollisionNormal(Vector2.ZERO);
139         if (mPreviousPosition.length2() != 0) {
140             CollisionSystem collision = sSystemRegistry.collisionSystem;
141             if (collision != null) {
142                 final int left = mHorizontalOffset;
143                 final int bottom = mVerticalOffset;
144                 final int right = left + mWidth;
145                 final int top = bottom + mHeight;
146                 final float centerOffsetX = ((mWidth) / 2.0f) + left;
147                 final float centerOffsetY = ((mHeight) / 2.0f) + bottom;
148 
149                 mCurrentPosition.set(parentObject.getPosition());
150                 mDelta.set(mCurrentPosition);
151                 mDelta.subtract(mPreviousPosition);
152 
153                 mPreviousCenter.set(centerOffsetX, centerOffsetY);
154                 mPreviousCenter.add(mPreviousPosition);
155 
156                 boolean horizontalHit = false;
157                 boolean verticalHit = false;
158 
159                 mVerticalHitPoint.zero();
160                 mVerticalHitNormal.zero();
161                 mHorizontalHitPoint.zero();
162                 mHorizontalHitNormal.zero();
163 
164 
165                 // The order in which we sweep the horizontal and vertical space can affect the
166                 // final result because we perform incremental snapping mid-sweep.  So it is
167                 // necessary to sweep in the primary direction of movement first.
168                 if (Math.abs(mDelta.x) > Math.abs(mDelta.y)) {
169                     horizontalHit = sweepHorizontal(mPreviousCenter, mCurrentPosition, mDelta, left,
170                             right, centerOffsetY, mHorizontalHitPoint, mHorizontalHitNormal,
171                             parentObject);
172                     verticalHit = sweepVertical(mPreviousCenter, mCurrentPosition, mDelta, bottom,
173                             top, centerOffsetX, mVerticalHitPoint, mVerticalHitNormal,
174                             parentObject);
175 
176                 } else {
177                     verticalHit = sweepVertical(mPreviousCenter, mCurrentPosition, mDelta, bottom,
178                             top, centerOffsetX, mVerticalHitPoint, mVerticalHitNormal,
179                             parentObject);
180                     horizontalHit = sweepHorizontal(mPreviousCenter, mCurrentPosition, mDelta, left,
181                             right, centerOffsetY, mHorizontalHitPoint, mHorizontalHitNormal,
182                             parentObject);
183                 }
184 
185                 // force the collision volume to stay within the bounds of the world.
186                 LevelSystem level = sSystemRegistry.levelSystem;
187                 if (level != null) {
188                     if (mCurrentPosition.x + left < 0.0f) {
189                         mCurrentPosition.x = (-left + 1);
190                         horizontalHit = true;
191                         mHorizontalHitNormal.x = (mHorizontalHitNormal.x + 1.0f);
192                         mHorizontalHitNormal.normalize();
193                     } else if (mCurrentPosition.x + right > level.getLevelWidth()) {
194                         mCurrentPosition.x = (level.getLevelWidth() - right - 1);
195                         mHorizontalHitNormal.x = (mHorizontalHitNormal.x - 1.0f);
196                         mHorizontalHitNormal.normalize();
197                         horizontalHit = true;
198                     }
199 
200                     /*if (mCurrentPosition.y + bottom < 0.0f) {
201                         mCurrentPosition.y = (-bottom + 1);
202                         verticalHit = true;
203                         mVerticalHitNormal.y = (mVerticalHitNormal.y + 1.0f);
204                         mVerticalHitNormal.normalize();
205                     } else*/ if (mCurrentPosition.y + top > level.getLevelHeight()) {
206                         mCurrentPosition.y = (level.getLevelHeight() - top - 1);
207                         mVerticalHitNormal.y = (mVerticalHitNormal.y - 1.0f);
208                         mVerticalHitNormal.normalize();
209                         verticalHit = true;
210                     }
211 
212                 }
213 
214 
215                 // One more set of tests to make sure that we are aligned with the surface.
216                 // This time we will just check the inside of the bounding box for intersections.
217                 // The sweep tests above will keep us out of collision in most cases, but this
218                 // test will ensure that we are aligned to incline surfaces correctly.
219 
220                 // Shoot a vertical line through the middle of the box.
221                 if (mDelta.x != 0.0f && mDelta.y != 0.0f) {
222                     float yStart = top;
223                     float yEnd = bottom;
224 
225 
226                     mRayStart.set(centerOffsetX, yStart);
227                     mRayStart.add(mCurrentPosition);
228 
229                     mRayEnd.set(centerOffsetX, yEnd);
230                     mRayEnd.add(mCurrentPosition);
231 
232                     mFilterDirection.set(mDelta);
233 
234                     if (collision.castRay(mRayStart, mRayEnd, mFilterDirection, mVerticalHitPoint,
235                             mVerticalHitNormal, parentObject)) {
236 
237                         // If we found a collision, use this surface as our vertical intersection
238                         // for this frame, even if the sweep above also found something.
239                         verticalHit = true;
240                         // snap
241                         if (mVerticalHitNormal.y > 0.0f) {
242                             mCurrentPosition.y = (mVerticalHitPoint.y - bottom);
243                         } else if (mVerticalHitNormal.y < 0.0f) {
244                             mCurrentPosition.y = (mVerticalHitPoint.y - top);
245                         }
246                     }
247 
248 
249                     // Now the horizontal version of the same test
250                     float xStart = left;
251                     float xEnd = right;
252                     if (mDelta.x < 0.0f) {
253                     	xStart = right;
254                     	xEnd = left;
255                     }
256 
257 
258                     mRayStart.set(xStart, centerOffsetY);
259                     mRayStart.add(mCurrentPosition);
260 
261                     mRayEnd.set(xEnd, centerOffsetY);
262                     mRayEnd.add(mCurrentPosition);
263 
264                     mFilterDirection.set(mDelta);
265 
266                     if (collision.castRay(mRayStart, mRayEnd, mFilterDirection, mHorizontalHitPoint,
267                             mHorizontalHitNormal, parentObject)) {
268 
269                         // If we found a collision, use this surface as our horizontal intersection
270                         // for this frame, even if the sweep above also found something.
271                         horizontalHit = true;
272                         // snap
273                         if (mHorizontalHitNormal.x > 0.0f) {
274                             mCurrentPosition.x = (mHorizontalHitPoint.x - left);
275                         } else if (mHorizontalHitNormal.x < 0.0f) {
276                             mCurrentPosition.x = (mHorizontalHitPoint.x - right);
277                         }
278                     }
279                 }
280 
281 
282                 // Record the intersection for other systems to use.
283                 final TimeSystem timeSystem = sSystemRegistry.timeSystem;
284 
285                 if (timeSystem != null) {
286                     float time = timeSystem.getGameTime();
287                     if (horizontalHit) {
288                         if (mHorizontalHitNormal.x > 0.0f) {
289                             parentObject.setLastTouchedLeftWallTime(time);
290                         } else {
291                             parentObject.setLastTouchedRightWallTime(time);
292                         }
293                         //parentObject.setBackgroundCollisionNormal(mHorizontalHitNormal);
294                     }
295 
296                     if (verticalHit) {
297                         if (mVerticalHitNormal.y > 0.0f) {
298                             parentObject.setLastTouchedFloorTime(time);
299                         } else {
300                             parentObject.setLastTouchedCeilingTime(time);
301                         }
302                         //parentObject.setBackgroundCollisionNormal(mVerticalHitNormal);
303                     }
304 
305 
306                     // If we hit multiple surfaces, merge their normals together to produce an
307                     // average direction of obstruction.
308                     if (true) { //(verticalHit && horizontalHit) {
309                         mMergedNormal.set(mVerticalHitNormal);
310                         mMergedNormal.add(mHorizontalHitNormal);
311                         mMergedNormal.normalize();
312                         parentObject.setBackgroundCollisionNormal(mMergedNormal);
313                     }
314 
315                     parentObject.setPosition(mCurrentPosition);
316                 }
317 
318             }
319         }
320         mPreviousPosition.set(parentObject.getPosition());
321     }
322 
323     /* Sweeps the space between two points looking for surfaces that oppose horizontal movement. */
sweepHorizontal(Vector2 previousPosition, Vector2 currentPosition, Vector2 delta, int left, int right, float centerY, Vector2 hitPoint, Vector2 hitNormal, GameObject parentObject)324     protected boolean sweepHorizontal(Vector2 previousPosition, Vector2 currentPosition, Vector2 delta,
325             int left, int right, float centerY, Vector2 hitPoint, Vector2 hitNormal,
326             GameObject parentObject) {
327         boolean hit = false;
328         if (!Utils.close(delta.x, 0.0f)) {
329             CollisionSystem collision = sSystemRegistry.collisionSystem;
330 
331             // Shoot a ray from the center of the previous frame's box to the edge (left or right,
332             // depending on the direction of movement) of the current box.
333             mTestPointStart.y = (centerY);
334             mTestPointStart.x = (left);
335             int offset = -left;
336             if (delta.x > 0.0f) {
337                 mTestPointStart.x = (right);
338                 offset = -right;
339             }
340 
341             // Filter out surfaces that do not oppose motion in the horizontal direction, or
342             // push in the same direction as movement.
343             mFilterDirection.set(delta);
344             mFilterDirection.y = (0);
345 
346             mTestPointEnd.set(currentPosition);
347             mTestPointEnd.add(mTestPointStart);
348             if (collision.castRay(previousPosition, mTestPointEnd, mFilterDirection,
349                     hitPoint, hitNormal, parentObject)) {
350                 // snap
351                 currentPosition.x = (hitPoint.x + offset);
352                 hit = true;
353             }
354         }
355         return hit;
356     }
357 
358     /* Sweeps the space between two points looking for surfaces that oppose vertical movement. */
sweepVertical(Vector2 previousPosition, Vector2 currentPosition, Vector2 delta, int bottom, int top, float centerX, Vector2 hitPoint, Vector2 hitNormal, GameObject parentObject)359     protected boolean sweepVertical(Vector2 previousPosition, Vector2 currentPosition, Vector2 delta,
360             int bottom, int top, float centerX, Vector2 hitPoint, Vector2 hitNormal,
361             GameObject parentObject) {
362         boolean hit = false;
363         if (!Utils.close(delta.y, 0.0f)) {
364             CollisionSystem collision = sSystemRegistry.collisionSystem;
365             // Shoot a ray from the center of the previous frame's box to the edge (top or bottom,
366             // depending on the direction of movement) of the current box.
367             mTestPointStart.x = (centerX);
368             mTestPointStart.y = (bottom);
369             int offset = -bottom;
370             if (delta.y > 0.0f) {
371                 mTestPointStart.y = (top);
372                 offset = -top;
373             }
374 
375             mFilterDirection.set(delta);
376             mFilterDirection.x = (0);
377 
378             mTestPointEnd.set(currentPosition);
379             mTestPointEnd.add(mTestPointStart);
380             if (collision.castRay(previousPosition, mTestPointEnd, mFilterDirection,
381                     hitPoint, hitNormal, parentObject)) {
382                 hit = true;
383                 // snap
384                 currentPosition.y = (hitPoint.y + offset);
385             }
386 
387         }
388         return hit;
389     }
390 
391     /** Comparator for hit points. */
392     private static class HitPointDistanceComparator implements Comparator<HitPoint>  {
393         private Vector2 mOrigin;
394 
HitPointDistanceComparator()395         public HitPointDistanceComparator() {
396             super();
397             mOrigin = new Vector2();
398         }
399 
setOrigin(Vector2 origin)400         public final void setOrigin(Vector2 origin) {
401             mOrigin.set(origin);
402         }
403 
setOrigin(float x, float y)404         public final void setOrigin(float x, float y) {
405             mOrigin.set(x, y);
406         }
407 
compare(HitPoint object1, HitPoint object2)408         public int compare(HitPoint object1, HitPoint object2) {
409             int result = 0;
410             if (object1 != null && object2 != null) {
411                 final float obj1Distance = object1.hitPoint.distance2(mOrigin);
412                 final float obj2Distance = object2.hitPoint.distance2(mOrigin);
413                 final float distanceDelta = obj1Distance - obj2Distance;
414                 result = distanceDelta < 0.0f ? -1 : 1;
415             } else if (object1 == null && object2 != null) {
416                 result = 1;
417             } else if (object2 == null && object1 != null) {
418                 result = -1;
419             }
420             return result;
421         }
422     }
423 }
424