@@ -200,15 +200,114 @@ def __init__(self, control_points):
200200 coeff = [math .factorial (self ._N - 1 )
201201 // (math .factorial (i ) * math .factorial (self ._N - 1 - i ))
202202 for i in range (self ._N )]
203- self ._px = self ._cpoints .T * coeff
203+ self ._px = ( self ._cpoints .T * coeff ). T
204204
205205 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
207213
208214 def point_at_t (self , t ):
209215 """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 from 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 )
212311
213312 @property
214313 def control_points (self ):
@@ -279,6 +378,7 @@ def axis_aligned_extrema(self):
279378 """
280379 n = self .degree
281380 Cj = self .polynomial_coefficients
381+ # much faster than .differentiate(self).polynomial_coefficients
282382 dCj = np .atleast_2d (np .arange (1 , n + 1 )).T * Cj [1 :]
283383 if len (dCj ) == 0 :
284384 return np .array ([]), np .array ([])
0 commit comments