/*
* File name: AbstractEnvironment2D.java
* Java version: 6.0
* Author(s): Lukas König
* File created: 06.05.2010
*
* (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.Color;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.awt.image.RGBImageFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import eas.math.geometry.LineSegment2D;
import eas.math.geometry.Polygon2D;
import eas.math.geometry.Rectangle2D;
import eas.math.geometry.Vector2D;
import eas.math.geometry.Vector3D;
import eas.miscellaneous.StaticMethods;
import eas.plugins.Plugin;
import eas.simulation.Wink;
import eas.simulation.agent.AbstractAgent;
import eas.simulation.spatial.sim2D.standardAgents.AbstractAgent2D;
import eas.simulation.spatial.sim2D.standardScenes.Scene2D;
import eas.simulation.spatial.sim2D.visualizer.EnvironmentVisualizer;
import eas.simulation.spatial.sim2D.visualizer.StandardVisualizer;
import eas.simulation.spatial.sim2D.visualizer.Visualizer3D;
import eas.simulation.spatial.standardEnvironments.AbstractEnvironmentSpatial;
import eas.simulation.standardEnvironments.AbstractEnvironment;
import eas.startSetup.ParCollection;
import eas.startSetup.SingleParameter;
import eas.startSetup.parameterDatatypes.ArrayListInt;
import eas.startSetup.parameterDatatypes.Datatypes;
/**
* @author Lukas König
*/
public abstract class AbstractEnvironment2D<AgentType extends AbstractAgent2D<?>> extends AbstractEnvironmentSpatial<AgentType> {
private static final long serialVersionUID = -4458694823295641410L;
/**
* The bounding box enclosing all parts of this environment any
* part of an agent has ever been.
*/
private Rectangle2D boundingBox;
/**
* The box enclosing the visualized part of the environmental field.
* <code>null</code> means that the bounding box is visualized.
*/
private Rectangle2D zoomBox = null;
/**
* The current positions of the agents.
*/
private HashMap<Integer, Vector2D> agentPositions;
/**
* The current directions of view of the agents.
*/
private HashMap<Integer, Double> agentAngles;
/**
* The scaling of the agents in X- and Y- direction.
*/
private HashMap<Integer, Vector2D> agentScales;
/**
* The list of colliding agents.
*/
private HashMap<Integer, AgentType> collidingAgents;
/**
* The agent shapes as located in the environment. <code>null</code>
* entries indicate shapes that have to be recalculated.
*/
private HashMap<Integer, Polygon2D> trueShapes;
/**
* The parameter collection for this environment.
*/
private ParCollection pars;
private boolean showGrid;
private Vector2D gridDistance;
private Vector2D gridDelta;
public AbstractEnvironment2D(int id, ParCollection params) {
this("Environment2D_" + id, id, null, params);
}
/**
* Constructor.
* @param fatherEnvironment The environment this environment is part of
* (may be null if this is a top level
* environment (most common case)).
*/
public AbstractEnvironment2D(
final String ident,
final int id,
final AbstractEnvironment<?> fatherEnv,
final ParCollection params) {
super(ident, id, fatherEnv, params);
this.pars = params;
this.envVisualizerObject = new StandardVisualizer(this);
this.agents2D = new HashMap<Integer, AgentType>();
this.collidingAgents = new HashMap<Integer, AgentType>();
this.agentPositions = new HashMap<Integer, Vector2D>();
this.agentAngles = new HashMap<Integer, Double>();
this.agentScales = new HashMap<Integer, Vector2D>();
this.geschwLinks = new HashMap<Integer, Double>();
this.geschwRechts = new HashMap<Integer, Double>();
this.trueShapes = new HashMap<Integer, Polygon2D>();
this.positionsInView = new HashMap<Integer, Vector2D>();
this.createBoundingBox();
if (params != null) {
if (params.getParValueBoolean("StrangeView?")) {
this.setEnvVisualizerObject(new Visualizer3D(this));
}
this.isDetailedView = pars.getParValueBoolean("DetailedAgentView?");
this.showGrid = pars.getParValueBoolean("ShowGrid?");
this.gridDistance = pars.getParValueVector2D("GridDistance");
this.gridDelta = pars.getParValueVector2D("GridDelta");
this.setShowIDs(pars.getParValueBoolean("showAgentIDs?"));
try {
ArrayListInt bckColor = pars.getParValueArrayListInt("BackgroundColor");
this.backgroundColor = new Color(bckColor.get(0), bckColor.get(1), bckColor.get(2), bckColor.get(3));
} catch (Exception e) {
this.backgroundColor = Color.white;
}
}
}
private Color backgroundColor;
/**
* @return Returns the backgroundColor.
*/
public Color getBackgroundColor() {
return this.backgroundColor;
}
/**
* Adds an agent to the list of agents and places it in the environment.
* The agent does not collide with others. Note that the id of the agent
* may change due to id collisions. The new id of the agent can be
* received by the method <code>agent.id()</code>.
*
* @param position The position to place the agent.
* @param angle The direction of view [0..360[ deg.
* @param agent The agent to be added.
*
* @return If the agent was added (always <code>true</code>).
*/
public boolean addAgent(
final AgentType agent,
final Vector2D position,
final double angle) {
return this.addAgent(agent, position, angle, null);
}
/**
* The list of all agents (duplicate list to list in AbstractEnvironment).
*/
private HashMap<Integer, AgentType> agents2D;
private Boolean produceWarning = null;
/**
* Note that the id of the agent
* may change due to id collisions. The new id of the agent can be
* received by the method <code>agent.id()</code>.
*/
public synchronized boolean addAgent(
final AgentType agent,
final Vector2D position,
final double angle,
final Vector2D scale) {
int id;
id = agent.id();
if (produceWarning == null) {
produceWarning = this.pars.getParValueBoolean("ProduceWarningWhenAgentIDChanges?");
}
// ID existiert bereits.
if (this.agentAngles.get(id) != null) {
id = this.firstFreeID;
if (produceWarning) {
StaticMethods.logWarning(
this.getEnvironmentName() + " --> Agent ID " + agent.id()
+ " was replaced by ID " + id + ".",
this.pars);
}
agent.setID(id);
}
super.addAgent(agent);
this.agents2D.put(agent.id(), agent);
this.agentPositions.put(id, position);
this.agentAngles.put(id, angle);
if (scale != null) {
this.agentScales.put(id, scale);
}
this.extendBoundingBox(this.getAgentShapeInEnvironment(id));
this.requestRedraw();
if (id == this.firstFreeID) {
this.findNextFreeID();
}
return true;
}
/**
* Adds an agent to the list of agents and places it in the environment.
* The agent collides with others and is added only if no other agent
* collides with it.
* <BR><BR>
* Note that the id of the agent
* may change due to id collisions. The new id of the agent can be
* received by the method <code>agent.id()</code>.
*
* @param position The position to place the agent.
* @param angle The direction of view [0..360[ deg.
* @param agent The agent to be added.
*
* @return If the agent was added.
*/
public boolean addCollidingAgent(
final AgentType agent,
final Vector2D position,
final double angle) {
return this.addCollidingAgent(agent, position, angle, null);
}
/**
* Note that the id of the agent
* may change due to id collisions. The new id of the agent can be
* received by the method <code>agent.id()</code>.
*/
public synchronized boolean addCollidingAgent(
final AgentType agent,
final Vector2D position,
final double angle,
final Vector2D scale) {
this.addAgent(agent, position, angle, scale);
this.collidingAgents.put(agent.id(), agent);
if (this.collides(agent, position, angle, scale)) {
this.removeAgent(agent.id());
return false;
}
return true;
}
/**
* Tests if some agent would collide with any other agent in the
* environment if it was set to some angle angle and to some position
* pos. If the agent is not a colliding agent, the method will return
* false.
*
* @param agent The agent to be tested.
* @param pos The position of the agent.
* @param angle The angle of the agent.
*
* @return True iff the agents would collide.
*/
public boolean collides(
AgentType agent,
Vector2D pos,
double angle,
Vector2D scale) {
if (!this.collidingAgents.containsKey(agent.id())) {
return false;
}
for (AgentType a : this.collidingAgents.values()) {
if (this.collides(
agent,
pos,
angle,
scale,
a,
this.getAgentPosition(a.id()),
this.getAgentAngle(a.id()),
this.getAgentScale(a.id()))) {
return true;
}
}
return false;
}
/**
* Tests if some agent would collide with another agent if both were set
* to some angles angle1 and angle2, respectively, and to positions pos1
* and pos2, respectively. If one of the agents is not a colliding agent,
* the method will return false.
* <BR>
* <BR>
* The method can be overriden to provide some custom collision testing,
* e.g., if a faster method is wanted that does not test for intersections
* with any line of the shape polygons.
*
* @param a1 The first agent.
* @param pos1 The position of the first agent.
* @param angle1 The angle of the first agent.
* @param scale1 The scale of the first agent (may be null).
* @param a2 The second agent.
* @param pos2 The position of the second agent.
* @param angle2 The angle of the second agent.
* @param scale2 The scale of the second agent (may be null).
*
* @return True iff the agents would collide.
*/
public boolean collides(
AgentType a1,
Vector2D pos1,
double angle1,
Vector2D scale1,
AgentType a2,
Vector2D pos2,
double angle2,
Vector2D scale2) {
boolean intersects;
if (a1 == a2
|| !this.collidingAgents.containsKey(a1.id())
|| !this.collidingAgents.containsKey(a2.id())) {
return false;
}
Polygon2D p1 = this.getAgentShapeInEnvironment(a1.id(), pos1, scale1, angle1);
Polygon2D p2 = this.getAgentShapeInEnvironment(a2.id(), pos2, scale2, angle2);
// Polygons with less than two points.
if (p1.nPoints() < 2 && p2.nPoints() < 2) {
return p1.equals(p2);
}
intersects = p1.intersect(p2);
// Tests if one polygon has some intersection with the other.
if (intersects) {
return true;
}
// Tests if one polygon is completely within the other.
return p1.isPointWithin(p2.get(0)) || p2.isPointWithin(p1.get(0));
}
/**
* @param agentID Some agent in the environment.
* @return Iff the agent is a colliding agent, i.e., if it potentially
* CAN collide with other colliding agents.
*/
public boolean isCollidingAgent(final int agentID) {
return this.collidingAgents.containsKey(agentID);
}
/**
* Removes an agent from the list of agents AND deletes it from the
* environment.
*
* @param agentID The agent to be removed.
*
* @return Iff it was possible to remove the agent
* (usually equivalent to "iff the agent existed").
*/
@Override
public synchronized boolean removeAgent(final int agentID) {
if (super.removeAgent(agentID)) {
this.agents2D.remove(agentID);
this.collidingAgents.remove(agentID);
this.agentAngles.remove(agentID);
this.agentPositions.remove(agentID);
this.agentScales.remove(agentID);
this.trueShapes.put(agentID, null);
this.requestRedraw();
if (agentID < this.firstFreeID) {
this.firstFreeID = agentID;
}
return true;
} else {
return false;
}
}
/**
* Erzeugt einen gültigen Winkel zwischen 0° (=) und 360°(<).
*
* @param w Der zu normalisierende Winkel.
*
* @return Der normalisierte Winkel.
*/
private double validAngleDEG(final double angle) {
if (angle >= 0) {
return angle % 360;
} else if (((Double) angle).isNaN()) {
return Double.NaN;
} else {
return 360 - this.validAngleDEG(-angle);
}
}
/**
* Erzeugt einen gültigen Winkel zwischen 0�(=) und Math.PI(<).
*
* @param w Der zu normalisierende Winkel.
*
* @return Der normalisierte Winkel.
*/
@SuppressWarnings("unused")
private double validAngleRAD(final double winkel) {
return this.validAngleDEG(winkel * 180 / Math.PI) / 180 * Math.PI;
}
/**
* Sets the agent's position.
*
* @param agentID The agent to set.
* @param pos The position to set.
*
* @return True iff the position was successfully set.
*/
public boolean setAgentPosition(final int agentID, final Vector2D pos) {
return this.setAgentAngleAndPositionAndScaleSimultaneously(
agentID,
pos,
this.getAgentAngle(agentID),
this.getAgentScale(agentID));
}
/**
* Sets the agent's angle of view measured relatively to a normal angle
* pointing to (1, 0).
*
* @param agentID Der zu setzende Agent.
* @param angle Der Winkel der Blickrichtung in Grad.
*
* @return Ob der Blickwinkel geändert werden konnte.
*/
public boolean setAgentAngle(final int agentID, final double angle) {
return this.setAgentAngleAndPositionAndScaleSimultaneously(
agentID,
this.getAgentPosition(agentID),
angle,
this.getAgentScale(agentID));
}
/**
* @param agentID Agent's id.
* @param scale The new scale of the robot.
*
* @return If the scale was altered (i.e., no collision occured).
*/
public boolean setAgentScale(final int agentID, final Vector2D scale) {
return this.setAgentAngleAndPositionAndScaleSimultaneously(
agentID,
this.getAgentPosition(agentID),
this.getAgentAngle(agentID),
scale);
}
/**
* Sets the viewing angle and the position of the given agent
* simultaneously to the given values.
*
* @param agentID The agent whoes angle and position should be altered.
* @param pos The new position.
* @param angle The new angle.
* @param scale The scale of the agent (may be null).
*
* @return Iff the angle and position could be set without a collision.
*/
public synchronized boolean setAgentAngleAndPositionAndScaleSimultaneously(
final int agentID,
final Vector2D pos,
final double angle,
final Vector2D scale) {
double angleVal = this.validAngleDEG(angle);
Vector2D scaleReal = scale;
if (scale == null) {
scaleReal = this.agentScales.get(agentID);
}
// Collides tests automatically weather the agent is a colliding agent.
if (this.collides(
this.getAgent(agentID),
pos,
angleVal,
scaleReal)) {
return false;
}
this.agentAngles.put(agentID, angleVal);
this.agentPositions.put(agentID, new Vector2D(pos));
if (scale != null) {
this.agentScales.put(agentID, scale);
}
// Set real agent position out of date.
this.trueShapes.put(agentID, null);
this.extendBoundingBox(this.getAgentShapeInEnvironment(agentID));
this.requestRedraw();
return true;
}
/**
* @param agent Some agent in the environment.
* @return The angle of this agent (null, if it does not exist).
*/
public Double getAgentAngle(final int agentID) {
return this.agentAngles.get(agentID);
}
/**
* @param agent Some agent in the environment.
* @return The normalized vector of the direction the agent is facing to
* (Line Of Vision; null, if agent does not exist).
*/
public Vector2D getNormalizedLOV(final int agentID) {
Double angle = this.getAgentAngle(agentID);
Vector2D vec = null;
if (angle == null) {
return null;
}
angle = new Double(angle / 180 * Math.PI);
vec = new Vector2D(0, 1);
vec.rotate(Vector2D.NULL_VECTOR, angle);
vec.norm();
return vec;
}
/**
* @param agent Some agent in the environment.
* @return The position of this agent (null, if it does not exist).
*/
public Vector2D getAgentPosition(final int agentID) {
return this.agentPositions.get(agentID);
}
/**
* @param agent Some agent in the environment.
* @return The scale of this agent.
*/
public Vector2D getAgentScale(final int agentID) {
Vector2D skal = this.agentScales.get(agentID);
if (skal == null) {
return new Vector2D(1, 1);
} else {
return skal;
}
}
/**
* @param agent An agent in this environment.
*
* @return The shape of the agent at the current environment state (i.e.,
* with the current orientation considered), null if the agent
* did not exist.
*/
public Polygon2D getAgentShape(final int agentID) {
if (this.agents2D.containsKey(agentID)) {
return this.agents2D.get(agentID).getAgentShape();
}
return null;
}
/**
* @return Returns the screenWidth (must be greater than 50).
*/
public double getScreenWidth() {
return this.screenWidth;
}
/**
* @return Returns the screenHeight.
*/
public double getScreenHeight() {
return this.screenHeight;
}
/**
* @param width The screenWidth to set.
*/
public void setScreenWidth(double width) {
if (width != this.screenWidth && width > this.screenLeft) {
this.screenWidth = width;
this.requestRedraw();
}
}
/**
* @param height The screenHeight to set (must be greater than 50).
*/
public void setScreenHeight(double height) {
if (height != this.screenHeight && height > this.screenUp) {
this.screenHeight = height;
this.requestRedraw();
}
}
private double screenWidth = 700;
private double screenHeight = 700;
private final double screenLeft = 50;
private final double screenUp = 50;
private transient BufferedImage field;
public double globalScale() {
if (this.boundingBox == null) {
return 1;
}
if (this.zoomBox == null) {
return Math.min(
(screenWidth - 2 * screenLeft) / this.boundingBox.getWidth(),
(screenHeight - 2 * screenUp) / this.boundingBox.getHeight());
} else {
return Math.min(
(screenWidth - 2 * screenLeft) / this.zoomBox.getWidth(),
(screenHeight - 2 * screenUp) / this.zoomBox.getHeight());
}
}
public Polygon2D getPinkBorder() {
double globalScale = this.globalScale();
Rectangle2D visibleBox = this.getCurrentViewBox();
Polygon2D b = visibleBox.toPol2D();
b.scale(Vector2D.NULL_VECTOR, new Vector2D(globalScale, globalScale));
Vector2D midTranslation = new Vector2D(screenWidth / 2, screenHeight / 2);
midTranslation.sub(b.centerPoint());
Polygon2D bound = visibleBox.toPol2D();
bound.scale(Vector2D.NULL_VECTOR, new Vector2D(globalScale, globalScale));
bound.translate(midTranslation);
return bound;
}
private Boolean isDetailedView;
private boolean showIDs = true;
/**
* @return Returns the showIDs.
*/
public boolean isShowIDs() {
return this.showIDs;
}
/**
* @param showIDs The showIDs to set.
*/
public synchronized void setShowIDs(boolean showIDs) {
this.showIDs = showIDs;
}
private LinkedList<Polygon2D> grid;
/**
* If the camera view is tight to the CURRENT bounds of the agents
* in "fit to agents" mode.
*/
private boolean perfectMatch = false;
private boolean redrawField = true;
private boolean redrawGrid = true;
/**
* @return Returns the perfectMatch.
*/
public boolean isPerfectFit() {
return this.perfectMatch;
}
/**
* @param perfectMatch The perfectMatch to set.
*/
public synchronized void setPerfectFit(boolean perfectMatch) {
this.perfectMatch = perfectMatch;
}
/**
* Returns the environment visualization in the specified size and zoom
* window. Note that the parameters screen size and zoom box are changed
* only for the one call of this method and set back afterwards, meaning
* that a future call of the method generateEnvironmentView() will not be
* affected by calling this method.
*
* @param screenWidthOneTime The Width of the screen the view is shown in.
* @param screenHeightOneTime The Height of the screen the view is shown in.
* @param zoomBoxOneTime The zoom window of the environment.
*
* @return An image view of the current environment state.
*/
public synchronized BufferedImage generateEnvironmentView(
final double screenWidthOneTime,
final double screenHeightOneTime,
final double screenAngleRADOneTime,
final Vector2D screenAngleCenterPointOneTime,
final Rectangle2D zoomBoxOneTime) {
Rectangle2D oldZoom = null;
Vector2D oldCenterPoint = this.getVisualizationAngleCenterPoint();
double oldAngle = this.getVisualizationAngleRAD();
if (this.zoomBox != null) {
oldZoom = new Rectangle2D(this.zoomBox.upperLeftCorner(), this.zoomBox.lowerRightCorner());
}
double oldWidth = this.getScreenWidth();
double oldHeight = this.getScreenHeight();
this.setZoomBox(zoomBoxOneTime);
this.setScreenWidth(screenWidthOneTime);
this.setScreenHeight(screenHeightOneTime);
this.setVisualizationAngle(screenAngleCenterPointOneTime, screenAngleRADOneTime);
BufferedImage zwischImg = this.getOutsideView();
this.setZoomBox(oldZoom);
this.setScreenWidth(oldWidth);
this.setScreenHeight(oldHeight);
this.setVisualizationAngle(oldCenterPoint, oldAngle);
return zwischImg;
}
@Override
public BufferedImage getOutsideView() {
return this.getOutsideView(null);
}
/**
* Standard implementation of the environment visualization. This method
* can be overriden to provide a custom visualization of the current
* environment state. This method has to be conform to
* <code>getImgPos(...)</code>.
*
* @return An image view of the current environment state.
*/
public synchronized BufferedImage getOutsideView(Graphics2D g2) {
Rectangle2D visibleBox;
if (this.isPerfectFit() && this.zoomBox == null) {
this.createBoundingBox();
}
if (this.redrawField) {
this.field = new BufferedImage(
(int) this.screenWidth,
(int) this.screenHeight,
BufferedImage.TYPE_4BYTE_ABGR);
} else {
return this.field;
}
visibleBox = this.getCurrentViewBox();
Graphics2D g;
if (g2 == null) {
g = this.field.createGraphics();
} else {
g = g2;
}
Polygon2D shape;
int id;
g.setBackground(this.backgroundColor);
g.clearRect(0, 0, this.field.getWidth(), this.field.getHeight());
double globalScale = this.globalScale();
Polygon2D b = visibleBox.toPol2D();
b.scale(Vector2D.NULL_VECTOR, new Vector2D(globalScale, globalScale));
Vector2D midTranslation = new Vector2D(screenWidth / 2, screenHeight / 2);
midTranslation.sub(b.centerPoint());
Rectangle2D border = new Rectangle2D(
new Vector2D(screenLeft, screenUp),
new Vector2D(screenWidth - screenLeft, screenHeight - screenUp));
Polygon2D bound = visibleBox.toPol2D();
bound.scale(Vector2D.NULL_VECTOR, new Vector2D(globalScale, globalScale));
bound.translate(midTranslation);
g.setColor(Color.red);
g.drawPolygon(border.toPol());
if (this.pars.getParValueBoolean("PaintPinkBoundingBox?")) {
g.setColor(Color.pink);
g.drawPolygon(bound.toPol());
g.drawString(
visibleBox.upperLeftCorner().toString(),
(int) bound.getBoundingBox().upperLeftCorner().x,
(int) bound.getBoundingBox().upperLeftCorner().y - 10);
String rechtsunten = visibleBox.lowerRightCorner().toString();
g.drawString(
rechtsunten,
(int) Math.min(bound.getBoundingBox().lowerRightCorner().x,
this.screenWidth - rechtsunten.length() * 8),
(int) bound.getBoundingBox().lowerRightCorner().y + 20);
}
Rectangle2D clip = this.getClippingRectangle();
g.clipRect(
(int) clip.upperLeftCorner().x,
(int) clip.upperLeftCorner().y,
(int) clip.lowerRightCorner().x,
(int) clip.lowerRightCorner().y);
Rectangle2D visibleRedBox = new Rectangle2D(
// visibleBox.liObEcke(),
this.getRealPosFromScreenPos(new Vector2D(0, 0)),
// visibleBox.rechtUntEcke(),
this.getRealPosFromScreenPos(new Vector2D(screenWidth, screenHeight)));
// Paint grid if set.
paintGrid(g, visibleRedBox);
for (AgentType a : this.agents2D.values()) {
id = a.id();
// shape = new Polygon2D(this.getAgentShapeInEnvironment(a.id()));
// shape.skaliere(Vector2D.NULL_VECTOR, new Vector2D(globalScale, globalScale));
// shape.verschiebe(midTranslation);
shape = this.getPolygonInVisualization(this.getAgentShapeInEnvironment(a.id()));
Polygon p = shape.toPol();
Color col = a.getAgentColor();
if (col != null) {
g.setColor(col);
g.fillPolygon(p);
}
// Agent outside view.
BufferedImage img = a.getOutsideView();
if (this.isDetailedView && img != null) {
img = this.makeColorTransparent(img, Color.white);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.drawImage(
img,
(int) (shape.centerPoint().x - shape.getBoundingBox().getWidth() / 2),
(int) (shape.centerPoint().y - shape.getBoundingBox().getHeight() / 2),
(int) shape.getBoundingBox().getWidth(),
(int) shape.getBoundingBox().getHeight(),
null);
}
g.setColor(Color.DARK_GRAY);
g.drawPolygon(p);
if (this.showIDs) {
Vector2D pos = shape.centerPoint();
g.drawString("" + id, (int) pos.x, (int) pos.y);
}
}
this.redrawField = false;
return field;
}
private BufferedImage imageToBufferedImage(Image image) {
BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = bufferedImage.createGraphics();
g2.drawImage(image, 0, 0, null);
g2.dispose();
return bufferedImage;
}
private BufferedImage makeColorTransparent(BufferedImage im, final Color color) {
ImageFilter filter = new RGBImageFilter() {
// the color we are looking for... Alpha bits are set to opaque
public int markerRGB = color.getRGB() | 0xFF000000;
@Override
public final int filterRGB(int x, int y, int rgb) {
if ((rgb | 0xFF000000) == markerRGB) {
// Mark the alpha bits as zero - transparent
return 0x00FFFFFF & rgb;
} else {
// nothing to do
return rgb;
}
}
};
ImageProducer ip = new FilteredImageSource(im.getSource(), filter);
return this.imageToBufferedImage(Toolkit.getDefaultToolkit().createImage(ip));
}
/**
* @param g
* @param globalScale
* @param midTranslation
* @param visibleRedBox
*/
private void paintGrid(Graphics2D g, Rectangle2D visibleRedBox) {
// Paint grid.
double epsilon = 0.00001;
if (this.showGrid
&& Math.abs(this.gridDistance.x) > epsilon
&& Math.abs(this.gridDistance.y) > epsilon) {
if (this.redrawGrid) {
grid = new LinkedList<Polygon2D>();
double length = Math.max(
visibleRedBox.getHeight(),
visibleRedBox.getWidth());
double midX = -visibleRedBox.middle().x;
double midY = -visibleRedBox.middle().y;
// Vertikal.
for (double d = this.getRealPosFromScreenPos(new Vector2D(0, 0)).x + this.gridDelta.x;
d <= visibleRedBox.lowerRightCorner().x;
d += this.gridDistance.x) {
Polygon2D p = new Polygon2D();
Vector2D posU = this.getPointInVisualization(new Vector2D(d, -length - midY));
Vector2D posL = this.getPointInVisualization(new Vector2D(d, length - midY));
p.add(posU);
p.add(posL);
this.grid.add(p);
}
// Horizontal.
for (double d = this.getRealPosFromScreenPos(new Vector2D(0, 0)).y + this.gridDelta.y;
d <= visibleRedBox.lowerRightCorner().y;
d += this.gridDistance.y) {
Polygon2D p = new Polygon2D();
Vector2D posL = this.getPointInVisualization(new Vector2D(-length - midX, d));
Vector2D posR = this.getPointInVisualization(new Vector2D(length - midX, d));
p.add(posL);
p.add(posR);
this.grid.add(p);
}
this.redrawGrid = false;
}
g.setColor(Color.black);
for (Polygon2D pol : grid) {
g.drawPolygon(pol.toPol());
}
}
}
public Rectangle2D getClippingRectangle() {
return new Rectangle2D(
new Vector2D(screenLeft, screenUp),
new Vector2D(
screenWidth - 2 * screenLeft,
screenHeight - 2 * screenUp));
}
/**
* The positions of the agents in the visualization view.
* <code>null</code> means that the positions has to be recalculated.
*/
private HashMap<Integer, Vector2D> positionsInView;
/**
* Requests a redraw of the field and a recalculation of the agent
* positions in the view.
*/
public synchronized void requestRedraw() {
this.redrawField = true;
this.positionsInView.clear();
}
/**
* Same as getPositionInVisualization(final int agentID), but with explicit request of
* zoom box and screen sizes.
*
* @param agentID The ID of the requested agent.
*/
public synchronized Vector2D getPositionInVisualization(
final int agentID,
final double screenWidthOneTime,
final double screenHeightOneTime,
final double screenAngleRADOneTime,
final Vector2D screenAngleCenterPointOneTime,
final Rectangle2D zoomBoxOneTime) {
return this.getPositionInVisualization(
this.getAgentPosition(agentID),
screenWidthOneTime,
screenHeightOneTime,
screenAngleRADOneTime,
screenAngleCenterPointOneTime,
zoomBoxOneTime);
}
/**
* Same as getPositionInVisualization(final Vector2D fieldPos), but with explicit
* request of zoom box and screen sizes.
*
* @param fieldPos A point in the environmental coordinate system.
*/
public synchronized Vector2D getPositionInVisualization(
final Vector2D fieldPos,
final double screenWidthOneTime,
final double screenHeightOneTime,
final double screenAngleRADOneTime,
final Vector2D screenAngleCenterPointOneTime,
final Rectangle2D zoomBoxOneTime) {
Rectangle2D oldZoom = null;
Vector2D oldCenterPoint = this.getVisualizationAngleCenterPoint();
double oldAngle = this.getVisualizationAngleRAD();
if (this.zoomBox != null) {
oldZoom = new Rectangle2D(this.zoomBox.upperLeftCorner(), this.zoomBox.lowerRightCorner());
}
double oldWidth = this.getScreenWidth();
double oldHeight = this.getScreenHeight();
this.setZoomBox(zoomBoxOneTime);
this.setScreenWidth(screenWidthOneTime);
this.setScreenHeight(screenHeightOneTime);
this.setVisualizationAngle(screenAngleCenterPointOneTime, screenAngleRADOneTime);
Vector2D zwischPos = this.getPointInVisualization(fieldPos);
this.setZoomBox(oldZoom);
this.setScreenWidth(oldWidth);
this.setScreenHeight(oldHeight);
this.setVisualizationAngle(oldCenterPoint, oldAngle);
return zwischPos;
}
/**
* Returns the position of an agent in the field view. The standard
* implementation can be overridden in order to provide a custom
* visualization according to <code>generateEnvironmentView()</code>.
*
* @param agentID The agent's ID.
*
* @return The agent's position in the visualization.
*/
@Override
public synchronized Vector2D getPositionInVisualization(final int agentID) {
if (this.getAgent(agentID) == null) {
return null;
}
Vector2D posStored = this.positionsInView.get(agentID);
if (posStored != null) {
return posStored;
}
return this.getPointInVisualization(this.getAgentPosition(agentID));
// double scale = this.globalScale();
// Polygon2D b = this.getCurrentViewBox().toPol2D();
//
// b.scale(Vector2D.NULL_VECTOR, new Vector2D(scale, scale));
// Vector2D pos = new Vector2D(this.getAgentPosition(agentID));
// pos.scale(Vector2D.NULL_VECTOR, new Vector2D(scale, scale));
// Vector2D midTranslation = new Vector2D(screenWidth / 2, screenHeight / 2);
// midTranslation.sub(b.centerPoint());
// pos.add(midTranslation);
// this.positionsInView.put(agentID, pos);
//
// return pos;
}
private Vector2D visualizationAngleCenterPoint;
private double visualizationAngleRAD;
public Vector2D getVisualizationAngleCenterPoint() {
return this.visualizationAngleCenterPoint;
}
public double getVisualizationAngleRAD() {
return this.visualizationAngleRAD;
}
/**
* Sets the visualization angle.
*
* @param centerPoint The point to turn around. Set to null to avoid
* expensive calculations if no angle is desired.
* @param angleRAD The angle.
*/
public void setVisualizationAngle(Vector2D centerPoint, double angleRAD) {
if (centerPoint == null && this.visualizationAngleCenterPoint != null
|| (centerPoint != null
&& (angleRAD != this.visualizationAngleRAD
|| !centerPoint.equals(this.visualizationAngleCenterPoint)))) {
this.trueShapes.clear();
}
this.visualizationAngleCenterPoint = centerPoint;
this.visualizationAngleRAD = angleRAD;
}
public void resetVisualizationAngle() {
this.setVisualizationAngle(null, 0);
}
private EnvironmentVisualizer envVisualizerObject;
public void setEnvVisualizerObject(EnvironmentVisualizer envVisualizerObject) {
this.envVisualizerObject = envVisualizerObject;
}
/**
* Returns the position of a point in the visualized field view. The
* standard implementation can be overridden in order to provide a custom
* visualization according to <code>generateEnvironmentView()</code>.
*
* @param fieldPos The position in the actual field.
*
* @return The position in the visualized field.
*/
public Vector2D getPointInVisualization(final Vector2D fieldPos2) {
return this.envVisualizerObject.getPointInVisualization(new Vector3D(fieldPos2.x, fieldPos2.y, 0));
}
public Vector2D getRealPosFromScreenPos(final Vector2D screenPos) {
return this.envVisualizerObject.getRealPosFromScreenPos(screenPos);
}
/**
* @param agentID An agent id.
*
* @return The agent that has this id or null if no agent has this id.
*/
@Override
public AgentType getAgent(final int agentID) {
return this.agents2D.get(agentID);
}
/**
* Finds the agent nearest to the given agent according to the Euclidian
* distance of their positions.
*
* @param agent The agent whoes nearest agent is to be found.
*
* @return The agent closest to the given agent. If the agent is the
* only one in the environment, <code>null</code> is returned.
* If the agent is not in the environment, a RuntimeException is
* thrown.
*/
public AbstractAgent2D<?> nearestAgent(final AbstractAgent2D<?> agent) {
AbstractAgent2D<?> nearest = null;
double distance = Double.MAX_VALUE;
double currDist;
Vector2D pos = this.getAgentPosition(agent.id());
for (AbstractAgent2D<?> a : this.agents2D.values()) {
if (a != agent) {
currDist = pos.distance(this.getAgentPosition(a.id()));
if (currDist < distance) {
nearest = a;
distance = currDist;
}
}
}
return nearest;
}
/**
* Traces a ray until the nearest collision with an agent shape.
*
* @param position The position from where to trace.
* @param direction The direction in which to trace.
* @return The point of impact if one exists, null otherwise.
*/
public CollisionData nearestRayCollision(
Vector2D position,
Vector2D direction) {
return this.nearestRayCollision(position, direction, (AgentType) null);
}
/**
* Traces a ray until the nearest collision with an agent shape, but
* ignores non-colliding agents. The agents from the ignore list are
* also ignored.
*
* @param position The position from where to trace.
* @param direction The direction in which to trace.
* @param ignore The agents to be ignored (in addition to the non-
* colliding agents). This parameter may be null.
* @return The point of impact if one exists, null otherwise.
*/
public CollisionData nearestRayCollisionIgnoreNonColliding(
final Vector2D position,
final Vector2D direction,
final LinkedList<AgentType> ignore) {
LinkedList<AgentType> ignoreAll
= new LinkedList<AgentType>();
ignoreAll.addAll(this.agents2D.values());
ignoreAll.removeAll(this.collidingAgents.values());
if (ignore != null) {
ignoreAll.addAll(ignore);
}
return this.nearestRayCollision(position, direction, ignoreAll);
}
/**
* Traces a ray until the nearest collision with an agent shape, but
* ignores non-colliding agents. The ignore agent is
* also ignored.
*
* @param position The position from where to trace.
* @param direction The direction in which to trace.
* @param ignore The agent to be ignored (in addition to the non-
* colliding agents).
* @return The point of impact if one exists, null otherwise.
*/
public CollisionData nearestRayCollisionIgnoreNonColliding(
final Vector2D position,
final Vector2D direction,
final AgentType ignore) {
LinkedList<AgentType> list = new LinkedList<AgentType>();
list.add(ignore);
return nearestRayCollisionIgnoreNonColliding(position, direction, list);
}
public CollisionData nearestRayCollision(
Vector2D position,
Vector2D direction,
int ignoreID) {
return this.nearestRayCollision(position, direction, this.getAgent(ignoreID));
}
/**
* Traces a ray until the nearest collision with an agent shape.
*
* @param position The position from where to trace.
* @param direction The direction in which to trace.
* @param ignore The agents to be ignored.
* @return The point of impact if one exists, null otherwise.
*/
public CollisionData nearestRayCollision(
Vector2D position,
Vector2D direction,
AgentType ignore) {
LinkedList<AgentType> ignorelist = new LinkedList<AgentType>();
if (ignore != null) {
ignorelist.add(ignore);
}
return this.nearestRayCollision(position, direction, ignorelist);
}
/**
* Traces a ray until the nearest collision with an agent shape. [This is
* the main method, all other overridden methods within this class call
* this method.]
*
* @param position The position from where to trace.
* @param direction The direction in which to trace.
* @param ignore The list of agents to be ignored.
* @return The point of impact if one exists, (null, null) otherwise.
*/
public CollisionData nearestRayCollision(
Vector2D position,
Vector2D direction,
List<AgentType> ignore) {
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 (AbstractAgent2D<?> a : this.agents2D.values()) {
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);
HashMap<AbstractAgent2D<?>, LinkedList<Vector2D>> schnittpunkte
= new HashMap<AbstractAgent2D<?>, LinkedList<Vector2D>>();
LineSegment2D s = new LineSegment2D(position, furthest);
Polygon2D p;
// Find all intersections of ray with agents.
for (AbstractAgent2D<?> a : this.agents2D.values()) {
if (!ignore.contains(a)) {
p = this.getAgentShapeInEnvironment(a.id());
if (this.isTouchable(s, p.getBoundingBox())) {
schnittpunkte.put(a, p.intersectAll(s));
}
}
}
double minDistance = Double.MAX_VALUE;
Vector2D minVector = null;
AbstractAgent2D<?> minAgent = null;
for (AbstractAgent2D<?> a : schnittpunkte.keySet()) {
for (Vector2D v : schnittpunkte.get(a)) {
distanz = position.distance(v);
if (distanz < minDistance) {
minDistance = distanz;
minVector = v;
minAgent = a;
}
}
}
return new CollisionData(minAgent, minVector);
}
/**
* Find out if an agent is touchable, due to the position of
* the bounding box.
*
* @param strecke
* @param boundingBox
* @return
*/
@SuppressWarnings("unused")
protected boolean isTouchable(LineSegment2D strecke, Rectangle2D boundingBox) {
return true; // strecke.isLeftOf(boundingBox.liObEcke()) != strecke.isLeftOf(boundingBox.rechtUntEcke());
}
/**
* Maximalgeschwidigkeit der Räder.
*/
private final double maxGesch = 100;
/**
* Minimalgeschwidigkeit der Räder.
*/
private final double minGesch = -100;
/**
* Geschwindigkeit des linken Rades in Winkel (Radians) / Zyklus.
*/
private HashMap<Integer, Double> geschwRechts;
/**
* Geschwindigkeit des rechten Rades in Winkel (Radians) / Zyklus.
*/
private HashMap<Integer, Double> geschwLinks;
/**
* Geschwindigkeit, unter der das Rad als nichtbewegend gilt.
*/
private final double epsilon = 0.001;
/**
* Berechnet die Strecke, die ein Rad auf eine Ebene abrollt, wenn es sich
* bei einem angegeben Radius um einen bestimmten Winkel um die Mittelachse
* dreht. Die Reibung wird dabei als unendlich angesehen. (Also eigentlich
* wird einfach die Länge des Kreisbogens um den angegebenen Winkel
* berechnet.)
*
* @param winkRadians Der Winkel, um den sich das Rad dreht.
* @param radRadius Der Radius des Rades.
*
* @return Die vom Rad abgerollte Strecke.
*/
private double streckeGefahrenBeiWink(
final double winkRadians,
final double radRadius) {
return radRadius * winkRadians;
}
/**
* führt die angegebene Drehung / Zyklus der beiden Räder INVERTIERT ab.
* Für eine nicht invertierte Bewegung ist die Methode
* <code>vorwaerts</code> vorgesehen.
*
* @return Ob die Bewegung erfolgreich war (oder aufgrund von Kollisionen
* zurückgesetzt werden musste).
*/
public synchronized boolean differentialDriveBackward(
final int agentID) {
double altdrehL = this.getWheelSpeedLeft(agentID);
double altdrehR = this.getWheelSpeedRight(agentID);
boolean erfolgreich;
this.geschwLinks.put(agentID, altdrehL * -1);
this.geschwRechts.put(agentID, altdrehR * -1);
erfolgreich = this.differentialDriveForward(agentID);
this.geschwLinks.put(agentID, altdrehL);
this.geschwRechts.put(agentID, altdrehR);
return erfolgreich;
}
private HashMap<Integer, Double> wheelErrorsLeft = new HashMap<Integer, Double>();
private HashMap<Integer, Double> wheelErrorsRight = new HashMap<Integer, Double>();
private HashMap<Integer, Double> wheelErrorsLeftForwardTendency = new HashMap<Integer, Double>();
private HashMap<Integer, Double> wheelErrorsRightForwardTendency = new HashMap<Integer, Double>();
private Random wheelRand;
/**
* @param agentID The ID of the agent.
*
* @param error Error (0.1 means that the wheelspeed is varied around
* the desired value by at most +/- 10 percent).
*/
public void setWheelErrorLeft(int agentID, double error, double forwardTendency) {
wheelErrorsLeft.put(agentID, error);
wheelErrorsLeftForwardTendency.put(agentID, forwardTendency);
}
/**
* @param agentID The ID of the agent.
*
* @param error Error (0.1 means that the wheelspeed is varied around
* the desired value by at most +/- 10 percent).
*/
public void setWheelErrorRight(int agentID, double error, double forwardTendency) {
wheelErrorsRight.put(agentID, error);
wheelErrorsRightForwardTendency.put(agentID, forwardTendency);
}
/**
* führt die angegebene Drehung / Zyklus der beiden Räder ab. Dabei wird
* die Drehrichtung so beibehalten, wie sie die Motoren angeben. Für eine
* Invertierung der Drehrichtung kann die Methode <code>rueckwaerts</code>
* verwendet werden.
*
* @return Ob die Bewegung erfolgreich war (oder aufgrund von Kollisionen
* zurückgesetzt werden musste).
*/
public boolean differentialDriveForward(final int agentID) {
Vector2D radiuses = this.pars.getParValueVector2D("WheelRadiuses");
if (this.wheelRand == null) {
this.wheelRand = new Random(pars.getSeed());
}
double wheelerrorLeft = 0;
double wheelerrorRight = 0;
double wheelspeedRight = this.getWheelSpeedRight(agentID);
double wheelspeedLeft = this.getWheelSpeedLeft(agentID);
Double tempErrorLeft = this.wheelErrorsLeft.get(agentID);
Double tempErrorRight = this.wheelErrorsRight.get(agentID);
Double tempErrorForwTendLeft = this.wheelErrorsLeftForwardTendency.get(agentID);
Double tempErrorForwTendRight = this.wheelErrorsRightForwardTendency.get(agentID);
if (tempErrorLeft != null) {
wheelerrorLeft = this.wheelRand.nextDouble() * wheelspeedLeft * tempErrorLeft * 2 - wheelspeedLeft * tempErrorLeft;
}
if (tempErrorRight != null) {
wheelerrorRight = this.wheelRand.nextDouble() * wheelspeedRight * tempErrorRight * 2 - wheelspeedRight * tempErrorRight;
}
if (tempErrorForwTendLeft == null) {
tempErrorForwTendLeft = 0.0;
}
if (tempErrorForwTendRight == null) {
tempErrorForwTendRight = 0.0;
}
double r = this.streckeGefahrenBeiWink(
wheelspeedRight + wheelerrorRight + radiuses.x * tempErrorForwTendRight, radiuses.x)
* 1; // this.pars.getVerzerr();
double l = this.streckeGefahrenBeiWink(
wheelspeedLeft + wheelerrorLeft + radiuses.y * tempErrorForwTendLeft, radiuses.y)
* 1; // this.pars.getVerzerr();
double b = this.getAgent(agentID).getAgentShape().getBoundingBox().getWidth();
b *= this.getAgentScale(agentID).x;
double rad;
double alpha;
Vector2D neuPos = new Vector2D(this.getAgentPosition(agentID));
Vector2D mitte = new Vector2D(this.getAgentPosition(agentID));
Vector2D richtung = new Vector2D(this.getNormalizedLOV(agentID));
Vector2D altePos;
double alteBlick;
boolean bool;
if (Math.abs(l) < this.epsilon && Math.abs(r) < this.epsilon) {
return true; // Beide Räder auf Null.
} else if (Math.abs(l - r) < this.epsilon) { // Differenz gleich.
neuPos = new Vector2D(this.getAgentPosition(agentID));
richtung = new Vector2D(this.getNormalizedLOV(agentID));
richtung.setLength(r);
neuPos.translate(richtung);
return this.setAgentPosition(agentID, neuPos);
} else if (Math.abs(l + r) < this.epsilon) { // Genau entgegengesetzt.
rad = b / 2;
alpha = r / rad;
return this.setAgentAngle(agentID, this.getAgentAngle(agentID) - alpha
/ Math.PI * 180);
} else if (Math.abs(l) > Math.abs(r)) {
rad = b / (l / r - 1) + b / 2;
richtung.ortho();
richtung.setLength(rad);
mitte.sub(richtung);
if (r * l > 0) { // r und l haben dasselbe Vorzeichen.
alpha = r / rad;
} else {
alpha = 2 * r / b;
}
alpha = l / rad;
neuPos.rotate(mitte, alpha);
altePos = new Vector2D(this.getAgentPosition(agentID));
alteBlick = this.getAgentAngle(agentID);
bool = this.setAgentPosition(agentID, neuPos);
if (bool) {
bool = this.setAgentAngle(agentID, this.getAgentAngle(agentID) + alpha
/ Math.PI * 180);
if (!bool) {
this.setAgentAngle(agentID, alteBlick);
this.setAgentPosition(agentID, altePos);
return false;
}
return true;
} else {
this.setAgentPosition(agentID, altePos);
return false;
}
} else if (Math.abs(r) > Math.abs(l)) {
rad = b / (r / l - 1) + b / 2;
richtung.ortho();
richtung.setLength(rad);
mitte.translate(richtung);
if (r * l > 0) { // r und l haben dasselbe Vorzeichen.
alpha = r / rad;
} else {
alpha = 2 * r / b;
}
neuPos.rotate(mitte, -alpha);
altePos = new Vector2D(this.getAgentPosition(agentID));
alteBlick = this.getAgentAngle(agentID);
bool = this.setAgentPosition(agentID, neuPos);
if (bool) {
bool = this.setAgentAngle(agentID, this.getAgentAngle(agentID) - alpha
/ Math.PI * 180);
if (!bool) {
this.setAgentAngle(agentID, alteBlick);
this.setAgentPosition(agentID, altePos);
return false;
}
return true;
} else {
this.setAgentPosition(agentID, altePos);
return false;
}
}
return false;
}
/**
* Setzt die Radgeschwidigkeit des linken Rades
* in Winkel (Radians) / Zyklus. (Wird bei minGesch und maxGesch
* abgeschnitten.)
*
* @param wert Der Wert, auf den die Geschwindigkeit gesetzt werden soll.
*/
public synchronized void setWheelSpeedLeft(final int agentID, final double wert) {
this.geschwLinks.put(
agentID,
Math.min(Math.max(wert, this.minGesch), this.maxGesch));
}
/**
* Setzt die Radgeschwidigkeit des rechten Rades
* in Winkel (Radians) / Zyklus. (Wird bei minGesch und maxGesch
* abgeschnitten.)
*
* @param wert Der Wert, auf den die Geschwindigkeit gesetzt werden soll.
*/
public synchronized void setWheelSpeedRight(final int agentID,
final double wert) {
this.geschwRechts.put(agentID,
Math.min(Math.max(wert, this.minGesch), this.maxGesch));
}
/**
* @return Returns the geschwRechts (null = 0).
*/
public double getWheelSpeedRight(final int agentID) {
Double d = this.geschwRechts.get(agentID);
if (d == null) {
return 0;
} else {
return d;
}
}
/**
* @return Returns the geschwLinks.
*/
public double getWheelSpeedLeft(final int agentID) {
Double d = this.geschwLinks.get(agentID);
if (d == null) {
return 0;
} else {
return d;
}
}
/**
* Extends the BoundingBox by all points of the given polygon.
*
* @param pol The polygon to be included.
*/
private synchronized void extendBoundingBox(final Polygon2D pol) {
if (pol == null) {
return;
}
this.redrawGrid = true;
Rectangle2D bound = pol.getBoundingBox();
if (this.boundingBox == null) {
this.boundingBox = new Rectangle2D(bound.upperLeftCorner(),
bound.lowerRightCorner());
return;
}
if (bound.upperLeftCorner().x < this.boundingBox.upperLeftCorner().x) {
this.boundingBox.setLeft(bound.upperLeftCorner().x);
}
if (bound.upperLeftCorner().y < this.boundingBox.upperLeftCorner().y) {
this.boundingBox.setTop(bound.upperLeftCorner().y);
}
if (bound.lowerRightCorner().x > this.boundingBox.lowerRightCorner().x) {
this.boundingBox.setRight(bound.lowerRightCorner().x);
}
if (bound.lowerRightCorner().y > this.boundingBox.lowerRightCorner().y) {
this.boundingBox.setBottom(bound.lowerRightCorner().y);
}
}
/**
* Creates a new bounding box.
*/
private synchronized void createBoundingBox() {
Polygon2D p;
if (this.agents2D == null || this.agents2D.size() == 0) {
// this.boundingBox = new Rectangle2D(
// new Vector2D(-10, -10), new Vector2D(10, 10));
return;
}
this.boundingBox = null;
for (AbstractAgent2D<?> a : this.agents2D.values()) {
p = this.getAgentShapeInEnvironment(a.id());
this.extendBoundingBox(p);
}
}
/**
* Returns the agent shape as visualized in the environment.
*
* @param agentID The agent id.
*
* @return The shape if the agent exists, null otherwise.
*/
public synchronized Polygon2D getAgentShapeInEnvironment(final int agentID) {
if (this.getAgent(agentID) == null) {
return null;
}
Polygon2D trueShape = this.trueShapes.get(agentID);
if (trueShape != null) {
return trueShape;
}
trueShape = this.getAgentShapeInEnvironment(
agentID,
this.getAgentPosition(agentID),
this.getAgentScale(agentID),
this.getAgentAngle(agentID));
this.trueShapes.put(agentID, trueShape);
return trueShape;
}
/**
* Generates the agent polygon shape as located in the environment.
*
* @param agentID The ID of the agent.
*
* @return The agent shape in the environment.
*/
public Polygon2D getAgentShapeInEnvironment(
final int agentID,
final Vector2D pos,
final Vector2D scale,
final double angle) {
Polygon2D trueShape;
Polygon2D agtShape = this.getAgentShape(agentID);
trueShape = new Polygon2D(agtShape);
if (scale != null) {
trueShape.scale(Vector2D.NULL_VECTOR, scale);
}
trueShape.rotate(Vector2D.NULL_VECTOR, angle / 180 * Math.PI);
trueShape.translate(pos);
return trueShape;
}
/**
* @return Returns the pars.
*/
public ParCollection getParCollection() {
return this.pars;
}
/**
* Sets the middle of the zoom box to a specified point. The size of the
* box is not changed (only the position).
*
* @param middle The new middle of the zoom box.
*/
public synchronized void setZoomBoxMiddle(final Vector2D middle) {
if (this.zoomBox == null || middle == null) {
return;
} else {
Vector2D move = new Vector2D(middle);
move.sub(this.zoomBox.middle());
this.zoomBox.translate(move);
this.redrawGrid = true;
this.requestRedraw();
}
}
/**
* @param newZoomBox The zoomBox to set. <code>null</code> means that the
* bounding box is visualized.
*/
public synchronized void setZoomBox(final Rectangle2D newZoomBox) {
if (newZoomBox == null) {
this.zoomBox = null;
this.forceRecalculation();
return;
}
this.zoomBox = new Rectangle2D(
newZoomBox.upperLeftCorner(),
newZoomBox.lowerRightCorner());
this.forceRecalculation();
}
/**
* Force recalculation of positions in view and image.
*/
private void forceRecalculation() {
this.redrawField = true;
this.redrawGrid = true;
Set<Integer> keySet = this.positionsInView.keySet();
for (int i : keySet) {
this.positionsInView.put(i, null);
}
this.requestRedraw();
}
public void setZoomScale(double scale) {
Rectangle2D zb;
Rectangle2D currentBound = this.getCurrentViewBox();
Vector2D middle = new Vector2D(this.getZoomBoxMiddle());
zb = new Rectangle2D(
new Vector2D(currentBound.upperLeftCorner()),
new Vector2D(currentBound.lowerRightCorner()));
zb.scale(Vector2D.NULL_VECTOR, new Vector2D(scale, scale));
this.setZoomBox(zb);
this.setZoomBoxMiddle(middle);
}
public Vector2D getZoomBoxMiddle() {
return this.getCurrentViewBox().middle();
}
/**
* @return Returns the zoomBox.
*/
public Rectangle2D getZoomBox() {
return this.zoomBox;
}
/**
* @param recalculate Iff the zoom box is forced to be recalculated.
* Otherwise only an upper bound of the bounding box
* is returned which reflects the maximal area that
* agents have visited so far. The internal bounding
* box is not affected.
*
* @return The bounding box of this scene (null if no agent has been
* placed so far).
*/
public synchronized Rectangle2D getBoundingBox(final boolean recalculate) {
Rectangle2D oldBoundingBox = null;
Rectangle2D newBoundingBox;
boolean newlyCreated = false;
if (this.boundingBox == null) {
this.createBoundingBox();
newlyCreated = true;
}
if (this.boundingBox != null) {
oldBoundingBox = new Rectangle2D(this.boundingBox);
}
if (recalculate) {
if (!newlyCreated) {
this.createBoundingBox();
}
newBoundingBox = this.boundingBox;
this.boundingBox = oldBoundingBox;
return newBoundingBox;
}
return this.boundingBox;
}
/**
*
* @return The current bounding box of the field view, i.e., the zoom box
* if it is not <code>null</code> or else the automatically
* calculated bounding box.
*/
public Rectangle2D getCurrentViewBox() {
Rectangle2D box;
if (this.zoomBox == null) {
box = this.boundingBox;
} else {
box = this.zoomBox;
}
if (box == null) {
return new Rectangle2D(new Vector2D(0, 0), new Vector2D(100, 100));
}
return box;
}
/**
* Adds a scene to this environment. If a colliding agent does not fit on
* the field, it is omitted.
*
* @param scene The scene to add.
*/
public void addScene(final Scene2D<AgentType> scene) {
boolean added;
for (AgentType a : scene.getAgentList()) {
added = false;
if (scene.isColliding(a.id())) {
if (this.addCollidingAgent(
a,
scene.getAgentPosition(a.id()),
scene.getAgentAngle(a.id()),
scene.getAgentScale(a.id()))) {
added = true;
} else {
StaticMethods.logWarning(
"Agent "
+ a.id()
+ " from scene \""
+ scene.getIdentity()
+ "\" could not be placed into environment \""
+ this.getEnvironmentName()
+ "\".",
this.pars);
}
} else {
this.addAgent(
a,
scene.getAgentPosition(a.id()),
scene.getAgentAngle(a.id()),
scene.getAgentScale(a.id()));
added = true;
}
if (added) {
a.setEnvironment(this);
}
}
}
/**
* The smallest free ID.
*/
private int firstFreeID = 0;
/**
* Find the next smallest free ID if the current one is occupied.
*/
private synchronized void findNextFreeID() {
while (this.agentAngles.get(this.firstFreeID) != null) {
this.firstFreeID++;
}
}
/**
* Generates a scene from the current agents in the environment.
*
* @return The current scene.
*/
public Scene2D<AgentType> generateScene() {
Scene2D<AgentType> currentScene = new Scene2D<AgentType>(
"Scene_from_"
+ this.getEnvironmentName()
+ "_at_time_step_"
+ this.getSimTime()
+ "_(time_stamp_" + System.currentTimeMillis() + ")",
this.pars);
for (AgentType agent : this.agents2D.values()) {
if (this.isCollidingAgent(agent.id())) {
currentScene.addCollidingAgent(
agent,
this.getAgentPosition(agent.id()),
this.getAgentAngle(agent.id()),
this.getAgentScale(agent.id()));
} else {
currentScene.addAgent(
agent,
this.getAgentPosition(agent.id()),
this.getAgentAngle(agent.id()),
this.getAgentScale(agent.id()));
}
}
return currentScene;
}
public List<Integer> calculateAgentsInBox(final Rectangle2D box) {
LinkedList<Integer> agentList = new LinkedList<Integer>();
for (int i : this.agentPositions.keySet()) {
if (this.agentPositions.get(i).isWithinRectangleOR(
box.upperLeftCorner(), box.lowerRightCorner())) {
agentList.add(i);
}
}
return agentList;
}
/**
* Adds a visualizing plugin to this environment. The information if a
* visualizer is currently observing the environment should be passed on
* to agents and generic sensors to provide them with the possibility of
* omitting expensive visualization computations as long as no visualizers
* are available. Any Plugin that uses the methods generateEnvironmentView,
* getControllerView, and getSensorView of the classes AbstractEnvironment,
* AbstractAgent, and GenericSensor, resp., has to add itself to this
* list.
*
* @param vis The observing visualization.
*/
@Override
public synchronized void addVisualizer(final Plugin<?> vis) {
super.addVisualizer(vis);
for (AbstractAgent2D<?> a : this.agents2D.values()) {
a.setVisualized(true);
}
}
/**
* Removes a visualizing plugin from this environment. The information if a
* visualizer is currently observing the environment should be passed on
* to agents and generic sensors to provide them with the possibility of
* omitting expensive visualization computations as long as no visualizers
* are available.
*
* @param vis The observing visualization.
*/
@Override
public synchronized void removeVisualizer(final Plugin<AbstractEnvironment<AbstractAgent<?>>> vis) {
super.removeVisualizer(vis);
if (!this.isVisualized()) {
for (AbstractAgent2D<?> a : this.agents2D.values()) {
a.setVisualized(false);
}
}
}
@Override
public List<SingleParameter> getParameters() {
List<SingleParameter> params = super.getParameters();
params.add(new SingleParameter(
"DetailedAgentView?",
Datatypes.BOOLEAN,
true,
"If the agent picture is painted in the environment "
+ "during visualization.",
"ABSTRACT_ENVIRONMENT_2D"));
params.add(new SingleParameter(
"ShowGrid?",
Datatypes.BOOLEAN,
false,
"If a grid is painted in the environment "
+ "during visualization.",
"ABSTRACT_ENVIRONMENT_2D"));
params.add(new SingleParameter(
"GridDistance",
Datatypes.VECTOR2D,
new Vector2D(1, 1),
"The distance of the single grid lines.",
"ABSTRACT_ENVIRONMENT_2D"));
params.add(new SingleParameter(
"GridDelta",
Datatypes.VECTOR2D,
new Vector2D(0, 0),
"The delta translation correction of the grid.",
"ABSTRACT_ENVIRONMENT_2D"));
params.add(new SingleParameter(
"showAgentIDs?",
Datatypes.BOOLEAN,
false,
"If agent IDs should be shown in visualization.",
"ABSTRACT_ENVIRONMENT_2D"));
params.add(new SingleParameter(
"WheelRadiuses",
Datatypes.VECTOR2D,
new Vector2D(1, 1),
"The radiuses (R_l, R_r) of the wheels of the agents.",
"ABSTRACT_ENVIRONMENT_2D"));
ArrayListInt transparent = new ArrayListInt(new Integer[] {255, 255, 255, 0});
params.add(new SingleParameter(
"BackgroundColor",
Datatypes.INTEGER_ARR,
transparent,
"The color (R, G, B, A) element {0, ..., 255} x {0, ..., 255} x {0, ..., 255} x {0, ..., 255} of the arena's background.",
"ABSTRACT_ENVIRONMENT_2D"));
params.add(new SingleParameter(
"PaintPinkBoundingBox?",
Datatypes.BOOLEAN,
true,
"If the pink bounding box is painted in the visualization.",
"ABSTRACT_ENVIRONMENT_2D"));
params.add(new SingleParameter(
"StrangeView?",
Datatypes.BOOLEAN,
false,
"Displays the fuzzy view.",
"ABSTRACT_ENVIRONMENT_2D"));
params.add(new SingleParameter(
"ProduceWarningWhenAgentIDChanges?",
Datatypes.BOOLEAN,
false,
"Produces a warning whenever an agent is inserted in an "
+ "environment which already has an agent with that id.",
"ABSTRACT_ENVIRONMENT_2D"));
return params;
}
@Override
public boolean addAgent(AgentType agent) {
return this.addAgent(agent, new Vector2D(0, 0), 0);
}
/**
* @param pos A position in the cartesian koordinate system of the
* environment.
*
* @return An agent whoes shape includes the position, null if there
* does not exist such an agent.
*/
public AbstractAgent2D<?> getAgentAtPosition(final Vector2D pos) {
for (AbstractAgent2D<?> a : this.agents2D.values()) {
if (this.getAgentShapeInEnvironment(a.id()).isPointWithin(pos)) {
return a;
}
}
return null;
}
public Polygon2D getPolygonInVisualization(final Polygon2D polInEnv) {
Polygon2D pol = new Polygon2D();
for (Vector2D point : polInEnv) {
pol.add(this.getPointInVisualization(point));
}
return pol;
}
public Polygon2D getAgentShapeInVisualization(final int agentID) {
Polygon2D pol = new Polygon2D();
for (Vector2D point : this.getAgentShapeInEnvironment(agentID)) {
pol.add(this.getPointInVisualization(point));
}
return pol;
}
/**
* Same as getAgentShapeInVisualization(final int agentID), but with explicit request of
* zoom box and screen sizes.
*/
public synchronized Polygon2D getAgentShapeInVisualization(
final int agentID,
final double screenWidthOneTime,
final double screenHeightOneTime,
final Rectangle2D zoomBoxOneTime) {
Rectangle2D oldZoom = null;
if (this.zoomBox != null) {
oldZoom = new Rectangle2D(this.zoomBox.upperLeftCorner(), this.zoomBox.lowerRightCorner());
}
double oldWidth = this.getScreenWidth();
double oldHeight = this.getScreenHeight();
this.setZoomBox(zoomBoxOneTime);
this.setScreenWidth(screenWidthOneTime);
this.setScreenHeight(screenHeightOneTime);
Polygon2D zwischPol = this.getAgentShapeInVisualization(agentID);
this.setZoomBox(oldZoom);
this.setScreenWidth(oldWidth);
this.setScreenHeight(oldHeight);
return zwischPol;
}
public void setAgentCollidingState(final int agentID, final boolean isColliding) {
if (isColliding) {
this.collidingAgents.put(agentID, null);
} else {
this.collidingAgents.remove(agentID);
}
}
private Random rand;
public boolean simulateAccident(int agentID) {
if (this.rand == null) {
this.rand = new Random(this.pars.getSeed());
}
return this.simulateAccident(360, 4, new Random(), agentID);
}
public boolean simulateAccident(
double maxAngle,
double maxDistance,
Random rand,
int agentID) {
if (this.rand == null) {
this.rand = rand;
}
boolean erfolgreich = false;
double xVersch = 0;
double yVersch = 0;
int modus;
ArrayList<Integer> modi = new ArrayList<Integer>(6);
modi.add(new Integer(0));
modi.add(new Integer(1));
modi.add(new Integer(2));
modi.add(new Integer(3));
modi.add(new Integer(4));
modi.add(new Integer(5));
while (!erfolgreich && modi.size() > 0) {
modus = rand.nextInt(modi.size());
switch (modi.get(modus).intValue()) {
case 0:
xVersch = 1;
yVersch = 0;
break;
case 1:
xVersch = 0;
yVersch = 1;
break;
case 2:
xVersch = 1;
yVersch = 1;
break;
case 3:
xVersch = -1;
yVersch = 0;
break;
case 4:
xVersch = 0;
yVersch = -1;
break;
case 5:
xVersch = -1;
yVersch = -1;
break;
default:
break;
}
if (this.setAgentPosition(
agentID,
new Vector2D(
this.getAgentPosition(agentID).x + xVersch * maxDistance,
this.getAgentPosition(agentID).y + yVersch * maxDistance))) {
erfolgreich = true;
}
modi.remove(modus);
}
if (this.setAgentAngle(agentID, this.getAgentAngle(agentID) - (rand.nextDouble() * maxAngle))) {
erfolgreich = true;
}
return erfolgreich;
}
/**
* The step method of an environment is executed by a standard scheduler
* every tick of the course of time. More generally, the step method can
* be called by a scheduler at every step in time. It should provide for
* a continued running of the simulation. The current simulation time is
* accessible by the parameter Wink. Note that the step method is called
* for regular time steps only; events are to be handled over the method
* handleEvent.
*/
@Override
public void step(Wink simTime) {
}
@Override
public void onSimulationResumed() {
super.onSimulationResumed();
this.requestRedraw();
}
}