package greenfoot.collision.ibsp;

import greenfoot.Actor;
import greenfoot.ActorVisitor;
import greenfoot.collision.*;

import java.awt.Color;
import java.awt.Graphics;
import java.util.*;


| A collision checker using a Binary Space Partition tree. | | <p>Each node of the tree represents a rectangular area, and potentially has | two non-overlapping child nodes which together cover the same area as their | parent. | | @author Davin McCall | public class IBSPColChecker implements CollisionChecker{ public static final int X_AXIS = 0; public static final int Y_AXIS = 1; public static final int PARENT_LEFT = 0; public static final int PARENT_RIGHT = 1; public static final int PARENT_NONE = 3; public static final int REBALANCE_THRESHOLD = 20; private GOCollisionQuery actorQuery = new GOCollisionQuery(); private NeighbourCollisionQuery neighbourQuery = new NeighbourCollisionQuery(); private PointCollisionQuery pointQuery = new PointCollisionQuery(); private InRangeQuery inRangeQuery = new InRangeQuery(); private int cellSize; private BSPNode bspTree; public static boolean debugging = false;
| (non-Javadoc) | @see greenfoot.collision.CollisionChecker#initialize(int, int, int, boolean) | public void initialize(int width, int height, int cellSize, boolean wrap) { this.cellSize = cellSize; }
| | @see greenfoot.collision.CollisionChecker#addObject(greenfoot.Actor) | public void addObject(Actor actor) { Rect bounds = getActorBounds(actor); if (bspTree == null) { int splitAxis; int splitPos; if (bounds.getWidth() > bounds.getHeight()) { splitAxis = X_AXIS; splitPos = bounds.getMiddleX(); } else { splitAxis = Y_AXIS; splitPos = bounds.getMiddleY(); } bspTree = BSPNodeCache.getBSPNode(); bspTree.getArea().copyFrom(bounds); bspTree.setSplitAxis(splitAxis); bspTree.setSplitPos(splitPos); bspTree.addActor(actor); } else { Rect treeArea = bspTree.getArea(); while (! treeArea.contains(bounds)){ if (bounds.getX() < treeArea.getX()) { int bx = treeArea.getX() - treeArea.getWidth(); Rect newArea = new Rect(bx, treeArea.getY(), treeArea.getRight() - bx, treeArea.getHeight()); BSPNode newTop = BSPNodeCache.getBSPNode(); newTop.getArea().copyFrom(newArea); newTop.setSplitAxis(X_AXIS); newTop.setSplitPos(treeArea.getX()); newTop.setChild(PARENT_RIGHT, bspTree); bspTree = newTop; treeArea = newArea; } if (bounds.getRight() > treeArea.getRight()) { int bx = treeArea.getRight() + treeArea.getWidth(); Rect newArea = new Rect(treeArea.getX(), treeArea.getY(), bx - treeArea.getX(), treeArea.getHeight()); BSPNode newTop = BSPNodeCache.getBSPNode(); newTop.getArea().copyFrom(newArea); newTop.setSplitAxis(X_AXIS); newTop.setSplitPos(treeArea.getRight()); newTop.setChild(PARENT_LEFT, bspTree); bspTree = newTop; treeArea = newArea; } if (bounds.getY() < treeArea.getY()) { int by = treeArea.getY() - treeArea.getHeight(); Rect newArea = new Rect(treeArea.getX(), by, treeArea.getWidth(), treeArea.getTop() - by); BSPNode newTop = BSPNodeCache.getBSPNode(); newTop.getArea().copyFrom(newArea); newTop.setSplitAxis(Y_AXIS); newTop.setSplitPos(treeArea.getY()); newTop.setChild(PARENT_RIGHT, bspTree); bspTree = newTop; treeArea = newArea; } if (bounds.getTop() > treeArea.getTop()) { int by = treeArea.getTop() + treeArea.getHeight(); Rect newArea = new Rect(treeArea.getX(), treeArea.getY(), treeArea.getWidth(), by - treeArea.getY()); BSPNode newTop = BSPNodeCache.getBSPNode(); newTop.getArea().copyFrom(newArea); newTop.setSplitAxis(Y_AXIS); newTop.setSplitPos(treeArea.getTop()); newTop.setChild(PARENT_LEFT, bspTree); bspTree = newTop; treeArea = newArea; } } insertObject(actor, bounds, bounds, treeArea, bspTree); } }
| Check the consistency of the tree, useful for debugging. | public void checkConsistency(boolean checkActorBounds) { if (! debugging) { return; } LinkedList<BSPNode> stack = new LinkedList<BSPNode>(); stack.add(bspTree); while (! stack.isEmpty()) { BSPNode node = stack.removeLast(); if (node != null) { Rect nodeArea = node.getArea(); List<Actor> actors = node.getActorsList(); for (Actor actor : actors) { Rect actorBounds = getActorBounds(actor); if (checkActorBounds && ! nodeArea.intersects(actorBounds)) { throw new IllegalStateException("Actor not contained within region bounds?"); } } if (node.getLeft() != null) { Rect leftArea = node.getLeft().getArea(); if (! Rect.equals(leftArea, node.getLeftArea())) { throw new IllegalStateException("Areas wrong!"); } } if (node.getRight() != null) { Rect rightArea = node.getRight().getArea(); if (! Rect.equals(rightArea, node.getRightArea())) { throw new IllegalStateException("Areas wrong!"); } } stack.add(node.getLeft()); stack.add(node.getRight()); } } }
| Insert an actor into the tree at the given position | | @param actor The actor to insert | @param actorBounds The total bounds of the actor | @param bounds The bounds of the actor (limited to the present area) | @param area The total area represented by the current search node | @param node The current search node (null, if the search has reached its end!) | private void insertObject(Actor actor, Rect actorBounds, Rect bounds, Rect area, BSPNode node) { if (node.containsActor(actor)) { return; } if (node.isEmpty() || (area.getWidth() <= actorBounds.getWidth() && area.getHeight() <= actorBounds.getHeight())) { node.addActor(actor); return; } Rect leftArea = node.getLeftArea(); Rect rightArea = node.getRightArea(); Rect leftIntersects = Rect.getIntersection(leftArea, bounds); Rect rightIntersects = Rect.getIntersection(rightArea, bounds); if (leftIntersects != null) { if (node.getLeft() == null) { BSPNode newLeft = createNewNode(leftArea); newLeft.addActor(actor); node.setChild(PARENT_LEFT, newLeft); } else { insertObject(actor, actorBounds, leftIntersects, leftArea, node.getLeft()); } } if (rightIntersects != null) { if (node.getRight() == null) { BSPNode newRight = createNewNode(rightArea); newRight.addActor(actor); node.setChild(PARENT_RIGHT, newRight); } else { insertObject(actor, actorBounds, rightIntersects, rightArea, node.getRight()); } } }
| Create a new node for the given area. | private BSPNode createNewNode(Rect area) { int splitAxis, splitPos; if (area.getWidth() > area.getHeight()) { splitAxis = X_AXIS; splitPos = area.getMiddleX(); } else { splitAxis = Y_AXIS; splitPos = area.getMiddleY(); } BSPNode newNode = BSPNodeCache.getBSPNode(); newNode.setArea(area); newNode.setSplitAxis(splitAxis); newNode.setSplitPos(splitPos); return newNode; } public final Rect getActorBounds(Actor actor) { Rect r = ActorVisitor.getBoundingRect(actor); return r; } public static void printTree(BSPNode node, String indent, String lead) { if (node == null) { return; } String xx = lead; xx += node + ": "; xx += node.getArea(); println(xx); BSPNode left = node.getLeft(); BSPNode right = node.getRight(); if (left != null) { String newIndent; if (right != null) { newIndent = indent + " |"; } else { newIndent = indent + " "; } printTree(left, newIndent, indent + " \\L-"); } if (right != null) { printTree(node.getRight(), indent + " ", indent + " \\R-"); } } public void printTree() { printTree(bspTree, "", ""); } public void removeObject(Actor object) { ActorNode node = getNodeForActor(object); while (node != null){ BSPNode bspNode = node.getBSPNode(); node.remove(); checkRemoveNode(bspNode); node = getNodeForActor(object); } }
| Check whether a node can be removed, and remove it if so, traversing up the | tree and so on. Returns the highest node which wasn't removed. | private BSPNode checkRemoveNode(BSPNode node) { while (node != null && node.isEmpty()){ BSPNode parent = node.getParent(); int side = (parent != null) ? parent.getChildSide(node) : PARENT_NONE; BSPNode left = node.getLeft(); BSPNode right = node.getRight(); if (left == null) { if (parent != null) { if (right != null) { right.getArea().copyFrom(node.getArea()); right.areaChanged(); } parent.setChild(side, right); } else { bspTree = right; if (right != null) { right.setParent(null); } } node.setChild(PARENT_RIGHT, null); BSPNodeCache.returnNode(node); node = parent; } else if (right == null) { if (parent != null) { if (left != null) { left.getArea().copyFrom(node.getArea()); left.areaChanged(); } parent.setChild(side, left); } else { bspTree = left; if (left != null) { left.setParent(null); } } node.setChild(PARENT_LEFT, null); BSPNodeCache.returnNode(node); node = parent; } else { break; } } return node; } private static int dbgCounter = 0; private static void println(String s) { if (dbgCounter < 3000) { System.out.println(s); } } public static ActorNode getNodeForActor(Actor object) { return (ActorNode) ActorVisitor.getData(object); } public static void setNodeForActor(Actor object, ActorNode node) { ActorVisitor.setData(object, node); }
| An actors position or size has changed - update the tree. | private void updateObject(Actor object) { ActorNode node = getNodeForActor(object); if (node == null) { return; } Rect newBounds = getActorBounds(object); if (! bspTree.getArea().contains(newBounds)) { while (node != null){ BSPNode rNode = node.getBSPNode(); node.remove(); checkRemoveNode(rNode); node = node.getNext(); } addObject(object); return; } while (node != null){ BSPNode bspNode = node.getBSPNode(); Rect bspArea = bspNode.getArea(); if (bspArea.contains(newBounds)) { ActorNode iter = getNodeForActor(object); while (iter != null){ if (iter != node) { BSPNode rNode = iter.getBSPNode(); iter.remove(); checkRemoveNode(rNode); } iter = iter.getNext(); } return; } else if (! bspArea.intersects(newBounds)) { BSPNode rNode = node.getBSPNode(); node.remove(); checkRemoveNode(rNode); if (bspTree == null) { addObject(object); return; } } node.clearMark(); node = node.getNext(); } node = getNodeForActor(object); BSPNode bspNode; Rect bspArea; if (node != null) { bspNode = node.getBSPNode(); while (bspNode != null && ! bspNode.getArea().contains(newBounds)){ bspNode = bspNode.getParent(); } if (bspNode == null) { while (node != null){ bspNode = node.getBSPNode(); node.remove(); checkRemoveNode(bspNode); node = node.getNext(); } addObject(object); return; } } else { bspNode = bspTree; } bspArea = bspNode.getArea(); insertObject(object, newBounds, newBounds, bspArea, bspNode); node = getNodeForActor(object); while (node != null){ if (! node.checkMark()) { bspNode = node.getBSPNode(); node.remove(); checkRemoveNode(bspNode); } node = node.getNext(); } } public void updateObjectLocation(Actor object, int oldX, int oldY) { updateObject(object); } public void updateObjectSize(Actor object) { updateObject(object); } private List getIntersectingObjects(Rect r, CollisionQuery query) { Set<Actor> set = new HashSet<Actor>(); getIntersectingObjects(r, query, set, bspTree); List<Actor> l = new ArrayList<Actor>(set); return l; } private void getIntersectingObjects(Rect r, CollisionQuery query, Set<Actor> resultSet, BSPNode startNode) { LinkedList<BSPNode> nodeStack = new LinkedList<BSPNode>(); if (startNode != null) { nodeStack.add(startNode); } while (! nodeStack.isEmpty()){ BSPNode node = nodeStack.removeLast(); if (node.getArea().intersects(r)) { Iterator<Actor> i = node.getActorsIterator(); while (i.hasNext()){ Actor actor = i.next(); if (query.checkCollision(actor)) { if (! resultSet.contains(actor)) { resultSet.add(actor); } } } BSPNode left = node.getLeft(); BSPNode right = node.getRight(); if (left != null) { nodeStack.add(left); } if (right != null) { nodeStack.add(right); } } } }
| Check if there is at least one actor in the given BSPNode which matches | the given collision query, and return it if so. | private Actor checkForOneCollision(Actor ignore, BSPNode node, CollisionQuery query) { Iterator<Actor> i = node.getActorsIterator(); while (i.hasNext()){ Actor candidate = i.next(); if (ignore != candidate && query.checkCollision(candidate)) { return candidate; } } return null; }
| Search for a single object which matches the given collision | query, starting from the given tree node and searching only | down the tree. | | @param ignore - do not return this actor | @param r Bounds - do not search nodes which don't intersect this | @param query The query to check objects against | @param startNode The node to begin the search from | @return The actor found, or null | private Actor getOneObjectDownTree(Actor ignore, Rect r, CollisionQuery query, BSPNode startNode) { if (startNode == null) { return null; } LinkedList<BSPNode> nodeStack = new LinkedList<BSPNode>(); nodeStack.add(startNode); while (! nodeStack.isEmpty()){ BSPNode node = nodeStack.removeLast(); if (node.getArea().intersects(r)) { Actor res = checkForOneCollision(ignore, node, query); if (res != null) { return res; } BSPNode left = node.getLeft(); BSPNode right = node.getRight(); if (left != null) { nodeStack.add(left); } if (right != null) { nodeStack.add(right); } } } return null; }
| Search down the tree, but only so far as the last node which fully contains the area. | @param r | @param query | @param actor | @return | private Actor getOneIntersectingDown(Rect r, CollisionQuery query, Actor actor) { if (bspTree == null) { return null; } LinkedList<BSPNode> nodeStack = new LinkedList<BSPNode>(); nodeStack.add(bspTree); while (! nodeStack.isEmpty()){ BSPNode node = nodeStack.removeLast(); if (node.getArea().contains(r)) { Actor res = checkForOneCollision(actor, node, query); if (res != null) { return res; } BSPNode left = node.getLeft(); BSPNode right = node.getRight(); if (left != null) { nodeStack.add(left); } if (right != null) { nodeStack.add(right); } } } return null; }
| Search up the tree, up to (not including) the node which fully contains the area. | @param r | @param query | @param actor | @param start | public Actor getOneIntersectingUp(Rect r, CollisionQuery query, Actor actor, BSPNode start) { while (start != null && ! start.getArea().contains(r)){ Actor res = checkForOneCollision(actor, start, query); if (res != null) { return res; } start = start.getParent(); } return null; } @SuppressWarnings("unchecked") public <T extends Actor> List getObjectsAt(int x, int y, Class<T> cls) { synchronized (pointQuery) { int px = x * cellSize + cellSize / 2; int py = y * cellSize + cellSize / 2; pointQuery.init(px, py, cls); return (List<T>) getIntersectingObjects(new Rect(px, py, 1, 1), pointQuery); } } @SuppressWarnings("unchecked") public <T extends Actor> List getIntersectingObjects(Actor actor, Class<T> cls) { Rect r = getActorBounds(actor); synchronized (actorQuery) { actorQuery.init(cls, actor); return (List<T>) getIntersectingObjects(r, actorQuery); } } @SuppressWarnings("unchecked") public <T extends Actor> List getObjectsInRange(int x, int y, int r, Class<T> cls) { int halfCell = cellSize / 2; int size = 2 * r * cellSize; Rect rect = new Rect((x - r) * cellSize + halfCell, (y - r) * cellSize + halfCell, size, size); List<T> result; synchronized (actorQuery) { actorQuery.init(cls, null); result = (List<T>) getIntersectingObjects(rect, actorQuery); } Iterator<T> i = result.iterator(); synchronized (inRangeQuery) { inRangeQuery.init(x * cellSize + halfCell , y * cellSize + halfCell, r * cellSize); while (i.hasNext()){ if (! inRangeQuery.checkCollision(i.next())) { i.remove(); } } } return result; } @SuppressWarnings("unchecked") 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); int xPixel = x * cellSize; int yPixel = y * cellSize; int dPixel = distance * cellSize; Rect r = new Rect(xPixel - dPixel, yPixel - dPixel, dPixel * 2 + 1, dPixel * 2 + 1); synchronized (neighbourQuery) { neighbourQuery.init(x, y, distance, diag, cls); List<T> res = (List<T>) getIntersectingObjects(r, neighbourQuery); return res; } } public <T extends Actor> List getObjectsInDirection(int x, int y, int angle, int length, Class<T> cls) { return new ArrayList<T>(); } @SuppressWarnings("unchecked") public <T extends Actor> List getObjects(Class<T> cls) { Set<T> set = new HashSet<T>(); LinkedList<BSPNode> nodeStack = new LinkedList<BSPNode>(); if (bspTree != null) { nodeStack.add(bspTree); } while (! nodeStack.isEmpty()){ BSPNode node = nodeStack.removeLast(); Iterator<Actor> i = node.getActorsIterator(); while (i.hasNext()){ Actor actor = i.next(); if (cls == null || cls.isInstance(actor)) { set.add((T) actor); } } BSPNode left = node.getLeft(); BSPNode right = node.getRight(); if (left != null) { nodeStack.add(left); } if (right != null) { nodeStack.add(right); } } List<T> list = new ArrayList<T>(set); return list; } public List getObjectsList() { return getObjects(null); } public final void startSequence() { } @SuppressWarnings("unchecked") public <T extends Actor> T getOneObjectAt(Actor object, int dx, int dy, Class<T> cls) { synchronized (pointQuery) { int px = dx * cellSize + cellSize / 2; int py = dy * cellSize + cellSize / 2; pointQuery.init(px, py, cls); CollisionQuery query = pointQuery; if (cls != null) { query = new ClassQuery(cls, pointQuery); } return (T) getOneIntersectingDown(new Rect(px, py, 1, 1), query, object); } } @SuppressWarnings("unchecked") public <T extends Actor> T getOneIntersectingObject(Actor actor, Class<T> cls) { Rect r = getActorBounds(actor); synchronized (actorQuery) { actorQuery.init(cls, actor); ActorNode node = getNodeForActor(actor); do { BSPNode bspNode = node.getBSPNode(); T ret = (T) getOneObjectDownTree(actor, r, actorQuery, bspNode); if (ret != null) { return ret; } ret = (T) getOneIntersectingUp(r, actorQuery, actor, bspNode.getParent()); if (ret != null) { return ret; } node = node.getNext(); } while (node != null){; return (T) getOneIntersectingDown(r, actorQuery, actor); } } } public void paintDebug(Graphics g) { LinkedList<BSPNode> nodeStack = new LinkedList<BSPNode>(); nodeStack.add(bspTree); Color oldColor = g.getColor(); g.setColor(Color.RED); while (! nodeStack.isEmpty()){ BSPNode node = nodeStack.removeLast(); if (node != null) { Rect area = node.getArea(); g.drawRect(area.getX(), area.getY(), area.getWidth(), area.getHeight()); nodeStack.add(node.getLeft()); nodeStack.add(node.getRight()); } } g.setColor(oldColor); } }
top, use, map, class IBSPColChecker

.   initialize
.   addObject
.   checkConsistency
.   insertObject
.   createNewNode
.   getActorBounds
.   printTree
.   printTree
.   removeObject
.   checkRemoveNode
.   println
.   getNodeForActor
.   setNodeForActor
.   updateObject
.   updateObjectLocation
.   updateObjectSize
.   getIntersectingObjects
.   getIntersectingObjects
.   checkForOneCollision
.   getOneObjectDownTree
.   getOneIntersectingDown
.   getOneIntersectingUp
.   getObjectsAt
.   getIntersectingObjects
.   getObjectsInRange
.   getNeighbours
.   getObjectsInDirection
.   getObjects
.   getObjectsList
.   startSequence
.   getOneObjectAt
.   getOneIntersectingObject
.   paintDebug




1075 neLoCode + 40 LoComm