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