blob: be8221d64341cd06f05ac93652ff5f04d29361b6 [file] [log] [blame]
/*
* 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 android.content.res.AssetManager;
import java.io.IOException;
import java.io.InputStream;
/**
* Collision detection system. Provides a ray-based interface for finding surfaces in the collision
* world. This version is based on a collision world of line segments, organized into an array of
* tiles. The underlying detection algorithm isn't relevant to calling code, however, so this class
* may be extended to provide a completely different collision detection scheme.
*
* This class also provides a system for runtime-generated collision segments. These temporary
* segments are cleared each frame, and consequently must be constantly re-submitted if they are
* intended to persist. Temporary segments are useful for dynamic solid objects, such as moving
* platforms.
*
* CollisionSystem.TileVisitor is an interface for traversing individual collision tiles. Ray casts
* can be used to run user code over the collision world by passing different TileVisitor
* implementations to executeRay. Provided is TileTestVisitor, a visitor that compares the segments
* of each tile visited with the ray and searches for points of intersection.
*
*/
public class CollisionSystem extends BaseObject {
private TiledWorld mWorld;
private CollisionTile[] mCollisionTiles;
private LineSegmentPool mSegmentPool;
private int mTileWidth;
private int mTileHeight;
private TileTestVisitor mTileSegmentTester;
private FixedSizeArray<LineSegment> mTemporarySegments;
private FixedSizeArray<LineSegment> mPendingTemporarySegments;
private byte[] mWorkspaceBytes; // Included here to avoid runtime allocation during file io.
private static final int MAX_TEMPORARY_SEGMENTS = 256;
public CollisionSystem() {
super();
mTileSegmentTester = new TileTestVisitor();
mSegmentPool = new LineSegmentPool(MAX_TEMPORARY_SEGMENTS);
mTemporarySegments = new FixedSizeArray<LineSegment>(MAX_TEMPORARY_SEGMENTS);
mPendingTemporarySegments = new FixedSizeArray<LineSegment>(MAX_TEMPORARY_SEGMENTS);
mWorkspaceBytes = new byte[4];
}
@Override
public void reset() {
mWorld = null;
mCollisionTiles = null;
final int count = mTemporarySegments.getCount();
for (int x = 0; x < count; x++) {
mSegmentPool.release(mTemporarySegments.get(x));
mTemporarySegments.set(x, null);
}
mTemporarySegments.clear();
final int pendingCount = mPendingTemporarySegments.getCount();
for (int x = 0; x < pendingCount; x++) {
mSegmentPool.release(mPendingTemporarySegments.get(x));
mPendingTemporarySegments.set(x, null);
}
mPendingTemporarySegments.clear();
}
/* Sets the current collision world to the supplied tile world. */
public void initialize(TiledWorld world, int tileWidth, int tileHeight) {
mWorld = world;
mTileWidth = tileWidth;
mTileHeight = tileHeight;
}
/**
* Casts a ray into the collision world. The ray is bound by the start and end points supplied.
* The first intersecting segment that is found is returned; in the case where more than one
* segment is found, the segment closest to the start point is returned.
*
* @param startPoint The starting point for the ray in world units.
* @param endPoint The end point for the ray in world units.
* @param movementDirection If set, only segments with normals that oppose this direction will
* be counted as valid intersections. If null, all intersecting segments will be
* considered valid.
* @param hitPoint The point of intersection between a ray and a surface, if one is found.
* @param hitNormal The normal of the intersecting surface if an intersection is found.
* @param excludeObject If set, dynamic surfaces from this object will be ignored.
* @return true if a valid intersecting surface was found, false otherwise.
*/
// TODO: switch to return data as a HitPoint.
public boolean castRay(Vector2 startPoint, Vector2 endPoint, Vector2 movementDirection,
Vector2 hitPoint, Vector2 hitNormal, GameObject excludeObject) {
boolean hit = false;
mTileSegmentTester.setup(movementDirection, mTileWidth, mTileHeight);
if (mCollisionTiles != null &&
executeRay(startPoint, endPoint, hitPoint, hitNormal, mTileSegmentTester) != -1) {
hit = true;
}
if (mTemporarySegments.getCount() > 0) {
VectorPool vectorPool = sSystemRegistry.vectorPool;
Vector2 tempHitPoint = vectorPool.allocate();
Vector2 tempHitNormal = vectorPool.allocate();
if (testSegmentAgainstList(mTemporarySegments, startPoint, endPoint, tempHitPoint,
tempHitNormal, movementDirection, excludeObject)) {
if (hit) {
// Check to see whether this collision is closer to the one we already found or
// not.
final float firstCollisionDistance = startPoint.distance2(hitPoint);
if (firstCollisionDistance > startPoint.distance2(tempHitPoint)) {
// The temporary surface is closer.
hitPoint.set(tempHitPoint);
hitNormal.set(tempHitNormal);
}
} else {
hit = true;
hitPoint.set(tempHitPoint);
hitNormal.set(tempHitNormal);
}
}
vectorPool.release(tempHitPoint);
vectorPool.release(tempHitNormal);
}
return hit;
}
public boolean testBox(float left, float right, float top, float bottom,
Vector2 movementDirection, FixedSizeArray<HitPoint> hitPoints,
GameObject excludeObject, boolean testDynamicSurfacesOnly) {
boolean foundHit = false;
// Test against the background.
if (!testDynamicSurfacesOnly) {
float startX = left;
float endX = right;
float startY = bottom;
float endY = top;
int xIncrement = 1;
int yIncrement = 1;
if (movementDirection != null) {
if (movementDirection.x < 0.0f) {
startX = right;
endX = left;
xIncrement = -1;
}
if (movementDirection.y < 0.0f) {
startY = top;
endY = bottom;
yIncrement = -1;
}
}
final int startTileX = Utils.clamp((int)(startX / mTileWidth), 0, mWorld.getWidth() - 1);
final int endTileX = Utils.clamp((int)(endX / mTileWidth), 0, mWorld.getWidth() - 1);
final int startTileY = Utils.clamp((int)(startY / mTileHeight), 0, mWorld.getHeight() - 1);
final int endTileY = Utils.clamp((int)(endY / mTileHeight), 0, mWorld.getHeight() - 1);
VectorPool vectorPool = sSystemRegistry.vectorPool;
Vector2 worldTileOffset = vectorPool.allocate();
final int[][] tileArray = mWorld.getTiles();
final int worldHeight = mWorld.getHeight() - 1;
for (int y = startTileY; y != endTileY + yIncrement; y += yIncrement) {
for (int x = startTileX; x != endTileX + xIncrement; x += xIncrement) {
final int tileIndex = tileArray[x][worldHeight - y];
if (tileIndex >= 0 && tileIndex < mCollisionTiles.length
&& mCollisionTiles[tileIndex] != null) {
final float xOffset = x * mTileWidth;
final float yOffset = y * mTileHeight;
final float tileSpaceLeft = left - xOffset;
final float tileSpaceRight = right - xOffset;
final float tileSpaceTop = top - yOffset;
final float tileSpaceBottom = bottom - yOffset;
worldTileOffset.set(xOffset, yOffset);
boolean hit = testBoxAgainstList(mCollisionTiles[tileIndex].segments,
tileSpaceLeft, tileSpaceRight, tileSpaceTop, tileSpaceBottom,
movementDirection, excludeObject, worldTileOffset, hitPoints);
if (hit) {
foundHit = true;
}
}
}
}
vectorPool.release(worldTileOffset);
}
// temporary segments
boolean tempHit = testBoxAgainstList(mTemporarySegments,
left, right, top, bottom,
movementDirection, excludeObject, Vector2.ZERO, hitPoints);
if (tempHit) {
foundHit = true;
}
return foundHit;
}
/* Inserts a temporary surface into the collision world. It will persist for one frame. */
public void addTemporarySurface(Vector2 startPoint, Vector2 endPoint, Vector2 normal,
GameObject ownerObject) {
LineSegment newSegment = mSegmentPool.allocate();
newSegment.set(startPoint, endPoint, normal);
newSegment.setOwner(ownerObject);
mPendingTemporarySegments.add(newSegment);
}
@Override
public void update(float timeDelta, BaseObject parent) {
// Clear temporary surfaces
final int count = mTemporarySegments.getCount();
if (mCollisionTiles != null && count > 0) {
for (int x = 0; x < count; x++) {
mSegmentPool.release(mTemporarySegments.get(x));
mTemporarySegments.set(x, null);
}
mTemporarySegments.clear();
}
// Temporary surfaces must persist for one frame in order to be reliable independent of
// frame execution order. So each frame we queue up inserted segments and then swap them
// into activity when this system is updated.
FixedSizeArray<LineSegment> swap = mTemporarySegments;
mTemporarySegments = mPendingTemporarySegments;
mPendingTemporarySegments = swap;
}
/**
* Shoots a ray through the collision world. This function is similar to executeRay() below,
* except that it is optimized for straight lines (either completely horizontal or completely
* vertical).
*
* @param startPoint The starting point for the ray, in world space.
* @param endPoint The ending point for the ray in world space.
* @param hitPoint Set to the intersection coordinates if an intersection is found.
* @param hitNormal Set to the normal of the intersecting surface if an intersection is found.
* @param visitor Class defining what work to perform at each tile step.
* @return The index of the tile that intersected the ray, or -1 if no intersection was found.
*/
protected int executeStraigtRay(final Vector2 startPoint, final Vector2 endPoint,
final int startTileX, final int startTileY, final int endTileX, final int endTileY,
final int deltaX, final int deltaY,
Vector2 hitPoint, Vector2 hitNormal, TileVisitor visitor) {
int currentX = startTileX;
int currentY = startTileY;
int xIncrement = 0;
int yIncrement = 0;
int distance = 0;
if (deltaX != 0) {
distance = Math.abs(deltaX) + 1;
xIncrement = Utils.sign(deltaX);
} else if (deltaY != 0) {
distance = Math.abs(deltaY) + 1;
yIncrement = Utils.sign(deltaY);
}
int hitTile = -1;
final int worldHeight = mWorld.getHeight() - 1;
final int[][] tileArray = mWorld.getTiles();
for (int x = 0; x < distance; x++) {
final int tileIndex = tileArray[currentX][worldHeight - currentY];
if (tileIndex >= 0 && tileIndex < mCollisionTiles.length
&& mCollisionTiles[tileIndex] != null) {
if (visitor.visit(mCollisionTiles[tileIndex], startPoint, endPoint,
hitPoint, hitNormal, currentX, currentY)) {
hitTile = tileIndex;
break;
}
}
currentX += xIncrement;
currentY += yIncrement;
}
return hitTile;
}
/**
* Shoots a ray through the collision world. Since the collision world is a 2D array of tiles,
* this algorithm traces a line in tile space and tests against each non-empty tile it visits.
* The Bresenham line algorithm is used for the actual traversal, but the action taken at each
* tile is defined by the visitor class passed to this function.
*
* @param startPoint The starting point for the ray, in world space.
* @param endPoint The ending point for the ray in world space.
* @param hitPoint Set to the intersection coordinates if an intersection is found.
* @param hitNormal Set to the normal of the intersecting surface if an intersection is found.
* @param visitor Class defining what work to perform at each tile step.
* @return The index of the tile that intersected the ray, or -1 if no intersection was found.
*/
protected int executeRay(Vector2 startPoint, Vector2 endPoint,
Vector2 hitPoint, Vector2 hitNormal, TileVisitor visitor) {
final int worldHeight = mWorld.getHeight();
final int worldWidth = mWorld.getWidth();
final int startTileX = worldToTileColumn(startPoint.x, worldWidth);
final int startTileY = worldToTileRow(startPoint.y, worldHeight);
final int endTileX = worldToTileColumn(endPoint.x, worldWidth);
final int endTileY = worldToTileRow(endPoint.y, worldHeight);
int currentX = startTileX;
int currentY = startTileY;
final int deltaX = endTileX - startTileX;
final int deltaY = endTileY - startTileY;
int hitTile = -1;
if (deltaX == 0 || deltaY == 0) {
hitTile = executeStraigtRay(startPoint, endPoint, startTileX, startTileY,
endTileX, endTileY, deltaX, deltaY, hitPoint, hitNormal, visitor);
} else {
final int xIncrement = deltaX != 0 ? Utils.sign(deltaX) : 0;
final int yIncrement = deltaY != 0 ? Utils.sign(deltaY) : 0;
// Note: I'm deviating from the Bresenham algorithm here by adding one to force the end
// tile to be visited.
final int lateralDelta = (endTileX > 0 && endTileX < worldWidth - 1) ? Math.abs(deltaX) + 1 : Math.abs(deltaX);
final int verticalDelta = (endTileY > 0 && endTileY < worldHeight - 1) ? Math.abs(deltaY) + 1 : Math.abs(deltaY);
final int deltaX2 = lateralDelta * 2;
final int deltaY2 = verticalDelta * 2;
final int worldHeightMinusOne = worldHeight - 1;
final int[][]tileArray = mWorld.getTiles();
// Bresenham line algorithm in tile space.
if (lateralDelta >= verticalDelta) {
int error = deltaY2 - lateralDelta;
for (int i = 0; i < lateralDelta; i++) {
final int tileIndex = tileArray[currentX][worldHeightMinusOne - currentY];
if (tileIndex >= 0 && tileIndex < mCollisionTiles.length
&& mCollisionTiles[tileIndex] != null) {
if (visitor.visit(mCollisionTiles[tileIndex], startPoint, endPoint,
hitPoint, hitNormal, currentX, currentY)) {
hitTile = tileIndex;
break;
}
}
if (error > 0) {
currentY += yIncrement;
error -= deltaX2;
}
error += deltaY2;
currentX += xIncrement;
}
}
else if (verticalDelta >= lateralDelta) {
int error = deltaX2 - verticalDelta;
for (int i = 0; i < verticalDelta; i++) {
final int tileIndex = tileArray[currentX][worldHeightMinusOne - currentY];
if (tileIndex >= 0 && tileIndex < mCollisionTiles.length
&& mCollisionTiles[tileIndex] != null) {
if (visitor.visit(mCollisionTiles[tileIndex], startPoint, endPoint,
hitPoint, hitNormal, currentX, currentY)) {
hitTile = tileIndex;
break;
}
}
if (error > 0) {
currentX += xIncrement;
error -= deltaY2;
}
error += deltaX2;
currentY += yIncrement;
}
}
}
return hitTile;
}
protected final int worldToTileColumn(final float x, final int width) {
return Utils.clamp((int)Math.floor(x / mTileWidth), 0, width - 1);
}
protected final int worldToTileRow(float y, final int height) {
return Utils.clamp((int)Math.floor(y / mTileHeight), 0, height - 1);
}
/*
* Given a list of segments and a ray, this function performs an intersection search and
* returns the closest intersecting segment, if any exists.
*/
protected static boolean testSegmentAgainstList(FixedSizeArray<LineSegment> segments,
Vector2 startPoint, Vector2 endPoint, Vector2 hitPoint, Vector2 hitNormal,
Vector2 movementDirection, GameObject excludeObject) {
boolean foundHit = false;
float closestDistance = -1;
float hitX = 0;
float hitY = 0;
float normalX = 0;
float normalY = 0;
final int count = segments.getCount();
final Object[] segmentArray = segments.getArray();
for (int x = 0; x < count; x++) {
LineSegment segment = (LineSegment)segmentArray[x];
// If a movement direction has been passed, filter out invalid surfaces by ignoring
// those that do not oppose movement. If no direction has been passed, accept all
// surfaces.
final float dot = movementDirection.length2() > 0.0f ?
movementDirection.dot(segment.mNormal) : -1.0f;
if (dot < 0.0f &&
(excludeObject == null || segment.owner != excludeObject) &&
segment.calculateIntersection(startPoint, endPoint, hitPoint)) {
final float distance = hitPoint.distance2(startPoint);
if (!foundHit || closestDistance > distance) {
closestDistance = distance;
foundHit = true;
normalX = segment.mNormal.x;
normalY = segment.mNormal.y;
// Store the components on their own so we don't have to allocate a vector
// in this loop.
hitX = hitPoint.x;
hitY = hitPoint.y;
}
}
}
if (foundHit) {
hitPoint.set(hitX, hitY);
hitNormal.set(normalX, normalY);
}
return foundHit;
}
protected static boolean testBoxAgainstList(FixedSizeArray<LineSegment> segments,
float left, float right, float top, float bottom,
Vector2 movementDirection, GameObject excludeObject, Vector2 outputOffset,
FixedSizeArray<HitPoint> outputHitPoints) {
int hitCount = 0;
final int maxSegments = outputHitPoints.getCapacity() - outputHitPoints.getCount();
final int count = segments.getCount();
final Object[] segmentArray = segments.getArray();
VectorPool vectorPool = sSystemRegistry.vectorPool;
HitPointPool hitPool = sSystemRegistry.hitPointPool;
Vector2 tempHitPoint = vectorPool.allocate();
for (int x = 0; x < count && hitCount < maxSegments; x++) {
LineSegment segment = (LineSegment)segmentArray[x];
// If a movement direction has been passed, filter out invalid surfaces by ignoring
// those that do not oppose movement. If no direction has been passed, accept all
// surfaces.
final float dot = movementDirection.length2() > 0.0f ?
movementDirection.dot(segment.mNormal) : -1.0f;
if (dot < 0.0f &&
(excludeObject == null || segment.owner != excludeObject) &&
segment.calculateIntersectionBox(left, right, top, bottom, tempHitPoint)) {
Vector2 hitPoint = vectorPool.allocate(tempHitPoint);
Vector2 hitNormal = vectorPool.allocate(segment.mNormal);
hitPoint.add(outputOffset);
HitPoint hit = hitPool.allocate();
hit.hitPoint = hitPoint;
hit.hitNormal = hitNormal;
outputHitPoints.add(hit);
hitCount++;
}
}
vectorPool.release(tempHitPoint);
return hitCount > 0;
}
/*
* Loads line segments from a binary file and builds the tiled collision database
* accordingly.
*/
public boolean loadCollisionTiles(InputStream stream) {
boolean success = false;
AssetManager.AssetInputStream byteStream = (AssetManager.AssetInputStream) stream;
int signature;
// TODO: this is a hack. I really should only allocate an array that is the size of the
// tileset, but at this point I don't actually know that size, so I allocate a buffer that's
// probably large enough.
mCollisionTiles = new CollisionTile[256];
try {
signature = (byte)byteStream.read();
if (signature == 52) {
// This file has the following deserialization format:
// read the number of tiles
// for each tile
// read the tile id
// read the number of segments
// for each segment
// read startx, starty, endx, endy, normalx, normaly
final int tileCount = byteStream.read();
final int size = (1 + 1 + 4 + 4 + 4 + 4 + 4 + 4) * tileCount;
if (byteStream.available() >= size) {
for (int x = 0; x < tileCount; x++) {
final int tileIndex = byteStream.read();
final int segmentCount = byteStream.read();
if (mCollisionTiles[tileIndex] == null && segmentCount > 0) {
mCollisionTiles[tileIndex] = new CollisionTile(segmentCount);
}
for (int y = 0; y < segmentCount; y++) {
byteStream.read(mWorkspaceBytes, 0, 4);
final float startX = Utils.byteArrayToFloat(mWorkspaceBytes);
byteStream.read(mWorkspaceBytes, 0, 4);
final float startY = Utils.byteArrayToFloat(mWorkspaceBytes);
byteStream.read(mWorkspaceBytes, 0, 4);
final float endX = Utils.byteArrayToFloat(mWorkspaceBytes);
byteStream.read(mWorkspaceBytes, 0, 4);
final float endY = Utils.byteArrayToFloat(mWorkspaceBytes);
byteStream.read(mWorkspaceBytes, 0, 4);
final float normalX = Utils.byteArrayToFloat(mWorkspaceBytes);
byteStream.read(mWorkspaceBytes, 0, 4);
final float normalY = Utils.byteArrayToFloat(mWorkspaceBytes);
// TODO: it might be wise to pool line segments. I don't think that
// this data will be loaded very often though, so this is ok for now.
LineSegment newSegment = new LineSegment();
newSegment.mStartPoint.set(startX, startY);
newSegment.mEndPoint.set(endX, endY);
newSegment.mNormal.set(normalX, normalY);
mCollisionTiles[tileIndex].addSegment(newSegment);
}
}
}
}
} catch (IOException e) {
//TODO: figure out the best way to deal with this. Assert?
}
return success;
}
/**
* An interface for visiting tiles during a ray cast. Implementations of TileVisitor
* can be passed to executeRay(); the visit() function will be invoked for each tile touched by
* the ray until the traversal is completed or visit() returns false.
*/
public abstract class TileVisitor extends AllocationGuard {
public TileVisitor() {
super();
}
// If true is returned, tile scanning continues. Otherwise it stops.
public abstract boolean visit(CollisionTile tile, Vector2 startPoint, Vector2 endPoint,
Vector2 hitPoint, Vector2 hitNormal, int tileX, int tileY);
}
/**
* TileTestVisitor tests the ray against a list of segments assigned to each tile. If any
* segment in any tile of the ray's path is found to be intersecting with the ray, traversal
* stops and intersection information is recorded.
*/
protected class TileTestVisitor extends TileVisitor {
// These vectors are all temporary storage variables allocated as class members to avoid
// runtime allocation.
private Vector2 mDelta;
private Vector2 mTileSpaceStart;
private Vector2 mTileSpaceEnd;
private Vector2 mTileSpaceOffset;
private int mTileHeight;
private int mTileWidth;
public TileTestVisitor() {
super();
mDelta = new Vector2();
mTileSpaceStart = new Vector2();
mTileSpaceEnd = new Vector2();
mTileSpaceOffset = new Vector2();
}
/**
* Sets the visitor up for a ray test. Initializes the size of the tiles and the direction
* of movement by which intersections should be filtered.
*/
public void setup(Vector2 movementDirection, int tileWidth, int tileHeight) {
if (movementDirection != null) {
mDelta.set(movementDirection);
mDelta.normalize();
} else {
mDelta.zero();
}
mTileWidth = tileWidth;
mTileHeight = tileHeight;
}
/**
* Converts the ray into tile space and then compares it to the segments
* stored in the current tile.
*/
@Override
public boolean visit(CollisionTile tile, Vector2 startPoint, Vector2 endPoint,
Vector2 hitPoint, Vector2 hitNormal, int tileX, int tileY) {
mTileSpaceOffset.set(tileX * mTileWidth, tileY * mTileHeight);
mTileSpaceStart.set(startPoint);
mTileSpaceStart.subtract(mTileSpaceOffset);
mTileSpaceEnd.set(endPoint);
mTileSpaceEnd.subtract(mTileSpaceOffset);
// find all the hits in the tile and pick the closest to the start point.
boolean foundHit = testSegmentAgainstList(tile.segments, mTileSpaceStart, mTileSpaceEnd,
hitPoint, hitNormal, mDelta, null);
if (foundHit) {
// The hitPoint is in tile space, so convert it back to world space.
hitPoint.add(mTileSpaceOffset);
}
return foundHit;
}
}
/**
* A class describing a single surface in the collision world. Surfaces are stored as a line
* segment and a normal. The normal must be normalized (its length must be 1.0) and should
* describe the direction that the segment "pushes against" in a collision.
*/
protected class LineSegment extends AllocationGuard {
private Vector2 mStartPoint;
private Vector2 mEndPoint;
public Vector2 mNormal;
public GameObject owner;
public LineSegment() {
super();
mStartPoint = new Vector2();
mEndPoint = new Vector2();
mNormal = new Vector2();
}
/* Sets up the line segment. Values are copied to local storage. */
public void set(Vector2 start, Vector2 end, Vector2 norm) {
mStartPoint.set(start);
mEndPoint.set(end);
mNormal.set(norm);
}
public void setOwner(GameObject ownerObject) {
owner = ownerObject;
}
/**
* Checks to see if these lines intersect by projecting one onto the other and then
* assuring that the collision point is within the range of each segment.
*/
public boolean calculateIntersection(Vector2 otherStart, Vector2 otherEnd,
Vector2 hitPoint) {
boolean intersecting = false;
// Reference: http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/
final float x1 = mStartPoint.x;
final float x2 = mEndPoint.x;
final float x3 = otherStart.x;
final float x4 = otherEnd.x;
final float y1 = mStartPoint.y;
final float y2 = mEndPoint.y;
final float y3 = otherStart.y;
final float y4 = otherEnd.y;
final float denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
if (denom != 0) {
final float uA = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denom;
final float uB = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denom;
if (uA >= 0.0f && uA <= 1.0f && uB >= 0.0f && uB <= 1.0f) {
final float hitX = x1 + (uA * (x2 - x1));
final float hitY = y1 + (uA * (y2 - y1));
hitPoint.set(hitX, hitY);
intersecting = true;
}
}
return intersecting;
}
// Based on http://www.garagegames.com/community/resources/view/309
public boolean calculateIntersectionBox(float left, float right, float top, float bottom,
Vector2 hitPoint) {
final float x1 = mStartPoint.x;
final float x2 = mEndPoint.x;
final float y1 = mStartPoint.y;
final float y2 = mEndPoint.y;
float startIntersect;
float endIntersect;
float intersectTimeStart = 0.0f;
float intersectTimeEnd = 1.0f;
if (x1 < x2) {
if (x1 > right || x2 < left) {
return false;
}
final float deltaX = x2 - x1;
startIntersect = (x1 < left) ? (left - x1) / deltaX : 0.0f;
endIntersect = (x2 > right) ? (right - x1) / deltaX : 1.0f;
} else {
if (x2 > right || x1 < left) {
return false;
}
final float deltaX = x2 - x1;
startIntersect = (x1 > right) ? (right - x1) / deltaX : 0.0f;
endIntersect = (x2 < left) ? (left - x1) / deltaX : 1.0f;
}
if (startIntersect > intersectTimeStart) {
intersectTimeStart = startIntersect;
}
if (endIntersect < intersectTimeEnd) {
intersectTimeEnd = endIntersect;
}
if (intersectTimeEnd < intersectTimeStart) {
return false;
}
// y
if (y1 < y2) {
if (y1 > top || y2 < bottom) {
return false;
}
final float deltaY = y2 - y1;
startIntersect = (y1 < bottom) ? (bottom - y1) / deltaY : 0.0f;
endIntersect = (y2 > top) ? (top - y1) / deltaY : 1.0f;
} else {
if (y2 > top || y1 < bottom) {
return false;
}
final float deltaY = y2 - y1;
startIntersect = (y1 > top) ? (top - y1) / deltaY : 0.0f;
endIntersect = (y2 < bottom) ? (bottom - y1) / deltaY : 1.0f;
}
if (startIntersect > intersectTimeStart) {
intersectTimeStart = startIntersect;
}
if (endIntersect < intersectTimeEnd) {
intersectTimeEnd = endIntersect;
}
if (intersectTimeEnd < intersectTimeStart) {
return false;
}
hitPoint.set(mEndPoint);
hitPoint.subtract(mStartPoint);
hitPoint.multiply(intersectTimeStart);
hitPoint.add(mStartPoint);
return true;
}
}
/**
* A pool of line segments.
*/
protected class LineSegmentPool extends TObjectPool<LineSegment> {
public LineSegmentPool() {
super();
}
public LineSegmentPool(int count) {
super(count);
}
@Override
public void reset() {
}
@Override
protected void fill() {
for (int x = 0; x < getSize(); x++) {
getAvailable().add(new LineSegment());
}
}
@Override
public void release(Object entry) {
((LineSegment)entry).owner = null;
super.release(entry);
}
}
/**
* A single collision tile. Manages a list of line segments.
*/
protected class CollisionTile extends AllocationGuard {
public FixedSizeArray<LineSegment> segments;
public CollisionTile(int maxSegments) {
super();
segments = new FixedSizeArray<LineSegment>(maxSegments);
}
public boolean addSegment(LineSegment segment) {
boolean success = false;
if (segments.getCount() < segments.getCapacity()) {
success = true;
}
segments.add(segment);
return success;
}
}
}