/* * Licensed Materials - Property of Perforce Software, Inc. * © Copyright Perforce Software, Inc. 2014, 2021 * © 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", Integer.valueOf(newX)); diagrammer.setObjectProperty(node, "y", Integer.valueOf(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", Integer.valueOf(newX)); diagrammer.setObjectProperty(node, "y", Integer.valueOf(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", Integer.valueOf(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", Integer.valueOf(newX)); diagrammer.setObjectProperty(node, "y", Integer.valueOf(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", Integer.valueOf(x)); node.setProperty("y", Integer.valueOf(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", Integer.valueOf(cx)); core.setProperty("y", Integer.valueOf(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(); } }