package greenfoot.collision;
import greenfoot.Actor;
import greenfoot.ActorVisitor;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
| Very good when objects only span one cell.
| Good when most of the cells are occupied by objects. It has a store for each
| cell location, so it could potentially take up a lot of memory if the world
| is big.
| Very poor performance when objects span multiple cells (noOfObjects^2)
| TODO: check performance with objects that spans multiple cells.
|
| @author Poul Henriksen
|
public class GridCollisionChecker
implements CollisionChecker{
class Cell
{
private HashMap<Class<?>,List<Actor>> classMap = new HashMap<Class<?>,List<Actor>>();
private List<Actor> objects = new ArrayList<Actor>();
public void add(Actor thing)
{
Class<?> clazz = thing.getClass();
List<Actor> list = classMap.get(clazz);
if (list == null) {
list = new ArrayList<Actor>();
classMap.put(clazz, list);
}
if (!list.contains(thing)) {
list.add(thing);
}
if (!objects.contains(thing)) {
objects.add(thing);
}
}
@SuppressWarnings("unchecked")
public <T> List<T> get(Class<T> cls)
{
return (List<T>) classMap.get(cls);
}
public void remove(Actor object)
{
objects.remove(object);
List<Actor> classes = classMap.get(object.getClass());
if (classes != null) {
classes.remove(object);
}
}
public boolean isEmpty()
{
return objects.isEmpty();
}
| Returns all objects in this cell. Be carefull not to modify this
| collection!!!
|
| TODO For performace testing it has not been made imuttable...
|
| @return
|
public List getAll()
{
return objects;
}
}
| A grid world is made up of cells. Each location in the grid is
| represented by a cell.
|
| @author Poul Henriksen
|
private class GridWorld
{
protected Cell[][] world;
public GridWorld(int width, int height)
{
world = new Cell[width][height];
}
public Cell get(int x, int y)
{
return world[x][y];
}
public void set(int x, int y, Cell cell)
{
world[x][y] = cell;
}
public int getWidth()
{
return world.length;
}
public int getHeight()
{
return world[0].length;
}
}
private class WrappingGridWorld
extends GridWorld
{
public WrappingGridWorld(int width, int height)
{
super(width, height);
}
public Cell get(int x, int y)
{
x = wrap(x, getWidth());
y = wrap(y, getHeight());
return world[x][y];
}
public void set(int x, int y, Cell cell)
{
x = wrap(x, getWidth());
y = wrap(y, getHeight());
world[x][y] = cell;
}
}
public static class Statistics
{
private static final String format = "%15s%15s%15s%15s%15s%15s";
private long objectsAt;
private long intersectionObjects;
private long objectsInRange;
private long neighbours;
private long objectsInDirection;
private long startTime = -1;
public void incGetObjectsAt()
{
initStartTime();
objectsAt++;
}
public void incGetIntersectingObjects()
{
initStartTime();
intersectionObjects++;
}
public void incGetObjectsInRange()
{
initStartTime();
objectsInRange++;
}
public void incGetNeighbours()
{
initStartTime();
neighbours++;
}
public void incGetObjectsInDirection()
{
initStartTime();
objectsInDirection++;
}
private void initStartTime()
{
if (startTime == -1) {
startTime = System.currentTimeMillis();
}
}
public String toString()
{
return String.format(format, new Object[] {
Long.valueOf(startTime),
Long.valueOf(objectsAt),
Long.valueOf(intersectionObjects),
Long.valueOf(objectsInRange),
Long.valueOf(neighbours),
Long.valueOf(objectsInDirection)
});
}
public static String headerString()
{
return String.format(format, new Object[] {
"startTime",
"objectsAt",
"intersection",
"oinRange",
"neighbours",
"inDirection"
});
}
}
private Set<Actor> objects;
private boolean wrap;
private GridWorld world;
private int cellSize;
private Statistics currentStats = new Statistics();
private List<Statistics> allStats = new ArrayList<Statistics>();
private static boolean PRINT_STATS = false;
public void initialize(int width, int height, int cellSize, boolean wrap)
{
this.wrap = wrap;
this.cellSize = cellSize;
objects = null;
if (PRINT_STATS) {
System.out.println(Statistics.headerString());
objects = new TreeSet<Actor>(new Comparator<Actor>() {
public int compare(Actor arg0, Actor arg1)
{
return arg0.hashCode() - arg1.hashCode();
}
});
}
else {
objects = new HashSet<Actor>();
}
if (wrap) {
world = new WrappingGridWorld(width, height);
}
else {
world = new GridWorld(width, height);
}
}
| Adds a Actor to the world.
| If the coordinates of the object is outside the worlds bounds, an
| exception is thrown.
|
| @param thing
| The new object to add.
|
public synchronized void addObject(Actor thing)
throws ArrayIndexOutOfBoundsException
{
testBounds(thing);
if (!objects.contains(thing)) {
int xpos = ActorVisitor.getX(thing);
int ypos = ActorVisitor.getY(thing);
Cell cell = world.get(xpos, ypos);
if (cell == null) {
cell = new Cell();
world.set(xpos, ypos, cell);
}
cell.add(thing);
objects.add(thing);
}
}
private void testBounds(Actor thing)
{
int ax = ActorVisitor.getX(thing);
int ay = ActorVisitor.getY(thing);
if (ax >= getWidth()) {
throw new ArrayIndexOutOfBoundsException(ax);
}
if (ay >= getHeight()) {
throw new ArrayIndexOutOfBoundsException(ay);
}
if (ax < 0) {
throw new ArrayIndexOutOfBoundsException(ax);
}
if (ay < 0) {
throw new ArrayIndexOutOfBoundsException(ay);
}
}
|
| TODO: Bad performance. Can be improved MUCH if we only handle worlds
| wehre objects spans a single cell.
|
| @see Actor#contains(int, int)
|
@SuppressWarnings("unchecked")
public <T extends Actor> List getObjectsAt(int x, int y, Class<T> cls)
{
if (wrap) {
x = wrap(x, world.getWidth());
y = wrap(y, world.getWidth());
}
List<T> objectsThere = new ArrayList<T>();
for (Iterator<Actor> iter = objects.iterator(); iter.hasNext();) {
currentStats.incGetObjectsAt();
Actor actor = iter.next();
int ax = x * cellSize + cellSize / 2;
int ay = y * cellSize + cellSize / 2;
if ((cls == null || cls.isInstance(actor)) && ActorVisitor.containsPoint(actor, ax, y - ay)) {
objectsThere.add((T) actor);
}
}
return objectsThere;
}
| Gets all objects within the given radius and of the given class (or
| subclass).
|
| The center of the circle is considered to be at the center of the cell.
| Objects which have the center within the circle is considered to be in
| range.
|
| @param x
| The x-coordinate of the center
| @param y
| The y-coordinate of the center
| @param r
| The radius
| @param cls
| Only objects of this class (or subclasses) are returned
| @return
|
@SuppressWarnings("unchecked")
public <T extends Actor> List getObjectsInRange(int x, int y, int r, Class<T> cls)
{
Iterator<Actor> iter = objects.iterator();
List<T> neighbours = new ArrayList<T>();
while (iter.hasNext()){
Object o = iter.next();
currentStats.incGetObjectsInRange();
if (cls == null || cls.isInstance(o)) {
Actor g = (Actor) o;
if (distance(x, y, g) <= r) {
neighbours.add((T) g);
}
}
}
return neighbours;
}
| Returns the shortest distance from the cell (center of cell ) to the
| center of the greenfoot object.
|
| @param x
| x-coordinate of the cell
| @param y
| y-coordinate of the cell
| @param actor
| @return
|
private double distance(int x, int y, Actor actor)
{
double gx = ActorVisitor.getX(actor);
double gy = ActorVisitor.getY(actor);
double dx = Math.abs(gx - x);
double dy = Math.abs(gy - y);
if (wrap) {
double dxWrap = getWidth() - dx;
double dyWrap = getWidth() - dy;
if (dx >= dxWrap) {
dx = dxWrap;
}
if (dy >= dyWrap) {
dy = dyWrap;
}
}
return Math.sqrt(dx * dx + dy * dy);
}
| Removes the object
|
| @param object
| the object to remove
|
public synchronized void removeObject(Actor object)
{
int ax = ActorVisitor.getX(object);
int ay = ActorVisitor.getY(object);
Cell cell = world.get(ax, ay);
if (cell != null) {
cell.remove(object);
if (cell.isEmpty()) {
world.set(ax, ay, null);
}
}
objects.remove(object);
}
| Gets the width of the world.
|
public int getWidth()
{
return world.getWidth();
}
| Gets the height of the world.
|
public int getHeight()
{
return world.getHeight();
}
|
| Updates the location of the object in the world.
|
| @param object
| The object which should be updated
| @param oldX
| The old X location of the object
| @param oldY
| The old Y location of the object
|
public void updateObjectLocation(Actor object, int oldX, int oldY)
{
Cell cell = world.get(oldX, oldY);
if (cell != null) {
cell.remove(object);
if (cell.isEmpty()) {
world.set(oldX, oldY, null);
}
}
int ax = ActorVisitor.getX(object);
int ay = ActorVisitor.getY(object);
cell = world.get(ax, ay);
if (cell == null) {
cell = new Cell();
world.set(ax, ay, cell);
}
cell.add(object);
}
public void updateObjectSize(Actor object)
{
return;
}
|
| This is very slow in this implementation as it checks against all objects
|
@SuppressWarnings("unchecked")
public <T extends Actor> List getIntersectingObjects(Actor actor, Class<T> cls)
{
List<T> intersecting = new ArrayList<T>();
for (Iterator<Actor> iter = objects.iterator(); iter.hasNext();) {
Actor element = iter.next();
currentStats.incGetIntersectingObjects();
if (element != actor && ActorVisitor.intersects(actor, element) && (cls == null || cls.isInstance(element))) {
intersecting.add((T) element);
}
}
return intersecting;
}
|
| @see greenfoot.collision.CollisionChecker#getNeighbours(int, boolean,
| java.lang.Class)
|
public <T extends Actor> List getNeighbours(Actor actor, int distance, boolean diag, Class<T> cls)
{
int x = ActorVisitor.getX(actor);
int y = ActorVisitor.getY(actor);
List<T> c = new ArrayList<T>();
if (diag) {
for (int dx = x - distance; dx <= x + distance; dx++) {
if (!wrap) {
if (dx < 0)
continue;
if (dx >= world.getWidth())
break;
}
for (int dy = y - distance; dy <= y + distance; dy++) {
if (!wrap) {
if (dy < 0)
continue;
if (dy >= world.getHeight())
break;
}
if (dx == x && dy == y)
continue;
Cell cell = world.get(dx, dy);
currentStats.incGetNeighbours();
if (cell != null) {
Collection<T> found = cell.get(cls);
if (found != null) {
c.addAll(found);
}
}
}
}
}
else {
int d = distance;
int xStart = x;
int yStart = y;
int dyEnd = d;
for (int dx = 0; dx <= d; dx++) {
for (int dy = dx - d; dy <= dyEnd; dy++) {
int xPos = xStart + dx;
int xNeg = xStart - dx;
int yPos = yStart + dy;
if (!wrap) {
if (yPos >= world.getHeight()) {
break;
}
if (yPos < 0) {
continue;
}
}
if (dx == 0 && dy == 0) {
continue;
}
currentStats.incGetNeighbours();
if (withinBounds(xPos, getWidth())) {
Cell cell = world.get(xPos, yPos);
if (cell != null) {
Collection<T> found = cell.get(cls);
if (found != null) {
c.addAll(found);
}
}
}
if (dx != 0 && withinBounds(xNeg, getWidth())) {
Cell cell = world.get(xNeg, yPos);
if (cell != null) {
Collection<T> found = cell.get(cls);
if (found != null) {
c.addAll(found);
}
}
}
}
dyEnd--;
}
}
return c;
}
| Return all objects that intersect a straight line from this object at a
| specified angle. The angle is clockwise relative to the current rotation
| of the object.
|
| This implementation is likely to change. Currently it uses a Bresenham algorithm.
|
| @param x x-coordinate
| @param y y-coordinate
| @param angle The angle relative to current rotation of the object.
| @param length How far we want to look (in cells)
| @param cls Class of objects to look for (passing 'null' will find all
| objects).
|
public <T extends Actor> List getObjectsInDirection(int x, int y, int angle, int length, Class<T> cls)
{
List<T> result = new ArrayList<T>();
double dy = (2 * Math.sin(Math.toRadians(angle)));
double dx = (2 * Math.cos(Math.toRadians(angle)));
int lxMax = (int) Math.abs(Math.round(length * Math.cos(Math.toRadians(angle))));
int lyMax = (int) Math.abs(Math.round(length * Math.sin(Math.toRadians(angle))));
int stepx, stepy;
if (dy < 0) {
dy = -dy;
stepy = -1;
}
else {
stepy = 1;
}
if (dx < 0) {
dx = -dx;
stepx = -1;
}
else {
stepx = 1;
}
result.addAll(getObjectsAt(x, y, cls));
if (dx > dy) {
double fraction = dy - (dx / 2);
for (int l=0; l< lxMax; l++) {
currentStats.incGetObjectsInDirection();
if (fraction >= 0) {
y += stepy;
fraction -= dx;
}
x += stepx;
fraction += dy;
result.addAll(getObjectsAt(x, y, cls));
}
}
else {
double fraction = dx - (dy / 2);
for (int l=0; l< lyMax; l++) {
currentStats.incGetObjectsInDirection();
if (fraction >= 0) {
x += stepx;
fraction -= dy;
}
y += stepy;
fraction += dx;
result.addAll(getObjectsAt(x, y, cls));
}
}
return result;
}
|
| Determines if x lies between 0 and width. If wrapping is on this will
| always return true.
|
| @param xPos
| @return
|
private boolean withinBounds(int x, int width)
{
return wrap || (!wrap && x >= 0 && x < width);
}
| Wraps the number x with the width
|
private int wrap(int x, int width)
{
int remainder = x % width;
if (remainder < 0) {
return width + remainder;
}
else {
return remainder;
}
}
public void startSequence()
{
if (PRINT_STATS) {
System.out.println(currentStats);
}
allStats.add(currentStats);
currentStats = new Statistics();
}
@SuppressWarnings("unchecked")
public <T extends Actor> List getObjects(Class<T> cls)
{
List<T> objectsThere = new ArrayList<T>();
for (Iterator<Actor> iter = objects.iterator(); iter.hasNext();) {
currentStats.incGetObjectsAt();
Actor actor = iter.next();
if (cls == null || cls.isInstance(actor)) {
objectsThere.add((T) actor);
}
}
return objectsThere;
}
public List getObjectsList()
{
List<Actor> l = new ArrayList<Actor>(objects);
return l;
}
public <T extends Actor> T getOneObjectAt(Actor actor, int dx, int dy, Class<T> cls)
{
List<T> neighbours = getObjectsAt(dx, dy, cls);
neighbours.remove(actor);
if (!neighbours.isEmpty()) {
return neighbours.get(0);
}
else {
return null;
}
}
public <T extends Actor> T getOneIntersectingObject(Actor object, Class<T> cls)
{
List<T> intersecting = getIntersectingObjects(object, cls);
if (!intersecting.isEmpty()) {
return intersecting.get(0);
}
else {
return null;
}
}
public void paintDebug(Graphics g)
{
}
}
top,
use,
map,
class GridCollisionChecker
top,
use,
map,
class GridCollisionChecker . Cell
. add
. remove
. isEmpty
. getAll
top,
use,
map,
class GridCollisionChecker . Cell . GridWorld
. GridWorld
. get
. set
. getWidth
. getHeight
top,
use,
map,
class GridCollisionChecker . Cell . GridWorld . WrappingGridWorld
. WrappingGridWorld
. get
. set
top,
use,
map,
class GridCollisionChecker . Cell . GridWorld . Statistics
. incGetObjectsAt
. incGetIntersectingObjects
. incGetObjectsInRange
. incGetNeighbours
. incGetObjectsInDirection
. initStartTime
. toString
. headerString
. initialize
. compare
. addObject
. testBounds
. getObjectsAt
. getObjectsInRange
. distance
. removeObject
. getWidth
. getHeight
. updateObjectLocation
. updateObjectSize
. getIntersectingObjects
. getNeighbours
. getObjectsInDirection
. withinBounds
. wrap
. startSequence
. getObjects
. getObjectsList
. getOneObjectAt
. getOneIntersectingObject
. paintDebug
834 neLoCode
+ 78 LoComm