com.tenio.engine.physic2d.utility.CellSpacePartition Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tenio-engine Show documentation
Show all versions of tenio-engine Show documentation
TenIO is a java NIO (Non-blocking I/O) based server specifically designed for multiplayer games.
It supports UDP and TCP transports which are handled by Netty for high-speed network transmission.
This is the engine module for the game related handlers of the framework.
package com.tenio.engine.physic2d.utility;
import com.tenio.engine.physic2d.common.BaseGameEntity;
import com.tenio.engine.physic2d.common.InvertedAabbBox2D;
import com.tenio.engine.physic2d.graphic.Paint;
import com.tenio.engine.physic2d.graphic.Renderable;
import com.tenio.engine.physic2d.math.Vector2;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;
/**
* This class is used to divide a 2D space into a grid of cells each of which
* may contain a number of entities. Once created and initialized with entities,
* fast proximity queries can be made by calling the CalculateNeighbors method
* with a position and proximity radius.
*
* If an entity is capable of moving, and therefore capable of moving between
* cells, the Update method should be called each update-cycle to synchronize
* the entity and the cell space it occupies
*
* @param the game entity template
* @author sallyx (https://www.sallyx.org/sally/en/game-ai/)
*/
public class CellSpacePartition implements Renderable {
/**
* Only for temporary calculations.
*/
private final InvertedAabbBox2D aabbBox2D = InvertedAabbBox2D.newInstance();
/**
* The required amount of cells in the space.
*/
private final List> cells = new ArrayList>();
/**
* This is used to store any valid neighbors when an agent searches its
* neighboring space.
*/
private final List neighbors;
// The width and height of the world space the entities inhabit
private final float spaceWidth;
private final float spaceHeight;
// The number of cells the space is going to be divided up into
private final int numCellsX;
private final int numCellsY;
private final float cellSizeX;
private final float cellSizeY;
/**
* This iterator will be used by the methods next and begin to traverse through
* the above vector of neighbors.
*/
private ListIterator currNeighbor;
/**
* Create a new instance.
*
* @param width width of 2D space
* @param height height of 2D space
* @param cellsX number of divisions horizontally
* @param cellsY number of divisions vertically
* @param maxEntities maximum number of entities to partition
*/
public CellSpacePartition(float width, float height, int cellsX, int cellsY, int maxEntities) {
spaceWidth = width;
spaceHeight = height;
numCellsX = cellsX;
numCellsY = cellsY;
neighbors = new ArrayList(maxEntities);
// calculate bounds of each cell
cellSizeX = width / cellsX;
cellSizeY = height / cellsY;
// create the cells
for (int y = 0; y < numCellsY; ++y) {
for (int x = 0; x < numCellsX; ++x) {
float left = x * cellSizeX;
float right = left + cellSizeX;
float top = y * cellSizeY;
float bot = top + cellSizeY;
cells.add(new Cell(left, top, right, bot));
}
}
}
/**
* Given a 2D vector representing a position within the game world, this method
* calculates an index into its appropriate cell.
*
* @param position the desired position
* @return the index
*/
private int getIndexByPosition(Vector2 position) {
int idx = (int) (numCellsX * position.x / spaceWidth)
+ ((int) ((numCellsY) * position.y / spaceHeight) * numCellsX);
// if the entity's position is equal to vector2d(m_dSpaceWidth, m_dSpaceHeight)
// then the index will overshoot. We need to check for this and adjust
if (idx > cells.size() - 1) {
idx = cells.size() - 1;
}
return idx;
}
/**
* Used to add the entities to the data structure adds entities to the class by
* allocating them to the appropriate cell.
*
* @param entity an entity
*/
public void addEntity(T entity) {
int idx = getIndexByPosition(entity.getPosition());
cells.get(idx).members.add(entity);
}
/**
* Update an entity's cell by calling this from your entity's Update method
* Checks to see if an entity has moved cells. If so the data structure is
* updated accordingly.
*
* @param entity an entity
* @param oldPosition see {@link Vector2}
*/
public void updateEntity(T entity, Vector2 oldPosition) {
// if the index for the old position and the new position are not equal then
// the entity has moved to another cell.
int oldIdx = getIndexByPosition(oldPosition);
int newIdx = getIndexByPosition(entity.getPosition());
if (newIdx == oldIdx) {
return;
}
// the entity has moved into another cell so delete from current cell
// and add to new one
cells.get(oldIdx).members.remove(entity);
cells.get(newIdx).members.add(entity);
}
/**
* This must be called to create the vector of neighbors.This method examines
* each cell within range of the target, If the cells contain entities then they
* are tested to see if they are situated within the target's neighborhood
* region. If they are added to neighbor list
*
* this method stores a target's neighbors in the neighbor vector. After you
* have called this method use the begin, next and end methods to iterate
* through the vector.
*
* @param targetPos see {@link Vector2}
* @param queryRadius radius value
*/
public void calculateNeighbors(Vector2 targetPos, float queryRadius) {
neighbors.clear();
// create the query box that is the bounding box of the target's query area
var temp = Vector2.newInstance().set(targetPos).sub(queryRadius, queryRadius);
aabbBox2D.setLeft(temp.x);
aabbBox2D.setTop(temp.y);
temp.set(targetPos).add(queryRadius, queryRadius);
aabbBox2D.setRight(temp.x);
aabbBox2D.setBottom(temp.y);
// iterate through each cell and test to see if its bounding box overlaps
// with the query box. If it does, and it also contains entities then
// make further proximity tests.
var cellListIterator = cells.listIterator();
while (cellListIterator.hasNext()) {
var curCell = cellListIterator.next();
// test to see if this cell contains members and if it overlaps the
// query box
if (curCell.bbox.isOverlappedWith(aabbBox2D) && !curCell.members.isEmpty()) {
// add any entities found within query radius to the neighbor list
var it = curCell.members.listIterator();
while (it.hasNext()) {
T ent = it.next();
if (temp.set(ent.getPosition()).getDistanceSqrValue(targetPos)
< queryRadius * queryRadius) {
neighbors.add(ent);
}
}
}
} // next cell
}
/**
* Retrieves a reference to the entity at the front of the neighbor vector.
*
* @return a reference to the entity at the front of the neighbor vector
*/
public T getFrontOfNeighbor() {
currNeighbor = neighbors.listIterator();
if (!currNeighbor.hasNext()) {
return null;
}
return currNeighbor.next();
}
/**
* Retrieves the next entity in the neighbor vector.
*
* @return the next entity in the neighbor vector
*/
public T getNextOfNeighbor() {
if (currNeighbor == null || !currNeighbor.hasNext()) {
return null;
}
return currNeighbor.next();
}
/**
* Check if the end of vector is found.
*
* @return true if the end of the vector is found (a zero value marks the end)
*/
public boolean isEndOfNeighbors() {
return (currNeighbor == null || (!currNeighbor.hasNext()));
}
/**
* Clears the cells of all entities.
*/
public void clearCells() {
var it = cells.listIterator();
while (it.hasNext()) {
it.next().members.clear();
}
}
@Override
public void render(Paint paint) {
var curCell = cells.listIterator();
while (curCell.hasNext()) {
curCell.next().bbox.render(paint);
}
}
}
| |
© 2015 - 2025 Weber Informatics LLC | Privacy Policy