/**
*
* Copyright 2008 - 2011
*
* 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.
*
* @project loon
* @author cping
* @email:javachenpeng@yahoo.com
* @version 0.1
*/
package loon.component;
import java.util.Iterator;
import loon.LSystem;
import loon.geom.RectBox;
import loon.utils.MathUtils;
import loon.utils.ObjectSet;
import loon.utils.SortedList;
import loon.utils.TArray;
public class BSPCollisionChecker implements CollisionChecker {
private final static int MAX_SIZE = 2048;
private final static BSPCollisionNode[] cache = new BSPCollisionNode[MAX_SIZE];
private int tail = 0;
private int size = 0;
public final static ActorNode getNodeForActor(Actor object) {
return (ActorNode) object.data;
}
public final static void setNodeForActor(Actor object, ActorNode node) {
object.data = node;
}
private synchronized final BSPCollisionNode getBSPNode() {
if (size == 0) {
return new BSPCollisionNode(new RectBox(), 0, 0);
} else {
int ppos = tail - size;
if (ppos < 0) {
ppos += MAX_SIZE;
}
BSPCollisionNode node = cache[ppos];
node.setParent((BSPCollisionNode) null);
--size;
return node;
}
}
private synchronized final void returnNode(BSPCollisionNode node) {
cache[tail++] = node;
if (tail == MAX_SIZE) {
tail = 0;
}
size = MathUtils.min(size + 1, MAX_SIZE);
if (node.getLeft() != null || node.getRight() != null) {
throw LSystem.runThrow("Size Error !");
}
}
public void startLoop() {
}
public void endLoop() {
}
private final CollisionBaseQuery actorQuery = new CollisionBaseQuery();
private final CollisionNeighbourQuery neighbourQuery = new CollisionNeighbourQuery();
private final CollisionPointQuery pointQuery = new CollisionPointQuery();
private final CollisionInRangeQuery inRangeQuery = new CollisionInRangeQuery();
private int cellSize;
private BSPCollisionNode bspTree;
private ObjectSet<Actor> cacheSet = new ObjectSet<Actor>();
private SortedList<BSPCollisionNode> cacheNodeStack = new SortedList<BSPCollisionNode>();
@Override
public void initialize(int cellSize) {
this.cellSize = cellSize;
}
@Override
public Iterator<Actor> getActorsIterator() {
if (bspTree != null) {
return bspTree.getActorsIterator();
}
return null;
}
@Override
public TArray<Actor> getActorsList() {
if (bspTree != null) {
return bspTree.getActorsList();
}
return null;
}
@Override
public synchronized void addObject(Actor actor) {
RectBox bounds = this.getActorBounds(actor);
float by;
if (this.bspTree == null) {
byte treeArea;
if (bounds.width > bounds.height) {
treeArea = 0;
by = bounds.getMiddleX();
} else {
treeArea = 1;
by = bounds.getMiddleY();
}
this.bspTree = getBSPNode();
this.bspTree.getArea().copy(bounds);
this.bspTree.setSplitAxis(treeArea);
this.bspTree.setSplitPos(by);
this.bspTree.addActor(actor);
} else {
int idx = 0;
RectBox treeArea1 = this.bspTree.getArea();
RectBox result1 = new RectBox();
RectBox result2 = new RectBox();
for (; !treeArea1.contains(bounds) && idx < MAX_SIZE;) {
RectBox newArea;
BSPCollisionNode newTop;
if (bounds.getX() < treeArea1.getX()) {
by = (treeArea1.getX() - treeArea1.width);
newArea = new RectBox(by, treeArea1.getY(),
treeArea1.getRight() - by, treeArea1.height);
newTop = getBSPNode();
newTop.getArea().copy(newArea);
newTop.setSplitAxis(0);
newTop.setSplitPos(treeArea1.getX());
newTop.setChild(1, this.bspTree);
this.bspTree = newTop;
treeArea1 = newArea;
}
if (bounds.getRight() > treeArea1.getRight()) {
by = (treeArea1.getRight() + treeArea1.width);
newArea = new RectBox(treeArea1.getX(), treeArea1.getY(),
by - treeArea1.getX(), treeArea1.height);
newTop = getBSPNode();
newTop.getArea().copy(newArea);
newTop.setSplitAxis(0);
newTop.setSplitPos(treeArea1.getRight());
newTop.setChild(0, this.bspTree);
this.bspTree = newTop;
treeArea1 = newArea;
}
if (bounds.getY() < treeArea1.getY()) {
by = (treeArea1.getY() - treeArea1.height);
newArea = new RectBox(treeArea1.getX(), by,
treeArea1.width, treeArea1.getBottom() - by);
newTop = getBSPNode();
newTop.getArea().copy(newArea);
newTop.setSplitAxis(1);
newTop.setSplitPos(treeArea1.getY());
newTop.setChild(1, this.bspTree);
this.bspTree = newTop;
treeArea1 = newArea;
}
if (bounds.getBottom() > treeArea1.getBottom()) {
by = (treeArea1.getBottom() + treeArea1.height);
newArea = new RectBox(treeArea1.getX(), treeArea1.getY(),
treeArea1.width, by - treeArea1.getY());
newTop = getBSPNode();
newTop.getArea().copy(newArea);
newTop.setSplitAxis(1);
newTop.setSplitPos(treeArea1.getBottom());
newTop.setChild(0, this.bspTree);
this.bspTree = newTop;
treeArea1 = newArea;
}
idx++;
}
this.insertObject(actor, bounds, bounds, treeArea1, this.bspTree,
result1, result2);
}
}
private void insertObject(Actor actor, RectBox actorBounds, RectBox bounds,
RectBox area, BSPCollisionNode node, RectBox result1,
RectBox result2) {
if (!node.containsActor(actor)) {
if (!node.isEmpty()
&& (area.width > actorBounds.width || area.height > actorBounds.height)) {
RectBox leftArea = node.getLeftArea();
RectBox rightArea = node.getRightArea();
RectBox leftIntersects = RectBox.getIntersection(leftArea,
bounds, result1);
RectBox rightIntersects = RectBox.getIntersection(rightArea,
bounds, result2);
BSPCollisionNode newRight;
if (leftIntersects != null) {
if (node.getLeft() == null) {
newRight = this.createNewNode(leftArea);
newRight.addActor(actor);
node.setChild(0, newRight);
} else {
this.insertObject(actor, actorBounds, leftIntersects,
leftArea, node.getLeft(), result1, result2);
}
}
if (rightIntersects != null) {
if (node.getRight() == null) {
newRight = this.createNewNode(rightArea);
newRight.addActor(actor);
node.setChild(1, newRight);
} else {
this.insertObject(actor, actorBounds, rightIntersects,
rightArea, node.getRight(), result1, result2);
}
}
} else {
node.addActor(actor);
}
}
}
@Override
public synchronized void clear() {
if (bspTree != null) {
bspTree.clear();
}
}
private synchronized BSPCollisionNode createNewNode(RectBox area) {
byte splitAxis;
float splitPos;
if (area.width > area.height) {
splitAxis = 0;
splitPos = area.getMiddleX();
} else {
splitAxis = 1;
splitPos = area.getMiddleY();
}
BSPCollisionNode newNode = getBSPNode();
newNode.setArea(area);
newNode.setSplitAxis(splitAxis);
newNode.setSplitPos(splitPos);
return newNode;
}
public synchronized final RectBox getActorBounds(Actor actor) {
return actor.getBoundingRect();
}
@Override
public synchronized void removeObject(Actor object) {
for (ActorNode node = getNodeForActor(object); node != null; node = getNodeForActor(object)) {
BSPCollisionNode bspNode = node.getBSPNode();
node.remove();
this.checkRemoveNode(bspNode);
}
}
private synchronized BSPCollisionNode checkRemoveNode(BSPCollisionNode node) {
int idx = 0;
for (; idx < MAX_SIZE;) {
if (node != null && node.isEmpty()) {
BSPCollisionNode parent = node.getParent();
int side = parent != null ? parent.getChildSide(node) : 3;
BSPCollisionNode left = node.getLeft();
BSPCollisionNode right = node.getRight();
if (left == null) {
if (parent != null) {
if (right != null) {
right.setArea(node.getArea());
}
parent.setChild(side, right);
} else {
this.bspTree = right;
if (right != null) {
right.setParent((BSPCollisionNode) null);
}
}
node.setChild(1, (BSPCollisionNode) null);
returnNode(node);
node = parent;
continue;
}
if (right == null) {
if (parent != null) {
if (left != null) {
left.setArea(node.getArea());
}
parent.setChild(side, left);
} else {
this.bspTree = left;
if (left != null) {
left.setParent((BSPCollisionNode) null);
}
}
node.setChild(0, (BSPCollisionNode) null);
returnNode(node);
node = parent;
continue;
}
}
idx++;
return node;
}
return null;
}
private synchronized void updateObject(Actor object) {
ActorNode node = getNodeForActor(object);
if (node != null) {
RectBox newBounds = this.getActorBounds(object);
BSPCollisionNode bspNode;
if (!this.bspTree.getArea().contains(newBounds)) {
for (; node != null;) {
bspNode = node.getBSPNode();
node.remove();
this.checkRemoveNode(bspNode);
node = node.getNext();
}
this.addObject(object);
} else {
RectBox bspArea;
RectBox result1 = new RectBox();
RectBox result2 = new RectBox();
while (node != null) {
bspNode = node.getBSPNode();
bspArea = bspNode.getArea();
if (bspArea.contains(newBounds)) {
for (ActorNode rNode2 = getNodeForActor(object); rNode2 != null; rNode2 = rNode2
.getNext()) {
if (rNode2 != node) {
BSPCollisionNode rNode1 = rNode2.getBSPNode();
rNode2.remove();
this.checkRemoveNode(rNode1);
}
}
return;
}
if (!bspArea.intersects(newBounds)) {
BSPCollisionNode rNode = node.getBSPNode();
node.remove();
this.checkRemoveNode(rNode);
}
node.clearMark();
node = node.getNext();
}
node = getNodeForActor(object);
if (node != null) {
for (bspNode = node.getBSPNode(); bspNode != null
&& !bspNode.getArea().contains(newBounds); bspNode = bspNode
.getParent()) {
}
if (bspNode == null) {
while (node != null) {
bspNode = node.getBSPNode();
node.remove();
this.checkRemoveNode(bspNode);
node = node.getNext();
}
this.addObject(object);
return;
}
} else {
bspNode = this.bspTree;
}
bspArea = bspNode.getArea();
this.insertObject(object, newBounds, newBounds, bspArea,
bspNode, result1, result2);
for (node = getNodeForActor(object); node != null; node = node
.getNext()) {
if (!node.checkMark()) {
bspNode = node.getBSPNode();
node.remove();
this.checkRemoveNode(bspNode);
}
}
}
}
}
@Override
public void updateObjectLocation(Actor object, float oldX, float oldY) {
this.updateObject(object);
}
@Override
public void updateObjectSize(Actor object) {
this.updateObject(object);
}
private TArray<Actor> getIntersectingObjects(float[] r, CollisionQuery query) {
synchronized (cacheSet) {
cacheSet.clear();
this.getIntersectingObjects(r, query, cacheSet, this.bspTree);
TArray<Actor> l = new TArray<Actor>(cacheSet.size);
for (Iterator<Actor> it = cacheSet.iterator(); it.hasNext();) {
l.add(it.next());
}
return l;
}
}
private void intersectingObjects(float[] r, CollisionQuery query,
ObjectSet<Actor> set) {
this.getIntersectingObjects(r, query, set, this.bspTree);
}
private synchronized void getIntersectingObjects(float[] r,
CollisionQuery query, ObjectSet<Actor> resultSet,
BSPCollisionNode startNode) {
synchronized (cacheNodeStack) {
cacheNodeStack.clear();
try {
if (startNode != null) {
cacheNodeStack.add(startNode);
}
int idx = 0;
for (; cacheNodeStack.size() != 0 && idx < MAX_SIZE;) {
BSPCollisionNode node = (BSPCollisionNode) cacheNodeStack
.removeLast();
synchronized (node) {
if (node.getArea().intersects(r[0], r[1], r[2], r[3])) {
Iterator<Actor> i = node.getActorsIterator();
for (; i.hasNext();) {
Actor left = i.next();
if (query.checkCollision(left)
&& !resultSet.contains(left)) {
resultSet.add(left);
}
}
BSPCollisionNode left1 = node.getLeft();
BSPCollisionNode right = node.getRight();
if (left1 != null) {
cacheNodeStack.add(left1);
}
if (right != null) {
cacheNodeStack.add(right);
}
}
}
idx++;
}
} catch (Exception e) {
}
}
}
private synchronized Actor checkForOnlyCollision(Actor ignore,
BSPCollisionNode node, CollisionQuery query) {
if (node == null) {
return null;
}
Iterator<Actor> i = node.getActorsIterator();
Actor candidate;
do {
if (!i.hasNext()) {
return null;
}
candidate = i.next();
} while (ignore == candidate || !query.checkCollision(candidate));
return candidate;
}
private synchronized Actor getOnlyObjectDownTree(Actor ignore, RectBox r,
CollisionQuery query, BSPCollisionNode startNode) {
if (startNode == null) {
return null;
} else {
synchronized (cacheNodeStack) {
cacheNodeStack.clear();
if (startNode != null) {
cacheNodeStack.add(startNode);
}
while (cacheNodeStack.size() != 0) {
BSPCollisionNode node = (BSPCollisionNode) cacheNodeStack
.removeLast();
if (node.getArea().intersects(r)) {
Actor res = this.checkForOnlyCollision(ignore, node,
query);
if (res != null) {
return res;
}
BSPCollisionNode left = node.getLeft();
BSPCollisionNode right = node.getRight();
if (left != null) {
cacheNodeStack.add(left);
}
if (right != null) {
cacheNodeStack.add(right);
}
}
}
}
return null;
}
}
private synchronized Actor getOnlyIntersectingDown(RectBox r,
CollisionQuery query, Actor actor) {
if (this.bspTree == null) {
return null;
} else {
synchronized (cacheNodeStack) {
cacheNodeStack.clear();
cacheNodeStack.add(this.bspTree);
int idx = 0;
for (; cacheNodeStack.size() != 0 && idx < MAX_SIZE;) {
BSPCollisionNode node = (BSPCollisionNode) cacheNodeStack
.removeLast();
if (node.getArea().contains(r)) {
Actor res = this.checkForOnlyCollision(actor, node,
query);
if (res != null) {
return res;
}
BSPCollisionNode left = node.getLeft();
BSPCollisionNode right = node.getRight();
if (left != null) {
cacheNodeStack.add(left);
}
if (right != null) {
cacheNodeStack.add(right);
}
}
}
}
return null;
}
}
public synchronized Actor getOnlyIntersectingUp(RectBox r,
CollisionQuery query, Actor actor, BSPCollisionNode start) {
for (; start != null && !start.getArea().contains(r);) {
Actor res = this.checkForOnlyCollision(actor, start, query);
if (res != null) {
return res;
}
start = start.getParent();
}
return null;
}
@Override
public synchronized TArray<Actor> getObjectsAt(float x, float y, String flag) {
synchronized (this.pointQuery) {
float px = x * this.cellSize + this.cellSize / 2f;
float py = y * this.cellSize + this.cellSize / 2f;
this.pointQuery.init(px, py, flag);
float[] r = { px, py, 1, 1 };
return this.getIntersectingObjects(r, this.pointQuery);
}
}
@Override
public synchronized TArray<Actor> getIntersectingObjects(Actor actor,
String flag) {
RectBox r = this.getActorBounds(actor);
synchronized (this.actorQuery) {
this.actorQuery.init(flag, actor);
return this.getIntersectingObjects(r.toFloat(), this.actorQuery);
}
}
@Override
public synchronized TArray<Actor> getObjectsInRange(float x, float y,
float r, String flag) {
float halfCell = this.cellSize / 2;
float size = 2 * r * this.cellSize;
float[] rect = { (x - r) * this.cellSize + halfCell,
(y - r) * this.cellSize + halfCell, size, size };
cacheSet.clear();
synchronized (this.actorQuery) {
this.actorQuery.init(flag, null);
intersectingObjects(rect, this.actorQuery, cacheSet);
}
synchronized (this.inRangeQuery) {
this.inRangeQuery.init(x * this.cellSize + halfCell, y
* this.cellSize + halfCell, r * this.cellSize);
TArray<Actor> rangeResult = new TArray<Actor>();
Iterator<Actor> it = cacheSet.iterator();
for (; it.hasNext();) {
Actor a = it.next();
if (this.inRangeQuery.checkCollision(a)) {
rangeResult.add(a);
}
}
return rangeResult;
}
}
@Override
public synchronized TArray<Actor> getNeighbours(Actor actor, float distance,
boolean diag, String flag) {
float x = actor.getX();
float y = actor.getY();
float xPixel = x * this.cellSize;
float yPixel = y * this.cellSize;
float dPixel = distance * this.cellSize;
float[] r = { xPixel - dPixel, yPixel - dPixel, dPixel * 2 + 1,
dPixel * 2 + 1 };
synchronized (this.neighbourQuery) {
this.neighbourQuery.init(x, y, distance, diag, flag);
TArray<Actor> res = this.getIntersectingObjects(r,
this.neighbourQuery);
return res;
}
}
@Override
public synchronized TArray<Actor> getObjectsList() {
return this.getObjects((String) null);
}
@Override
public synchronized Actor getOnlyObjectAt(Actor object, float dx, float dy,
String flag) {
synchronized (this.pointQuery) {
float px = dx * this.cellSize + this.cellSize / 2f;
float py = dy * this.cellSize + this.cellSize / 2f;
this.pointQuery.init(px, py, flag);
Object query = this.pointQuery;
if (flag != null) {
query = new CollisionClassQuery(flag, this.pointQuery);
}
return this.getOnlyIntersectingDown(new RectBox(px, py, 1, 1),
(CollisionQuery) query, object);
}
}
@Override
public synchronized Actor getOnlyIntersectingObject(Actor actor, String flag) {
RectBox rect = this.getActorBounds(actor);
synchronized (this.actorQuery) {
this.actorQuery.init(flag, actor);
ActorNode node = getNodeForActor(actor);
if (node == null) {
return null;
}
do {
BSPCollisionNode bspNode = node.getBSPNode();
Actor result = this.getOnlyObjectDownTree(actor, rect,
this.actorQuery, bspNode);
if (result != null) {
return result;
}
result = this.getOnlyIntersectingUp(rect, this.actorQuery,
actor, bspNode.getParent());
if (result != null) {
return result;
}
node = node.getNext();
} while (node != null);
return this.getOnlyIntersectingDown(rect, this.actorQuery, actor);
}
}
public void dispose() {
if (cacheSet != null) {
cacheSet.clear();
}
if (cacheNodeStack != null) {
cacheNodeStack.clear();
}
if (cache != null) {
for (int i = 0; i < cache.length; i++) {
if (cache[i] != null) {
cache[i] = null;
}
}
}
}
@Override
public synchronized TArray<Actor> getObjects(String flag) {
synchronized (cacheSet) {
cacheSet.clear();
}
synchronized (cacheNodeStack) {
if (this.bspTree != null) {
cacheNodeStack.add(this.bspTree);
}
for (; cacheNodeStack.size() != 0;) {
BSPCollisionNode node = (BSPCollisionNode) cacheNodeStack
.removeLast();
Iterator<Actor> i = node.getActorsIterator();
while (i.hasNext()) {
Actor left = i.next();
if (flag == null || flag.equals(left.getFlag())) {
cacheSet.add(left);
}
}
BSPCollisionNode left1 = node.getLeft();
BSPCollisionNode right = node.getRight();
if (left1 != null) {
cacheNodeStack.add(left1);
}
if (right != null) {
cacheNodeStack.add(right);
}
}
TArray<Actor> result = new TArray<Actor>(cacheSet.size);
for (Iterator<Actor> it = cacheSet.iterator(); it.hasNext();) {
result.add(it.next());
}
return result;
}
}
}