1 package org.opentrafficsim.draw.gtu;
2
3 import java.awt.BasicStroke;
4 import java.awt.Color;
5 import java.awt.Graphics2D;
6 import java.awt.geom.Ellipse2D;
7 import java.awt.geom.Rectangle2D;
8 import java.awt.geom.RectangularShape;
9 import java.awt.image.ImageObserver;
10 import java.util.function.Supplier;
11
12 import org.djunits.value.vdouble.scalar.Length;
13 import org.djutils.base.Identifiable;
14 import org.djutils.draw.point.DirectedPoint2d;
15 import org.opentrafficsim.base.geometry.OtsLine2d;
16 import org.opentrafficsim.base.geometry.OtsShape;
17 import org.opentrafficsim.draw.DrawLevel;
18 import org.opentrafficsim.draw.OtsRenderable;
19 import org.opentrafficsim.draw.RenderableTextSource;
20 import org.opentrafficsim.draw.TextAlignment;
21 import org.opentrafficsim.draw.gtu.DefaultCarAnimation.GtuData;
22
23 import nl.tudelft.simulation.naming.context.Contextualized;
24
25 /**
26 * Draw a car.
27 * <p>
28 * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
29 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
30 * </p>
31 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
32 * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
33 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
34 */
35 public class DefaultCarAnimation extends OtsRenderable<GtuData>
36 {
37 /** the Text object to destroy when the GTU animation is destroyed. */
38 private Text text;
39
40 /** Hashcode. */
41 private final int hashCode;
42
43 /** GTU outline. */
44 private Rectangle2D.Double rectangle;
45
46 /** Front indicator (white circle). */
47 private Ellipse2D.Double frontIndicator;
48
49 /** Left indicator. */
50 private Rectangle2D.Double leftIndicator;
51
52 /** Right indicator. */
53 private Rectangle2D.Double rightIndicator;
54
55 /** Left brake light. */
56 private Rectangle2D.Double leftBrake;
57
58 /** Right brake light. */
59 private Rectangle2D.Double rightBrake;
60
61 /** Marker if zoomed out. */
62 private RectangularShape marker;
63
64 /**
65 * Construct the DefaultCarAnimation for a LaneBasedIndividualCar.
66 * @param gtu the Car to draw
67 * @param contextualized context provider
68 */
69 public DefaultCarAnimation(final GtuData gtu, final Contextualized contextualized)
70 {
71 super(gtu, contextualized);
72 this.hashCode = gtu.hashCode();
73 this.text = new Text(gtu, gtu::getId, 0.0f, 0.0f, TextAlignment.CENTER, Color.BLACK, contextualized,
74 new RenderableTextSource.ContrastToBackground()
75 {
76 @Override
77 public Color getBackgroundColor()
78 {
79 return gtu.getColor();
80 }
81 }).setDynamic(true);
82 }
83
84 @Override
85 public final void paint(final Graphics2D graphics, final ImageObserver observer)
86 {
87 setRendering(graphics);
88 final GtuData gtu = getSource();
89 if (this.rectangle == null)
90 {
91 // set shapes, this is done in paint() and not the constructor, as the super constructor binds to context causing
92 // paint commands before the shapes are calculated in the constructor
93 final double length = gtu.getLength().si;
94 final double lFront = gtu.getFront().si;
95 final double lRear = gtu.getRear().si;
96 final double width = gtu.getWidth().si;
97 final double w2 = width / 2;
98 final double w4 = width / 4;
99 this.rectangle = new Rectangle2D.Double(lRear, -w2, length, width);
100 this.frontIndicator = new Ellipse2D.Double(lFront - w2 - w4, -w4, w2, w2);
101 this.leftIndicator = new Rectangle2D.Double(lFront - w4, -w2, w4, w4);
102 this.rightIndicator = new Rectangle2D.Double(lFront - w4, w2 - w4, w4, w4);
103 this.leftBrake = new Rectangle2D.Double(lRear, w2 - w4, w4, w4);
104 this.rightBrake = new Rectangle2D.Double(lRear, -w2, w4, w4);
105 this.marker = gtu.getMarker();
106 }
107
108 double scale = graphics.getTransform().getDeterminant();
109 // Math.sqrt(Math.pow(graphics.getTransform()..getScaleX(), 2)
110 // Math.pow(graphics.getTransform().getScaleY(), 2));
111 if (scale > 1)
112 {
113 Color color = gtu.getColor();
114 graphics.setColor(color);
115 BasicStroke saveStroke = (BasicStroke) graphics.getStroke();
116 graphics.setStroke(new BasicStroke(0.05f)); // 5 cm
117 graphics.fill(this.rectangle);
118
119 graphics.setColor(Color.WHITE);
120 graphics.fill(this.frontIndicator);
121 // Draw a white disk at the front to indicate which side faces forward
122 if (color.equals(Color.WHITE))
123 {
124 // Put a black ring around it
125 graphics.setColor(Color.BLACK);
126 graphics.draw(this.frontIndicator);
127 }
128
129 // turn indicator lights
130 graphics.setColor(Color.YELLOW);
131 if (gtu.leftIndicatorOn())
132 {
133 graphics.fill(this.leftIndicator);
134 if (color.equals(Color.YELLOW))
135 {
136 graphics.setColor(Color.BLACK);
137 graphics.draw(this.leftIndicator);
138 }
139 }
140 if (gtu.rightIndicatorOn())
141 {
142 graphics.fill(this.rightIndicator);
143 if (color.equals(Color.YELLOW))
144 {
145 graphics.setColor(Color.BLACK);
146 graphics.draw(this.rightIndicator);
147 }
148 }
149
150 // braking lights
151 if (gtu.isBrakingLightsOn())
152 {
153 graphics.setColor(Color.RED);
154 graphics.fill(this.leftBrake);
155 graphics.fill(this.rightBrake);
156 if (color.equals(Color.RED))
157 {
158 graphics.setColor(Color.BLACK);
159 graphics.draw(this.leftBrake);
160 graphics.draw(this.rightBrake);
161 }
162 }
163
164 // path is absolute, so need to rotate
165 /*-
166 graphics.setStroke(new BasicStroke(0.10f));
167 graphics.setColor(Color.MAGENTA);
168 Transform2d transform = OtsLocatable.toBoundsTransform(gtu.getLocation());
169 Path2D.Float path = new Path2D.Float();
170 boolean started = false;
171 for (Point2d point : gtu.getPath())
172 {
173 Point2d p = transform.transform(point);
174 if (!started)
175 {
176 path.moveTo(p.x, -p.y);
177 }
178 else
179 {
180 path.lineTo(p.x, -p.y);
181 }
182 started = true;
183 }
184 graphics.draw(path);
185 */
186
187 graphics.setStroke(saveStroke);
188 }
189 else
190 {
191 // zoomed out, draw as marker with 7px diameter
192 graphics.setColor(gtu.getColor());
193 double w = 7.0 / Math.sqrt(scale);
194 double x = -w / 2.0;
195 this.marker.setFrame(x, x, w, w);
196 graphics.fill(this.marker);
197 }
198
199 resetRendering(graphics);
200 }
201
202 @Override
203 public void destroy(final Contextualized contextProvider)
204 {
205 super.destroy(contextProvider);
206 this.text.destroy(contextProvider);
207 }
208
209 @Override
210 public int hashCode()
211 {
212 return this.hashCode;
213 }
214
215 @Override
216 public boolean equals(final Object object)
217 {
218 // only here to prevent a 'hashCode without equals' warning
219 return super.equals(object);
220 }
221
222 /**
223 * Text animation for the Car. Separate class to be able to turn it on and off...
224 * <p>
225 * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
226 * <br>
227 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
228 * </p>
229 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
230 * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
231 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
232 */
233 public static class Text extends RenderableTextSource<GtuData, Text>
234 {
235 /** is the animation destroyed? */
236 private boolean isTextDestroyed = false;
237
238 /**
239 * Constructor.
240 * @param source the object for which the text is displayed
241 * @param text the text to display
242 * @param dx the horizontal movement of the text, in meters
243 * @param dy the vertical movement of the text, in meters
244 * @param textAlignment where to place the text
245 * @param color the color of the text
246 * @param contextualized context provider
247 */
248 public Text(final GtuData source, final Supplier<String> text, final float dx, final float dy,
249 final TextAlignment textAlignment, final Color color, final Contextualized contextualized)
250 {
251 super(source, text, dx, dy, textAlignment, color, 1.0f, 12.0f, 50f, contextualized,
252 RenderableTextSource.RENDERWHEN1);
253 }
254
255 /**
256 * Constructor.
257 * @param source the object for which the text is displayed
258 * @param text the text to display
259 * @param dx the horizontal movement of the text, in meters
260 * @param dy the vertical movement of the text, in meters
261 * @param textAlignment where to place the text
262 * @param color the color of the text
263 * @param contextualized context provider
264 * @param background TextAnimation.ContrastToBackground; connection to retrieve the current background color
265 */
266 @SuppressWarnings("parameternumber")
267 public Text(final GtuData source, final Supplier<String> text, final float dx, final float dy,
268 final TextAlignment textAlignment, final Color color, final Contextualized contextualized,
269 final RenderableTextSource.ContrastToBackground background)
270 {
271 super(source, text, dx, dy, textAlignment, color, 1.0f, 12.0f, 50f, contextualized, background, RENDERWHEN1);
272 setRotate(false);
273 }
274
275 @Override
276 public final String toString()
277 {
278 return "Text [isTextDestroyed=" + this.isTextDestroyed + "]";
279 }
280
281 }
282
283 /**
284 * GtuData provides the information required to draw a link.
285 * <p>
286 * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
287 * <br>
288 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
289 * </p>
290 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
291 * @author <a href="https://github.com/peter-knoppers">Peter Knoppers</a>
292 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
293 */
294 public interface GtuData extends OtsShape, Identifiable
295 {
296 /**
297 * Returns the GTU color.
298 * @return GTU color.
299 */
300 Color getColor();
301
302 /**
303 * Returns the length.
304 * @return length.
305 */
306 Length getLength();
307
308 /**
309 * Returns the width.
310 * @return width.
311 */
312 Length getWidth();
313
314 /**
315 * Returns the distance towards the front.
316 * @return distance towards the front.
317 */
318 Length getFront();
319
320 /**
321 * Returns the distance towards the rear.
322 * @return distance towards the rear.
323 */
324 Length getRear();
325
326 /**
327 * Returns whether the left indicator is on.
328 * @return whether the left indicator is on.
329 */
330 boolean leftIndicatorOn();
331
332 /**
333 * Returns whether the right indicator is on.
334 * @return whether the right indicator is on.
335 */
336 boolean rightIndicatorOn();
337
338 /**
339 * Returns the shape of a marker to show when zoomed out.
340 * @return shape of a marker to show when zoomed out.
341 */
342 default RectangularShape getMarker()
343 {
344 return new Ellipse2D.Double(0, 0, 0, 0);
345 }
346
347 /**
348 * Returns whether the braking lights are on.
349 * @return whether the braking lights are on.
350 */
351 boolean isBrakingLightsOn();
352
353 @Override
354 DirectedPoint2d getLocation();
355
356 @Override
357 default double getZ()
358 {
359 return DrawLevel.GTU.getZ();
360 }
361
362 @Override
363 default double getDirZ()
364 {
365 DirectedPoint2d p = getLocation();
366 return p == null ? 0.0 : p.getDirZ();
367 }
368
369 /**
370 * Returns the path.
371 * @return path
372 */
373 default OtsLine2d getPath()
374 {
375 return null;
376 }
377
378 /**
379 * Marker for GTU when zoomed out.
380 */
381 enum GtuMarker
382 {
383 /** Circle. */
384 CIRCLE(new Ellipse2D.Double(0, 0, 0, 0)),
385
386 /** Square. */
387 SQUARE(new Rectangle2D.Double(0, 0, 0, 0));
388
389 /** Shape. */
390 private RectangularShape shape;
391
392 /**
393 * Constructor.
394 * @param shape shape
395 */
396 GtuMarker(final RectangularShape shape)
397 {
398 this.shape = shape;
399 }
400
401 /**
402 * Returns the shape.
403 * @return shape.
404 */
405 public RectangularShape getShape()
406 {
407 return this.shape;
408 }
409 }
410 }
411
412 }