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(GTUType.TRUCK) ? new Rectangle2D.Double(0, 0, 0, 0)
169                     : 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             graphics.setColor(this.gtuColorer.getColor(gtu));
178             BasicStroke saveStroke = (BasicStroke) graphics.getStroke();
179             graphics.setStroke(new BasicStroke(0));
180             graphics.fill(this.rectangle);
181 
182             // Draw a white disk at the front to indicate which side faces forward
183             graphics.setColor(Color.WHITE);
184             graphics.fill(this.frontIndicator);
185 
186             // turn indicator lights
187             graphics.setColor(Color.YELLOW);
188             if (gtu.getTurnIndicatorStatus() != null && gtu.getTurnIndicatorStatus().isLeftOrBoth())
189             {
190                 graphics.fill(this.leftIndicator);
191             }
192             if (gtu.getTurnIndicatorStatus() != null && gtu.getTurnIndicatorStatus().isRightOrBoth())
193             {
194                 graphics.fill(this.rightIndicator);
195             }
196 
197             // braking lights
198             if (gtu.isBrakingLightsOn())
199             {
200                 graphics.setColor(Color.RED);
201                 graphics.fill(this.leftBrake);
202                 graphics.fill(this.rightBrake);
203             }
204             graphics.setStroke(saveStroke);
205         }
206         else
207         {
208             // zoomed out, draw as marker with 7px diameter
209             graphics.setColor(this.gtuColorer.getColor(gtu));
210             double w = 7.0 / Math.sqrt(scale);
211             double x = -w / 2.0;
212             this.dot.setFrame(x, x, w, w);
213             graphics.fill(this.dot);
214         }
215 
216     }
217 
218     /** {@inheritDoc} */
219     @Override
220     public final void destroy() throws NamingException
221     {
222         this.isDestroyed = true;
223         super.destroy();
224         this.text.destroy();
225     }
226 
227     /** {@inheritDoc} */
228     @Override
229     @SuppressWarnings("checkstyle:designforextension")
230     public ClonableRenderable2DInterface<LaneBasedGTU> clone(final LaneBasedGTU newSource,
231             final SimulatorInterface.TimeDoubleUnit newSimulator) throws NamingException, RemoteException
232     {
233         // the constructor also constructs the corresponding Text object
234         return new DefaultCarAnimation(newSource, newSimulator, this.gtuColorer);
235     }
236 
237     /** {@inheritDoc} */
238     @Override
239     public final String toString()
240     {
241         return super.toString(); // this.getSource().toString();
242     }
243 
244     /** {@inheritDoc} */
245     @Override
246     public int hashCode()
247     {
248         return this.hashCode;
249     }
250 
251     /** {@inheritDoc} */
252     @Override
253     public boolean equals(final Object object)
254     {
255         // only here to prevent a 'hashCode without equals' warning
256         return super.equals(object);
257     }
258 
259     /**
260      * Text animation for the Car. Separate class to be able to turn it on and off...
261      * <p>
262      * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
263      * <br>
264      * BSD-style license. See <a href="http://opentrafficsim.org/docs/current/license.html">OpenTrafficSim License</a>.
265      * </p>
266      * $LastChangedDate: 2015-07-24 02:58:59 +0200 (Fri, 24 Jul 2015) $, @version $Revision: 1147 $, by $Author: averbraeck $,
267      * initial version Dec 11, 2016 <br>
268      * @author <a href="http://www.tbm.tudelft.nl/averbraeck">Alexander Verbraeck</a>
269      * @author <a href="http://www.tudelft.nl/pknoppers">Peter Knoppers</a>
270      * @author <a href="http://www.transport.citg.tudelft.nl">Wouter Schakel</a>
271      */
272     public class Text extends TextAnimation
273     {
274         /** */
275         private static final long serialVersionUID = 20161211L;
276 
277         /** is the animation destroyed? */
278         private boolean isTextDestroyed = false;
279 
280         /**
281          * @param source Locatable; the object for which the text is displayed
282          * @param text String; the text to display
283          * @param dx float; the horizontal movement of the text, in meters
284          * @param dy float; the vertical movement of the text, in meters
285          * @param textAlignment TextAlignment; where to place the text
286          * @param color Color; the color of the text
287          * @param simulator SimulatorInterface.TimeDoubleUnit; the simulator
288          * @throws NamingException when animation context cannot be created or retrieved
289          * @throws RemoteException - when remote context cannot be found
290          */
291         public Text(final Locatable source, final String text, final float dx, final float dy,
292                 final TextAlignment textAlignment, final Color color, final SimulatorInterface.TimeDoubleUnit simulator)
293                 throws RemoteException, NamingException
294         {
295             super(source, text, dx, dy, textAlignment, color, 1.0f, 12.0f, simulator);
296         }
297 
298         /** {@inheritDoc} */
299         @Override
300         public final void paint(final Graphics2D graphics, final ImageObserver observer) throws RemoteException
301         {
302             final LaneBasedIndividualGTU car = (LaneBasedIndividualGTU) getSource();
303 
304             if (car.isDestroyed())
305             {
306                 if (!this.isTextDestroyed)
307                 {
308                     try
309                     {
310                         destroy();
311                     }
312                     catch (Exception e)
313                     {
314                         System.err.println("Error while destroying text animation of GTU " + car.getId());
315                     }
316                     this.isTextDestroyed = true;
317                 }
318                 return;
319             }
320 
321             super.paint(graphics, observer);
322         }
323 
324         /** {@inheritDoc} */
325         @Override
326         @SuppressWarnings("checkstyle:designforextension")
327         public DirectedPoint getLocation() throws RemoteException
328         {
329             // draw always on top, and not upside down.
330             DirectedPoint p = ((LaneBasedIndividualGTU) getSource()).getLocation();
331             double a = Angle.normalizePi(p.getRotZ());
332             if (a > Math.PI / 2.0 || a < -0.99 * Math.PI / 2.0)
333             {
334                 a += Math.PI;
335             }
336             return new DirectedPoint(p.x, p.y, Double.MAX_VALUE, 0.0, 0.0, a);
337         }
338 
339         /** {@inheritDoc} */
340         @Override
341         @SuppressWarnings("checkstyle:designforextension")
342         public TextAnimation clone(final Locatable newSource, final SimulatorInterface.TimeDoubleUnit newSimulator)
343                 throws RemoteException, NamingException
344         {
345             return new Text(newSource, getText(), getDx(), getDy(), getTextAlignment(), getColor(), newSimulator);
346         }
347 
348         /** {@inheritDoc} */
349         @Override
350         public final String toString()
351         {
352             return "Text [isTextDestroyed=" + this.isTextDestroyed + "]";
353         }
354 
355     }
356 
357 }