package org.opentrafficsim.draw.factory; import java.awt.Color; import java.rmi.RemoteException; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import javax.naming.NamingException; import org.djutils.event.EventInterface; import org.djutils.event.EventListenerInterface; import org.djutils.logger.CategoryLogger; import org.opentrafficsim.core.animation.gtu.colorer.GtuColorer; import org.opentrafficsim.core.dsol.OTSSimulatorInterface; import org.opentrafficsim.core.gtu.Gtu; import org.opentrafficsim.core.gtu.GtuGenerator; import org.opentrafficsim.core.gtu.GtuType; import org.opentrafficsim.core.network.LateralDirectionality; import org.opentrafficsim.core.network.LinkInterface; import org.opentrafficsim.core.network.Network; import org.opentrafficsim.core.network.NetworkInterface; import org.opentrafficsim.core.network.NodeInterface; import org.opentrafficsim.core.object.ObjectInterface; import org.opentrafficsim.draw.core.OTSDrawingException; import org.opentrafficsim.draw.gtu.DefaultCarAnimation; import org.opentrafficsim.draw.network.LinkAnimation; import org.opentrafficsim.draw.network.NodeAnimation; import org.opentrafficsim.draw.road.BusStopAnimation; import org.opentrafficsim.draw.road.ConflictAnimation; import org.opentrafficsim.draw.road.LaneAnimation; import org.opentrafficsim.draw.road.SensorAnimation; import org.opentrafficsim.draw.road.ShoulderAnimation; import org.opentrafficsim.draw.road.SpeedSignAnimation; import org.opentrafficsim.draw.road.StripeAnimation; import org.opentrafficsim.draw.road.StripeAnimation.TYPE; import org.opentrafficsim.draw.road.TrafficLightAnimation; import org.opentrafficsim.draw.road.TrafficLightSensorAnimation; import org.opentrafficsim.road.gtu.lane.LaneBasedGtu; import org.opentrafficsim.road.network.lane.CrossSectionElement; import org.opentrafficsim.road.network.lane.CrossSectionLink; import org.opentrafficsim.road.network.lane.Lane; import org.opentrafficsim.road.network.lane.Shoulder; import org.opentrafficsim.road.network.lane.Stripe; import org.opentrafficsim.road.network.lane.conflict.Conflict; import org.opentrafficsim.road.network.lane.object.BusStop; import org.opentrafficsim.road.network.lane.object.SpeedSign; import org.opentrafficsim.road.network.lane.object.sensor.DestinationSensor; import org.opentrafficsim.road.network.lane.object.sensor.SingleSensor; import org.opentrafficsim.road.network.lane.object.sensor.SinkSensor; import org.opentrafficsim.road.network.lane.object.sensor.TrafficLightSensor; import org.opentrafficsim.road.network.lane.object.trafficlight.SimpleTrafficLight; import org.opentrafficsim.road.network.lane.object.trafficlight.TrafficLight; import nl.tudelft.simulation.dsol.SimRuntimeException; import nl.tudelft.simulation.dsol.animation.D2.Renderable2D; /** * DefaultAnimationFactory.java. *

* Copyright (c) 2003-2021 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. * BSD-style license. See OpenTrafficSim License. *

* @author Alexander Verbraeck */ public class DefaultAnimationFactory implements EventListenerInterface { /** */ private static final long serialVersionUID = 20210301L; /** The simulator. */ private final OTSSimulatorInterface simulator; /** GTU colorer. */ private final GtuColorer gtuColorer; /** Rendered GTUs. */ private Map> animatedGTUs = Collections.synchronizedMap(new LinkedHashMap<>()); /** rendered static objects. */ private Map> animatedObjects = Collections.synchronizedMap(new LinkedHashMap<>()); /** * Creates animations for nodes, links and lanes. The class will subscribe to the network and listen to changes, so the * adding and removing of GTUs and Objects is animated correctly. * @param network Network; the network * @param gtuColorer GtuColorer; GTU colorer * @param animateNetwork boolean; whether to animate the current network objects * @throws OTSDrawingException on drawing error */ protected DefaultAnimationFactory(final Network network, final GtuColorer gtuColorer, final boolean animateNetwork) throws OTSDrawingException { this.simulator = network.getSimulator(); this.gtuColorer = gtuColorer; // subscribe to adding and removing events network.addListener(this, NetworkInterface.ANIMATION_GTU_ADD_EVENT); network.addListener(this, NetworkInterface.ANIMATION_GTU_REMOVE_EVENT); network.addListener(this, NetworkInterface.ANIMATION_OBJECT_ADD_EVENT); network.addListener(this, NetworkInterface.ANIMATION_OBJECT_REMOVE_EVENT); network.addListener(this, NetworkInterface.ANIMATION_GENERATOR_ADD_EVENT); network.addListener(this, NetworkInterface.ANIMATION_GENERATOR_REMOVE_EVENT); // model the current infrastructure try { if (animateNetwork) { for (NodeInterface node : network.getNodeMap().values()) { new NodeAnimation(node, this.simulator); } for (LinkInterface link : network.getLinkMap().values()) { new LinkAnimation(link, this.simulator, 0.5f); if (link instanceof CrossSectionLink) { for (CrossSectionElement element : ((CrossSectionLink) link).getCrossSectionElementList()) { if (element instanceof Lane) { new LaneAnimation((Lane) element, this.simulator, Color.GRAY.brighter()); } else if (element instanceof Shoulder) { new ShoulderAnimation((Shoulder) element, this.simulator, Color.DARK_GRAY); } else if (element instanceof Stripe) { Stripe stripe = (Stripe) element; TYPE type; if (stripe.isPermeable(network.getGtuType(GtuType.DEFAULTS.CAR), LateralDirectionality.LEFT)) { type = stripe.isPermeable(network.getGtuType(GtuType.DEFAULTS.CAR), LateralDirectionality.RIGHT) ? TYPE.DASHED : TYPE.LEFTONLY; } else { type = stripe.isPermeable(network.getGtuType(GtuType.DEFAULTS.CAR), LateralDirectionality.RIGHT) ? TYPE.RIGHTONLY : TYPE.SOLID; } new StripeAnimation((Stripe) element, this.simulator, type); } } } } for (TrafficLight tl : network.getObjectMap(TrafficLight.class).values()) { new TrafficLightAnimation(tl, this.simulator); } } for (Gtu gtu : network.getGtus()) { Renderable2D gtuAnimation = new DefaultCarAnimation((LaneBasedGtu) gtu, this.simulator, this.gtuColorer); this.animatedGTUs.put((LaneBasedGtu) gtu, gtuAnimation); } for (ObjectInterface object : network.getObjectMap().values()) { animateStaticObject(object); } } catch (RemoteException | NamingException exception) { throw new OTSDrawingException("Exception while creating network animation.", exception); } } /** * Creates animations for nodes, links, lanes and GTUs. This can be used if the network is not read from XML. The class will * subscribe to the network and listen to changes, so the adding and removing of GTUs and Objects is animated correctly. * @param network Network; the network * @param simulator OTSSimulatorInterface; the simulator * @param gtuColorer GtuColorer; GTU colorer * @return the DefaultAnimationFactory * @throws OTSDrawingException on drawing error */ public static DefaultAnimationFactory animateNetwork(final Network network, final OTSSimulatorInterface simulator, final GtuColorer gtuColorer) throws OTSDrawingException { return new DefaultAnimationFactory(network, gtuColorer, true); } /** * Creates animations for nodes, links, lanes and GTUs. This can be used if the network is read from XML. The class will * subscribe to the network and listen to changes, so the adding and removing of GTUs and Objects is animated correctly. * @param network Network; the network * @param gtuColorer GtuColorer; GTU colorer * @return the DefaultAnimationFactory * @throws OTSDrawingException on drawing error */ public static DefaultAnimationFactory animateXmlNetwork(final Network network, final GtuColorer gtuColorer) throws OTSDrawingException { return new DefaultAnimationFactory(network, gtuColorer, false); } /** {@inheritDoc} */ @Override public void notify(final EventInterface event) throws RemoteException { try { if (event.getType().equals(NetworkInterface.ANIMATION_GTU_ADD_EVENT)) { // Schedule (delay) the addition of the GTU to ensure it has an operational plan LaneBasedGtu gtu = (LaneBasedGtu) event.getContent(); this.simulator.scheduleEventNow(this, this, "animateGTU", new Object[] {gtu}); } else if (event.getType().equals(NetworkInterface.ANIMATION_GTU_REMOVE_EVENT)) { LaneBasedGtu gtu = (LaneBasedGtu) event.getContent(); if (this.animatedGTUs.containsKey(gtu)) { this.animatedGTUs.get(gtu).destroy(this.simulator); this.animatedGTUs.remove(gtu); } } else if (event.getType().equals(NetworkInterface.ANIMATION_OBJECT_ADD_EVENT)) { ObjectInterface object = (ObjectInterface) event.getContent(); animateStaticObject(object); } else if (event.getType().equals(NetworkInterface.ANIMATION_OBJECT_REMOVE_EVENT)) { ObjectInterface object = (ObjectInterface) event.getContent(); if (this.animatedObjects.containsKey(object)) { this.animatedObjects.get(object).destroy(this.simulator); this.animatedObjects.remove(object); } } else if (event.getType().equals(NetworkInterface.ANIMATION_GENERATOR_ADD_EVENT)) { GtuGenerator gtuGenerator = (GtuGenerator) event.getContent(); animateGTUGenerator(gtuGenerator); } else if (event.getType().equals(NetworkInterface.ANIMATION_GENERATOR_REMOVE_EVENT)) { // TODO: change the way generators are animated } } catch (SimRuntimeException exception) { CategoryLogger.always().error(exception, "Exception while updating network animation."); } } /** * Draw the GTU (scheduled method). * @param gtu LaneBasedGtu; the GTU to draw */ protected void animateGTU(final LaneBasedGtu gtu) { try { Renderable2D gtuAnimation = new DefaultCarAnimation(gtu, this.simulator, this.gtuColorer); this.animatedGTUs.put(gtu, gtuAnimation); } catch (RemoteException | NamingException exception) { gtu.getSimulator().getLogger().always().error(exception, "Exception while drawing GTU."); } } /** * Draw the static object. * @param object ObjectInterface; the object to draw */ protected void animateStaticObject(final ObjectInterface object) { try { if (object instanceof SinkSensor) { SinkSensor sensor = (SinkSensor) object; // Renderable2D objectAnimation = new SinkAnimation(sensor, this.simulator); Renderable2D objectAnimation = new SensorAnimation(sensor, sensor.getLongitudinalPosition(), this.simulator, Color.YELLOW); this.animatedObjects.put(object, objectAnimation); } else if (object instanceof DestinationSensor) { DestinationSensor sensor = (DestinationSensor) object; // Renderable2D objectAnimation = new DestinationAnimation(sensor, this.simulator); Renderable2D objectAnimation = new SensorAnimation(sensor, sensor.getLongitudinalPosition(), this.simulator, Color.ORANGE); this.animatedObjects.put(object, objectAnimation); } else if (object instanceof SingleSensor) { SingleSensor sensor = (SingleSensor) object; Renderable2D objectAnimation = new SensorAnimation(sensor, sensor.getLongitudinalPosition(), this.simulator, Color.GREEN); this.animatedObjects.put(object, objectAnimation); } else if (object instanceof TrafficLightSensor) { TrafficLightSensor trafficLightSensor = (TrafficLightSensor) object; Renderable2D objectAnimation = new TrafficLightSensorAnimation(trafficLightSensor, trafficLightSensor.getSimulator()); this.animatedObjects.put(object, objectAnimation); } else if (object instanceof Conflict) { Conflict conflict = (Conflict) object; Renderable2D objectAnimation = new ConflictAnimation(conflict, this.simulator); this.animatedObjects.put(object, objectAnimation); } else if (object instanceof SpeedSign) { SpeedSign speedSign = (SpeedSign) object; Renderable2D objectAnimation = new SpeedSignAnimation(speedSign, this.simulator); this.animatedObjects.put(object, objectAnimation); } else if (object instanceof BusStop) { BusStop busStop = (BusStop) object; Renderable2D objectAnimation = new BusStopAnimation(busStop, this.simulator); this.animatedObjects.put(object, objectAnimation); } else if (object instanceof SimpleTrafficLight) { // No need to create an animation for this. } else { CategoryLogger.always().info("not creating animation for " + object); } } catch (RemoteException | NamingException exception) { CategoryLogger.always().error(exception, "Exception while drawing Object of class ObjectInterface."); } } /** * Draw the GTUGenerator. * @param gtuGenerator GtuGenerator; the GTUGenerator to draw */ protected void animateGTUGenerator(final GtuGenerator gtuGenerator) { // TODO: default animation of GTU generator } }