Skip to content

Instantly share code, notes, and snippets.

@junkdog
Created March 17, 2015 12:41
Show Gist options
  • Save junkdog/719dbc240578eb500059 to your computer and use it in GitHub Desktop.
Save junkdog/719dbc240578eb500059 to your computer and use it in GitHub Desktop.
package com.github.junkdog.shamans.system.spatial;
import static com.badlogic.gdx.math.MathUtils.clamp;
import static com.github.junkdog.shamans.Constants.HEIGHT;
import static com.github.junkdog.shamans.Constants.WIDTH;
import static com.github.junkdog.shamans.system.render.NearestNeighborsRenderSystem.FIND_NEIGHBORS;
import static java.lang.Math.min;
import lombok.Getter;
import lombok.Setter;
import com.artemis.Aspect;
import com.artemis.ComponentMapper;
import com.artemis.Entity;
import com.artemis.annotations.Profile;
import com.artemis.annotations.Wire;
import com.artemis.systems.EntityProcessingSystem;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.IntArray;
import com.badlogic.gdx.utils.IntIntMap;
import com.github.junkdog.shamans.component.Owner;
import com.github.junkdog.shamans.component.Position;
import com.github.junkdog.shamans.component.Repulsor;
import com.github.junkdog.shamans.profile.Profiler;
import com.github.junkdog.shamans.util.CappedLinkedList;
@Profile(using=Profiler.class, enabled=Profiler.PERF_PROFILE)
@Wire
public class GridSystem extends EntityProcessingSystem {
@Getter private int cols;
@Getter private int rows;
@Getter private float cellSize;
@Setter private int player1Id;
@Setter private int player2Id;
private final PlayerGrid player1Grid;
private final PlayerGrid player2Grid;
private ComponentMapper<Position> positionMapper;
private ComponentMapper<Owner> ownerMapper;
@SuppressWarnings("unchecked")
public GridSystem(OrthographicCamera camera, float gridSize) {
super(Aspect.getAspectForAll(Position.class, Owner.class).exclude(Repulsor.class));
this.cellSize = gridSize;
rows = (int)(HEIGHT / gridSize);
cols = (int)(WIDTH / gridSize);
player1Grid = new PlayerGrid(rows, cols);
player2Grid = new PlayerGrid(rows, cols);
}
@Override
protected void inserted(Entity e) {
playerGrid(e).inserted(e);
}
@Override
protected void removed(Entity e) {
playerGrid(e).removed(e);
}
@Override
protected void process(Entity e) {
playerGrid(e).process(e);
}
public PlayerGrid playerGrid(Entity e) {
int id = ownerMapper.has(e) ? ownerMapper.get(e).playerId : e.getId();
if (id == player1Id) return player1Grid;
if (id == player2Id) return player2Grid;
throw new RuntimeException("Owner does not match player ids: " + id);
}
public class PlayerGrid {
private final IntArray[] grid;
private final IntIntMap entityToGridMapping;
private final CappedLinkedList<Entity> foundEntities;
public PlayerGrid(int rows, int cols) {
foundEntities = new CappedLinkedList<Entity>(FIND_NEIGHBORS);
entityToGridMapping = new IntIntMap();
grid = new IntArray[rows * cols];
for (int i = 0; grid.length > i; i++) {
grid[i] = new IntArray();
}
}
protected void process(Entity e) {
Position pos = positionMapper.get(e);
int oldGrid = entityToGridMapping.get(e.getId(), -1);
assert oldGrid != -1;
int newGrid = grid(pos);
if (newGrid != oldGrid) {
entityToGridMapping.put(e.getId(), newGrid);
boolean removed = grid[oldGrid].removeValue(e.getId());
assert removed;
grid[newGrid].add(e.getId());
}
}
protected void removed(Entity e) {
if (entityToGridMapping.containsKey(e.getId())) {
int removed = entityToGridMapping.remove(e.getId(), -1);
assert removed != -1;
grid[grid(positionMapper.get(e))].removeValue(e.getId());
} else {
throw new RuntimeException("Tried removing entity: " + e);
}
}
protected void inserted(Entity e) {
Position pos = positionMapper.get(e);
grid[grid(pos)].add(e.getId());
entityToGridMapping.put(e.getId(), grid(pos));
}
public void findNearest(Array<Entity> fillArray, Entity source, int count) {
foundEntities.clear();
fillArray.clear();
// + 1 = self reference
count = min((count + 1), getActives().size());
if (getActives().size() < count) {
count = getActives().size();
}
foundEntities.setOrigin(positionMapper.get(source).x, positionMapper.get(source).y);
foundEntities.setMaxSize(count);
Position pos = positionMapper.get(source);
int y = row(pos);
int x = col(pos);
addEntitiesFromCell(x, y);
assert foundEntities.size > 0;
if (count < foundEntities.size) {
addToFillArray(fillArray, source, (count - 1));
return;
}
int perimeter = 1;
while (count > foundEntities.size || perimeter == 1) {
findInPerimeter(x, y, perimeter++);
}
addToFillArray(fillArray, source, (count - 1));
}
public IntArray entitiesInGrid(int x, int y) {
return grid[y * cols + x];
}
private int row(Position p) {
int row = (int)(p.y / cellSize);
return clamp(row, 0, rows - 1);
}
private int col(Position p) {
int col = (int)(p.x / cellSize);
return clamp(col, 0, cols - 1);
}
private int grid(Position p) {
return row(p) * cols + col(p);
}
private void addEntitiesFromCell(int x, int y) {
x = clamp(x, 0, cols - 1);
y = clamp(y, 0, rows - 1);
IntArray g = grid[(y * cols) + x];
for (int i = 0; g.size > i; i++) {
Entity e = world.getEntity(g.get(i));
foundEntities.offer(e, positionMapper.get(e));
}
}
private void findInPerimeter(int col, int row, int perimeter) {
int x = col - perimeter;
int y = row - perimeter;
for (int i = 0; (perimeter * 2) >= i; i++) {
addEntitiesFromCell(x + i, y);
}
for (int i = 1; (perimeter * 2) > i; i++) {
addEntitiesFromCell(col - perimeter, y + i);
addEntitiesFromCell(col + perimeter, y + i);
}
y = row + perimeter;
for (int i = 0; (perimeter * 2) >= i; i++) {
addEntitiesFromCell(x + i, y);
}
}
private void addToFillArray(Array<Entity> fillArray, Entity source, int count) {
foundEntities.iter(); foundEntities.next(); foundEntities.remove();
foundEntities.iter();
for (Entity e; (e = foundEntities.next()) != null; ) {
fillArray.add(e);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment