package org.osm2world.core.math.datastructures;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import org.osm2world.core.math.AxisAlignedBoundingBoxXZ;
/**
* a data structure that can be used to speed up intersection tests.
*
* An IntersectionTestObject is added to all grid cells that are at least
* partially covered by the object's axis-aligned bounding box.
* When testing for intersections or inclusions, only elements in the same
* cell need to be compared.
*/
public class IntersectionGrid<T extends IntersectionTestObject> {
private final AxisAlignedBoundingBoxXZ gridBounds;
private Collection<T>[][] cells;
private final int cellCountX, cellCountZ;
private final double cellSizeX, cellSizeZ;
public IntersectionGrid(AxisAlignedBoundingBoxXZ gridBounds,
int cellCountX, int cellCountZ) {
this.gridBounds = gridBounds;
@SuppressWarnings("unchecked") //cannot create generic array
Collection<T>[][] newCells
= new Collection[cellCountX][cellCountZ];
this.cells = newCells;
this.cellCountX = cellCountX;
this.cellCountZ = cellCountZ;
this.cellSizeX = gridBounds.sizeX() / cellCountX;
this.cellSizeZ = gridBounds.sizeZ() / cellCountZ;
}
/**
* alternative constructor that uses a target cell size to calculate
* the number of cells
*/
public IntersectionGrid(AxisAlignedBoundingBoxXZ gridBounds,
double approxCellSizeX, double approxCellSizeZ) {
this(gridBounds,
((int) (gridBounds.sizeX() / approxCellSizeX)) + 1,
((int) (gridBounds.sizeZ() / approxCellSizeZ)) + 1);
}
public Collection<T>[][] getCellArray() {
return cells;
}
/**
* returns the content object collections for all non-empty cells
*/
public Iterable<Collection<T>> getCells() {
return new Iterable<Collection<T>>() {
@Override public Iterator<Collection<T>> iterator() {
return new CellIterator();
}
};
}
/**
* read-only iterator for non-null cells
*/
private class CellIterator implements Iterator<Collection<T>> {
int x = 0;
int z = -1;
public CellIterator() {
toNext();
}
@Override
public boolean hasNext() {
return x < cellCountX;
}
@Override
public Collection<T> next() {
Collection<T> result = cells[x][z];
toNext();
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
private void toNext() {
do {
z ++;
if (z >= cellCountZ) {
z = 0;
x ++;
}
} while (x < cellCountX && cells[x][z] == null);
}
}
/**
* returns all non-empty cells that would contain the object.
* Will not modify the intersection grid, and it doesn't matter
* whether the object has been inserted or not.
*/
public Collection<Collection<T>> cellsFor(
IntersectionTestObject object) {
assert(gridBounds.contains(object));
AxisAlignedBoundingBoxXZ objectAABB = object.getAxisAlignedBoundingBoxXZ();
int minCellX = cellXForCoord(objectAABB.minX, objectAABB.minZ);
int minCellZ = cellZForCoord(objectAABB.minX, objectAABB.minZ);
int maxCellX = cellXForCoord(objectAABB.maxX, objectAABB.maxZ);
int maxCellZ = cellZForCoord(objectAABB.maxX, objectAABB.maxZ);
Collection<Collection<T>> result =
new ArrayList<Collection<T>>(
(maxCellX - minCellX) * (maxCellZ - minCellZ));
for (int cellX = minCellX; cellX <= maxCellX; cellX ++) {
for (int cellZ = minCellZ; cellZ <= maxCellZ; cellZ ++) {
if (cells[cellX][cellZ] != null) {
result.add(cells[cellX][cellZ]);
}
}
}
return result;
}
public void insert(T object) {
assert(gridBounds.contains(object));
AxisAlignedBoundingBoxXZ objectAABB = object.getAxisAlignedBoundingBoxXZ();
int minCellX = cellXForCoord(objectAABB.minX, objectAABB.minZ);
int minCellZ = cellZForCoord(objectAABB.minX, objectAABB.minZ);
int maxCellX = cellXForCoord(objectAABB.maxX, objectAABB.maxZ);
int maxCellZ = cellZForCoord(objectAABB.maxX, objectAABB.maxZ);
for (int cellX = minCellX; cellX <= maxCellX; cellX ++) {
for (int cellZ = minCellZ; cellZ <= maxCellZ; cellZ ++) {
addToCell(cellX, cellZ, object);
}
}
}
private void addToCell(int cellX, int cellZ, T object) {
if (cells[cellX][cellZ] == null) {
cells[cellX][cellZ] = new ArrayList<T>();
}
cells[cellX][cellZ].add(object);
}
public void remove(T object) {
assert(gridBounds.contains(object));
AxisAlignedBoundingBoxXZ objectAABB = object.getAxisAlignedBoundingBoxXZ();
int minCellX = cellXForCoord(objectAABB.minX, objectAABB.minZ);
int minCellZ = cellZForCoord(objectAABB.minX, objectAABB.minZ);
int maxCellX = cellXForCoord(objectAABB.maxX, objectAABB.maxZ);
int maxCellZ = cellZForCoord(objectAABB.maxX, objectAABB.maxZ);
for (int cellX = minCellX; cellX <= maxCellX; cellX ++) {
for (int cellZ = minCellZ; cellZ <= maxCellZ; cellZ ++) {
if (cells[cellX][cellZ] != null) {
cells[cellX][cellZ].remove(object);
}
}
}
}
/**
* returns the x index of the cell that contains the coordinate
*/
public final int cellXForCoord(double x, double z) {
return (int) ((x - gridBounds.minX) / cellSizeX);
}
/**
* returns the z index of the cell that contains the coordinate
*/
public final int cellZForCoord(double x, double z) {
return (int) ((z - gridBounds.minZ) / cellSizeZ);
}
}