3
3
"""
4
4
5
5
import math
6
+ import warnings
6
7
7
8
import numpy as np
8
9
9
10
import matplotlib .cbook as cbook
10
11
12
+ _comb = np .vectorize (math .comb )
11
13
12
14
class NonIntersectingPathException (ValueError ):
13
15
pass
@@ -213,32 +215,80 @@ def find_bezier_t_intersecting_with_closedpath(
213
215
start = middle
214
216
start_inside = middle_inside
215
217
216
-
217
218
class BezierSegment :
218
219
"""
219
- A D -dimensional Bezier segment.
220
+ A d -dimensional Bezier segment.
220
221
221
222
Parameters
222
223
----------
223
- control_points : (N, D ) array
224
+ control_points : (N, d ) array
224
225
Location of the *N* control points.
225
226
"""
226
227
227
228
def __init__ (self , control_points ):
228
- n = len (control_points )
229
- self ._orders = np .arange (n )
230
- coeff = [math .factorial (n - 1 )
231
- // (math .factorial (i ) * math .factorial (n - 1 - i ))
232
- for i in range (n )]
233
- self ._px = np .asarray (control_points ).T * coeff
229
+ self ._cpoints = np .asarray (control_points )
230
+ self ._N , self ._d = self ._cpoints .shape
231
+ self ._orders = np .arange (self ._N )
232
+ coeff = [math .factorial (self ._N - 1 )
233
+ // (math .factorial (i ) * math .factorial (self ._N - 1 - i ))
234
+ for i in range (self ._N )]
235
+ self ._px = self ._cpoints .T * coeff
236
+
237
+ def __call__ (self , t ):
238
+ return self .point_at_t (t )
234
239
235
240
def point_at_t (self , t ):
236
241
"""Return the point on the Bezier curve for parameter *t*."""
237
242
return tuple (
238
243
self ._px @ (((1 - t ) ** self ._orders )[::- 1 ] * t ** self ._orders ))
239
244
240
- def __call__ (self , t ):
241
- return self .point_at_t (t )
245
+ @property
246
+ def control_points (self ):
247
+ """The control points of the curve."""
248
+ return self ._cpoints
249
+
250
+ @property
251
+ def dimension (self ):
252
+ """The dimension of the curve."""
253
+ return self ._d
254
+
255
+ @property
256
+ def degree (self ):
257
+ """The number of control points in the curve."""
258
+ return self ._N - 1
259
+
260
+ @property
261
+ def polynomial_coefficients (self ):
262
+ r"""The polynomial coefficients of the Bezier curve.
263
+
264
+ Returns
265
+ -------
266
+ coefs : float, (n+1, d) array_like
267
+ coefficients after expanding in polynomial basis, where n is the
268
+ degree of the bezier curve
269
+
270
+ Notes
271
+ -----
272
+ The coefficients are calculated as
273
+
274
+ .. math::
275
+
276
+ {n \choose j} \sum_{i=0}^j (-1)^{i+j} {j \choose i} P_i
277
+
278
+ """
279
+ n = self .degree
280
+ if n > 10 :
281
+ warnings .warn ("Polynomial coefficients formula unstable for high "
282
+ "order Bezier curves!" , RuntimeWarning )
283
+ d = self .dimension
284
+ P = self .control_points
285
+ coefs = np .zeros ((n + 1 , d ))
286
+ for j in range (n + 1 ):
287
+ i = np .arange (j + 1 )
288
+ prefactor = np .power (- 1 , i + j ) * _comb (j , i )
289
+ prefactor = np .tile (prefactor , (d , 1 )).T
290
+ coefs [j ] = _comb (n , j ) * np .sum (prefactor * P [i ], axis = 0 )
291
+ return coefs
242
292
243
293
@property
244
294
def interior_extrema (self ):
@@ -254,33 +304,15 @@ def interior_extrema(self):
254
304
dzeros : float, array_like
255
305
of same size as dims. the $t$ such that $d/dx_i B(t) = 0$
256
306
"""
257
- if self .n <= 2 : # a line's extrema are always its tips
258
- p = [0 ] # roots returns empty array for zero polynomial
307
+ n = self .degree
308
+ Cj = self .polynomial_coefficients
309
+ dCj = np .atleast_2d (np .arange (1 , n + 1 )).T * Cj [1 :]
310
+ if len (dCj ) == 0 :
259
311
return np .array ([]), np .array ([])
260
- elif self .n == 3 : # quadratic curve
261
- # the bezier curve in standard form is
262
- # cp[0] * (1 - t)^2 + cp[1] * 2t(1-t) + cp[2] * t^2
263
- # can be re-written as
264
- # cp[0] + 2 (cp[1] - cp[0]) t + (cp[2] - 2 cp[1] + cp[0]) t^2
265
- # which has simple derivative
266
- # 2*(cp[2] - 2*cp[1] + cp[0]) t + 2*(cp[1] - cp[0])
267
- p = [2 * (self .cpoints [2 ] - 2 * self .cpoints [1 ] + self .cpoints [0 ]),
268
- 2 * (self .cpoints [1 ] - self .cpoints [0 ])]
269
- elif self .n == 4 : # cubic curve
270
- P = self .cpoints
271
- # derivative of cubic bezier curve has coefficients
272
- a = 3 * (P [3 ] - 3 * P [2 ] + 3 * P [1 ] - P [0 ])
273
- b = 6 * (P [2 ] - 2 * P [1 ] + P [0 ])
274
- c = 3 * (P [1 ] - P [0 ])
275
- p = [a , b , c ]
276
- else : # self.n > 4:
277
- # a general formula exists, but is not useful for matplotlib
278
- raise NotImplementedError ("Zero finding only implemented up to "
279
- "cubic curves." )
280
312
dims = []
281
313
roots = []
282
- for i , pi in enumerate (np . array ( p ) .T ):
283
- r = np .roots (pi )
314
+ for i , pi in enumerate (dCj .T ):
315
+ r = np .roots (pi [:: - 1 ] )
284
316
roots .append (r )
285
317
dims .append (i * np .ones_like (r ))
286
318
roots = np .concatenate (roots )
0 commit comments