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