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