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