bezier curve and spline

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# (c) Vsevolod Solovyov, 2009
class curve(object):
"""
Cubic Bezier curve.
>>> c1 = curve((0,0), (0,1), (1,0), (1,1))
>>> curve((0,0), (0,1), (1,0))
Traceback (most recent call last):
...
ValueError: Cubic Bezier curve must have 4 points, but 3 provided
You can use index to get points::
>>> c1[0]
(0, 0)
>>> list(c1.calc(steps=4))
[(0.0, 0.0), (0.15625, 0.4375), (0.5, 0.5), (0.84375, 0.5625), (1.0, 1.0)]
"""
def __init__(self, *points):
if len(points) != 4:
raise ValueError("Cubic Bezier curve must have 4 points, but %d provided" % len(points))
self.points = points
def __getitem__(self, item):
return self.points[item]
def calc(self, steps=20, last_step=True):
(x1, y1), (x2, y2), (x3, y3), (x4, y4) = self.points
step = 1./steps
for i in xrange(steps + last_step):
b = step * i
a = 1 - b
kab = 3*a*b
ka, ka2b, kab2, kb = a**3, kab*a, kab*b, b**3
yield (x1 * ka + x2 * ka2b + x3 * kab2 + x4 * kb,
y1 * ka + y2 * ka2b + y3 * kab2 + y4 * kb)
class spline(object):
"""
Spline constructs from cubic Bezier curves.
If we just add calculated points from both curves we get duplicating
entries::
>>> c1 = curve((0,0), (0,1), (1,0), (1,1))
>>> c2 = curve((1,1), (1,2), (2,1), (2,2))
>>> list(c1.calc(steps=2)) + list(c2.calc(steps=2))
[(0.0, 0.0), (0.5, 0.5), (1.0, 1.0), (1.0, 1.0), (1.5, 1.5), (2.0, 2.0)]
But spline avoids that by using `last_step` argument of curve.calc()::
>>> s = spline(c1, c2)
>>> list(s.calc(steps=2))
[(0.0, 0.0), (0.5, 0.5), (1.0, 1.0), (1.5, 1.5), (2.0, 2.0)]
Additionaly, it checks for arguments order::
>>> spline(c2, c1)
Traceback (most recent call last):
...
ValueError: Corresponding end and begin points are not equal.
"""
def __init__(self, *curves):
self.curves = []
map(self.add_curve, curves)
def add_curve(self, curve):
if self.curves and self.curves[-1][-1] != curve[0]:
raise ValueError("Corresponding end and begin points are not equal.")
else:
self.curves.append(curve)
def calc(self, steps=20):
last_num = len(self.curves) - 1
for i, curve in enumerate(self.curves):
for p in curve.calc(steps, i == last_num):
yield p
if __name__ == "__main__":
import doctest
doctest.testmod()