Here’s the code.
The grid and object management class:
package mygame;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
import com.simsilica.mathd.Grid;
import com.simsilica.mathd.GridCell;
import com.simsilica.mathd.Vec3d;
import com.simsilica.mathd.Vec3i;
/**
*
* @author Makary
*/
public class SpatialHash {
public static Multimap<GridCell, GameObject> index = MultimapBuilder.hashKeys().hashSetValues().build();
public static Grid worldGrid;
public static GridCell cell;
public static GridCell previousCell;
public static int gridSize =3;
public static void Setup(){
worldGrid = new Grid(gridSize);
}
public static void AddObject(GameObject gameObj){ // puts the object in a cell based on its position.
cell = worldGrid.getContainingCell(gameObj.getPosition());
index.put(cell, gameObj);
}
public static void updateObject(GameObject gameObj){
cell = worldGrid.getContainingCell(gameObj.getPosition());
if(!index.get(cell).contains(gameObj)){
previousCell = worldGrid.getContainingCell(gameObj.getPreviousCellVec());
index.get(previousCell).remove(gameObj);
index.put(cell, gameObj);
gameObj.setPreviousCellVec(cell.getWorldOrigin().toVec3d());
}
}
public static void checkForCollisionSmall(GameObject gameObj){ // this method checks for collision for small (with smaller radius than grid size) objects
Vec3i min = worldGrid.worldToCell(new Vec3d(gameObj.getPosition().x-gameObj.getRadius(),gameObj.getPosition().y-gameObj.getRadius(),gameObj.getPosition().z-gameObj.getRadius()));
Vec3i max = worldGrid.worldToCell(new Vec3d(gameObj.getPosition().x+gameObj.getRadius(),gameObj.getPosition().y+gameObj.getRadius(),gameObj.getPosition().z+gameObj.getRadius()));
for( int x = min.x; x <= max.x; x++ ) {
for( int y = min.x; y <= max.x; y++ ) {
for( int z = min.x; z <= max.x; z++ ) {
GridCell testedCell = worldGrid.getContainingCell(x, y, z);
for(GameObject collisionObject : index.get(testedCell)){
if(gameObj != collisionObject && gameObj.getPosition().toVector3f().distance(collisionObject.getPosition().toVector3f()) <= collisionObject.getRadius() &&gameObj.getPosition().toVector3f().distance(collisionObject.getPosition().toVector3f()) <= gameObj.getRadius()){
// what to do when the collision occurs?
}
}
}
}
}
}
public static void createObject(Vec3d spawnpoint,float radius,boolean type, Vec3d spawnpointCopy){
GameObject newObject = new GameObject(); // object is an unit or a projectile
newObject.setPosition(spawnpoint);
newObject.setRadius(radius);
newObject.setType(type);
newObject.setPreviousCellVec(spawnpointCopy);
AddObject(newObject);
}
}
The GameObject class:
package mygame;
import com.simsilica.mathd.Vec3d;
/**
*
* @author Makary
*/
public class GameObject {
public float radius;
public Vec3d position;
public boolean type; // true = projectile, false = hittable (unit, wall ,tree, whatever)
Vec3d previousCellVec; // helps with removing objects from its cell, neccesary to avoid ConcurrentModificationError
public float getRadius() {
return radius;
}
public void setRadius(float radius) {
this.radius = radius;
}
public Vec3d getPosition() {
return position;
}
public void setPosition(Vec3d position) {
this.position = position;
}
public void setType(boolean type){
this.type = type;
}
public boolean getType(){
return type;
}
public void setPreviousCellVec(Vec3d previousCell){
this.previousCellVec = previousCell;
}
public Vec3d getPreviousCellVec(){
return previousCellVec;
}
}
Example of how to update all objects in the world grid (put it in the SimpleUpdate method): (i implemented it in another class therefore there is SpatialHash. before every variable)
ArrayList<GameObject> arrayList = new ArrayList<>();
arrayList.addAll(SpatialHash.index.values());
for (int i=0; i < arrayList.size(); i++){
SpatialHash.checkForCollisionSmall(arrayList.get(i));
Vector3f vectorToMove = new Vector3f(arrayList.get(i).getPosition().toVector3f()); // to later be used for spatial movement
arrayList.get(i).setPosition(new Vec3d(arrayList.get(i).getPosition().x+0.0f,1.5d,arrayList.get(i).getPosition().z+0.0f));
SpatialHash.updateObject(arrayList.get(i));
}
}
if you want to target performance rather than realism (eg. projectiles fired upward wont turn around and fall to the ground) you can replace
public boolean type;
with something like:
public byte type; // example: 1= tree,2= projectile fired upwards, 3= projectile fired horizontally
and make it so the GameObjects of types 2 and 3 cannot possibly collide, and then you can cut out the “y” loop in the loop:
for( int x = min.x; x <= max.x; x++ ) {
for( int y = min.y; y <= max.y; y++ ) {
for( int z = min.z; z <= max.z; z++ ) {
GridCell testedCell = worldGrid.getContainingCell(x, y, z);
for(GameObject collisionObject : index.get(testedCell)){
if(gameObj != collisionObject && gameObj.getPosition().toVector3f().distance(collisionObject.getPosition().toVector3f()) <= collisionObject.getRadius() &&gameObj.getPosition().toVector3f().distance(collisionObject.getPosition().toVector3f()) <= gameObj.getRadius()){
// what to do when the collision occurs?
}
}
}
}
}
EDIT: when checking for collision, try to put the fastest if() conditions first, otherwise you may end up with a lot of unnecessary calculations in nested loops which are in an updtae method which can really slow down the performance, so be wise when editing collision checking methods.