package nl.tudelft.simulation.dsol.animation.D2;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.image.ImageObserver;
import java.rmi.RemoteException;
import java.util.concurrent.atomic.AtomicInteger;
import javax.naming.NamingException;
import org.djutils.draw.bounds.Bounds2d;
import org.djutils.draw.point.Point2d;
import org.djutils.logger.CategoryLogger;
import nl.tudelft.simulation.dsol.animation.Locatable;
import nl.tudelft.simulation.dsol.simulators.AnimatorInterface;
import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
import nl.tudelft.simulation.language.d2.Shape2d;
import nl.tudelft.simulation.naming.context.util.ContextUtil;
/**
* The Renderable2D provides an easy accessible renderable object that can be drawn on an absolute or relative position, scaled,
* flipped, and rotated. For scaling, several options exist:
* - scale: whether to scale the drawing at all; e.g. for a legend, absolute coordinates might be used (scale = false);
* - scaleY: whether to scale differently in X and Y direction, e.g. for a map at higher latitudes (scaleY = true);
* - scaleObject: whether to scale the drawing larger or smaller than the scale factor of the extent (e.g., to draw an object on
* a map where the units of the object are in meters, while the map is in lat / lon degrees).
* The default values are: translate = true; scale = true; flip = false; rotate = true; scaleY = false; scaleObject = false.
*
* Copyright (c) 2002-2021 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 * @parampaint(graphics, observer)
method to do the actual work.
* @param graphics Graphics2D; the graphics object
* @param extent Bounds2d; the extent of the panel
* @param screenSize Dimension; the screen of the panel
* @param renderableScale RenderableScale; the scale to use (usually RenderableScaleDefault where X/Y ratio is 1)
* @param observer ImageObserver; the observer of the renderableInterface
*/
protected synchronized void paint(final Graphics2D graphics, final Bounds2d extent, final Dimension screenSize,
final RenderableScale renderableScale, final ImageObserver observer)
{
try
{
AffineTransform transform = graphics.getTransform();
Bounds2d rectangle = BoundsUtil.zIntersect(this.source.getLocation(), this.source.getBounds(), this.source.getZ());
if (rectangle == null || (!Shape2d.overlaps(extent, rectangle) && isTranslate()))
{
return;
}
// Let's transform
if (isTranslate())
{
Point2D screenCoordinates = renderableScale.getScreenCoordinates(this.source.getLocation(), extent, screenSize);
graphics.translate(screenCoordinates.getX(), screenCoordinates.getY());
}
if (isScale())
{
double objectScaleFactor = isScaleObject() ? renderableScale.getObjectScaleFactor() : 1.0;
if (isScaleY())
{
graphics.scale(objectScaleFactor / renderableScale.getXScale(extent, screenSize),
objectScaleFactor / renderableScale.getYScale(extent, screenSize));
}
else
{
graphics.scale(objectScaleFactor / renderableScale.getXScale(extent, screenSize), objectScaleFactor
* renderableScale.getYScaleRatio() / renderableScale.getYScale(extent, screenSize));
}
}
double angle = -this.source.getDirZ();
if (angle != 0.0)
{
if (isFlip() && angle < -Math.PI)
{
angle = angle + Math.PI;
}
if (isRotate())
{
graphics.rotate(angle);
}
}
// Now we paint
this.paint(graphics, observer);
// Let's untransform
graphics.setTransform(transform);
}
catch (Exception exception)
{
CategoryLogger.always().warn(exception, "paint");
}
}
/** {@inheritDoc} */
@Override
@SuppressWarnings("checkstyle:designforextension")
public boolean contains(final Point2d pointWorldCoordinates, final Bounds2d extent)
{
try
{
Bounds2d intersect = BoundsUtil.zIntersect(this.source.getLocation(), this.source.getBounds(), this.source.getZ());
if (intersect == null)
{
throw new IllegalStateException(
"empty intersect: location.z is not in bounds. This is probably due to a modeling error. "
+ "See the javadoc of the Locatable interface.");
}
return intersect.contains(pointWorldCoordinates);
}
catch (RemoteException exception)
{
CategoryLogger.always().warn(exception, "contains");
return false;
}
}
/** {@inheritDoc} */
@Override
@SuppressWarnings("checkstyle:designforextension")
public void destroy(final SimulatorInterface, ?, ?> simulator)
{
try
{
ContextUtil.lookupOrCreateSubContext(simulator.getReplication().getContext(), "animation/2D")
.unbind(Integer.toString(this.id));
}
catch (NamingException | RemoteException exception)
{
CategoryLogger.always().warn(exception);
}
}
/** {@inheritDoc} */
@Override
public int getId()
{
return this.id;
}
/** {@inheritDoc} */
@Override
@SuppressWarnings("checkstyle:designforextension")
public String toString()
{
return "Renderable2D [source=" + this.source + "]";
}
/**
* Draws an animation on a world coordinate around [x,y] = [0,0].
* @param graphics Graphics2D; the graphics object
* @param observer ImageObserver; the observer
*/
public abstract void paint(Graphics2D graphics, ImageObserver observer);
}