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