/*
* File name: AbstractEnvironment2DFast.java (package eas.simulation.spatial.sim2D.standardEnvironments)
* Author(s): Lukas König
* Java version: 6.0
* Generation date: 14.02.2011 (18:27:21)
*
* (c) This file and the EAS (Easy Agent Simulation) framework containing it
* is protected by Creative Commons by-nc-sa license. Any altered or
* further developed versions of this file have to meet the agreements
* stated by the license conditions.
*
* In a nutshell
* -------------
* You are free:
* - to Share -- to copy, distribute and transmit the work
* - to Remix -- to adapt the work
*
* Under the following conditions:
* - Attribution -- You must attribute the work in the manner specified by the
* author or licensor (but not in any way that suggests that they endorse
* you or your use of the work).
* - Noncommercial -- You may not use this work for commercial purposes.
* - Share Alike -- If you alter, transform, or build upon this work, you may
* distribute the resulting work only under the same or a similar license to
* this one.
*
* + Detailed license conditions (Germany):
* http://creativecommons.org/licenses/by-nc-sa/3.0/de/
* + Detailed license conditions (unported):
* http://creativecommons.org/licenses/by-nc-sa/3.0/deed.en
*
* This header must be placed in the beginning of any version of this file.
*/
package eas.simulation.spatial.sim2D.standardEnvironments;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import eas.math.geometry.LineSegment2D;
import eas.math.geometry.Polygon2D;
import eas.math.geometry.Rectangle2D;
import eas.math.geometry.Vector2D;
import eas.simulation.spatial.sim2D.standardAgents.AbstractAgent2D;
import eas.simulation.standardEnvironments.AbstractEnvironment;
import eas.startSetup.ParCollection;
import eas.startSetup.SingleParameter;
import eas.startSetup.parameterDatatypes.Datatypes;
/**
* This class implements a 2D environment that overrides the methods for
* agent and ray collisions to allow for a more efficient way of calculating
* such collisions.<BR>
* <BR>
* This improvement is achieved by a rigorous simplification of the agent
* shapes, namely allowing rectangles (that can be positioned horizontally or
* vertically) and circles only. Any agent added to <code>this</code> is
* first treated as a rectangle, namely using its own bounding box as shape.
* To make agents being treated as a circle, they have to be specially
* declared. They are treated as circles with a diameter of their bounding
* box width.
*
* @author Lukas König
*
*/
public abstract class AbstractEnvironment2DFast<AgentType extends AbstractAgent2D<?>> extends AbstractEnvironment2D<AgentType> {
private static final long serialVersionUID = -3622561922205972079L;
/**
* The matrix of tiles, each of which holds a list of agents.
*/
private LinkedList<Integer>[][] tiles;
private Rectangle2D[][] tileCoordinates;
private int tileNumHorizontally;
private int tileNumVertically;
private Rectangle2D localBoundingBox;
public AbstractEnvironment2DFast(int id, ParCollection params) {
this("AbstractEnvironment2DFast_" + id, id, null, params);
}
/**
* @param ident
* @param params
* @param fatherEnvironment The environment this environment ist part of
* (may be null if this is a top level
* environment (most common case)).
*/
@SuppressWarnings("unchecked")
public AbstractEnvironment2DFast(String ident, int id, AbstractEnvironment<?> fatherEnv, ParCollection params) {
super(ident, id, fatherEnv, params);
tileNumHorizontally = params.getParValueInt("TilesHorizontally");
tileNumVertically = params.getParValueInt("TilesVertically");
boundingBuffer = params.getParValueVector2D("BoundingBuffer");
showTiles = params.getParValueBoolean("ShowTiles?");
epsilon = params.getParValueDouble("Epsilon");
showRays = params.getParValueBoolean("ShowSensorRays?");
tiles = new LinkedList[tileNumHorizontally][tileNumVertically];
tileCoordinates = new Rectangle2D[tileNumHorizontally][tileNumVertically];
agentTouchingTiles = new HashMap<Integer, LinkedList<Vector2D>>();
this.refreshLocalBoundingBox();
}
private Vector2D boundingBuffer;
private Rectangle2D oldBoundingBox;
private double epsilon;
/**
* This method should be fast if no changes to bounding box have been made.
*/
@SuppressWarnings("unchecked")
private void refreshLocalBoundingBox() {
Rectangle2D newBoundingBox = super.getBoundingBox(false);
if (newBoundingBox == null) {
newBoundingBox = new Rectangle2D(new Vector2D(0, 0), new Vector2D(1, 1));
this.localBoundingBox = newBoundingBox;
}
if (this.tileNumHorizontally <= 0) {
// StaticMethods.logDebug("Number of tiles horizontally must be positive, is: " + this.tileNumHorizontally, this.getParCollection());
return;
}
if (this.tileNumVertically <= 0) {
// StaticMethods.logDebug("Number of tiles vertically must be positive, is: " + this.tileNumVertically, this.getParCollection());
return;
}
// If bounding box changed (==> refresh and reset tile coordinates).
if (this.oldBoundingBox == null
|| Math.abs(this.oldBoundingBox.upperLeftCorner().x - newBoundingBox.upperLeftCorner().x) > epsilon
|| Math.abs(this.oldBoundingBox.upperLeftCorner().y - newBoundingBox.upperLeftCorner().y) > epsilon
|| Math.abs(this.oldBoundingBox.lowerRightCorner().x - newBoundingBox.lowerRightCorner().x) > epsilon
|| Math.abs(this.oldBoundingBox.lowerRightCorner().y - newBoundingBox.lowerRightCorner().y) > epsilon) {
double x1 = newBoundingBox.upperLeftCorner().x - boundingBuffer.x,
x2 = newBoundingBox.lowerRightCorner().x + boundingBuffer.x,
y1 = newBoundingBox.upperLeftCorner().y - boundingBuffer.y,
y2 = newBoundingBox.lowerRightCorner().y + boundingBuffer.y;
// System.out.println("bound");
tiles = new LinkedList[tileNumHorizontally][tileNumVertically];
this.localBoundingBox = new Rectangle2D(new Vector2D(x1, y1), new Vector2D(x2, y2));
this.oldBoundingBox = new Rectangle2D(newBoundingBox);
this.tileCoordinates = new Rectangle2D[tileNumHorizontally][tileNumVertically];
this.agentTouchingTiles.clear();
}
}
public synchronized Rectangle2D getTileRectangle(final Vector2D tileID) {
// if (tileID == null
// || tileID.x < 0
// || tileID.y < 0
// || tileID.x >= this.tileNumHorizontally
// || tileID.y >= this.tileNumVertically) {
// return null;
// }
if (this.tileCoordinates[(int) tileID.x][(int) tileID.y] == null) {
double left = this.localBoundingBox.upperLeftCorner().x;
double right = this.localBoundingBox.lowerRightCorner().x;
double up = this.localBoundingBox.upperLeftCorner().y;
double down = this.localBoundingBox.lowerRightCorner().y;
double tileWidth = (right - left) / this.tileNumHorizontally;
double tileHeight = (down - up) / this.tileNumVertically;
Vector2D upperLeft = new Vector2D(left + tileWidth * tileID.x, up
+ tileHeight * tileID.y);
Vector2D lowerRight = new Vector2D(upperLeft.x + tileWidth,
upperLeft.y + tileHeight);
this.tileCoordinates[(int) tileID.x][(int) tileID.y] = new Rectangle2D(
upperLeft, lowerRight);
}
return this.tileCoordinates[(int) tileID.x][(int) tileID.y];
}
public Vector2D getTileID(final Vector2D pointInsideTile) {
if (pointInsideTile == null
|| !this.localBoundingBox.isPointInside(pointInsideTile)) {
return null;
}
double left = this.localBoundingBox.upperLeftCorner().x;
double right = this.localBoundingBox.lowerRightCorner().x;
double up = this.localBoundingBox.upperLeftCorner().y;
double down = this.localBoundingBox.lowerRightCorner().y;
double tileWidth = (right - left) / this.tileNumHorizontally;
double tileHeight = (down - up) / this.tileNumVertically;
int xID = (int) ((pointInsideTile.x - left) / tileWidth);
int yID = (int) ((pointInsideTile.y - up) / tileHeight);
return new Vector2D(xID, yID);
}
private HashMap<Integer, LinkedList<Vector2D>> agentTouchingTiles;
public LinkedList<Vector2D> getTilesTouchingAgent(final int agentID) {
LinkedList<Vector2D> oldTiles = this.agentTouchingTiles.get(agentID);
if (oldTiles != null) {
return oldTiles;
}
try {
return this.getTilesTouchingAgent(
agentID,
this.getAgentPosition(agentID),
this.getAgentAngle(agentID),
this.getAgentScale(agentID));
} catch (Exception e) {
return new LinkedList<Vector2D>();
}
}
public synchronized LinkedList<Vector2D> getTilesTouchingAgent(
final int agentID,
final Vector2D pos,
final double angle,
final Vector2D scale) {
return this.getTilesTouchingAgent(agentID, this.getAgentShapeInEnvironment(agentID, pos, scale, angle).getBoundingBox());
}
public synchronized LinkedList<Vector2D> getTilesTouchingAgent(
final int agentID,
final Rectangle2D agentBoundingBox) {
LinkedList<Vector2D> tiles = new LinkedList<Vector2D>();
// TODO: This works fine for all agents, but can be optimized e.g. for circle agents.
if (this.isAgentShapeRectangle(agentID) || this.isAgentShapeCircle(agentID)) {
double li = agentBoundingBox.upperLeftCorner().x;
double re = agentBoundingBox.lowerRightCorner().x;
double ob = agentBoundingBox.upperLeftCorner().y;
double un = agentBoundingBox.lowerRightCorner().y;
Vector2D liobTile = this.getTileID(new Vector2D(li, ob));
Vector2D reunTile = this.getTileID(new Vector2D(re, un));
if (liobTile != null && reunTile != null) {
for (double i = liobTile.x; i <= reunTile.x; i++) {
for (double j = liobTile.y; j <= reunTile.y; j++) {
tiles.add(new Vector2D(i, j));
}
}
}
this.agentTouchingTiles.put(agentID, tiles);
} else if (this.isAgentShapeCircle(agentID)) {
}
return tiles;
}
private void removeAgentFromTileLists(final Integer agentID) {
this.removeAgentFromTileLists(
agentID,
this.getAgentShapeInEnvironment(agentID, this.getAgentPosition(agentID), this.getAgentScale(agentID), this.getAgentAngle(agentID)).getBoundingBox());
}
private void removeAgentFromTileLists(
final Integer agentID,
final Rectangle2D agentBoundingBox) {
LinkedList<Vector2D> tileList = this.getTilesTouchingAgent(agentID, agentBoundingBox);
for (Vector2D tileID : tileList) {
int tileX = (int) tileID.x;
int tileY = (int) tileID.y;
if (this.tiles[tileX][tileY] != null && this.tiles[tileX][tileY].size() > 0) {
// if (this.tiles[tileX][tileY].contains(agentID)) {
this.tiles[tileX][tileY].remove(agentID);
// }
}
}
}
public HashSet<Integer> getAgentsTouchingTilesFromAll(final List<Vector2D> tileIDs) {
HashSet<Integer> agentList = new HashSet<Integer>();
for (Vector2D v : tileIDs) {
agentList.addAll(this.getAgentsTouchingTile(v));
}
return agentList;
}
public synchronized List<Integer> getAgentsTouchingTile(final Vector2D tileID) {
int tileX = (int) tileID.x;
int tileY = (int) tileID.y;
if (this.tiles[tileX][tileY] == null) {
// This is not efficient and should be called rarely.
// System.out.println("inefficient" + i++);
this.tiles[tileX][tileY] = new LinkedList<Integer>();
for (AgentType a : this.getAgents()) {
if (this.getTilesTouchingAgent(a.id()).contains(tileID)) {
this.tiles[tileX][tileY].add(a.id());
}
}
}
return this.tiles[tileX][tileY];
}
private void registerAgentToTileLists(final int agentID) {
this.registerAgentToTileLists(
agentID,
this.getAgentPosition(agentID),
this.getAgentAngle(agentID),
this.getAgentScale(agentID));
}
private void registerAgentToTileLists(
final int agentID,
final Vector2D pos,
final double angle,
final Vector2D scale) {
LinkedList<Vector2D> tileList = this.getTilesTouchingAgent(agentID, pos, angle, scale);
for (Vector2D tileID : tileList) {
int tileX = (int) tileID.x;
int tileY = (int) tileID.y;
if (this.tiles[tileX][tileY] == null) {
// This is inefficient.
this.getTilesTouchingAgent(agentID, pos, angle, scale);
} else {
this.tiles[tileX][tileY].add(agentID);
}
}
}
@Override
public synchronized boolean addAgent(AgentType agent,
Vector2D position, double angle, Vector2D scale) {
boolean result = super.addAgent(agent, position, angle, scale);
if (result) {
this.refreshLocalBoundingBox();
this.registerAgentToTileLists(agent.id(), position, angle, scale);
}
return result;
}
@Override
public synchronized boolean setAgentAngleAndPositionAndScaleSimultaneously(
int agentID, Vector2D pos, double angle, Vector2D scale) {
this.refreshLocalBoundingBox();
this.removeAgentFromTileLists(agentID);
this.registerAgentToTileLists(agentID, pos, angle, scale);
boolean result = super.setAgentAngleAndPositionAndScaleSimultaneously(agentID, pos, angle, scale);
if (result) {
// if (this.isCollidingAgent(agentID)) {
// this.removeAgentFromTileLists(agentID);
// this.agentTouchingTiles.remove(agentID);
// }
// this.refreshLocalBoundingBox();
// if (this.isCollidingAgent(agentID)) {
// this.registerAgentToTileLists(agentID, pos, angle, scale);
// }
} else {
this.removeAgentFromTileLists(agentID, this.getAgentShapeInEnvironment(agentID, pos, scale, angle).getBoundingBox());
this.registerAgentToTileLists(agentID);
}
return result;
}
@Override
public synchronized boolean removeAgent(int agentID) {
Vector2D pos = this.getAgentPosition(agentID);
double angle = this.getAgentAngle(agentID);
Vector2D scale = this.getAgentScale(agentID);
if (scale == null) {
scale = new Vector2D(1, 1);
}
Rectangle2D boundingBox = this.getAgentShapeInEnvironment(agentID, pos, scale, angle).getBoundingBox();
boolean result = super.removeAgent(agentID);
if (result) {
if (this.agentTouchingTiles.containsKey(agentID)) {
this.removeAgentFromTileLists(agentID, boundingBox);
this.agentTouchingTiles.remove(agentID);
}
this.refreshLocalBoundingBox();
}
return result;
}
@SuppressWarnings("unused")
private boolean isAgentShapeCircle(final int agentID) {
return false;
}
@SuppressWarnings("unused")
private boolean isAgentShapeRectangle(final int agentID) {
return true;
}
@Override
public boolean collides(
AgentType a1,
Vector2D pos1,
double angle1,
Vector2D scale1,
AgentType a2,
Vector2D pos2,
double angle2,
Vector2D scale2) {
return super.collides(a1, pos1, angle1, scale1, a2, pos2, angle2, scale2);
}
@Override
public synchronized boolean collides(
AgentType agent,
Vector2D pos,
double angle,
Vector2D scale) {
if (!this.isCollidingAgent(agent.id())) {
return false;
}
int agentID = agent.id();
List<Vector2D> tileList = this.getTilesTouchingAgent(agentID, pos, angle, scale);
HashSet<Integer> agentSet = this.getAgentsTouchingTilesFromAll(tileList);
for (int i : agentSet) {
try {
if (this.collides(
agent,
pos,
angle,
scale,
this.getAgent(i),
this.getAgentPosition(i),
this.getAgentAngle(i),
this.getAgentScale(i))) {
return true;
}
} catch (Exception e) {
}
}
return false;
}
private HashSet<Polygon2D> rays = new HashSet<Polygon2D>();
/**
* TODO: Es kann immer noch passieren, dass an Eckpunkten der Strahl eine
* Kachel überspringt! Dafür wird der Korrekturparameter benötigt.
*/
@Override
public CollisionData nearestRayCollision(
Vector2D position,
Vector2D direction,
List<AgentType> ignore) {
final double korrektor = Math.max(localBoundingBox.getWidth(), localBoundingBox.getHeight()) / 2000;
CollisionData data;
// data = super.nearestRayCollision(position, direction, ignore);
ArrayList<Integer> ignoreIDs = new ArrayList<Integer>(ignore.size());
for (AgentType agent : ignore) {
ignoreIDs.add(agent.id());
}
double maxdist = 0;
double distanz;
Rectangle2D bound;
/*
* Find a point that is clearly beyond the last point occupied
* by a part of an agent in ray direction. Determine by this a
* maximal possible distance of a ray collision.
*/
for (AgentType a : this.getAgents()) {
bound = a.getAgentShape().getBoundingBox();
distanz = position.distance(
this.getAgentPosition(a.id()))
+ bound.getWidth()
+ bound.getHeight();
if (distanz > maxdist) {
maxdist = distanz;
}
}
Vector2D furthest = new Vector2D(position);
Vector2D furthestDir = new Vector2D(direction);
furthestDir.setLength(maxdist);
furthest.translate(furthestDir);
LineSegment2D s = new LineSegment2D(position, furthest);
Vector2D positionTile = this.getTileID(position);
List<Integer> touchingAgents = new LinkedList<Integer>();
touchingAgents.addAll(this.getAgentsTouchingTile(positionTile));
touchingAgents.removeAll(ignoreIDs);
CollisionData nearestTouching = this.nearestTouchingIfSameTile(touchingAgents, s, position, positionTile);
Vector2D lastTile = null;
// Pixellinie auf Tiles zeichnen.
if (nearestTouching == null || nearestTouching.getAgent() == null) {
double x1 = s.getAnfPkt().x;
double y1 = s.getAnfPkt().y;
double x2 = s.getEndPkt().x;
double y2 = s.getEndPkt().y;
int xx1, xx2, yy1, yy2, aktXi, aktYi;
double aktX, aktY, schrittX, schrittY;
// double tileLaengeX = this.localBoundingBox.getBreite() / this.tileNumHorizontally;
// double tileLaengeY = this.localBoundingBox.getHoehe() / this.tileNumVertically;
if (Math.abs(x2 - x1) > Math.abs(y2 - y1)) {
xx1 = (int) Math.round(x1);
yy1 = (int) Math.round(y1);
xx2 = (int) Math.round(x2);
yy2 = (int) Math.round(y2);
schrittY = korrektor * ((double) yy2 - (double) yy1) / ((double) xx2 - (double) xx1);
aktY = yy1;
if (x1 < x2) {
for (aktX = xx1; aktX <= xx2; aktX += korrektor) {
aktXi = (int) Math.round(aktX);
aktYi = (int) Math.round(aktY);
positionTile = this.getTileID(new Vector2D(aktXi, aktYi));
/*
* Dieser Fall tritt auf, wenn keine Agenten innerhalb der
* bounding Box getroffen wurden.
*/
if (positionTile == null) {
nearestTouching = new CollisionData(null, null);
break;
}
if (!positionTile.equals(lastTile)) {
if (showRays && showTiles && isVisualized()) {
this.rays.add(this.getTileRectangle(positionTile)
.toPol2D());
}
touchingAgents = new LinkedList<Integer>();
touchingAgents.addAll(this.getAgentsTouchingTile(positionTile));
touchingAgents.removeAll(ignoreIDs);
nearestTouching = this.nearestTouchingIfSameTile(
touchingAgents, s, position, positionTile);
if (nearestTouching != null) {
break;
}
}
lastTile = positionTile;
aktY = aktY + schrittY;
}
} else {
for (aktX = xx1; aktX >= xx2; aktX -= korrektor) {
aktXi = (int) Math.round(aktX);
aktYi = (int) Math.round(aktY);
positionTile = this.getTileID(new Vector2D(aktXi, aktYi));
/*
* Dieser Fall tritt auf, wenn keine Agenten innerhalb der
* bounding Box getroffen wurden.
*/
if (positionTile == null) {
nearestTouching = new CollisionData(null, null);
break;
}
if (!positionTile.equals(lastTile)) {
if (showRays && showTiles && isVisualized()) {
this.rays.add(this.getTileRectangle(
positionTile).toPol2D());
}
touchingAgents = new LinkedList<Integer>();
touchingAgents.addAll(this.getAgentsTouchingTile(positionTile));
touchingAgents.removeAll(ignoreIDs);
nearestTouching = this.nearestTouchingIfSameTile(
touchingAgents, s, position, positionTile);
if (nearestTouching != null) {
break;
}
}
lastTile = positionTile;
aktY = aktY - schrittY;
}
}
} else {
xx1 = (int) Math.round(x1);
yy1 = (int) Math.round(y1);
xx2 = (int) Math.round(x2);
yy2 = (int) Math.round(y2);
schrittX = korrektor * ((double) xx2 - (double) xx1) / ((double) yy2 - (double) yy1);
aktX = xx1;
if (y1 < y2) {
for (aktY = yy1; aktY <= yy2; aktY += korrektor) {
aktXi = (int) Math.round(aktX);
aktYi = (int) Math.round(aktY);
positionTile = this.getTileID(new Vector2D(aktXi, aktYi));
/*
* Dieser Fall tritt auf, wenn keine Agenten innerhalb der
* bounding Box getroffen wurden.
*/
if (positionTile == null) {
nearestTouching = new CollisionData(null, null);
break;
}
if (!positionTile.equals(lastTile)) {
if (showRays && showTiles && isVisualized()) {
this.rays.add(this.getTileRectangle(
positionTile).toPol2D());
}
touchingAgents = new LinkedList<Integer>();
touchingAgents.addAll(this.getAgentsTouchingTile(positionTile));
touchingAgents.removeAll(ignoreIDs);
nearestTouching = this.nearestTouchingIfSameTile(
touchingAgents, s, position, positionTile);
if (nearestTouching != null) {
break;
}
}
lastTile = positionTile;
aktX = aktX + schrittX;
}
} else {
for (aktY = yy1; aktY >= yy2; aktY -= korrektor) {
aktXi = (int) Math.round(aktX);
aktYi = (int) Math.round(aktY);
positionTile = this.getTileID(new Vector2D(aktXi, aktYi));
/*
* Dieser Fall tritt auf, wenn keine Agenten innerhalb der
* bounding Box getroffen wurden.
*/
if (positionTile == null) {
nearestTouching = new CollisionData(null, null);
break;
}
if (!positionTile.equals(lastTile)) {
if (showRays && showTiles && isVisualized()) {
this.rays.add(this.getTileRectangle(
positionTile).toPol2D());
}
touchingAgents = new LinkedList<Integer>();
touchingAgents.addAll(this.getAgentsTouchingTile(positionTile));
touchingAgents.removeAll(ignoreIDs);
nearestTouching = this.nearestTouchingIfSameTile(
touchingAgents, s, position, positionTile);
if (nearestTouching != null) {
break;
}
}
lastTile = positionTile;
aktX = aktX - schrittX;
}
}
}
}
data = nearestTouching;
if (showRays && isVisualized()) {
if (data.getCollisionPoint() != null) {
Polygon2D pol = new Polygon2D();
pol.add(new Vector2D(position));
pol.add(data.getCollisionPoint());
rays.add(pol);
}
}
return data;
}
private CollisionData nearestTouchingIfSameTile(
List<Integer> agents,
LineSegment2D ray,
Vector2D position,
Vector2D currentTileID) {
double distanz;
Polygon2D p;
HashMap<AgentType, LinkedList<Vector2D>> schnittpunkte
= new HashMap<AgentType, LinkedList<Vector2D>>();
// Find all intersections of ray with agents.
for (int agentID : agents) {
p = this.getAgentShapeInEnvironment(agentID);
if (this.isTouchable(ray, p.getBoundingBox())) {
schnittpunkte.put(this.getAgent(agentID), p.intersectAll(ray));
}
}
double minDistance = Double.MAX_VALUE;
Vector2D minVector = null;
AgentType minAgent = null;
for (AgentType a : schnittpunkte.keySet()) {
for (Vector2D v : schnittpunkte.get(a)) {
distanz = position.distance(v);
if (distanz < minDistance) {
minDistance = distanz;
minVector = v;
minAgent = a;
}
}
}
if (minVector != null && !this.getTileID(minVector).equals(currentTileID)) {
return null;
}
if (minAgent == null) {
return null;
} else {
return new CollisionData(minAgent, minVector);
}
}
@Override
public List<SingleParameter> getParameters() {
List<SingleParameter> list = super.getParameters();
list.add(new SingleParameter("TilesHorizontally", Datatypes.INTEGER, 10, "Number of tiles horizontally for collision calculation.", "ABSTRACT_ENVIRONMENT_2D_FAST"));
list.add(new SingleParameter("TilesVertically", Datatypes.INTEGER, 10, "Number of tiles vertically for collision calculation.", "ABSTRACT_ENVIRONMENT_2D_FAST"));
list.add(new SingleParameter("BoundingBuffer", Datatypes.VECTOR2D, new Vector2D(1, 1), "Buffer added left, right, above and below the bounding box.", "ABSTRACT_ENVIRONMENT_2D_FAST"));
list.add(new SingleParameter("ShowTiles?", Datatypes.BOOLEAN, false, "If the tiles are shown in visualization.", "ABSTRACT_ENVIRONMENT_2D_FAST"));
list.add(new SingleParameter("Epsilon", Datatypes.DOUBLE, 0.01, "The maximal unrecognized change value to the bounding box (should be less than the bounding buffer).", "ABSTRACT_ENVIRONMENT_2D_FAST"));
list.add(new SingleParameter("ShowSensorRays?", Datatypes.BOOLEAN, false, "Shows laser sensor rays that have been sent out in the last cycle.", "ABSTRACT_ENVIRONMENT_2D_FAST"));
return list;
}
private boolean showTiles;
private boolean showRays;
@Override
public BufferedImage getOutsideView() {
return this.getOutsideView(null);
}
@Override
public synchronized BufferedImage getOutsideView(Graphics2D gg) {
BufferedImage img = super.getOutsideView(gg);
Graphics2D g;
if (gg == null) {
g = img.createGraphics();
} else {
g = gg;
}
if (showTiles) {
for (int i = 0; i < this.tileNumHorizontally; i++) {
for (int j = 0; j < this.tileNumVertically; j++) {
Rectangle2D r = this.getTileRectangle(new Vector2D(i, j));
Polygon2D polInVis = super.getPolygonInVisualization(r.toPol2D());
Vector2D pos = polInVis.centerPoint();
g.setColor(Color.orange);
g.drawPolygon(polInVis.toPol());
// g.drawString(i + " / " + j, (float) pos.x, (float) pos.y);
g.drawString("" + this.getAgentsTouchingTile(new Vector2D(i, j)).size(), (float) pos.x, (float) pos.y);
}
}
}
g.setColor(Color.blue);
if (showRays) {
g.drawString("(Rays may be delayed by one simulation cycle.)", 200, 25);
g.setStroke(new BasicStroke(2));
for (Polygon2D pol : this.rays) {
g.drawPolygon(this.getPolygonInVisualization(pol).toPol());
}
}
rays.clear();
// return super.getOutsideView();
return img;
}
}