LaneStructureAnimation.java

package org.opentrafficsim.draw.lane;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.awt.image.ImageObserver;
import java.lang.reflect.Field;
import java.rmi.RemoteException;
import java.util.LinkedHashSet;
import java.util.Set;

import javax.naming.NamingException;

import org.djutils.exceptions.Try;
import org.opentrafficsim.core.geometry.OTSLine3D;
import org.opentrafficsim.core.geometry.OTSPoint3D;
import org.opentrafficsim.core.gtu.GTU;
import org.opentrafficsim.road.gtu.lane.perception.LaneStructureRecord;
import org.opentrafficsim.road.gtu.lane.perception.RollingLaneStructure;
import org.opentrafficsim.road.gtu.lane.perception.RollingLaneStructureRecord;
import org.opentrafficsim.road.gtu.lane.perception.RollingLaneStructureRecord.RecordLink;

import nl.tudelft.simulation.dsol.animation.D2.Renderable2D;
import nl.tudelft.simulation.language.d3.DirectedPoint;

/**
 * LaneStructureAnimation.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 LaneStructureAnimation extends Renderable2D<LaneStructureLocatable>
{
    /** Destroyed. */
    private boolean isDestroyed = false;

    /**
     * @param source LaneStructureLocatable; dummy locatable
     * @throws NamingException on naming exception
     * @throws RemoteException on remote exception
     */
    LaneStructureAnimation(final LaneStructureLocatable source) throws NamingException, RemoteException
    {
        super(source, source.getGtu().getSimulator());
        this.setFlip(false);
        this.setRotate(false);
    }

    /** {@inheritDoc} */
    @Override
    public void paint(final Graphics2D graphics, final ImageObserver observer) throws RemoteException
    {
        if (!this.isDestroyed)
        {
            if (getSource().getGtu().isDestroyed())
            {
                this.isDestroyed = true;
                Try.execute(() -> destroy(), "Exception during deletion of LaneStructureAnimation.");
                return;
            }
            else
            {
                LaneStructureRecord rt = getSource().getRollingLaneStructure().getRootRecord();
                if (rt != null)
                {
                    paintRecord(rt, graphics);
                }
            }
        }
    }

    /**
     * @param lsr LaneStructureRecord; record
     * @param graphics Graphics2D; graphics
     */
    @SuppressWarnings({"unchecked"})
    private void paintRecord(final LaneStructureRecord lsr, final Graphics2D graphics)
    {
        // line
        DirectedPoint loc = Try.assign(() -> getSource().getLocation(), "Unable to return location.");
        graphics.setStroke(new BasicStroke(
                getSource().getRollingLaneStructure().animationAccess.getCrossSectionRecords().containsValue(lsr) ? 1.0f
                        : 0.5f));
        graphics.setColor(
                getSource().getRollingLaneStructure().animationAccess.getCrossSectionRecords().containsValue(lsr) ? Color.PINK
                        : getSource().getRollingLaneStructure().animationAccess.getUpstreamEdge().contains(lsr) ? Color.MAGENTA
                                : getSource().getRollingLaneStructure().animationAccess.getDownstreamEdge().contains(lsr)
                                        ? Color.GREEN : Color.CYAN);
        OTSLine3D line = Try.assign(() -> lsr.getLane().getCenterLine().extractFractional(0.1, 0.9),
                "Exception while painting LaneStructures");
        Path2D.Double path = new Path2D.Double();
        boolean start = true;
        for (OTSPoint3D point : line.getPoints())
        {
            if (start)
            {
                path.moveTo(point.x - loc.x, -(point.y - loc.y));
                start = false;
            }
            else
            {
                path.lineTo(point.x - loc.x, -(point.y - loc.y));
            }
        }
        graphics.draw(path);
        // connection
        Field sourceField = Try.assign(() -> RollingLaneStructureRecord.class.getDeclaredField("source"),
                "Exception while painting LaneStructure");
        sourceField.setAccessible(true);
        LaneStructureRecord src =
                Try.assign(() -> (LaneStructureRecord) sourceField.get(lsr), "Exception while painting LaneStructure");
        if (src != null)
        {
            Field sourceLinkField = Try.assign(() -> RollingLaneStructureRecord.class.getDeclaredField("sourceLink"),
                    "Exception while painting LaneStructure");
            sourceLinkField.setAccessible(true);
            RecordLink link = (RecordLink) Try.assign(() -> sourceLinkField.get(lsr), "Exception while painting LaneStructure");
            float f1 = link.equals(RecordLink.DOWN) ? 0.9f : link.equals(RecordLink.UP) ? 0.1f : 0.5f;
            float f2 = link.equals(RecordLink.DOWN) ? 0.0f : link.equals(RecordLink.UP) ? 1.0f : 0.5f;
            f1 = src.getDirection().isPlus() ? f1 : 1.0f - f1;
            f2 = lsr.getDirection().isPlus() ? f2 : 1.0f - f2;
            float f3 = f1;
            float f4 = f2;
            DirectedPoint p1 = Try.assign(() -> src.getLane().getCenterLine().getLocationFraction(f3),
                    "Exception while painting LaneStructure");
            DirectedPoint p2 = Try.assign(() -> line.getLocationFraction(f4), "Exception while painting LaneStructure");
            path = new Path2D.Double();
            path.moveTo(p1.x - loc.x, -(p1.y - loc.y));
            path.lineTo(p2.x - loc.x, -(p2.y - loc.y));
            graphics.setStroke(
                    new BasicStroke(0.15f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[] {.3f, 1.2f}, 0f));
            graphics.setColor(Color.DARK_GRAY);
            graphics.draw(path);
        }
        // left/right
        paintLateralConnection(lsr, lsr.getLeft(), Color.RED, graphics, loc);
        paintLateralConnection(lsr, lsr.getRight(), Color.BLUE, graphics, loc);
        // recursion to depending records
        Field dependentField = Try.assign(() -> RollingLaneStructureRecord.class.getDeclaredField("dependentRecords"),
                "Exception while painting LaneStructure");
        dependentField.setAccessible(true);
        Set<LaneStructureRecord> dependables =
                (Set<LaneStructureRecord>) Try.assign(() -> dependentField.get(lsr), "Exception while painting LaneStructure");
        if (dependables != null)
        {
            for (LaneStructureRecord dependable : new LinkedHashSet<>(dependables)) // concurrency
            {
                paintRecord(dependable, graphics);
            }
        }
    }

    /**
     * Paint the connection to a lateral record.
     * @param main LaneStructureRecord; main record
     * @param adj LaneStructureRecord; adjacent record, can be {@code null}
     * @param color Color; color
     * @param graphics Graphics2D; graphics
     * @param loc DirectedPoint; location
     */
    private void paintLateralConnection(final LaneStructureRecord main, final LaneStructureRecord adj, final Color color,
            final Graphics2D graphics, final DirectedPoint loc)
    {
        if (adj == null)
        {
            return;
        }
        float f1 = main.getDirection().isPlus() ? 0.45f : 0.55f;
        float f2 = adj.getDirection().isPlus() ? 0.55f : 0.45f;
        DirectedPoint p1 = Try.assign(() -> main.getLane().getCenterLine().getLocationFraction(f1),
                "Exception while painting LaneStructure");
        DirectedPoint p2 = Try.assign(() -> adj.getLane().getCenterLine().getLocationFraction(f2),
                "Exception while painting LaneStructure");
        Path2D.Double path = new Path2D.Double();
        path.moveTo(p1.x - loc.x, -(p1.y - loc.y));
        path.lineTo(p2.x - loc.x, -(p2.y - loc.y));
        graphics.setStroke(
                new BasicStroke(0.05f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER, 10f, new float[] {.15f, 0.6f}, 0f));
        graphics.setColor(color);
        graphics.draw(path);
    }

    /**
     * Enables visualization of this lane structure. This is purely for debugging purposes.
     * @param rollingLaneStructure RollingLaneStructure; the lane structure to visualize
     * @param gtu GTU; GTU to animate the LaneStructure of
     */
    public static final void visualize(final RollingLaneStructure rollingLaneStructure, final GTU gtu)
    {
        Try.execute(() -> new LaneStructureAnimation(new LaneStructureLocatable(rollingLaneStructure, gtu)),
                "Could not create animation.");
    }

}