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