chiark / gitweb /
curveopt: wip
[moebius3.git] / moebius.py
1
2 from __future__ import print_function
3
4 import numpy as np
5 from numpy import cos, sin
6
7 from bezier import BezierSegment
8 from moenp import *
9 from moebez import *
10 from curveopt import *
11
12 import sys
13
14 class ParametricCircle:
15   def __init__(pc, c, r0, r1):
16     ''' circle centred on c
17         with theta=0 point at c+r0
18         and with theta=tau/4 point at c+r1 '''
19     pc._c  = c
20     pc._r0 = r0
21     pc._r1 = r1
22   def radius(pc, theta):
23     return pc._r0 * cos(theta) + pc._r1 * sin(theta)
24   def point(pc, theta):
25     return pc._c + pc.radius(theta)
26
27 class Twirler(ParametricCircle):
28   def __init__(tw, c, r0, r1, cycles, begin_zeta):
29     ''' circle centred on c, etc.
30         but with an orientation at each point, orthogonal to
31           the circle
32         the orientation circles round cycles times during the
33           whole cycle (if cycles<1, to get a whole circling of
34           the dirn around the circle, must pass some theta>tau)
35         begin_zeta is the angle from outwards at theta==0
36           positive meaning in the direction of r0 x r1 from r0
37     '''
38     ParametricCircle.__init__(tw, c, r0, r1)
39     tw._cycles = cycles
40     tw._begin_zeta = begin_zeta
41     tw._axis = np.cross(r0, r1)
42   def dirn(tw, theta, extra_zeta=0):
43     ''' unit vector for dirn at theta,
44          but rotated extra_theta more around the circle
45     '''
46     zeta = tw._begin_zeta + theta * tw._cycles + extra_zeta
47     r = tw.radius(theta)
48     return cos(zeta) * r + sin(zeta) * tw._axis
49
50 class MoebiusHalf:
51   def __init__(m, nu, nt):
52     '''
53      MoebiusHalf().edge is a Twirler for the edge,
54       expecting theta = u * tau (see MoebiusHalf().point)
55       with dirn pointing into the surface
56     '''
57     m.edge    = Twirler(origin,  unit_z, unit_x, -2, tau/2)
58     m.midline = Twirler(-unit_z, unit_z, unit_y, -0.5, 0)
59     m.nu      = nu
60     m.nt      = nt
61     m._thetas = [ u * tau for u in np.linspace(0, 1, nu+1) ]
62     m._cp2b = BezierSegment([ (c,) for c in [0.33,0.33, 1.50]])
63     m._dbeziers = [ m._dbezier(theta) for theta in m._thetas ]
64     check = int(nu/3)-1
65     m._dbezier(m._thetas[check], OptimisedCurve)
66     #m._dbezier(m._thetas[check+1], HelixishCurve)
67     #m._dbezier(m._thetas[check+2], HelixishCurve)
68   def _dbezier(m, theta, dconstructor=DiscreteBezier):
69     cp = [None] * 4
70     cp[0] =               m.edge   .point(theta)
71     cp[3] =               m.midline.point(theta*2)
72     ncp3 = np.linalg.norm(cp[3])
73     cpt = ncp3 * ncp3 / 4
74     cp2scale = m._cp2b.point_at_t(ncp3/2)
75     cp1scale = 1.0 * cpt + 0.33 * (1-cpt)
76     #print('u=%d ncp3=%f cp2scale=%f' % (u, ncp3, cp2scale),
77     #      file=sys.stderr)
78     cp[1] = cp[0] + cp1scale * m.edge   .dirn (theta)
79     cp[2] = cp[3] + cp2scale * m.midline.dirn (theta*2)
80     return dconstructor(cp, m.nt)
81   def point(m, iu, it):
82     '''
83     0 <= iu <= nu     meaning 0 <= u <= 1
84                       along the extent (well, along the edge)
85                        0 and 1 are both the top half of the flat traverse
86                        0.5        is the bottom half of the flat traverse
87     0 <= it <= nt     across the half-traverse
88                        0 is the edge, 1 is the midline
89     '''
90     return np.array(m._dbeziers[iu].point_at_it(it))
91
92   def details(m, iu, t):
93     '''
94     returns tuple of 4 vectors:
95            - point location
96            - normal (+ve is in the +ve y direction at iu=t=0) unit vector
97            - along extent   (towrds +ve iu)                   unit vector
98            - along traverse (towards +ve t)                   unit vector
99     '''
100     if iu == m.nu:
101       return m.details(0, t)
102     p = m.point(iu, t)
103     vec_t = unit_v( m.point(iu, t + 0.01) - p )
104     if t == 0:
105       normal = m.edge.dirn(m._thetas[iu], extra_zeta=-tau/4)
106       vec_u = np.cross(vec_t, normal)
107     else:
108       vec_u = unit_v( m.point(iu+1, t) - p )
109       normal = np.cross(vec_u, vec_t)
110     return p, normal, vec_u, vec_t
111
112   def point_offset(m, iu, t, offset):
113     '''
114     offset by offset perpendicular to the surface
115      at the top (iu=t=0), +ve offset is in the +ve y direction
116     '''
117     p, normal, dummy, dummy = m.details(iu, t)
118     return p + offset * normal
119
120 class Moebius():
121   def __init__(m, nv, nw):
122     '''
123       0 <= v <= nw    along the extent,    v=0 is the flat traverse
124       0 <= w <= nv    across the traverse  nw must be even
125       the top is both   v=0, w=0  v=nv, w=nw
126     '''
127     assert(nw % 1 == 0)
128     m.nv = nv
129     m.nw = nw
130     m.nt = nw/2
131     m.h = MoebiusHalf(nu=nv*2, nt=m.nt)
132
133   def _vw2tiu_kw(m, v, w):
134     if w <= m.nt:
135       it = w
136       iu = v
137     else:
138       it = m.nw - w
139       iu = m.nv + v
140     #print('v,w=%d,%d => it,iu=%d,%d' % (v,w,it,iu),
141     #      file=sys.stderr)
142     return { 'it': it, 'iu': iu }
143
144   def point(m, v, w):
145     return m.h.point(**m._vw2tiu_kw(v,w))
146
147   def point_offset(m, v, w, offset):
148     return m.h.point_offset(offset=
149                             offset if w <= m.nt else -offset,
150                             **m._vw2tiu_kw(v,w))
151
152   def details(m, v, w):
153     '''
154     returns tuple of 4 vectors:
155            - point location
156            - normal (+ve is in the +ve y direction at iu=t=0) unit vector
157            - along extent   (towrds +ve v)                    unit vector
158            - along traverse (towards +ve w)                   unit vector
159     '''
160     p, normal, vec_v, vec_w = m.h.details(**m._vw2tiu_kw(v,w))
161     if w > m.nt:
162       normal = -normal
163       vec_w  = -vec_w
164     return p, normal, vec_v, vec_w