package greenfoot.collision;

import greenfoot.Actor;
import greenfoot.ActorVisitor;
import greenfoot.util.Circle;

import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.PriorityQueue;


| This collision checker is based on a Bounding Volume Hierarchy formed by | circles. The tree is build by insertion. | | | Some of the good properties of this: | <ul> | <li>On-line, which means it is cheap to insert and remove objects.</li> | <li>Good for many kinds of object distributions.</li> | <li>Moderate tree construction time</li> | <li> </li> | </ul> | | Some of the bad properties of this: | <ul> | <li>Only decent for strictly cell based scenarios.</li> | </ul> | | | This implementation is based on the Balltree On-line Insertion algorithm | described in: <a | href="http://www.icsi.berkeley.edu/ftp/global/pub/techreports/1989/tr-89-063.pdf">Five |* Balltree Construction Algorithms by Stephen M. Omohundro</a> * * @author Poul Henriksen * */ public class BVHInsChecker implements CollisionChecker{ class Node { public Node parent; | |public Node left; // child | |public Node right; // child | |public Circle circle; | |private Actor actor; | |public Node(Circle circle) | |{ | |this.circle = circle; | |} | |public Node() | |{ | |circle = new Circle(); | |} | |public Node(Circle circle, Actor actor) | |{ | |if (actor == null) { | |throw new NullPointerException("Actor may not be null."); } this.circle = circle; this.actor = actor; } public Actor getActor() { return actor; | |} | |public boolean isLeaf() | |{ | |return (left == null && right == null); | |} | |/** | Get collisions with the circle b and then use the specific collision | checker for the Actors to make low-level collision check. | | @param c | Circle to find intersections with | @param checker | Optional collision checker. If null it will just find | collisions based on the circle. | @param result | List to put the result in. | public void getIntersections(Circle c, CollisionQuery checker, List<Actor> result) { if (!c.intersects(this.circle)) { return; } if (isLeaf() && (checker != null && checker.checkCollision(getActor()))) { result.add(getActor()); } else if (!isLeaf()) { left.getIntersections(c, checker, result); right.getIntersections(c, checker, result); } } private Actor getOneIntersectingObject(Circle c, CollisionQuery checker) { return getOneIntersectingObjectUpwards(c, checker); } private Actor getOneIntersectingObjectDownwards(Circle c, CollisionQuery checker) { if (!c.intersects(this.circle)) { return null; } if (isLeaf() && (checker != null && checker.checkCollision(getActor()))) { return getActor(); } else if (!isLeaf()) { Actor res = left.getOneIntersectingObjectDownwards(c, checker); if (res != null) { return res; } else { return right.getOneIntersectingObjectDownwards(c, checker); } } return null; }
| Searches for intersections with the circle c, starting from this node and going upwards in the tree. | @param c Circle to check collision against | @param checker Query to do fine grained checks | @return | private Actor getOneIntersectingObjectUpwards(Circle c, CollisionQuery checker) { Node sibling = getSibling(); Actor result = null; if (sibling != null) { result = sibling.getOneIntersectingObjectDownwards(sibling.circle, checker); } if (result == null && parent != null) { return parent.getOneIntersectingObjectUpwards(c, checker); } else if (result != null) { return result; } return null; }
| Removes this node from the tree it is in by clearing all pointers to | and from this node. Resets all fields in this node and any pointers | from parent or children. The node will have the same state as when it | was created. | public void reset() { if (left != null && left.parent == this) { left.parent = null; } if (right != null && right.parent == this) { right.parent = null; } if (parent != null) { if (parent.left == this) { parent.left = null; } else if (parent.right == this) { parent.right = null; } } parent = null; left = null; right = null; } private Node getSibling() { if (parent != null) { if (parent.left == this) { return parent.right; } else { return parent.left; } } return null; } }
| A circle fringe represents extra information calculated about a node in a | tree. In particular the "ancestor expansion" which is the total increase |* in the size of the ancestor when insert a new node as a sibling to this * node. * * @author Poul Henriksen * */ static class CircleFringe implements Comparable<CircleFringe> | |{ | |/** The sibling node for this circle fring e private Node node;
| Total expansion of all the ancestors of node | private double ancestorExpansion;
| Volume of the new parent for the sibling node and the new node | private double volume; public CircleFringe(Node n, double ancestorExpansion, double volume) { this.ancestorExpansion = ancestorExpansion; this.volume = volume; this.node = n; }
| Will create a new fringe with the same contents as the other fringe. | | @param other | public CircleFringe(CircleFringe other) { copyValuesFrom(other); } public void copyValuesFrom(CircleFringe other) { node = other.getNode(); ancestorExpansion = other.getAncestorExpansion(); volume = other.getVolume(); }
| Get total increase in the size of the ancestor when insert a new node | as a sibling to this node. | public double getAncestorExpansion() { return ancestorExpansion; }
| Set total increase in the size of the ancestor when insert a new node | as a sibling to this node. | public void setAncestorExpansion(double ancestorExpansion) { this.ancestorExpansion = ancestorExpansion; } public int compareTo(CircleFringe other) { return (int) (this.ancestorExpansion - other.getAncestorExpansion()); }
| The node that this fringe relates to. | public Node getNode() { return node; }
| The node that this fringe relates to. | public void setNode(Node node) { this.node = node; }
| Set the volume of this fringe. That is, the size of the new parent | from inserting the new node as a sibling to this node. | public void setVolume(double volume) { this.volume = volume; }
| Get the volume of this fringe. That is, the size of the new parent | from inserting the new node as a sibling to this node. | public double getVolume() { return volume; }
| Return the cost of this fringe. That is: volume + ancestor expansion | @return | public double getCost() { return ancestorExpansion + volume; } }
| Tree of circles. The leaf nodes contains the objects. Each objects has a | bounding circle. The parent of the two nodes has a bounding circle which | is the bounding circle of the two child circles. | | @author Poul Henriksen | class CircleTree { private Node root; private int size; private Node lastInsertionPoint; public void addNode(Node n, Node bestGuess) { Node sibling = bestSibling(n, bestGuess); insertAtNode(n, sibling); } public void addNode(Node n) { if (! contains(lastInsertionPoint)) { lastInsertionPoint = null; } Node sibling = bestSibling(n, lastInsertionPoint); insertAtNode(n, sibling); lastInsertionPoint = n.getSibling(); } private boolean contains(Node n) { if (n == null) { return false; } if (root == n || n.parent != null) { return true; } return false; }
| Find the best place to insert the new node. | public Node bestSibling(Node newNode, Node bestGuess) { if (getRoot() == null) { return null; } if (root.isLeaf()) { return root; } CircleFringe rootFringe = createFringe(newNode, root); final CircleFringe best = new CircleFringe(rootFringe); if (bestGuess != null) { CircleFringe newFringe = createFringe(newNode, bestGuess); if (newFringe.getCost() < best.getCost()) { best.copyValuesFrom(newFringe); } } PriorityQueue<CircleFringe> fringeQueue = new PriorityQueue<CircleFringe>(); fringeQueue.add(rootFringe); bestSiblingSearch(newNode, best, fringeQueue); return best.getNode(); }
| Searches through the tree for the best sibling. | private void bestSiblingSearch(Node newNode, final CircleFringe best, PriorityQueue<CircleFringe> fringeQueue) { while (!fringeQueue.isEmpty()){ CircleFringe tf = fringeQueue.poll(); if (tf.getAncestorExpansion() >= (best.getCost())) { break; } else { double newAExp = tf.getCost() - tf.getNode().circle.getVolume(); processNode(newNode, tf.getNode().left, newAExp, best, fringeQueue); processNode(newNode, tf.getNode().right, newAExp, best, fringeQueue); } } }
| Creates the fringe for newNode at currentNode. | | TODO: Find the best fringe from the currentNode and up to the root instead of only the fringe at the current node. | @param newNode The new node that is to be inserted | @param currentNode The node for which to create the fringe | @return | private CircleFringe createFringe(Node newNode, Node currentNode) { Circle bestCircle; double bestCost; bestCircle = new Circle(); bestCircle.merge(currentNode.circle, newNode.circle); bestCost = bestCircle.getVolume(); double ae = 0; Node n = currentNode; while (n.parent != null){ Circle enclosingCircle = new Circle(); enclosingCircle.merge(n.circle, newNode.circle); double delta = enclosingCircle.getVolume() - n.parent.circle.getVolume(); if (delta == 0) { break; } ae += delta; n = n.parent; } CircleFringe newFringe = new CircleFringe(currentNode, ae, bestCost); return newFringe; }
| Looks at a node and checks if it is better than the currently best | result. It also creates a new fringe and inserts it into the queue. | private void processNode(Node newNode, Node childNode, double newAExp, final CircleFringe best, PriorityQueue<CircleFringe> fringeQueue) { Circle enclosingCircle = new Circle(); enclosingCircle.merge(childNode.circle, newNode.circle); double enclosingVolume = enclosingCircle.getVolume(); if ( (newAExp + enclosingVolume) < best.getCost()) { best.setVolume(enclosingVolume); best.setAncestorExpansion(enclosingVolume); best.setNode(childNode); } if (!childNode.isLeaf()) { CircleFringe newFringe = new CircleFringe(childNode, newAExp, enclosingVolume); fringeQueue.add(newFringe); } }
| Insert new node as a sibling of a node in the tree. | | @param newNode | The new node | @param sibling | A node already in the tree the new node will be a sibling | of. | public void insertAtNode(Node newNode, Node sibling) { if (getRoot() == null) { setRoot(newNode); } else { Node newParent = new Node(); newParent.parent = sibling.parent; if (sibling.parent == null) { setRoot(newParent); } else if (sibling.parent.left == sibling) { sibling.parent.left = newParent; } else { sibling.parent.right = newParent; } newParent.left = sibling; newParent.right = newNode; newNode.parent = newParent; sibling.parent = newParent; newParent.circle.merge(newNode.circle, sibling.circle); repairParents(newParent); } size++; }
| Adjust the parents bounding volumes to fit with this node. | | @param newParent | private void repairParents(Node newParent) { Node p = newParent.parent; while (p != null){ int radius = p.circle.getRadius(); p.circle.merge(p.left.circle, p.right.circle); if (p.circle.getRadius() == radius) { break; } p = p.parent; } }
| Used when a node has moved or changed size. | public void repairNode(Node n) { if (n == null) return; Node sibling = removeNode(n); addNode(n, sibling); }
| @return The sibling from which it was removed. | public Node removeNode(Node n) { Node sibling = null; if (n == null) { return null; } else if (n == root) { setRoot(null); } else { sibling = n.getSibling(); Node parent = n.parent; sibling.parent = parent.parent; if (parent.parent == null) { setRoot(sibling); } else if (parent.parent.left == parent) { parent.parent.left = sibling; } else { parent.parent.right = sibling; } parent.reset(); repairParents(sibling); } n.reset(); size--; return sibling; } public List getIntersections(Circle b, CollisionQuery c) { List<Actor> result = new ArrayList<Actor>(); if (getRoot() == null) { return result; } getRoot().getIntersections(b, c, result); return result; } public Actor getOneIntersectingObject(Node node, Circle circle, CollisionQuery checker) { if (node != null) { return node.getOneIntersectingObject(circle, checker); } else if (root != null) { return root.getOneIntersectingObject(circle, checker); } else { return null; } } public Actor getOneIntersectingObject(Node node, CollisionQuery checker) { return node.getOneIntersectingObject(node.circle, checker); } public void paintDebug(Graphics g) { if (getRoot() != null) { paintNode(getRoot(), g); } } private void paintNode(Node n, Graphics g) { paintCircle(n.circle, g); if (n.left != null) { g.setColor(Color.BLUE); paintLine(n, n.left, g); paintNode(n.left, g); } if (n.right != null) { g.setColor(Color.GREEN); paintLine(n, n.right, g); paintNode(n.right, g); } } private void paintLine(Node from, Node to, Graphics g) { if (from == null || to == null) { return; } g.drawLine(from.circle.getX(), from.circle.getY(), to.circle.getX(), to.circle.getY()); } private void paintCircle(Circle b, Graphics g) { if (b != null) { g.setColor(Color.RED); g.drawOval(b.getX() - b.getRadius(), b.getY() - b.getRadius(), b.getRadius() * 2, b.getRadius() * 2); } } public int size() { return size; } public void setRoot(Node root) { this.root = root; } public Node getRoot() { return root; } } public CircleTree tree; private GOCollisionQuery actorQuery = new GOCollisionQuery(); private NeighbourCollisionQuery neighbourQuery = new NeighbourCollisionQuery(); private PointCollisionQuery pointQuery = new PointCollisionQuery(); private int cellSize; private List<Actor> objects; public void initialize(int width, int height, int cellSize, boolean wrap) { tree = new CircleTree(); this.cellSize = cellSize; objects = new ArrayList<Actor>(); } public synchronized void addObject(Actor actor) { if (objects.contains(actor)) { return; } Node n = createNode(actor); ActorVisitor.setData(actor, n); tree.addNode(n); objects.add(actor); } private Node createNode(Actor actor) { Circle c = getCircle(actor); Node n = new Node(c, actor); return n; } public synchronized void removeObject(Actor object) { tree.removeNode((Node) ActorVisitor.getData(object)); ActorVisitor.setData(object, null); objects.remove(object); } public synchronized void updateObjectLocation(Actor object, int oldX, int oldY) { int ax = ActorVisitor.getX(object); int ay = ActorVisitor.getY(object); if (ax == oldX && ay == oldY) { return; } Node n = (Node) ActorVisitor.getData(object); Circle c = getCircle(object); if (c != null && n != null) { n.circle.setX(c.getX()); n.circle.setY(c.getY()); tree.repairNode(n); } } public synchronized void updateObjectSize(Actor object) { throw new RuntimeException("No longer working because of missing bounding circle");
/*Circle c = ActorVisitor.getBoundingCircle(object); | |if (c != null && n != null) {}n.circle.setRadius(c.getRadius() * cellSize); | |tree.repairNode(n); | |} } @SuppressWarnings("unchecked") public <T extends Actor> List getObjectsAt(int x, int y, Class<T> cls) { int halfCell = cellSize / 2; Circle b = new Circle(x * cellSize + halfCell, y * cellSize + halfCell, 0); synchronized (pointQuery) { pointQuery.init(x, y, cls); return (List<T>) tree.getIntersections(b, pointQuery); } } @SuppressWarnings("unchecked") public <T extends Actor> List getIntersectingObjects(Actor actor, Class<T> cls) { Circle b = getCircle(actor); synchronized (actorQuery) { actorQuery.init(cls, actor); return (List<T>) tree.getIntersections(b, actorQuery); } } private Circle getCircle(Actor actor) { throw new RuntimeException("No longer working because of missing bounding circle");
| Circle c = ActorVisitor.getBoundingCircle(actor); | |if (c == null) {} return null; | |} | |Circle b = new Circle(c.getX() * cellSize, c.getY() * cellSize, c.getRadius() * cellSize); | |return b; } @SuppressWarnings("unchecked") public <T extends Actor> List getObjectsInRange(int x, int y, int r, Class<T> cls) { Circle b = new Circle(x * cellSize, y * cellSize, r * cellSize); synchronized (actorQuery) { actorQuery.init(cls, null); return (List<T>) tree.getIntersections(b, actorQuery); } } @SuppressWarnings("unchecked") public <T extends Actor> List getNeighbours(Actor a, int distance, boolean diag, Class<T> cls) { int x = ActorVisitor.getX(a); int y = ActorVisitor.getY(a); int xPixel = x * cellSize; int yPixel = y * cellSize; int dPixel = distance * cellSize; int r = 0; if (diag) { r = (int) Math.ceil(Math.sqrt(dPixel * dPixel + dPixel * dPixel)); } else { double dy = 0.5 * cellSize; double dx = dPixel + dy; r = (int) Math.sqrt(dy * dy + dx * dx); } Circle c = new Circle(xPixel, yPixel, r); synchronized (neighbourQuery) { neighbourQuery.init(x, y, distance, diag, cls); return (List<T>) tree.getIntersections(c, neighbourQuery); } } public <T extends Actor> List getObjectsInDirection(int x, int y, int angle, int length, Class<T> cls) { throw new RuntimeException("NOT IMPLEMENTED YET"); } @SuppressWarnings("unchecked") public <T extends Actor> List getObjects(Class<T> cls) { if (cls == null) { return (List<T>) new ArrayList<Actor>(objects); } List<T> l = new ArrayList<T>(); for (Iterator<Actor> iter = objects.iterator(); iter.hasNext();) { Actor actor = iter.next(); if (cls.isInstance(actor)) { l.add((T) actor); } } return l; } public List getObjectsList() { return objects; } public void startSequence() { } @SuppressWarnings("unchecked") public <T extends Actor> T getOneObjectAt(Actor actor, int x, int y, Class<T> cls) { int halfCell = cellSize / 2; Circle b = new Circle(x * cellSize + halfCell, y * cellSize + halfCell, 0); synchronized (pointQuery) { pointQuery.init(x, y, cls); Node node = (Node) ActorVisitor.getData(actor); return (T) tree.getOneIntersectingObject(node, b, pointQuery); } } @SuppressWarnings("unchecked") public <T extends Actor> T getOneIntersectingObject(Actor object, Class<T> cls) { synchronized (actorQuery) { actorQuery.init(cls, object); Node node = (Node) ActorVisitor.getData(object); if (node == null) { return null; } return (T) tree.getOneIntersectingObject(node, actorQuery); } }
| Paint bounding boxes used in the circletree. | public void paintDebug(Graphics g) { int missing = 0; synchronized (this) { missing = (objects.size() - tree.size()); tree.paintDebug(g); } if (missing > 0) { System.out.println("Objects missing: " + missing); } } }

.   getIntersections
.   getOneIntersectingObject
.   getOneIntersectingObjectDownwards
.   getOneIntersectingObjectUpwards
.   reset
.   getSibling
.   CircleFringe
.   CircleFringe
.   copyValuesFrom
.   getAncestorExpansion
.   setAncestorExpansion
.   compareTo
.   getNode
.   setNode
.   setVolume
.   getVolume
.   getCost
.   - CircleTree
.   addNode
.   addNode
.   contains
.   bestSibling
.   bestSiblingSearch
.   createFringe
.   processNode
.   insertAtNode
.   repairParents
.   repairNode
.   removeNode
.   getIntersections
.   getOneIntersectingObject
.   getOneIntersectingObject
.   paintDebug
.   paintNode
.   paintLine
.   paintCircle
.   size
.   setRoot
.   getRoot
.   initialize
.   addObject
.   createNode
.   removeObject
.   updateObjectLocation
.   updateObjectSize
.   getObjectsAt
.   getIntersectingObjects
.   getCircle
.   getObjectsInRange
.   getNeighbours
.   getObjectsInDirection
.   getObjects
.   getObjectsList
.   startSequence
.   getOneObjectAt
.   getOneIntersectingObject
.   paintDebug




926 neLoCode + 110 LoComm