1 package org.opentrafficsim.animation;
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.BufferedImage;
11 import java.awt.image.ImageObserver;
12 import java.io.File;
13 import java.io.IOException;
14 import java.util.Optional;
15 import java.util.Set;
16
17 import javax.imageio.ImageIO;
18
19 import org.djunits.value.vdouble.scalar.Duration;
20 import org.djutils.draw.line.Polygon2d;
21 import org.djutils.draw.point.DirectedPoint2d;
22 import org.djutils.draw.point.Point2d;
23 import org.djutils.math.AngleUtil;
24 import org.opentrafficsim.animation.PerceptionAnimation.ChannelAttention;
25 import org.opentrafficsim.base.geometry.OtsShape;
26 import org.opentrafficsim.draw.BoundsPaintScale;
27 import org.opentrafficsim.draw.Colors;
28 import org.opentrafficsim.draw.DrawLevel;
29 import org.opentrafficsim.draw.OtsRenderable;
30 import org.opentrafficsim.road.gtu.lane.LaneBasedGtu;
31 import org.opentrafficsim.road.gtu.lane.perception.mental.Mental;
32 import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelFuller;
33 import org.opentrafficsim.road.gtu.lane.perception.mental.channel.ChannelTask;
34 import org.opentrafficsim.road.network.lane.conflict.Conflict;
35
36
37
38
39
40
41
42
43
44 public class PerceptionAnimation extends OtsRenderable<ChannelAttention>
45 {
46
47
48 private static final double MAX_RADIUS = 1.425;
49
50
51 private static final double CENTER_RADIUS = 3.0;
52
53
54 private static final double CENTER_RADIUS_OBJECTS = 6.0;
55
56
57 private static final float LINE_WIDTH = 0.15f;
58
59
60 private static final BoundsPaintScale SCALE =
61 new BoundsPaintScale(new double[] {0.0, 0.25, 0.5, 0.75, 1.0}, Colors.GREEN_RED_DARK);
62
63
64
65
66
67 public PerceptionAnimation(final LaneBasedGtu gtu)
68 {
69 super(new ChannelAttention(gtu), gtu.getSimulator());
70 }
71
72 @Override
73 public void paint(final Graphics2D graphics, final ImageObserver observer)
74 {
75 LaneBasedGtu gtu = getSource().getGtu();
76 Optional<Mental> mental = gtu.getTacticalPlanner().getPerception().getMental();
77 if (mental.isPresent() && mental.get() instanceof ChannelFuller fuller)
78 {
79 AffineTransform transform = graphics.getTransform();
80 graphics.setStroke(new BasicStroke(LINE_WIDTH));
81 graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
82
83 Set<Object> channels = fuller.getChannels();
84 boolean hasInVehicle = channels.contains(ChannelTask.IN_VEHICLE);
85 for (Object channel : channels)
86 {
87 double attention = fuller.getAttention(channel);
88 Duration perceptionDelay = fuller.getPerceptionDelay(channel);
89 double angle;
90 double radius = CENTER_RADIUS;
91 boolean drawLine = !hasInVehicle;
92 if (ChannelTask.LEFT.equals(channel))
93 {
94 angle = Math.PI / 2.0;
95 }
96 else if (ChannelTask.FRONT.equals(channel))
97 {
98 angle = 0.0;
99 }
100 else if (ChannelTask.RIGHT.equals(channel))
101 {
102 angle = -Math.PI / 2.0;
103 }
104 else if (ChannelTask.REAR.equals(channel))
105 {
106 angle = Math.PI;
107 }
108 else if (ChannelTask.IN_VEHICLE.equals(channel))
109 {
110 angle = 0.0;
111 radius = 0.0;
112 }
113 else if (channel instanceof OtsShape object)
114 {
115 Point2d point;
116 if (channel instanceof Conflict conflict)
117 {
118
119 double x = conflict.getOtherConflict().getLongitudinalPosition().si - 25.0;
120 point = conflict.getOtherConflict().getLane().getCenterLine().getLocationExtendedSI(x < 0.0 ? 0.0 : x);
121 }
122 else
123 {
124 point = object.getLocation();
125 }
126 angle = AngleUtil.normalizeAroundZero(gtu.getLocation().directionTo(point) - gtu.getLocation().dirZ);
127 radius = CENTER_RADIUS_OBJECTS;
128 drawLine = true;
129 }
130 else
131 {
132 continue;
133 }
134 drawAttentionCircle(graphics, gtu.getCenter().dx().si, attention, perceptionDelay, angle, radius, drawLine);
135 graphics.setTransform(transform);
136 }
137 }
138
139 }
140
141
142
143
144
145
146
147
148
149
150
151 private static void drawAttentionCircle(final Graphics2D graphics, final double dx, final double attention,
152 final Duration perceptionDelay, final double angle, final double radius, final boolean drawLine)
153 {
154
155 graphics.translate(dx, 0.0);
156 graphics.rotate(-angle, 0.0, 0.0);
157
158
159 if (drawLine)
160 {
161 graphics.setColor(Color.GRAY);
162 graphics.draw(new Line2D.Double(0.0, 0.0, radius - MAX_RADIUS - LINE_WIDTH, 0.0));
163 }
164
165
166 Color color = SCALE.getPaint(Math.min(1.0, perceptionDelay.si));
167 graphics.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), 48));
168 graphics.fill(new Ellipse2D.Double(radius - MAX_RADIUS, -MAX_RADIUS, 2.0 * MAX_RADIUS, 2.0 * MAX_RADIUS));
169
170
171 graphics.setColor(color);
172 double r = Math.sqrt(attention);
173 graphics.fill(
174 new Ellipse2D.Double(radius - r * MAX_RADIUS, -r * MAX_RADIUS, 2.0 * r * MAX_RADIUS, 2.0 * r * MAX_RADIUS));
175
176
177 graphics.setColor(Color.GRAY);
178 float lineWidth = LINE_WIDTH - 0.02f;
179 graphics.draw(new Ellipse2D.Double(radius - MAX_RADIUS - .5 * lineWidth, -MAX_RADIUS - .5 * lineWidth,
180 2.0 * MAX_RADIUS + lineWidth, 2.0 * MAX_RADIUS + lineWidth));
181 }
182
183
184
185
186
187
188 public static void main(final String[] args) throws IOException
189 {
190 BufferedImage im = new BufferedImage(24, 24, BufferedImage.TYPE_INT_ARGB);
191 Graphics2D g = (Graphics2D) im.getGraphics();
192 g.setStroke(new BasicStroke(LINE_WIDTH));
193 g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
194 g.translate(12.0, 12.0);
195 g.scale(3.2, 3.2);
196 AffineTransform transform = g.getTransform();
197 drawAttentionCircle(g, 0.0, 0.8, Duration.ZERO, 0.25 * Math.PI, CENTER_RADIUS, true);
198 g.setTransform(transform);
199 drawAttentionCircle(g, 0.0, 0.4, Duration.ofSI(0.2), 0.75 * Math.PI, CENTER_RADIUS, true);
200 g.setTransform(transform);
201 drawAttentionCircle(g, 0.0, 0.2, Duration.ofSI(0.4), -0.25 * Math.PI, CENTER_RADIUS, true);
202 g.setTransform(transform);
203 drawAttentionCircle(g, 0.0, 0.1, Duration.ofSI(0.8), -0.75 * Math.PI, CENTER_RADIUS, true);
204 File outputFile = new File(".." + File.separator + "ots-swing" + File.separator + "src" + File.separator + "main"
205 + File.separator + "resources" + File.separator + "icons" + File.separator + "Perception24.png");
206 ImageIO.write(im, "png", outputFile);
207 System.out.println("Icon written to: " + outputFile.getAbsolutePath());
208 }
209
210
211
212
213 public static class ChannelAttention implements OtsShape
214 {
215
216 private final LaneBasedGtu gtu;
217
218
219
220
221
222 public ChannelAttention(final LaneBasedGtu gtu)
223 {
224 this.gtu = gtu;
225 }
226
227
228
229
230
231 public LaneBasedGtu getGtu()
232 {
233 return this.gtu;
234 }
235
236 @Override
237 public DirectedPoint2d getLocation()
238 {
239 return this.gtu.getLocation();
240 }
241
242 @Override
243 public double getZ()
244 {
245 return DrawLevel.LABEL.getZ();
246 }
247
248 @Override
249 public Polygon2d getRelativeContour()
250 {
251 return this.gtu.getRelativeContour();
252 }
253
254 }
255
256 }