TextAnimation.java

  1. package org.opentrafficsim.draw.core;

  2. import java.awt.Color;
  3. import java.awt.Dimension;
  4. import java.awt.Font;
  5. import java.awt.FontMetrics;
  6. import java.awt.Graphics2D;
  7. import java.awt.geom.Point2D;
  8. import java.awt.geom.Rectangle2D;
  9. import java.awt.image.ImageObserver;
  10. import java.io.Serializable;
  11. import java.rmi.RemoteException;

  12. import javax.media.j3d.Bounds;
  13. import javax.naming.NamingException;

  14. import nl.tudelft.simulation.dsol.animation.Locatable;
  15. import nl.tudelft.simulation.dsol.animation.D2.Renderable2D;
  16. import nl.tudelft.simulation.dsol.logger.SimLogger;
  17. import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
  18. import nl.tudelft.simulation.language.d3.BoundingBox;
  19. import nl.tudelft.simulation.language.d3.DirectedPoint;

  20. /**
  21.  * Display a text for another Locatable object.
  22.  * <p>
  23.  * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
  24.  * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
  25.  * </p>
  26.  * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
  27.  * initial version Dec 11, 2016 <br>
  28.  * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  29.  * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  30.  * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  31.  */
  32. public abstract class TextAnimation implements Locatable, Serializable
  33. {
  34.     /** */
  35.     private static final long serialVersionUID = 20161211L;

  36.     /** The object for which the text is displayed. */
  37.     private final Locatable source;

  38.     /** The text to display. */
  39.     private String text;

  40.     /** The horizontal movement of the text, in meters. */
  41.     private float dx;

  42.     /** The vertical movement of the text, in meters. */
  43.     private float dy;

  44.     /** Whether to center or not. */
  45.     private final TextAlignment textAlignment;

  46.     /** The color of the text. */
  47.     private Color color;

  48.     /** FontSize the size of the font; default = 2.0 (meters). */
  49.     private final float fontSize;

  50.     /** Minimum font size to trigger scaling. */
  51.     private final float minFontSize;

  52.     /** Maximum font size to trigger scaling. */
  53.     private final float maxFontSize;

  54.     /** The animation implementation. */
  55.     private final AnimationImpl animationImpl;

  56.     /** The font. */
  57.     private Font font;

  58.     /** Access to the current background color. */
  59.     private final ContrastToBackground background;

  60.     /** The font rectangle. */
  61.     private Rectangle2D fontRectangle = null;

  62.     /** Render dependent on font scale. */
  63.     private final ScaleDependentRendering scaleDependentRendering;

  64.     /**
  65.      * Construct a new TextAnimation.
  66.      * @param source Locatable; the object for which the text is displayed
  67.      * @param text String; the text to display
  68.      * @param dx float; the horizontal movement of the text, in meters
  69.      * @param dy float; the vertical movement of the text, in meters
  70.      * @param textAlignment TextAlignment; where to place the text
  71.      * @param color Color; the color of the text
  72.      * @param fontSize float; the size of the font; default = 2.0 (meters)
  73.      * @param minFontSize float; minimum font size resulting from scaling
  74.      * @param maxFontSize float; maximum font size resulting from scaling
  75.      * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
  76.      * @param background ContrastToBackground; allows querying the background color and adaptation of the actual color of the
  77.      *            text to ensure contrast
  78.      * @param scaleDependentRendering ScaleDependentRendering; suppress rendering when font scale is too small
  79.      * @throws NamingException when animation context cannot be created or retrieved
  80.      * @throws RemoteException when remote context cannot be found
  81.      */
  82.     @SuppressWarnings("checkstyle:parameternumber")
  83.     public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
  84.             final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
  85.             final float maxFontSize, final SimulatorInterface.TimeDoubleUnit simulator, final ContrastToBackground background,
  86.             final ScaleDependentRendering scaleDependentRendering) throws RemoteException, NamingException
  87.     {
  88.         this.source = source;
  89.         this.text = text;
  90.         this.dx = dx;
  91.         this.dy = dy;
  92.         this.textAlignment = textAlignment;
  93.         this.color = color;
  94.         this.fontSize = fontSize;
  95.         this.minFontSize = minFontSize;
  96.         this.maxFontSize = maxFontSize;
  97.         this.background = background;
  98.         this.scaleDependentRendering = scaleDependentRendering;

  99.         this.font = new Font("SansSerif", Font.PLAIN, 2);
  100.         if (this.fontSize != 2.0f)
  101.         {
  102.             this.font = this.font.deriveFont(this.fontSize);
  103.         }

  104.         this.animationImpl = new AnimationImpl(this, simulator);
  105.     }

  106.     /**
  107.      * Construct a new TextAnimation without contrast to background protection and no minimum font scale.
  108.      * @param source Locatable; the object for which the text is displayed
  109.      * @param text String; the text to display
  110.      * @param dx float; the horizontal movement of the text, in meters
  111.      * @param dy float; the vertical movement of the text, in meters
  112.      * @param textAlignment TextAlignment; where to place the text
  113.      * @param color Color; the color of the text
  114.      * @param fontSize float; the size of the font; default = 2.0 (meters)
  115.      * @param minFontSize float; minimum font size resulting from scaling
  116.      * @param maxFontSize float; maximum font size resulting from scaling
  117.      * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
  118.      * @param scaleDependentRendering ScaleDependentRendering; render text only when bigger than minimum scale
  119.      * @throws NamingException when animation context cannot be created or retrieved
  120.      * @throws RemoteException when remote context cannot be found
  121.      */
  122.     @SuppressWarnings("checkstyle:parameternumber")
  123.     public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
  124.             final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
  125.             final float maxFontSize, final SimulatorInterface.TimeDoubleUnit simulator,
  126.             final ScaleDependentRendering scaleDependentRendering) throws RemoteException, NamingException
  127.     {
  128.         this(source, text, dx, dy, textAlignment, color, fontSize, minFontSize, maxFontSize, simulator, null,
  129.                 scaleDependentRendering);
  130.     }

  131.     /**
  132.      * @param source Locatable; the object for which the text is displayed
  133.      * @param text String; the text to display
  134.      * @param dx float; the horizontal movement of the text, in meters
  135.      * @param dy float; the vertical movement of the text, in meters
  136.      * @param textAlignment TextAlignment; where to place the text
  137.      * @param color Color; the color of the text
  138.      * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
  139.      * @param scaleDependentRendering ScaleDependentRendering; render text only when bigger than minimum scale
  140.      * @throws NamingException when animation context cannot be created or retrieved
  141.      * @throws RemoteException when remote context cannot be found
  142.      */
  143.     public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
  144.             final TextAlignment textAlignment, final Color color, final SimulatorInterface.TimeDoubleUnit simulator,
  145.             final ScaleDependentRendering scaleDependentRendering) throws RemoteException, NamingException
  146.     {
  147.         this(source, text, dx, dy, textAlignment, color, 2.0f, 12.0f, 50f, simulator, scaleDependentRendering);
  148.     }

  149.     /** {@inheritDoc} */
  150.     @Override
  151.     @SuppressWarnings("checkstyle:designforextension")
  152.     public DirectedPoint getLocation() throws RemoteException
  153.     {
  154.         // draw always on top.
  155.         DirectedPoint p = this.source.getLocation();
  156.         return new DirectedPoint(p.x, p.y, Double.MAX_VALUE, 0.0, 0.0, p.getRotZ());
  157.     }

  158.     /** {@inheritDoc} */
  159.     @Override
  160.     public final Bounds getBounds() throws RemoteException
  161.     {
  162.         return new BoundingBox(0.0, 0.0, 0.0);
  163.     }

  164.     /**
  165.      * paint() method so it can be overridden or extended.
  166.      * @param graphics Graphics2D; the graphics object
  167.      * @param observer ImageObserver; the observer
  168.      * @throws RemoteException on network exception
  169.      */
  170.     @SuppressWarnings("checkstyle:designforextension")
  171.     public void paint(final Graphics2D graphics, final ImageObserver observer) throws RemoteException
  172.     {
  173.         double scale = Math.sqrt(graphics.getTransform().getDeterminant());
  174.         Rectangle2D scaledFontRectangle;
  175.         synchronized (this.font)
  176.         {
  177.             if (!this.scaleDependentRendering.isRendered(scale))
  178.             {
  179.                 return;
  180.             }
  181.             if (scale < this.minFontSize / this.fontSize)
  182.             {
  183.                 graphics.setFont(this.font.deriveFont((float) (this.minFontSize / scale)));
  184.                 FontMetrics fm = graphics.getFontMetrics();
  185.                 scaledFontRectangle = fm.getStringBounds(this.text, graphics);
  186.             }
  187.             else if (scale > this.maxFontSize / this.fontSize)
  188.             {
  189.                 graphics.setFont(this.font.deriveFont((float) (this.maxFontSize / scale)));
  190.                 FontMetrics fm = graphics.getFontMetrics();
  191.                 scaledFontRectangle = fm.getStringBounds(this.text, graphics);
  192.             }
  193.             else
  194.             {
  195.                 graphics.setFont(this.font);
  196.                 if (this.fontRectangle == null)
  197.                 {
  198.                     FontMetrics fm = graphics.getFontMetrics();
  199.                     this.fontRectangle = fm.getStringBounds(this.text, graphics);
  200.                 }
  201.                 scaledFontRectangle = this.fontRectangle;
  202.             }
  203.             Color useColor = this.color;
  204.             if (null != this.background && useColor.equals(this.background.getBackgroundColor()))
  205.             {
  206.                 // Construct an alternative color
  207.                 if (Color.BLACK.equals(useColor))
  208.                 {
  209.                     useColor = Color.WHITE;
  210.                 }
  211.                 else
  212.                 {
  213.                     useColor = Color.BLACK;
  214.                 }
  215.             }
  216.             graphics.setColor(useColor);
  217.             float dxText =
  218.                     this.textAlignment.equals(TextAlignment.LEFT) ? 0.0f : this.textAlignment.equals(TextAlignment.CENTER)
  219.                             ? (float) -scaledFontRectangle.getWidth() / 2.0f : (float) -scaledFontRectangle.getWidth();
  220.             graphics.drawString(this.text, dxText + this.dx, this.fontSize / 2.0f - this.dy);
  221.         }
  222.     }

  223.     /**
  224.      * Destroy the text animation.
  225.      */
  226.     public final void destroy()
  227.     {
  228.         try
  229.         {
  230.             this.animationImpl.destroy();
  231.         }
  232.         catch (NamingException exception)
  233.         {
  234.             SimLogger.always().warn(exception, "Tried to destroy Text for GTU animation of GTU {}", this.source.toString());
  235.         }
  236.     }

  237.     /**
  238.      * Clone the TextAnimation and return a copy for the new source on the new simulator.
  239.      * @param newSource Locatable; the new source to link to the text animation
  240.      * @param newSimulator SimulatorInterface.TimeDoubleUnit; the new simulator to register the animation on
  241.      * @return TextAnimation; a copy of this TextAnimation
  242.      * @throws RemoteException when remote animation cannot be reached
  243.      * @throws NamingException when animation name cannot be found or bound in the Context
  244.      */
  245.     public abstract TextAnimation clone(Locatable newSource, SimulatorInterface.TimeDoubleUnit newSimulator)
  246.             throws RemoteException, NamingException;

  247.     /**
  248.      * Retrieve the source.
  249.      * @return Locatable; the source
  250.      */
  251.     protected final Locatable getSource()
  252.     {
  253.         return this.source;
  254.     }

  255.     /**
  256.      * Retrieve dx.
  257.      * @return float; the value of dx
  258.      */
  259.     protected final float getDx()
  260.     {
  261.         return this.dx;
  262.     }

  263.     /**
  264.      * Retrieve dy.
  265.      * @return float; the value of dy
  266.      */
  267.     protected final float getDy()
  268.     {
  269.         return this.dy;
  270.     }

  271.     /**
  272.      * Sets a new offset.
  273.      * @param x float; dx
  274.      * @param y float; dy
  275.      */
  276.     protected final void setXY(final float x, final float y)
  277.     {
  278.         this.dx = x;
  279.         this.dy = y;
  280.     }

  281.     /**
  282.      * Retrieve the text alignment.
  283.      * @return TextAlignment; the text alignment
  284.      */
  285.     protected final TextAlignment getTextAlignment()
  286.     {
  287.         return this.textAlignment;
  288.     }

  289.     /**
  290.      * Retrieve the font size.
  291.      * @return float; the font size
  292.      */
  293.     protected final float getFontSize()
  294.     {
  295.         return this.fontSize;
  296.     }

  297.     /**
  298.      * Retrieve the font.
  299.      * @return Font; the font
  300.      */
  301.     protected final Font getFont()
  302.     {
  303.         return this.font;
  304.     }

  305.     /**
  306.      * Retrieve the current text.
  307.      * @return String; the current text
  308.      */
  309.     protected final String getText()
  310.     {
  311.         return this.text;
  312.     }

  313.     /**
  314.      * Update the text.
  315.      * @param text String; the new text
  316.      */
  317.     protected final void setText(final String text)
  318.     {
  319.         this.text = text;
  320.         synchronized (this.font)
  321.         {
  322.             this.fontRectangle = null;
  323.         }
  324.     }

  325.     /**
  326.      * Retrieve the current color.
  327.      * @return Color; the current color
  328.      */
  329.     protected final Color getColor()
  330.     {
  331.         return this.color;
  332.     }

  333.     /**
  334.      * Update the color.
  335.      * @param color Color; the new color
  336.      */
  337.     protected final void setColor(final Color color)
  338.     {
  339.         this.color = color;
  340.     }

  341.     /**
  342.      * Retrieve the current flip status.
  343.      * @return boolean; the current flip status
  344.      */
  345.     public final boolean isFlip()
  346.     {
  347.         return this.animationImpl.isFlip();
  348.     }

  349.     /**
  350.      * Update the flip status.
  351.      * @param flip boolean; the new flip status
  352.      */
  353.     public final void setFlip(final boolean flip)
  354.     {
  355.         this.animationImpl.setFlip(flip);
  356.     }

  357.     /**
  358.      * Retrieve the current rotation status.
  359.      * @return boolean; the current rotation status
  360.      */
  361.     public final boolean isRotate()
  362.     {
  363.         return this.animationImpl.isRotate();
  364.     }

  365.     /**
  366.      * Update the rotation status.
  367.      * @param rotate boolean; the new rotation status
  368.      */
  369.     public final void setRotate(final boolean rotate)
  370.     {
  371.         this.animationImpl.setRotate(rotate);

  372.     }

  373.     /**
  374.      * Retrieve the current scale status.
  375.      * @return boolean; the current scale status
  376.      */
  377.     public final boolean isScale()
  378.     {
  379.         return this.animationImpl.isScale();
  380.     }

  381.     /**
  382.      * Update the scale status.
  383.      * @param scale boolean; the new scale status
  384.      */
  385.     public final void setScale(final boolean scale)
  386.     {
  387.         this.animationImpl.setScale(scale);
  388.     }

  389.     /**
  390.      * Retrieve the current translate status.
  391.      * @return boolean; the current translate status
  392.      */
  393.     public final boolean isTranslate()
  394.     {
  395.         return this.animationImpl.isTranslate();
  396.     }

  397.     /**
  398.      * Update the translate status.
  399.      * @param translate boolean; the new translate status
  400.      */
  401.     public final void setTranslate(final boolean translate)
  402.     {
  403.         this.animationImpl.setTranslate(translate);
  404.     }

  405.     /**
  406.      * The implementation of the text animation. Cloning will be taken care of by the overarching TextAnimation-derived class.
  407.      * <p>
  408.      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
  409.      * <br>
  410.      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
  411.      * </p>
  412.      * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
  413.      * initial version Dec 11, 2016 <br>
  414.      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
  415.      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
  416.      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
  417.      */
  418.     private static class AnimationImpl extends Renderable2D<Locatable> implements Serializable
  419.     {
  420.         /** */
  421.         private static final long serialVersionUID = 20170400L;

  422.         /**
  423.          * Construct a new AnimationImpl.
  424.          * @param source Locatable; the source
  425.          * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
  426.          * @throws NamingException when animation context cannot be created or retrieved
  427.          * @throws RemoteException when remote context cannot be found
  428.          */
  429.         AnimationImpl(final Locatable source, final SimulatorInterface.TimeDoubleUnit simulator)
  430.                 throws NamingException, RemoteException
  431.         {
  432.             super(source, simulator);
  433.         }

  434.         /** {@inheritDoc} */
  435.         @Override
  436.         public final void paint(final Graphics2D graphics, final ImageObserver observer) throws RemoteException
  437.         {
  438.             TextAnimation ta = ((TextAnimation) getSource());
  439.             ta.paint(graphics, observer);
  440.         }

  441.         /** {@inheritDoc} */
  442.         @Override
  443.         public boolean contains(final Point2D pointWorldCoordinates, final Rectangle2D extent, final Dimension screen)
  444.         {
  445.             return false;
  446.         }

  447.         /** {@inheritDoc} */
  448.         @Override
  449.         public final String toString()
  450.         {
  451.             return "TextAnimation.AnimationImpl []";
  452.         }

  453.     }

  454.     /**
  455.      * Retrieve the scale dependent rendering qualifier (used in cloning).
  456.      * @return ScaleDependentRendering; the rendering qualifier of this TextAnimation
  457.      */
  458.     protected ScaleDependentRendering getScaleDependentRendering()
  459.     {
  460.         return this.scaleDependentRendering;
  461.     }

  462.     /**
  463.      * Interface to obtain the color of the background.
  464.      */
  465.     public interface ContrastToBackground
  466.     {
  467.         /**
  468.          * Retrieve the color of the background.
  469.          * @return Color; the (current) color of the background
  470.          */
  471.         Color getBackgroundColor();
  472.     }

  473.     /**
  474.      * Determine if a Feature object should be rendered.
  475.      */
  476.     public interface ScaleDependentRendering
  477.     {
  478.         /**
  479.          * Determine if a Text should be rendered, depending on the scale.
  480.          * @param scale double; the current font scale
  481.          * @return boolean; true if the text should be rendered at the scale; false if the text should not be rendered at the
  482.          *         scale
  483.          */
  484.         boolean isRendered(double scale);
  485.     }

  486.     /** Always render the Text. */
  487.     public static final ScaleDependentRendering RENDERALWAYS = new ScaleDependentRendering()
  488.     {

  489.         @Override
  490.         public boolean isRendered(final double scale)
  491.         {
  492.             return true;
  493.         }
  494.     };

  495.     /** Don't render texts when smaller than 1. */
  496.     public static final ScaleDependentRendering RENDERWHEN1 = new ScaleDependentRendering()
  497.     {

  498.         @Override
  499.         public boolean isRendered(final double scale)
  500.         {
  501.             return scale >= 1.0;
  502.         }
  503.     };

  504.     /** Don't render texts when smaller than 2. */
  505.     public static final ScaleDependentRendering RENDERWHEN10 = new ScaleDependentRendering()
  506.     {

  507.         @Override
  508.         public boolean isRendered(final double scale)
  509.         {
  510.             return scale >= 0.1;
  511.         }
  512.     };

  513.     /** Don't render texts when smaller than 2. */
  514.     public static final ScaleDependentRendering RENDERWHEN100 = new ScaleDependentRendering()
  515.     {

  516.         @Override
  517.         public boolean isRendered(final double scale)
  518.         {
  519.             return scale >= 0.01;
  520.         }
  521.     };

  522. }