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 package com.replica.replicaisland;
     18 
     19 import java.util.Comparator;
     20 
     21 import com.replica.replicaisland.CollisionParameters.HitType;
     22 
     23 /**
     24  * A system for calculating collisions between moving game objects.  This system accepts collision
     25  * volumes from game objects each frame and performs a series of tests to see which of them
     26  * overlap.  Collisions are only considered between offending "attack" volumes and receiving
     27  * "vulnerability" volumes.  This implementation works by using a sweep-and-prune algorithm:
     28  * objects to be considered are sorted in the x axis and then compared in one dimension for
     29  * overlaps.  A bounding volume that encompasses all attack and vulnerability volumes is used for
     30  * this test, and when an intersection is found the actual offending and receiving volumes are
     31  * compared.  If an intersection is detected both objects receive notification via a
     32  * HitReactionComponent, if one has been specified.
     33  */
     34 public class GameObjectCollisionSystem extends BaseObject {
     35     private static final int MAX_COLLIDING_OBJECTS = 256;
     36     private static final int COLLISION_RECORD_POOL_SIZE = 256;
     37     private static final CollisionVolumeComparator sCollisionVolumeComparator
     38         = new CollisionVolumeComparator();
     39     private static CollisionVolume.FlipInfo sFlip = new CollisionVolume.FlipInfo();
     40     private static CollisionVolume.FlipInfo sOtherFlip = new CollisionVolume.FlipInfo();
     41 
     42     FixedSizeArray<CollisionVolumeRecord> mObjects;
     43     CollisionVolumeRecordPool mRecordPool;
     44 	private boolean mDrawDebugBoundingVolume = false;
     45 	private boolean mDrawDebugCollisionVolumes = false;
     46 
     47 
     48     public GameObjectCollisionSystem() {
     49         super();
     50         mObjects = new FixedSizeArray<CollisionVolumeRecord>(MAX_COLLIDING_OBJECTS);
     51         mObjects.setComparator(sCollisionVolumeComparator);
     52         //mObjects.setSorter(new ShellSorter<CollisionVolumeRecord>());
     53         mRecordPool = new CollisionVolumeRecordPool(COLLISION_RECORD_POOL_SIZE);
     54     }
     55 
     56     @Override
     57     public void reset() {
     58         final int count = mObjects.getCount();
     59 
     60         for (int x = 0; x < count; x++) {
     61             mRecordPool.release(mObjects.get(x));
     62         }
     63         mObjects.clear();
     64 
     65         mDrawDebugBoundingVolume = false;
     66         mDrawDebugCollisionVolumes = false;
     67     }
     68 
     69     /**
     70      * Adds a game object, and its related volumes, to the dynamic collision world for one frame.
     71      * Once registered for collisions the object may damage other objects via attack volumes or
     72      * receive damage from other volumes via vulnerability volumes.
     73      * @param object  The object to consider for collision.
     74      * @param reactionComponent  A HitReactionComponent to notify when an intersection is calculated.
     75      * If null, the intersection will still occur and no notification will be sent.
     76      * @param boundingVolume  A volume that describes the game object in space.  It should encompass
     77      * all of the attack and vulnerability volumes.
     78      * @param attackVolumes  A list of volumes that can hit other game objects.  May be null.
     79      * @param vulnerabilityVolumes  A list of volumes that can receive hits from other game objects.
     80      * May be null.
     81      */
     82     public void registerForCollisions(GameObject object,
     83             HitReactionComponent reactionComponent,
     84             CollisionVolume boundingVolume,
     85             FixedSizeArray<CollisionVolume> attackVolumes,
     86             FixedSizeArray<CollisionVolume> vulnerabilityVolumes) {
     87         CollisionVolumeRecord record = mRecordPool.allocate();
     88         if (record != null && object != null && boundingVolume != null
     89                 && (attackVolumes != null || vulnerabilityVolumes != null)) {
     90             record.object = object;
     91             record.boundingVolume = boundingVolume;
     92             record.attackVolumes = attackVolumes;
     93             record.vulnerabilityVolumes = vulnerabilityVolumes;
     94             record.reactionComponent = reactionComponent;
     95             mObjects.add(record);
     96         }
     97     }
     98 
     99     @Override
    100     public void update(float timeDelta, BaseObject parent) {
    101         // Sort the objects by their x position.
    102         mObjects.sort(true);
    103 
    104         final int count = mObjects.getCount();
    105         for (int x = 0; x < count; x++) {
    106             final CollisionVolumeRecord record = mObjects.get(x);
    107             final Vector2 position = record.object.getPosition();
    108             sFlip.flipX = (record.object.facingDirection.x < 0.0f);
    109             sFlip.flipY = (record.object.facingDirection.y < 0.0f);
    110             sFlip.parentWidth = record.object.width;
    111             sFlip.parentHeight = record.object.height;
    112 
    113             if (sSystemRegistry.debugSystem != null) {
    114             	drawDebugVolumes(record);
    115             }
    116 
    117             final float maxX = record.boundingVolume.getMaxXPosition(sFlip) + position.x;
    118             for (int y = x + 1; y < count; y++) {
    119                 final CollisionVolumeRecord other = mObjects.get(y);
    120                 final Vector2 otherPosition = other.object.getPosition();
    121                 sOtherFlip.flipX = (other.object.facingDirection.x < 0.0f);
    122                 sOtherFlip.flipY = (other.object.facingDirection.y < 0.0f);
    123                 sOtherFlip.parentWidth = other.object.width;
    124                 sOtherFlip.parentHeight = other.object.height;
    125 
    126                 if (otherPosition.x + other.boundingVolume.getMinXPosition(sOtherFlip) > maxX) {
    127                     // These objects can't possibly be colliding.  And since the list is sorted,
    128                     // there are no potentially colliding objects after this object
    129                     // either, so we're done!
    130                     break;
    131                 } else {
    132                 	final boolean testRequired = (record.attackVolumes != null && other.vulnerabilityVolumes != null) ||
    133                 		(record.vulnerabilityVolumes != null && other.attackVolumes != null);
    134                     if (testRequired && record.boundingVolume.intersects(position, sFlip,
    135                         other.boundingVolume, otherPosition, sOtherFlip)) {
    136                         // These two objects are potentially colliding.
    137                         // Now we must test all attack vs vulnerability boxes.
    138                         final int hit = testAttackAgainstVulnerability(
    139                                 record.attackVolumes,
    140                                 other.vulnerabilityVolumes,
    141                                 position,
    142                                 otherPosition,
    143                                 sFlip,
    144                                 sOtherFlip);
    145                         if (hit != HitType.INVALID) {
    146                             boolean hitAccepted = false;
    147                             if (other.reactionComponent != null) {
    148                                 hitAccepted = other.reactionComponent.receivedHit(
    149                                         other.object, record.object, hit);
    150                             }
    151                             if (record.reactionComponent != null) {
    152                                 record.reactionComponent.hitVictim(
    153                                         record.object, other.object, hit, hitAccepted);
    154                             }
    155 
    156                         }
    157 
    158                         final int hit2 = testAttackAgainstVulnerability(
    159                                 other.attackVolumes,
    160                                 record.vulnerabilityVolumes,
    161                                 otherPosition,
    162                                 position,
    163                                 sOtherFlip,
    164                                 sFlip);
    165                         if (hit2 != HitType.INVALID) {
    166                             boolean hitAccepted = false;
    167                             if (record.reactionComponent != null) {
    168                                 hitAccepted = record.reactionComponent.receivedHit(
    169                                         record.object, other.object, hit2);
    170                             }
    171                             if (other.reactionComponent != null) {
    172                                 other.reactionComponent.hitVictim(
    173                                         other.object, record.object, hit2, hitAccepted);
    174                             }
    175 
    176                         }
    177                     }
    178                 }
    179             }
    180             // This is a little tricky.  Since we always sweep forward in the list it's safe
    181             // to invalidate the current record after we've tested it.  This way we don't have to
    182             // iterate over the object list twice.
    183             mRecordPool.release(record);
    184         }
    185 
    186         mObjects.clear();
    187     }
    188 
    189     /** Compares the passed list of attack volumes against the passed list of vulnerability volumes
    190      * and returns a hit type if an intersection is found.
    191      * @param attackVolumes  Offensive collision volumes.
    192      * @param vulnerabilityVolumes  Receiving collision volumes.
    193      * @param attackPosition  The world position of the attacking object.
    194      * @param vulnerabilityPosition  The world position of the receiving object.
    195      * @return  The hit type of the first attacking volume that intersects a vulnerability volume,
    196      * or HitType.INVALID if no intersections are found.
    197      */
    198     private int testAttackAgainstVulnerability(
    199             FixedSizeArray<CollisionVolume> attackVolumes,
    200             FixedSizeArray<CollisionVolume> vulnerabilityVolumes,
    201             Vector2 attackPosition,
    202             Vector2 vulnerabilityPosition,
    203             CollisionVolume.FlipInfo attackFlip,
    204             CollisionVolume.FlipInfo vulnerabilityFlip) {
    205         int intersectionType = HitType.INVALID;
    206         if (attackVolumes != null && vulnerabilityVolumes != null) {
    207             final int attackCount = attackVolumes.getCount();
    208             for (int x = 0; x < attackCount && intersectionType == HitType.INVALID; x++) {
    209                 final CollisionVolume attackVolume = attackVolumes.get(x);
    210                 final int hitType = attackVolume.getHitType();
    211                 if (hitType != HitType.INVALID) {
    212                     final int vulnerabilityCount = vulnerabilityVolumes.getCount();
    213                     for (int y = 0; y < vulnerabilityCount; y++) {
    214                         final CollisionVolume vulnerabilityVolume = vulnerabilityVolumes.get(y);
    215                         final int vulnerableType = vulnerabilityVolume.getHitType();
    216                         if (vulnerableType == HitType.INVALID || vulnerableType == hitType) {
    217                             if (attackVolume.intersects(attackPosition, attackFlip,
    218                                     vulnerabilityVolume, vulnerabilityPosition,
    219                                     vulnerabilityFlip)) {
    220                                 intersectionType = hitType;
    221                                 break;
    222                             }
    223                         }
    224                     }
    225                 }
    226             }
    227         }
    228 
    229         return intersectionType;
    230     }
    231 
    232     private final void drawDebugVolumes(CollisionVolumeRecord record) {
    233     	final Vector2 position = record.object.getPosition();
    234     	if (mDrawDebugBoundingVolume) {
    235 	    	final CollisionVolume boundingVolume = record.boundingVolume;
    236 	    	sSystemRegistry.debugSystem.drawShape(
    237 	    			position.x + boundingVolume.getMinXPosition(sFlip), position.y + boundingVolume.getMinYPosition(sFlip),
    238 	    			boundingVolume.getMaxX() - boundingVolume.getMinX(),
    239 	    			boundingVolume.getMaxY() - boundingVolume.getMinY(),
    240 	    			DebugSystem.SHAPE_CIRCLE,
    241 	    			DebugSystem.COLOR_OUTLINE);
    242     	}
    243     	if (mDrawDebugCollisionVolumes) {
    244 	    	if (record.attackVolumes != null) {
    245 	    		final int attackVolumeCount = record.attackVolumes.getCount();
    246 	    		for (int y = 0; y < attackVolumeCount; y++) {
    247 	    			CollisionVolume volume = record.attackVolumes.get(y);
    248 	    			sSystemRegistry.debugSystem.drawShape(
    249 	    					position.x + volume.getMinXPosition(sFlip), position.y + volume.getMinYPosition(sFlip),
    250 	    					volume.getMaxX() - volume.getMinX(),
    251 	    					volume.getMaxY() - volume.getMinY(),
    252 	    	    			volume.getClass() == AABoxCollisionVolume.class ? DebugSystem.SHAPE_BOX : DebugSystem.SHAPE_CIRCLE,
    253 	    	    			DebugSystem.COLOR_RED);
    254 	    		}
    255 	    	}
    256 
    257 	    	if (record.vulnerabilityVolumes != null) {
    258 	    		final int vulnVolumeCount = record.vulnerabilityVolumes.getCount();
    259 	    		for (int y = 0; y < vulnVolumeCount; y++) {
    260 	    			CollisionVolume volume = record.vulnerabilityVolumes.get(y);
    261 	    			sSystemRegistry.debugSystem.drawShape(
    262 	    					position.x + volume.getMinXPosition(sFlip), position.y + volume.getMinYPosition(sFlip),
    263 	    					volume.getMaxX() - volume.getMinX(),
    264 	    					volume.getMaxY() - volume.getMinY(),
    265 	    	    			volume.getClass() == AABoxCollisionVolume.class ? DebugSystem.SHAPE_BOX : DebugSystem.SHAPE_CIRCLE,
    266 	    	    			DebugSystem.COLOR_BLUE);
    267 	    		}
    268 	    	}
    269     	}
    270     }
    271 
    272     public void setDebugPrefs(boolean drawBoundingVolumes, boolean drawCollisionVolumes) {
    273 		mDrawDebugBoundingVolume = drawBoundingVolumes;
    274 		mDrawDebugCollisionVolumes = drawCollisionVolumes;
    275 	}
    276 
    277     /** A record of a single game object and its associated collision info.  */
    278     private class CollisionVolumeRecord extends AllocationGuard {
    279         public GameObject object;
    280         public HitReactionComponent reactionComponent;
    281         public CollisionVolume boundingVolume;
    282         public FixedSizeArray<CollisionVolume> attackVolumes;
    283         public FixedSizeArray<CollisionVolume> vulnerabilityVolumes;
    284 
    285         public void reset() {
    286             object = null;
    287             attackVolumes = null;
    288             vulnerabilityVolumes = null;
    289             boundingVolume = null;
    290             reactionComponent = null;
    291         }
    292     }
    293 
    294     /** A pool of collision volume records.  */
    295     private class CollisionVolumeRecordPool extends TObjectPool<CollisionVolumeRecord> {
    296 
    297         public CollisionVolumeRecordPool(int count) {
    298             super(count);
    299         }
    300 
    301         @Override
    302         protected void fill() {
    303             for (int x = 0; x < getSize(); x++) {
    304                 getAvailable().add(new CollisionVolumeRecord());
    305             }
    306         }
    307 
    308         @Override
    309         public void release(Object entry) {
    310             ((CollisionVolumeRecord)entry).reset();
    311             super.release(entry);
    312         }
    313 
    314     }
    315 
    316     /**
    317      * Comparator for game objects that considers the world position of the object's bounding
    318      * volume and sorts objects from left to right on the x axis. */
    319     public final static class CollisionVolumeComparator implements Comparator<CollisionVolumeRecord> {
    320         private static CollisionVolume.FlipInfo sCompareFlip = new CollisionVolume.FlipInfo();
    321         public int compare(CollisionVolumeRecord object1, CollisionVolumeRecord object2) {
    322             int result = 0;
    323             if (object1 == null && object2 != null) {
    324                 result = 1;
    325             } else if (object1 != null && object2 == null) {
    326                 result = -1;
    327             } else if (object1 != null && object2 != null) {
    328                 sCompareFlip.flipX = (object1.object.facingDirection.x < 0.0f);
    329                 sCompareFlip.flipY = (object1.object.facingDirection.y < 0.0f);
    330                 sCompareFlip.parentWidth = object1.object.width;
    331                 sCompareFlip.parentHeight = object1.object.height;
    332 
    333                 final float minX1 = object1.object.getPosition().x
    334                     + object1.boundingVolume.getMinXPosition(sCompareFlip);
    335 
    336                 sCompareFlip.flipX = (object2.object.facingDirection.x < 0.0f);
    337                 sCompareFlip.flipY = (object2.object.facingDirection.y < 0.0f);
    338                 sCompareFlip.parentWidth = object2.object.width;
    339                 sCompareFlip.parentHeight = object2.object.height;
    340 
    341                 final float minX2 = object2.object.getPosition().x
    342                     + object2.boundingVolume.getMinXPosition(sCompareFlip);
    343 
    344                 final float delta = minX1 - minX2;
    345                 if (delta < 0.0f) {
    346                     result = -1;
    347                 } else if (delta > 0.0f) {
    348                     result = 1;
    349                 }
    350             }
    351             return result;
    352         }
    353     }
    354 
    355 
    356 
    357 }
    358