Commit 0820012e authored by Stefan Schuhbaeck's avatar Stefan Schuhbaeck
Browse files

add real random spawn to groups. add small spawnbuffer to spawn grid if

useRandomSpawn=false.
parent 72b623d6
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;
......@@ -9,7 +10,11 @@ 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;
......@@ -18,6 +23,8 @@ 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;
......@@ -51,7 +58,7 @@ public class GroupSourceController extends SourceController {
Iterator<Integer> iter = groupsToSpawn.iterator();
while (iter.hasNext()) {
int groupSize = iter.next();
List<VPoint> newGroup = spawnArray.getNextFreeGroup(
List<VPoint> newGroup = getRealRandomPositions(
groupSize,
random,
getDynElementsAtSource().stream()
......@@ -138,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);
......
......@@ -13,10 +13,10 @@ import org.vadere.state.scenario.Pedestrian;
import org.vadere.state.scenario.Source;
import org.vadere.state.scenario.Topography;
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.VShape;
import java.awt.geom.Rectangle2D;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
......@@ -64,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);
......
......@@ -85,6 +85,17 @@ public class GroupSpawnArray extends SpawnArray {
}
// Groups
public List<VPoint> getDefaultGroup(int groupSize) {
GroupPlacementHelper pHelper = getHelper(groupSize);
int firstValidIndex = pHelper.getValidSpawnPointsForGroupInBound().get(0);
List<VPoint> points = new ArrayList<>();
for (int i = 0; i < groupSize; i++) {
int index = validSpawnPointMapInBoundShape.get(pHelper.getOverlappingIndex(firstValidIndex, i));
VPoint candidatePoint = allowedSpawnPoints.get(index).clone();
points.add(candidatePoint);
}
return points;
}
@Deprecated
public LinkedList<VPoint> getNextGroup(int groupSize, @NotNull final List<VShape> blockPedestrianShapes) {
......@@ -160,7 +171,6 @@ public class GroupSpawnArray extends SpawnArray {
return points;
}
private GroupPlacementHelper getHelper(int groupSize) {
GroupPlacementHelper pHelper;
if (groupPlacementHelpers.containsKey(groupSize)) {
......
......@@ -13,6 +13,7 @@ import java.util.function.Function;
public class SpawnArray {
private static Logger logger = LogManager.getLogger(SpawnArray.class);
private static double SPAWN_BUFFER = 0.001;
protected final VRectangle spawnElementBound;
protected final VRectangle bound;
......@@ -49,8 +50,8 @@ public class SpawnArray {
allowedSpawnPoints = new ArrayList<>(xDim * yDim);
//offset left upper corner to center point.
eX = (xDim == 1) ? bound.getCenterX() : spawnElementBound.x + spawnElementBound.width / 2;
eY = (yDim == 1) ? bound.getCenterY() : spawnElementBound.y + spawnElementBound.height / 2;
eX = (xDim == 1) ? bound.getCenterX() : spawnElementBound.x + spawnElementBound.width / 2 + SPAWN_BUFFER;
eY = (yDim == 1) ? bound.getCenterY() : spawnElementBound.y + spawnElementBound.height / 2 + SPAWN_BUFFER;
logger.info(String.format(
"Dimension of Source is to small for at least one dimension to contain designated spawnElement with Bound (%.2f x %.2f) Set to (%d x %d)",
spawnElementBound.width, spawnElementBound.height, xDim, yDim));
......@@ -58,8 +59,8 @@ public class SpawnArray {
} else {
allowedSpawnPoints = new ArrayList<>(xDim * yDim);
//offset left upper corner to center point.
eX = spawnElementBound.x + spawnElementBound.width / 2;
eY = spawnElementBound.y + spawnElementBound.height / 2;
eX = spawnElementBound.x + spawnElementBound.width / 2 + SPAWN_BUFFER;
eY = spawnElementBound.y + spawnElementBound.height / 2 + SPAWN_BUFFER;
}
firstSpawnPoint = new VPoint(bound.x + eX, bound.y + eY);
......
......@@ -13,14 +13,25 @@ import org.vadere.state.attributes.models.AttributesCGM;
import org.vadere.state.attributes.scenario.AttributesAgent;
import org.vadere.state.attributes.scenario.AttributesSource;
import org.vadere.state.attributes.scenario.SourceTestAttributesBuilder;
import org.vadere.state.scenario.DynamicElement;
import org.vadere.state.scenario.Pedestrian;
import org.vadere.state.scenario.Source;
import org.vadere.state.scenario.Topography;
import org.vadere.state.util.StateJsonConverter;
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.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class GroupSourceControllerTest extends TestSourceControllerUsingConstantSpawnRate {
......@@ -401,4 +412,84 @@ public class GroupSourceControllerTest extends TestSourceControllerUsingConstant
}
private static final String sourceJson = "{\n" +
" \"id\" : 2,\n" +
" \"shape\" : {\n" +
" \"type\" : \"POLYGON\",\n" +
" \"points\" : [ {\n" +
" \"x\" : 506.39999999999964,\n" +
" \"y\" : 509.40000000000146\n" +
" }, {\n" +
" \"x\" : 502.10000000000036,\n" +
" \"y\" : 507.59999999999854\n" +
" }, {\n" +
" \"x\" : 501.60000000000036,\n" +
" \"y\" : 503.2999999999993\n" +
" }, {\n" +
" \"x\" : 503.89999999999964,\n" +
" \"y\" : 501.59999999999854\n" +
" }, {\n" +
" \"x\" : 508.7999999999993,\n" +
" \"y\" : 503.2999999999993\n" +
" }, {\n" +
" \"x\" : 510.39999999999964,\n" +
" \"y\" : 506.7000000000007\n" +
" }, {\n" +
" \"x\" : 506.2999999999993,\n" +
" \"y\" : 508.7000000000007\n" +
" } ]\n" +
" },\n" +
" \"interSpawnTimeDistribution\" : \"org.vadere.state.scenario.ConstantDistribution\",\n" +
" \"distributionParameters\" : [ 1.0 ],\n" +
" \"spawnNumber\" : 35,\n" +
" \"maxSpawnNumberTotal\" : -1,\n" +
" \"startTime\" : 0.0,\n" +
" \"endTime\" : 0.0,\n" +
" \"spawnAtRandomPositions\" : true,\n" +
" \"useFreeSpaceOnly\" : true,\n" +
" \"targetIds\" : [ 1 ],\n" +
" \"groupSizeDistribution\" : [ 0.1, 0.1, 0.1, 0.1, 0.6 ],\n" +
" \"dynamicElementType\" : \"PEDESTRIAN\"\n" +
"}";
@Test
public void testCentroid() {
AttributesSource attributesSource =
StateJsonConverter.deserializeObjectFromJson(sourceJson, AttributesSource.class);
Source source = new Source(attributesSource);
System.out.println(source.getShape().getBounds2D());
System.out.println(source.getShape().getCentroid());
System.out.println(source.getShape().getCircumCircle());
VRectangle bound = new VRectangle(source.getShape().getBounds2D());
System.out.println(bound.getCentroid());
}
@Test
public void testSource() {
AttributesSource attributesSource =
StateJsonConverter.deserializeObjectFromJson(sourceJson, AttributesSource.class);
VShape a = new VCircle(new VPoint(503.9265351385102, 506.9174145081969), 0.195);
Pedestrian pedA = new Pedestrian(new AttributesAgent(1), new Random(1));
pedA.setPosition(((VCircle) a).getCenter());
VShape b = new VCircle(new VPoint(504.19098333791044, 506.8493305279853), 0.195);
Pedestrian pedB = new Pedestrian(new AttributesAgent(2), new Random(1));
pedB.setPosition(((VCircle) b).getCenter());
assertTrue(a.intersects(b));
Source source = new Source(attributesSource);
Topography topography = new Topography();
topography.addElement(pedA);
topography.addElement(pedB);
LinkedCellsGrid grid = topography.getSpatialMap(DynamicElement.class);
VCircle center = source.getShape().getCircumCircle();
List<VPoint> inSource = grid.getObjects(center.getCenter(), center.getRadius());
assertEquals(2, inSource.size());
}
}
\ No newline at end of file
......@@ -257,12 +257,19 @@ public class VCircle implements VShape, ICircleSector {
@Override
public boolean intersects(VShape shape) {
if(shape instanceof VCircle) {
VCircle otherCircle = (VCircle)shape;
if (shape instanceof VCircle) {
VCircle otherCircle = (VCircle) shape;
return otherCircle.getCenter().distance(this.getCenter()) < (otherCircle.getRadius() + this.getRadius());
}
else {
} else {
return VShape.super.intersects(shape);
}
}
@Override
public String toString() {
return "VCircle{" +
"center=" + center +
", radius=" + radius +
'}';
}
}
......@@ -8,8 +8,6 @@ import org.vadere.util.geometry.GeometryUtils;
/**
* Immutable point.
*
*
*/
public class VPoint implements Cloneable {
......@@ -18,7 +16,8 @@ public class VPoint implements Cloneable {
public double x;
public double y;
public VPoint() {}
public VPoint() {
}
public VPoint(double x, double y) {
this.x = x;
......@@ -188,4 +187,8 @@ public class VPoint implements Cloneable {
public double distanceToOrigin() {
return Math.sqrt(x * x + y * y);
}
public Point2D.Double asPoint2D() {
return new Point2D.Double(x, y);
}
}
......@@ -4,6 +4,7 @@ import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
......@@ -11,8 +12,7 @@ import org.vadere.util.geometry.GeometryUtils;
import org.vadere.util.geometry.ShapeType;
/**
* Note: A polygon which has the same points as a rectangle is not
* equals to the rectangle.
* Note: A polygon which has the same points as a rectangle is not equals to the rectangle.
*/
public class VPolygon extends Path2D.Double implements VShape {
private static final long serialVersionUID = 6534837112398242609L;
......@@ -35,11 +35,9 @@ public class VPolygon extends Path2D.Double implements VShape {
}
/**
* Check whether the given polygon intersects with the open ball around
* "center" with given radius.
*
* @param center
* @param radius
* Check whether the given polygon intersects with the open ball around "center" with given
* radius.
*
* @return true if any point of the polygon lies within the open ball.
*/
public boolean intersects(VPoint center, double radius) {
......@@ -75,11 +73,11 @@ public class VPolygon extends Path2D.Double implements VShape {
/**
* Returns a list of all points of this geometry.
*
*
* @return A list of points.
*/
public List<VPoint> getPoints() {
List<VPoint> resultList = new LinkedList<VPoint>();
List<VPoint> resultList = new ArrayList<>(); // use ArrayList for better index retrieval
PathIterator iterator = this.getPathIterator(null);
double[] coords = new double[6];
......@@ -118,11 +116,7 @@ public class VPolygon extends Path2D.Double implements VShape {
}
/**
* Check whether all lines of this polygon intersect somewhere with the
* given polygon.
*
* @param intersectingPolygon
* @return
* Check whether all lines of this polygon intersect somewhere with the given polygon.
*/
public boolean intersects(final VPolygon intersectingPolygon) {
......@@ -231,7 +225,7 @@ public class VPolygon extends Path2D.Double implements VShape {
}
public LinkedList<VPolygon> borderAsShapes(double borderWidth,
double shapeShrinkOffset, double segmentGrowOffset) {
double shapeShrinkOffset, double segmentGrowOffset) {
LinkedList<VPolygon> border = new LinkedList<VPolygon>();
PathIterator vertexItr = getPathIterator(null);
double lastVertex[] = null;
......@@ -261,61 +255,61 @@ public class VPolygon extends Path2D.Double implements VShape {
segmentVertices
.moveTo(lastVertex[0]
- delta[0]
- delta[0]
* segmentGrowOffset
- delta[1]
- delta[1]
* (borderOffset + shapeShrinkOffset + segmentGrowOffset),
lastVertex[1]
- delta[1]
* segmentGrowOffset
* segmentGrowOffset
+ delta[0]
* (borderOffset + shapeShrinkOffset + segmentGrowOffset));
* (borderOffset + shapeShrinkOffset + segmentGrowOffset));
segmentVertices
.lineTo(lastVertex[0]
- delta[0]
- delta[0]
* segmentGrowOffset
+ delta[1]
+ delta[1]
* (borderOffset - shapeShrinkOffset + segmentGrowOffset),
lastVertex[1]
- delta[1]
* segmentGrowOffset
* segmentGrowOffset
- delta[0]
* (borderOffset - shapeShrinkOffset + segmentGrowOffset));
* (borderOffset - shapeShrinkOffset + segmentGrowOffset));
segmentVertices
.lineTo(curVertex[0]
+ delta[0]
+ delta[0]
* segmentGrowOffset
+ delta[1]
+ delta[1]
* (borderOffset - shapeShrinkOffset + segmentGrowOffset),
curVertex[1]
+ delta[1]
* segmentGrowOffset
* segmentGrowOffset
- delta[0]
* (borderOffset - shapeShrinkOffset + segmentGrowOffset));
* (borderOffset - shapeShrinkOffset + segmentGrowOffset));
segmentVertices
.lineTo(curVertex[0]
+ delta[0]
+ delta[0]
* segmentGrowOffset
- delta[1]
- delta[1]
* (borderOffset + shapeShrinkOffset + segmentGrowOffset),
curVertex[1]
+ delta[1]
* segmentGrowOffset
* segmentGrowOffset
+ delta[0]
* (borderOffset + shapeShrinkOffset + segmentGrowOffset));
* (borderOffset + shapeShrinkOffset + segmentGrowOffset));
/* Insert first vertex as last too. */
segmentVertices
.lineTo(lastVertex[0]
- delta[0]
- delta[0]
* segmentGrowOffset
- delta[1]
- delta[1]
* (borderOffset + shapeShrinkOffset + segmentGrowOffset),
lastVertex[1]
- delta[1]
* segmentGrowOffset
* segmentGrowOffset
+ delta[0]
* (borderOffset + shapeShrinkOffset + segmentGrowOffset));
* (borderOffset + shapeShrinkOffset + segmentGrowOffset));
border.add(new VPolygon(segmentVertices));
......@@ -414,6 +408,9 @@ public class VPolygon extends Path2D.Double implements VShape {
return new VPolygon(new Path2D.Double(this, transform));
}
/**
* based on https://stackoverflow.com/a/2792459
*/
@Override
public VPoint getCentroid() {
List<VPoint> pointList = getPoints();
......@@ -425,11 +422,24 @@ public class VPolygon extends Path2D.Double implements VShape {
- pointList.get(i).getY() * pointList.get(i + 1).getX();
xValue += (pointList.get(i).getX() + pointList.get(i + 1).getX())
* (pointList.get(i).getX() * pointList.get(i + 1).getY()
- pointList.get(i).getY() * pointList.get(i + 1).getX());
- pointList.get(i).getY() * pointList.get(i + 1).getX());
yValue += (pointList.get(i).getY() + pointList.get(i + 1).getY())
* (pointList.get(i).getX() * pointList.get(i + 1).getY()
- pointList.get(i).getY() * pointList.get(i + 1).getX());
- pointList.get(i).getY() * pointList.get(i + 1).getX());
}
// last with first point. This is outside of the loop to remove modulo operation
// only needed in the last loop.
int i = pointList.size() - 1;
area += pointList.get(i).getX() * pointList.get(0).getY()
- pointList.get(i).getY() * pointList.get(0).getX();
xValue += (pointList.get(i).getX() + pointList.get(0).getX())
* (pointList.get(i).getX() * pointList.get(0).getY()
- pointList.get(i).getY() * pointList.get(0).getX());
yValue += (pointList.get(i).getY() + pointList.get(0).getY())
* (pointList.get(i).getX() * pointList.get(0).getY()
- pointList.get(i).getY() * pointList.get(0).getX());
area /= 2;
xValue /= (6 * area);
yValue /= (6 * area);
......
......@@ -2,12 +2,12 @@ package org.vadere.util.geometry.shapes;
import java.awt.Shape;
import java.awt.geom.Area;
import java.awt.geom.Rectangle2D;
import org.vadere.util.geometry.ShapeType;
/**
* Geometric shape and position.
*
*/
public interface VShape extends Shape, Cloneable {
double distance(VPoint point);
......@@ -28,7 +28,18 @@ public interface VShape extends Shape, Cloneable {
ShapeType getType();
default boolean intersects(VShape shape){
/**
* {@link VCircle} containing all points of underling shape. similar to bound but a circle
* rather than a Rectangle.
*/
default VCircle getCircumCircle() {
Rectangle2D bound = getBounds2D();
double radius =
Math.sqrt(bound.getWidth() * bound.getWidth() + bound.getHeight() * bound.getHeight());
return new VCircle(new VPoint(bound.getCenterX(), bound.getCenterY()), radius);
}
default boolean intersects(VShape shape) {
Area thisShape = new Area(this);
Area otherShape = new Area(shape);
Area thisShapeCpy = new Area(this);
......@@ -36,14 +47,14 @@ public interface VShape extends Shape, Cloneable {