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 /** The animation implementation. */
66 private final AnimationImpl animationImpl;
67
68 /** The font. */
69 private Font font;
70
71 /** The font rectangle. */
72 private Rectangle2D fontRectangle = null;
73
74 /**
75 * @param source Locatable; the object for which the text is displayed
76 * @param text String; the text to display
77 * @param dx float; the horizontal movement of the text, in meters
78 * @param dy float; the vertical movement of the text, in meters
79 * @param textAlignment TextAlignment; where to place the text
80 * @param color Color; the color of the text
81 * @param fontSize float; the size of the font; default = 2.0 (meters)
82 * @param minFontSize float; minimum font size to trigger scaling
83 * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
84 * @throws NamingException when animation context cannot be created or retrieved
85 * @throws RemoteException when remote context cannot be found
86 */
87 @SuppressWarnings("checkstyle:parameternumber")
88 public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
89 final TextAlignment textAlignment, final Color color, final float fontSize, final float minFontSize,
90 final SimulatorInterface.TimeDoubleUnit simulator) throws RemoteException, NamingException
91 {
92 this.source = source;
93 this.text = text;
94 this.dx = dx;
95 this.dy = dy;
96 this.textAlignment = textAlignment;
97 this.color = color;
98 this.fontSize = fontSize;
99 this.minFontSize = minFontSize;
100
101 this.font = new Font("SansSerif", Font.PLAIN, 2);
102 if (this.fontSize != 2.0f)
103 {
104 this.font = this.font.deriveFont(this.fontSize);
105 }
106
107 this.animationImpl = new AnimationImpl(this, simulator);
108 }
109
110 /**
111 * @param source Locatable; the object for which the text is displayed
112 * @param text String; the text to display
113 * @param dx float; the horizontal movement of the text, in meters
114 * @param dy float; the vertical movement of the text, in meters
115 * @param textAlignment TextAlignment; where to place the text
116 * @param color Color; the color of the text
117 * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
118 * @throws NamingException when animation context cannot be created or retrieved
119 * @throws RemoteException when remote context cannot be found
120 */
121 public TextAnimation(final Locatable source, final String text, final float dx, final float dy,
122 final TextAlignment textAlignment, final Color color, final SimulatorInterface.TimeDoubleUnit simulator)
123 throws RemoteException, NamingException
124 {
125 this(source, text, dx, dy, textAlignment, color, 2.0f, 12.0f, simulator);
126 }
127
128 /** {@inheritDoc} */
129 @Override
130 @SuppressWarnings("checkstyle:designforextension")
131 public DirectedPoint getLocation() throws RemoteException
132 {
133 // draw always on top.
134 DirectedPoint p = this.source.getLocation();
135 return new DirectedPoint(p.x, p.y, Double.MAX_VALUE, 0.0, 0.0, p.getRotZ());
136 }
137
138 /** {@inheritDoc} */
139 @Override
140 public final Bounds getBounds() throws RemoteException
141 {
142 return new BoundingBox(0.0, 0.0, 0.0);
143 }
144
145 /**
146 * paint() method so it can be overridden or extended.
147 * @param graphics Graphics2D; the graphics object
148 * @param observer ImageObserver; the observer
149 * @throws RemoteException on network exception
150 */
151 @SuppressWarnings("checkstyle:designforextension")
152 public void paint(final Graphics2D graphics, final ImageObserver observer) throws RemoteException
153 {
154 double scale = Math.sqrt(graphics.getTransform().getDeterminant());
155 Rectangle2D scaledFontRectangle;
156 synchronized (this.font)
157 {
158 if (scale < this.minFontSize)
159 {
160 graphics.setFont(this.font.deriveFont((float) (this.minFontSize / scale)));
161 FontMetrics fm = graphics.getFontMetrics();
162 scaledFontRectangle = fm.getStringBounds(this.text, graphics);
163 }
164 else
165 {
166 graphics.setFont(this.font);
167 if (this.fontRectangle == null)
168 {
169 FontMetrics fm = graphics.getFontMetrics();
170 this.fontRectangle = fm.getStringBounds(this.text, graphics);
171 }
172 scaledFontRectangle = this.fontRectangle;
173 }
174 graphics.setColor(this.color);
175 float dxText =
176 this.textAlignment.equals(TextAlignment.LEFT) ? 0.0f : this.textAlignment.equals(TextAlignment.CENTER)
177 ? (float) -scaledFontRectangle.getWidth() / 2.0f : (float) -scaledFontRectangle.getWidth();
178 graphics.drawString(this.text, dxText + this.dx, this.fontSize / 2.0f - this.dy);
179 }
180 }
181
182 /**
183 * Destroy the text animation.
184 */
185 public final void destroy()
186 {
187 try
188 {
189 this.animationImpl.destroy();
190 }
191 catch (NamingException exception)
192 {
193 SimLogger.always().warn(exception, "Tried to destroy Text for GTU animation of GTU {}", this.source.toString());
194 }
195 }
196
197 /**
198 * Clone the TextAnimation and return a copy for the new source on the new simulator.
199 * @param newSource Locatable; the new source to link to the text animation
200 * @param newSimulator SimulatorInterface.TimeDoubleUnit; the new simulator to register the animation on
201 * @return TextAnimation; a copy of this TextAnimation
202 * @throws RemoteException when remote animation cannot be reached
203 * @throws NamingException when animation name cannot be found or bound in the Context
204 */
205 public abstract TextAnimation clone(Locatable newSource, SimulatorInterface.TimeDoubleUnit newSimulator)
206 throws RemoteException, NamingException;
207
208 /**
209 * Retrieve the source.
210 * @return Locatable; the source
211 */
212 protected final Locatable getSource()
213 {
214 return this.source;
215 }
216
217 /**
218 * Retrieve dx.
219 * @return float; the value of dx
220 */
221 protected final float getDx()
222 {
223 return this.dx;
224 }
225
226 /**
227 * Retrieve dy.
228 * @return float; the value of dy
229 */
230 protected final float getDy()
231 {
232 return this.dy;
233 }
234
235 /**
236 * Sets a new offset.
237 * @param x float; dx
238 * @param y float; dy
239 */
240 protected final void setXY(final float x, final float y)
241 {
242 this.dx = x;
243 this.dy = y;
244 }
245
246 /**
247 * Retrieve the text alignment.
248 * @return TextAlignment; the text alignment
249 */
250 protected final TextAlignment getTextAlignment()
251 {
252 return this.textAlignment;
253 }
254
255 /**
256 * Retrieve the font size.
257 * @return float; the font size
258 */
259 protected final float getFontSize()
260 {
261 return this.fontSize;
262 }
263
264 /**
265 * Retrieve the font.
266 * @return Font; the font
267 */
268 protected final Font getFont()
269 {
270 return this.font;
271 }
272
273 /**
274 * Retrieve the current text.
275 * @return String; the current text
276 */
277 protected final String getText()
278 {
279 return this.text;
280 }
281
282 /**
283 * Update the text.
284 * @param text String; the new text
285 */
286 protected final void setText(final String text)
287 {
288 this.text = text;
289 synchronized (this.font)
290 {
291 this.fontRectangle = null;
292 }
293 }
294
295 /**
296 * Retrieve the current color.
297 * @return Color; the current color
298 */
299 protected final Color getColor()
300 {
301 return this.color;
302 }
303
304 /**
305 * Update the color.
306 * @param color Color; the new color
307 */
308 protected final void setColor(final Color color)
309 {
310 this.color = color;
311 }
312
313 /**
314 * Retrieve the current flip status.
315 * @return boolean; the current flip status
316 */
317 public final boolean isFlip()
318 {
319 return this.animationImpl.isFlip();
320 }
321
322 /**
323 * Update the flip status.
324 * @param flip boolean; the new flip status
325 */
326 public final void setFlip(final boolean flip)
327 {
328 this.animationImpl.setFlip(flip);
329 }
330
331 /**
332 * Retrieve the current rotation status.
333 * @return boolean; the current rotation status
334 */
335 public final boolean isRotate()
336 {
337 return this.animationImpl.isRotate();
338 }
339
340 /**
341 * Update the rotation status.
342 * @param rotate boolean; the new rotation status
343 */
344 public final void setRotate(final boolean rotate)
345 {
346 this.animationImpl.setRotate(rotate);
347
348 }
349
350 /**
351 * Retrieve the current scale status.
352 * @return boolean; the current scale status
353 */
354 public final boolean isScale()
355 {
356 return this.animationImpl.isScale();
357 }
358
359 /**
360 * Update the scale status.
361 * @param scale boolean; the new scale status
362 */
363 public final void setScale(final boolean scale)
364 {
365 this.animationImpl.setScale(scale);
366 }
367
368 /**
369 * Retrieve the current translate status.
370 * @return boolean; the current translate status
371 */
372 public final boolean isTranslate()
373 {
374 return this.animationImpl.isTranslate();
375 }
376
377 /**
378 * Update the translate status.
379 * @param translate boolean; the new translate status
380 */
381 public final void setTranslate(final boolean translate)
382 {
383 this.animationImpl.setTranslate(translate);
384 }
385
386 /**
387 * The implementation of the text animation. Cloning will be taken care of by the overarching TextAnimation-derived class.
388 * <p>
389 * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
390 * <br>
391 * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
392 * </p>
393 * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
394 * initial version Dec 11, 2016 <br>
395 * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
396 * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
397 * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
398 */
399 private static class AnimationImpl extends Renderable2D<Locatable> implements Serializable
400 {
401 /** */
402 private static final long serialVersionUID = 20170400L;
403
404 /**
405 * Construct a new AnimationImpl.
406 * @param source Locatable; the source
407 * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
408 * @throws NamingException when animation context cannot be created or retrieved
409 * @throws RemoteException when remote context cannot be found
410 */
411 AnimationImpl(final Locatable source, final SimulatorInterface.TimeDoubleUnit simulator)
412 throws NamingException, RemoteException
413 {
414 super(source, simulator);
415 }
416
417 /** {@inheritDoc} */
418 @Override
419 public final void paint(final Graphics2D graphics, final ImageObserver observer) throws RemoteException
420 {
421 TextAnimation ta = ((TextAnimation) getSource());
422 ta.paint(graphics, observer);
423 }
424
425 /** {@inheritDoc} */
426 @Override
427 public boolean contains(final Point2D pointWorldCoordinates, final Rectangle2D extent, final Dimension screen)
428 {
429 return false;
430 }
431
432 /** {@inheritDoc} */
433 @Override
434 public final String toString()
435 {
436 return "TextAnimation.AnimationImpl []";
437 }
438
439 }
440 }