| /* |
| * 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; |
| |
| /** |
| * Handles collision against the background. Snaps colliding objects out of collision and reports |
| * the hit to the parent game object. |
| */ |
| public class BackgroundCollisionComponent extends GameComponent { |
| private Vector2 mPreviousPosition; |
| private int mWidth; |
| private int mHeight; |
| private int mHorizontalOffset; |
| private int mVerticalOffset; |
| |
| // Workspace vectors. Allocated up front for speed. |
| private Vector2 mCurrentPosition; |
| private Vector2 mPreviousCenter; |
| private Vector2 mDelta; |
| private Vector2 mFilterDirection; |
| private Vector2 mHorizontalHitPoint; |
| private Vector2 mHorizontalHitNormal; |
| private Vector2 mVerticalHitPoint; |
| private Vector2 mVerticalHitNormal; |
| private Vector2 mRayStart; |
| private Vector2 mRayEnd; |
| private Vector2 mTestPointStart; |
| private Vector2 mTestPointEnd; |
| private Vector2 mMergedNormal; |
| |
| /** |
| * Sets up the collision bounding box. This box may be a different size than the bounds of the |
| * sprite that this object controls. |
| * @param width The width of the collision box. |
| * @param height The height of the collision box. |
| * @param horzOffset The offset of the collision box from the object's origin in the x axis. |
| * @param vertOffset The offset of the collision box from the object's origin in the y axis. |
| */ |
| public BackgroundCollisionComponent(int width, int height, int horzOffset, int vertOffset) { |
| super(); |
| setPhase(ComponentPhases.COLLISION_RESPONSE.ordinal()); |
| mPreviousPosition = new Vector2(); |
| mWidth = width; |
| mHeight = height; |
| mHorizontalOffset = horzOffset; |
| mVerticalOffset = vertOffset; |
| |
| mCurrentPosition = new Vector2(); |
| mPreviousCenter = new Vector2(); |
| mDelta = new Vector2(); |
| mFilterDirection = new Vector2(); |
| mHorizontalHitPoint = new Vector2(); |
| mHorizontalHitNormal = new Vector2(); |
| mVerticalHitPoint = new Vector2(); |
| mVerticalHitNormal = new Vector2(); |
| mRayStart = new Vector2(); |
| mRayEnd = new Vector2(); |
| mTestPointStart = new Vector2(); |
| mTestPointEnd = new Vector2(); |
| mMergedNormal = new Vector2(); |
| } |
| |
| public BackgroundCollisionComponent() { |
| super(); |
| setPhase(ComponentPhases.COLLISION_RESPONSE.ordinal()); |
| mPreviousPosition = new Vector2(); |
| mCurrentPosition = new Vector2(); |
| mPreviousCenter = new Vector2(); |
| mDelta = new Vector2(); |
| mFilterDirection = new Vector2(); |
| mHorizontalHitPoint = new Vector2(); |
| mHorizontalHitNormal = new Vector2(); |
| mVerticalHitPoint = new Vector2(); |
| mVerticalHitNormal = new Vector2(); |
| mRayStart = new Vector2(); |
| mRayEnd = new Vector2(); |
| mTestPointStart = new Vector2(); |
| mTestPointEnd = new Vector2(); |
| mMergedNormal = new Vector2(); |
| } |
| |
| @Override |
| public void reset() { |
| mPreviousPosition.zero(); |
| } |
| |
| public void setSize(int width, int height) { |
| mWidth = width; |
| mHeight = height; |
| // TODO: Resize might cause new collisions. |
| } |
| |
| public void setOffset(int horzOffset, int vertOffset) { |
| mHorizontalOffset = horzOffset; |
| mVerticalOffset = vertOffset; |
| } |
| |
| /** |
| * This function is the meat of the collision response logic. Our collision detection and |
| * response must be capable of dealing with arbitrary surfaces and must be frame rate |
| * independent (we must sweep the space in-between frames to find collisions reliably). The |
| * following algorithm is used to keep the collision box out of the collision world. |
| * 1. Cast a ray from the center point of the box at its position last frame to the edge |
| * of the box at its current position. If the ray intersects anything, snap the box |
| * back to the point of intersection. |
| * 2. Perform Step 1 twice: once looking for surfaces opposing horizontal movement and |
| * again for surfaces opposing vertical movement. These two ray tests approximate the |
| * movement of the box between the previous frame and this one. |
| * 3. Since most collisions are collisions with the ground, more precision is required for |
| * vertical intersections. Perform another ray test, this time from the top of the |
| * box's position (after snapping in Step 2) to the bottom. Snap out of any vertical |
| * surfaces that the ray encounters. This will ensure consistent snapping behavior on |
| * incline surfaces. |
| * 4. Add the normals of the surfaces that were hit up and normalize the result to produce |
| * a direction describing the average slope of the surfaces that the box is resting on. |
| * Physics will use this value as a normal to resolve collisions with the background. |
| */ |
| @Override |
| public void update(float timeDelta, BaseObject parent) { |
| GameObject parentObject = (GameObject) parent; |
| parentObject.setBackgroundCollisionNormal(Vector2.ZERO); |
| if (mPreviousPosition.length2() != 0) { |
| CollisionSystem collision = sSystemRegistry.collisionSystem; |
| if (collision != null) { |
| final int left = mHorizontalOffset; |
| final int bottom = mVerticalOffset; |
| final int right = left + mWidth; |
| final int top = bottom + mHeight; |
| final float centerOffsetX = ((mWidth) / 2.0f) + left; |
| final float centerOffsetY = ((mHeight) / 2.0f) + bottom; |
| |
| mCurrentPosition.set(parentObject.getPosition()); |
| mDelta.set(mCurrentPosition); |
| mDelta.subtract(mPreviousPosition); |
| |
| mPreviousCenter.set(centerOffsetX, centerOffsetY); |
| mPreviousCenter.add(mPreviousPosition); |
| |
| boolean horizontalHit = false; |
| boolean verticalHit = false; |
| |
| mVerticalHitPoint.zero(); |
| mVerticalHitNormal.zero(); |
| mHorizontalHitPoint.zero(); |
| mHorizontalHitNormal.zero(); |
| |
| |
| // The order in which we sweep the horizontal and vertical space can affect the |
| // final result because we perform incremental snapping mid-sweep. So it is |
| // necessary to sweep in the primary direction of movement first. |
| if (Math.abs(mDelta.x) > Math.abs(mDelta.y)) { |
| horizontalHit = sweepHorizontal(mPreviousCenter, mCurrentPosition, mDelta, left, |
| right, centerOffsetY, mHorizontalHitPoint, mHorizontalHitNormal, |
| parentObject); |
| verticalHit = sweepVertical(mPreviousCenter, mCurrentPosition, mDelta, bottom, |
| top, centerOffsetX, mVerticalHitPoint, mVerticalHitNormal, |
| parentObject); |
| |
| } else { |
| verticalHit = sweepVertical(mPreviousCenter, mCurrentPosition, mDelta, bottom, |
| top, centerOffsetX, mVerticalHitPoint, mVerticalHitNormal, |
| parentObject); |
| horizontalHit = sweepHorizontal(mPreviousCenter, mCurrentPosition, mDelta, left, |
| right, centerOffsetY, mHorizontalHitPoint, mHorizontalHitNormal, |
| parentObject); |
| } |
| |
| // force the collision volume to stay within the bounds of the world. |
| LevelSystem level = sSystemRegistry.levelSystem; |
| if (level != null) { |
| if (mCurrentPosition.x + left < 0.0f) { |
| mCurrentPosition.x = (-left + 1); |
| horizontalHit = true; |
| mHorizontalHitNormal.x = (mHorizontalHitNormal.x + 1.0f); |
| mHorizontalHitNormal.normalize(); |
| } else if (mCurrentPosition.x + right > level.getLevelWidth()) { |
| mCurrentPosition.x = (level.getLevelWidth() - right - 1); |
| mHorizontalHitNormal.x = (mHorizontalHitNormal.x - 1.0f); |
| mHorizontalHitNormal.normalize(); |
| horizontalHit = true; |
| } |
| |
| /*if (mCurrentPosition.y + bottom < 0.0f) { |
| mCurrentPosition.y = (-bottom + 1); |
| verticalHit = true; |
| mVerticalHitNormal.y = (mVerticalHitNormal.y + 1.0f); |
| mVerticalHitNormal.normalize(); |
| } else*/ if (mCurrentPosition.y + top > level.getLevelHeight()) { |
| mCurrentPosition.y = (level.getLevelHeight() - top - 1); |
| mVerticalHitNormal.y = (mVerticalHitNormal.y - 1.0f); |
| mVerticalHitNormal.normalize(); |
| verticalHit = true; |
| } |
| |
| } |
| |
| |
| // One more set of tests to make sure that we are aligned with the surface. |
| // This time we will just check the inside of the bounding box for intersections. |
| // The sweep tests above will keep us out of collision in most cases, but this |
| // test will ensure that we are aligned to incline surfaces correctly. |
| |
| // Shoot a vertical line through the middle of the box. |
| if (mDelta.x != 0.0f && mDelta.y != 0.0f) { |
| float yStart = top; |
| float yEnd = bottom; |
| |
| |
| mRayStart.set(centerOffsetX, yStart); |
| mRayStart.add(mCurrentPosition); |
| |
| mRayEnd.set(centerOffsetX, yEnd); |
| mRayEnd.add(mCurrentPosition); |
| |
| mFilterDirection.set(mDelta); |
| |
| if (collision.castRay(mRayStart, mRayEnd, mFilterDirection, mVerticalHitPoint, |
| mVerticalHitNormal, parentObject)) { |
| |
| // If we found a collision, use this surface as our vertical intersection |
| // for this frame, even if the sweep above also found something. |
| verticalHit = true; |
| // snap |
| if (mVerticalHitNormal.y > 0.0f) { |
| mCurrentPosition.y = (mVerticalHitPoint.y - bottom); |
| } else if (mVerticalHitNormal.y < 0.0f) { |
| mCurrentPosition.y = (mVerticalHitPoint.y - top); |
| } |
| } |
| |
| |
| // Now the horizontal version of the same test |
| float xStart = left; |
| float xEnd = right; |
| if (mDelta.x < 0.0f) { |
| xStart = right; |
| xEnd = left; |
| } |
| |
| |
| mRayStart.set(xStart, centerOffsetY); |
| mRayStart.add(mCurrentPosition); |
| |
| mRayEnd.set(xEnd, centerOffsetY); |
| mRayEnd.add(mCurrentPosition); |
| |
| mFilterDirection.set(mDelta); |
| |
| if (collision.castRay(mRayStart, mRayEnd, mFilterDirection, mHorizontalHitPoint, |
| mHorizontalHitNormal, parentObject)) { |
| |
| // If we found a collision, use this surface as our horizontal intersection |
| // for this frame, even if the sweep above also found something. |
| horizontalHit = true; |
| // snap |
| if (mHorizontalHitNormal.x > 0.0f) { |
| mCurrentPosition.x = (mHorizontalHitPoint.x - left); |
| } else if (mHorizontalHitNormal.x < 0.0f) { |
| mCurrentPosition.x = (mHorizontalHitPoint.x - right); |
| } |
| } |
| } |
| |
| |
| // Record the intersection for other systems to use. |
| final TimeSystem timeSystem = sSystemRegistry.timeSystem; |
| |
| if (timeSystem != null) { |
| float time = timeSystem.getGameTime(); |
| if (horizontalHit) { |
| if (mHorizontalHitNormal.x > 0.0f) { |
| parentObject.setLastTouchedLeftWallTime(time); |
| } else { |
| parentObject.setLastTouchedRightWallTime(time); |
| } |
| //parentObject.setBackgroundCollisionNormal(mHorizontalHitNormal); |
| } |
| |
| if (verticalHit) { |
| if (mVerticalHitNormal.y > 0.0f) { |
| parentObject.setLastTouchedFloorTime(time); |
| } else { |
| parentObject.setLastTouchedCeilingTime(time); |
| } |
| //parentObject.setBackgroundCollisionNormal(mVerticalHitNormal); |
| } |
| |
| |
| // If we hit multiple surfaces, merge their normals together to produce an |
| // average direction of obstruction. |
| if (true) { //(verticalHit && horizontalHit) { |
| mMergedNormal.set(mVerticalHitNormal); |
| mMergedNormal.add(mHorizontalHitNormal); |
| mMergedNormal.normalize(); |
| parentObject.setBackgroundCollisionNormal(mMergedNormal); |
| } |
| |
| parentObject.setPosition(mCurrentPosition); |
| } |
| |
| } |
| } |
| mPreviousPosition.set(parentObject.getPosition()); |
| } |
| |
| /* Sweeps the space between two points looking for surfaces that oppose horizontal movement. */ |
| protected boolean sweepHorizontal(Vector2 previousPosition, Vector2 currentPosition, Vector2 delta, |
| int left, int right, float centerY, Vector2 hitPoint, Vector2 hitNormal, |
| GameObject parentObject) { |
| boolean hit = false; |
| if (!Utils.close(delta.x, 0.0f)) { |
| CollisionSystem collision = sSystemRegistry.collisionSystem; |
| |
| // Shoot a ray from the center of the previous frame's box to the edge (left or right, |
| // depending on the direction of movement) of the current box. |
| mTestPointStart.y = (centerY); |
| mTestPointStart.x = (left); |
| int offset = -left; |
| if (delta.x > 0.0f) { |
| mTestPointStart.x = (right); |
| offset = -right; |
| } |
| |
| // Filter out surfaces that do not oppose motion in the horizontal direction, or |
| // push in the same direction as movement. |
| mFilterDirection.set(delta); |
| mFilterDirection.y = (0); |
| |
| mTestPointEnd.set(currentPosition); |
| mTestPointEnd.add(mTestPointStart); |
| if (collision.castRay(previousPosition, mTestPointEnd, mFilterDirection, |
| hitPoint, hitNormal, parentObject)) { |
| // snap |
| currentPosition.x = (hitPoint.x + offset); |
| hit = true; |
| } |
| } |
| return hit; |
| } |
| |
| /* Sweeps the space between two points looking for surfaces that oppose vertical movement. */ |
| protected boolean sweepVertical(Vector2 previousPosition, Vector2 currentPosition, Vector2 delta, |
| int bottom, int top, float centerX, Vector2 hitPoint, Vector2 hitNormal, |
| GameObject parentObject) { |
| boolean hit = false; |
| if (!Utils.close(delta.y, 0.0f)) { |
| CollisionSystem collision = sSystemRegistry.collisionSystem; |
| // Shoot a ray from the center of the previous frame's box to the edge (top or bottom, |
| // depending on the direction of movement) of the current box. |
| mTestPointStart.x = (centerX); |
| mTestPointStart.y = (bottom); |
| int offset = -bottom; |
| if (delta.y > 0.0f) { |
| mTestPointStart.y = (top); |
| offset = -top; |
| } |
| |
| mFilterDirection.set(delta); |
| mFilterDirection.x = (0); |
| |
| mTestPointEnd.set(currentPosition); |
| mTestPointEnd.add(mTestPointStart); |
| if (collision.castRay(previousPosition, mTestPointEnd, mFilterDirection, |
| hitPoint, hitNormal, parentObject)) { |
| hit = true; |
| // snap |
| currentPosition.y = (hitPoint.y + offset); |
| } |
| |
| } |
| return hit; |
| } |
| |
| /** Comparator for hit points. */ |
| private static class HitPointDistanceComparator implements Comparator<HitPoint> { |
| private Vector2 mOrigin; |
| |
| public HitPointDistanceComparator() { |
| super(); |
| mOrigin = new Vector2(); |
| } |
| |
| public final void setOrigin(Vector2 origin) { |
| mOrigin.set(origin); |
| } |
| |
| public final void setOrigin(float x, float y) { |
| mOrigin.set(x, y); |
| } |
| |
| public int compare(HitPoint object1, HitPoint object2) { |
| int result = 0; |
| if (object1 != null && object2 != null) { |
| final float obj1Distance = object1.hitPoint.distance2(mOrigin); |
| final float obj2Distance = object2.hitPoint.distance2(mOrigin); |
| final float distanceDelta = obj1Distance - obj2Distance; |
| result = distanceDelta < 0.0f ? -1 : 1; |
| } else if (object1 == null && object2 != null) { |
| result = 1; |
| } else if (object2 == null && object1 != null) { |
| result = -1; |
| } |
| return result; |
| } |
| } |
| } |