-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpoly.lua
197 lines (187 loc) · 5.43 KB
/
poly.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
local util = require "util"
local pi = math.pi
local sqrt2 = math.sqrt(2)
local function dashed(points, pattern)
local x0, y0, x1, y1
local cx, cy
local i, j
local d, h
local polylines = {}
local polyline
local draw = true
x0, y0 = unpack(points[1])
polyline = {{x0, y0}}
i = 2
x1, y1 = unpack(points[i])
j = 1
d = pattern[j]
while true do
h = util.hypot(x1-x0, y1-y0)
if d < h then
cx = x0 + (x1-x0)*d/h
cy = y0 + (y1-y0)*d/h
if draw then
table.insert(polyline, {cx, cy})
table.insert(polylines, polyline)
else
polyline = {{cx, cy}}
end
x0, y0 = cx, cy
draw = not draw
if j < #pattern then j = j + 1 else j = 1 end
d = pattern[j]
else
if draw then
table.insert(polyline, {x1, y1})
end
d = d - h
if i < #points then i = i + 1 else break end
x0, y0 = x1, y1
x1, y1 = unpack(points[i])
end
end
if draw then
table.insert(polyline, points[i])
table.insert(polylines, polyline)
end
return polylines
end
-- convert bezier curve {{ax, ay}, {bx, by}, {cx, cy}} to polyline
local function bezier(curve)
local a, b, c, h
local ax, ay, bx, by, cx, cy
local dx, dy, ex, ey, fx, fy
local points = {}
local stack = {curve}
while #stack > 0 do
a, b, c = unpack(table.remove(stack))
ax, ay = unpack(a)
bx, by = unpack(b)
cx, cy = unpack(c)
h = math.abs((ax-cx)*(by-ay)-(ax-bx)*(cy-ay))/util.hypot(cx-ax, cy-ay)
if h > 1 then -- split curve
dx, dy = (ax+bx)/2, (ay+by)/2
fx, fy = (bx+cx)/2, (by+cy)/2
ex, ey = (dx+fx)/2, (dy+fy)/2
table.insert(stack, {{ex, ey}, {fx, fy}, {cx, cy}})
table.insert(stack, {{ax, ay}, {dx, dy}, {ex, ey}})
else -- add point to polyline
table.insert(points, {cx, cy})
end
end
return points
end
-- convert a sequence of linked Bézier curves to a polyline
local function unfold(control_points)
local s, a, b
local px, py, qx, qy, rx, ry
s = control_points[1]
b = control_points[2]
local points = {s}
local sub
if b[3] then
table.insert(points, b)
s = b
end
for i = 3, #control_points do
a = b
b = control_points[i]
if a[3] then
if b[3] then
table.insert(points, b)
s = b
end
else
if b[3] then
px, py, _ = unpack(s)
qx, qy, _ = unpack(a)
rx, ry, _ = unpack(b)
sub = bezier({{px, py}, {qx, qy}, {rx, ry}})
for i, p in ipairs(sub) do
table.insert(points, p)
end
s = b
else
px, py, _ = unpack(s)
qx, qy, _ = unpack(a)
rx, ry, _ = unpack(b)
rx, ry = (qx+rx)/2, (qy+ry)/2
sub = bezier({{px, py}, {qx, qy}, {rx, ry}})
for i, p in ipairs(sub) do
table.insert(points, p)
end
s = {rx, ry, true}
end
end
end
return points
end
-- bezigon that approximates a circle
local function bcircle(x, y, r)
local a = r * (sqrt2-1)
return {
{x , y+r, true},
{x+a, y+r, false},
{x+r, y+a, false},
{x+r, y-a, false},
{x+a, y-r, false},
{x-a, y-r, false},
{x-r, y-a, false},
{x-r, y+a, false},
{x-a, y+r, false},
{x , y+r, true}
}
end
-- regular polygon
local function ngon(x, y, r, n, mina, maxa)
mina = mina or 0
maxa = maxa or 2 * pi
local a = 2 * pi / n -- angle between points
local pgon = {}
local px, py
local pa = mina
while pa < maxa do
px = x + r * math.cos(pa)
py = y + r * math.sin(pa)
table.insert(pgon, {px, py})
pa = pa + a
end
px = x + r * math.cos(maxa)
py = y + r * math.sin(maxa)
table.insert(pgon, {px, py})
return pgon
end
-- approximate an arc between angles mina and maxa
local function parc(x, y, r, mina, maxa)
local h = 0.5 -- maximum radius-apothem allowed
local n = math.ceil(pi / math.acos(1 - h/r)) -- # of sides
return ngon(x, y, r, n, mina, maxa)
end
-- regular polygon that approximates a circle
local function pcircle(x, y, r)
return parc(x, y, r, 0, 2 * pi)
end
local function arrow_head(x0, y0, x1, y1, w, h)
local dx, dy = x1-x0, y1-y0
local a = math.atan2(dy, dx) -- line angle
local b = math.atan2(-dx, dy) -- perpendicular angle
local mx, my = math.cos(a), math.sin(a)
local nx, ny = math.cos(b), math.sin(b)
local bx, by = x1 - mx * h, y1 - my * h -- back of arrow
local lx, ly = bx - nx * w/2, by - ny * w/2 -- left point
local rx, ry = bx + nx * w/2, by + ny * w/2 -- right point
bx, by = bx + mx * h/2, by + my * h/2 -- off-curve point
local control_points = {
{lx, ly, true},
{x1, y1, true},
{rx, ry, true},
{bx, by, false},
{lx, ly, true},
}
return unfold(control_points)
--~ return control_points
end
return {
dashed=dashed, unfold=unfold, bcircle=bcircle, ngon=ngon,
parc=parc, pcircle=pcircle, arrow_head=arrow_head
}