ベジェ曲線同士の交点
2019/01/15
Python2.7.8
3次ベジェ曲線同士の交点の算出。
曲線を囲む長方形の重なりを調べ、重なる場合に曲線を2分割することを繰り返して、探索的に交点の近似解を導く方法。
# -*- coding: utf-8 -*-
def intersection_of_bezier_curves(bezier0,bezier1):
#bezier = p0,p1,p2,p3
points = []
buff = []
buff.append([bezier0,bezier1])
for n in range(10000):
bezier0,bezier1 = buff.pop(0)
xmin0,ymin0,xmax0,ymax0 = bezier_box(*bezier0)
xmin1,ymin1,xmax1,ymax1 = bezier_box(*bezier1)
s = (abs(xmax0-xmin0)+abs(ymax0-ymin0)+
abs(xmax1-xmin1)+abs(ymax1-ymin1) )
if s < 0.00001:
x = (xmax0+xmin0+xmax1+xmin1)/4
y = (ymax0+ymin0+ymax1+ymin1)/4
points.append((x,y))
elif not (xmin0 > xmax1 or xmax0 < xmin1 or
ymin0 > ymax1 or ymax0 < ymin1): #if overlap
b0,b1 = bezier_split(bezier0,0.5)
b2,b3 = bezier_split(bezier1,0.5)
buff += [[b0,b2],[b0,b3],[b1,b2],[b1,b3]]
if len(buff) == 0:
break
if n == 9999:
print('Error intersection_of_bezier_curves(): cannot convergence.')
return []
#marge
point_groups = []
for p in points:
is_included = False
for pgroup in point_groups:
sx = sy = 0
for tp in pgroup:
sx += tp[0]
sy += tp[1]
x = sx/len(pgroup)
y = sy/len(pgroup)
s = abs(p[0]-x)+abs(p[1]-y)
if s < 0.00001:
is_included = True
continue
if is_included:
pgroup.append(p)
else:
new_group = [p]
point_groups.append(new_group)
points = []
for pgroup in point_groups:
sx = sy = 0
for p in pgroup:
sx += p[0]
sy += p[1]
x = sx/len(pgroup)
y = sy/len(pgroup)
points.append((x,y))
print(str(len(points))+' points found.')
return points
def bezier_box(p0,p1,p2,p3):
bounds = [[],[]]
bounds[0] += [p0[0],p3[0]]
bounds[1] += [p0[1],p3[1]]
for i in [0,1]:
a = float(-3*p0[i] + 9*p1[i] - 9*p2[i] + 3*p3[i])
b = float( 6*p0[i] - 12*p1[i] + 6*p2[i])
c = float( 3*p1[i] - 3*p0[i])
if a == 0:
if b == 0: continue
tlist = [-c / b]
else:
tlist = quadratic_equation(a,b,c)
for t in tlist:
if 0 < t < 1:
v = (1-t)**3*p0[i] + 3*(1-t)**2*t*p1[i] + 3*(1-t)*t**2*p2[i] + t**3*p3[i]
bounds[i].append(v)
return [min(bounds[0]),min(bounds[1]),
max(bounds[0]),max(bounds[1])]
def bezier_split(points,t):
if not 0 < t < 1:
print('Error bezier_split(): t must be 0<t<1.')
return
pn = len(points)
result = [[],[]]
result[0] = [[0.0,0.0] for i in range(pn)]
result[1] = [[0.0,0.0] for i in range(pn)]
result[0][0] = points[0]
result[1][pn-1] = points[pn-1]
prev = points
for i in range(1,pn):
tmp = [[0.0,0.0] for k in range(pn-i)]
for j in range(pn-i):
tmp[j][0] = prev[j][0] + (prev[j+1][0]-prev[j][0]) * t
tmp[j][1] = prev[j][1] + (prev[j+1][1]-prev[j][1]) * t
result[0][i][0] = tmp[0][0]
result[0][i][1] = tmp[0][1]
result[1][pn-i-1][0] = tmp[len(tmp)-1][0]
result[1][pn-i-1][1] = tmp[len(tmp)-1][1]
prev = tmp
return result
def bezier_curve_points(p0,p1,p2,p3,steps=20):
points = []
for i in range(steps+1):
t = i/float(steps)
x = (1-t)**3*p0[0] + 3*(1-t)**2*t*p1[0] + 3*(1-t)*t**2*p2[0] + t**3*p3[0]
y = (1-t)**3*p0[1] + 3*(1-t)**2*t*p1[1] + 3*(1-t)*t**2*p2[1] + t**3*p3[1]
points.append([x,y])
return points
def quadratic_equation(a,b,c):
d = b*b-4*a*c
if d < 0:
return []
if d == 0:
x = -b/(2.0*a)
return [x]
else:
x0 = (-b+d**0.5)/(2.0*a)
x1 = (-b-d**0.5)/(2.0*a)
return [x0,x1]
def main():
import Tkinter
window = Tkinter.Tk()
canvas = Tkinter.Canvas(window,width=300,height=200)
canvas.pack()
#bezier_points0 = [[20,10],[160,80],[220,150],[30,190]]
#bezier_points1 = [[280,10],[60,80],[40,170],[290,180]]
bezier_points0 = [[20,50],[150,10],[200,190],[280,170]]
bezier_points1 = [[50,10],[100,150],[200,50],[250,190]]
cross_points = intersection_of_bezier_curves(bezier_points0,bezier_points1)
r = 3
for p in [bezier_points0,bezier_points1]:
for i in range(4):
canvas.create_oval(p[i][0]-r,p[i][1]-r,
p[i][0]+r,p[i][1]+r,outline='gray')
canvas.create_line(p[0][0],p[0][1],p[1][0],p[1][1],fill='gray')
canvas.create_line(p[2][0],p[2][1],p[3][0],p[3][1],fill='gray')
canvas.create_line(bezier_curve_points(*bezier_points0))
canvas.create_line(bezier_curve_points(*bezier_points1))
r = 3
for x,y in cross_points:
canvas.create_oval(x-r,y-r,x+r,y+r,outline='red')
window.mainloop()
if __name__ == '__main__':
main()
ベジエ曲線の交点について
http://d.hatena.ne.jp/shspage/20140629/1404031123
A Primer on Bézier Curves #Curve/curve intersection
https://pomax.github.io/bezierinfo/#curveintersection
ベジェ曲線の位置の t(0~1)を返すタイプ
# -*- coding: utf-8 -*-
def intersection_of_bezier_curves(bezier0,bezier1):
#bezier = p0,p1,p2,p3
position = []
buff = []
buff.append([[bezier0,0.0,1.0],[bezier1,0.0,1.0]])
for n in range(10000):
temp = buff.pop(0)
bezier0,t0,t1 = temp[0]
bezier1,t2,t3 = temp[1]
xmin0,ymin0,xmax0,ymax0 = bezier_box(*bezier0)
xmin1,ymin1,xmax1,ymax1 = bezier_box(*bezier1)
s = (abs(xmax0-xmin0)+abs(ymax0-ymin0)+
abs(xmax1-xmin1)+abs(ymax1-ymin1) )
if s < 0.00001:
position.append([(t0+t1)/2,(t2+t3)/2])
elif not (xmin0 > xmax1 or xmax0 < xmin1 or
ymin0 > ymax1 or ymax0 < ymin1): #if overlap
b0,b1 = bezier_split(bezier0,0.5)
b2,b3 = bezier_split(bezier1,0.5)
c0 = [b0,t0,(t1+t0)/2]
c1 = [b1,(t1+t0)/2,t1]
c2 = [b2,t2,(t3+t2)/2]
c3 = [b3,(t3+t2)/2,t3]
buff += [[c0,c2],[c0,c3],[c1,c2],[c1,c3]]
if len(buff) == 0:
break
if n == 9999:
print('Error intersection_of_bezier_curves(): cannot convergence.')
return []
#marge
groups = []
for t in position:
is_included = False
for group in groups:
d0 = abs(group[0][0]-t[0])
d1 = abs(group[0][1]-t[1])
if d0 < 0.00001 and d1 < 0.00001:
is_included = True
continue
if is_included:
group.append(t)
else:
groups.append([t])
position = []
for group in groups:
t = [0,0]
for _t in group:
for i in range(2):
t[i] += _t[i]
for i in range(2):
t[i] = t[i]/len(group)
position.append(t)
print(str(len(position))+' points found.')
return position
def bezier_box(p0,p1,p2,p3):
bounds = [[],[]]
bounds[0] += [p0[0],p3[0]]
bounds[1] += [p0[1],p3[1]]
for i in [0,1]:
a = float(-3*p0[i] + 9*p1[i] - 9*p2[i] + 3*p3[i])
b = float( 6*p0[i] - 12*p1[i] + 6*p2[i])
c = float( 3*p1[i] - 3*p0[i])
if a == 0:
if b == 0:
continue
tlist = [-c/b]
else:
tlist = quadratic_equation(a,b,c)
for t in tlist:
if 0 < t < 1:
v = (1-t)**3*p0[i] + 3*(1-t)**2*t*p1[i] + 3*(1-t)*t**2*p2[i] + t**3*p3[i]
bounds[i].append(v)
return [min(bounds[0]),min(bounds[1]),
max(bounds[0]),max(bounds[1])]
def bezier_split(points,t):
if not 0 < t < 1:
print('Error bezier_split(): t must be 0<t<1.')
return
pn = len(points)
result = [[],[]]
result[0] = [[0.0,0.0] for i in range(pn)]
result[1] = [[0.0,0.0] for i in range(pn)]
result[0][0] = points[0]
result[1][pn-1] = points[pn-1]
prev = points
for i in range(1,pn):
tmp = [[0.0,0.0] for k in range(pn-i)]
for j in range(pn-i):
tmp[j][0] = prev[j][0] + (prev[j+1][0]-prev[j][0]) * t
tmp[j][1] = prev[j][1] + (prev[j+1][1]-prev[j][1]) * t
result[0][i][0] = tmp[0][0]
result[0][i][1] = tmp[0][1]
result[1][pn-i-1][0] = tmp[len(tmp)-1][0]
result[1][pn-i-1][1] = tmp[len(tmp)-1][1]
prev = tmp
return result
def bezier_curve_points(p0,p1,p2,p3,steps=20):
points = []
for i in range(steps+1):
t = i/float(steps)
x = (1-t)**3*p0[0] + 3*(1-t)**2*t*p1[0] + 3*(1-t)*t**2*p2[0] + t**3*p3[0]
y = (1-t)**3*p0[1] + 3*(1-t)**2*t*p1[1] + 3*(1-t)*t**2*p2[1] + t**3*p3[1]
points.append((x,y))
return points
def quadratic_equation(a,b,c):
d = b*b-4*a*c
if d < 0:
return []
if d == 0:
x = -b/(2.0*a)
return [x]
else:
x0 = (-b+d**0.5)/(2.0*a)
x1 = (-b-d**0.5)/(2.0*a)
return [x0,x1]
def main():
import Tkinter
window = Tkinter.Tk()
canvas = Tkinter.Canvas(window,width=300,height=200,bg='white')
canvas.pack()
#bezier_points0 = [[20,10],[160,80],[220,150],[30,190]]
#bezier_points1 = [[280,10],[60,80],[40,170],[290,180]]
bezier_points0 = [[20,50],[150,10],[200,190],[280,170]]
bezier_points1 = [[50,10],[100,150],[200,50],[250,190]]
cross_points = []
tlist = intersection_of_bezier_curves(bezier_points0,bezier_points1)
beziers = [bezier_points0,bezier_points1]
for i in range(2):
p0,p1,p2,p3 = beziers[i]
for tpair in tlist:
t = tpair[i]
x = (1-t)**3*p0[0] + 3*(1-t)**2*t*p1[0] + 3*(1-t)*t**2*p2[0] + t**3*p3[0]
y = (1-t)**3*p0[1] + 3*(1-t)**2*t*p1[1] + 3*(1-t)*t**2*p2[1] + t**3*p3[1]
cross_points.append((x,y))
r = 3
for p in [bezier_points0,bezier_points1]:
for i in range(4):
canvas.create_oval(p[i][0]-r,p[i][1]-r,
p[i][0]+r,p[i][1]+r,outline='gray')
canvas.create_line(p[0][0],p[0][1],p[1][0],p[1][1],fill='gray')
canvas.create_line(p[2][0],p[2][1],p[3][0],p[3][1],fill='gray')
canvas.create_line(bezier_curve_points(*bezier_points0))
canvas.create_line(bezier_curve_points(*bezier_points1))
r = 3
for x,y in cross_points:
canvas.create_oval(x-r,y-r,x+r,y+r,outline='red')
window.mainloop()
if __name__ == '__main__':
main()