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;
}
}