1 package org.opentrafficsim.draw.road;
2
3 import java.awt.BasicStroke;
4 import java.awt.Color;
5 import java.awt.Graphics2D;
6 import java.awt.geom.Path2D;
7 import java.awt.image.ImageObserver;
8 import java.util.ArrayList;
9 import java.util.List;
10 import java.util.Set;
11
12 import org.djunits.value.vdouble.scalar.Length;
13 import org.djutils.draw.line.PolyLine2d;
14 import org.djutils.draw.point.DirectedPoint2d;
15 import org.djutils.draw.point.Point2d;
16 import org.opentrafficsim.base.StripeElement;
17 import org.opentrafficsim.base.geometry.DirectionalPolyLine;
18 import org.opentrafficsim.base.geometry.OtsLine2d.FractionalFallback;
19 import org.opentrafficsim.draw.DrawLevel;
20 import org.opentrafficsim.draw.LineLocatable;
21 import org.opentrafficsim.draw.OtsRenderable;
22 import org.opentrafficsim.draw.PaintPolygons;
23 import org.opentrafficsim.draw.road.StripeAnimation.StripeData;
24
25 import nl.tudelft.simulation.naming.context.Contextualized;
26
27
28
29
30
31
32
33
34
35
36 public class StripeAnimation extends OtsRenderable<StripeData>
37 {
38
39 private List<PaintData> paintDatas;
40
41
42 private Length pathOffset;
43
44
45
46
47
48
49 public StripeAnimation(final StripeData source, final Contextualized contextualized)
50 {
51 super(source, contextualized);
52 }
53
54
55
56
57
58
59 private List<PaintData> makePaths(final StripeData stripe)
60 {
61
62 List<PaintData> paintData = new ArrayList<>();
63 double width = stripe.getWidth(Length.ZERO).si;
64 double edgeOffset = .5 * width;
65 for (StripeElement element : stripe.getElements())
66 {
67 double w = element.width().si;
68 List<Point2d> path = new ArrayList<>();
69 if (element.isContinuous())
70 {
71 stripe.getCenterLine().directionalOffsetLine(edgeOffset).iterator().forEachRemaining(path::add);
72 stripe.getCenterLine().directionalOffsetLine(edgeOffset - w).reverse().iterator().forEachRemaining(path::add);
73 }
74 else if (!element.isGap())
75 {
76 double[] dashes = element.dashes().getValuesSI();
77 path.addAll(makeDashes(stripe.getCenterLine().directionalOffsetLine(edgeOffset - .5 * w),
78 stripe.getReferenceLine(), w, stripe.getDashOffset().si, dashes));
79 }
80 edgeOffset -= w;
81
82 if (!path.isEmpty())
83 {
84 paintData.add(new PaintData(PaintPolygons.getPaths(getSource().getLocation(), path), element.color()));
85 }
86 }
87 return paintData;
88 }
89
90
91
92
93
94
95
96
97
98
99
100
101 private List<Point2d> makeDashes(final DirectionalPolyLine centerLine, final PolyLine2d referenceLine, final double width,
102 final double startOffset, final double[] dashes)
103 {
104 double period = 0;
105 for (double length : dashes)
106 {
107 if (length < 0)
108 {
109 throw new Error("Bad pattern - on or off length is < 0");
110 }
111 period += length;
112 }
113 if (period <= 0)
114 {
115 throw new Error("Bad pattern - repeat period length is 0");
116 }
117
118 double referenceLength = referenceLine.getLength();
119 double position = -startOffset + dashes[0];
120 int phase = 1;
121 ArrayList<Point2d> result = new ArrayList<>();
122 boolean first = true;
123 boolean sameLine = centerLine.getPointList().equals(referenceLine.getPointList());
124 while (position < referenceLength)
125 {
126 double nextBoundary = position + dashes[phase++ % dashes.length];
127 if (nextBoundary > 0)
128 {
129 if (!first)
130 {
131 result.add(PaintPolygons.NEWPATH);
132 }
133 first = false;
134 if (position < 0)
135 {
136 position = 0;
137 }
138 double endPosition = nextBoundary;
139 if (endPosition > referenceLength)
140 {
141 endPosition = referenceLength;
142 }
143
144 double fraction1 = position / referenceLength;
145 double fraction2 = endPosition / referenceLength;
146 if (!sameLine)
147 {
148
149 DirectedPoint2d p1 = referenceLine.getLocationFraction(fraction1);
150 DirectedPoint2d p2 = referenceLine.getLocationFraction(fraction2);
151 fraction1 = centerLine.projectFractional(p1.x, p1.y, FractionalFallback.ENDPOINT);
152 fraction2 = centerLine.projectFractional(p2.x, p2.y, FractionalFallback.ENDPOINT);
153 }
154 DirectionalPolyLine dashCenter = centerLine.extractFractional(fraction1, fraction2);
155
156
157 dashCenter.directionalOffsetLine(width / 2).iterator().forEachRemaining(result::add);
158 dashCenter.directionalOffsetLine(-width / 2).reverse().iterator().forEachRemaining(result::add);
159 }
160 position = nextBoundary + dashes[phase++ % dashes.length];
161 }
162 return result;
163 }
164
165 @Override
166 public final void paint(final Graphics2D graphics, final ImageObserver observer)
167 {
168 update();
169 if (this.paintDatas != null)
170 {
171 for (PaintData paintData : this.paintDatas)
172 {
173 setRendering(graphics);
174 graphics.setStroke(new BasicStroke(2.0f));
175 PaintPolygons.paintPaths(graphics, paintData.color(), paintData.path(), true);
176 resetRendering(graphics);
177 }
178 }
179 }
180
181
182
183
184 private void update()
185 {
186 if (!getSource().getDashOffset().equals(this.pathOffset))
187 {
188 this.paintDatas = makePaths(getSource());
189 this.pathOffset = getSource().getDashOffset();
190 }
191 }
192
193 @Override
194 public final String toString()
195 {
196 return "StripeAnimation [source = " + getSource().toString() + ", paintDatas=" + this.paintDatas + "]";
197 }
198
199
200
201
202
203
204
205
206
207
208 public interface StripeData extends LineLocatable
209 {
210 @Override
211 DirectedPoint2d getLocation();
212
213
214
215
216
217 DirectionalPolyLine getCenterLine();
218
219
220
221
222
223 PolyLine2d getReferenceLine();
224
225
226
227
228
229 List<StripeElement> getElements();
230
231
232
233
234
235 Length getDashOffset();
236
237
238
239
240
241
242 Length getWidth(Length position);
243
244 @Override
245 default double getZ()
246 {
247 return DrawLevel.MARKING.getZ();
248 }
249 }
250
251
252
253
254
255
256 private record PaintData(Set<Path2D.Float> path, Color color)
257 {
258 }
259 }