1 package org.opentrafficsim.core.geometry;
2
3 import org.djunits.value.vdouble.scalar.Angle;
4 import org.djutils.draw.line.PolyLine2d;
5 import org.djutils.draw.point.OrientedPoint2d;
6 import org.djutils.draw.point.Point2d;
7 import org.djutils.exceptions.Throw;
8
9
10
11
12
13
14
15
16
17 public class ContinuousArc implements ContinuousLine
18 {
19
20
21 private final OrientedPoint2d startPoint;
22
23
24 private final double radius;
25
26
27 private final Angle angle;
28
29
30 private double sign;
31
32
33 private final Point2d center;
34
35
36
37
38
39
40
41
42 public ContinuousArc(final OrientedPoint2d startPoint, final double radius, final boolean left, final double length)
43 {
44 this(startPoint, radius, left, Angle.instantiateSI(
45 Throw.when(length, length <= 0.0, IllegalArgumentException.class, "Length must be above 0.") / radius));
46 }
47
48
49
50
51
52
53
54
55 public ContinuousArc(final OrientedPoint2d startPoint, final double radius, final boolean left, final Angle angle)
56 {
57 Throw.whenNull(startPoint, "Start point may not be null.");
58 Throw.when(radius < 0.0, IllegalArgumentException.class, "Radius must be positive.");
59 Throw.when(angle.si < 0.0, IllegalArgumentException.class, "Angle must be positive.");
60 this.startPoint = startPoint;
61 this.radius = radius;
62 this.sign = left ? 1.0 : -1.0;
63 this.angle = angle;
64 double dx = Math.cos(startPoint.dirZ) * this.sign * radius;
65 double dy = Math.sin(startPoint.dirZ) * this.sign * radius;
66
67 this.center = new Point2d(startPoint.x - dy, startPoint.y + dx);
68 }
69
70
71 @Override
72 public OrientedPoint2d getStartPoint()
73 {
74 return this.startPoint;
75 }
76
77
78 @Override
79 public OrientedPoint2d getEndPoint()
80 {
81 Point2d point = getPoint(1.0, 0.0);
82 double dirZ = this.startPoint.dirZ + this.sign * this.angle.si;
83 dirZ = dirZ > Math.PI ? dirZ - 2.0 * Math.PI : (dirZ < -Math.PI ? dirZ + 2.0 * Math.PI : 0.0);
84 return new OrientedPoint2d(point.x, point.y, dirZ);
85 }
86
87
88 @Override
89 public double getStartCurvature()
90 {
91 return 1.0 / this.radius;
92 }
93
94
95 @Override
96 public double getEndCurvature()
97 {
98 return getStartCurvature();
99 }
100
101
102 @Override
103 public double getStartRadius()
104 {
105 return this.radius;
106 }
107
108
109 @Override
110 public double getEndRadius()
111 {
112 return this.radius;
113 }
114
115
116
117
118
119
120
121 private Point2d getPoint(final double fraction, final double offset)
122 {
123 double len = this.radius - this.sign * offset;
124 double angle = this.startPoint.dirZ + this.sign * (this.angle.si * fraction);
125 double dx = this.sign * Math.cos(angle) * len;
126 double dy = this.sign * Math.sin(angle) * len;
127 return new Point2d(this.center.x + dy, this.center.y - dx);
128 }
129
130
131
132
133
134
135 private double getDirection(final double fraction)
136 {
137 double d = this.startPoint.dirZ + this.sign * (this.angle.si) * fraction;
138 while (d > Math.PI)
139 {
140 d -= (2 * Math.PI);
141 }
142 while (d < -Math.PI)
143 {
144 d += (2 * Math.PI);
145 }
146 return d;
147 }
148
149
150 @Override
151 public PolyLine2d flatten(final Flattener flattener)
152 {
153 Throw.whenNull(flattener, "Flattener may not be null.");
154 return flattener.flatten(new FlattableLine()
155 {
156
157 @Override
158 public Point2d get(final double fraction)
159 {
160 return getPoint(fraction, 0.0);
161 }
162
163
164 @Override
165 public double getDirection(final double fraction)
166 {
167 return ContinuousArc.this.getDirection(fraction);
168 }
169 });
170 }
171
172
173 @Override
174 public PolyLine2d flattenOffset(final FractionalLengthData offsets, final Flattener flattener)
175 {
176 Throw.whenNull(offsets, "Offsets may not be null.");
177 Throw.whenNull(flattener, "Flattener may not be null.");
178 return flattener.flatten(new FlattableLine()
179 {
180
181 @Override
182 public Point2d get(final double fraction)
183 {
184 return getPoint(fraction, offsets.get(fraction));
185 }
186
187
188 @Override
189 public double getDirection(final double fraction)
190 {
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205 double phi = (ContinuousArc.this.startPoint.dirZ
206 + ContinuousArc.this.sign * (ContinuousArc.this.angle.si * fraction - Math.PI / 2));
207 double sinPhi = Math.sin(phi);
208 double cosPhi = Math.cos(phi);
209 double sPhi = ContinuousArc.this.sign * offsets.get(fraction);
210 double sPhiD = offsets.getDerivative(fraction) / ContinuousArc.this.angle.si;
211 double dx = -sinPhi * (ContinuousArc.this.radius - sPhi) - cosPhi * sPhiD;
212 double dy = cosPhi * (ContinuousArc.this.radius - sPhi) - sinPhi * sPhiD;
213 return Math.atan2(ContinuousArc.this.sign * dy, ContinuousArc.this.sign * dx);
214 }
215 });
216 }
217
218
219 @Override
220 public double getLength()
221 {
222 return this.angle.si * this.radius;
223 }
224
225
226 @Override
227 public String toString()
228 {
229 return "ContinuousArc [startPoint=" + this.startPoint + ", radius=" + this.radius + ", angle=" + this.angle + ", left="
230 + (this.sign > 0.0) + "]";
231 }
232
233 }