1 package org.opentrafficsim.draw.road;
2
3 import java.awt.BasicStroke;
4 import java.awt.Color;
5 import java.awt.Font;
6 import java.awt.FontMetrics;
7 import java.awt.Graphics2D;
8 import java.awt.Rectangle;
9 import java.awt.font.TextAttribute;
10 import java.awt.geom.Ellipse2D;
11 import java.awt.geom.Path2D;
12 import java.awt.geom.RoundRectangle2D;
13 import java.awt.image.ImageObserver;
14 import java.rmi.RemoteException;
15 import java.util.Map;
16
17 import org.opentrafficsim.base.geometry.BoundingBox;
18 import org.opentrafficsim.base.geometry.OtsBounds2d;
19 import org.opentrafficsim.base.geometry.OtsLocatable;
20 import org.opentrafficsim.base.geometry.OtsRenderable;
21 import org.opentrafficsim.draw.DrawLevel;
22 import org.opentrafficsim.draw.road.PriorityAnimation.PriorityData;
23
24 import nl.tudelft.simulation.naming.context.Contextualized;
25
26
27
28
29
30
31
32
33
34 public class PriorityAnimation extends OtsRenderable<PriorityData>
35 {
36
37
38 private static final long serialVersionUID = 20240228L;
39
40
41 private static final Color SHADOW = new Color(0, 0, 0, 128);
42
43
44 private static final double SHADOW_DX = 0.1;
45
46
47 private static final double SHADOW_DY = 0.05;
48
49
50
51
52
53
54 public PriorityAnimation(final PriorityData source, final Contextualized contextProvider)
55 {
56 super(source, contextProvider);
57 }
58
59
60 @Override
61 public boolean isRotate()
62 {
63 return false;
64 }
65
66
67 @Override
68 public void paint(final Graphics2D graphics, final ImageObserver observer)
69 {
70 if (getSource().isNone())
71 {
72 return;
73 }
74 setRendering(graphics);
75 if (getSource().isAllStop() || getSource().isStop())
76 {
77 paintOctagon(graphics, 1.0, SHADOW, true, true);
78 paintOctagon(graphics, 1.0, Color.WHITE, true, false);
79 paintOctagon(graphics, 1.0, Color.BLACK, false, false);
80 paintOctagon(graphics, 0.868, new Color(230, 0, 0), true, false);
81 paintString(graphics, "STOP", Color.WHITE, 0.9f, getSource().isAllStop() ? -0.1f : 0.0f);
82 if (getSource().isAllStop())
83 {
84 paintString(graphics, "ALL WAY", Color.WHITE, 0.4f, 0.45f);
85 }
86 }
87 else if (getSource().isBusStop())
88 {
89 graphics.setColor(SHADOW);
90 graphics.fill(new Ellipse2D.Double(-1.0 + SHADOW_DX, -1.0 + SHADOW_DY, 2.0, 2.0));
91 Color blue = new Color(20, 94, 169);
92 graphics.setColor(blue);
93 graphics.fill(new Ellipse2D.Double(-1.0, -1.0, 2.0, 2.0));
94 graphics.setColor(Color.WHITE);
95 graphics.setStroke(new BasicStroke(0.04f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
96 graphics.draw(new Ellipse2D.Double(-0.94, -0.94, 1.88, 1.88));
97 paintBus(graphics, blue);
98 }
99 else if (getSource().isPriority())
100 {
101 paintDiamond(graphics, 1.0, SHADOW, true, true);
102 paintDiamond(graphics, 1.0, Color.WHITE, true, false);
103 paintDiamond(graphics, 11.0 / 12.0, Color.BLACK, false, false);
104 paintDiamond(graphics, 11.0 / 18.0, new Color(255, 204, 0), true, false);
105 }
106 else if (getSource().isYield())
107 {
108 paintTriangle(graphics, 1.0, SHADOW, true, true);
109 paintTriangle(graphics, 1.0, new Color(230, 0, 0), true, false);
110 paintTriangle(graphics, 0.9, Color.WHITE, false, false);
111 paintTriangle(graphics, 0.55, Color.WHITE, true, false);
112 }
113 resetRendering(graphics);
114 }
115
116
117
118
119
120
121
122
123
124 private void paintOctagon(final Graphics2D graphics, final double radius, final Color color, final boolean fill,
125 final boolean shadow)
126 {
127 double k = Math.tan(Math.PI / 8.0) * radius;
128 double dx = shadow ? SHADOW_DX : 0.0;
129 double dy = shadow ? SHADOW_DY : 0.0;
130 Path2D.Float path = new Path2D.Float();
131 path.moveTo(dx + radius, dy);
132 path.lineTo(dx + radius, dy + k);
133 path.lineTo(dx + k, dy + radius);
134 path.lineTo(dx - k, dy + radius);
135 path.lineTo(dx - radius, dy + k);
136 path.lineTo(dx - radius, dy - k);
137 path.lineTo(dx - k, dy - radius);
138 path.lineTo(dx + k, dy - radius);
139 path.lineTo(dx + radius, dy - k);
140 path.lineTo(dx + radius, dy);
141 graphics.setColor(color);
142 if (fill)
143 {
144 graphics.fill(path);
145 }
146 else
147 {
148 graphics.setStroke(new BasicStroke(0.02f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
149 graphics.draw(path);
150 }
151 }
152
153
154
155
156
157
158 private void paintBus(final Graphics2D graphics, final Color blue)
159 {
160
161 Path2D.Double path = new Path2D.Double();
162 path.moveTo(0.77, -0.07);
163 path.lineTo(0.74, -0.36);
164 path.lineTo(-0.69, -0.36);
165 path.lineTo(-0.77, -0.07);
166 path.lineTo(-0.77, 0.22);
167 path.lineTo(0.43, 0.22);
168 path.lineTo(0.77, 0.17);
169 path.lineTo(0.77, -0.07);
170 graphics.fill(path);
171
172 graphics.fill(new Ellipse2D.Double(-0.43 - 0.125, 0.22 - 0.125, 0.25, 0.25));
173 graphics.fill(new Ellipse2D.Double(0.43 - 0.125, 0.22 - 0.125, 0.25, 0.25));
174 graphics.setColor(blue);
175 graphics.setStroke(new BasicStroke(0.015f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
176 graphics.draw(new Ellipse2D.Double(-0.43 - 0.125, 0.22 - 0.125, 0.25, 0.25));
177 graphics.draw(new Ellipse2D.Double(0.43 - 0.125, 0.22 - 0.125, 0.25, 0.25));
178
179 graphics.setColor(blue);
180 path = new Path2D.Double();
181 path.moveTo(-0.52, -0.32);
182 path.lineTo(-0.66, -0.32);
183 path.lineTo(-0.73, -0.07);
184 path.lineTo(-0.52, -0.07);
185 path.lineTo(-0.52, -0.32);
186 graphics.fill(path);
187 for (double x : new double[] {-0.48, -0.23, 0.02, 0.27})
188 {
189 graphics.fill(new Rectangle.Double(x, -0.32, 0.21, 0.21));
190 }
191 path = new Path2D.Double();
192 path.moveTo(0.71, -0.32);
193 path.lineTo(0.52, -0.32);
194 path.lineTo(0.52, -0.11);
195 path.lineTo(0.73, -0.11);
196 path.lineTo(0.71, -0.32);
197 graphics.fill(path);
198 }
199
200
201
202
203
204
205
206
207
208 private void paintDiamond(final Graphics2D graphics, final double radius, final Color color, final boolean fill,
209 final boolean shadow)
210 {
211 double dx = shadow ? SHADOW_DX : 0.0;
212 double dy = shadow ? SHADOW_DY : 0.0;
213 graphics.setColor(color);
214 if (fill)
215 {
216 Path2D.Float path = new Path2D.Float();
217 path.moveTo(dx + radius, dy);
218 path.lineTo(dx, dy + radius);
219 path.lineTo(dx - radius, dy);
220 path.lineTo(dx, dy - radius);
221 path.lineTo(dx + radius, dy);
222 graphics.fill(path);
223 }
224 else
225 {
226
227 graphics.rotate(Math.PI / 4);
228 graphics.setStroke(new BasicStroke(0.04f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
229 double r = radius / Math.sqrt(2.0);
230 RoundRectangle2D.Double shape = new RoundRectangle2D.Double(-r, -r, 2.0 * r, 2.0 * r, 0.15 * r, 0.15 * r);
231 graphics.draw(shape);
232 graphics.rotate(-Math.PI / 4);
233 }
234 }
235
236
237
238
239
240
241
242
243
244 private void paintTriangle(final Graphics2D graphics, final double radius, final Color color, final boolean fill,
245 final boolean shadow)
246 {
247 double k = radius * Math.sqrt(3.0) / 3.0;
248 double g = (radius * Math.sqrt(3.0)) - k;
249 double dx = shadow ? SHADOW_DX : 0.0;
250 double dy = shadow ? SHADOW_DY : 0.0;
251 Path2D.Float path = new Path2D.Float();
252 path.moveTo(dx + 0.0, dy - k);
253 path.lineTo(dx + -radius, dy - k);
254 path.lineTo(dx + 0.0, dy + g);
255 path.lineTo(dx + radius, dy - k);
256 path.lineTo(dx + 0.0, dy - k);
257 graphics.setColor(color);
258 if (fill)
259 {
260 graphics.fill(path);
261 }
262 else
263 {
264 graphics.setStroke(new BasicStroke(0.04f, BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER));
265 graphics.draw(path);
266 }
267 }
268
269
270
271
272
273
274
275
276
277 private void paintString(final Graphics2D graphics, final String text, final Color color, final float fontSize,
278 final float dy)
279 {
280 if (graphics.getTransform().getDeterminant() > 400000)
281 {
282
283
284
285
286
287
288
289 return;
290 }
291 graphics.setColor(color);
292 int fontSizeMetrics = 100;
293 float factor = fontSize / fontSizeMetrics;
294 Font font = new Font("Arial", Font.BOLD, fontSizeMetrics).deriveFont(Map.of(TextAttribute.WIDTH, 0.67f));
295 graphics.setFont(font.deriveFont(fontSize));
296 FontMetrics metrics = graphics.getFontMetrics(font);
297 float w = metrics.stringWidth(text) * factor;
298 float d = metrics.getDescent() * factor;
299 float h = metrics.getHeight() * factor;
300 graphics.drawString(text, -w / 2.0f, dy + h / 2.0f - d);
301 }
302
303
304
305
306
307
308
309
310
311
312
313
314 public interface PriorityData extends OtsLocatable
315 {
316
317 static OtsBounds2d BOUNDS = new BoundingBox(2.0, 2.0);
318
319
320 @Override
321 default OtsBounds2d getBounds()
322 {
323 return BOUNDS;
324 }
325
326
327 @Override
328 default double getZ() throws RemoteException
329 {
330 return DrawLevel.NODE.getZ();
331 }
332
333
334
335
336
337 boolean isAllStop();
338
339
340
341
342
343 boolean isBusStop();
344
345
346
347
348
349 boolean isNone();
350
351
352
353
354
355 boolean isPriority();
356
357
358
359
360
361 boolean isStop();
362
363
364
365
366
367 boolean isYield();
368 }
369
370 }