bezier.curve module¶
Helper for Bézier Curves.
See Curve-Curve Intersection for examples using the
Curve class to find intersections.
-
class
bezier.curve.IntersectionStrategy¶ Enum determining the type of intersection algorithm to use.
-
GEOMETRIC= 0¶ Geometric approach to intersection (via subdivision).
-
ALGEBRAIC= 1¶ Algebraic approach to intersection (via implicitization).
-
-
class
bezier.curve.Curve(nodes, degree, _copy=True)¶ Bases:
bezier._base.BaseRepresents a Bézier curve.
We take the traditional definition: a Bézier curve is a mapping from \(s \in \left[0, 1\right]\) to convex combinations of points \(v_0, v_1, \ldots, v_n\) in some vector space:
\[B(s) = \sum_{j = 0}^n \binom{n}{j} s^j (1 - s)^{n - j} \cdot v_j\]
>>> import bezier >>> nodes = np.asfortranarray([ ... [0.0, 0.625, 1.0], ... [0.0, 0.5 , 0.5], ... ]) >>> curve = bezier.Curve(nodes, degree=2) >>> curve <Curve (degree=2, dimension=2)>
Parameters: - nodes (numpy.ndarray) – The nodes in the curve. The columns represent each node while the rows are the dimension of the ambient space.
- degree (int) – The degree of the curve. This is assumed to
correctly correspond to the number of
nodes. Usefrom_nodes()if the degree has not yet been computed. - _copy (bool) – Flag indicating if the nodes should be copied before
being stored. Defaults to
Truesince callers may freely mutatenodesafter passing in.
-
classmethod
from_nodes(nodes, _copy=True)¶ Create a
Curvefrom nodes.Computes the
degreebased on the shape ofnodes.Parameters: - nodes (numpy.ndarray) – The nodes in the curve. The columns represent each node while the rows are the dimension of the ambient space.
- _copy (bool) – Flag indicating if the nodes should be copied before
being stored. Defaults to
Truesince callers may freely mutatenodesafter passing in.
Returns: The constructed curve.
Return type:
-
length¶ float – The length of the current curve.
-
evaluate(s)¶ Evaluate \(B(s)\) along the curve.
This method acts as a (partial) inverse to
locate().See
evaluate_multi()for more details.
>>> nodes = np.asfortranarray([ ... [0.0, 0.625, 1.0], ... [0.0, 0.5 , 0.5], ... ]) >>> curve = bezier.Curve(nodes, degree=2) >>> curve.evaluate(0.75) array([[0.796875], [0.46875 ]])
Parameters: s (float) – Parameter along the curve. Returns: The point on the curve (as a two dimensional NumPy array with a single column). Return type: numpy.ndarray
-
evaluate_multi(s_vals)¶ Evaluate \(B(s)\) for multiple points along the curve.
This is done via a modified Horner’s method (vectorized for each
s-value).>>> nodes = np.asfortranarray([ ... [0.0, 1.0], ... [0.0, 2.0], ... [0.0, 3.0], ... ]) >>> curve = bezier.Curve(nodes, degree=1) >>> curve <Curve (degree=1, dimension=3)> >>> s_vals = np.linspace(0.0, 1.0, 5) >>> curve.evaluate_multi(s_vals) array([[0. , 0.25, 0.5 , 0.75, 1. ], [0. , 0.5 , 1. , 1.5 , 2. ], [0. , 0.75, 1.5 , 2.25, 3. ]])
Parameters: s_vals (numpy.ndarray) – Parameters along the curve (as a 1D array). Returns: The points on the curve. As a two dimensional NumPy array, with the columns corresponding to each svalue and the rows to the dimension.Return type: numpy.ndarray
-
plot(num_pts, color=None, alpha=None, ax=None)¶ Plot the current curve.
Parameters: Returns: The axis containing the plot. This may be a newly created axis.
Return type: Raises: NotImplementedError– If the curve’s dimension is not2.
-
subdivide()¶ Split the curve \(B(s)\) into a left and right half.
Takes the interval \(\left[0, 1\right]\) and splits the curve into \(B_1 = B\left(\left[0, \frac{1}{2}\right]\right)\) and \(B_2 = B\left(\left[\frac{1}{2}, 1\right]\right)\). In order to do this, also reparameterizes the curve, hence the resulting left and right halves have new nodes.
>>> nodes = np.asfortranarray([ ... [0.0, 1.25, 2.0], ... [0.0, 3.0 , 1.0], ... ]) >>> curve = bezier.Curve(nodes, degree=2) >>> left, right = curve.subdivide() >>> left.nodes array([[0. , 0.625, 1.125], [0. , 1.5 , 1.75 ]]) >>> right.nodes array([[1.125, 1.625, 2. ], [1.75 , 2. , 1. ]])
Returns: The left and right sub-curves. Return type: Tuple[Curve,Curve]
-
intersect(other, strategy=<IntersectionStrategy.GEOMETRIC: 0>, _verify=True)¶ Find the points of intersection with another curve.
See Curve-Curve Intersection for more details.
>>> nodes1 = np.asfortranarray([ ... [0.0, 0.375, 0.75 ], ... [0.0, 0.75 , 0.375], ... ]) >>> curve1 = bezier.Curve(nodes1, degree=2) >>> nodes2 = np.asfortranarray([ ... [0.5, 0.5 ], ... [0.0, 0.75], ... ]) >>> curve2 = bezier.Curve(nodes2, degree=1) >>> intersections = curve1.intersect(curve2) >>> 3.0 * intersections array([[2.], [2.]]) >>> s_vals = intersections[0, :] >>> curve1.evaluate_multi(s_vals) array([[0.5], [0.5]])
Parameters: - other (Curve) – Other curve to intersect with.
- strategy (
Optional[IntersectionStrategy]) – The intersection algorithm to use. Defaults to geometric. - _verify (
Optional[bool]) – Indicates if extra caution should be used to verify assumptions about the input and current curve. Can be disabled to speed up execution time. Defaults toTrue.
Returns: 2 x Narray ofs- andt-parameters where intersections occur (possibly empty).Return type: Raises: TypeError– Ifotheris not a curve (and_verify=True).NotImplementedError– If at least one of the curves isn’t two-dimensional (and_verify=True).ValueError– Ifstrategyis not a validIntersectionStrategy.
-
elevate()¶ Return a degree-elevated version of the current curve.
Does this by converting the current nodes \(v_0, \ldots, v_n\) to new nodes \(w_0, \ldots, w_{n + 1}\) where
\[\begin{split}\begin{align*} w_0 &= v_0 \\ w_j &= \frac{j}{n + 1} v_{j - 1} + \frac{n + 1 - j}{n + 1} v_j \\ w_{n + 1} &= v_n \end{align*}\end{split}\]
>>> nodes = np.asfortranarray([ ... [0.0, 1.5, 3.0], ... [0.0, 1.5, 0.0], ... ]) >>> curve = bezier.Curve(nodes, degree=2) >>> elevated = curve.elevate() >>> elevated <Curve (degree=3, dimension=2)> >>> elevated.nodes array([[0., 1., 2., 3.], [0., 1., 1., 0.]])
Returns: The degree-elevated curve. Return type: Curve
-
reduce_()¶ Return a degree-reduced version of the current curve.
Does this by converting the current nodes \(v_0, \ldots, v_n\) to new nodes \(w_0, \ldots, w_{n - 1}\) that correspond to reversing the
elevate()process.This uses the pseudo-inverse of the elevation matrix. For example when elevating from degree 2 to 3, the matrix \(E_2\) is given by
\[\begin{split}\mathbf{v} = \left[\begin{array}{c c c} v_0 & v_1 & v_2 \end{array}\right] \longmapsto \left[\begin{array}{c c c c} v_0 & \frac{v_0 + 2 v_1}{3} & \frac{2 v_1 + v_2}{3} & v_2 \end{array}\right] = \frac{1}{3} \mathbf{v} \left[\begin{array}{c c c c} 3 & 1 & 0 & 0 \\ 0 & 2 & 2 & 0 \\ 0 & 0 & 1 & 3 \end{array}\right]\end{split}\]and the (right) pseudo-inverse is given by
\[\begin{split}R_2 = E_2^T \left(E_2 E_2^T\right)^{-1} = \frac{1}{20} \left[\begin{array}{c c c} 19 & -5 & 1 \\ 3 & 15 & -3 \\ -3 & 15 & 3 \\ 1 & -5 & 19 \end{array}\right].\end{split}\]Warning
Though degree-elevation preserves the start and end nodes, degree reduction has no such guarantee. Rather, the nodes produced are “best” in the least squares sense (when solving the normal equations).
>>> nodes = np.asfortranarray([ ... [-3.0, 0.0, 1.0, 0.0], ... [ 3.0, 2.0, 3.0, 6.0], ... ]) >>> curve = bezier.Curve(nodes, degree=3) >>> reduced = curve.reduce_() >>> reduced <Curve (degree=2, dimension=2)> >>> reduced.nodes array([[-3. , 1.5, 0. ], [ 3. , 1.5, 6. ]])
In the case that the current curve is not degree-elevated.
>>> nodes = np.asfortranarray([ ... [0.0, 1.25, 3.75, 5.0], ... [2.5, 5.0 , 7.5 , 2.5], ... ]) >>> curve = bezier.Curve(nodes, degree=3) >>> reduced = curve.reduce_() >>> reduced <Curve (degree=2, dimension=2)> >>> reduced.nodes array([[-0.125, 2.5 , 5.125], [ 2.125, 8.125, 2.875]])
Returns: The degree-reduced curve. Return type: Curve
-
specialize(start, end)¶ Specialize the curve to a given sub-interval.
>>> nodes = np.asfortranarray([ ... [0.0, 0.5, 1.0], ... [0.0, 1.0, 0.0], ... ]) >>> curve = bezier.Curve(nodes, degree=2) >>> new_curve = curve.specialize(-0.25, 0.75) >>> new_curve.nodes array([[-0.25 , 0.25 , 0.75 ], [-0.625, 0.875, 0.375]])
This is generalized version of
subdivide(), and can even match the output of that method:>>> left, right = curve.subdivide() >>> also_left = curve.specialize(0.0, 0.5) >>> np.all(also_left.nodes == left.nodes) True >>> also_right = curve.specialize(0.5, 1.0) >>> np.all(also_right.nodes == right.nodes) True
Parameters: Returns: The newly-specialized curve.
Return type:
-
locate(point)¶ Find a point on the current curve.
Solves for \(s\) in \(B(s) = p\).
This method acts as a (partial) inverse to
evaluate().Note
A unique solution is only guaranteed if the current curve has no self-intersections. This code assumes, but doesn’t check, that this is true.
>>> nodes = np.asfortranarray([ ... [0.0, 1.0, 3.0, 4.0], ... [0.0, 2.0, 1.0, 0.0], ... ]) >>> curve = bezier.Curve(nodes, degree=3) >>> point1 = np.asfortranarray([ ... [3.09375 ], ... [0.703125], ... ]) >>> s = curve.locate(point1) >>> s 0.75 >>> point2 = np.asfortranarray([ ... [2.0], ... [0.5], ... ]) >>> curve.locate(point2) is None True
Parameters: point (numpy.ndarray) – A ( D x 1) point on the curve, where \(D\) is the dimension of the curve.Returns: The parameter value (\(s\)) corresponding to pointorNoneif the point is not on thecurve.Return type: Optional[float]Raises: ValueError– If the dimension of thepointdoesn’t match the dimension of the current curve.
-
degree¶ int – The degree of the current shape.
-
dimension¶ int – The dimension that the shape lives in.
For example, if the shape lives in \(\mathbf{R}^3\), then the dimension is
3.
-
nodes¶ numpy.ndarray – The nodes that define the current shape.