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