package sim.physics2D.collisionDetection;
import sim.util.Bag;
import sim.physics2D.physicalObject.*;
import java.util.*;
/** BroadPhaseCollision2D performs "broad phase" collision detection.
* It's goal is to quickly determine which objects are near enough together
* to justify the cost of more exact "narrow phase" collision detection.
* It uses a dimension reduction strategy that determines if objects
* are simulateously overlapping on the X and Y axes. If so, the pair
* is added to the "ActiveList" for later processing by the narrow phase
* logic - Collision2D
*/
// See http://www.cs.jhu.edu/~cohen/Publications/icollide.pdf for more information
// about dimension reduction for broad phase collision detection.
class BroadPhaseCollision2D
{
private static final int X_DIM = 0;
private static final int Y_DIM = 1;
private static final double ENDPOINT_PADDING = .5;
private Bag[] arDimEPBags = new Bag[2];
private Bag arOS; // Keeps track of the overlaps
private HashSet activeList; // Objects which could currently be colliding
/** OverlapStatus tracks whether or not an object is overlapping in the
* X and Y dimensions.
*/
private class OverlapStatus
{
boolean[] dimension = new boolean[2];
}
/** EndPoints are used in the dimension lists to represent the start and end
* of each object. Each object has 2 EndPoints in both dimensions.
*/
private class EndPoint
{
boolean start; // false if this is EndPoint further from the origen
PhysicalObject2D objCol; // The object this EndPoint represents
double offset; // How far this EndPoint is from the center of the object
int dimension; // What dimension list this EndPoint is in
EndPoint(PhysicalObject2D objCol, boolean start, int dimension)
{
this.start = start;
this.objCol = objCol;
if (start)
this.offset = -1 * (getMaxDistanceFromCenter(objCol, dimension)) - ENDPOINT_PADDING;
else
this.offset = getMaxDistanceFromCenter(objCol, dimension) + ENDPOINT_PADDING;
this.dimension = dimension;
}
private double getMaxDistanceFromCenter(PhysicalObject2D objCol, int dimension)
{
if (dimension == X_DIM)
return objCol.getShape().getMaxXDistanceFromCenter();
else
return objCol.getShape().getMaxYDistanceFromCenter();
}
// An EndPoint's position is its object's position plus its offset
double getPos()
{
if (dimension == X_DIM)
return objCol.getPosition().x + offset;
else
return objCol.getPosition().y + offset;
}
}
BroadPhaseCollision2D()
{
arOS = new Bag();
arDimEPBags[0] = new Bag();
arDimEPBags[1] = new Bag();
activeList = new HashSet();
}
/** Returns a HashSet containing a list of the object pairs that
* could possibly be colliding.
*/
HashSet getActiveList()
{
return activeList;
}
/** Register an object for collision detection. */
void register(PhysicalObject2D objCol)
{
// Create and add the end points
EndPoint epStart = new EndPoint(objCol, true, 0);
EndPoint epEnd = new EndPoint(objCol, false, 0);
// X endpoint list
arDimEPBags[0].add(epStart);
arDimEPBags[0].add(epEnd);
epStart = new EndPoint(objCol, true, 1);
epEnd = new EndPoint(objCol, false, 1);
// Y endpoint list
arDimEPBags[1].add(epStart);
arDimEPBags[1].add(epEnd);
// Keep an lower diagonal matrix of overlap status
// objects. The column number is the current object
// and the row number is the object with which this one
// is being compared.
int index = objCol.getIndex();
if (index == 0)
arOS.add(null);
else
{
arOS.add(new OverlapStatus[index]);
// initialize the overlap status array
for (int i = 0; i < index; i++)
{
OverlapStatus[] arTmp = (OverlapStatus[])arOS.objs[index];
arTmp[i] = new OverlapStatus();
arTmp[i].dimension[0] = false;
arTmp[i].dimension[1] = false;
}
}
// Sort the arrays (which initializes the overlap statuses)
insertionSort(0);
insertionSort(1);
}
/** Run through all objects to see if they are colliding */
void testCollisions()
{
// Sort the arrays. This should be pretty efficient
// since the lists should be almost sorted
insertionSort(0);
insertionSort(1);
}
////////////////////////////////////////////////////
// INSERTION SORT
////////////////////////////////////////////////////
// Loop through the list of endpoints starting at 0.
// If the current endpoint's position is less than the
// previous endpoint's position, move the current endpoint
// back in the list until it is sorted. While moving back,
// update the overlap status arrays appropriately.
private void insertionSort(int dimension)
{
Bag arList = arDimEPBags[dimension];
int curEPIndex = 0;
// loop through the list
while (curEPIndex < arList.numObjs)
{
if (curEPIndex > 0)
{
int prevEPIndex = curEPIndex - 1;
EndPoint curEP = (EndPoint)arList.objs[curEPIndex];
EndPoint prevEP = (EndPoint)arList.objs[prevEPIndex];
if (prevEP.getPos() > curEP.getPos())
orderedInsert(arList, curEPIndex - 1, dimension);
}
curEPIndex += 1;
}
}
// Swaps the EndPoint down the list until it is ordered correctly
private void orderedInsert(Bag arList, int seekEPIndex, int dimension)
{
// go back until we find an EP less than this one
while (seekEPIndex >= 0
&& ((EndPoint)arList.objs[seekEPIndex]).getPos() > ((EndPoint)arList.objs[seekEPIndex + 1]).getPos())
{
// Check if overlap status should change based on the
// end point that is being reordered and the end point
// that it just passed
checkOverlaps((EndPoint)arList.objs[seekEPIndex + 1], (EndPoint)arList.objs[seekEPIndex], dimension);
// Swap down
Object temp = arList.objs[seekEPIndex + 1];
arList.objs[seekEPIndex + 1] = arList.objs[seekEPIndex];
arList.objs[seekEPIndex] = temp;
seekEPIndex--;
}
}
// Update the overlap statuses the EndPoint and the one it is being
// swapped with.
private void checkOverlaps(EndPoint curEP, EndPoint prevEP, int dimension)
{
// Higher index must be first since we have an upper triangular matrix
OverlapStatus objOS;
if (curEP.objCol.getIndex() > prevEP.objCol.getIndex())
{
OverlapStatus[] arTmp = (OverlapStatus[])arOS.objs[curEP.objCol.getIndex()];
objOS = arTmp[prevEP.objCol.getIndex()];
}
else
{
OverlapStatus[] arTmp = (OverlapStatus[])arOS.objs[prevEP.objCol.getIndex()];
objOS = arTmp[curEP.objCol.getIndex()];
}
// See if we create overlaps
if (curEP.start == true)
{
// curEP is a start point, since we have moved it behind something, we are
// now overlapping in this dimension
objOS.dimension[dimension] = true;
// see if both dimensions are true
if (objOS.dimension[0] && objOS.dimension[1])
{
if (!(curEP.objCol instanceof StationaryObject2D && prevEP.objCol instanceof StationaryObject2D))
{
CollisionPair pair = new CollisionPair(curEP.objCol, prevEP.objCol);
activeList.add(pair);
}
}
}
else
{
// This is the end point, set the overlap status to false if we move behind the
// start of something else. Since we are never setting to true, don't ever test
// for collisions
if (prevEP.start == true)
{
objOS.dimension[dimension] = false;
// Remove from the active list (does nothing if not already on)
CollisionPair pair = new CollisionPair(curEP.objCol, prevEP.objCol);
activeList.remove(pair);
}
}
}
}