/*
* Licensed Materials - Property of Rogue Wave Software, Inc.
* © Copyright Rogue Wave Software, Inc. 2014, 2017
* © Copyright IBM Corp. 2009, 2014
* © Copyright ILOG 1996, 2009
* All Rights Reserved.
*
* Note to U.S. Government Users Restricted Rights:
* The Software and Documentation were developed at private expense and
* are "Commercial Items" as that term is defined at 48 CFR 2.101,
* consisting of "Commercial Computer Software" and
* "Commercial Computer Software Documentation", as such terms are
* used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4,
* as applicable.
*/
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.Collection;
import java.util.Iterator;
import java.util.Properties;
import java.util.Vector;
import javax.swing.Timer;
import ilog.views.IlvGraphic;
import ilog.views.IlvLinkImage;
import ilog.views.IlvPoint;
import ilog.views.IlvRect;
import ilog.views.IlvStroke;
import ilog.views.diagrammer.IlvDiagrammer;
import ilog.views.graphic.IlvArc;
import ilog.views.graphic.IlvMarker;
import ilog.views.sdm.IlvSDMEngine;
import ilog.views.sdm.graphic.IlvGeneralLink;
import ilog.views.sdm.model.IlvDefaultSDMNode;
import ilog.views.sdm.renderer.IlvSDMRenderer;
/**
* This class implements a simulator that animates objects.
*
*/
public final class Simulator implements ActionListener {
/**
* Use a custom renderer to simulate CSS
*/
private static class NoCSSRenderer extends IlvSDMRenderer {
Interactor _interactor = new Interactor();
Override
public IlvGraphic createLinkGraphic(IlvSDMEngine engine, Object link, IlvGraphic from, IlvGraphic to) {
/*
* link { class : "ilog.views.sdm.graphic.IlvGeneralLink" ; lineWidth : 15
* foreground : yellow ; endCap : CAP_ROUND ; }
*/
IlvGeneralLink g = new IlvGeneralLink();
g.setFrom(from);
g.setTo(to);
g.setLineWidth(15);
g.setForeground(Color.yellow);
g.setEndCap(IlvStroke.CAP_ROUND);
return g;
}
Override
public void addLinkGraphic(IlvSDMEngine engine, Object link, IlvGraphic graphic, boolean redraw) {
// be sure link layer is below node layer
engine.getGrapher().addLink((IlvLinkImage) graphic, 9, redraw);
}
Override
public IlvGraphic createNodeGraphic(IlvSDMEngine engine, Object node) {
/*
* node.core{ class : "ilog.views.graphic.IlvMarker" ; type : "0" ;
* LinkConnector : "@#connector"; }
*/
if ("core".equals(engine.getModel().getTag(node))) {
Integer X = (Integer) engine.getModel().getObjectProperty(node, "x");
Integer Y = (Integer) engine.getModel().getObjectProperty(node, "y");
IlvMarker result = new IlvMarker(new IlvPoint(X.doubleValue(), Y.doubleValue()), 0);
/*
* Subobject#connector { class :
* "ilog.views.linkconnector.IlvCenterLinkConnector"; }
*/
new ilog.views.linkconnector.IlvCenterLinkConnector().attach(result, false);
return result;
}
/*
* node{ class : "Graphic" ; color : "@color"; label : "@label"; position
* : "@position"; LinkConnector : "@#connector"; Interactor : "Interactor"
* ; }
*/
Graphic result = new Graphic();
updateNodeProperties(engine, node, result);
new ilog.views.linkconnector.IlvCenterLinkConnector().attach(result, false);
result.setObjectInteractor(_interactor);
return result;
}
Override
public void addNodeGraphic(IlvSDMEngine engine, Object node, IlvGraphic graphic, boolean redraw) {
// be sure link layer is below node layer
engine.getGrapher().addNode(graphic, 10, redraw);
}
Override
public void propertiesChanged(IlvSDMEngine engine, Object object, Collection<String> propertyNames,
IlvGraphic graphic) {
if (graphic instanceof Graphic) {
Graphic g = (Graphic) graphic;
updateNodeProperties(engine, object, g);
}
// no other property changed expected
}
// update volatile Graphic properties
private void updateNodeProperties(IlvSDMEngine engine, Object node, Graphic g) {
g.setColor((String) engine.getModel().getObjectProperty(node, "color"));
g.setLabel((String) engine.getModel().getObjectProperty(node, "label"));
g.setPosition((String) engine.getModel().getObjectProperty(node, "position"));
// update position
Integer X = (Integer) engine.getModel().getObjectProperty(node, "x");
Integer Y = (Integer) engine.getModel().getObjectProperty(node, "y");
g.move(X.doubleValue(), Y.doubleValue());
}
}
/**
* The name of the resource that contains the CSS.
*/
private static final String CSS = "node.css";
/**
* The name of the properties file that controls the simulation.
*/
private static final String PROPERTIES = "simulator.properties";
/**
* The external properties that control the simulation.
*/
private static Properties properties;
/**
* The instance of the IlvDiagrammer.
*/
private IlvDiagrammer diagrammer;
/**
* The Swing timer used to animate the objects in the model.
*/
private Timer timer;
// Properties that control the simulation.
/**
* The minimum number of objects to create in the random model.
*/
private int minObjects = getSimulatorInt("min.objects");
/**
* The maximum number of objects to create in the random model.
*/
private int maxObjects = getSimulatorInt("max.objects");
/**
* The maximum speed for the objects in the model.
*/
private int maxSpeed = getSimulatorInt("max.speed");
/**
* The delay between clock ticks of the simulator.
*/
private int timerDelay = getSimulatorInt("simulator.interval");
private int speedAccelerator = 1;
/**
* the rotating speed of the "radar" in degrees per timer delay
*/
private int radarSpeed = getSimulatorInt("radar.speed");
/**
* center and size of the ellipse, used as trajectory for moving the objects
*/
private int cx = 382;
private int cy = 280;
private int rx = 108;
private int ry = 50;
/**
* A node used as the center of the ellipse. Its display properties are
* defined in the CSS
*/
private IlvDefaultSDMNode core;
/**
* A graphic object representing the area of the radar
*/
private IlvArc radar = null;
/**
* Poistion of the current node that is being moved
*/
private final IlvPoint nodePosition = new IlvPoint();
/**
* graphics objects representing areas on the drawing.
*/
private IlvGraphic redZone;
private IlvGraphic blueZone;
private IlvGraphic greenZone;
/**
* counters
*/
// private int inRedZone = 0;
// private int inBlueZone = 0;
// private int inGreenZone = 0;
/**
* position of the next object to park
*/
int parkLimitX = CustomGraphics.VIEW_WIDTH;
/**
* an array to store object which are already parked
*/
Vector<Node> parkedNodes = new Vector<Node>();
/**
* A formatter to display decimal values
*/
private DecimalFormat formatter = new DecimalFormat("#.#");
/**
* The node choosen as target
*/
private Node targetNode = null;
/**
* The link that will link the core and the target
*/
private Object link = null;
/**
* The primary speed of the target
*/
int primaryNodeSpeed = 0;
/**
* Create an instance of the simulator.
*
* @param diagram
* the instance of the diagrammer.
* @param useCSS
* if false use a custom renderer (without CSS) as well.
*
* @throws Exception
* if an error occurs for any reason.
*/
public Simulator(final IlvDiagrammer diagram, boolean useCSS) throws Exception {
diagrammer = diagram;
timer = new Timer(timerDelay, this);
if (useCSS) {
// this is the style-sheet for our symbols
URL css = Simulator.class.getResource(CSS);
if (css != null) {
diagrammer.getEngine().setBaseURL(css.toString());
diagrammer.setStyleSheet(css);
} else {
throw new Exception("Unable to load CSS " + CSS);
}
} else {
// no css: use a custom renderer to simulate the css.
diagram.getEngine().setRenderer(new NoCSSRenderer());
}
}
/**
* Start the simulator.
*/
public void start() {
if (!timer.isRunning()) {
timer.start();
}
}
/**
* Stop the simulator.
*/
public void stop() {
if (timer.isRunning()) {
timer.stop();
}
}
/**
* Perform the simulator action. This method is called by the timer, according
* to specified delay. Following tasks are done<br>
* - it moves the radar area - it moves all the nodes, excepts the link and
* the core, and get a random target - creates a link between the core and the
* target node (if exists) - if there's a target, bring it closer to the core
*
* @param e
* the {@link ActionEvent ActionEvent} associated with this action.
*/
Override
public void actionPerformed(final ActionEvent e) {
try {
setAdjusting(true);
// move radar. The radar area is an arc. To give the impression
// that it is turning, we change the start angle
radar.setStartAngle(radar.getStartAngle() - radarSpeed);
radar.reDraw();
Iterator<?> it = diagrammer.getAllObjects();
// reinit counters
// inRedZone = 0;
// inGreenZone = 0;
// inBlueZone = 0;
// move each node (except core and link)
while (it.hasNext()) {
Object obj = it.next();
if (obj != null && (obj instanceof Node) && obj != core && !diagrammer.isLink(obj)) {
Node node = (Node) obj;
moveNode(node);
// get a random target, among nodes with orbit >1
if (targetNode == null && node.getOrbit() > 1 && getRandom(0, 100) == 3) {
nodePosition.setLocation(node.getX(), node.getY());
if (radar.contains(nodePosition, nodePosition, null) && node.getProperty("action").equals("turning")) {
targetNode = node;
}
}
}
}
// all nodes have been moved and a target has been selected
// then creates a link if it does not yet exists
if (link == null && targetNode != null) {
// get the node speed before accelerating it to the radar speed
primaryNodeSpeed = targetNode.getSpeed();
targetNode.setSpeed(radarSpeed);
// clear printed information
diagrammer.setObjectProperty(targetNode, "label", "");
diagrammer.setObjectProperty(targetNode, "position", "");
// creates a link and adds it to the diagrammer
link = diagrammer.createLink("link", core, targetNode);
diagrammer.addObject(link, null);
}
// if a link is created then get the node closer and closer to the core
// until its orbit is <=1
// when done (orbit=1), reset node primary speed and printed data, remove
// link and
// prepare to get next target
if (link != null) {
double orbit = targetNode.getOrbit();
if (orbit > 1) {
// get node closer by changing its orbit
orbit -= 0.02;
targetNode.setOrbit(orbit);
} else {
// reset primary speed and printed data
targetNode.setSpeed(primaryNodeSpeed);
diagrammer.setObjectProperty(targetNode, "color", "black");
diagrammer.setObjectProperty(targetNode, "label", "");
diagrammer.setObjectProperty(targetNode, "position", "");
// remove link and reset data in order to prepare next target
diagrammer.removeObject(link);
targetNode = null;
link = null;
}
}
} finally {
setAdjusting(false);
}
}
/**
* Move the Node according to its "action" property:<br>
* - make it turning around the core, according to speed, angle and orbit -
* park it in the right bottom corner of the view - unpark it
*
* @param node
* the Node to move.
*/
private void moveNode(final Node node) {
// If the node is parked then do nothing
String action = (String) node.getProperty("action");
if (action.equals("parked")) {
return;
}
// gets position of the node for future computation
int newX = node.getX();
int newY = node.getY();
// gets node data
double orbit = node.getOrbit();
int angle = node.getAngle();
int speed = node.getSpeed();
// --- the node is turning ---
if (action.startsWith("turning")) {
// computes new position
angle = angle + speed;
node.setAngle(angle);
newX = cx + (int) (Math.cos(Math.toRadians(angle)) * (orbit * rx));
newY = cy + (int) (Math.sin(Math.toRadians(angle)) * (orbit * ry));
nodePosition.setLocation(newX, newY);
// changes "position" property of the node
if (node != targetNode) {
diagrammer.setObjectProperty(node, "position",
"L" + formatter.format(orbit) + " / " + angle + "\u00B0 / " + speed);
}
// moves the node
diagrammer.setObjectProperty(node, "x", new Integer(newX));
diagrammer.setObjectProperty(node, "y", new Integer(newY));
// Do not do following actions if orbit <=1
if (orbit <= 1) {
return;
}
// get IlvGraphic objects corresponding to areas
if (redZone == null)
redZone = diagrammer.getView().getManager().getObject("REDZONE");
if (blueZone == null)
blueZone = diagrammer.getView().getManager().getObject("BLUEZONE");
if (greenZone == null)
greenZone = diagrammer.getView().getManager().getObject("GREENZONE");
// test if the node enters/leaves a predefined area, and changes its
// "color" property
if (redZone.contains(nodePosition, nodePosition, null)) {
diagrammer.setObjectProperty(node, "color", "red");
// inRedZone++;
} else if (blueZone.contains(nodePosition, nodePosition, null)) {
diagrammer.setObjectProperty(node, "color", "blue");
// inBlueZone++;
} else if (greenZone.contains(nodePosition, nodePosition, null)) {
diagrammer.setObjectProperty(node, "color", "green");
// inGreenZone++;
}
}
// --- the node must be parked ---
else if (action.startsWith("parking")) {
// compute new Y until reaching the bottom line
if (!action.endsWith("_x")) {
newY += 12;
if (newY >= 550) {
newY = 550;
node.setProperty("action", "parking_x"); // this indicates that in
// next timer animations, the
// node should only moves on
// X
diagrammer.setObjectProperty(node, "label", "");
diagrammer.setObjectProperty(node, "position", "");
}
}
// compute new X when bottom has been reached
else {
newX += 12;
if (newX + 9 >= parkLimitX) { // when nodes reach the parking position
// stop it
newX = parkLimitX - 12;
parkLimitX = parkLimitX - 18;
node.setProperty("action", "parked"); // this "parked" property value
// prevents the node from moving
// next times
parkedNodes.add(node); // adds the node in the list of the parked
// nodes
}
}
diagrammer.setObjectProperty(node, "x", new Integer(newX));
diagrammer.setObjectProperty(node, "y", new Integer(newY));
}
// --- the node must be unparked ---
else if (action.startsWith("unparking")) {
// compute new Y until to reach the top line
if (!action.endsWith("_x")) {
// all the nodes which are already parked should be reparked before
// that the node leaves its parking position for the first time
if (newY == 550) {
int idx = parkedNodes.indexOf(node);
parkLimitX = parkLimitX + 18; // new limit for next park
for (int i = idx + 1; i < parkedNodes.size(); i++) {
Node n = parkedNodes.get(i);
// translate nodes by 18 pixels
int newx = ((Integer) n.getProperty("x")) + 18;
diagrammer.setObjectProperty(n, "x", new Integer(newx));
}
// current not is not anymmore part od the parked nodes
parkedNodes.remove(node);
}
// compute new Y
newY -= 12;
int topline = cy + (int) (Math.sin(Math.toRadians(angle)) * (orbit * ry));
if (newY < topline) {
newY = topline;
node.setProperty("action", "unparking_x"); // this indicates that in
// next timer aniation, the
// node should only moves
// on X
}
}
// compute X when top line has been reached
else {
newX -= 12;
int positionX = cx + (int) (Math.cos(Math.toRadians(angle)) * (orbit * rx));
if (newX - 9 < positionX) { // 9 = width of node's icone/2
newX = positionX;
node.setProperty("action", "turning"); // node is unparked, it can
// continue to turn around the
// core
}
}
// move the node to its new position
diagrammer.setObjectProperty(node, "x", new Integer(newX));
diagrammer.setObjectProperty(node, "y", new Integer(newY));
}
}
/**
* Add a random node to the model.
*/
private void addRandomNode() {
// we create our own node we do not ask the diagrammer to do it
double orbit = getRandom(2, 4);
int angle = getRandom(0, 360);
int speed = getRandom(1, maxSpeed);
int id2 = getRandom(0, 10);
int id1 = getRandom(0, 10);
String tag = "S." + id1 + "." + id2;
Node node = new Node(tag, orbit, angle, speed);
// set custom defined properties
diagrammer.setObjectProperty(node, "action", "turning");
diagrammer.setObjectProperty(node, "color", "green");
diagrammer.setObjectProperty(node, "label", tag);
diagrammer.setObjectProperty(node, "position", "L" + orbit + " / " + angle + "\u00B0 / " + speed);
// now set the initial location
int x = cx + (int) (Math.cos(Math.toRadians(angle)) * (orbit * rx));
int y = cy + (int) (Math.sin(Math.toRadians(angle)) * (orbit * ry));
node.setProperty("x", new Integer(x));
node.setProperty("y", new Integer(y));
diagrammer.addObject(node, null);
}
/**
* Create a random model.
*/
public void createRandomModel() {
try {
setAdjusting(true);
// delete any old nodes
Iterator<?> it = diagrammer.getAllObjects();
while (it.hasNext()) {
diagrammer.removeObject(it.next());
it = diagrammer.getAllObjects();
}
// how many nodes should we add?
int nNodes = getRandom(minObjects, maxObjects);
// create the nodes
for (int i = 0; i < nNodes; i++) {
addRandomNode();
}
// create the core
core = new IlvDefaultSDMNode("core");
core.setProperty("x", new Integer(cx));
core.setProperty("y", new Integer(cy));
diagrammer.addObject(core, null);
// creates the radar
if (radar == null) {
double rrx = 5 * rx;
double rry = 4 * ry;
radar = new IlvArc(new IlvRect(cx - rrx, cy - rry, 2 * rrx, 2 * rry), 0, 45, false, true);
radar.setBackground(new Color(255, 255, 0, 30));
diagrammer.getView().getManager().addObject(radar, 1, false);
}
} finally {
setAdjusting(false);
}
}
/**
* Get a value for the simulator from the properties files as a string.
*
* @param name
* the name of the property to get from the properties file.
*
* @return the value of the property.
*
* @throws IOException
* if the property can not be found for any reason.
*/
private static String getSimulatorString(final String name) throws IOException {
if (properties == null) {
properties = new Properties();
InputStream props = Simulator.class.getResourceAsStream(PROPERTIES);
if (props == null) {
throw new IOException("Not able to open resource " + PROPERTIES);
}
try {
properties.load(props);
} finally {
try {
props.close();
} catch (IOException e) {
// ignore
}
}
}
if (properties == null) {
throw new IOException("Not able to read properties " + PROPERTIES);
}
String res = properties.getProperty(name);
if (res != null) {
return res;
}
// if we get here then a problem occurred
throw new IOException("No property " + name + " in " + PROPERTIES);
}
/**
* Get a value for the simulator from the properties files as an integer.
*
* @param name
* the name of the property to get from the properties file.
*
* @return the value of the property.
*
* @throws IOException
* if the property can not be found for any reason.
*/
private static int getSimulatorInt(final String name) throws IOException {
return Integer.parseInt(getSimulatorString(name));
}
/**
* Create a random number within a given range.
*
* @param min
* the minimum value for the random number.
* @param max
* the maximum value for the random number.
*
* @return the random number.
*/
private int getRandom(final int min, final int max) {
double d = Math.random() * (max - min);
return (min + (int) (d + 0.5));
}
/**
* Convenience method to set the adjusting flag on the Diagrammer engine.
*
* @param adjusting
* <code>true</code> if we are making changes to the model,
* <code>false</code> if we have finished the changes to the model.
*/
private void setAdjusting(final boolean adjusting) {
diagrammer.getEngine().setAdjusting(adjusting);
}
/**
* Accelerate the timer delay
*
* @param speedAccelerator
* the factor of acceleration
*/
public void setSpeedAccelerator(int speedAccelerator) {
// do nothing if zero
if (speedAccelerator < 1) {
this.speedAccelerator = 1;
return;
}
this.speedAccelerator = speedAccelerator;
int newdelay = timerDelay / speedAccelerator;
timer.setDelay(newdelay);
}
/**
* Reset the simulator and recreate the model
*/
public void reset() {
stop();
timer = new Timer(timerDelay, this);
setSpeedAccelerator(speedAccelerator);
parkedNodes.clear();
parkLimitX = CustomGraphics.VIEW_WIDTH;
createRandomModel();
}
}