16.12.2021, 9:00 - 11:00: Due to updates GitLab may be unavailable for some minutes between 09:00 and 11:00.

Commit 922fde6e authored by Stefan Schuhbaeck's avatar Stefan Schuhbaeck
Browse files

Merge branch 'source_shape'

parents 38b5f9b1 d678b2ce
Pipeline #72863 failed with stages
in 76 minutes and 46 seconds
......@@ -51,6 +51,8 @@ public class AgentRender implements Renderer {
}
private void renderGroup(Pedestrian ped, Graphics2D g) {
g.setColor(Color.DARK_GRAY);
g.fill(ped.getShape());
g.setColor(getGroupColor(ped));
DefaultRenderer.fill(getShape(ped), g);
}
......
......@@ -260,6 +260,8 @@ public class TopographyWindow extends JPanel {
obstacleAndTargetDrawModes.add(pen2);
sourceDrawModes.add(rectangle);
sourceDrawModes.add(pen);
sourceDrawModes.add(pen2);
sourceDrawModes.add(dot);
/* open obstacle paint method dialog action */
......
......@@ -14,6 +14,8 @@ ScenarioChecker.source.targetIdNotFound=The following target ids where not found
ScenarioChecker.source.noTargetIdSet=No Target Ids set for Source.
ScenarioChecker.source.noTargetIdAndNoSpawn=No Target Ids set for Source with SpawnNumber 0. This might be an error.
ScenarioChecker.source.idNotUnique=Multiple Sources have the same ID.
ScenarioChecker.source.spawnAtRandomButNotAtFreeSpace=Combination isSpawnAtRandomPositions=true and isUseFreeSpaceOnly=false not allowed.
ScenarioChecker.source.spawnUseNotAtFreeSpace=Deprecated. Will be removed in future relases. Not all models can handle overlapping pedestrians.
ScenarioChecker.stairs.wrongTreadDim=Stair treadDepth outside of allowed dimension. Change the thread numbers to compensate.
ScenarioChecker.target.unused=The target is not used in any source. Remove target to increase performance.
ScenarioChecker.pedestrian.speedsetup=speedDistributionMean must be within min/max range.
......
......@@ -13,6 +13,8 @@ ScenarioChecker.type.processor.warning=Procesor Warnung
ScenarioChecker.source.targetIdNotFound=Die folgenden Ziel-IDs wurden nicht im Szenario gefunden.
ScenarioChecker.source.noTargetIdSet=In der Quelle wurden keine Ziel Ids vergeben.
ScenarioChecker.source.noTargetIdAndNoSpawn=In der Quelle wurden keine Ziel Ids vergeben, aber die Spawn Anzahl ist bei 0.
ScenarioChecker.source.spawnAtRandomButNotAtFreeSpace=Kombination aus isSpawnAtRandomPositions=true und isUseFreeSpaceOnly=false ist nicht erlaubt.
ScenarioChecker.source.spawnUseNotAtFreeSpace=Deprecated. Dieses Attribut wird in zuk\u00fcnfigen version entfernt. Nicht alle Modell k\u00f6nnen mit \u00fcberlappungen umgehen.
ScenarioChecker.source.idNotUnique=Quellen haben keine eindeutige ID.
ScenarioChecker.stairs.wrongTreadDim=Stufentiefe ist au\u00dferhalb des Definitionsbereichs. Passen Sie die Anzahl der Stufen entsprechend an.
ScenarioChecker.target.unused=Das Ziel wird von keiner Quelle verwendet. Entferne das Ziel um die Performance zu erh\u00f6hen
......
package org.vadere.simulator.control;
import org.jetbrains.annotations.NotNull;
import org.vadere.simulator.control.util.GroupSpawnArray;
import org.vadere.simulator.models.DynamicElementFactory;
import org.vadere.simulator.models.groups.GroupModel;
import org.vadere.state.attributes.scenario.AttributesDynamicElement;
import org.vadere.state.scenario.Source;
import org.vadere.state.scenario.Topography;
import org.vadere.util.geometry.PointPositioned;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VRectangle;
import org.vadere.util.geometry.shapes.VShape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
public class GroupSourceController extends SourceController {
private static final int NUMBER_OF_REPOSITION_TRIES = 20;
private final GroupModel groupModel;
private LinkedList<Integer> groupsToSpawn;
protected final GroupSpawnArray spawnArray;
public GroupSourceController(Topography scenario, Source source,
DynamicElementFactory dynamicElementFactory,
......@@ -24,6 +36,14 @@ public class GroupSourceController extends SourceController {
super(scenario, source, dynamicElementFactory, attributesDynamicElement, random);
this.groupModel = groupModel;
this.groupsToSpawn = new LinkedList<>();
VRectangle elementBound = new VRectangle(dynamicElementFactory.getDynamicElementRequiredPlace(new VPoint(0, 0)).getBounds2D());
this.spawnArray = new GroupSpawnArray(source.getShape(),
new VRectangle(0, 0, elementBound.getWidth(), elementBound.getHeight()),
dynamicElementFactory::getDynamicElementRequiredPlace,
this::testFreeSpace);
}
@Override
......@@ -38,7 +58,14 @@ public class GroupSourceController extends SourceController {
Iterator<Integer> iter = groupsToSpawn.iterator();
while (iter.hasNext()) {
int groupSize = iter.next();
List<VPoint> newGroup = spawnArray.getNextFreeGroup(groupSize, random, getDynElementsAtSource());
List<VPoint> newGroup = getRealRandomPositions(
groupSize,
random,
getDynElementsAtSource().stream()
.map(PointPositioned::getPosition)
.map(dynamicElementFactory::getDynamicElementRequiredPlace)
.collect(Collectors.toList())
);
if (newGroup.size() > 0) {
// add immediately to Scenario to update DynElementsAtSource
addElementToScenario(newGroup);
......@@ -51,7 +78,14 @@ public class GroupSourceController extends SourceController {
Iterator<Integer> iter = groupsToSpawn.iterator();
while (iter.hasNext()) {
int groupSize = iter.next();
List<VPoint> newGroup = spawnArray.getNextGroup(groupSize, random, getDynElementsAtSource());
List<VPoint> newGroup = spawnArray.getNextGroup(
groupSize,
random,
getDynElementsAtSource().stream()
.map(PointPositioned::getPosition)
.map(dynamicElementFactory::getDynamicElementRequiredPlace)
.collect(Collectors.toList())
);
if (newGroup.isEmpty())
throw new RuntimeException("Cannot spawn new Group. Source " + source.getId() + " is set " +
"to useFreeSpaceOnly == false but no space is left to spawn group without exactly" +
......@@ -68,7 +102,13 @@ public class GroupSourceController extends SourceController {
Iterator<Integer> iter = groupsToSpawn.iterator();
while (iter.hasNext()) {
int groupSize = iter.next();
List<VPoint> newGroup = spawnArray.getNextFreeGroup(groupSize, getDynElementsAtSource());
List<VPoint> newGroup = spawnArray.getNextFreeGroup(
groupSize,
getDynElementsAtSource().stream()
.map(PointPositioned::getPosition)
.map(dynamicElementFactory::getDynamicElementRequiredPlace)
.collect(Collectors.toList())
);
if (newGroup != null && !newGroup.isEmpty()) {
// add immediately to Scenario to update DynElementsAtSource
addElementToScenario(newGroup);
......@@ -81,7 +121,13 @@ public class GroupSourceController extends SourceController {
Iterator<Integer> iter = groupsToSpawn.iterator();
while (iter.hasNext()) {
int groupSize = iter.next();
List<VPoint> newGroup = spawnArray.getNextGroup(groupSize, getDynElementsAtSource());
List<VPoint> newGroup = spawnArray.getNextGroup(
groupSize,
getDynElementsAtSource().stream()
.map(PointPositioned::getPosition)
.map(dynamicElementFactory::getDynamicElementRequiredPlace)
.collect(Collectors.toList())
);
if (newGroup == null || newGroup.isEmpty())
throw new RuntimeException("Cannot spawn new Group. Source " + source.getId() + " is set " +
"to useFreeSpaceOnly == false but no space is left to spawn group without exactly" +
......@@ -99,6 +145,72 @@ public class GroupSourceController extends SourceController {
}
}
/**
* Computes random positions for ONE group based on the blockPedestrianShapes which contains
* the shapes representing the required space for the specified group size. For each required
* position the algorithms tries {@link GroupSourceController#NUMBER_OF_REPOSITION_TRIES} times
* to get a feasible free position for this group.
*
* @param groupSize size of group to spawn at a random positions
* @param random random generator
* @param blockPedestrianShapes the required space of other pedestrians
* @return list of Points representing the group members or an empty list if group cannot be
* placed after {@link GroupSourceController#NUMBER_OF_REPOSITION_TRIES} of tries.
*/
private List<VPoint> getRealRandomPositions(final int groupSize, @NotNull final Random random, @NotNull final List<VShape> blockPedestrianShapes) {
List<VPoint> randomPositions = new ArrayList<>(groupSize);
List<VPoint> defaultPoints = spawnArray.getDefaultGroup(groupSize);
for (int i = 0; i < NUMBER_OF_REPOSITION_TRIES; i++) {
randomPositions = moveRandomInSourceBound(defaultPoints, random);
boolean groupValid = randomPositions.stream()
.map(dynamicElementFactory::getDynamicElementRequiredPlace)
.allMatch(candidateShape ->
source.getShape().containsShape(candidateShape) &&
testFreeSpace(candidateShape, blockPedestrianShapes));
if (groupValid) {
return randomPositions;
}
}
return new ArrayList<>();
}
/**
* @param points default points of group members. First allowed position if the spawn would be
* based on the spawn grid.
* @param random random object
* @return transformed set of points based on a random translation and rotation within the
* bound of the source
*/
private List<VPoint> moveRandomInSourceBound(List<VPoint> points, @NotNull final Random random) {
Rectangle2D bound = source.getShape().getBounds2D();
double angle = random.nextDouble() * 2 * Math.PI;
VPoint p0 = points.get(0);
double dxBound = (bound.getX() - p0.getX());
double dyBound = (bound.getY() - p0.getY());
double dxRnd = random.nextDouble() * (bound.getMaxX() - bound.getX());
double dyRnd = random.nextDouble() * (bound.getMaxY() - bound.getY());
AffineTransform at0 = new AffineTransform();
at0.setToTranslation(dxBound, dyBound);
AffineTransform at1 = new AffineTransform();
at1.setToRotation(angle, bound.getX(), bound.getY());
AffineTransform at2 = new AffineTransform();
at2.setToTranslation(dxRnd, dyRnd);
List<VPoint> ret = new ArrayList<>();
points.stream().map(VPoint::asPoint2D).forEach(p -> {
at0.transform(p, p);
at1.transform(p, p);
at2.transform(p, p);
ret.add(new VPoint(p));
}
);
return ret;
}
private void addElementToScenario(List<VPoint> group) {
if (!group.isEmpty() && !isMaximumNumberOfSpawnedElementsReached()) {
addNewAgentToScenario(group);
......
......@@ -3,14 +3,17 @@ package org.vadere.simulator.control;
import org.jetbrains.annotations.NotNull;
import org.vadere.simulator.models.DynamicElementFactory;
import org.vadere.state.attributes.scenario.AttributesDynamicElement;
import org.vadere.state.scenario.Obstacle;
import org.vadere.state.scenario.Source;
import org.vadere.state.scenario.Topography;
import org.vadere.simulator.control.util.SingleSpawnArray;
import org.vadere.util.geometry.PointPositioned;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VRectangle;
import org.vadere.util.geometry.shapes.VShape;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Random;
......@@ -19,15 +22,21 @@ import java.util.stream.Collectors;
public class SingleSourceController extends SourceController {
private int numberToSpawn;
private DynamicElementFactory dynamicElementFactory;
private static final int NUMBER_OF_REPOSITION_TRIES = 10;
private static final int NUMBER_OF_POINT_SEARCH = 1_000; // todo based on shape and position of source
private SingleSpawnArray spawnArray;
public SingleSourceController(Topography scenario, Source source,
DynamicElementFactory dynamicElementFactory,
AttributesDynamicElement attributesDynamicElement,
Random random) {
super(scenario, source, dynamicElementFactory, attributesDynamicElement, random);
this.dynamicElementFactory = dynamicElementFactory;
VRectangle elementBound = new VRectangle(dynamicElementFactory.getDynamicElementRequiredPlace(new VPoint(0, 0)).getBounds2D());
this.spawnArray = new SingleSpawnArray(source.getShape(),
new VRectangle(0, 0, elementBound.getWidth(), elementBound.getHeight()),
this.dynamicElementFactory::getDynamicElementRequiredPlace,
this::testFreeSpace);
}
@Override
......@@ -35,18 +44,17 @@ public class SingleSourceController extends SourceController {
if (!isSourceFinished(simTimeInSec)) {
if (simTimeInSec >= timeOfNextEvent || numberToSpawn > 0) {
determineNumberOfSpawnsAndNextEvent(simTimeInSec);
List<VPoint> spawnPoints = new LinkedList<>();
List<VPoint> spawnPoints;
if (sourceAttributes.isSpawnAtRandomPositions()) {
if (sourceAttributes.isUseFreeSpaceOnly()) {
//spawnPoints = spawnArray.getNextFreeRandomSpawnPoints(numberToSpawn, random, getDynElementsAtSource());
spawnPoints = getRealRandomPositions(
numberToSpawn,
random,
getDynElementsAtSource().stream()
.map(element -> element.getPosition())
.map(position -> dynamicElementFactory.getDynamicElementRequiredPlace(position))
.map(PointPositioned::getPosition)
.map(dynamicElementFactory::getDynamicElementRequiredPlace)
.collect(Collectors.toList())
);
......@@ -54,19 +62,25 @@ public class SingleSourceController extends SourceController {
assert (numberToSpawn >= 0);
} else {
throw new IllegalArgumentException("use random position without free space only makes no sense.");
/*spawnPoints = spawnArray.getNextRandomSpawnPoints(numberToSpawn, random, getDynElementsAtSource());
numberToSpawn -= spawnPoints.size();
assert (numberToSpawn >= 0);*/
}
} else {
if (sourceAttributes.isUseFreeSpaceOnly()) {
spawnPoints = spawnArray.getNextFreeSpawnPoints(numberToSpawn, getDynElementsAtSource());
spawnPoints = getRealPositions(
numberToSpawn,
getDynElementsAtSource().stream()
.map(PointPositioned::getPosition)
.map(dynamicElementFactory::getDynamicElementRequiredPlace)
.collect(Collectors.toList())
);
numberToSpawn -= spawnPoints.size();
assert (numberToSpawn >= 0);
} else {
spawnPoints = spawnArray.getNextSpawnPoints(numberToSpawn, getDynElementsAtSource());
spawnPoints = getRealPositions(
numberToSpawn,
new ArrayList<>()
);
numberToSpawn -= spawnPoints.size();
assert (numberToSpawn >= 0);
}
......@@ -83,9 +97,26 @@ public class SingleSourceController extends SourceController {
}
}
private List<VPoint> getRealPositions(final int numberToSpawn, @NotNull final List<VShape> blockPedestrianShapes) {
List<VPoint> positions = new ArrayList<>(numberToSpawn);
for (int i = 0; i < numberToSpawn; i++) {
Optional<VPoint> optPosition = spawnArray.getNextPosition(blockPedestrianShapes);
if (optPosition.isPresent()) {
VPoint position = optPosition.get();
blockPedestrianShapes.add(dynamicElementFactory.getDynamicElementRequiredPlace(position));
positions.add(position);
}
}
return positions;
}
/**
* Computes numberToSpawn or less random positions based on the blockPedestrianShapes which contains the shapes representing the required space of each pedestrian.
* For each required position the algorithms tries {@link SingleSourceController#NUMBER_OF_REPOSITION_TRIES} times to get a feasible free position.
* Computes numberToSpawn or less random positions based on the blockPedestrianShapes which
* contains the shapes representing the required space of each pedestrian. For each required
* position the algorithms tries {@link SingleSourceController#NUMBER_OF_REPOSITION_TRIES} times
* to get a feasible free position.
*
* @param numberToSpawn number of required spawn positions
* @param random random generator
......@@ -95,8 +126,8 @@ public class SingleSourceController extends SourceController {
private List<VPoint> getRealRandomPositions(final int numberToSpawn, @NotNull final Random random, @NotNull final List<VShape> blockPedestrianShapes) {
List<VPoint> randomPositions = new ArrayList<>(numberToSpawn);
for(int i = 0; i < numberToSpawn; i++) {
Optional<VPoint> optRandomPosition = getNextRandomPosition(random, blockPedestrianShapes, NUMBER_OF_REPOSITION_TRIES);
for (int i = 0; i < numberToSpawn; i++) {
Optional<VPoint> optRandomPosition = getNextRandomPosition(random, blockPedestrianShapes, NUMBER_OF_POINT_SEARCH, NUMBER_OF_REPOSITION_TRIES);
if (optRandomPosition.isPresent()) {
VPoint randomPosition = optRandomPosition.get();
......@@ -108,16 +139,26 @@ public class SingleSourceController extends SourceController {
return randomPositions;
}
private Optional<VPoint> getNextRandomPosition(@NotNull final Random random, @NotNull final List<VShape> blockPedestrianShapes, final int tries) {
private Optional<VPoint> getNextRandomPosition(@NotNull final Random random, @NotNull final List<VShape> blockPedestrianShapes,
final int tries_find_valid_point, final int tries_reposition) {
Rectangle2D rec = source.getShape().getBounds2D();
for(int i = 0; i < tries; i++) {
VPoint randomPoint = new VPoint(rec.getMinX() + random.nextDouble() * rec.getWidth(), rec.getMinY() + random.nextDouble() * rec.getHeight());
VShape freeSpaceRequired = dynamicElementFactory.getDynamicElementRequiredPlace(randomPoint);
for (int i = 0; i < tries_reposition; i++) {
VShape freeSpaceRequired = null;
VPoint randomPoint = null;
boolean pointFound = false;
// find point in source boundary
int j = 0;
while (j < tries_find_valid_point && !pointFound) {
randomPoint = new VPoint(rec.getMinX() + random.nextDouble() * rec.getWidth(), rec.getMinY() + random.nextDouble() * rec.getHeight());
freeSpaceRequired = dynamicElementFactory.getDynamicElementRequiredPlace(randomPoint);
pointFound = source.getShape().containsShape(freeSpaceRequired);
j++;
}
// no intersection with other free spaces (obstacles & other pedestrians)
if(blockPedestrianShapes.stream().noneMatch(shape -> shape.intersects(freeSpaceRequired))
&& this.getTopography().getObstacles().stream().noneMatch(obs -> obs.getShape().intersects(freeSpaceRequired))) {
if (testFreeSpace(freeSpaceRequired, blockPedestrianShapes)) {
return Optional.of(randomPoint);
}
}
......@@ -125,7 +166,6 @@ public class SingleSourceController extends SourceController {
return Optional.empty();
}
@Override
protected boolean isQueueEmpty() {
return numberToSpawn == 0;
......
......@@ -8,16 +8,15 @@ import org.vadere.state.scenario.Agent;
import org.vadere.state.scenario.Car;
import org.vadere.state.scenario.DistributionFactory;
import org.vadere.state.scenario.DynamicElement;
import org.vadere.state.scenario.Obstacle;
import org.vadere.state.scenario.Pedestrian;
import org.vadere.state.scenario.Source;
import org.vadere.state.scenario.Topography;
import org.vadere.state.util.SpawnArray;
import org.vadere.util.geometry.LinkedCellsGrid;
import org.vadere.util.geometry.shapes.VCircle;
import org.vadere.util.geometry.shapes.VPoint;
import org.vadere.util.geometry.shapes.VRectangle;
import org.vadere.util.geometry.shapes.VShape;
import java.awt.geom.Rectangle2D;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
......@@ -25,21 +24,22 @@ import java.util.Random;
public abstract class SourceController {
protected final double NO_EVENT = Double.MAX_VALUE;
public static final double SPAWN_BUFFER_SIZE = 0.03;
// public static final double SPAWN_BUFFER_SIZE = 0.03;
protected final Source source;
private final DynamicElementFactory dynamicElementFactory;
protected final DynamicElementFactory dynamicElementFactory;
private final Topography topography;
protected final Random random;
/** <code>null</code>, if there is no next event. */
/**
* <code>null</code>, if there is no next event.
*/
protected Double timeOfNextEvent;
protected RealDistribution distribution;
protected final AttributesSource sourceAttributes;
protected final AttributesDynamicElement attributesDynamicElement;
protected int dynamicElementsCreatedTotal;
protected final SpawnArray spawnArray;
public SourceController(Topography scenario, Source source,
......@@ -53,11 +53,6 @@ public abstract class SourceController {
this.topography = scenario;
this.random = random;
VRectangle elementBound = new VRectangle(dynamicElementFactory.getDynamicElementRequiredPlace(new VPoint(0,0)).getBounds2D());
this.spawnArray = new SpawnArray(new VRectangle(source.getShape().getBounds2D()),
new VRectangle(0, 0,elementBound.getWidth() + SPAWN_BUFFER_SIZE, elementBound.getHeight() + SPAWN_BUFFER_SIZE));
timeOfNextEvent = sourceAttributes.getStartTime();
try {
DistributionFactory factory = DistributionFactory
......@@ -69,15 +64,16 @@ public abstract class SourceController {
}
}
/**
* @return List of DynamicElements within a circle, which surrounds the source shape completely
*/
protected List<DynamicElement> getDynElementsAtSource() {
Rectangle2D rec = source.getShape().getBounds2D();
double maxDim = rec.getWidth() > rec.getHeight() ? rec.getWidth() : rec.getHeight();
return getDynElementsAtPosition(source.getShape().getCentroid(), maxDim / 2);
return getDynElementsAtPosition(source.getShape().getCircumCircle());
}
protected List<DynamicElement> getDynElementsAtPosition(VPoint sourcePosition, double radius) {
protected List<DynamicElement> getDynElementsAtPosition(VCircle circumCircle) {
LinkedCellsGrid<DynamicElement> dynElements = topography.getSpatialMap(DynamicElement.class);
return dynElements.getObjects(sourcePosition, radius);
return dynElements.getObjects(circumCircle.getCenter(), circumCircle.getRadius());
}
abstract public void update(double simTimeInSec);
......@@ -107,6 +103,14 @@ public abstract class SourceController {
&& dynamicElementsCreatedTotal >= maxNumber;
}
protected boolean testFreeSpace(final VShape freeSpace, final List<VShape> blockPedestrianShapes) {
boolean pedOverlap = blockPedestrianShapes.stream().noneMatch(shape -> shape.intersects(freeSpace));
boolean obstOverlap = this.getTopography().getObstacles().stream()
.map(Obstacle::getShape).noneMatch(shape -> shape.intersects(freeSpace));
return pedOverlap && obstOverlap;
}
abstract protected boolean isQueueEmpty();
abstract protected void determineNumberOfSpawnsAndNextEvent(double simTimeInSec);
......@@ -131,8 +135,8 @@ public abstract class SourceController {
}
/**
* note that most models create their own pedestrians and ignore the attributes given here.
* the source is mostly used to set the position and target ids, not the attributes.
* note that most models create their own pedestrians and ignore the attributes given here. the
* source is mostly used to set the position and target ids, not the attributes.
*/
protected void addNewAgentToScenario(final List<VPoint> position) {
position.forEach(p -> addNewAgentToScenario(p));
......
package org.vadere.simulator.control.util;
import java.util.ArrayList;
import java.util.HashMap;
/**
* Simplifies placement of a group within a There are two placement strategies supported.
* <li>NoneOverlapping:</li>
* With this strategy a group spawns do not overlap. E.g The first 2x2 Group will start at (0,0) and
* the second at (2,0) under the assumption the underlying source is big enough to hold two 2x2
* groups in the x dimension.
*
* <li>Overlapping:</li>
* With this strategy the second group will start at (1,0)
*/
public class GroupPlacementHelper {
private final int boundedShapeGridCellsX;
private final int boundedShapeGridCellsY;
private final int groupSize;
private final int groupDimX;
private final int groupDimY;
private final int noneOverlapXGroupCount;
private final int noneOverlapYGroupCount;
private final int groupPlacementCountX;
private final int groupPlacementCountY;
private ArrayList<Integer> validSpawnPointsForGroupInBound;
public GroupPlacementHelper(int boundedShapeGridCellsX, int boundedShapeGridCellsY,
int groupSize, HashMap<Integer, Integer> validSpawnPointMapInBoundShape) {
if (groupSize > boundedShapeGridCellsX * boundedShapeGridCellsY)
throw new IndexOutOfBoundsException("GroupSize: " + groupSize
+ "to big for given Bound " + boundedShapeGridCellsX + " x " + boundedShapeGridCellsY);
this.boundedShapeGridCellsX = boundedShapeGridCellsX;
this.boundedShapeGridCellsY = boundedShapeGridCellsY;
this.groupSize = groupSize;
int dimGx, dimGy;
// dimension of smallest square contain a group of size groupSize
dimGx = (int) Math.ceil(Math.sqrt(groupSize));
dimGx = (dimGx > boundedShapeGridCellsX) ? boundedShapeGridCellsX : dimGx;
// dimGy set to minimize lost space in resulting rectangle (or square if groupSize is a square number)
dimGy = dimGx * (dimGx - 1) < groupSize ? dimGx : dimGx - 1;
this.groupDimX = dimGx;
this.groupDimY = dimGy;
this.noneOverlapXGroupCount = boundedShapeGridCellsX / dimGx;
this.noneOverlapYGroupCount = boundedShapeGridCellsY / dimGy;
//
this.groupPlacementCountX = boundedShapeGridCellsX - (dimGx - 1);
this.groupPlacementCountY = boundedShapeGridCellsY - (dimGy - 1);
validSpawnPointsForGroupInBound = new ArrayList<>();
for (int i = 0; i < getOverlappingGroupCount(); i++) { // i group spawn location
if (isGridCellWithinSource(validSpawnPointMapInBoundShape, i)) {
validSpawnPointsForGroupInBound.add(i);
}
}
}
/**
* @param validSpawnPointMapInBoundShape mapping of rectangular bound grid to valid coordinates