package nl.tudelft.simulation.dsol.animation.D2;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.media.j3d.BoundingBox;
import javax.naming.Binding;
import javax.naming.NamingEnumeration;
import javax.naming.event.EventContext;
import javax.naming.event.NamespaceChangeListener;
import javax.naming.event.NamingEvent;
import javax.naming.event.NamingExceptionEvent;
import javax.vecmath.Point3d;
import javax.vecmath.Point4i;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import nl.tudelft.simulation.dsol.animation.Locatable;
import nl.tudelft.simulation.dsol.animation.D2.mouse.InputListener;
import nl.tudelft.simulation.dsol.simulators.AnimatorInterface;
import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
import nl.tudelft.simulation.event.EventInterface;
import nl.tudelft.simulation.event.EventListenerInterface;
import nl.tudelft.simulation.language.d3.DirectedPoint;
import nl.tudelft.simulation.naming.context.ContextUtil;
/**
* The AnimationPanel to display animated (Locatable) objects. Added the possibility to witch layers on and off. By
* default all layers will be drawn, so no changes to existing software need to be made.
* (c) copyright 2002-2016 Delft University of Technology , the
* Netherlands.
* See for project information www.simulation.tudelft.nl
* License of use: Lesser General Public License (LGPL) , no
* warranty.
* @version $Revision: 1.2 $ $Date: 2010/08/10 11:37:49 $
* @author Peter Jacobs
*/
public class AnimationPanel extends GridPanel implements EventListenerInterface, NamespaceChangeListener
{
/** */
private static final long serialVersionUID = 1L;
/** the elements of this panel. */
private SortedSet> elements =
new TreeSet>(new Renderable2DComparator());
/** filter for types to be shown or not. */
private Map, Boolean> visibilityMap = new HashMap<>();
/** cache of the classes that are hidden. */
private Set> hiddenClasses = new HashSet<>();
/** cache of the classes that are shown. */
private Set> shownClasses = new HashSet<>();
/** the simulator. */
private SimulatorInterface, ?, ?> simulator;
/** the eventContext. */
private EventContext context = null;
/** a line that helps the user to see where he is dragging. */
private Point4i dragLine = new Point4i();
/** enable drag line. */
private boolean dragLineEnabled = false;
/** List of drawable objects. */
private List> elementList = new ArrayList<>();
/** dirty flag for the list. */
private boolean dirty = false;
/** the logger. */
private static Logger logger = LogManager.getLogger(AnimationPanel.class);
/**
* constructs a new AnimationPanel.
* @param extent the extent of the panel
* @param size the size of the panel.
* @param simulator the simulator of which we want to know the events for animation
*/
public AnimationPanel(final Rectangle2D extent, final Dimension size, final SimulatorInterface, ?, ?> simulator)
{
super(extent, size);
super.showGrid = true;
InputListener listener = new InputListener(this);
this.simulator = simulator;
this.addMouseListener(listener);
this.addMouseMotionListener(listener);
this.addMouseWheelListener(listener);
this.addKeyListener(listener);
try
{
simulator.addListener(this, AnimatorInterface.UPDATE_ANIMATION_EVENT);
simulator.addListener(this, SimulatorInterface.START_REPLICATION_EVENT);
}
catch (RemoteException exception)
{
exception.printStackTrace();
}
}
/** {@inheritDoc} */
@Override
public final void paintComponent(final Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
// draw the grid.
super.paintComponent(g2);
// update drawable elements when necessary
if (this.dirty)
{
synchronized (this.elementList)
{
this.elementList.clear();
this.elementList.addAll(this.elements);
this.dirty = false;
}
}
// draw the animation elements.
for (Renderable2DInterface extends Locatable> element : this.elementList)
{
if (isShowElement(element))
{
element.paint(g2, this.getExtent(), this.getSize(), this);
}
}
// draw drag line if enabled.
if (this.dragLineEnabled)
{
g.setColor(Color.BLACK);
g.drawLine(this.dragLine.w, this.dragLine.x, this.dragLine.y, this.dragLine.z);
this.dragLineEnabled = false;
}
}
/**
* Test whether the element needs to be shown on the screen or not.
* @param element the renderable element to test
* @return whether the element needs to be shown or not
*/
public boolean isShowElement(final Renderable2DInterface extends Locatable> element)
{
Class extends Locatable> locatableClass = element.getSource().getClass();
if (this.hiddenClasses.contains(locatableClass))
{
return false;
}
else
{
boolean show = true;
if (!this.shownClasses.contains(locatableClass))
{
for (Class extends Locatable> lc : this.visibilityMap.keySet())
{
if (lc.isAssignableFrom(locatableClass))
{
if (!this.visibilityMap.get(lc))
{
show = false;
}
}
}
// add to the right cache
if (show)
{
this.shownClasses.add(locatableClass);
}
else
{
this.hiddenClasses.add(locatableClass);
}
}
return show;
}
}
/** {@inheritDoc} */
@Override
public void notify(final EventInterface event) throws RemoteException
{
if (event.getSource() instanceof AnimatorInterface
&& event.getType().equals(AnimatorInterface.UPDATE_ANIMATION_EVENT) && this.isShowing())
{
if (this.getWidth() > 0 || this.getHeight() > 0)
{
this.repaint();
}
return;
}
if (event.getSource() instanceof AnimatorInterface
&& event.getType().equals(SimulatorInterface.START_REPLICATION_EVENT))
{
synchronized (this.elementList)
{
this.elements.clear();
try
{
if (this.context != null)
{
this.context.removeNamingListener(this);
}
this.context = (EventContext) ContextUtil.lookup(this.simulator.getReplication().getContext(),
"/animation/2D");
this.context.addNamingListener("", EventContext.SUBTREE_SCOPE, this);
NamingEnumeration list = this.context.listBindings("");
while (list.hasMore())
{
Binding binding = list.next();
this.objectAdded(new NamingEvent(this.context, -1, binding, binding, null));
}
this.repaint();
}
catch (Exception exception)
{
logger.warn("notify", exception);
}
}
}
}
/** {@inheritDoc} */
@Override
public void objectAdded(final NamingEvent namingEvent)
{
@SuppressWarnings("unchecked")
Renderable2DInterface extends Locatable> element =
(Renderable2DInterface extends Locatable>) namingEvent.getNewBinding().getObject();
synchronized (this.elementList)
{
this.elements.add(element);
this.dirty = true;
}
}
/** {@inheritDoc} */
@Override
public void objectRemoved(final NamingEvent namingEvent)
{
@SuppressWarnings("unchecked")
Renderable2DInterface extends Locatable> element =
(Renderable2DInterface extends Locatable>) namingEvent.getOldBinding().getObject();
synchronized (this.elementList)
{
this.elements.remove(element);
this.dirty = true;
}
}
/** {@inheritDoc} */
@Override
public void objectRenamed(final NamingEvent namingEvent)
{
this.objectRemoved(namingEvent);
this.objectAdded(namingEvent);
}
/** {@inheritDoc} */
@Override
public void namingExceptionThrown(final NamingExceptionEvent namingEvent)
{
logger.warn("namingExceptionThrown", namingEvent.getException());
}
/**
* Calculate the full extent based on the current positions of the objects.
* @return the full extent of the animation.
*/
public final synchronized Rectangle2D fullExtent()
{
double minX = Double.MAX_VALUE;
double maxX = -Double.MAX_VALUE;
double minY = Double.MAX_VALUE;
double maxY = -Double.MAX_VALUE;
Point3d p3dL = new Point3d();
Point3d p3dU = new Point3d();
try
{
for (Renderable2DInterface extends Locatable> renderable : this.elementList)
{
DirectedPoint l = renderable.getSource().getLocation();
BoundingBox b = new BoundingBox(renderable.getSource().getBounds());
b.getLower(p3dL);
b.getUpper(p3dU);
minX = Math.min(minX, l.x + Math.min(p3dL.x, p3dU.x));
minY = Math.min(minY, l.y + Math.min(p3dL.y, p3dU.y));
maxX = Math.max(maxX, l.x + Math.max(p3dL.x, p3dU.x));
maxY = Math.max(maxY, l.y + Math.max(p3dL.y, p3dU.y));
}
}
catch (Exception e)
{
// ignore
}
minX = minX - 0.05 * Math.abs(minX);
minY = minY - 0.05 * Math.abs(minY);
maxX = maxX + 0.05 * Math.abs(maxX);
maxY = maxY + 0.05 * Math.abs(maxY);
return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
}
/**
* resets the panel to its an extent that covers all displayed objects.
*/
public final synchronized void zoomAll()
{
this.extent = Renderable2DInterface.Util.computeVisibleExtent(fullExtent(), this.getSize());
this.repaint();
}
/**
* Set a class to be shown in the animation to true.
* @param locatableClass the class for which the animation has to be shown.
*/
public final void showClass(final Class extends Locatable> locatableClass)
{
this.visibilityMap.put(locatableClass, true);
this.shownClasses.clear();
this.hiddenClasses.clear();
this.repaint();
}
/**
* Set a class to be hidden in the animation to true.
* @param locatableClass the class for which the animation has to be hidden.
*/
public final void hideClass(final Class extends Locatable> locatableClass)
{
this.visibilityMap.put(locatableClass, false);
this.shownClasses.clear();
this.hiddenClasses.clear();
this.repaint();
}
/**
* Toggle a class to be displayed in the animation to its reverse value.
* @param locatableClass the class for which a visible animation has to be turned off or vice versa.
*/
public final void toggleClass(final Class extends Locatable> locatableClass)
{
if (!this.visibilityMap.containsKey(locatableClass))
{
showClass(locatableClass);
}
this.visibilityMap.put(locatableClass, !this.visibilityMap.get(locatableClass));
this.shownClasses.clear();
this.hiddenClasses.clear();
this.repaint();
}
/**
* @return the set of animation elements.
*/
public final SortedSet> getElements()
{
return this.elements;
}
/**
* @return returns the dragLine.
*/
public final Point4i getDragLine()
{
return this.dragLine;
}
/**
* @return returns the dragLineEnabled.
*/
public final boolean isDragLineEnabled()
{
return this.dragLineEnabled;
}
/**
* @param dragLineEnabled the dragLineEnabled to set.
*/
public final void setDragLineEnabled(final boolean dragLineEnabled)
{
this.dragLineEnabled = dragLineEnabled;
}
}