package nl.tudelft.simulation.dsol.animation.D2; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.ImageObserver; import java.rmi.RemoteException; import javax.naming.Context; import javax.naming.NamingException; import nl.tudelft.simulation.dsol.animation.Locatable; import nl.tudelft.simulation.dsol.logger.SimLogger; import nl.tudelft.simulation.dsol.simulators.AnimatorInterface; import nl.tudelft.simulation.dsol.simulators.SimulatorInterface; import nl.tudelft.simulation.language.d2.Shape; import nl.tudelft.simulation.language.d3.BoundsUtil; import nl.tudelft.simulation.language.d3.DirectedPoint; import nl.tudelft.simulation.naming.context.ContextUtil; /** * The Renderable2D provides an easy accessible renderable object. *

* Copyright (c) 2002-2019 Delft University of Technology, Jaffalaan 5, 2628 BX Delft, the Netherlands. All rights * reserved. See for project information * https://simulation.tudelft.nl. The DSOL project is distributed under a three-clause BSD-style license, which can * be found at * https://simulation.tudelft.nl/dsol/3.0/license.html. *

* @author Peter Jacobs * @param the Locatable class of the source that indicates the location of the Renderable on the screen */ public abstract class Renderable2D implements Renderable2DInterface { /** * Storage of the boolean flags, to prevent each flag from taking 32 bits... The initial value is binary 1011 = 0B: * rotate = true, flip = false, scale = true, translate = true. */ private byte flags = 0x0B; /** whether to rotate the renderable. Flag is 1000 */ private static final byte ROTATE_FLAG = 0x08; /** whether to flip the renderable after rotating 180 degrees. Flag is 0100 */ private static final byte FLIP_FLAG = 0x04; /** whether to scale the renderable when zooming in or out. Flag is 0010 */ private static final byte SCALE_FLAG = 0x02; /** whether to translate the renderable when panning. Flag is 0001 */ private static final byte TRANSLATE_FLAG = 0x01; /** the source of the renderable. TODO Make weak reference and destroy renderable when source ceases to exist. */ @SuppressWarnings("checkstyle:visibilitymodifier") private final T source; /** the naming Context in which to store the Renderable with a pointer to the Locatable object. */ private final Context context; /** the unique id. */ private final long id; /** the generator for the unique id. TODO Static for now, should be replaced by a context dependent one. */ private static long lastGeneratedId = 0; /** * constructs a new Renderable2D. * @param source T; the source * @param simulator SimulatorInterface<?,?,?>; the simulator * @throws NamingException when animation context cannot be created or retrieved * @throws RemoteException when remote context cannot be found */ public Renderable2D(final T source, final SimulatorInterface simulator) throws NamingException, RemoteException { this.id = ++lastGeneratedId; this.source = source; this.context = ContextUtil.lookup(simulator.getReplication().getContext(), "/animation/2D"); if (!(simulator instanceof AnimatorInterface)) { // We are currently running without animation return; } this.bind2Context(simulator); } /** * binds a renderable2D to the context. The reason for specifying this in an independent method instead of adding * the code in the constructor is related to the RFE submitted by van Houten that in specific distributed context, * such binding must be overwritten. * @param simulator SimulatorInterface<?,?,?>; the simulator used for binding the object. * @throws NamingException when animation context cannot be created or retrieved * @throws RemoteException when remote context cannot be found */ protected final void bind2Context(final SimulatorInterface simulator) throws NamingException, RemoteException { this.context.bind(Long.toString(this.id), this); } /** * @return Returns the flip. */ public final boolean isFlip() { return (this.flags & FLIP_FLAG) != 0; } /** * @param flip boolean; The flip to set. */ @SuppressWarnings("checkstyle:needbraces") public final void setFlip(final boolean flip) { if (flip) this.flags |= FLIP_FLAG; else this.flags &= (~FLIP_FLAG); } /** * @return Returns the rotate. */ public final boolean isRotate() { return (this.flags & ROTATE_FLAG) != 0; } /** * @param rotate boolean; The rotate to set. */ @SuppressWarnings("checkstyle:needbraces") public final void setRotate(final boolean rotate) { if (rotate) this.flags |= ROTATE_FLAG; else this.flags &= (~ROTATE_FLAG); } /** * @return Returns the scale. */ public final boolean isScale() { return (this.flags & SCALE_FLAG) != 0; } /** * @param scale boolean; The scale to set. */ @SuppressWarnings("checkstyle:needbraces") public final void setScale(final boolean scale) { if (scale) this.flags |= SCALE_FLAG; else this.flags &= (~SCALE_FLAG); } /** * @return Returns the translate. */ public final boolean isTranslate() { return (this.flags & TRANSLATE_FLAG) != 0; } /** * @param translate boolean; The translate to set. */ @SuppressWarnings("checkstyle:needbraces") public final void setTranslate(final boolean translate) { if (translate) this.flags |= TRANSLATE_FLAG; else this.flags &= (~TRANSLATE_FLAG); } /** {@inheritDoc} */ @Override public T getSource() { return this.source; } /** {@inheritDoc} */ @Override public synchronized void paint(final Graphics2D graphics, final Rectangle2D extent, final Dimension screenSize, final ImageObserver observer) { try { DirectedPoint location = this.source.getLocation(); Rectangle2D rectangle = BoundsUtil.getIntersect(this.source.getLocation(), this.source.getBounds(), location.z); if (rectangle == null || (!Shape.overlaps(extent, rectangle) && isTranslate())) { return; } Point2D screenCoordinates = Renderable2DInterface.Util .getScreenCoordinates(this.source.getLocation().to2D(), extent, screenSize); // Let's transform if (isTranslate()) { graphics.translate(screenCoordinates.getX(), screenCoordinates.getY()); } double scaleFactor = Renderable2DInterface.Util.getScale(extent, screenSize); if (isScale()) { graphics.scale(1.0 / scaleFactor, 1.0 / scaleFactor); } double angle = -location.getRotZ(); if (isFlip() && angle > Math.PI) { angle = angle - Math.PI; } if (isRotate() && angle != 0.0) { graphics.rotate(angle); } // Now we paint this.paint(graphics, observer); // Let's untransform if (isRotate() && angle != 0.0) { graphics.rotate(-angle); } if (isScale()) { graphics.scale(scaleFactor, scaleFactor); } if (isTranslate()) { graphics.translate(-screenCoordinates.getX(), -screenCoordinates.getY()); } } catch (Exception exception) { SimLogger.always().warn(exception, "paint"); } } /** {@inheritDoc} */ @Override @SuppressWarnings("checkstyle:designforextension") public boolean contains(final Point2D pointWorldCoordinates, final Rectangle2D extent, final Dimension screenSize) { try { Rectangle2D intersect = BoundsUtil.getIntersect(this.source.getLocation(), this.source.getBounds(), this.source.getLocation().z); if (intersect == null) { throw new NullPointerException( "empty intersect!: location.z is not in bounds. This is probably due to a modeling error. " + "See the javadoc of LocatableInterface."); } return intersect.contains(pointWorldCoordinates); } catch (RemoteException exception) { SimLogger.always().warn(exception, "contains"); return false; } } /** {@inheritDoc} */ @Override @SuppressWarnings("checkstyle:designforextension") public void destroy() throws NamingException { this.context.unbind(Long.toString(this.id)); } /** {@inheritDoc} */ @Override @SuppressWarnings("checkstyle:designforextension") public String toString() { return "Renderable2D [id=" + this.id + ", source=" + this.source + "]"; } /** * draws an animation on a world coordinates around [x,y=0,0]. * @param graphics Graphics2D; the graphics object * @param observer ImageObserver; the observer * @throws RemoteException on network exception */ public abstract void paint(final Graphics2D graphics, final ImageObserver observer) throws RemoteException; /** {@inheritDoc} */ @Override @SuppressWarnings("checkstyle:designforextension") public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (this.id ^ (this.id >>> 32)); return result; } /** {@inheritDoc} */ @Override @SuppressWarnings({"checkstyle:designforextension", "checkstyle:needbraces"}) public boolean equals(final Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Renderable2D other = (Renderable2D) obj; if (this.id != other.id) return false; return true; } }