Home | History | Annotate | Download | only in replicaisland
      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      */
     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 
     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
    100     public void reset() {
    101         mPreviousPosition.zero();
    102     }
    103 
    104     public void setSize(int width, int height) {
    105         mWidth = width;
    106         mHeight = height;
    107         // TODO: Resize might cause new collisions.
    108     }
    109 
    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
    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. */
    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. */
    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 
    395         public HitPointDistanceComparator() {
    396             super();
    397             mOrigin = new Vector2();
    398         }
    399 
    400         public final void setOrigin(Vector2 origin) {
    401             mOrigin.set(origin);
    402         }
    403 
    404         public final void setOrigin(float x, float y) {
    405             mOrigin.set(x, y);
    406         }
    407 
    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