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
}
}