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 }