@@ -9,8 +9,9 @@ module Geometry.Bezier
9
9
, bezierS
10
10
11
11
-- * Subdividing
12
- , bezierSubdivideT
13
- , bezierSubdivideS
12
+ , bezierSubdivideEquiparametric
13
+ , bezierSubdivideEquidistant
14
+ , bezierSubdivideCasteljau
14
15
15
16
-- * Interpolation
16
17
, bezierSmoothen
@@ -41,6 +42,26 @@ import Numerics.LinearEquationSystem
41
42
42
43
43
44
45
+ -- $references
46
+ --
47
+ -- == Arc length parameterization
48
+ --
49
+ -- * Moving Along a Curve with Specified Speed (2019)
50
+ -- by David Eberly
51
+ -- https://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf
52
+ --
53
+ -- == Smoothening
54
+ --
55
+ -- * Cubic Bézier Splines
56
+ -- by Michael Joost
57
+ -- https://www.michael-joost.de/bezierfit.pdf
58
+ --
59
+ -- * Building Smooth Paths Using Bézier Curves
60
+ -- by Stuart Kent
61
+ -- https://www.stkent.com/2015/07/03/building-smooth-paths-using-bezier-curves.html
62
+
63
+
64
+
44
65
-- | Cubic Bezier curve, defined by start, first/second control points, and end.
45
66
data Bezier = Bezier ! Vec2 ! Vec2 ! Vec2 ! Vec2 deriving (Eq , Ord , Show )
46
67
@@ -144,20 +165,20 @@ bezierLength bezier = retryExponentiallyUntilPrecision (integrateSimpson13 f 0 1
144
165
-- | Trace a 'Bezier' curve with a number of points, using the polynomial curve
145
166
-- parameterization. This is very fast, but leads to unevenly spaced points.
146
167
--
147
- -- For subdivision by arc length, use 'bezierSubdivideS '.
148
- bezierSubdivideT
168
+ -- For subdivision by arc length, use 'bezierSubdivideEquidistant '.
169
+ bezierSubdivideEquiparametric
149
170
:: Int
150
171
-> Bezier
151
172
-> [Vec2 ]
152
- bezierSubdivideT n bz = map (bezierT bz) points
173
+ bezierSubdivideEquiparametric n bz = map (bezierT bz) points
153
174
where
154
175
points = map (\ x -> fromIntegral x / fromIntegral (n- 1 )) [0 .. n- 1 ]
155
176
156
177
-- | Trace a 'Bezier' curve with a number of evenly spaced points by arc length.
157
- -- This is much more expensive than 'bezierSubdivideT ', but may be desirable for
178
+ -- This is much more expensive than 'bezierSubdivideEquiparametric ', but may be desirable for
158
179
-- aesthetic purposes.
159
180
--
160
- -- Here it is alongside 'bezierSubdivideT ':
181
+ -- Here it is alongside 'bezierSubdivideEquiparametric ':
161
182
--
162
183
-- <<docs/haddock/Geometry/Bezier/subdivide_s_t_comparison.svg>>
163
184
--
@@ -167,8 +188,8 @@ bezierSubdivideT n bz = map (bezierT bz) points
167
188
-- let curve = let curveRaw = transform (rotate (deg (-30))) (Bezier (Vec2 0 0) (Vec2 1 5) (Vec2 2.5 (-1)) (Vec2 3 3))
168
189
-- fitToBox = transform (transformBoundingBox curveRaw (Vec2 10 10, Vec2 290 90) (TransformBBSettings FitWidthHeight IgnoreAspect FitAlignCenter))
169
190
-- in fitToBox curveRaw
170
- -- evenlySpaced = bezierSubdivideS 16 curve
171
- -- unevenlySpaced = bezierSubdivideT 16 curve
191
+ -- evenlySpaced = bezierSubdivideEquidistant 16 curve
192
+ -- unevenlySpaced = bezierSubdivideEquiparametric 16 curve
172
193
-- offsetBelow :: Transform geo => geo -> geo
173
194
-- offsetBelow = transform (translate (Vec2 0 50))
174
195
-- cairoScope $ do
@@ -189,8 +210,8 @@ bezierSubdivideT n bz = map (bezierT bz) points
189
210
-- cairoScope (setColor (black `withOpacity` 0.2) >> connect e u)
190
211
-- :}
191
212
-- Generated file: size 17KB, crc32: 0x7c147951
192
- bezierSubdivideS :: Int -> Bezier -> [Vec2 ]
193
- bezierSubdivideS n bz = map bezier distances
213
+ bezierSubdivideEquidistant :: Int -> Bezier -> [Vec2 ]
214
+ bezierSubdivideEquidistant n bz = map bezier distances
194
215
where
195
216
196
217
-- The step width should correlate with the length of the curve to get a decent
@@ -246,23 +267,65 @@ s_to_t_lut_ode bz ds = LookupTable1 (sol_to_vec sol)
246
267
t0 = 0
247
268
s0 = 0
248
269
249
- -- $references
270
+ -- | Approximage a Bezier curve with line segments up to a certain precision, using
271
+ -- relatively few points.
250
272
--
251
- -- == Arc length parameterization
252
- --
253
- -- * Moving Along a Curve with Specified Speed (2019)
254
- -- by David Eberly
255
- -- https://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf
273
+ -- The idea behind Casteljau subdivision is that each Bézier curve can be exactly
274
+ -- subdivided into two Bézier curves (of same degree). This is done recursively, in
275
+ -- this implementation (and commonly) in the middle of the curve. Once a curve
276
+ -- segment is flat enough (given by the tolerance parameter), it is simply rendered
277
+ -- as a line.
256
278
--
257
- -- == Smoothening
279
+ -- <<docs/haddock/Geometry/Bezier/bezierSubdivideCasteljau.svg>>
258
280
--
259
- -- * Cubic Bézier Splines
260
- -- by Michael Joost
261
- -- https://www.michael-joost.de/bezierfit.pdf
262
- --
263
- -- * Building Smooth Paths Using Bézier Curves
264
- -- by Stuart Kent
265
- -- https://www.stkent.com/2015/07/03/building-smooth-paths-using-bezier-curves.html
281
+ -- === __(image code)__
282
+ -- >>> :{
283
+ -- haddockRender "Geometry/Bezier/bezierSubdivideCasteljau.svg" 500 330 $ do
284
+ -- let curve = let curveRaw = transform (rotate (deg (-30))) (Bezier (Vec2 0 0) (Vec2 1 5) (Vec2 2.5 (-1)) (Vec2 3 3))
285
+ -- fitToBox = transform (transformBoundingBox curveRaw (shrinkBoundingBox 10 [zero, Vec2 500 200]) (TransformBBSettings FitWidthHeight IgnoreAspect FitAlignCenter))
286
+ -- in fitToBox curveRaw
287
+ -- paintOffset = Vec2 0 30
288
+ -- for_ (zip [0..] [50,25,10,2]) $ \(i, tolerance) -> cairoScope $ do
289
+ -- let points = bezierSubdivideCasteljau tolerance (transform (translate (fromIntegral i *. paintOffset)) curve)
290
+ -- setColor (mathematica97 i)
291
+ -- C.setLineWidth 2
292
+ -- sketch (Polyline points) >> C.stroke
293
+ -- for_ points $ \p -> sketch (Circle p 3) >> C.fill
294
+ -- cairoScope $ do
295
+ -- C.setLineWidth 3
296
+ -- setColor black
297
+ -- sketch (transform (translate (4*.paintOffset)) curve)
298
+ -- C.stroke
299
+ -- :}
300
+ -- Generated file: size 20KB, crc32: 0x679b311c
301
+ bezierSubdivideCasteljau :: Double -> Bezier -> [Vec2 ]
302
+ bezierSubdivideCasteljau tolerance curve@ (Bezier pFirst _ _ _) = pFirst : go curve
303
+ where
304
+ go (Bezier p1 p2@ (Vec2 x2 y2) p3@ (Vec2 x3 y3) p4@ (Vec2 x4 y4)) =
305
+ let
306
+ p12 = (p1 +. p2 ) /. 2
307
+ p23 = (p2 +. p3 ) /. 2
308
+ p34 = (p3 +. p4 ) /. 2
309
+ p123 = (p12 +. p23 ) /. 2
310
+ p234 = (p23 +. p34 ) /. 2
311
+ p1234 = (p123 +. p234) /. 2
312
+
313
+ dp@ (Vec2 dx dy) = p4 -. p1
314
+
315
+ -- d2, d3 are the distance from p2, p3 from the line
316
+ -- connecting p1 and p4. A curve is flat when those
317
+ -- two are short together.
318
+ d2 = abs ((x2- x4)* dy - (y2- y4)* dx)
319
+ d3 = abs ((x3- x4)* dy - (y3- y4)* dx)
320
+ curveIsFlat = (d2 + d3)* (d2 + d3) < tolerance^ 2 * normSquare dp
321
+ in if curveIsFlat
322
+ then -- We return only the last point so we don’t get duplicate
323
+ -- points for each start+end of adjacent curves.
324
+ -- The very first point is forgotten by the
325
+ [p4]
326
+ else go (Bezier p1 p12 p123 p1234)
327
+ ++
328
+ go (Bezier p1234 p234 p34 p4)
266
329
267
330
-- | Smoothen a number of points by putting a Bezier curve between each pair.
268
331
-- Useful to e.g. make a sketch nicer, or interpolate between points of a crude
0 commit comments