# bezier.curve module¶

Helper for Bézier Curves.

See Curve-Curve Intersection for examples using the Curve class to find intersections.

class bezier.curve.Curve(nodes, start=0.0, end=1.0, root=None, _copy=True)

Bases: bezier._base.Base

Represents 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.array([
...     [0.0  , 0.0],
...     [0.625, 0.5],
...     [1.0  , 0.5],
... ])
>>> curve = bezier.Curve(nodes)
>>> curve
<Curve (degree=2, dimension=2)>

Parameters: nodes (numpy.ndarray) – The nodes in the curve. The rows represent each node while the columns are the dimension of the ambient space. start (Optional [ float ]) – The beginning of the sub-interval that this curve represents. end (Optional [ float ]) – The end of the sub-interval that this curve represents. root (Optional [ Curve ]) – The root curve that contains this current curve. _copy (bool) – Flag indicating if the nodes should be copied before being stored. Defaults to True since callers may freely mutate nodes after passing in.
__repr__()

Representation of current object.

Returns: Object representation. str
length

float: The length of the current curve.

start

float: Start of sub-interval this curve represents.

This value is used to track the current curve in the re-parameterization / subdivision process. The curve is still defined on the unit interval, but this value illustrates how this curve relates to a “parent” curve. For example:

>>> nodes = np.array([
...     [0.0, 0.0],
...     [1.0, 2.0],
... ])
>>> curve = bezier.Curve(nodes)
>>> curve
<Curve (degree=1, dimension=2)>
>>> left, right = curve.subdivide()
>>> left
<Curve (degree=1, dimension=2, start=0, end=0.5)>
>>> right
<Curve (degree=1, dimension=2, start=0.5, end=1)>
>>> _, mid_right = left.subdivide()
>>> mid_right
<Curve (degree=1, dimension=2, start=0.25, end=0.5)>
>>> mid_right.nodes
array([[ 0.25, 0.5 ],
[ 0.5 , 1.  ]])

end

float: End of sub-interval this curve represents.

See start for more information.

root

Curve: The “root” curve that contains the current curve.

This indicates that the current curve is a section of the “root” curve. For example:

>>> _, right = curve.subdivide()
>>> right
<Curve (degree=2, dimension=2, start=0.5, end=1)>
>>> right.root is curve
True
>>> right.evaluate(0.0) == curve.evaluate(0.5)
array([ True, True], dtype=bool)
>>>
>>> mid_left, _ = right.subdivide()
>>> mid_left
<Curve (degree=2, dimension=2, start=0.5, end=0.75)>
>>> mid_left.root is curve
True
>>> mid_left.evaluate(1.0) == curve.evaluate(0.75)
array([ True, True], dtype=bool)

edge_index

Optional [ int ] : The index of the edge among a group of edges.

>>> curve.edge_index
1
>>> curve.previous_edge
<Curve (degree=1, dimension=2)>
>>> curve.previous_edge.edge_index
0
>>> curve.next_edge
<Curve (degree=1, dimension=2)>
>>> curve.next_edge.edge_index
2


This is intended to be used when a Curve is created as part of a larger structure like a Surface or CurvedPolygon.

next_edge

Optional [ Curve ] : An edge that comes after the current one.

This is intended to be used when a Curve is created as part of a larger structure like a Surface or CurvedPolygon.

previous_edge

Optional [ Curve ] : An edge that comes before the current one.

This is intended to be used when a Curve is created as part of a larger structure like a Surface or CurvedPolygon.

evaluate(s)

Evaluate $$B(s)$$ along the curve.

See evaluate_multi() for more details.

>>> nodes = np.array([
...     [0.0  , 0.0],
...     [0.625, 0.5],
...     [1.0  , 0.5],
... ])
>>> curve = bezier.Curve(nodes)
>>> curve.evaluate(0.75)
array([ 0.796875, 0.46875 ])

Parameters: s (float) – Parameter along the curve. The point on the curve (as a one dimensional NumPy array). numpy.ndarray
evaluate_multi(s_vals)

Evaluate $$B(s)$$ for multiple points along the curve.

This is done by first evaluating each member of the Bernstein basis at each value in s_vals and then applying those to the control points for the current curve.

This is done instead of using de Casteljau’s algorithm. Implementing de Casteljau is problematic because it requires a choice between one of two methods:

• vectorize operations of the form $$(1 - s)v + s w$$, which requires a copy of the curve’s control points for each value in s_vals
• avoid vectorization and compute each point in serial

Instead, we can use vectorized operations to build up the Bernstein basis values.

>>> nodes = np.array([
...     [0.0, 0.0, 0.0],
...     [1.0, 2.0, 3.0],
... ])
>>> curve = bezier.Curve(nodes)
>>> curve
<Curve (degree=1, dimension=3)>
>>> s_vals = np.linspace(0.0, 1.0, 5)
>>> curve.evaluate_multi(s_vals)
array([[ 0.  , 0.  , 0.  ],
[ 0.25, 0.5 , 0.75],
[ 0.5 , 1.  , 1.5 ],
[ 0.75, 1.5 , 2.25],
[ 1.  , 2.  , 3.  ]])

Parameters: s_vals (numpy.ndarray) – Parameters along the curve (as a 1D array). The points on the curve. As a two dimensional NumPy array, with the rows corresponding to each s value and the columns to the dimension. numpy.ndarray
plot(num_pts, ax=None, show=False)

Plot the current curve.

Parameters: num_pts (int) – Number of points to plot. ax (Optional [ matplotlib.artist.Artist ]) – matplotlib axis object to add plot to. show (Optional [ bool ]) – Flag indicating if the plot should be shown. The axis containing the plot. This may be a newly created axis. matplotlib.artist.Artist NotImplementedError – If the curve’s dimension is not 2.
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.array([
...     [0.0 , 0.0],
...     [1.25, 3.0],
...     [2.0 , 1.0],
... ])
>>> curve = bezier.Curve(nodes)
>>> left, right = curve.subdivide()
>>> left
<Curve (degree=2, dimension=2, start=0, end=0.5)>
>>> left.nodes
array([[ 0.   , 0.   ],
[ 0.625, 1.5  ],
[ 1.125, 1.75 ]])
>>> right
<Curve (degree=2, dimension=2, start=0.5, end=1)>
>>> right.nodes
array([[ 1.125, 1.75 ],
[ 1.625, 2.   ],
[ 2.   , 1.   ]])

Returns: The left and right sub-curves. Tuple [ Curve, Curve ]
intersect(other)

Find the points of intersection with another curve.

See Curve-Curve Intersection for more details.

>>> nodes1 = np.array([
...     [0.0  , 0.0  ],
...     [0.375, 0.75 ],
...     [0.75 , 0.375],
... ])
>>> curve1 = bezier.Curve(nodes1)
>>> nodes2 = np.array([
...     [0.5, 0.0 ],
...     [0.5, 0.75],
... ])
>>> curve2 = bezier.Curve(nodes2)
>>> intersections = curve1.intersect(curve2)
>>> intersections
array([[ 0.5, 0.5]])

Parameters: other (Curve) – Other curve to intersect with. Array of intersection points (possibly empty). numpy.ndarray TypeError – If other is not a curve. NotImplementedError – If both curves aren’t two-dimensional.
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}
Returns: The degree-elevated curve. Curve
__eq__(other)

Check equality against another shape.

Returns: Boolean indicating if the shapes are the same. bool
__ne__(other)

Check inequality against another shape.

Returns: Boolean indicating if the shapes are not the same. bool
copy()

Make a copy of the current shape.

Returns: Instance of the current shape.
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.

specialize(start, end)

Specialize the curve to a given sub-interval.

>>> curve = bezier.Curve(np.array([
...     [0.0, 0.0],
...     [0.5, 1.0],
...     [1.0, 0.0],
... ]))
>>> new_curve = curve.specialize(-0.25, 0.75)
>>> new_curve
<Curve (degree=2, dimension=2, start=-0.25, end=0.75)>
>>> new_curve.nodes
array([[-0.25 , -0.625],
[ 0.25 ,  0.875],
[ 0.75 ,  0.375]])


This is generalized version of subdivide(), and can even match the output of that method:

>>> left, right = curve.subdivide()
>>> left == curve.specialize(0.0, 0.5)
True
>>> right == curve.specialize(0.5, 1.0)
True

Parameters: start (float) – The start point of the interval we are specializing to. end (float) – The end point of the interval we are specializing to. The newly-specialized curve. Curve