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