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 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 element) { Class locatableClass = element.getSource().getClass(); if (this.hiddenClasses.contains(locatableClass)) { return false; } else { boolean show = true; if (!this.shownClasses.contains(locatableClass)) { for (Class 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 element = (Renderable2DInterface) namingEvent.getNewBinding().getObject(); synchronized (this.elementList) { this.elements.add(element); this.dirty = true; } } /** {@inheritDoc} */ @Override public void objectRemoved(final NamingEvent namingEvent) { @SuppressWarnings("unchecked") Renderable2DInterface element = (Renderable2DInterface) 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 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 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 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 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; } }