2020-01-25 06:18:43 +00:00
|
|
|
# https://en.wikipedia.org/wiki/B%C3%A9zier_curve
|
|
|
|
# https://www.tutorialspoint.com/computer_graphics/computer_graphics_curves.htm
|
2020-09-23 11:30:13 +00:00
|
|
|
from __future__ import annotations
|
2020-07-06 07:44:19 +00:00
|
|
|
|
2024-04-02 19:29:34 +00:00
|
|
|
from scipy.special import comb
|
2020-01-25 06:18:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
class BezierCurve:
|
|
|
|
"""
|
|
|
|
Bezier curve is a weighted sum of a set of control points.
|
|
|
|
Generate Bezier curves from a given set of control points.
|
|
|
|
This implementation works only for 2d coordinates in the xy plane.
|
|
|
|
"""
|
|
|
|
|
2020-09-23 11:30:13 +00:00
|
|
|
def __init__(self, list_of_points: list[tuple[float, float]]):
|
2020-01-25 06:18:43 +00:00
|
|
|
"""
|
|
|
|
list_of_points: Control points in the xy plane on which to interpolate. These
|
|
|
|
points control the behavior (shape) of the Bezier curve.
|
|
|
|
"""
|
|
|
|
self.list_of_points = list_of_points
|
|
|
|
# Degree determines the flexibility of the curve.
|
|
|
|
# Degree = 1 will produce a straight line.
|
|
|
|
self.degree = len(list_of_points) - 1
|
|
|
|
|
2020-09-23 11:30:13 +00:00
|
|
|
def basis_function(self, t: float) -> list[float]:
|
2020-01-25 06:18:43 +00:00
|
|
|
"""
|
|
|
|
The basis function determines the weight of each control point at time t.
|
|
|
|
t: time value between 0 and 1 inclusive at which to evaluate the basis of
|
|
|
|
the curve.
|
|
|
|
returns the x, y values of basis function at time t
|
|
|
|
|
|
|
|
>>> curve = BezierCurve([(1,1), (1,2)])
|
2024-09-30 21:01:15 +00:00
|
|
|
>>> [float(x) for x in curve.basis_function(0)]
|
2020-01-25 06:18:43 +00:00
|
|
|
[1.0, 0.0]
|
2024-09-30 21:01:15 +00:00
|
|
|
>>> [float(x) for x in curve.basis_function(1)]
|
2020-01-25 06:18:43 +00:00
|
|
|
[0.0, 1.0]
|
|
|
|
"""
|
|
|
|
assert 0 <= t <= 1, "Time t must be between 0 and 1."
|
2020-09-23 11:30:13 +00:00
|
|
|
output_values: list[float] = []
|
2020-01-25 06:18:43 +00:00
|
|
|
for i in range(len(self.list_of_points)):
|
|
|
|
# basis function for each i
|
|
|
|
output_values.append(
|
2022-01-30 19:29:54 +00:00
|
|
|
comb(self.degree, i) * ((1 - t) ** (self.degree - i)) * (t**i)
|
2020-01-25 06:18:43 +00:00
|
|
|
)
|
|
|
|
# the basis must sum up to 1 for it to produce a valid Bezier curve.
|
|
|
|
assert round(sum(output_values), 5) == 1
|
|
|
|
return output_values
|
|
|
|
|
2020-09-23 11:30:13 +00:00
|
|
|
def bezier_curve_function(self, t: float) -> tuple[float, float]:
|
2020-01-25 06:18:43 +00:00
|
|
|
"""
|
|
|
|
The function to produce the values of the Bezier curve at time t.
|
|
|
|
t: the value of time t at which to evaluate the Bezier function
|
|
|
|
Returns the x, y coordinates of the Bezier curve at time t.
|
|
|
|
The first point in the curve is when t = 0.
|
|
|
|
The last point in the curve is when t = 1.
|
|
|
|
|
|
|
|
>>> curve = BezierCurve([(1,1), (1,2)])
|
2024-09-30 21:01:15 +00:00
|
|
|
>>> tuple(float(x) for x in curve.bezier_curve_function(0))
|
2020-01-25 06:18:43 +00:00
|
|
|
(1.0, 1.0)
|
2024-09-30 21:01:15 +00:00
|
|
|
>>> tuple(float(x) for x in curve.bezier_curve_function(1))
|
2020-01-25 06:18:43 +00:00
|
|
|
(1.0, 2.0)
|
|
|
|
"""
|
|
|
|
|
|
|
|
assert 0 <= t <= 1, "Time t must be between 0 and 1."
|
|
|
|
|
|
|
|
basis_function = self.basis_function(t)
|
|
|
|
x = 0.0
|
|
|
|
y = 0.0
|
|
|
|
for i in range(len(self.list_of_points)):
|
|
|
|
# For all points, sum up the product of i-th basis function and i-th point.
|
|
|
|
x += basis_function[i] * self.list_of_points[i][0]
|
|
|
|
y += basis_function[i] * self.list_of_points[i][1]
|
|
|
|
return (x, y)
|
|
|
|
|
|
|
|
def plot_curve(self, step_size: float = 0.01):
|
|
|
|
"""
|
|
|
|
Plots the Bezier curve using matplotlib plotting capabilities.
|
|
|
|
step_size: defines the step(s) at which to evaluate the Bezier curve.
|
|
|
|
The smaller the step size, the finer the curve produced.
|
|
|
|
"""
|
2024-03-28 18:03:23 +00:00
|
|
|
from matplotlib import pyplot as plt
|
2020-01-25 06:18:43 +00:00
|
|
|
|
2020-09-23 11:30:13 +00:00
|
|
|
to_plot_x: list[float] = [] # x coordinates of points to plot
|
|
|
|
to_plot_y: list[float] = [] # y coordinates of points to plot
|
2020-01-25 06:18:43 +00:00
|
|
|
|
|
|
|
t = 0.0
|
|
|
|
while t <= 1:
|
|
|
|
value = self.bezier_curve_function(t)
|
|
|
|
to_plot_x.append(value[0])
|
|
|
|
to_plot_y.append(value[1])
|
|
|
|
t += step_size
|
|
|
|
|
|
|
|
x = [i[0] for i in self.list_of_points]
|
|
|
|
y = [i[1] for i in self.list_of_points]
|
|
|
|
|
|
|
|
plt.plot(
|
|
|
|
to_plot_x,
|
|
|
|
to_plot_y,
|
|
|
|
color="blue",
|
|
|
|
label="Curve of Degree " + str(self.degree),
|
|
|
|
)
|
|
|
|
plt.scatter(x, y, color="red", label="Control Points")
|
|
|
|
plt.legend()
|
|
|
|
plt.show()
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
import doctest
|
|
|
|
|
|
|
|
doctest.testmod()
|
|
|
|
|
|
|
|
BezierCurve([(1, 2), (3, 5)]).plot_curve() # degree 1
|
|
|
|
BezierCurve([(0, 0), (5, 5), (5, 0)]).plot_curve() # degree 2
|
|
|
|
BezierCurve([(0, 0), (5, 5), (5, 0), (2.5, -2.5)]).plot_curve() # degree 3
|