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.rmi.RemoteException;
11 import java.util.function.Supplier;
12
13 import javax.naming.NamingException;
14
15 import org.djunits.value.vdouble.scalar.Length;
16 import org.djutils.base.Identifiable;
17 import org.djutils.draw.point.OrientedPoint2d;
18 import org.opentrafficsim.base.geometry.OtsLocatable;
19 import org.opentrafficsim.base.geometry.OtsRenderable;
20 import org.opentrafficsim.draw.DrawLevel;
21 import org.opentrafficsim.draw.TextAlignment;
22 import org.opentrafficsim.draw.TextAnimation;
23 import org.opentrafficsim.draw.gtu.DefaultCarAnimation.GtuData;
24
25 import nl.tudelft.simulation.naming.context.Contextualized;
26
27 /**
28 * Draw a car.
29 * <p>
30 * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
31 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
32 * </p>
33 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
34 * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
35 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
36 */
37 public class DefaultCarAnimation extends OtsRenderable<GtuData>
38 {
39 /** */
40 private static final long serialVersionUID = 20150000L;
41
42 /** the Text object to destroy when the GTU animation is destroyed. */
43 private Text text;
44
45 /** Hashcode. */
46 private final int hashCode;
47
48 /** GTU outline. */
49 private Rectangle2D.Double rectangle;
50
51 /** Front indicator (white circle). */
52 private Ellipse2D.Double frontIndicator;
53
54 /** Left indicator. */
55 private Rectangle2D.Double leftIndicator;
56
57 /** Right indicator. */
58 private Rectangle2D.Double rightIndicator;
59
60 /** Left brake light. */
61 private Rectangle2D.Double leftBrake;
62
63 /** Right brake light. */
64 private Rectangle2D.Double rightBrake;
65
66 /** Marker if zoomed out. */
67 private RectangularShape marker;
68
69 /**
70 * Construct the DefaultCarAnimation for a LaneBasedIndividualCar.
71 * @param gtu GtuData; the Car to draw
72 * @param contextualized Contextualized; context provider
73 * @throws NamingException in case of registration failure of the animation
74 * @throws RemoteException on communication failure
75 */
76 public DefaultCarAnimation(final GtuData gtu, final Contextualized contextualized) throws NamingException, RemoteException
77 {
78 super(gtu, contextualized);
79 this.hashCode = gtu.hashCode();
80 this.text = new Text(gtu, gtu::getId, 0.0f, 0.0f, TextAlignment.CENTER, Color.BLACK, contextualized,
81 new TextAnimation.ContrastToBackground()
82 {
83 /** {@inheritDoc} */
84 @Override
85 public Color getBackgroundColor()
86 {
87 return gtu.getColor();
88 }
89 }).setDynamic(true);
90 }
91
92 /** {@inheritDoc} */
93 @Override
94 public final void paint(final Graphics2D graphics, final ImageObserver observer)
95 {
96 setRendering(graphics);
97 final GtuData gtu = getSource();
98 if (this.rectangle == null)
99 {
100 // set shapes, this is done in paint() and not the constructor, as the super constructor binds to context causing
101 // paint commands before the shapes are calculated in the constructor
102 final double length = gtu.getLength().si;
103 final double lFront = gtu.getFront().si;
104 final double lRear = gtu.getRear().si;
105 final double width = gtu.getWidth().si;
106 final double w2 = width / 2;
107 final double w4 = width / 4;
108 this.rectangle = new Rectangle2D.Double(lRear, -w2, length, width);
109 this.frontIndicator = new Ellipse2D.Double(lFront - w2 - w4, -w4, w2, w2);
110 this.leftIndicator = new Rectangle2D.Double(lFront - w4, -w2, w4, w4);
111 this.rightIndicator = new Rectangle2D.Double(lFront - w4, w2 - w4, w4, w4);
112 this.leftBrake = new Rectangle2D.Double(lRear, w2 - w4, w4, w4);
113 this.rightBrake = new Rectangle2D.Double(lRear, -w2, w4, w4);
114 this.marker = gtu.getMarker();
115 }
116
117 double scale = graphics.getTransform().getDeterminant();
118 // Math.sqrt(Math.pow(graphics.getTransform()..getScaleX(), 2)
119 // Math.pow(graphics.getTransform().getScaleY(), 2));
120 if (scale > 1)
121 {
122 Color color = gtu.getColor();
123 graphics.setColor(color);
124 BasicStroke saveStroke = (BasicStroke) graphics.getStroke();
125 graphics.setStroke(new BasicStroke(0.05f)); // 5 cm
126 graphics.fill(this.rectangle);
127
128 graphics.setColor(Color.WHITE);
129 graphics.fill(this.frontIndicator);
130 // Draw a white disk at the front to indicate which side faces forward
131 if (color.equals(Color.WHITE))
132 {
133 // Put a black ring around it
134 graphics.setColor(Color.BLACK);
135 graphics.draw(this.frontIndicator);
136 }
137
138 // turn indicator lights
139 graphics.setColor(Color.YELLOW);
140 if (gtu.leftIndicatorOn())
141 {
142 graphics.fill(this.leftIndicator);
143 if (color.equals(Color.YELLOW))
144 {
145 graphics.setColor(Color.BLACK);
146 graphics.draw(this.leftIndicator);
147 }
148 }
149 if (gtu.rightIndicatorOn())
150 {
151 graphics.fill(this.rightIndicator);
152 if (color.equals(Color.YELLOW))
153 {
154 graphics.setColor(Color.BLACK);
155 graphics.draw(this.rightIndicator);
156 }
157 }
158
159 // braking lights
160 if (gtu.isBrakingLightsOn())
161 {
162 graphics.setColor(Color.RED);
163 graphics.fill(this.leftBrake);
164 graphics.fill(this.rightBrake);
165 if (color.equals(Color.RED))
166 {
167 graphics.setColor(Color.BLACK);
168 graphics.draw(this.leftBrake);
169 graphics.draw(this.rightBrake);
170 }
171 }
172 graphics.setStroke(saveStroke);
173 }
174 else
175 {
176 // zoomed out, draw as marker with 7px diameter
177 graphics.setColor(gtu.getColor());
178 double w = 7.0 / Math.sqrt(scale);
179 double x = -w / 2.0;
180 this.marker.setFrame(x, x, w, w);
181 graphics.fill(this.marker);
182 }
183 resetRendering(graphics);
184 }
185
186 /** {@inheritDoc} */
187 @Override
188 public void destroy(final Contextualized contextProvider)
189 {
190 super.destroy(contextProvider);
191 this.text.destroy(contextProvider);
192 }
193
194 /** {@inheritDoc} */
195 @Override
196 public int hashCode()
197 {
198 return this.hashCode;
199 }
200
201 /** {@inheritDoc} */
202 @Override
203 public boolean equals(final Object object)
204 {
205 // only here to prevent a 'hashCode without equals' warning
206 return super.equals(object);
207 }
208
209 /**
210 * Text animation for the Car. Separate class to be able to turn it on and off...
211 * <p>
212 * Copyright (c) 2013-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
213 * <br>
214 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
215 * </p>
216 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
217 * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
218 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
219 */
220 public class Text extends TextAnimation<GtuData, Text>
221 {
222 /** */
223 private static final long serialVersionUID = 20161211L;
224
225 /** is the animation destroyed? */
226 private boolean isTextDestroyed = false;
227
228 /**
229 * @param source GtuData; the object for which the text is displayed
230 * @param text Supplier<String>; the text to display
231 * @param dx float; the horizontal movement of the text, in meters
232 * @param dy float; the vertical movement of the text, in meters
233 * @param textAlignment TextAlignment; where to place the text
234 * @param color Color; the color of the text
235 * @param contextualized Contextualized; context provider
236 * @throws NamingException when animation context cannot be created or retrieved
237 * @throws RemoteException - when remote context cannot be found
238 */
239 public Text(final GtuData source, final Supplier<String> text, final float dx, final float dy,
240 final TextAlignment textAlignment, final Color color, final Contextualized contextualized)
241 throws RemoteException, NamingException
242 {
243 super(source, text, dx, dy, textAlignment, color, 1.0f, 12.0f, 50f, contextualized, TextAnimation.RENDERWHEN1);
244 }
245
246 /**
247 * @param source GtuData; the object for which the text is displayed
248 * @param text Supplier<String>; the text to display
249 * @param dx float; the horizontal movement of the text, in meters
250 * @param dy float; the vertical movement of the text, in meters
251 * @param textAlignment TextAlignment; where to place the text
252 * @param color Color; the color of the text
253 * @param contextualized Contextualized; context provider
254 * @param background TextAnimation.ContrastToBackground; connection to retrieve the current background color
255 * @throws NamingException when animation context cannot be created or retrieved
256 * @throws RemoteException - when remote context cannot be found
257 */
258 @SuppressWarnings("parameternumber")
259 public Text(final GtuData source, final Supplier<String> text, final float dx, final float dy,
260 final TextAlignment textAlignment, final Color color, final Contextualized contextualized,
261 final TextAnimation.ContrastToBackground background) throws RemoteException, NamingException
262 {
263 super(source, text, dx, dy, textAlignment, color, 1.0f, 12.0f, 50f, contextualized, background, RENDERWHEN1);
264 }
265
266 /** {@inheritDoc} */
267 @Override
268 public final String toString()
269 {
270 return "Text [isTextDestroyed=" + this.isTextDestroyed + "]";
271 }
272
273 }
274
275 /**
276 * GtuData provides the information required to draw a link.
277 * <p>
278 * Copyright (c) 2023-2024 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved.
279 * <br>
280 * BSD-style license. See <a href="https://opentrafficsim.org/docs/license.html">OpenTrafficSim License</a>.
281 * </p>
282 * @author <a href="https://github.com/averbraeck">Alexander Verbraeck</a>
283 * @author <a href="https://tudelft.nl/staff/p.knoppers-1">Peter Knoppers</a>
284 * @author <a href="https://github.com/wjschakel">Wouter Schakel</a>
285 */
286 public interface GtuData extends OtsLocatable, Identifiable
287 {
288 /**
289 * Returns the GTU color.
290 * @return Color; GTU color.
291 */
292 Color getColor();
293
294 /**
295 * Returns the length.
296 * @return Length; length.
297 */
298 Length getLength();
299
300 /**
301 * Returns the width.
302 * @return Length; width.
303 */
304 Length getWidth();
305
306 /**
307 * Returns the distance towards the front.
308 * @return Length; distance towards the front.
309 */
310 Length getFront();
311
312 /**
313 * Returns the distance towards the rear.
314 * @return Length; distance towards the rear.
315 */
316 Length getRear();
317
318 /**
319 * Returns whether the left indicator is on.
320 * @return boolean; whether the left indicator is on.
321 */
322 boolean leftIndicatorOn();
323
324 /**
325 * Returns whether the right indicator is on.
326 * @return boolean; whether the right indicator is on.
327 */
328 boolean rightIndicatorOn();
329
330 /**
331 * Returns the shape of a marker to show when zoomed out.
332 * @return RectangularShape; shape of a marker to show when zoomed out.
333 */
334 default RectangularShape getMarker()
335 {
336 return new Ellipse2D.Double(0, 0, 0, 0);
337 }
338
339 /**
340 * Returns whether the braking lights are on.
341 * @return boolean; whether the braking lights are on.
342 */
343 boolean isBrakingLightsOn();
344
345 /** {@inheritDoc} */
346 @Override
347 OrientedPoint2d getLocation();
348
349 /** {@inheritDoc} */
350 @Override
351 default double getZ()
352 {
353 return DrawLevel.GTU.getZ();
354 }
355 }
356
357 }