2 from __future__ import print_function
5 from numpy import cos, sin
7 from bezier import BezierSegment
13 origin = np.array((0,0,0))
14 unit_x = np.array((1,0,0))
15 unit_y = np.array((0,1,0))
16 unit_z = np.array((0,0,1))
18 class DoubleCubicBezier():
20 single = BezierSegment(cp)
21 midpoint = np.array(single.point_at_t(0.5))
22 mid_dirn = single.point_at_t(0.5 + 0.001) - midpoint
23 mid_dirn /= np.linalg.norm(mid_dirn)
25 mid_scale = ocp_factor * 0.5 * (np.linalg.norm(cp[1] - cp[0]) +
26 np.linalg.norm(cp[3] - cp[2]))
27 db.b0 = BezierSegment([ cp[0],
28 cp[1] * ocp_factor + cp[0] * (1-ocp_factor),
29 midpoint - mid_dirn * mid_scale,
31 db.b1 = BezierSegment([ midpoint,
32 midpoint + mid_dirn * mid_scale,
33 cp[2] * ocp_factor + cp[3] * (1-ocp_factor),
35 def point_at_t(db, t):
37 return db.b0.point_at_t(t*2)
39 return db.b1.point_at_t(t*2 - 1)
41 class ParametricCircle:
42 def __init__(pc, c, r0, r1):
43 ''' circle centred on c
44 with theta=0 point at c+r0
45 and with theta=tau/4 point at c+r1 '''
49 def radius(pc, theta):
50 return pc._r0 * cos(theta) + pc._r1 * sin(theta)
52 return pc._c + pc.radius(theta)
54 class Twirler(ParametricCircle):
55 def __init__(tw, c, r0, r1, cycles, begin_zeta):
56 ''' circle centred on c, etc.
57 but with an orientation at each point, orthogonal to
59 the orientation circles round cycles times during the
60 whole cycle (if cycles<1, to get a whole circling of
61 the dirn around the circle, must pass some theta>tau)
62 begin_zeta is the angle from outwards at theta==0
63 positive meaning in the direction of r0 x r1 from r0
65 ParametricCircle.__init__(tw, c, r0, r1)
67 tw._begin_zeta = begin_zeta
68 tw._axis = np.cross(r0, r1)
69 def dirn(tw, theta, extra_zeta=0):
70 ''' unit vector for dirn at theta,
71 but rotated extra_theta more around the circle
73 zeta = tw._begin_zeta + theta * tw._cycles + extra_zeta
75 return cos(zeta) * r + sin(zeta) * tw._axis
80 MoebiusHalf().edge is a Twirler for the edge,
81 expecting theta = u * tau (see MoebiusHalf().point)
82 with dirn pointing into the surface
84 m.edge = Twirler(origin, unit_z, unit_x, -2, tau/2)
85 m.midline = Twirler(-unit_z, unit_z, unit_y, -0.5, 0)
87 m._thetas = [ u * tau for u in np.linspace(0, 1, nu+1) ]
88 m._cp2b = BezierSegment([ (c,) for c in [0.33,0.33, 1.50]])
89 m._beziers = [ m._bezier(theta) for theta in m._thetas ]
90 def _bezier(m, theta):
92 cp[0] = m.edge .point(theta)
93 cp[3] = m.midline.point(theta*2)
94 ncp3 = np.linalg.norm(cp[3])
96 cp2scale = m._cp2b.point_at_t(ncp3/2)
97 cp1scale = 1.0 * cpt + 0.33 * (1-cpt)
98 #print('u=%d ncp3=%f cp2scale=%f' % (u, ncp3, cp2scale),
100 cp[1] = cp[0] + cp1scale * m.edge .dirn (theta)
101 cp[2] = cp[3] + cp2scale * m.midline.dirn (theta*2)
102 return DoubleCubicBezier(cp)
105 0 <= iu <= nu meaning 0 <= u <= 1
106 along the extent (well, along the edge)
107 0 and 1 are both the top half of the flat traverse
108 0.5 is the bottom half of the flat traverse
109 0 <= t <= 1 across the half-traverse
110 0 is the edge, 1 is the midline
112 return np.array(m._beziers[iu].point_at_t(t))
114 def point_offset(m, iu, t, offset):
116 offset by offset perpendicular to the surface
117 at the top (iu=t=0), in the y direction
121 dirn = m.edge.dirn(m._thetas[iu], extra_zeta=-tau/4)
123 return m.point_offset(0, t, offset)
125 vec_t = m.point(iu, t + 0.01) - p
126 vec_u = m.point(iu+1, t) - p
127 dirn = np.cross(vec_u, vec_t)
128 return p + offset * dirn / np.linalg.norm(dirn)
131 def __init__(m, nv, nw):
133 0 <= v <= nw along the extent, v=0 is the flat traverse
134 0 <= w <= nv across the traverse nw must be even
135 the top is both v=0, w=0 v=nv, w=nw
141 m._t_vals = np.linspace(0, 1, m.nt+1)
142 m.h = MoebiusHalf(nu=nv*2)
144 def _vw2tiu_kw(m, v, w):
151 #print('v,w=%d,%d => it,iu=%d,%d' % (v,w,it,iu),
153 return { 't': m._t_vals[it], 'iu': iu }
156 return m.h.point(**m._vw2tiu_kw(v,w))
158 def point_offset(m, v, w, offset):
159 return m.h.point_offset(offset=
160 offset if w <= m.nt else -offset,