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.
*
* Copyright (c) 2003-2020 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights reserved. See
* for project information www.simulation.tudelft.nl. The
* source code and binary code of this software is proprietary information of Delft University of Technology.
* @author Alexander Verbraeck
*/
public class LaneStructureAnimation extends Renderable2D
{
/** 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 dependables =
(Set) 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.");
}
}