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