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
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
|
-- Copyright 2014 by Till Tantau
--
-- This file may be distributed an/or modified
--
-- 1. under the LaTeX Project Public License and/or
-- 2. under the GNU Public License
--
-- See the file doc/generic/pgf/licenses/LICENSE for more information
-- @release $Header: /cvsroot/pgf/pgf/generic/pgf/graphdrawing/lua/pgf/gd/model/Path_arced.lua,v 1.2 2015/05/08 10:23:50 tantau Exp $
local Path = require 'pgf.gd.model.Path'
-- Imports
local Coordinate = require "pgf.gd.model.Coordinate"
local Transform = require "pgf.gd.lib.Transform"
-- Locals
local rigid = Path.rigid
local tan = math.tan
local sin = math.sin
local cos = math.cos
local sqrt = math.sqrt
local atan2 = math.atan2
local abs = math.abs
local to_rad = math.pi/180
local to_deg = 180/math.pi
local pi_half = math.pi/2
local function sin_quarter(x)
x = x % 360
if x == 0 then
return 0
elseif x == 90 then
return 1
elseif x == 180 then
return 0
else
return -1
end
end
local function cos_quarter(x)
x = x % 360
if x == 0 then
return 1
elseif x == 90 then
return 0
elseif x == 180 then
return -1
else
return 0
end
end
local function atan2deg(y,x)
-- Works like atan2, but returns the angle in degrees and, returns
-- exactly a multiple of 90 if x or y are zero
if x == 0 then
if y < 0 then
return -90
else
return 90
end
elseif y == 0 then
if x < 0 then
return 180
else
return 0
end
else
return atan2(y,x) * to_deg
end
end
local function subarc (path, startx, starty, start_angle, delta, radius, trans, center_x, center_y)
local end_angle = start_angle + delta
local factor = tan (delta*to_rad/4) * 1.333333333333333333333 * radius
local s1, c1, s190, c190, s2, c2, s290, c290
if start_angle % 90 == 0 then
s1, c1, s190, c190 = sin_quarter(start_angle), cos_quarter(start_angle), sin_quarter(start_angle+90), cos_quarter(start_angle+90)
else
local a1 = start_angle*to_rad
s1, c1, s190, c190 = sin(a1), cos(a1), sin(a1+pi_half), cos(a1+pi_half)
end
if end_angle % 90 == 0 then
s2, c2, s290, c290 = sin_quarter(end_angle), cos_quarter(end_angle), sin_quarter(end_angle-90), cos_quarter(end_angle-90)
else
local a2 = end_angle * to_rad
s2, c2, s290, c290 = sin(a2), cos(a2), sin(a2-pi_half), cos(a2-pi_half)
end
local lastx, lasty = center_x + c2*radius, center_y + s2*radius
path[#path + 1] = "curveto"
path[#path + 1] = Coordinate.new (startx + c190*factor, starty + s190*factor)
path[#path + 1] = Coordinate.new (lastx + c290*factor, lasty + s290*factor)
path[#path + 1] = Coordinate.new (lastx, lasty)
if trans then
path[#path-2]:apply(trans)
path[#path-1]:apply(trans)
path[#path ]:apply(trans)
end
return lastx, lasty, end_angle
end
local function arc (path, start, start_angle, end_angle, radius, trans, centerx, centery)
-- @param path is the path object
-- @param start is the start coordinate
-- @param start_angle is given in degrees
-- @param end_angle is given in degrees
-- @param radius is the radius
-- @param trans is an optional transformation matrix that gets applied to all computed points
-- @param centerx optionally: x-part of the center of the circle
-- @param centery optionally: y-part of the center of the circle
local startx, starty = start.x, start.y
-- Compute center:
centerx = centerx or startx - cos(start_angle*to_rad)*radius
centery = centery or starty - sin(start_angle*to_rad)*radius
if start_angle < end_angle then
-- First, ensure that the angles are in a reasonable range:
start_angle = start_angle % 360
end_angle = end_angle % 360
if end_angle <= start_angle then
-- In case the modulo has inadvertedly moved the end angle
-- before the start angle:
end_angle = end_angle + 360
end
-- Ok, now create a series of arcs that are at most quarter-cycles:
while start_angle < end_angle do
if start_angle + 179 < end_angle then
-- Add a quarter cycle:
startx, starty, start_angle = subarc(path, startx, starty, start_angle, 90, radius, trans, centerx, centery)
elseif start_angle + 90 < end_angle then
-- Add 60 degrees to ensure that there are no small segments
-- at the end
startx, starty, start_angle = subarc(path, startx, starty, start_angle, (end_angle-start_angle)/2, radius, trans, centerx, centery)
else
subarc(path, startx, starty, start_angle, end_angle - start_angle, radius, trans, centerx, centery)
break
end
end
elseif start_angle > end_angle then
-- First, ensure that the angles are in a reasonable range:
start_angle = start_angle % 360
end_angle = end_angle % 360
if end_angle >= start_angle then
-- In case the modulo has inadvertedly moved the end angle
-- before the start angle:
end_angle = end_angle - 360
end
-- Ok, now create a series of arcs that are at most quarter-cycles:
while start_angle > end_angle do
if start_angle - 179 > end_angle then
-- Add a quarter cycle:
startx, starty, start_angle = subarc(path, startx, starty, start_angle, -90, radius, trans, centerx, centery)
elseif start_angle - 90 > end_angle then
-- Add 60 degrees to ensure that there are no small segments
-- at the end
startx, starty, start_angle = subarc(path, startx, starty, start_angle, (end_angle-start_angle)/2, radius, trans, centerx, centery)
else
subarc(path, startx, starty, start_angle, end_angle - start_angle, radius, trans, centerx, centery)
break
end
end
-- else, do nothing
end
end
-- Doc see Path.lua
function Path:appendArc(start_angle,end_angle,radius, trans)
local start = rigid(self[#self])
assert(type(start) == "table", "trying to append an arc to a path that does not end with a coordinate")
if trans then
start = start:clone()
start:apply(Transform.invert(trans))
end
arc (self, start, start_angle, end_angle, radius, trans)
end
-- Doc see Path.lua
function Path:appendArcTo (target, radius_or_center, clockwise, trans)
local start = rigid(self[#self])
assert(type(start) == "table", "trying to append an arc to a path that does not end with a coordinate")
local trans_target = target
local centerx, centery, radius
if type(radius_or_center) == "number" then
radius = radius_or_center
else
centerx, centery = radius_or_center.x, radius_or_center.y
end
if trans then
start = start:clone()
trans_target = target:clone()
local itrans = Transform.invert(trans)
start:apply(itrans)
trans_target:apply(itrans)
if centerx then
local t = radius_or_center:clone()
t:apply(itrans)
centerx, centery = t.x, t.y
end
end
if not centerx then
-- Compute center
local dx, dy = target.x - start.x, target.y - start.y
if abs(dx) == abs(dy) and abs(dx) == radius then
if (dx < 0 and dy < 0) or (dx > 0 and dy > 0) then
centerx = start.x
centery = trans_target.y
else
centerx = trans_target.x
centery = start.y
end
else
local l_sq = dx*dx + dy*dy
if l_sq >= radius*radius*4*0.999999 then
centerx = (start.x+trans_target.x) / 2
centery = (start.y+trans_target.y) / 2
assert(l_sq <= radius*radius*4/0.999999, "radius too small for arc")
else
-- Normalize
local l = sqrt(l_sq)
local nx = dx / l
local ny = dy / l
local e = sqrt(radius*radius - 0.25*l_sq)
centerx = start.x + 0.5*dx - ny*e
centery = start.y + 0.5*dy + nx*e
end
end
end
local start_dx, start_dy, target_dx, target_dy =
start.x - centerx, start.y - centery,
trans_target.x - centerx, trans_target.y - centery
if not radius then
-- Center is given, compute radius:
radius_sq = start_dx^2 + start_dy^2
-- Ensure that the circle is, indeed, centered:
assert (abs(target_dx^2 + target_dy^2 - radius_sq)/radius_sq < 1e-5, "attempting to add an arc with incorrect center")
radius = sqrt(radius_sq)
end
-- Compute start and end angle:
local start_angle = atan2deg(start_dy, start_dx)
local end_angle = atan2deg(target_dy, target_dx)
if clockwise then
if end_angle > start_angle then
end_angle = end_angle - 360
end
else
if end_angle < start_angle then
end_angle = end_angle + 360
end
end
arc (self, start, start_angle, end_angle, radius, trans, centerx, centery)
-- Patch last point to avoid rounding problems:
self[#self] = target
end
-- Done
return true
|