Java程序  |  358行  |  16.65 KB

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.replica.replicaisland;

import java.util.Comparator;

import com.replica.replicaisland.CollisionParameters.HitType;

/**
 * A system for calculating collisions between moving game objects.  This system accepts collision
 * volumes from game objects each frame and performs a series of tests to see which of them
 * overlap.  Collisions are only considered between offending "attack" volumes and receiving
 * "vulnerability" volumes.  This implementation works by using a sweep-and-prune algorithm:
 * objects to be considered are sorted in the x axis and then compared in one dimension for
 * overlaps.  A bounding volume that encompasses all attack and vulnerability volumes is used for
 * this test, and when an intersection is found the actual offending and receiving volumes are 
 * compared.  If an intersection is detected both objects receive notification via a
 * HitReactionComponent, if one has been specified.
 */
public class GameObjectCollisionSystem extends BaseObject {
    private static final int MAX_COLLIDING_OBJECTS = 256;
    private static final int COLLISION_RECORD_POOL_SIZE = 256;
    private static final CollisionVolumeComparator sCollisionVolumeComparator 
        = new CollisionVolumeComparator();
    private static CollisionVolume.FlipInfo sFlip = new CollisionVolume.FlipInfo();
    private static CollisionVolume.FlipInfo sOtherFlip = new CollisionVolume.FlipInfo();

    FixedSizeArray<CollisionVolumeRecord> mObjects;
    CollisionVolumeRecordPool mRecordPool;
	private boolean mDrawDebugBoundingVolume = false;
	private boolean mDrawDebugCollisionVolumes = false;
    
    
    public GameObjectCollisionSystem() {
        super();
        mObjects = new FixedSizeArray<CollisionVolumeRecord>(MAX_COLLIDING_OBJECTS);
        mObjects.setComparator(sCollisionVolumeComparator);
        //mObjects.setSorter(new ShellSorter<CollisionVolumeRecord>());
        mRecordPool = new CollisionVolumeRecordPool(COLLISION_RECORD_POOL_SIZE);
    }
    
    @Override
    public void reset() {
        final int count = mObjects.getCount();
        
        for (int x = 0; x < count; x++) {
            mRecordPool.release(mObjects.get(x));
        }
        mObjects.clear();
        
        mDrawDebugBoundingVolume = false;
        mDrawDebugCollisionVolumes = false;
    }
    
    /** 
     * Adds a game object, and its related volumes, to the dynamic collision world for one frame.
     * Once registered for collisions the object may damage other objects via attack volumes or
     * receive damage from other volumes via vulnerability volumes.
     * @param object  The object to consider for collision.
     * @param reactionComponent  A HitReactionComponent to notify when an intersection is calculated.
     * If null, the intersection will still occur and no notification will be sent.
     * @param boundingVolume  A volume that describes the game object in space.  It should encompass
     * all of the attack and vulnerability volumes.
     * @param attackVolumes  A list of volumes that can hit other game objects.  May be null.
     * @param vulnerabilityVolumes  A list of volumes that can receive hits from other game objects.
     * May be null.
     */
    public void registerForCollisions(GameObject object, 
            HitReactionComponent reactionComponent,
            CollisionVolume boundingVolume,
            FixedSizeArray<CollisionVolume> attackVolumes,
            FixedSizeArray<CollisionVolume> vulnerabilityVolumes) {
        CollisionVolumeRecord record = mRecordPool.allocate();
        if (record != null && object != null && boundingVolume != null 
                && (attackVolumes != null || vulnerabilityVolumes != null)) {
            record.object = object;
            record.boundingVolume = boundingVolume;
            record.attackVolumes = attackVolumes;
            record.vulnerabilityVolumes = vulnerabilityVolumes;
            record.reactionComponent = reactionComponent;
            mObjects.add(record);
        }
    }
    
    @Override
    public void update(float timeDelta, BaseObject parent) {
        // Sort the objects by their x position.
        mObjects.sort(true);
        
        final int count = mObjects.getCount();
        for (int x = 0; x < count; x++) {
            final CollisionVolumeRecord record = mObjects.get(x);
            final Vector2 position = record.object.getPosition();
            sFlip.flipX = (record.object.facingDirection.x < 0.0f);
            sFlip.flipY = (record.object.facingDirection.y < 0.0f);
            sFlip.parentWidth = record.object.width;
            sFlip.parentHeight = record.object.height;
            
            if (sSystemRegistry.debugSystem != null) {
            	drawDebugVolumes(record);
            }
            
            final float maxX = record.boundingVolume.getMaxXPosition(sFlip) + position.x;
            for (int y = x + 1; y < count; y++) {
                final CollisionVolumeRecord other = mObjects.get(y);
                final Vector2 otherPosition = other.object.getPosition();
                sOtherFlip.flipX = (other.object.facingDirection.x < 0.0f);
                sOtherFlip.flipY = (other.object.facingDirection.y < 0.0f);
                sOtherFlip.parentWidth = other.object.width;
                sOtherFlip.parentHeight = other.object.height;
                
                if (otherPosition.x + other.boundingVolume.getMinXPosition(sOtherFlip) > maxX) {
                    // These objects can't possibly be colliding.  And since the list is sorted,
                    // there are no potentially colliding objects after this object
                    // either, so we're done!
                    break;
                } else {
                	final boolean testRequired = (record.attackVolumes != null && other.vulnerabilityVolumes != null) ||
                		(record.vulnerabilityVolumes != null && other.attackVolumes != null);
                    if (testRequired && record.boundingVolume.intersects(position, sFlip,
                        other.boundingVolume, otherPosition, sOtherFlip)) {
                        // These two objects are potentially colliding.
                        // Now we must test all attack vs vulnerability boxes.
                        final int hit = testAttackAgainstVulnerability(
                                record.attackVolumes,
                                other.vulnerabilityVolumes, 
                                position,
                                otherPosition,
                                sFlip,
                                sOtherFlip);
                        if (hit != HitType.INVALID) {
                            boolean hitAccepted = false;
                            if (other.reactionComponent != null) {
                                hitAccepted = other.reactionComponent.receivedHit(
                                        other.object, record.object, hit);
                            }
                            if (record.reactionComponent != null) {
                                record.reactionComponent.hitVictim(
                                        record.object, other.object, hit, hitAccepted);
                            }
                            
                        }
                        
                        final int hit2 = testAttackAgainstVulnerability(
                                other.attackVolumes,
                                record.vulnerabilityVolumes, 
                                otherPosition, 
                                position,
                                sOtherFlip,
                                sFlip);
                        if (hit2 != HitType.INVALID) {
                            boolean hitAccepted = false;
                            if (record.reactionComponent != null) {
                                hitAccepted = record.reactionComponent.receivedHit(
                                        record.object, other.object, hit2);
                            }
                            if (other.reactionComponent != null) {
                                other.reactionComponent.hitVictim(
                                        other.object, record.object, hit2, hitAccepted);
                            }
                            
                        }
                    }
                }
            }
            // This is a little tricky.  Since we always sweep forward in the list it's safe
            // to invalidate the current record after we've tested it.  This way we don't have to
            // iterate over the object list twice.
            mRecordPool.release(record);
        }
        
        mObjects.clear();
    }
    
    /** Compares the passed list of attack volumes against the passed list of vulnerability volumes
     * and returns a hit type if an intersection is found.
     * @param attackVolumes  Offensive collision volumes.
     * @param vulnerabilityVolumes  Receiving collision volumes.
     * @param attackPosition  The world position of the attacking object.
     * @param vulnerabilityPosition  The world position of the receiving object.
     * @return  The hit type of the first attacking volume that intersects a vulnerability volume, 
     * or HitType.INVALID if no intersections are found.
     */
    private int testAttackAgainstVulnerability(
            FixedSizeArray<CollisionVolume> attackVolumes,
            FixedSizeArray<CollisionVolume> vulnerabilityVolumes,
            Vector2 attackPosition,
            Vector2 vulnerabilityPosition,
            CollisionVolume.FlipInfo attackFlip,
            CollisionVolume.FlipInfo vulnerabilityFlip) {
        int intersectionType = HitType.INVALID;
        if (attackVolumes != null && vulnerabilityVolumes != null) {
            final int attackCount = attackVolumes.getCount();
            for (int x = 0; x < attackCount && intersectionType == HitType.INVALID; x++) {
                final CollisionVolume attackVolume = attackVolumes.get(x);
                final int hitType = attackVolume.getHitType();
                if (hitType != HitType.INVALID) {
                    final int vulnerabilityCount = vulnerabilityVolumes.getCount();
                    for (int y = 0; y < vulnerabilityCount; y++) {
                        final CollisionVolume vulnerabilityVolume = vulnerabilityVolumes.get(y);
                        final int vulnerableType = vulnerabilityVolume.getHitType();
                        if (vulnerableType == HitType.INVALID || vulnerableType == hitType) {
                            if (attackVolume.intersects(attackPosition, attackFlip,
                                    vulnerabilityVolume, vulnerabilityPosition, 
                                    vulnerabilityFlip)) {
                                intersectionType = hitType;
                                break;
                            }
                        }
                    }
                }
            }
        }
        
        return intersectionType;
    }
    
    private final void drawDebugVolumes(CollisionVolumeRecord record) {
    	final Vector2 position = record.object.getPosition();
    	if (mDrawDebugBoundingVolume) {
	    	final CollisionVolume boundingVolume = record.boundingVolume;
	    	sSystemRegistry.debugSystem.drawShape(
	    			position.x + boundingVolume.getMinXPosition(sFlip), position.y + boundingVolume.getMinYPosition(sFlip), 
	    			boundingVolume.getMaxX() - boundingVolume.getMinX(), 
	    			boundingVolume.getMaxY() - boundingVolume.getMinY(), 
	    			DebugSystem.SHAPE_CIRCLE,
	    			DebugSystem.COLOR_OUTLINE);
    	}
    	if (mDrawDebugCollisionVolumes) {
	    	if (record.attackVolumes != null) {
	    		final int attackVolumeCount = record.attackVolumes.getCount();
	    		for (int y = 0; y < attackVolumeCount; y++) {
	    			CollisionVolume volume = record.attackVolumes.get(y);
	    			sSystemRegistry.debugSystem.drawShape(
	    					position.x + volume.getMinXPosition(sFlip), position.y + volume.getMinYPosition(sFlip), 
	    					volume.getMaxX() - volume.getMinX(), 
	    					volume.getMaxY() - volume.getMinY(), 
	    	    			volume.getClass() == AABoxCollisionVolume.class ? DebugSystem.SHAPE_BOX : DebugSystem.SHAPE_CIRCLE,
	    	    			DebugSystem.COLOR_RED);
	    		}
	    	}
	    	
	    	if (record.vulnerabilityVolumes != null) {
	    		final int vulnVolumeCount = record.vulnerabilityVolumes.getCount();
	    		for (int y = 0; y < vulnVolumeCount; y++) {
	    			CollisionVolume volume = record.vulnerabilityVolumes.get(y);
	    			sSystemRegistry.debugSystem.drawShape(
	    					position.x + volume.getMinXPosition(sFlip), position.y + volume.getMinYPosition(sFlip), 
	    					volume.getMaxX() - volume.getMinX(), 
	    					volume.getMaxY() - volume.getMinY(), 
	    	    			volume.getClass() == AABoxCollisionVolume.class ? DebugSystem.SHAPE_BOX : DebugSystem.SHAPE_CIRCLE,
	    	    			DebugSystem.COLOR_BLUE);
	    		}
	    	}
    	}
    }
    
    public void setDebugPrefs(boolean drawBoundingVolumes, boolean drawCollisionVolumes) {
		mDrawDebugBoundingVolume = drawBoundingVolumes;
		mDrawDebugCollisionVolumes = drawCollisionVolumes;
	}
    
    /** A record of a single game object and its associated collision info.  */
    private class CollisionVolumeRecord extends AllocationGuard {
        public GameObject object;
        public HitReactionComponent reactionComponent;
        public CollisionVolume boundingVolume;
        public FixedSizeArray<CollisionVolume> attackVolumes;
        public FixedSizeArray<CollisionVolume> vulnerabilityVolumes;
        
        public void reset() {
            object = null;
            attackVolumes = null;
            vulnerabilityVolumes = null;
            boundingVolume = null;
            reactionComponent = null;
        }
    }
    
    /** A pool of collision volume records.  */
    private class CollisionVolumeRecordPool extends TObjectPool<CollisionVolumeRecord> {

        public CollisionVolumeRecordPool(int count) {
            super(count);
        }
        
        @Override
        protected void fill() {
            for (int x = 0; x < getSize(); x++) {
                getAvailable().add(new CollisionVolumeRecord());
            }
        }

        @Override
        public void release(Object entry) {
            ((CollisionVolumeRecord)entry).reset();
            super.release(entry);
        }

    }
    
    /** 
     * Comparator for game objects that considers the world position of the object's bounding
     * volume and sorts objects from left to right on the x axis. */
    public final static class CollisionVolumeComparator implements Comparator<CollisionVolumeRecord> {
        private static CollisionVolume.FlipInfo sCompareFlip = new CollisionVolume.FlipInfo();
        public int compare(CollisionVolumeRecord object1, CollisionVolumeRecord object2) {
            int result = 0;
            if (object1 == null && object2 != null) {
                result = 1;
            } else if (object1 != null && object2 == null) {
                result = -1;
            } else if (object1 != null && object2 != null) {
                sCompareFlip.flipX = (object1.object.facingDirection.x < 0.0f);
                sCompareFlip.flipY = (object1.object.facingDirection.y < 0.0f);
                sCompareFlip.parentWidth = object1.object.width;
                sCompareFlip.parentHeight = object1.object.height;
                
                final float minX1 = object1.object.getPosition().x 
                    + object1.boundingVolume.getMinXPosition(sCompareFlip);
                
                sCompareFlip.flipX = (object2.object.facingDirection.x < 0.0f);
                sCompareFlip.flipY = (object2.object.facingDirection.y < 0.0f);
                sCompareFlip.parentWidth = object2.object.width;
                sCompareFlip.parentHeight = object2.object.height;
                
                final float minX2 = object2.object.getPosition().x 
                    + object2.boundingVolume.getMinXPosition(sCompareFlip);
                
                final float delta = minX1 - minX2;
                if (delta < 0.0f) {
                    result = -1;
                } else if (delta > 0.0f) {
                    result = 1;
                }
            }
            return result;
        }
    }

	
   
}