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