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 }