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.LocatableInterface; 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.logger.Logger; import nl.tudelft.simulation.naming.context.ContextUtil; /** * The Renderable2D provides an easy accessible renderable object. *

* (c) copyright 2002-2005 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.1 $ $Date: 2010/08/10 11:37:20 $ * @author Peter Jacobs */ 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") protected final LocatableInterface source; /** the context for (un)binding. */ @SuppressWarnings("checkstyle:visibilitymodifier") protected Context context; /** * constructs a new Renderable2D. * @param source the source * @param simulator the simulator * @throws NamingException when animation context cannot be created or retrieved * @throws RemoteException when remote context cannot be found */ public Renderable2D(final LocatableInterface source, final SimulatorInterface simulator) throws NamingException, RemoteException { this.source = source; 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 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 = ContextUtil.lookup(simulator.getReplication().getContext(), "/animation/2D"); ContextUtil.bind(this.context, this); } /** * @return Returns the flip. */ public final boolean isFlip() { return (this.flags & FLIP_FLAG) != 0; } /** * @param flip 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 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 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 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 final LocatableInterface getSource() { return this.source; } /** {@inheritDoc} */ @Override public final synchronized void paint(final Graphics2D graphics, final Rectangle2D extent, final Dimension screen, final ImageObserver observer) { try { DirectedPoint location = this.source.getLocation(); Rectangle2D rectangle = BoundsUtil.getIntersect(this.source.getLocation(), this.source.getBounds(), location.z); if (!Shape.overlaps(extent, rectangle) && isTranslate()) { return; } Point2D screenCoordinates = Renderable2DInterface.Util.getScreenCoordinates(this.source.getLocation().to2D(), extent, screen); // Let's transform if (isTranslate()) { graphics.translate(screenCoordinates.getX(), screenCoordinates.getY()); } double scaleFactor = Renderable2DInterface.Util.getScale(extent, screen); 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) { Logger.warning(this, "paint", exception); } } /** {@inheritDoc} */ @Override public final boolean contains(final Point2D pointWorldCoordinates, final Rectangle2D extent, final Dimension screen) { 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 off LocatableInterface."); } return intersect.contains(pointWorldCoordinates); } catch (RemoteException exception) { Logger.warning(this, "contains", exception); return false; } } /** {@inheritDoc} */ @Override public final void destroy() throws NamingException { ContextUtil.unbind(this.context, this); } /** {@inheritDoc} */ @Override @SuppressWarnings("checkstyle:designforextension") public String toString() { return "" + hashCode(); } /** * draws an animation on a world coordinates around [x,y=0,0]. * @param graphics the graphics object * @param observer 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 + ((this.source == null) ? 0 : this.source.hashCode()); 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.source == null) { if (other.source != null) return false; } else if (!this.source.equals(other.source)) return false; return true; } }