View Javadoc
1   package org.opentrafficsim.draw.core;
2   
3   import java.awt.Color;
4   import java.awt.Dimension;
5   import java.awt.Font;
6   import java.awt.FontMetrics;
7   import java.awt.Graphics2D;
8   import java.awt.geom.Point2D;
9   import java.awt.geom.Rectangle2D;
10  import java.awt.image.ImageObserver;
11  import java.io.Serializable;
12  import java.rmi.RemoteException;
13  
14  import javax.media.j3d.Bounds;
15  import javax.naming.NamingException;
16  
17  import org.djutils.logger.CategoryLogger;
18  
19  import nl.tudelft.simulation.dsol.animation.Locatable;
20  import nl.tudelft.simulation.dsol.animation.D2.Renderable2D;
21  import nl.tudelft.simulation.dsol.simulators.SimulatorInterface;
22  import nl.tudelft.simulation.language.d3.BoundingBox;
23  import nl.tudelft.simulation.language.d3.DirectedPoint;
24  
25  /**
26   * Display a text for another Locatable object.
27   * <p>
28   * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
29   * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
30   * </p>
31   * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
32   * initial version Dec 11, 2016 <br>
33   * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
34   * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
35   * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
36   */
37  public abstract class TextAnimation implements Locatable, Serializable
38  {
39      /** */
40      private static final long serialVersionUID = 20161211L;
41  
42      /** The object for which the text is displayed. */
43      private final Locatable source;
44  
45      /** The text to display. */
46      private String text;
47  
48      /** The horizontal movement of the text, in meters. */
49      private float dx;
50  
51      /** The vertical movement of the text, in meters. */
52      private float dy;
53  
54      /** Whether to center or not. */
55      private final TextAlignment textAlignment;
56  
57      /** The color of the text. */
58      private Color color;
59  
60      /** FontSize the size of the font; default = 2.0 (meters). */
61      private final float fontSize;
62  
63      /** Minimum font size to trigger scaling. */
64      private final float minFontSize;
65  
66      /** Maximum font size to trigger scaling. */
67      private final float maxFontSize;
68  
69      /** The animation implementation. */
70      private final AnimationImpl animationImpl;
71  
72      /** The font. */
73      private Font font;
74  
75      /** Access to the current background color. */
76      private final ContrastToBackground background;
77  
78      /** The font rectangle. */
79      private Rectangle2D fontRectangle = null;
80  
81      /** Render dependent on font scale. */
82      private final ScaleDependentRendering scaleDependentRendering;
83  
84      /**
85       * Construct a new TextAnimation.
86       * @param source Locatable; the object for which the text is displayed
87       * @param text String; the text to display
88       * @param dx float; the horizontal movement of the text, in meters
89       * @param dy float; the vertical movement of the text, in meters
90       * @param textAlignment TextAlignment; where to place the text
91       * @param color Color; the color of the text
92       * @param fontSize float; the size of the font; default = 2.0 (meters)
93       * @param minFontSize float; minimum font size resulting from scaling
94       * @param maxFontSize float; maximum font size resulting from scaling
95       * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
96       * @param background ContrastToBackground; allows querying the background color and adaptation of the actual color of the
97       *            text to ensure contrast
98       * @param scaleDependentRendering ScaleDependentRendering; suppress rendering when font scale is too small
99       * @throws NamingException when animation context cannot be created or retrieved
100      * @throws RemoteException when remote context cannot be found
101      */
102     @SuppressWarnings("checkstyle:parameternumber")
103     public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
104             final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
105             final float maxFontSize, final SimulatorInterface.TimeDoubleUnit simulator, final ContrastToBackground background,
106             final ScaleDependentRendering scaleDependentRendering) throws RemoteException, NamingException
107     {
108         this.source = source;
109         this.text = text;
110         this.dx = dx;
111         this.dy = dy;
112         this.textAlignment = textAlignment;
113         this.color = color;
114         this.fontSize = fontSize;
115         this.minFontSize = minFontSize;
116         this.maxFontSize = maxFontSize;
117         this.background = background;
118         this.scaleDependentRendering = scaleDependentRendering;
119 
120         this.font = new Font("SansSerif", Font.PLAIN, 2);
121         if (this.fontSize != 2.0f)
122         {
123             this.font = this.font.deriveFont(this.fontSize);
124         }
125 
126         this.animationImpl = new AnimationImpl(this, simulator);
127     }
128 
129     /**
130      * Construct a new TextAnimation without contrast to background protection and no minimum font scale.
131      * @param source Locatable; the object for which the text is displayed
132      * @param text String; the text to display
133      * @param dx float; the horizontal movement of the text, in meters
134      * @param dy float; the vertical movement of the text, in meters
135      * @param textAlignment TextAlignment; where to place the text
136      * @param color Color; the color of the text
137      * @param fontSize float; the size of the font; default = 2.0 (meters)
138      * @param minFontSize float; minimum font size resulting from scaling
139      * @param maxFontSize float; maximum font size resulting from scaling
140      * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
141      * @param scaleDependentRendering ScaleDependentRendering; render text only when bigger than minimum scale
142      * @throws NamingException when animation context cannot be created or retrieved
143      * @throws RemoteException when remote context cannot be found
144      */
145     @SuppressWarnings("checkstyle:parameternumber")
146     public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
147             final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
148             final float maxFontSize, final SimulatorInterface.TimeDoubleUnit simulator,
149             final ScaleDependentRendering scaleDependentRendering) throws RemoteException, NamingException
150     {
151         this(source, text, dx, dy, textAlignment, color, fontSize, minFontSize, maxFontSize, simulator, null,
152                 scaleDependentRendering);
153     }
154 
155     /**
156      * @param source Locatable; the object for which the text is displayed
157      * @param text String; the text to display
158      * @param dx float; the horizontal movement of the text, in meters
159      * @param dy float; the vertical movement of the text, in meters
160      * @param textAlignment TextAlignment; where to place the text
161      * @param color Color; the color of the text
162      * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
163      * @param scaleDependentRendering ScaleDependentRendering; render text only when bigger than minimum scale
164      * @throws NamingException when animation context cannot be created or retrieved
165      * @throws RemoteException when remote context cannot be found
166      */
167     public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
168             final TextAlignment textAlignment, final Color color, final SimulatorInterface.TimeDoubleUnit simulator,
169             final ScaleDependentRendering scaleDependentRendering) throws RemoteException, NamingException
170     {
171         this(source, text, dx, dy, textAlignment, color, 2.0f, 12.0f, 50f, simulator, scaleDependentRendering);
172     }
173 
174     /** {@inheritDoc} */
175     @Override
176     @SuppressWarnings("checkstyle:designforextension")
177     public DirectedPoint getLocation() throws RemoteException
178     {
179         // draw always on top.
180         DirectedPoint p = this.source.getLocation();
181         return new DirectedPoint(p.x, p.y, Double.MAX_VALUE, 0.0, 0.0, p.getRotZ());
182     }
183 
184     /** {@inheritDoc} */
185     @Override
186     public final Bounds getBounds() throws RemoteException
187     {
188         return new BoundingBox(0.0, 0.0, 0.0);
189     }
190 
191     /**
192      * paint() method so it can be overridden or extended.
193      * @param graphics Graphics2D; the graphics object
194      * @param observer ImageObserver; the observer
195      * @throws RemoteException on network exception
196      */
197     @SuppressWarnings("checkstyle:designforextension")
198     public void paint(final Graphics2D graphics, final ImageObserver observer) throws RemoteException
199     {
200         double scale = Math.sqrt(graphics.getTransform().getDeterminant());
201         Rectangle2D scaledFontRectangle;
202         synchronized (this.font)
203         {
204             if (!this.scaleDependentRendering.isRendered(scale))
205             {
206                 return;
207             }
208             if (scale < this.minFontSize / this.fontSize)
209             {
210                 graphics.setFont(this.font.deriveFont((float) (this.minFontSize / scale)));
211                 FontMetrics fm = graphics.getFontMetrics();
212                 scaledFontRectangle = fm.getStringBounds(this.text, graphics);
213             }
214             else if (scale > this.maxFontSize / this.fontSize)
215             {
216                 graphics.setFont(this.font.deriveFont((float) (this.maxFontSize / scale)));
217                 FontMetrics fm = graphics.getFontMetrics();
218                 scaledFontRectangle = fm.getStringBounds(this.text, graphics);
219             }
220             else
221             {
222                 graphics.setFont(this.font);
223                 if (this.fontRectangle == null)
224                 {
225                     FontMetrics fm = graphics.getFontMetrics();
226                     this.fontRectangle = fm.getStringBounds(this.text, graphics);
227                 }
228                 scaledFontRectangle = this.fontRectangle;
229             }
230             Color useColor = this.color;
231             if (null != this.background && useColor.equals(this.background.getBackgroundColor()))
232             {
233                 // Construct an alternative color
234                 if (Color.BLACK.equals(useColor))
235                 {
236                     useColor = Color.WHITE;
237                 }
238                 else
239                 {
240                     useColor = Color.BLACK;
241                 }
242             }
243             graphics.setColor(useColor);
244             float dxText =
245                     this.textAlignment.equals(TextAlignment.LEFT) ? 0.0f : this.textAlignment.equals(TextAlignment.CENTER)
246                             ? (float) -scaledFontRectangle.getWidth() / 2.0f : (float) -scaledFontRectangle.getWidth();
247             graphics.drawString(this.text, dxText + this.dx, this.fontSize / 2.0f - this.dy);
248         }
249     }
250 
251     /**
252      * Destroy the text animation.
253      */
254     public final void destroy()
255     {
256         try
257         {
258             this.animationImpl.destroy();
259         }
260         catch (NamingException | RemoteException exception)
261         {
262             CategoryLogger.always().warn(exception, "Tried to destroy Text for GTU animation of GTU {}",
263                     this.source.toString());
264         }
265     }
266 
267     /**
268      * Clone the TextAnimation and return a copy for the new source on the new simulator.
269      * @param newSource Locatable; the new source to link to the text animation
270      * @param newSimulator SimulatorInterface.TimeDoubleUnit; the new simulator to register the animation on
271      * @return TextAnimation; a copy of this TextAnimation
272      * @throws RemoteException when remote animation cannot be reached
273      * @throws NamingException when animation name cannot be found or bound in the Context
274      */
275     public abstract TextAnimation clone(Locatable newSource, SimulatorInterface.TimeDoubleUnit newSimulator)
276             throws RemoteException, NamingException;
277 
278     /**
279      * Retrieve the source.
280      * @return Locatable; the source
281      */
282     protected final Locatable getSource()
283     {
284         return this.source;
285     }
286 
287     /**
288      * Retrieve dx.
289      * @return float; the value of dx
290      */
291     protected final float getDx()
292     {
293         return this.dx;
294     }
295 
296     /**
297      * Retrieve dy.
298      * @return float; the value of dy
299      */
300     protected final float getDy()
301     {
302         return this.dy;
303     }
304 
305     /**
306      * Sets a new offset.
307      * @param x float; dx
308      * @param y float; dy
309      */
310     protected final void setXY(final float x, final float y)
311     {
312         this.dx = x;
313         this.dy = y;
314     }
315 
316     /**
317      * Retrieve the text alignment.
318      * @return TextAlignment; the text alignment
319      */
320     protected final TextAlignment getTextAlignment()
321     {
322         return this.textAlignment;
323     }
324 
325     /**
326      * Retrieve the font size.
327      * @return float; the font size
328      */
329     protected final float getFontSize()
330     {
331         return this.fontSize;
332     }
333 
334     /**
335      * Retrieve the font.
336      * @return Font; the font
337      */
338     protected final Font getFont()
339     {
340         return this.font;
341     }
342 
343     /**
344      * Retrieve the current text.
345      * @return String; the current text
346      */
347     protected final String getText()
348     {
349         return this.text;
350     }
351 
352     /**
353      * Update the text.
354      * @param text String; the new text
355      */
356     protected final void setText(final String text)
357     {
358         this.text = text;
359         synchronized (this.font)
360         {
361             this.fontRectangle = null;
362         }
363     }
364 
365     /**
366      * Retrieve the current color.
367      * @return Color; the current color
368      */
369     protected final Color getColor()
370     {
371         return this.color;
372     }
373 
374     /**
375      * Update the color.
376      * @param color Color; the new color
377      */
378     protected final void setColor(final Color color)
379     {
380         this.color = color;
381     }
382 
383     /**
384      * Retrieve the current flip status.
385      * @return boolean; the current flip status
386      */
387     public final boolean isFlip()
388     {
389         return this.animationImpl.isFlip();
390     }
391 
392     /**
393      * Update the flip status.
394      * @param flip boolean; the new flip status
395      */
396     public final void setFlip(final boolean flip)
397     {
398         this.animationImpl.setFlip(flip);
399     }
400 
401     /**
402      * Retrieve the current rotation status.
403      * @return boolean; the current rotation status
404      */
405     public final boolean isRotate()
406     {
407         return this.animationImpl.isRotate();
408     }
409 
410     /**
411      * Update the rotation status.
412      * @param rotate boolean; the new rotation status
413      */
414     public final void setRotate(final boolean rotate)
415     {
416         this.animationImpl.setRotate(rotate);
417 
418     }
419 
420     /**
421      * Retrieve the current scale status.
422      * @return boolean; the current scale status
423      */
424     public final boolean isScale()
425     {
426         return this.animationImpl.isScale();
427     }
428 
429     /**
430      * Update the scale status.
431      * @param scale boolean; the new scale status
432      */
433     public final void setScale(final boolean scale)
434     {
435         this.animationImpl.setScale(scale);
436     }
437 
438     /**
439      * Retrieve the current translate status.
440      * @return boolean; the current translate status
441      */
442     public final boolean isTranslate()
443     {
444         return this.animationImpl.isTranslate();
445     }
446 
447     /**
448      * Update the translate status.
449      * @param translate boolean; the new translate status
450      */
451     public final void setTranslate(final boolean translate)
452     {
453         this.animationImpl.setTranslate(translate);
454     }
455 
456     /**
457      * The implementation of the text animation. Cloning will be taken care of by the overarching TextAnimation-derived class.
458      * <p>
459      * Copyright (c) 2013-2020 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
460      * <br>
461      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
462      * </p>
463      * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
464      * initial version Dec 11, 2016 <br>
465      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
466      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
467      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
468      */
469     private static class AnimationImpl extends Renderable2D<Locatable> implements Serializable
470     {
471         /** */
472         private static final long serialVersionUID = 20170400L;
473 
474         /**
475          * Construct a new AnimationImpl.
476          * @param source Locatable; the source
477          * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
478          * @throws NamingException when animation context cannot be created or retrieved
479          * @throws RemoteException when remote context cannot be found
480          */
481         AnimationImpl(final Locatable source, final SimulatorInterface.TimeDoubleUnit simulator)
482                 throws NamingException, RemoteException
483         {
484             super(source, simulator);
485         }
486 
487         /** {@inheritDoc} */
488         @Override
489         public final void paint(final Graphics2D graphics, final ImageObserver observer) throws RemoteException
490         {
491             TextAnimation./../../org/opentrafficsim/draw/core/TextAnimation.html#TextAnimation">TextAnimation ta = ((TextAnimation) getSource());
492             ta.paint(graphics, observer);
493         }
494 
495         /** {@inheritDoc} */
496         @Override
497         public boolean contains(final Point2D pointWorldCoordinates, final Rectangle2D extent, final Dimension screen)
498         {
499             return false;
500         }
501 
502         /** {@inheritDoc} */
503         @Override
504         public final String toString()
505         {
506             return "TextAnimation.AnimationImpl []";
507         }
508 
509     }
510 
511     /**
512      * Retrieve the scale dependent rendering qualifier (used in cloning).
513      * @return ScaleDependentRendering; the rendering qualifier of this TextAnimation
514      */
515     protected ScaleDependentRendering getScaleDependentRendering()
516     {
517         return this.scaleDependentRendering;
518     }
519 
520     /**
521      * Interface to obtain the color of the background.
522      */
523     public interface ContrastToBackground
524     {
525         /**
526          * Retrieve the color of the background.
527          * @return Color; the (current) color of the background
528          */
529         Color getBackgroundColor();
530     }
531 
532     /**
533      * Determine if a Feature object should be rendered.
534      */
535     public interface ScaleDependentRendering
536     {
537         /**
538          * Determine if a Text should be rendered, depending on the scale.
539          * @param scale double; the current font scale
540          * @return boolean; true if the text should be rendered at the scale; false if the text should not be rendered at the
541          *         scale
542          */
543         boolean isRendered(double scale);
544     }
545 
546     /** Always render the Text. */
547     public static final ScaleDependentRendering RENDERALWAYS = new ScaleDependentRendering()
548     {
549 
550         @Override
551         public boolean isRendered(final double scale)
552         {
553             return true;
554         }
555     };
556 
557     /** Don't render texts when smaller than 1. */
558     public static final ScaleDependentRendering RENDERWHEN1 = new ScaleDependentRendering()
559     {
560 
561         @Override
562         public boolean isRendered(final double scale)
563         {
564             return scale >= 1.0;
565         }
566     };
567 
568     /** Don't render texts when smaller than 2. */
569     public static final ScaleDependentRendering RENDERWHEN10 = new ScaleDependentRendering()
570     {
571 
572         @Override
573         public boolean isRendered(final double scale)
574         {
575             return scale >= 0.1;
576         }
577     };
578 
579     /** Don't render texts when smaller than 2. */
580     public static final ScaleDependentRendering RENDERWHEN100 = new ScaleDependentRendering()
581     {
582 
583         @Override
584         public boolean isRendered(final double scale)
585         {
586             return scale >= 0.01;
587         }
588     };
589 
590 }