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.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 }