View Javadoc
1   package org.opentrafficsim.animation.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.animation.DrawLevel;
16  import org.opentrafficsim.animation.OtsRenderable;
17  import org.opentrafficsim.animation.RenderableTextSource;
18  import org.opentrafficsim.animation.TextAlignment;
19  import org.opentrafficsim.animation.gtu.DefaultCarAnimation.GtuData;
20  import org.opentrafficsim.base.geometry.OtsLine2d;
21  import org.opentrafficsim.base.geometry.OtsShape;
22  
23  import nl.tudelft.simulation.naming.context.Contextualized;
24  
25  /**
26   * Draw a car.
27   * <p>
28   * Copyright (c) 2013-2026 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 Alexander Verbraeck
32   * @author Peter Knoppers
33   * @author Wouter Schakel
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 scaleX = graphics.getTransform().getScaleX();
109         double scaleY = graphics.getTransform().getScaleY();
110         if (scaleX > 1 && scaleY > 1)
111         {
112             // Rotate as isRotate() returns falls for when zoomed out and drawing dots
113             graphics.rotate(-gtu.getDirZ());
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 
166             graphics.setStroke(saveStroke);
167         }
168         else
169         {
170             // zoomed out, draw as marker with 7px diameter
171             graphics.setColor(gtu.getColor());
172             double w = 7.0 / scaleX;
173             double h = 7.0 / scaleY;
174             double x = -w / 2.0;
175             double y = -h / 2.0;
176             this.marker.setFrame(x, y, w, h);
177             graphics.fill(this.marker);
178         }
179 
180         resetRendering(graphics);
181     }
182 
183     @Override
184     public boolean isRotate()
185     {
186         return false;
187     }
188 
189     @Override
190     public void destroy(final Contextualized contextProvider)
191     {
192         super.destroy(contextProvider);
193         this.text.destroy(contextProvider);
194     }
195 
196     @Override
197     public int hashCode()
198     {
199         return this.hashCode;
200     }
201 
202     @Override
203     public boolean equals(final Object object)
204     {
205         // only here to prevent a 'hashCode without equals' warning
206         return super.equals(object);
207     }
208 
209     /**
210      * Text animation for the Car. Separate class to be able to turn it on and off...
211      * <p>
212      * Copyright (c) 2013-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
213      * <br>
214      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
215      * </p>
216      * @author Alexander Verbraeck
217      * @author Peter Knoppers
218      * @author Wouter Schakel
219      */
220     public static class Text extends RenderableTextSource<GtuData, Text>
221     {
222         /** is the animation destroyed? */
223         private boolean isTextDestroyed = false;
224 
225         /**
226          * Constructor.
227          * @param source the object for which the text is displayed
228          * @param text the text to display
229          * @param dx the horizontal movement of the text, in meters
230          * @param dy the vertical movement of the text, in meters
231          * @param textAlignment where to place the text
232          * @param color the color of the text
233          * @param contextualized context provider
234          */
235         public Text(final GtuData source, final Supplier<String> text, final float dx, final float dy,
236                 final TextAlignment textAlignment, final Color color, final Contextualized contextualized)
237         {
238             super(source, text, dx, dy, textAlignment, color, 1.0f, 12.0f, 50f, contextualized,
239                     RenderableTextSource.RENDERWHEN1);
240         }
241 
242         /**
243          * Constructor.
244          * @param source the object for which the text is displayed
245          * @param text the text to display
246          * @param dx the horizontal movement of the text, in meters
247          * @param dy the vertical movement of the text, in meters
248          * @param textAlignment where to place the text
249          * @param color the color of the text
250          * @param contextualized context provider
251          * @param background TextAnimation.ContrastToBackground; connection to retrieve the current background color
252          */
253         @SuppressWarnings("parameternumber")
254         public Text(final GtuData source, final Supplier<String> text, final float dx, final float dy,
255                 final TextAlignment textAlignment, final Color color, final Contextualized contextualized,
256                 final RenderableTextSource.ContrastToBackground background)
257         {
258             super(source, text, dx, dy, textAlignment, color, 1.0f, 12.0f, 50f, contextualized, background, RENDERWHEN1);
259             setRotate(false);
260         }
261 
262         @Override
263         public final String toString()
264         {
265             return "Text [isTextDestroyed=" + this.isTextDestroyed + "]";
266         }
267 
268     }
269 
270     /**
271      * GtuData provides the information required to draw a link.
272      * <p>
273      * Copyright (c) 2023-2026 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
274      * <br>
275      * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
276      * </p>
277      * @author Alexander Verbraeck
278      * @author Peter Knoppers
279      * @author Wouter Schakel
280      */
281     public interface GtuData extends OtsShape, Identifiable
282     {
283         /**
284          * Returns the GTU color.
285          * @return GTU color.
286          */
287         Color getColor();
288 
289         /**
290          * Returns the length.
291          * @return length.
292          */
293         Length getLength();
294 
295         /**
296          * Returns the width.
297          * @return width.
298          */
299         Length getWidth();
300 
301         /**
302          * Returns the distance towards the front.
303          * @return distance towards the front.
304          */
305         Length getFront();
306 
307         /**
308          * Returns the distance towards the rear.
309          * @return distance towards the rear.
310          */
311         Length getRear();
312 
313         /**
314          * Returns whether the left indicator is on.
315          * @return whether the left indicator is on.
316          */
317         boolean leftIndicatorOn();
318 
319         /**
320          * Returns whether the right indicator is on.
321          * @return whether the right indicator is on.
322          */
323         boolean rightIndicatorOn();
324 
325         /**
326          * Returns the shape of a marker to show when zoomed out.
327          * @return shape of a marker to show when zoomed out.
328          */
329         default RectangularShape getMarker()
330         {
331             return new Ellipse2D.Double(0, 0, 0, 0);
332         }
333 
334         /**
335          * Returns whether the braking lights are on.
336          * @return whether the braking lights are on.
337          */
338         boolean isBrakingLightsOn();
339 
340         @Override
341         DirectedPoint2d getLocation();
342 
343         @Override
344         default double getZ()
345         {
346             return DrawLevel.GTU.getZ();
347         }
348 
349         @Override
350         default double getDirZ()
351         {
352             DirectedPoint2d p = getLocation();
353             return p == null ? 0.0 : p.getDirZ();
354         }
355 
356         /**
357          * Returns the path.
358          * @return path
359          */
360         default OtsLine2d getPath()
361         {
362             return null;
363         }
364 
365         /**
366          * Marker for GTU when zoomed out.
367          */
368         enum GtuMarker
369         {
370             /** Circle. */
371             CIRCLE(new Ellipse2D.Double(0, 0, 0, 0)),
372 
373             /** Square. */
374             SQUARE(new Rectangle2D.Double(0, 0, 0, 0));
375 
376             /** Shape. */
377             private RectangularShape shape;
378 
379             /**
380              * Constructor.
381              * @param shape shape
382              */
383             GtuMarker(final RectangularShape shape)
384             {
385                 this.shape = shape;
386             }
387 
388             /**
389              * Returns the shape.
390              * @return shape.
391              */
392             public RectangularShape getShape()
393             {
394                 return this.shape;
395             }
396         }
397     }
398 
399 }