/*
* File name: Scene2D.java
* Java version: 6.0
* Author(s): Lukas König
* File created: 09.10.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.standardScenes;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import eas.math.geometry.Rectangle2D;
import eas.math.geometry.Vector2D;
import eas.miscellaneous.StaticMethods;
import eas.simulation.Wink;
import eas.simulation.spatial.sim2D.standardAgents.AbstractAgent2D;
import eas.simulation.spatial.sim2D.standardEnvironments.AbstractEnvironment2D;
import eas.startSetup.ParCollection;
/**
* @author Lukas König
*
*/
public class Scene2D<AgentType extends AbstractAgent2D<?>> implements Serializable {
private static final long serialVersionUID = 1939377131147263727L;
private HashMap<Integer, AgentType> agentList;
private HashMap<Integer, Vector2D> agentPositions;
private HashMap<Integer, Double> agentAngles;
private HashMap<Integer, Vector2D> agentScales;
private HashMap<Integer, Boolean> collidingAgents;
private AffineTransform sceneTransformation;
private double sceneAngle = 0;
private Vector2D sceneScale = new Vector2D(1, 1);
private String identity;
private ParCollection pars;
public Scene2D(final String ident, final ParCollection params) {
this.pars = params;
this.identity = ident;
this.clearScene();
}
private int firstFreeID = 0;
private void findNextFreeID() {
while (this.agentAngles.get(this.firstFreeID) != null) {
this.firstFreeID++;
}
}
/**
* 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> or by the return
* value of this method.
*
* @param agent
* @param agentPosition
* @param agentViewAngle
* @param agentScale
*
* @return The id this agent was set to in the scene.
*/
public int addAgent(
final AgentType agent,
final Vector2D agentPosition,
final double agentViewAngle,
final Vector2D agentScale) {
int id;
id = agent.id();
// ID existiert bereits.
if (this.agentAngles.get(id) != null) {
id = this.firstFreeID;
if (this.pars.getParValueBoolean("ProduceWarningWhenAgentIDChanges?")) {
StaticMethods.logWarning(
this.identity + " --> Agent ID " + agent.id()
+ " was replaced by ID " + id + ".",
this.pars);
}
agent.setID(id);
}
this.agentList.put(agent.id(), agent);
this.agentAngles.put(id, agentViewAngle);
this.agentPositions.put(id, agentPosition);
this.agentScales.put(id, agentScale);
this.collidingAgents.put(id, false);
if (id == this.firstFreeID) {
this.findNextFreeID();
}
return id;
}
/**
* Note that adding a colliding agent to a scene will not check for
* spatial collisions with other agents in the scene. However, when
* placing the scene in an environment a colliding agent may not be
* placed into the environment if it would collide with an existing
* agent.
*
* 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> or by the return value of this method.
*
* @param agent
* @param agentPosition
* @param agentViewAngle
* @param agentScale
*
* @return The id this agent was set to in the scene.
*/
public int addCollidingAgent(
final AgentType agent,
final Vector2D agentPosition,
final double agentViewAngle,
final Vector2D agentScale) {
int id;
id = agent.id();
// ID existiert bereits.
if (this.agentAngles.get(id) != null) {
id = this.firstFreeID;
if (this.pars.getParValueBoolean("ProduceWarningWhenAgentIDChanges?")) {
StaticMethods.logWarning(
this.identity + " --> Agent ID " + agent.id()
+ " was replaced by ID " + id + ".",
this.pars);
}
agent.setID(id);
}
this.agentList.put(agent.id(), agent);
this.agentAngles.put(agent.id(), agentViewAngle);
this.agentPositions.put(agent.id(), agentPosition);
this.agentScales.put(agent.id(), agentScale);
this.collidingAgents.put(agent.id(), true);
if (id == this.firstFreeID) {
this.findNextFreeID();
}
return id;
}
public void addScene(final Scene2D<AgentType> scene) {
int id;
for (AgentType a : scene.getAgentList()) {
id = a.id();
if (scene.isColliding(id)) {
this.addCollidingAgent(
a,
scene.getAgentPosition(id),
scene.getAgentAngle(id),
scene.getAgentScale(id));
} else {
this.addAgent(
a,
scene.getAgentPosition(id),
scene.getAgentAngle(id),
scene.getAgentScale(id));
}
}
}
public void clearScene() {
if (this.agentList != null) {
ArrayList<Integer> agentIds = new ArrayList<Integer>(this.agentList.keySet());
for (int id : agentIds) {
this.removeAgent(id);
}
}
this.agentList = new HashMap<Integer, AgentType>();
this.agentAngles = new HashMap<Integer, Double>();
this.agentPositions = new HashMap<Integer, Vector2D>();
this.agentScales = new HashMap<Integer, Vector2D>();
this.collidingAgents = new HashMap<Integer, Boolean>();
this.sceneTransformation = new AffineTransform();
this.sceneAngle = 0;
}
public void removeAgent(final Integer agentID) {
this.agentList.remove(agentID);
this.agentAngles.remove(agentID);
this.agentPositions.remove(agentID);
this.agentScales.remove(agentID);
this.collidingAgents.remove(agentID);
if (agentID < this.firstFreeID) {
this.firstFreeID = agentID;
}
}
public void rotateScene(final double angleDEG) {
this.sceneTransformation.rotate(angleDEG / 180 * Math.PI);
this.sceneAngle += angleDEG;
}
public void scaleScene(final Vector2D scaling) {
this.sceneTransformation.scale(scaling.x, scaling.y);
this.sceneScale.x *= scaling.x;
this.sceneScale.y *= scaling.y;
}
public void scaleScene(final double scaling) {
this.sceneTransformation.scale(scaling, scaling);
this.sceneScale.x *= scaling;
this.sceneScale.y *= scaling;
}
public void translateScene(final Vector2D translation) {
this.sceneTransformation.translate(translation.x, translation.y);
}
public static final int FITTING_MODE_FIT_UPPER_LEFT = 0;
public static final int FITTING_MODE_FIT_LOWER_RIGHT = 1;
public static final int FITTING_MODE_FIT_CENTER = 2;
public void fitInBoundingRectangle(final Vector2D upperLeft, final Vector2D lowerRight) {
this.fitInBoundingRectangle(upperLeft, lowerRight, 0);
}
/**
* Makes the scene fit into the given bounding rectangle. There, the scenes
* width / height relation is preserved. The scene is scaled to fit in the
* rectangle with one dimension possible being not tight to the bounds, and
* the upper-left corner is set to the upper-left corner given by the
* parameter (note that the scene is not centered in the non-fitting
* dimension).<BR>
* <BR>
* CAUTION: The scene may be rotated, but not scaled or translated before.
*
* @param upperLeft The upper-left corner of the box the scene should fit
* in. This is also the upper-left corner of the bounding
* box of the transformed scene. (Standard - may vary
* depending on fitting mode.)
* @param lowerRight The lower-right corner of the box the scene should fit
* in. This is not neccessarily the lower-right corner
* of the bounding box of the target scene in BOTH
* dimensions (it is, however, in at least one dimension).
* (Standard - may vary depending on fitting mode.)
* @param
*
*/
public void fitInBoundingRectangle(final Vector2D upperLeft, final Vector2D lowerRight, final int mode) {
Scene2D<AgentType> scaledScene = new Scene2D<AgentType>("", this.pars);
Scene2D<AgentType> sceneCopy = new Scene2D<AgentType>("", this.pars);
scaledScene.addScene(this);
sceneCopy.addScene(this);
Rectangle2D boundingBox = this.getBoundingBox();
double currentWidth = boundingBox.getWidth();
double currentHeight = boundingBox.getHeight();
double desiredWidth = Math.abs(lowerRight.x - upperLeft.x);
double desiredHeight = Math.abs(lowerRight.y - upperLeft.y);
Vector2D newUpperLeftCorner = new Vector2D(Math.min(upperLeft.x, lowerRight.x), Math.min(upperLeft.y, lowerRight.y));
Vector2D newLowerRightCorner = new Vector2D(Math.max(upperLeft.x, lowerRight.x), Math.max(upperLeft.y, lowerRight.y));
double scaleX = desiredWidth / currentWidth;
double scaleY = desiredHeight / currentHeight;
double scale = Math.min(scaleX, scaleY);
scaledScene.scaleScene(scale);
double transX, transY;
if (mode == Scene2D.FITTING_MODE_FIT_UPPER_LEFT) {
Vector2D currentUpperLeftCorner = scaledScene.getBoundingBox().upperLeftCorner();
transX = newUpperLeftCorner.x - currentUpperLeftCorner.x;
transY = newUpperLeftCorner.y - currentUpperLeftCorner.y;
} else if (mode == Scene2D.FITTING_MODE_FIT_LOWER_RIGHT) {
Vector2D currentLowerRightCorner = scaledScene.getBoundingBox().lowerRightCorner();
transX = newLowerRightCorner.x - currentLowerRightCorner.x;
transY = newLowerRightCorner.y - currentLowerRightCorner.y;
} else { // First case again - center mode not yet implemented!
Vector2D currentUpperLeftCorner = scaledScene.getBoundingBox().upperLeftCorner();
transX = newUpperLeftCorner.x - currentUpperLeftCorner.x;
transY = newUpperLeftCorner.y - currentUpperLeftCorner.y;
}
Vector2D translation = new Vector2D(transX, transY);
sceneCopy.translateScene(translation);
sceneCopy.scaleScene(scale);
this.clearScene();
this.addScene(sceneCopy);
}
/**
* Preserves the current scene, meaning that the scene transformations
* are fixed. After invocation of this method, all transformations
* performed on the transformation matrix are transferred to the agent
* positions, angles and scales, and a reset is performed on the
* transformation matrix.
*
* TODO: Some bug present? ==> Test!
*/
public void preserveScene() {
Scene2D<AgentType> sceneCopy = new Scene2D<AgentType>("", this.pars);
sceneCopy.addScene(this);
this.clearScene();
this.addScene(sceneCopy);
}
public Collection<AgentType> getAgentList() {
return this.agentList.values();
}
/*
* { m00 m10 m01 m11 m02 m12 }.
* { m00 m10 m01 m11 }.
* [ x'] [ m00 m01 m02 ] [ x ] [ m00x + m01y + m02 ]
* [ y'] = [ m10 m11 m12 ] [ y ] = [ m10x + m11y + m12 ]
* [ 1 ] [ 0 0 1 ] [ 1 ] [ 1 ]
*/
public Vector2D getAgentPosition(final int agentID) {
double[] matrix = new double[6];
this.sceneTransformation.getMatrix(matrix);
Vector2D pos = new Vector2D(this.agentPositions.get(agentID));
double m00 = matrix[0];
double m10 = matrix[1];
double m01 = matrix[2];
double m11 = matrix[3];
double m02 = matrix[4];
double m12 = matrix[5];
double x = pos.x;
double y = pos.y;
pos.x = m00 * x + m01 * y + m02;
pos.y = m10 * x + m11 * y + m12;
return pos;
}
public double getAgentAngle(final int agentID) {
return this.agentAngles.get(agentID) + this.sceneAngle;
}
public Vector2D getAgentScale(final int agentID) {
Vector2D agentScale = this.agentScales.get(agentID);
Vector2D completeScale = new Vector2D(
this.sceneScale.x,
this.sceneScale.y);
completeScale.x *= agentScale.x;
completeScale.y *= agentScale.y;
return completeScale;
}
public String getIdentity() {
return this.identity;
}
/**
* @param agentID
*
* @return The agents collision status (true => is colliding). null if
* agent does not exist.
*/
public Boolean isColliding(final int agentID) {
return this.collidingAgents.get(agentID);
}
/**
* Generates a BufferedImage view of the scene by creating an
* Abstractenvironment2D, adding this scene to it and returning the
* fieldView of the environment.
*
* @return A field view of the environment.
*/
public BufferedImage getSceneFiew() {
AbstractEnvironment2D<AgentType> env
= new AbstractEnvironment2D<AgentType>(0, this.pars) {
/**
*
*/
private static final long serialVersionUID = 5497508512710657553L;
@Override
public void step(Wink simTime) {
}
};
env.addScene(this);
return env.getOutsideView();
}
/**
* @return The bounding box of this scene (null if no agent has been
* placed so far).
* The calculation is not efficient as the scene is converted
* into an environment and its bounding box calculation is used.
* TODO: Make efficient.
*/
public Rectangle2D getBoundingBox() {
AbstractEnvironment2D<AgentType> env = new AbstractEnvironment2D<AgentType>(0, this.pars) {
/**
*
*/
private static final long serialVersionUID = 6941143245566787898L;
@Override
public void step(Wink simTime) {
}
};
env.addScene(this);
return env.getBoundingBox(true);
}
}