@@ -200,15 +200,114 @@ def __init__(self, control_points):
200
200
coeff = [math .factorial (self ._N - 1 )
201
201
// (math .factorial (i ) * math .factorial (self ._N - 1 - i ))
202
202
for i in range (self ._N )]
203
- self ._px = self ._cpoints .T * coeff
203
+ self ._px = ( self ._cpoints .T * coeff ). T
204
204
205
205
def __call__ (self , t ):
206
- return self .point_at_t (t )
206
+ t = np .array (t )
207
+ orders_shape = (1 ,)* t .ndim + self ._orders .shape
208
+ t_shape = t .shape + (1 ,) # self._orders.ndim == 1
209
+ orders = np .reshape (self ._orders , orders_shape )
210
+ rev_orders = np .reshape (self ._orders [::- 1 ], orders_shape )
211
+ t = np .reshape (t , t_shape )
212
+ return ((1 - t )** rev_orders * t ** orders ) @ self ._px
207
213
208
214
def point_at_t (self , t ):
209
215
"""Return the point on the Bezier curve for parameter *t*."""
210
- return tuple (
211
- self ._px @ (((1 - t ) ** self ._orders )[::- 1 ] * t ** self ._orders ))
216
+ return tuple (self (t ))
217
+
218
+ def arc_area (self ):
219
+ r"""
220
+ (Signed) area swept out by ray from origin to curve.
221
+
222
+ Notes
223
+ -----
224
+ A simple, analytical formula is possible for arbitrary bezier curves.
225
+
226
+ Given a bezier curve B(t), in order to calculate the area of the arc
227
+ swept out by the ray fr
10000
om the origin to the curve, we simply need to
228
+ compute :math:`\frac{1}{2}\int_0^1 B(t) \cdot n(t) dt`, where
229
+ :math:`n(t) = u^{(1)}(t) \hat{x}_0 - u{(0)}(t) \hat{x}_1` is the normal
230
+ vector oriented away from the origin and :math:`u^{(i)}(t) =
231
+ \frac{d}{dt} B^{(i)}(t)` is the :math:`i`th component of the curve's
232
+ tangent vector. (This formula can be found by applying the divergence
233
+ theorem to :math:`F(x,y) = [x, y]/2`, and calculates the *signed* area
234
+ for a counter-clockwise curve, by the right hand rule).
235
+
236
+ The control points of the curve are just its coefficients in a
237
+ Bernstein expansion, so if we let :math:`P_i = [P^{(0)}_i, P^{(1)}_i]`
238
+ be the :math:`i`'th control point, then
239
+
240
+ .. math::
241
+
242
+ \frac{1}{2}\int_0^1 B(t) \cdot n(t) dt
243
+ &= \frac{1}{2}\int_0^1 B^{(0)}(t) \frac{d}{dt} B^{(1)}(t)
244
+ - B^{(1)}(t) \frac{d}{dt} B^{(0)}(t)
245
+ dt \\
246
+ &= \frac{1}{2}\int_0^1
247
+ \left( \sum_{j=0}^n P_j^{(0)} b_{j,n} \right)
248
+ \left( n \sum_{k=0}^{n-1} (P_{k+1}^{(1)} -
249
+ P_{k}^{(1)}) b_{j,n} \right)
250
+ \\
251
+ &\hspace{1em} - \left( \sum_{j=0}^n P_j^{(1)} b_{j,n}
252
+ \right) \left( n \sum_{k=0}^{n-1} (P_{k+1}^{(0)}
253
+ - P_{k}^{(0)}) b_{j,n} \right)
254
+ dt,
255
+
256
+ where :math:`b_{\nu, n}(t) = {n \choose \nu} t^\nu {(1 - t)}^{n-\nu}`
257
+ is the :math:`\nu`'th Bernstein polynomial of degree :math:`n`.
258
+
259
+ Grouping :math:`t^l(1-t)^m` terms together for each :math:`l`,
260
+ :math:`m`, we get that the integrand becomes
261
+
262
+ .. math::
263
+
264
+ \sum_{j=0}^n \sum_{k=0}^{n-1}
265
+ {n \choose j} {{n - 1} \choose k}
266
+ &\left[P_j^{(0)} (P_{k+1}^{(1)} - P_{k}^{(1)})
267
+ - P_j^{(1)} (P_{k+1}^{(0)} - P_{k}^{(0)})\right] \\
268
+ &\hspace{1em}\times{}t^{j + k} {(1 - t)}^{2n - 1 - j - k}
269
+
270
+ or just
271
+
272
+ .. math::
273
+
274
+ \sum_{j=0}^n \sum_{k=0}^{n-1}
275
+ \frac{{n \choose j} {{n - 1} \choose k}}
276
+ {{{2n - 1} \choose {j+k}}}
277
+ [P_j^{(0)} (P_{k+1}^{(1)} - P_{k}^{(1)})
278
+ - P_j^{(1)} (P_{k+1}^{(0)} - P_{k}^{(0)})]
279
+ b_{j+k,2n-1}(t).
280
+
281
+ Interchanging sum and integral, and using the fact that :math:`\int_0^1
282
+ b_{\nu, n}(t) dt = \frac{1}{n + 1}`, we conclude that the
283
+ original integral can
284
+ simply be written as
285
+
286
+ .. math::
287
+
288
+ \frac{1}{2}&\int_0^1 B(t) \cdot n(t) dt
289
+ \\
290
+ &= \frac{1}{4}\sum_{j=0}^n \sum_{k=0}^{n-1}
291
+ \frac{{n \choose j} {{n - 1} \choose k}}
292
+ {{{2n - 1} \choose {j+k}}}
293
+ [P_j^{(0)} (P_{k+1}^{(1)} - P_{k}^{(1)})
294
+ - P_j^{(1)} (P_{k+1}^{(0)} - P_{k}^{(0)})]
295
+ """
296
+ n = self .degree
297
+ area = 0
298
+ P = self .control_points
299
+ dP = np .diff (P , axis = 0 )
300
+ for j in range (n + 1 ):
301
+ for k in range (n ):
302
+ area += _comb (n , j )* _comb (n - 1 , k )/ _comb (2 * n - 1 , j + k ) \
303
+ * (P [j , 0 ]* dP [k , 1 ] - P [j , 1 ]* dP [k , 0 ])
304
+ return (1 / 4 )* area
305
+
306
+ @classmethod
307
+ def differentiate (cls , B ):
308
+ """Return the derivative of a BezierSegment, itself a BezierSegment"""
309
+ dcontrol_points = B .degree * np .diff (B .control_points , axis = 0 )
310
+ return cls (dcontrol_points )
212
311
213
312
@property
214
313
def control_points (self ):
@@ -279,6 +378,7 @@ def axis_aligned_extrema(self):
279
378
"""
280
379
n = self .degree
281
380
Cj = self .polynomial_coefficients
381
+ # much faster than .differentiate(self).polynomial_coefficients
282
382
dCj = np .atleast_2d (np .arange (1 , n + 1 )).T * Cj [1 :]
283
383
if len (dCj ) == 0 :
284
384
return np .array ([]), np .array ([])
0 commit comments