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 @Override
71 public OrientedPoint2d getStartPoint()
72 {
73 return this.startPoint;
74 }
75
76 @Override
77 public OrientedPoint2d getEndPoint()
78 {
79 Point2d point = getPoint(1.0, 0.0);
80 double dirZ = this.startPoint.dirZ + this.sign * this.angle.si;
81 dirZ = dirZ > Math.PI ? dirZ - 2.0 * Math.PI : (dirZ < -Math.PI ? dirZ + 2.0 * Math.PI : 0.0);
82 return new OrientedPoint2d(point.x, point.y, dirZ);
83 }
84
85 @Override
86 public double getStartCurvature()
87 {
88 return 1.0 / this.radius;
89 }
90
91 @Override
92 public double getEndCurvature()
93 {
94 return getStartCurvature();
95 }
96
97 @Override
98 public double getStartRadius()
99 {
100 return this.radius;
101 }
102
103 @Override
104 public double getEndRadius()
105 {
106 return this.radius;
107 }
108
109
110
111
112
113
114
115 private Point2d getPoint(final double fraction, final double offset)
116 {
117 double len = this.radius - this.sign * offset;
118 double ang = this.startPoint.dirZ + this.sign * (this.angle.si * fraction);
119 double dx = this.sign * Math.cos(ang) * len;
120 double dy = this.sign * Math.sin(ang) * len;
121 return new Point2d(this.center.x + dy, this.center.y - dx);
122 }
123
124
125
126
127
128
129 private double getDirection(final double fraction)
130 {
131 double d = this.startPoint.dirZ + this.sign * (this.angle.si) * fraction;
132 while (d > Math.PI)
133 {
134 d -= (2 * Math.PI);
135 }
136 while (d < -Math.PI)
137 {
138 d += (2 * Math.PI);
139 }
140 return d;
141 }
142
143 @Override
144 public PolyLine2d flatten(final Flattener flattener)
145 {
146 Throw.whenNull(flattener, "Flattener may not be null.");
147 return flattener.flatten(new FlattableLine()
148 {
149 @Override
150 public Point2d get(final double fraction)
151 {
152 return getPoint(fraction, 0.0);
153 }
154
155 @Override
156 public double getDirection(final double fraction)
157 {
158 return ContinuousArc.this.getDirection(fraction);
159 }
160 });
161 }
162
163 @Override
164 public PolyLine2d flattenOffset(final ContinuousDoubleFunction offset, final Flattener flattener)
165 {
166 Throw.whenNull(offset, "Offset may not be null.");
167 Throw.whenNull(flattener, "Flattener may not be null.");
168 return flattener.flatten(new FlattableLine()
169 {
170 @Override
171 public Point2d get(final double fraction)
172 {
173 return getPoint(fraction, offset.apply(fraction));
174 }
175
176 @Override
177 public double getDirection(final double fraction)
178 {
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193 double phi = (ContinuousArc.this.startPoint.dirZ
194 + ContinuousArc.this.sign * (ContinuousArc.this.angle.si * fraction - Math.PI / 2));
195 double sinPhi = Math.sin(phi);
196 double cosPhi = Math.cos(phi);
197 double sPhi = ContinuousArc.this.sign * offset.apply(fraction);
198 double sPhiD = offset.getDerivative(fraction) / ContinuousArc.this.angle.si;
199 double dx = -sinPhi * (ContinuousArc.this.radius - sPhi) - cosPhi * sPhiD;
200 double dy = cosPhi * (ContinuousArc.this.radius - sPhi) - sinPhi * sPhiD;
201 return Math.atan2(ContinuousArc.this.sign * dy, ContinuousArc.this.sign * dx);
202 }
203 });
204 }
205
206 @Override
207 public double getLength()
208 {
209 return this.angle.si * this.radius;
210 }
211
212 @Override
213 public String toString()
214 {
215 return "ContinuousArc [startPoint=" + this.startPoint + ", radius=" + this.radius + ", angle=" + this.angle + ", left="
216 + (this.sign > 0.0) + "]";
217 }
218
219 }