@@ -24,7 +24,7 @@ struct ConstantOffset{T, S} <: OffsetSegment{T, S}
2424end
2525
2626"""
27- struct ConstantOffset {T,S} <: OffsetSegment{T,S}
27+ struct GeneralOffset {T,S} <: OffsetSegment{T,S}
2828"""
2929struct GeneralOffset{T, S} <: OffsetSegment{T, S}
3030 seg:: S
6767
6868function tangent(s:: OffsetSegment , t)
6969 # calculate derivative w.r.t. s.seg arclength
70- # d/dt (s(t) + offset(t) * normal(t))
70+ # d/dt (s.seg (t) + offset(s, t) * normal(s.seg, t))
7171 base_dir = direction(s. seg, t)
7272 base_tangent = Point(cos(base_dir), sin(base_dir))
7373 base_normal = Point(- base_tangent. y, base_tangent. x)
@@ -84,38 +84,46 @@ function signed_curvature(seg::Segment, s)
8484end
8585
8686function curvatureradius(seg:: ConstantOffset , s)
87- r = curvatureradius(seg. seg, s)
87+ r = curvatureradius(seg. seg, s) # Ignore offset * dκds term
8888 return r - getoffset(seg, s)
8989end
9090
9191function curvatureradius(seg:: GeneralOffset , s)
92- # This one is only approximate for BSpline offsets
93- # let s be seg.seg arclength, let t be seg arclength
94- # tangent(seg, s) defined above is dseg/ds
95- # ||d^2/dt ^2 seg(s)|| = ||d/dt tangent(seg, s) ds/dt ||
96- # ||d/ds tangent(seg, s)|| || ds/dt||^2
97- # Where ||ds/dt || = ||tangent(seg, s) || = 1 for non-offset segments
92+ # Only approximate for BSpline offsets (ignores offset * dκds term)
93+ # let s (above argument) be seg.seg arclength, let l be seg arclength
94+ # g is dseg/ds, defined above as `tangent(seg, s)`
95+ # ||d^2/dl ^2 seg(s)|| = ||d/dl (g ds/dl) ||
96+ # ||( d/ds g) ( ds/dl)^2 + g d^2s/dl^2||
97+ # Where ||ds/dl || = 1/||g || = 1 for non-offset segments
9898 # But needs a correction for offsets
99- # dt/ds = κ n # Sorry t is tangent for the rest of this comment
99+ # dt/ds = κ n # t is tangent, n normal, κ curvature of base curve
100100 # dn/ds = -κ t
101- # || κ n + (d2_offset n - κ t d_offset) - d_offset κ t - offset κ^2 n - offset t dκ/ds ||
101+ # ||( κ n + (d2_offset n - κ t d_offset) - d_offset κ t - offset κ^2 n - offset t dκ/ds)(ds/dl)^2 + tangent(seg, s) d^2s/dl^2 ||
102102
103103 r = curvatureradius(seg. seg, s)
104- reparam = 1 / (sqrt((1 - getoffset(seg, s) / r)^ 2 + offset_derivative(seg, s)^ 2 ))
104+ ds_dl = 1 / (sqrt((1 - getoffset(seg, s) / r)^ 2 + offset_derivative(seg, s)^ 2 ))
105105 base_dir = direction(seg. seg, s)
106106 base_tangent = Point(cos(base_dir), sin(base_dir)) # True for non-offset segments (arclength parameterized)
107107 base_normal = Point(- base_tangent. y, base_tangent. x)
108108
109109 d_offset = offset_derivative(seg, s)
110110 d2_offset = ForwardDiff. derivative(s_ -> offset_derivative(seg, s_), s)
111+ d2s_dl2 =
112+ (- 1 / 2 ) *
113+ ds_dl^ 3 *
114+ (
115+ - 2 * (offset_derivative(seg, s) / r) * (1 - getoffset(seg, s) / r) +
116+ 2 * offset_derivative(seg, s) * d2_offset
117+ )
118+
111119 d2_seg =
112120 (
113121 (1 / r) * base_normal +
114122 d2_offset * base_normal +
115123 - 2 * (1 / r) * base_tangent * d_offset +
116124 - ((1 / r) * base_normal) * ((1 / r) * getoffset(seg, s))
117- # - getoffset(seg, s) * base_tangent * dκds # Nonzero for BSplines but basically fine to ignore
118- ) * reparam ^ 2
125+ # - getoffset(seg, s) * base_tangent * dκds(seg.seg, s) # Nonzero for BSplines but basically fine to ignore
126+ ) * ds_dl ^ 2 + tangent(seg, s) * d2s_dl2
119127 return sign(r) / norm(d2_seg)
120128end
121129
174182function arclength(f:: ConstantOffset{T, BSpline{T}} , t1:: Real = 1.0 ; t0:: Real = 0.0 ) where {T}
175183 check_interval(f, t0, t1)
176184 l_orig = arclength(f. seg, t1; t0= t0)
185+ # Constant offset just changes tangent component of gradient
186+ # So we can just add the extra contribution due to curvature
177187 G = StaticArrays. @MVector [zero(Point{T})]
178188 H = StaticArrays. @MVector [zero(Point{T})]
179189 l_extra = f. offset * quadgk(t -> _curvature_arclength!(f. seg, G, H, t), t0, t1)[1 ]
@@ -183,23 +193,23 @@ end
183193# For general offsets we also have to add the contribution from varying offset
184194function arclength(f:: GeneralOffset{T, BSpline{T}} , t1:: Real = 1.0 ; t0:: Real = 0.0 ) where {T}
185195 check_interval(f, t0, t1)
186- # If we unwind the curvature, each offset segment will have length (sqrt(1+x^2) ds)
187- # where x is the derivative of the offset
188- l_orig = quadgk(
189- s -> sqrt(1 + (ForwardDiff. derivative(f. offset, s))^ 2 ),
190- t_to_arclength(f. seg, t0),
191- t_to_arclength(f. seg, t1)
192- )[1 ]
193- # Now add curvature contribution as usual
194196 G = StaticArrays. @MVector [zero(Point{T})]
195197 H = StaticArrays. @MVector [zero(Point{T})]
196- l_extra = quadgk(
198+ # Each offset segment will have length (sqrt(s′^2 +x^2) ds)
199+ # where x is the derivative of the offset (normal component of arclength)
200+ # and s′ is the curvature*offset contribution (tangent arclength)
201+ l = quadgk(
197202 t ->
198- _curvature_arclength!(f. seg, G, H, t) * getoffset(f, t_to_arclength(f. seg, t)),
203+ sqrt(
204+ (
205+ 1 -
206+ _curvature!(f. seg, G, H, t) * getoffset(f, t_to_arclength(f. seg, t))
207+ )^ 2 + (offset_derivative(f, t_to_arclength(f. seg, t)))^ 2
208+ ) * dsdt(t, f. seg. r),
199209 t0,
200210 t1
201211 )[1 ]
202- return l_orig + l_extra # + because of _curvature_arclength! sign convention
212+ return l
203213end
204214
205215function _curvature_arclength!(b:: BSpline , G, H, t)
@@ -211,6 +221,13 @@ function _curvature_arclength!(b::BSpline, G, H, t)
211221 return uconvert(NoUnits, (G[1 ]. y * H[1 ]. x - G[1 ]. x * H[1 ]. y) / norm(G[1 ])^ 2 )
212222end
213223
224+ function _curvature!(b:: BSpline , G, H, t)
225+ Paths. Interpolations. gradient!(G, b. r, t)
226+ Paths. Interpolations. hessian!(H, b. r, t)
227+ return uconvert(NoUnits, (G[1 ]. x * H[1 ]. y - G[1 ]. y * H[1 ]. x) / norm(G[1 ])^ 2 ) /
228+ norm(G[1 ])
229+ end
230+
214231function _split(seg:: ConstantOffset , x)
215232 segs = split(seg. seg, x)
216233 return offset(segs[1 ], seg. offset), offset(segs[2 ], seg. offset)
0 commit comments