Commit f761199f authored by Stefan Schuhbaeck's avatar Stefan Schuhbaeck
Browse files

Spawn Array to allow single and group spawn for a Source.

parent 7acc699d
package org.vadere.state.util;
import org.vadere.state.scenario.DynamicElement;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VRectangle;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
/**
* Defines spawn points for a source and returns valid coordinates for spawning.
*/
public class SpawnArray {
// number of spawn elements in x and y Dimension.
private final int xDim;
private final int yDim;
private final VPoint[] spawnPoints;
private final VRectangle spawnElementBound;
private int nextPoint;
// not an index put a way to calculate the index 1..n where n is the number of possible ways to place a given group
// key: groupSize
// value: number of next group.
private HashMap<Integer, Integer> nextGroupPos;
public SpawnArray(VRectangle bound, VRectangle spawnElementBound) {
xDim = (int) (bound.width / spawnElementBound.width);
yDim = (int) (bound.height / spawnElementBound.height);
this.spawnElementBound = spawnElementBound;
spawnPoints = new VPoint[xDim * yDim];
//offset left upper corner to center point.
double eX = spawnElementBound.x + spawnElementBound.width / 2;
double eY = spawnElementBound.y + spawnElementBound.height / 2;
VPoint firstSpawnPoint = new VPoint(bound.x + eX, bound.y + eY);
for (int i = 0; i < spawnPoints.length; i++) {
spawnPoints[i] = firstSpawnPoint.add(new VPoint(2 * eX * (i % xDim), 2 * eY * (i / xDim)));
}
nextPoint = 0;
nextGroupPos = new HashMap<>();
}
/**
* @return maximum dimension of element. If VCircle or square it is the same in x and y. For
* other shapes the bounding box is used and from this the biggest dimension.
*/
public double getMaxElementDim() {
return spawnElementBound.width > spawnElementBound.height ? spawnElementBound.width : spawnElementBound.height;
}
/**
* @return next SpawnPointIndex used to spawn underling spawnElementBound
*/
public int getNextSpawnPointIndex() {
return nextPoint;
}
/**
* @return copy of spawnPoint used for underling source shape.
*/
public VPoint[] getSpawnPoints() {
return Arrays.copyOf(spawnPoints, spawnPoints.length);
}
public VPoint getNextSpawnPoint() {
VPoint ret = spawnPoints[nextPoint].clone();
nextPoint = (nextPoint + 1) % spawnPoints.length;
return ret;
}
public VPoint getNextRandomPoint(Random rnd) {
int index = rnd.nextInt(spawnPoints.length);
return spawnPoints[index];
}
/**
* @param neighbours Test against this List. Caller must ensure that this neighbours are in the
* vicinity of the source bound.
* @return first free space within the spawn points.
*/
public VPoint getNextFreePoint(final List<DynamicElement> neighbours) {
double d = getMaxElementDim() / 2; // radius.
for (VPoint p : spawnPoints) {
boolean overlap = neighbours.parallelStream().anyMatch(n -> ((n.getShape().distance(p) < d) || n.getShape().contains(p)));
if (!overlap) {
return p.clone();
}
}
return null;
}
/**
* This function only spawns non overlapping groups. This means that for instance within a
* source of the size 4x4 there are only 4 possible locations to spawn a group of the size 4
*
* **00 | 00** | 0000 | 0000 **00 | 00** | 0000 | 0000 0000 | 0000 | **00 | 00** 0000 | 0000 |
* **00 | 00**
*
* This function does not test if the space is occupied.
*
* @param groupSize size of group which should be spawned
* @return Point List
*/
public LinkedList<VPoint> getNextGroup(int groupSize) {
return nextGroup(groupSize, null);
}
/**
* Same as getNextGroup(int groupSize) but the selection of the possible spawn location is
* chosen randomly
*/
public LinkedList<VPoint> getNextGroup(int groupSize, Random rnd) {
return nextGroup(groupSize, rnd);
}
private LinkedList<VPoint> nextGroup(int groupSize, Random rnd) {
if (groupSize > spawnPoints.length)
throw new IndexOutOfBoundsException("GroupSize: " + groupSize
+ "to big for source. Max Groupsize of source is " + spawnPoints.length);
// dimGx width of smallest square containing a Group of size groupSize
int dimGx = (int) Math.ceil(Math.sqrt(groupSize));
// dimGy set to minimize lost space in resulting rectangle (or square if groupSize is a square number)
int dimGy = dimGx * dimGx == groupSize ? dimGx : dimGx - 1;
int maxXGroups = xDim / dimGx; // only this many groups in x dim
int maxYGroups = yDim / dimGy; // only this many groups ind y dim
int groupNumber = nextGroupPos.getOrDefault(groupSize, 0);
int start;
if (rnd != null) {
start = rnd.nextInt(maxXGroups * maxYGroups);
} else {
start = (groupNumber % maxXGroups) * dimGx + (groupNumber / maxXGroups) * xDim * dimGy;
}
LinkedList points = new LinkedList();
for (int i = 0; i < groupSize; i++) {
// move index in group
int index = start + (i % dimGx) + (i / dimGx) * xDim;
points.add(spawnPoints[index].clone());
}
nextGroupPos.put(groupSize, (groupNumber + 1) % (maxXGroups * maxYGroups));
return points;
}
/**
* This function only can spawn overlapping groups but the possible location is tested if it is
* free. A source of the size 4x4 hast 9 possible spawn locations for a group of the size 4.
* Each location is test if all spots within the group are free. If so this location is return.
*
* **00 | 0**0 | 00** | 0000 | 0000 | 0000 | 0000 | 0000 | 0000 **00 | 0**0 | 00** | **00 | 0**0
* | 00** | 0000 | 0000 | 0000 0000 | 0000 | 0000 | **00 | 0**0 | 00** | **00 | 0**0 | 00** 0000
* | 0000 | 0000 | 0000 | 0000 | 0000 | **00 | 0**0 | 00**
*
* @param groupSize size of group which should be spawned
* @return Point List or null if no free location is found for given group size.
*/
public LinkedList<VPoint> getNextFreeGroupPos(int groupSize, final List<DynamicElement> neighbours) {
if (groupSize > spawnPoints.length)
throw new IndexOutOfBoundsException("GroupSize: " + groupSize
+ "to big for source. Max Groupsize of source is " + spawnPoints.length);
// dimGx width of smallest square containing a Group of size groupSize
int dimGx = (int) Math.ceil(Math.sqrt(groupSize));
// dimGy set to minimize lost space in resulting rectangle (or square if groupSize is a square number)
int dimGy = dimGx * dimGx == groupSize ? dimGx : dimGx - 1;
int maxXGroupsWithOverlap = xDim - (dimGx - 1);
int maxYGroupsWithOverlap = yDim - (dimGy - 1);
double d = getMaxElementDim() / 2; // radius.
LinkedList<VPoint> points = new LinkedList<>();
//over all overlapping groups in source
boolean overlap = true;
for (int groupNumber = 0; groupNumber < maxXGroupsWithOverlap * maxYGroupsWithOverlap; groupNumber++) {
int start = (groupNumber % maxXGroupsWithOverlap) + (groupNumber / maxXGroupsWithOverlap) * xDim;
for (int i = 0; i < groupSize; i++) {
int index = start + (i % dimGx) + (i / dimGx) * xDim;
VPoint p = spawnPoints[index].clone();
overlap = neighbours.parallelStream().anyMatch(n -> ((n.getShape().distance(p) < d) || n.getShape().contains(p)));
if (!overlap) {
points.add(p);
} else {
points = new LinkedList<>();
break;
}
}
// if a group was found with no overlapping point break and return group.
if (!overlap)
break;
}
if (points.size() < groupSize) {
return null;
} else {
return points;
}
}
}
package org.vadere.state.util;
import org.junit.Test;
import org.mockito.Mockito;
import org.vadere.state.scenario.DynamicElement;
import org.vadere.util.geometry.shapes.VCircle;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VRectangle;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.IntStream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
public class SpawnArrayTest {
VRectangle source;
VRectangle elementBound;
SpawnArray spawnArray;
@Test
public void NumberOfElements() {
// Number of Elements
source = new VRectangle(1.0, 1.0, 9.9, 9.9);
elementBound = new VRectangle(0.0, 0.0, 1.0, 1.0);
spawnArray = new SpawnArray(source, elementBound);
assertEquals("expected spawn points don't match", 81, spawnArray.getSpawnPoints().length);
}
@Test
public void Points() {
source = new VRectangle(1.0, 1.0, 9.9, 9.9);
elementBound = new VRectangle(0.0, 0.0, 1.0, 1.0);
spawnArray = new SpawnArray(source, elementBound);
// first Point
VPoint p = spawnArray.getNextSpawnPoint();
assertEquals("Point does not match", p, new VPoint(1.5, 1.5));
assertEquals("Next Element does not match", 1, spawnArray.getNextSpawnPointIndex());
// 10 more points
IntStream.range(0, 10).forEach(i -> spawnArray.getNextSpawnPoint());
assertEquals("Next Element does not match", 11, spawnArray.getNextSpawnPointIndex());
VPoint first = new VPoint(source.x + elementBound.width / 2, source.y + elementBound.height / 2);
assertEquals("Point does not match", spawnArray.getNextSpawnPoint(),
new VPoint(first.x + 2 * 2 * elementBound.width / 2, first.y + 1 * 2 * elementBound.height / 2));
// now at point 12 because getNextSpawnPoint() increments NextPointIndex
// spawn 81 more to wrapp around to point 12.
IntStream.range(0, 81).forEach(i -> spawnArray.getNextSpawnPoint());
assertEquals("Next Element does not match", 12, spawnArray.getNextSpawnPointIndex());
assertEquals("Point does not match", spawnArray.getNextSpawnPoint(),
new VPoint(first.x + 3 * 2 * elementBound.width / 2, first.y + 1 * 2 * elementBound.height / 2));
}
@Test
public void testFreeSpawn() {
source = new VRectangle(1.0, 1.0, 2.0, 2.0);
elementBound = new VRectangle(0.0, 0.0, 1.0, 1.0);
spawnArray = new SpawnArray(source, elementBound);
assertEquals("Number of spawn points does not match", 4, spawnArray.getSpawnPoints().length);
assertNull("There should not be a free spot.", spawnArray.getNextFreePoint(
createMock(0.5, spawnArray.getSpawnPoints())));
VPoint[] spawnPoints = spawnArray.getSpawnPoints();
assertNotEquals("Point 1 is occupied and should not be returned", spawnPoints[1], spawnArray.getNextFreePoint(
createMock(0.5, spawnPoints[1])
));
assertNotNull("There should be three valid points", spawnArray.getNextFreePoint(
createMock(0.5, spawnPoints[1])
));
}
@Test
public void testGroupSpawnMatchDims() {
source = new VRectangle(1.0, 1.0, 8.0, 8.0);
elementBound = new VRectangle(0.0, 0.0, 1.0, 1.0);
spawnArray = new SpawnArray(source, elementBound);
assertEquals("Number of spawn points does not match", 64, spawnArray.getSpawnPoints().length);
VPoint[] spawnPoint = spawnArray.getSpawnPoints();
//group 0
LinkedList<VPoint> group = spawnArray.getNextGroup(4);
assertEquals(group.pollFirst(), spawnPoint[0]);
assertEquals(group.pollFirst(), spawnPoint[1]);
assertEquals(group.pollFirst(), spawnPoint[8]);
assertEquals(group.pollFirst(), spawnPoint[9]);
//group 1
group = spawnArray.getNextGroup(4);
assertEquals(group.pollFirst(), spawnPoint[2]);
assertEquals(group.pollFirst(), spawnPoint[3]);
assertEquals(group.pollFirst(), spawnPoint[10]);
assertEquals(group.pollFirst(), spawnPoint[11]);
//group 2-3
spawnArray.getNextGroup(4);
group = spawnArray.getNextGroup(4);
assertEquals(group.pollFirst(), spawnPoint[6]);
assertEquals(group.pollFirst(), spawnPoint[7]);
assertEquals(group.pollFirst(), spawnPoint[14]);
assertEquals(group.pollFirst(), spawnPoint[15]);
//group 4 (line wrap)
group = spawnArray.getNextGroup(4);
assertEquals(group.pollFirst(), spawnPoint[16]);
assertEquals(group.pollFirst(), spawnPoint[17]);
assertEquals(group.pollFirst(), spawnPoint[24]);
assertEquals(group.pollFirst(), spawnPoint[25]);
//group 15 (last group)
IntStream.range(0, 10).forEach(i -> spawnArray.getNextGroup(4));
group = spawnArray.getNextGroup(4);
assertEquals(group.pollFirst(), spawnPoint[54]);
assertEquals(group.pollFirst(), spawnPoint[55]);
assertEquals(group.pollFirst(), spawnPoint[62]);
assertEquals(group.pollFirst(), spawnPoint[63]);
// group 0 (wrap around to first group)
group = spawnArray.getNextGroup(4);
assertEquals(group.pollFirst(), spawnPoint[0]);
assertEquals(group.pollFirst(), spawnPoint[1]);
assertEquals(group.pollFirst(), spawnPoint[8]);
assertEquals(group.pollFirst(), spawnPoint[9]);
}
@Test
public void testGroupSpawnNoMatchDims() {
source = new VRectangle(1.0, 1.0, 8.0, 8.0);
elementBound = new VRectangle(0.0, 0.0, 1.0, 1.0);
spawnArray = new SpawnArray(source, elementBound);
assertEquals("Number of spawn points does not match", 64, spawnArray.getSpawnPoints().length);
VPoint[] spawnPoint = spawnArray.getSpawnPoints();
//group 0
LinkedList<VPoint> group = spawnArray.getNextGroup(6);
assertEquals(group.pollFirst(), spawnPoint[0]);
assertEquals(group.pollFirst(), spawnPoint[1]);
assertEquals(group.pollFirst(), spawnPoint[2]);
assertEquals(group.pollFirst(), spawnPoint[8]);
assertEquals(group.pollFirst(), spawnPoint[9]);
assertEquals(group.pollFirst(), spawnPoint[10]);
//group 1
group = spawnArray.getNextGroup(6);
assertEquals(group.pollFirst(), spawnPoint[3]);
assertEquals(group.pollFirst(), spawnPoint[4]);
assertEquals(group.pollFirst(), spawnPoint[5]);
assertEquals(group.pollFirst(), spawnPoint[11]);
assertEquals(group.pollFirst(), spawnPoint[12]);
assertEquals(group.pollFirst(), spawnPoint[13]);
//group 2 (line wrap)
group = spawnArray.getNextGroup(6);
assertEquals(group.pollFirst(), spawnPoint[16]);
assertEquals(group.pollFirst(), spawnPoint[17]);
assertEquals(group.pollFirst(), spawnPoint[18]);
assertEquals(group.pollFirst(), spawnPoint[24]);
assertEquals(group.pollFirst(), spawnPoint[25]);
assertEquals(group.pollFirst(), spawnPoint[26]);
//group 7 (last group)
IntStream.range(0, 4).forEach(i -> spawnArray.getNextGroup(6));
group = spawnArray.getNextGroup(6);
assertEquals(group.pollFirst(), spawnPoint[51]);
assertEquals(group.pollFirst(), spawnPoint[52]);
assertEquals(group.pollFirst(), spawnPoint[53]);
assertEquals(group.pollFirst(), spawnPoint[59]);
assertEquals(group.pollFirst(), spawnPoint[60]);
assertEquals(group.pollFirst(), spawnPoint[61]);
//group 0 (wrap around to first group)
group = spawnArray.getNextGroup(6);
assertEquals(group.pollFirst(), spawnPoint[0]);
assertEquals(group.pollFirst(), spawnPoint[1]);
assertEquals(group.pollFirst(), spawnPoint[2]);
assertEquals(group.pollFirst(), spawnPoint[8]);
assertEquals(group.pollFirst(), spawnPoint[9]);
assertEquals(group.pollFirst(), spawnPoint[10]);
}
@Test(expected = IndexOutOfBoundsException.class)
public void mixedGroupWithErr() {
source = new VRectangle(1.0, 1.0, 8.0, 8.0);
elementBound = new VRectangle(0.0, 0.0, 1.0, 1.0);
spawnArray = new SpawnArray(source, elementBound);
assertEquals("Number of spawn points does not match", 64, spawnArray.getSpawnPoints().length);
VPoint[] spawnPoint = spawnArray.getSpawnPoints();
//group 3 lines
LinkedList<VPoint> group = spawnArray.getNextGroup(9);
assertEquals(group.pollFirst(), spawnPoint[0]);
assertEquals(group.pollFirst(), spawnPoint[1]);
assertEquals(group.pollFirst(), spawnPoint[2]);
assertEquals(group.pollFirst(), spawnPoint[8]);
assertEquals(group.pollFirst(), spawnPoint[9]);
assertEquals(group.pollFirst(), spawnPoint[10]);
assertEquals(group.pollFirst(), spawnPoint[16]);
assertEquals(group.pollFirst(), spawnPoint[17]);
assertEquals(group.pollFirst(), spawnPoint[18]);
//group 0
group = spawnArray.getNextGroup(4);
assertEquals(group.pollFirst(), spawnPoint[0]);
assertEquals(group.pollFirst(), spawnPoint[1]);
assertEquals(group.pollFirst(), spawnPoint[8]);
assertEquals(group.pollFirst(), spawnPoint[9]);
// spawning different size groups does not effect other groups
spawnArray.getNextGroup(9);
spawnArray.getNextGroup(9);
//group 1
group = spawnArray.getNextGroup(4);
assertEquals(group.pollFirst(), spawnPoint[2]);
assertEquals(group.pollFirst(), spawnPoint[3]);
assertEquals(group.pollFirst(), spawnPoint[10]);
assertEquals(group.pollFirst(), spawnPoint[11]);
spawnArray.getNextGroup(100);
}
@Test
public void testFreeGroupSpawn() {
source = new VRectangle(1.0, 1.0, 8.0, 8.0);
elementBound = new VRectangle(0.0, 0.0, 1.0, 1.0);
spawnArray = new SpawnArray(source, elementBound);
assertEquals("Number of spawn points does not match", 64, spawnArray.getSpawnPoints().length);
VPoint[] spawnPoint = spawnArray.getSpawnPoints();
LinkedList<VPoint> group = spawnArray.getNextFreeGroupPos(6, createMock(0.5, new VPoint(99.0, 99.0)));
assertEquals(group.pollFirst(), spawnPoint[0]);
assertEquals(group.pollFirst(), spawnPoint[1]);
assertEquals(group.pollFirst(), spawnPoint[2]);
assertEquals(group.pollFirst(), spawnPoint[8]);
assertEquals(group.pollFirst(), spawnPoint[9]);
assertEquals(group.pollFirst(), spawnPoint[10]);
// empty neighbours
group = spawnArray.getNextFreeGroupPos(6, new LinkedList<DynamicElement>());
assertEquals(group.pollFirst(), spawnPoint[0]);
assertEquals(group.pollFirst(), spawnPoint[1]);
assertEquals(group.pollFirst(), spawnPoint[2]);
assertEquals(group.pollFirst(), spawnPoint[8]);
assertEquals(group.pollFirst(), spawnPoint[9]);
assertEquals(group.pollFirst(), spawnPoint[10]);
// match group 3 (with overlapping groups)
List<DynamicElement> dynamicElements = createMock(0.5, spawnPoint[0], spawnPoint[1], spawnPoint[10]);
group = spawnArray.getNextFreeGroupPos(6, dynamicElements);
assertEquals(group.pollFirst(), spawnPoint[3]);
assertEquals(group.pollFirst(), spawnPoint[4]);
assertEquals(group.pollFirst(), spawnPoint[5]);
assertEquals(group.pollFirst(), spawnPoint[11]);
assertEquals(group.pollFirst(), spawnPoint[12]);
assertEquals(group.pollFirst(), spawnPoint[13]);
//match group 9 (with overlapping groups and line wrap)
dynamicElements = createMock(0.5,
spawnPoint[0],
spawnPoint[1],
spawnPoint[2],
spawnPoint[3],
spawnPoint[6],
spawnPoint[9]);
group = spawnArray.getNextFreeGroupPos(6, dynamicElements);
assertEquals(group.pollFirst(), spawnPoint[10]);
assertEquals(group.pollFirst(), spawnPoint[11]);
assertEquals(group.pollFirst(), spawnPoint[12]);
assertEquals(group.pollFirst(), spawnPoint[18]);
assertEquals(group.pollFirst(), spawnPoint[19]);
assertEquals(group.pollFirst(), spawnPoint[20]);
}
private List<DynamicElement> createMock(double r, VPoint... points) {
LinkedList<DynamicElement> elements = new LinkedList<>();
for (VPoint p : points) {
VCircle c = new VCircle(p, r);
DynamicElement e = Mockito.mock(DynamicElement.class, Mockito.RETURNS_DEEP_STUBS);
Mockito.when(e.getShape()).thenReturn(c);
elements.add(e);
}
return elements;
}
}
\ No newline at end of file
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment