DefaultAnimationFactory.java

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.geometry.OTSGeometryException;
import org.opentrafficsim.core.gtu.GTU;
import org.opentrafficsim.core.gtu.GTUType;
import org.opentrafficsim.core.gtu.GtuGenerator;
import org.opentrafficsim.core.network.LateralDirectionality;
import org.opentrafficsim.core.network.Link;
import org.opentrafficsim.core.network.Network;
import org.opentrafficsim.core.network.Node;
import org.opentrafficsim.core.network.OTSNetwork;
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.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.trafficlight.TrafficLight;

import nl.tudelft.simulation.dsol.SimRuntimeException;
import nl.tudelft.simulation.dsol.animation.D2.Renderable2D;

/**
 * DefaultAnimationFactory.java. <br>
 * <br>
 * Copyright (c) 2003-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
 * for project information <a href="https://www.simulation.tudelft.nl/" target="_blank">www.simulation.tudelft.nl</a>. The
 * source code and binary code of this software is proprietary information of Delft University of Technology.
 * @author <a href="https://www.tudelft.nl/averbraeck" target="_blank">Alexander Verbraeck</a>
 */
public class DefaultAnimationFactory implements EventListenerInterface
{
    /** the simulator. */
    private final OTSSimulatorInterface simulator;

    /** GTU colorer. */
    private final GTUColorer gtuColorer;

    /** rendered gtus. */
    private Map<LaneBasedGTU, Renderable2D<LaneBasedGTU>> animatedGTUs = Collections.synchronizedMap(new LinkedHashMap<>());

    /** rendered static objects. */
    public Map<ObjectInterface, Renderable2D<?>> 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 OTSNetwork; 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 OTSNetwork 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, Network.ANIMATION_GTU_ADD_EVENT);
        network.addListener(this, Network.ANIMATION_GTU_REMOVE_EVENT);
        network.addListener(this, Network.ANIMATION_OBJECT_ADD_EVENT);
        network.addListener(this, Network.ANIMATION_OBJECT_REMOVE_EVENT);
        network.addListener(this, Network.ANIMATION_GENERATOR_ADD_EVENT);
        network.addListener(this, Network.ANIMATION_GENERATOR_REMOVE_EVENT);

        // model the current infrastructure
        try
        {
            if (animateNetwork)
            {
                for (Node node : network.getNodeMap().values())
                {
                    new NodeAnimation(node, simulator);
                }
                for (Link link : network.getLinkMap().values())
                {
                    new LinkAnimation(link, simulator, 0.5f);
                    if (link instanceof CrossSectionLink)
                    {
                        for (CrossSectionElement element : ((CrossSectionLink) link).getCrossSectionElementList())
                        {
                            if (element instanceof Lane)
                            {
                                new LaneAnimation((Lane) element, simulator, Color.GRAY.brighter());
                            }
                            else if (element instanceof Shoulder)
                            {
                                new ShoulderAnimation((Shoulder) element, 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, simulator, type);
                            }
                        }
                    }
                }

                for (TrafficLight tl : network.getObjectMap(TrafficLight.class).values())
                {
                    new TrafficLightAnimation(tl, simulator);
                }

            }

            for (GTU gtu : network.getGTUs())
            {
                Renderable2D<LaneBasedGTU> gtuAnimation =
                        new DefaultCarAnimation((LaneBasedGTU) gtu, simulator, this.gtuColorer);
                this.animatedGTUs.put((LaneBasedGTU) gtu, gtuAnimation);
            }

            for (ObjectInterface object : network.getObjectMap().values())
            {
                animateStaticObject(object);
            }
        }
        catch (RemoteException | NamingException | OTSGeometryException 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 OTSNetwork; 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 OTSNetwork 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 OTSNetwork; the network
     * @param gtuColorer GTUColorer; GTU colorer
     * @return the DefaultAnimationFactory
     * @throws OTSDrawingException on drawing error
     */
    public static DefaultAnimationFactory animateXmlNetwork(final OTSNetwork 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(Network.ANIMATION_GTU_ADD_EVENT))
            {
                // schedule the addition of the GTU to prevent it from not having an operational plan
                LaneBasedGTU gtu = (LaneBasedGTU) event.getContent();
                this.simulator.scheduleEventNow(this, this, "animateGTU", new Object[] {gtu});
            }
            else if (event.getType().equals(Network.ANIMATION_GTU_REMOVE_EVENT))
            {
                LaneBasedGTU gtu = (LaneBasedGTU) event.getContent();
                if (this.animatedGTUs.containsKey(gtu))
                {
                    this.animatedGTUs.get(gtu).destroy();
                    this.animatedGTUs.remove(gtu);
                }
            }
            else if (event.getType().equals(Network.ANIMATION_OBJECT_ADD_EVENT))
            {
                ObjectInterface object = (ObjectInterface) event.getContent();
                animateStaticObject(object);
            }
            else if (event.getType().equals(Network.ANIMATION_OBJECT_REMOVE_EVENT))
            {
                ObjectInterface object = (ObjectInterface) event.getContent();
                if (this.animatedObjects.containsKey(object))
                {
                    this.animatedObjects.get(object).destroy();
                    this.animatedObjects.remove(object);
                }
            }
            else if (event.getType().equals(Network.ANIMATION_GENERATOR_ADD_EVENT))
            {
                GtuGenerator gtuGenerator = (GtuGenerator) event.getContent();
                animateGTUGenerator(gtuGenerator);
            }
            else if (event.getType().equals(Network.ANIMATION_GENERATOR_REMOVE_EVENT))
            {
                // TODO: change the way generators are animated
            }
        }
        catch (NamingException | 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<LaneBasedGTU> 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<SinkSensor> objectAnimation = new SinkAnimation(sensor, this.simulator);
                Renderable2D<SingleSensor> 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<DestinationSensor> objectAnimation = new DestinationAnimation(sensor, this.simulator);
                Renderable2D<SingleSensor> 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<SingleSensor> objectAnimation =
                        new SensorAnimation(sensor, sensor.getLongitudinalPosition(), this.simulator, Color.GREEN);
                this.animatedObjects.put(object, objectAnimation);
            }
            else if (object instanceof Conflict)
            {
                Conflict conflict = (Conflict) object;
                Renderable2D<Conflict> objectAnimation = new ConflictAnimation(conflict, this.simulator);
                this.animatedObjects.put(object, objectAnimation);
            }
            else if (object instanceof SpeedSign)
            {
                SpeedSign speedSign = (SpeedSign) object;
                Renderable2D<SpeedSign> objectAnimation = new SpeedSignAnimation(speedSign, this.simulator);
                this.animatedObjects.put(object, objectAnimation);
            }
            else if (object instanceof BusStop)
            {
                BusStop busStop = (BusStop) object;
                Renderable2D<BusStop> objectAnimation = new BusStopAnimation(busStop, this.simulator);
                this.animatedObjects.put(object, objectAnimation);
            }
        }
        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
    }

}