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