Skip to content

Added implementation for Bezier Curve, under a new graphics directory. #1713

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 25, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 114 additions & 0 deletions graphics/bezier_curve.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# https://en.wikipedia.org/wiki/B%C3%A9zier_curve
# https://www.tutorialspoint.com/computer_graphics/computer_graphics_curves.htm

from typing import List, Tuple
from scipy.special import comb


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.
"""

def __init__(self, list_of_points: List[Tuple[float, float]]):
"""
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

def basis_function(self, t: float) -> List[float]:
"""
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)])
>>> curve.basis_function(0)
[1.0, 0.0]
>>> curve.basis_function(1)
[0.0, 1.0]
"""
assert 0 <= t <= 1, "Time t must be between 0 and 1."
output_values: List[float] = []
for i in range(len(self.list_of_points)):
# basis function for each i
output_values.append(
comb(self.degree, i) * ((1 - t) ** (self.degree - i)) * (t ** i)
)
# 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

def bezier_curve_function(self, t: float) -> Tuple[float, float]:
"""
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)])
>>> curve.bezier_curve_function(0)
(1.0, 1.0)
>>> curve.bezier_curve_function(1)
(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.
"""
import matplotlib.pyplot as plt

to_plot_x: List[float] = [] # x coordinates of points to plot
to_plot_y: List[float] = [] # y coordinates of points to plot

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