Skip to content

Commit 14a0ec4

Browse files
committed
Add quadratic bezier curves
1 parent 522d749 commit 14a0ec4

File tree

2 files changed

+159
-0
lines changed

2 files changed

+159
-0
lines changed

‎py2d/Bezier.py‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""Bezier curve tools.
2+
3+
All functions in this module assume the following naming:
4+
5+
C1
6+
____
7+
/ \ P2
8+
| \______|
9+
P1
10+
C2
11+
12+
A bezier curve is formed between two points P1 and P2, with curve direction coming from control points Ci.
13+
14+
References:
15+
www.caffeineowl.com/graphics/2d/vectorial/bezierintro.htm
16+
"""
17+
18+
from py2d.Math import Vector, distance_point_line
19+
20+
21+
def point_on_cubic_bezier(p1,p2,c1,c2,t):
22+
"""Find a point on a cubic bezier curve, i.e. a bezier curve with two control points.
23+
24+
@type p1: Vector
25+
@param p1: Start point of the curve
26+
27+
@type p2: Vector
28+
@param p2: Stop point of the curve
29+
30+
@type c1: Vector
31+
@param c1: Control point for point A
32+
33+
@type c2: Vector
34+
@param c2: Control point for point B
35+
36+
@type t: float
37+
@param t: Relative position on the bezier curve between 0 and 1
38+
"""
39+
40+
one_minus_t = 1.0 - t
41+
one_minus_t_2 = one_minus_t * one_minus_t
42+
one_minus_t_3 = one_minus_t_2 * one_minus_t
43+
44+
t_2 = t * t
45+
t_3 = t_2 * t
46+
47+
return p1 * one_minus_t_3 + c1 * (3 * one_minus_t_2 * t) + c2 * (3 * one_minus_t * t_2) + p2 * t_3
48+
49+
50+
def subdivide_cubic_bezier(p1,p2,c1,c2,t):
51+
"""Subdivide a cubic bezier curve and return the point on the curve plus new control points"""
52+
53+
one_minus_t = 1.0 - t
54+
55+
a = p1 * one_minus_t + c1 * t
56+
b = c1 * one_minus_t + c2 * t
57+
c = c2 * one_minus_t + p2 * t
58+
59+
m = a * one_minus_t + b * t
60+
n = b * one_minus_t + c * t
61+
62+
p = m * one_minus_t + n * t
63+
64+
return a,m,p,n,c
65+
66+
67+
def flatten_cubic_bezier(p1,p2,c1,c2, max_divisions=None, max_flatness=0.1):
68+
out = []
69+
70+
if not __is_flat(max_divisions, max_flatness, __bezier_flatness(p1,p2,c1,c2)):
71+
a,m,p,n,c = subdivide_cubic_bezier(p1,p2,c1,c2,0.5)
72+
73+
md_rec = max_divisions - 1 if max_divisions else None
74+
75+
out.extend(flatten_cubic_bezier(p1,p,a,m, md_rec, max_flatness))
76+
out.append(p)
77+
out.extend(flatten_cubic_bezier(p,p2,n,c, md_rec, max_flatness))
78+
79+
return out
80+
81+
def __is_flat(max_divisions, max_flatness, flatness):
82+
return (max_divisions == 0) or (max_flatness != None and flatness <= max_flatness)
83+
84+
def __bezier_flatness(p1,p2, *c):
85+
return min(distance_point_line(cp, p1, p2) for cp in c)

��py2d/examples/Bezier.py‎

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import pygame
2+
from pygame.locals import *
3+
4+
from py2d.Bezier import *
5+
from py2d.Math import *
6+
import py2d.examples.Main
7+
8+
SELECTION_DISTANCE = 20
9+
10+
class Bezier(py2d.examples.Main.Example):
11+
"""Bezier curve sample
12+
13+
Draw around end and control points of the bezier curve.
14+
15+
Key mappings:
16+
MOUSE1: Drag points
17+
18+
Have fun!
19+
"""
20+
21+
def __init__(self, runner):
22+
self.runner = runner
23+
self.title = "Simple Drawing"
24+
25+
self.p1 = Vector(200,400)
26+
self.p2 = Vector(400,400)
27+
self.c1 = Vector(500,50)
28+
self.c2 = Vector(100,50)
29+
30+
31+
self.points = ( ('P1', (255,0,0), self.p1),
32+
('P2', (255,0,0), self.p2),
33+
('C1', (0,255,0), self.c1),
34+
('C2', (0,255,0), self.c2) )
35+
36+
self.sel_point = None
37+
38+
def update(self, time_elapsed):
39+
pass
40+
41+
def render(self):
42+
43+
pygame.draw.line(self.runner.screen, 0x006600, self.p1.as_tuple(), self.c1.as_tuple())
44+
pygame.draw.line(self.runner.screen, 0x006600, self.p2.as_tuple(), self.c2.as_tuple())
45+
46+
for label, color, pos in self.points:
47+
self.draw_point(pos, color, label)
48+
49+
bezier = [self.p1] + flatten_cubic_bezier(self.p1, self.p2, self.c1, self.c2) + [self.p2]
50+
51+
pygame.draw.lines(self.runner.screen, 0xffffff, False, [p.as_tuple() for p in bezier], 2)
52+
53+
if self.sel_point:
54+
pygame.draw.ellipse(self.runner.screen, 0xfff00, pygame.Rect( (self.sel_point.x - 4, self.sel_point.y - 4), (8,8)) , 1)
55+
56+
def draw_point(self, p, color, label=None):
57+
pygame.draw.ellipse(self.runner.screen, color, pygame.Rect(p.as_tuple(), (2,2)))
58+
if label:
59+
self.runner.screen.blit(self.runner.font.render(label, False, color), p.as_tuple())
60+
61+
def mouse_down(self, pos, button):
62+
if button == 1:
63+
mouse = Vector(*pos)
64+
65+
nearest = min(self.points, key=lambda p: (p[2]-mouse).length)
66+
67+
if (nearest[2] - mouse).length_squared <= SELECTION_DISTANCE:
68+
self.sel_point = nearest[2]
69+
else:
70+
self.sel_point = None
71+
72+
def mouse_move(self, pos, rel, buttons):
73+
if buttons[0] and self.sel_point:
74+
self.sel_point.x, self.sel_point.y = pos

0 commit comments

Comments
 (0)