Package eas.simulation.spatial.sim2D.standardEnvironments

Source Code of eas.simulation.spatial.sim2D.standardEnvironments.AbstractEnvironment2D

/*
* 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();
    }
}
TOP

Related Classes of eas.simulation.spatial.sim2D.standardEnvironments.AbstractEnvironment2D

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.