ベジェ曲線パスのストライプ

2019/01/10

Python2.7.8

``` # -*- coding: utf-8 -*-

import math import Tkinter

class Node: p = [0.0,0.0] #self point p0 = [0.0,0.0] #control point p1 = [0.0,0.0] #control point

def __init__(self,p,p0,p1):
    self.p = p
    self.p0 = p0
    self.p1 = p1

class Path: x = 0.0 y = 0.0 nodes = [] closed = False steps = 20

def __init__(self,nodes = None):
    if nodes == None:
        return
    self.nodes = nodes

def points(self):
    if len(self.nodes) == 0:
        return []
    points = []
    points.append(self.nodes[0].p)
    for i in range(1,len(self.nodes)):
        points += self._segment_points(i-1,i)
    if self.closed:
        points += self._segment_points(i,0)
        points = points[:-1]
    return points

def _segment_points(self,i,j):
    points = []
    n0 = self.nodes[i]
    n1 = self.nodes[j]
    pts = self.bezier_curve_points(n0.p,n0.p1,n1.p0,n1.p)
    points += pts[1:]
    return points

def bezier_curve_points(self,p0,p1,p2,p3):
    points = []
    for i in range(self.steps+1):
        t = float(i)/self.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

class Pattan: degree = -60 # -90 <= degree <= 90 lines = [] space = 10 def init(self,path): self.path = path self.update()

def update(self):
    #range
    area = [float('inf'),float('inf'),0.0,0.0] #xmin,ymin,xmax,ymax
    for p in self.path.points():
        if   area[0] > p[0]: area[0] = p[0]
        elif area[2] < p[0]: area[2] = p[0]
        if   area[1] > p[1]: area[1] = p[1]
        elif area[3] < p[1]: area[3] = p[1]

    #lines
    lines = []
    sp = self.space
    if self.degree == 90 or self.degree == -90:
        a = 1.0
        b = 0.0
        count = int((area[2]-area[0])/sp+1)
        for n in range(count):
            x = area[0] +sp*n -sp/2
            c = -x
            lines.append((a,b,c))
    else:
        a = math.tan(math.radians(self.degree))
        b = -1.0
        sp = abs(sp/math.cos(math.radians(self.degree)))
        dy = abs(a*(area[2]-area[0]))
        count = int(abs(area[3]-area[1]+dy)/sp+1)
        if a < 0:
            start_y = area[1]-sp/2
        else:
            start_y = area[1]-dy-sp/2
        x = area[0]
        for n in range(count):
            y = start_y +sp*n
            c = -a*x -b*y
            lines.append((a,b,c))
    #cut lines
    self.lines = []
    self.cross_points = []
    nodes = self.path.nodes + [self.path.nodes[0]]
    for line in lines:
        cross = []
        for i in range(len(self.path.nodes)):
            p0 = nodes[i].p
            p1 = nodes[i].p1
            p2 = nodes[i+1].p0
            p3 = nodes[i+1].p
            pts = intersection_of_bezier_and_line((p0,p1,p2,p3),line)
            cross += pts
        self.cross_points += cross #for debug
        if len(cross) % 2 != 0:
            print('error Pattan update()')
            continue
        cross.sort()
        for j in range(0,len(cross),2):
            new_line = cross[j] + cross[j+1]
            self.lines.append(new_line)

def intersection_of_bezier_and_line((p0,p1,p2,p3),(a,b,c)): f0 = a p0[0] + b p0[1] + c f1 = 3 (a p1[0] + b p1[1] + c) f2 = 3 (a p2[0] + b p2[1] + c) f3 = a p3[0] + b p3[1] + c

_a = -f0 + f1 - f2 + f3
_b = 3 * f0 - 2 * f1 + f2
_c = -3 * f0 + f1
_d = f0
if _a == 0:
    if _b == 0:
        if _c == 0: tlist = []
        else: tlist = -_d/_c
    else:
        tlist = quadratic_equation(_b,_c,_d)
else:
    tlist = cubic_equation(_a,_b,_c,_d)
points = []
for t in tlist:
    if t < 0 or 1 < t:
        continue
    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 cubic_equation(a,b,c,d): p = -b2/(9.0*a*2) + c/(3.0a) q = b3/(27.0*a3) - bc/(6.0a2) + d/(2.0a) t = complex(q2+p3) w =(-1.0 +1j3.0**0.5)/2.0

u = [0,0,0]
u[0] = (-q +t**0.5)**(1.0/3.0)
u[1] = u[0] * w
u[2] = u[0] * w**2
v = [0,0,0]
v[0] = (-q -t**0.5)**(1.0/3.0)
v[1] = v[0] * w
v[2] = v[0] * w**2

x_list = []
for i in range(3):
  for j in range(3):
    if abs(u[i]*v[j] + p) < 0.0001:
        x = u[i] + v[j]
        if abs(x.imag) < 0.0000001:
            x = x.real - b/(3.0*a)
            x_list.append(x)
return x_list

def quadratic_equation(a,b,c): t = complex(bb-4a*c)*0.5 x0 = (-b+t)/(2.0a) x1 = (-b-t)/(2.0*a) return [x0.real,x1.real]

def main(): window = Tkinter.Tk() canvas = Tkinter.Canvas(window,width=300,height=200) canvas.pack()

n0 = Node([20,50],[30,10],[10,100])
n1 = Node([50,180],[20,170],[80,190])  
n2 = Node([140,100],[50,90],[120,190])
n3 = Node([280,70],[250,150],[220,10])

path = Path([n0,n1,n2,n3])
path.closed = True
#n1.p1_avoid = True
#n2.p0_avoid = True
path.pattan = Pattan(path)

if path.closed:
    canvas.create_polygon(path.points(),fill='gray',outline='red')
else:
    canvas.create_line(path.points(),fill='red')
r = 3
for n in path.nodes:
    canvas.create_oval(n.p[0]-r,n.p[1]-r,n.p[0]+r,n.p[1]+r)
    canvas.create_oval(n.p0[0]-r,n.p0[1]-r,n.p0[0]+r,n.p0[1]+r)
    canvas.create_oval(n.p1[0]-r,n.p1[1]-r,n.p1[0]+r,n.p1[1]+r)
    canvas.create_line(n.p[0],n.p[1],n.p0[0],n.p0[1])
    canvas.create_line(n.p[0],n.p[1],n.p1[0],n.p1[1])
for xy in path.pattan.lines:
    canvas.create_line(xy)
#r = 2
#for p in path.pattan.cross_points:
#    canvas.create_oval(p[0]-r,p[1]-r,p[0]+r,p[1]+r)

window.mainloop()

if name == 'main': main()