View Javadoc
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 }