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