1 package org.opentrafficsim.animation.gtu;
2
3 import java.awt.BasicStroke;
4 import java.awt.Color;
5 import java.awt.Graphics2D;
6 import java.awt.RenderingHints;
7 import java.awt.geom.AffineTransform;
8 import java.awt.geom.Ellipse2D;
9 import java.awt.geom.Line2D;
10 import java.awt.image.ImageObserver;
11 import java.util.Set;
12
13 import org.djunits.value.vdouble.scalar.Angle;
14 import org.djunits.value.vdouble.scalar.Duration;
15 import org.djunits.value.vdouble.scalar.Length;
16 import org.djutils.exceptions.Throw;
17 import org.opentrafficsim.animation.BoundsPaintScale;
18 import org.opentrafficsim.animation.Colors;
19 import org.opentrafficsim.animation.OtsRenderable;
20 import org.opentrafficsim.animation.gtu.PerceptionAnimation.PerceptionData;
21 import org.opentrafficsim.animation.gtu.PerceptionAnimation.PerceptionData.ChannelData;
22 import org.opentrafficsim.animation.gtu.PerceptionAnimation.PerceptionData.ChannelRadius;
23 import org.opentrafficsim.base.geometry.OtsShape;
24
25 import nl.tudelft.simulation.naming.context.Contextualized;
26
27
28
29
30
31
32
33
34
35 public class PerceptionAnimation extends OtsRenderable<PerceptionData>
36 {
37
38
39 private static final double MAX_RADIUS = 1.425;
40
41
42 private static final double CENTER_RADIUS = 3.0;
43
44
45 private static final double CENTER_RADIUS_OBJECTS = 6.0;
46
47
48 private static final float LINE_WIDTH = 0.15f;
49
50
51 private static final BoundsPaintScale SCALE =
52 new BoundsPaintScale(new double[] {0.0, 0.25, 0.5, 0.75, 1.0}, Colors.GREEN_RED_DARK);
53
54
55
56
57
58
59 public PerceptionAnimation(final PerceptionData perceptionData, final Contextualized contextualized)
60 {
61 super(perceptionData, contextualized);
62 }
63
64 @Override
65 public void paint(final Graphics2D graphics, final ImageObserver observer)
66 {
67 if (getSource().getChannels() == null || getSource().getChannels().isEmpty())
68 {
69 return;
70 }
71
72 AffineTransform transform = graphics.getTransform();
73 graphics.setStroke(new BasicStroke(LINE_WIDTH));
74 graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
75
76 boolean hasInVehicle = getSource().getChannels().stream().anyMatch((cd) -> cd.radius.equals(ChannelRadius.ZERO));
77 double dx = getSource().getDeltaCenterX().si;
78 for (ChannelData channelData : getSource().getChannels())
79 {
80 double radius = channelData.radius().equals(ChannelRadius.NEAR) ? CENTER_RADIUS
81 : (channelData.radius().equals(ChannelRadius.FAR) ? CENTER_RADIUS_OBJECTS : 0.0);
82 boolean drawLine = (channelData.radius().equals(ChannelRadius.NEAR) && !hasInVehicle)
83 || channelData.radius().equals(ChannelRadius.FAR);
84 drawAttentionCircle(graphics, dx, channelData.attention(), channelData.perceptionDelay().si, channelData.angle().si,
85 radius, drawLine);
86 graphics.setTransform(transform);
87 }
88
89 }
90
91
92
93
94
95
96
97
98
99
100
101 private static void drawAttentionCircle(final Graphics2D graphics, final double dx, final double attention,
102 final double perceptionDelay, final double angle, final double radius, final boolean drawLine)
103 {
104
105 graphics.translate(dx, 0.0);
106 graphics.rotate(-angle, 0.0, 0.0);
107
108
109 if (drawLine)
110 {
111 graphics.setColor(Color.GRAY);
112 graphics.draw(new Line2D.Double(0.0, 0.0, radius - MAX_RADIUS - LINE_WIDTH, 0.0));
113 }
114
115
116 Color color = SCALE.getPaint(Math.min(1.0, perceptionDelay));
117 graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 48));
118 graphics.fill(new Ellipse2D.Double(radius - MAX_RADIUS, -MAX_RADIUS, 2.0 * MAX_RADIUS, 2.0 * MAX_RADIUS));
119
120
121 graphics.setColor(color);
122 double r = Math.sqrt(attention);
123 graphics.fill(
124 new Ellipse2D.Double(radius - r * MAX_RADIUS, -r * MAX_RADIUS, 2.0 * r * MAX_RADIUS, 2.0 * r * MAX_RADIUS));
125
126
127 graphics.setColor(Color.GRAY);
128 float lineWidth = LINE_WIDTH - 0.02f;
129 graphics.draw(new Ellipse2D.Double(radius - MAX_RADIUS - .5 * lineWidth, -MAX_RADIUS - .5 * lineWidth,
130 2.0 * MAX_RADIUS + lineWidth, 2.0 * MAX_RADIUS + lineWidth));
131 }
132
133
134
135
136 public interface PerceptionData extends OtsShape
137 {
138
139
140
141
142
143 Length getDeltaCenterX();
144
145
146
147
148
149 Set<ChannelData> getChannels();
150
151
152
153
154
155
156
157
158 record ChannelData(Angle angle, ChannelRadius radius, double attention, Duration perceptionDelay)
159 {
160
161
162
163
164
165
166
167 public ChannelData
168 {
169 Throw.whenNull(angle, "angle");
170 Throw.whenNull(radius, "radius");
171 Throw.whenNull(attention, "attention");
172 Throw.whenNull(perceptionDelay, "perceptionDelay");
173 }
174 }
175
176
177
178
179 enum ChannelRadius
180 {
181
182 ZERO,
183
184
185 NEAR,
186
187
188 FAR;
189 }
190
191 }
192
193 }