# (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()