~tex-sx/tex-sx/development

130 by Andrew Stacey (Thargelion)
Implemented some of Bruno ideas
1
% \iffalse meta-comment
2
%<*internal>
3
\def\nameofplainTeX{plain}
4
\ifx\fmtname\nameofplainTeX\else
5
  \expandafter\begingroup
6
\fi
7
%</internal>
8
%<*install>
9
\input docstrip.tex
10
\keepsilent
11
\askforoverwritefalse
12
\preamble
13
Copyright (C) 2012 by Andrew Stacey
14
-------------------------------------------
15
16
This file may be distributed and/or modified under the
17
conditions of the LaTeX Project Public License, either version 1.3
18
of this license or (at
19
 your option) any later version.
20
The latest version of this license is in:
21
22
   http://www.latex-project.org/lppl.txt
23
24
and version 1.3 or later is part of all distributions of LaTeX
25
version 2005/12/01 or later.
26
\endpreamble
27
\generate{\file{tikzlibraryhobby.code.tex} {\from{hobby.dtx}{tikzlibrary}}}
28
\generate{\file{pgflibraryhobby.code.tex} {\from{hobby.dtx}{pgflibrary}}}
29
\generate{\file{hobby.code.tex}
30
{\from{hobby.dtx}{hobby}}}
31
\generate{\file{pml3array.sty}
32
{\from{hobby.dtx}{array}}}
33
%</install>
34
%<install>\endbatchfile
35
%<*internal>
36
\generate{
37
  \file{\jobname.ins}{\from{\jobname.dtx}{install}}
38
}
39
\ifx\fmtname\nameofplainTeX
40
  \expandafter\endbatchfile
41
\else
42
  \expandafter\endgroup
43
\fi
44
%</internal>
45
%<*driver>
46
\documentclass{l3doc}
47
\usepackage[T1]{fontenc}
48
\usepackage{csquotes}
49
\usepackage{lmodern}
50
\usepackage{tikz}
51
\usepackage{amsmath}
52
\usetikzlibrary{hobby,decorations.pathreplacing}
53
\usepackage[margin=3cm]{geometry}
54
\EnableCrossrefs
55
\CodelineIndex
56
\RecordChanges
57
\begin{document}
58
  \DocInput{\jobname.dtx}
59
\end{document}
60
%</driver>
61
% \fi
62
%
63
% \title{Hobby's Algorithm in TikZ/PGF}
64
% \author{Andrew Stacey}
65
% \date{2012-05-15}
66
% \maketitle
67
%
68
% \tikzset{
69
%   show curve controls/.style={
70
%    decoration={
71
%      show path construction,
72
%      curveto code={
73
%      \draw [blue, dashed]
74
%          (\tikzinputsegmentfirst)    -- (\tikzinputsegmentsupporta)
75
%          node [at end, draw, solid, red, inner sep=2pt]{};
76
%        \draw [blue, dashed]
77
%          (\tikzinputsegmentsupportb) -- (\tikzinputsegmentlast)
78
%          node [at start, draw, solid, red, inner sep=2pt]{};
79
%      }
80
%    },decorate
81
%  },
82
% }
83
%
84
% \begin{figure}
85
% \centering
86
% \begin{tikzpicture}[scale=.5]
87
% \draw[scale=.1,postaction=show curve controls,line width=1mm,red] (0,0)
88
% .. controls (26.76463,-1.84543) and (51.4094,14.58441) .. (60,40)
89
% .. controls (67.09875,61.00188) and (59.76253,84.57518) .. (40,90)
90
% .. controls (25.35715,94.01947) and (10.48064,84.5022) .. (10,70)
91
% .. controls (9.62895,58.80421) and (18.80421,49.62895) .. (30,50);
92
% 
93
% \fill[green] (0,0) circle[radius=2pt]
94
%  (6,4) circle[radius=2pt]
95
%  (4,9) circle[radius=2pt]
96
%  (1,7) circle[radius=2pt]
97
%  (3,5) circle[radius=2pt];
98
% \draw[postaction=show curve controls,thick] (0,0) to[curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
99
% \begin{scope}[xshift=10cm]
100
% \draw[scale=.1,postaction=show curve controls,line width=1mm,red] (0,0)
101
% .. controls (5.18756,-26.8353) and (60.36073,-18.40036) .. (60,40)
102
% .. controls (59.87714,59.889) and (57.33896,81.64203) .. (40,90)
103
% .. controls (22.39987,98.48387) and (4.72404,84.46368) .. (10,70)
104
% .. controls (13.38637,60.7165) and (26.35591,59.1351) .. (30,50)
105
% .. controls (39.19409,26.95198) and (-4.10555,21.23804) .. (0,0); % 
106
% \fill[green] (0,0) circle[radius=2pt]
107
%  (6,4) circle[radius=2pt]
108
%  (4,9) circle[radius=2pt]
109
%  (1,7) circle[radius=2pt]
110
%  (3,5) circle[radius=2pt];
111
% \draw[postaction=show curve controls,thick] (0,0) to[closed,curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
112
% \end{scope}
113
% \end{tikzpicture}
114
% \caption{Hobby's algorithm in TikZ overlaying the output of MetaPost}
115
% \end{figure}
116
%
117
% \section{Usage}
118
%
119
% The package is provided in form of a TikZ library.
120
% It can be loaded with
121
% \begin{verbatim}
122
% \usetikzlibrary{hobby}
123
% \end{verbatim}
124
%
125
% The TikZ library installs a \Verb+to path+ which draws a smooth curve through the given points:
126
%
127
% \begin{verbatim}
128
% \begin{tikzpicture}
129
% \draw (0,0) to[curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
130
% \end{tikzpicture}
131
% \end{verbatim}
132
%
133
% \begin{center}
134
% \begin{tikzpicture}[scale=.5]
135
% \draw (0,0) to[curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
136
% \end{tikzpicture}
137
% \end{center}
138
%
139
% The path can be open, as above, or closed:
140
%
141
% \begin{verbatim}
142
% \begin{tikzpicture}
143
% \draw (0,0) to[closed,curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
144
% \end{tikzpicture}
145
% \end{verbatim}
146
%
147
% \begin{center}
148
% \begin{tikzpicture}[scale=.5]
149
% \draw (0,0) to[closed,curve through={(6,4) .. (4,9) .. (1,7)}] (3,5);
150
% \end{tikzpicture}
151
% \end{center}
152
153
% There is also the facility to subvert TikZ's path processor and define curves simply using the \Verb+..+ separator between points.
154
% Note that this relies on something a little special in TikZ: the syntax \Verb+(0,0) .. (2,3)+ is currently detected and processed but there is no action assigned to that syntax.
155
% If a later version of TikZ assigns some action to that syntax, this package should make its override optional.
156
%
157
% \begin{verbatim}
158
% \begin{tikzpicture}
159
% \draw (-3,0) -- (0,0) .. (6,4) .. (4,9) .. (1,7) .. (3,5) -- ++(2,0);
160
% \end{tikzpicture}
161
% \end{verbatim}
162
%
163
% \begin{center}
164
% \begin{tikzpicture}[scale=.5]
165
% \draw (-3,0) -- (0,0) .. (6,4) .. (4,9) .. (1,7) .. (3,5) -- ++(2,0);
166
% \end{tikzpicture}
167
% \end{center}
168
%
169
% The algorithm can deal with open or closed paths, it is possible to vary the ``tensions'' between the specified points of the paths, and for an open path it is possible to specify the incoming and outgoing angles either directly or via certain ``curl'' parameters.
170
% See the Examples section for more examples.
171
%
172
% The algorithm is actually implemented in \LaTeX3 with (almost\footnote{At the moment, \LaTeX3 lacks a \Verb+atan2+ function so \Verb+PGFMath+ is used to remedy that.}) no reference to TikZ or PGF.
173
% The TikZ library is simply a wrapper that takes the user's input, converts it into the right format for the \LaTeX3 code, and then calls that code to generate the path.
174
% 
175
% \section{Implementing Hobby's Algorithm}
176
% 
177
% We start with a list of \(n+1\) points, \(z_0, \dotsc, z_n\).
178
% The base code assumes that these are already stored in two arrays\footnote{Arrays are thinly disguised property lists}: the \(x\)--coordinates in \Verb+\l_hobby_points_x_array+ and the \(y\)--coordinates in \Verb+\l_hobby_points_y_array+.
179
% As our arrays are \(0\)--indexed, the actual number of points is one more than this.
180
% For a closed curve, we have \(z_n = z_0\)\footnote{Note that there is a difference between a closed curve and an open curve whose endpoints happen to overlap}.
181
% For closed curves it will be convenient to add an additional point at \(z_1\): thus \(z_{n+1} = z_1\).
182
% This makes \(z_n\) an internal point and makes the algorithms for closed paths and open paths agree longer than they would otherwise.
183
% The number of apparent points is stored as \Verb+\l_hobby_npoints_int+.
184
% Thus for an open path, \Verb+\l_hobby_npoints_int+ is \(n\), whilst for a closed path, it is \(n+1\)\footnote{In fact, we allow for the case where the user specifies a closed path but with \(z_n \ne z_0\).
185
% In that case, we assume that the user meant to repeat \(z_0\).
186
% This adds another point to the list.}.
187
% Following Hobby, let us write \(n'\) for \(n\) if the path is open and \(n+1\) if closed.
188
%
189
% From this we compute the distances and angles between successive points, storing these again as arrays.
190
% These are \Verb+\l_hobby_distances_array+ and \Verb+\l_hobby_angles_array+.
191
% The term indexed by \(k\) is the distance (or angle) of the line between the \(k\)th point and the \(k+1\)th point.
192
% For the internal nodes\footnote{Hobby calls the specified points \emph{knots}}, we store the difference in the angles in \Verb+\l_hobby_psi_array+.
193
% The \(k\)th value on this is the angle subtended at the \(k\)th node.
194
% This is thus indexed from \(1\) to \(n'-1\).
195
% 
196
% The bulk of the work consists in setting up a linear system to compute the angles of the control points.
197
% At a node, say \(z_i\), we have various pieces of information:
198
%
199
% \begin{enumerate}
200
% \item The angle of the incoming curve, \(\phi_i\), relative to the straight line from \(z_{i-1}\) to \(z_i\)
201
% \item The angle of the outgoing curve, \(\theta_i\), relative to the straight line from \(z_i\) to \(z_{i+1}\)
202
% \item The tension of the incoming curve, \(\overline{\tau}_i\)
203
% \item The tension of the outgoing curve, \(\tau_i\)
204
% \item The speed of the incoming curve, \(\sigma_i\)
205
% \item The speed of the outgoing curve, \(\rho_i\)
206
% \end{enumerate}
207
%
208
% The tensions are known at the start.
209
% The speeds are computed from the angles.
210
% Thus the key thing to compute is the angles.
211
% This is done by imposing a ``mock curvature'' condition.%
212
% The formula for the mock curvature is:
213
%^^A
214
% \[
215
%   \hat{k}(\theta,\phi,\tau,\overline{\tau}) = \tau^2 \left( \frac{2(\theta + \phi)}{\overline{\tau}} - 6\theta\right)
216
% \]
217
%^^A
218
% and the condition that the mock curvatures have to satisfy is that at each \emph{internal} node, the curvatures must match:
219
% %
220
% \[
221
%   \hat{k}(\phi_i,\theta_{i-1},\overline{\tau}_i,\tau_{i-1})/d_{i-1} = \hat{k}(\theta_i,\phi_{i+1},\tau_i,\overline{\tau}_{i+1})/d_i.
222
% \]
223
%^^A
224
% Substituting in yields:
225
%^^A
226
% \[
227
%   \frac{\overline{\tau}_i^2}{d_{i-1}} \left( \frac{2(\phi_i + \theta_{i-1})}{\tau_{i-1}} - 6\phi_i\right) = \frac{\tau_i^2}{d_i} \left( \frac{2(\theta_i + \phi_{i+1})}{\overline{\tau}_{i+1}} - 6\theta_i \right).
228
% \]
229
%^^A
230
% Let us rearrange that to the following:
231
%^^A
232
% \begin{align*}
233
%   d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 &\theta_{i-1} \\
234
%^^A
235
% +
236
% d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (1 - 3 \tau_{i-1}) &\phi_i \\
237
%^^A
238
% -
239
%  d_{i-1} \tau_{i-1} \tau_i^2 (1 - 3 \overline{\tau}_{i+1}) &\theta_i \\
240
%^^A
241
% -
242
%  d_{i-1} \tau_{i-1} \tau_i^2 &\phi_{i+1} \\
243
%^^A
244
% =
245
% 0
246
% \end{align*}
247
%^^A
248
% For both open and closed paths this holds for \(i=1\) to \(i=n' - 1\).
249
% 
250
% We also have the condition that \(\theta_i + \phi_i = -\psi_i\) where \(\psi_i\) is the angle subtended at a node by the lines to the adjacent nodes.
251
% This holds for the internal nodes\footnote{Recall that by dint of repetition, all nodes are effectively internal for a closed path}.
252
% Therefore for \(i=1\) to \(n'-1\) the above simplifies to the following:
253
% %
254
% \begin{align*}
255
%   d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 &\theta_{i-1} \\
256
% +
257
% (d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} - 1)
258
% +
259
%  d_{i-1} \tau_{i-1} \tau_i^2 (3 \overline{\tau}_{i+1} - 1)) &\theta_i \\
260
% +
261
%  d_{i-1} \tau_{i-1} \tau_i^2 & \theta_{i+1} \\
262
% =
263
% - d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} - 1) &\psi_i \\
264
% - d_{i-1} \tau_{i-1} \tau_i^2& \psi_{i+1}
265
% \end{align*}
266
% 
267
% For an open path we have two more equations.
268
% One involves \(\theta_0\).
269
% The other is the above for \(i = n'-1 = n-1\) with additional information regarding \(\psi_n\).
270
% It may be that one or either of \(\theta_0\) or \(\psi_n\) is specified in advance.
271
% In that case, the first equation is simply setting \(\theta_0\) to that value and the last equation involves substituting the value for \(\psi_n\) into the above.
272
% If not, they are given by formulae involving ``curl'' parameters \(\chi_0\) and \(\chi_n\) and result in the equations:
273
% %
274
% \begin{align*}
275
% \theta_0 &= \frac{\tau_0^3 + \chi_0 \overline{\tau}_1^3(3 \tau_0 - 1)}{\tau_0^3(3 \overline{\tau}_1 - 1) + \chi_0 \overline{\tau}_1^3} \phi_1 \\
276
% \phi_n &= \frac{\overline{\tau}_n^3 + \chi_n \tau_{n-1}^3(3 \overline{\tau}_n - 1)}{\overline{\tau}_n^3(3 \tau_{n-1} - 1) + \chi_n \tau_{n-1}^3} \theta_{n-1}
277
% \end{align*}
278
%^^A
279
% Using \(\phi_1 = - \psi_1 - \theta_1\), the first rearranges to:
280
%^^A
281
% \[
282
% (\tau_0^3(3 \overline{\tau}_1 - 1) + \chi_0 \overline{\tau}_1^3) \theta_0 + (\tau_0^3 + \chi_0 \overline{\tau}_1^3(3 \tau_0 - 1)) \theta_1 = - (\tau_0^3 + \chi_0 \overline{\tau}_1^3(3 \tau_0 - 1)) \psi_1.
283
% \]
284
%^^A
285
% The second should be substituted in to the general equation with \(i = n-1\).
286
% This yields:
287
%^^A  
288
% \begin{align*}
289
%   d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 &\theta_{n-2} \\
290
% +
291
% (d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1)
292
% +
293
%  d_{n-2} \tau_{n-2} \tau_{n-1}^2 (3 \overline{\tau}_{n} - 1) \\
294
% - d_{n-2} \tau_{n-2} \tau_{n-1}^2  \frac{\overline{\tau}_n^3 + \chi_n \tau_{n-1}^3(3 \overline{\tau}_n - 1)}{\overline{\tau}_n^3(3 \tau_{n-1} - 1) + \chi_n \tau_{n-1}^3}) & \theta_{n-1} \\
295
% =
296
% - d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) &\psi_{n-1}
297
% \end{align*}
298
%^^A
299
% This gives \(n'\) equations in \(n'\) unknowns (\(\theta_0\) to \(\theta_{n-1}\)).
300
% The coefficient matrix is tridiagonal.
301
% It is more natural to index the entries from \(0\).
302
% Let us write \(A_i\) for the subdiagonal, \(B_i\) for the main diagonal, and \(C_i\) for the superdiagonal.
303
% Let us write \(D_i\) for the target vector.
304
% Then for an open path we have the following formulae:
305
%^^A
306
% \begin{align*}
307
% A_i &= d_i \overline{\tau}_{i+1} \overline{\tau}^2_i \\
308
% B_0 &= \begin{cases}
309
% 1 & \text{if}\; \theta_0\; \text{given} \\
310
% \tau_0^3(3 \overline{\tau}_1 - 1) + \chi_0 \overline{\tau}^3_1 & \text{otherwise}
311
% \end{cases} \\
312
% B_i &= d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} -1) + d_{i-1} \tau_{i-1} \tau_i^2(3 \overline{\tau}_{i+1} - 1) \\
313
% B_{n-1} &= \begin{cases} d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) + d_{n-2} \tau_{n-2} \tau_{n-1}^2(3 \overline{\tau}_{n} - 1) & \text{if}\; \phi_n\; \text{given} \\
314
%  d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) + d_{n-2} \tau_{n-2} \tau_{n-1}^2(3 \overline{\tau}_{n} - 1)
315
% \\
316
% - d_{n-2} \tau_{n-2} \tau_{n-1}^2  \frac{\overline{\tau}_n^3 + \chi_n \tau_{n-1}^3(3 \overline{\tau}_n - 1)}{\overline{\tau}_n^3(3 \tau_{n-1} - 1) + \chi_n \tau_{n-1}^3}) & \text{otherwise}
317
% \end{cases} \\
318
% C_0 &= \begin{cases}
319
% 0 & \text{if}\; \theta_0\; \text{given} \\
320
% \tau_0^3 + \chi_0 \overline{\tau}_1^3(3\tau_0 - 1) & \text{otherwise}
321
% \end{cases} \\
322
% C_i &= d_{i-1} \tau_{i-1} \tau_i^2 \\
323
% D_0 &= \begin{cases}
324
% \overline{\theta}_0 & \text{if}\; \theta_0\; \text{given} \\
325
%  - (\tau_0^3 + \chi_0 \overline{\tau}_1^3(3 \tau_0 - 1)) \psi_1 & \text{otherwise}
326
% \end{cases} \\
327
% D_i &= - d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} - 1) \psi_i
328
% - d_{i-1} \tau_{i-1} \tau_i^2 \psi_{i+1} \\
329
% D_{n-1} &= \begin{cases}
330
% - d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) \psi_{n-1} - d_{n-2} \tau_{n-2} \tau_{n-1}^2 \overline{\phi}_n & \text{if}\; \phi_n\; \text{given} \\
331
% - d_{n-1} \overline{\tau}_{n} \overline{\tau}_{n-1}^2 (3 \tau_{n-2} - 1) \psi_{n-1} & \text{otherwise}
332
% \end{cases}
333
% \end{align*}
334
%
335
% For a closed path, we have \(n\) equations in \(n+2\) unknowns (\(\theta_0\) to \(\theta_{n+1}\)).
336
% However, we have not included all the information.
337
% Since we have repeated points, we need to identify \(\theta_0\) with \(\theta_n\) and \(\theta_1\) with \(\theta_{n+1}\).
338
% To get a system with \(n'\) equations in \(n'\) unknowns, we add the equation \(\theta_0 - \theta_n = 0\) and substitute in \(\theta_{n+1} = \theta_1\).
339
% The resulting matrix is not quite tridiagonal but has extra entries on the off-corners.
340
% However, it can be written in the form \(M + u v^\top\) with \(M\) tridiagonal.
341
% There is some freedom in choosing \(u\) and \(v\).
342
% For simplest computation, we take \(u = e_0 + e_{n'-1}\).
343
% This means that \(v = d_{n'-2} \tau_{n'-2} \tau_{n'-1}^2 e_1 - e_{n'-1}\).
344
% With the same notation as above, the matrix \(M\) is given by the following formulae:
345
%^^A
346
% \begin{align*}
347
% A_i &= d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 \\
348
%^^A
349
% B_0 &= 1 \\
350
%^^A
351
% B_i &= d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} -1) + d_{i-1} \tau_{i-1} \tau_i^2(3 \overline{\tau}_{i+1} - 1) \\
352
%^^A
353
% B_{n'-1} &= d_{n'-1} \overline{\tau}_{n'} \overline{\tau}_{n'-1}^2 (3 \tau_{n'-2} -1) + d_{n'-2} \tau_{n'-2} \tau_{n'-1}^2(3 \overline{\tau}_{n'} - 1) + 1\\
354
%^^A
355
% C_0 &= - d_{n'-2} \tau_{n'-2} \tau_{n'-1}^2 \\
356
%^^A
357
% C_i &= d_{i-1} \tau_{i-1} \tau_i^2 \\
358
%^^A
359
% D_0 &= 0 \\
360
%^^A
361
% D_i &= - d_i \overline{\tau}_{i+1} \overline{\tau}_i^2 (3 \tau_{i-1} - 1) \psi_i
362
% - d_{i-1} \tau_{i-1} \tau_i^2 \psi_{i+1} \\
363
%^^A
364
% D_{n'-1} &= - d_{n'-1} \overline{\tau}_{n'} \overline{\tau}_{n'-1}^2 (3 \tau_{n'-2} - 1) \psi_{n'-1}
365
% - d_{n'-2} \tau_{n'-2} \tau_{n'-1}^2 \psi_1
366
% \end{align*}
367
%
368
% The next step in the implementation is to compute these coefficients and store them in appropriate arrays.
369
% Having done that, we need to solve the resulting tridiagonal system.
370
% This is done by looping through the arrays doing the following substitutions (starting at \(i = 1\)):
371
% %
372
% \begin{align*}
373
% B_i' &= B_{i-1}' B_i - A_i C_{i-1}' \\
374
% C_i' &= B_{i-1}' C_i \\
375
% D_i' &= B_{i-1}' D_i - A_i D_{i-1}'
376
% \end{align*}
377
%^^A
378
% followed by back-substitution:
379
%^^A
380
% \begin{align*}
381
% \theta_{n-1} &= D_{n-1}'/B_{n-1}' \\
382
% \theta_i &= (D_i' - C_i' \theta_{i+1})/B_i'
383
% \end{align*}
384
%^^A
385
% For a closed path, we run this both with the vector \(D\) and the vector \(u = e_0 + e_{n'-1}\).
386
% Then to get the real answer, we use the Sherman--{}Morrison formula:
387
%^^A
388
% \[
389
% (M + u v^\top)^{-1} D = M^{-1} D - \frac{M^{-1} u v^\top M^{-1} D}{1 + v^\top M^{-1} u}.
390
% \]
391
%^^A
392
% 
393
% This leaves us with the values for \(\theta_i\).
394
% We now substitute these into Hobby's formulae for the lengths:
395
% %
396
% \begin{align*}
397
% \rho_i &= \frac{2 + \alpha_i}{1 + (1 - c) \cos \theta_i + c \cos \phi_{i+1}} \\
398
% \sigma_{i+1} &= \frac{2 - \alpha_i}{1 + (1 - c) \cos \phi_{i+1} + c \cos \theta_i} \\
399
% \alpha_i &= a (\sin \theta_i - b \sin \phi_{i+1})(\sin \phi_{i+1} - b \sin \theta_i)(\cos \theta_i - \cos \phi_{i+1})
400
% \end{align*}
401
% %
402
% where \(a = \sqrt{2}\), \(b = 1/16\), and \(c = (3 - \sqrt{5})/2\).
403
% 
404
% Now \(\theta_i\) is the angle relative to the line from \(z_i\) to \(z_{i+1}\), so to get the true angle we need to add back that angle.
405
% Fortunately, we stored those angles at the start.
406
% So the control points are:
407
% %
408
% \begin{gather*}
409
%   (\rho_i \cos (\theta_i + \omega_i), \rho_i \sin (\theta_i + \omega_i)) + z_i \\
410
% (-\sigma_i \cos(\theta_i + \omega_i), -\sigma_i \sin(\theta_i + \omega_i)) + z_{i+1}
411
% \end{gather*}
412
% 
413
% \section{Examples}
414
%
415
% \begin{verbatim}
416
% \begin{tikzpicture}
417
% \draw[postaction=show curve controls]
418
% (0,0) to[curve through={(1,.5) .. (2,0) .. (3,.5)}] (4,0);
419
% \end{tikzpicture}
420
% \end{verbatim}
421
%
422
% \begin{center}
423
% \begin{tikzpicture}
424
% \draw[postaction=show curve controls]
425
% (0,0) to[curve through={(1,.5) .. (2,0) .. (3,.5)}] (4,0);
426
% \end{tikzpicture}
427
% \end{center}
428
%
429
% \begin{verbatim}
430
% \begin{tikzpicture}
431
% \draw[postaction=show curve controls]
432
% (0,0) to[out angle=0,in angle=180,curve through={(1,.5) .. (2,0) .. (3,.5)}] (4,0);
433
% \end{tikzpicture}
434
% \end{verbatim}
435
%
436
% \begin{center}
437
% \begin{tikzpicture}
438
% \draw[postaction=show curve controls]
439
% (0,0) to[out angle=0,in angle=180,curve through={(1,.5) .. (2,0) .. (3,.5)}] (4,0);
440
% \end{tikzpicture}
441
% \end{center}
442
%
443
% \begin{verbatim}
444
% \begin{tikzpicture}
445
% \draw[postaction=show curve controls]
446
% (0,0) to[curve through={(1,.5) .. ([tension in=2]2,0) .. (3,.5)}] (4,0);
447
% \end{tikzpicture}
448
% \end{verbatim}
449
%
450
% \begin{center}
451
% \begin{tikzpicture}
452
% \draw[postaction=show curve controls]
453
% (0,0) to[curve through={(1,.5) .. ([tension in=2]2,0) .. (3,.5)}] (4,0);
454
% \end{tikzpicture}
455
% \end{center}
456
%
457
% \begin{verbatim}
458
% \begin{tikzpicture}
459
% \draw[postaction=show curve controls]
460
% (0,0) to[curve through={(1,.5) .. ([tension=2]2,0) .. (3,.5)}] (4,0);
461
% \end{tikzpicture}
462
% \end{verbatim}
463
%
464
% \begin{center}
465
% \begin{tikzpicture}
466
% \draw[postaction=show curve controls]
467
% (0,0) to[curve through={(1,.5) .. ([tension=2]2,0) .. (3,.5)}] (4,0);
468
% \end{tikzpicture}
469
% \end{center}
470
%
471
% \begin{verbatim}
472
% \begin{tikzpicture}
473
% \draw[postaction=show curve controls]
474
% (0,0) .. (1,.5) .. (2,0) .. (3,.5) .. (4,0);
475
% \end{tikzpicture}
476
% \end{verbatim}
477
%
478
% \begin{center}
479
% \begin{tikzpicture}
480
% \draw[postaction=show curve controls]
481
% (0,0) .. (1,.5) .. (2,0) .. (3,.5) .. (4,0);
482
% \end{tikzpicture}
483
% \end{center}
484
%
485
% \begin{verbatim}
486
% \begin{tikzpicture}[scale=.5]
487
% \draw (0,0) .. (6,4) .. (4,9) .. (1,7) .. (3,5) .. cycle;
488
% \end{tikzpicture}
489
% \end{verbatim}
490
%
491
% \begin{center}
492
% \begin{tikzpicture}[scale=.5]
493
% \draw (0,0) .. (6,4) .. (4,9) .. (1,7) .. (3,5) .. cycle;
494
% \end{tikzpicture}
495
% \end{center}
496
%
497
% \section{Edge Cases}
498
%
499
% Angles are constrained to lie in the interval \((-\pi,pi]\).
500
% This can introduce edge cases as there is a point where we have to compare an angle with \(-\pi\) and if it is equal, add \(2 \pi\).
501
% This will occur if the path ``doubles back'' on itself as in the next example.
502
% By nudging the repeated point slightly, the behaviour changes drastically.
503
%
504
% \begin{verbatim}
505
% \begin{tikzpicture}
506
% \draw (0,0) .. (1,0) .. (0,0) .. (0,-1);
507
% \draw[xshift=2cm] (0,0) .. (1,0) .. (0,0.1) .. (0,-1);
508
% \draw[xshift=4cm] (0,0) .. (1,0) .. (0,-0.1) .. (0,-1);
509
% \end{tikzpicture}
510
% \end{verbatim}
511
%
512
% \begin{center}
513
% \begin{tikzpicture}
514
% \draw (0,0) .. (1,0) .. (0,0) .. (0,-1);
515
% \draw[xshift=2cm] (0,0) .. (1,0) .. (0,0.1) .. (0,-1);
516
% \draw[xshift=4cm] (0,0) .. (1,0) .. (0,-0.1) .. (0,-1);
517
% \end{tikzpicture}
518
% \end{center}
519
%
520
% Due to the precision of the computations, it is not possible to always get this test correct.
521
% The simplest solution is to nudge the repeated point in one direction or the other.
522
% Experimenting shows that the ``nudge factor'' can be extremely small (note that it will be proportional to the distance between the specified points).
523
% It is best to nudge it in the direction most normal to the line between the specified points as the goal is to nudge the difference of the angles.
524
% An alternative solution is to add an additional point for the curve to go through.
525
%
526
% \begin{verbatim}
527
% \begin{tikzpicture}
528
% \draw (0,0) .. (1,0) .. (0,0) .. (0,-1);
529
% \draw[xshift=2cm] (0,0) .. (1,0) .. (0,0.002) .. (0,-1);
530
% \draw[xshift=4cm] (0,0) .. (1,0) .. (0,-0.002) .. (0,-1);
531
% \end{tikzpicture}
532
% \end{verbatim}
533
%
534
% \begin{center}
535
% \begin{tikzpicture}
536
% \draw (0,0) .. (1,0) .. (0,0) .. (0,-1);
537
% \draw[xshift=2cm] (0,0) .. (1,0) .. (0,0.002) .. (0,-1);
538
% \draw[xshift=4cm] (0,0) .. (1,0) .. (0,-0.002) .. (0,-1);
539
% \end{tikzpicture}
540
% \end{center}
541
%
542
% Lastly, it is possible to add an \Verb+excess angle+ key to a coordinate.
543
% This will add the corresponding multiple of \(2\pi\) to the angle difference.
544
%
545
% \begin{verbatim}
546
% \begin{tikzpicture}
547
% \draw (0,0) .. (1,0) .. (0,0) .. (0,-1);
548
% \draw[xshift=2cm] (0,0) .. ([excess angle=1]1,0) .. (0,0) .. (0,-1);
549
% \draw[xshift=4cm] (0,0) .. ([excess angle=-1]1,0) .. (0,0) .. (0,-1);
550
% \end{tikzpicture}
551
% \end{verbatim}
552
%
553
% \begin{center}
554
% \begin{tikzpicture}
555
% \draw (0,0) .. (1,0) .. (0,0) .. (0,-1);
556
% \draw[xshift=2cm] (0,0) .. ([excess angle=1]1,0) .. (0,0) .. (0,-1);
557
% \draw[xshift=4cm] (0,0) .. ([excess angle=-1]1,0) .. (0,0) .. (0,-1);
558
% \end{tikzpicture}
559
% \end{center}
560
%
561
% Although this is intended to be an integer, no check is done and so some quite odd curves can result from changing this parameter.
562
%
563
% \StopEventually{\PrintChanges}
564
% \section{Implementation}
565
%
566
% \subsection{Main Code}
567
%
568
% \iffalse
569
%<*hobby>
570
% \fi
571
%
572
% We use \LaTeX3 syntax so need to load the requisite packages
573
%    \begin{macrocode}
574
\RequirePackage{expl3}
575
\RequirePackage{xparse}
576
\RequirePackage{pml3array}
577
\ExplSyntaxOn
578
%    \end{macrocode}
579
% \subsubsection{Initialisation}
580
%
581
% We declare all our variables.
582
%
583
% Now we define our objects for use in generating the path.
584
%
585
% \begin{macro}{\l_hobby_closed_bool}
586
% \Verb+\l_hobby_closed_bool+ is \Verb+true+ if the path is closed.
587
%    \begin{macrocode}
588
\bool_new:N \l_hobby_closed_bool
589
%    \end{macrocode}
590
% \end{macro}
591
%
592
% \begin{macro}{\l_hobby_disjoint_bool}
593
% \Verb+\l_hobby_disjoint_bool+ is \Verb+true+ if the path should start with a \Verb+moveto+ command.
594
%    \begin{macrocode}
595
\bool_new:N \l_hobby_disjoint_bool
596
%    \end{macrocode}
597
% \end{macro}
598
%
599
% \begin{macro}{\l_hobby_points_array}
600
% \Verb+\l_hobby_points_array+ is an array holding the specified points on the path.
601
% In the \LaTeX3 code, a ``point'' is a token list of the form \Verb+x = <number>, y = <number>+.
602
% This gives us the greatest flexibility in passing points back and forth between the \LaTeX3 code and any calling code.
603
% The array is indexed by integers beginning with \(0\).
604
% In the documentation, we will use the notation \(z_k\) to refer to the \(k\)th point.
605
%    \begin{macrocode}
606
\array_new:N \l_hobby_points_array
607
%    \end{macrocode}
608
% \end{macro}
609
%
610
% \begin{macro}{\l_hobby_points_x_array}
611
% \Verb+\l_hobby_points_x_array+ is an array holding the \(x\)--{}coordinates of the specified points.
612
%    \begin{macrocode}
613
\array_new:N \l_hobby_points_x_array
614
%    \end{macrocode}
615
% \end{macro}
616
%
617
% \begin{macro}{\l_hobby_points_y_array}
618
% \Verb+\l_hobby_points_y_array+ is an array holding the \(y\)--{}coordinates of the specified points.
619
%    \begin{macrocode}
620
\array_new:N \l_hobby_points_y_array
621
%    \end{macrocode}
622
% \end{macro}
623
%
624
% \begin{macro}{\l_hobby_angles_array}
625
% \Verb+\l_hobby_angles_array+ is an array holding the angles of the lines between the points.
626
% Specifically, the angle indexed by \(k\) is the angle in radians of the line from \(z_k\) to \(z_{k+1}\).
627
%    \begin{macrocode}
628
\array_new:N \l_hobby_angles_array
629
%    \end{macrocode}
630
% \end{macro}
631
%
632
% \begin{macro}{\l_hobby_distances_array}
633
% \Verb+\l_hobby_distances_array+ is an array holding the distances between the points.
634
% Specifically, the distance indexed by \(k\), which we will write as \(d_k\), is the length of the line from \(z_k\) to \(z_{k+1}\).
635
%    \begin{macrocode}
636
\array_new:N \l_hobby_distances_array
637
%    \end{macrocode}
638
% \end{macro}
639
%
640
% \begin{macro}{\l_hobby_tension_out_array}
641
% \Verb+\l_hobby_tension_out_array+ is an array holding the tension for the path as it leaves each point.
642
% This is a parameter that controls how much the curve ``flexes'' as it leaves the point.
643
% In the following, this will be written \(\tau_k\).
644
%    \begin{macrocode}
645
\array_new:N \l_hobby_tension_out_array
646
%    \end{macrocode}
647
% \end{macro}
648
%
649
% \begin{macro}{\l_hobby_tension_in_array}
650
% \Verb+\l_hobby_tension_in_array+ is an array holding the tension for the path as it arrives at each point.
651
% This is a parameter that controls how much the curve ``flexes'' as it gets to the point.
652
% In the following, this will be written \(\overline{\tau}_k\).
653
%    \begin{macrocode}
654
\array_new:N \l_hobby_tension_in_array
655
%    \end{macrocode}
656
% \end{macro}
657
%
658
% \begin{macro}{\l_hobby_tria_array}
659
% \Verb+\l_hobby_tria_array+ is an array holding the subdiagonal of the linear system that has to be solved to find the angles of the control points.
660
% In the following, this will be denoted by \(A_i\).
661
% The first index is \(1\).
662
%    \begin{macrocode}
663
\array_new:N \l_hobby_tria_array
664
%    \end{macrocode}
665
% \end{macro}
666
%
667
% \begin{macro}{\l_hobby_trib_array}
668
% \Verb+\l_hobby_trib_array+ is an array holding the diagonal of the linear system that has to be solved to find the angles of the control points.
669
% In the following, this will be denoted by \(B_i\).
670
% The first index is \(0\).
671
%    \begin{macrocode}
672
\array_new:N \l_hobby_trib_array
673
%    \end{macrocode}
674
% \end{macro}
675
%
676
% \begin{macro}{\l_hobby_tric_array}
677
% \Verb+\l_hobby_tric_array+ is an array holding the superdiagonal of the linear system that has to be solved to find the angles of the control points.
678
% In the following, this will be denoted by \(C_i\).
679
% The first index is \(0\).
680
%    \begin{macrocode}
681
\array_new:N \l_hobby_tric_array
682
%    \end{macrocode}
683
% \end{macro}
684
%
685
% \begin{macro}{\l_hobby_trid_array}
686
% \Verb+\l_hobby_trid_array+ is an array holding the target vector of the linear system that has to be solved to find the angles of the control points.
687
% In the following, this will be denoted by \(D_i\).
688
% The first index is \(1\).
689
%    \begin{macrocode}
690
\array_new:N \l_hobby_trid_array
691
%    \end{macrocode}
692
% \end{macro}
693
%
694
% \begin{macro}{\l_hobby_triu_array}
695
% \Verb+\l_hobby_triu_array+ is an array holding the perturbation of the linear system for closed paths.
696
% The coefficient matrix for a \emph{open} path is tridiagonal and that means that Gaussian elimination runs faster than expected (\(O(n)\) instead of \(O(n^3)\)).
697
% The matrix for a closed path is not tridiagonal but is not far off.
698
% It can be solved by perturbing it to a tridiagonal matrix and then modifying the result.
699
% This array represents a utility vector in that perturbation. 
700
% In the following, the vector will be denoted by \(u\).
701
% The first index is \(1\).
702
%    \begin{macrocode}
703
\array_new:N \l_hobby_triu_array
704
%    \end{macrocode}
705
% \end{macro}
706
%
707
% \begin{macro}{\l_hobby_excessangle_array}
708
% \Verb+\l_hobby_excessangle_array+ is an array that allows the user to say that the algorithm should add a multiple of \(2 \pi\) to the angle differences.
709
% This is because these angles are wrapped to the interval \((-\pi,\pi]\) but the wrapping might go wrong near the end points due to computation accuracy.
710
% The first index is \(1\).
711
%    \begin{macrocode}
712
\array_new:N \l_hobby_excessangle_array
713
%    \end{macrocode}
714
% \end{macro}
715
%
716
% \begin{macro}{\l_hobby_psi_array}
717
% \Verb+\l_hobby_psi_array+ is an array holding the difference of the angles of the lines entering and exiting a point.
718
% That is, \(\psi_k\) is the angle between the lines joining \(z_k\) to \(z_{k-1}\) and \(z_{k+1}\).
719
% The first index is \(1\).
720
%    \begin{macrocode}
721
\array_new:N \l_hobby_psi_array
722
%    \end{macrocode}
723
% \end{macro}
724
%
725
% \begin{macro}{\l_hobby_theta_array}
726
% \Verb+\l_hobby_theta_array+ is an array holding the angles of the outgoing control points for the generated path.
727
% These are measured relative to the line joining the point to the next point on the path.
728
% The first index is \(0\).
729
%    \begin{macrocode}
730
\array_new:N \l_hobby_theta_array
731
%    \end{macrocode}
732
% \end{macro}
733
%
734
% \begin{macro}{\l_hobby_phi_array}
735
% \Verb+\l_hobby_phi_array+ is an array holding the angles of the incoming control points for the generated path.
736
% These are measured relative to the line joining the point to the previous point on the path.
737
% The first index is \(1\).
738
%    \begin{macrocode}
739
\array_new:N \l_hobby_phi_array
740
%    \end{macrocode}
741
% \end{macro}
742
%
743
% \begin{macro}{\l_hobby_sigma_array}
744
% \Verb+\l_hobby_sigma_array+ is an array holding the lengths of the outgoing control points for the generated path.
745
% The units are such that the length of the line to the next specified point is one unit.
746
%    \begin{macrocode}
747
\array_new:N \l_hobby_sigma_array
748
%    \end{macrocode}
749
% \end{macro}
750
%
751
% \begin{macro}{\l_hobby_rho_array}
752
% \Verb+\l_hobby_rho_array+ is an array holding the lengths of the incoming control points for the generated path.
753
% The units are such that the length of the line to the previous specified point is one unit.
754
%    \begin{macrocode}
755
\array_new:N \l_hobby_rho_array
756
%    \end{macrocode}
757
% \end{macro}
758
%
759
% \begin{macro}{\l_hobby_controla_array}
760
% \Verb+\l_hobby_controla_array+ is an array holding the coordinates of the first control points on the curves.
761
% The format is the same as for \Verb+\l_hobby_points_array+.
762
%    \begin{macrocode}
763
\array_new:N \l_hobby_controla_array
764
%    \end{macrocode}
765
% \end{macro}
766
%
767
% \begin{macro}{\l_hobby_controlb_array}
768
% \Verb+\l_hobby_controlb_array+ is an array holding the coordinates of the second control points on the curves.
769
% The format is the same as for \Verb+\l_hobby_points_array+.
770
%    \begin{macrocode}
771
\array_new:N \l_hobby_controlb_array
772
%    \end{macrocode}
773
% \end{macro}
774
%
775
% \begin{macro}{\l_hobby_triv_fp}
776
% \Verb+\l_hobby_triv_fp+ is a number which is used when doing the perturbation of the solution of the linear system for a closed curve.
777
% There is actually a vector, \(v\), that this corresponds to but that vector only has one component that needs computation.
778
%    \begin{macrocode}
779
\fp_new:N \l_hobby_triv_fp
780
%    \end{macrocode}
781
% \end{macro}
782
%
783
% \begin{macro}{\l_hobby_tempa_fp}
784
% \Verb+\l_hobby_tempa_fp+ is a temporary variable of type \Verb+fp+.
785
%    \begin{macrocode}
786
\fp_new:N \l_hobby_tempa_fp
787
%    \end{macrocode}
788
% \end{macro}
789
%
790
% \begin{macro}{\l_hobby_tempb_fp}
791
% \Verb+\l_hobby_tempb_fp+ is a temporary variable of type \Verb+fp+.
792
%    \begin{macrocode}
793
\fp_new:N \l_hobby_tempb_fp
794
%    \end{macrocode}
795
% \end{macro}
796
%
797
% \begin{macro}{\l_hobby_tempc_fp}
798
% \Verb+\l_hobby_tempc_fp+ is a temporary variable of type \Verb+fp+.
799
%    \begin{macrocode}
800
\fp_new:N \l_hobby_tempc_fp
801
%    \end{macrocode}
802
% \end{macro}
803
%
804
% \begin{macro}{\l_hobby_tempd_fp}
805
% \Verb+\l_hobby_tempd_fp+ is a temporary variable of type \Verb+fp+.
806
%    \begin{macrocode}
807
\fp_new:N \l_hobby_tempd_fp
808
%    \end{macrocode}
809
% \end{macro}
810
%
811
% \begin{macro}{\l_hobby_temps_fp}
812
% \Verb+\l_hobby_temps_fp+ is a temporary variable of type \Verb+fp+.
813
%    \begin{macrocode}
814
\fp_new:N \l_hobby_temps_fp
815
%    \end{macrocode}
816
% \end{macro}
817
%
818
% \begin{macro}{\l_hobby_incurl_fp}
819
% \Verb+\l_hobby_incurl_fp+ is the ``curl'' at the end of an open path.
820
% This is used if the angle at the end is not specified.
821
%    \begin{macrocode}
822
\fp_new:N \l_hobby_incurl_fp
823
\fp_set:Nn \l_hobby_incurl_fp {1}
824
%    \end{macrocode}
825
% \end{macro}
826
%
827
% \begin{macro}{\l_hobby_outcurl_fp}
828
% \Verb+\l_hobby_outcurl_fp+ is the ``curl'' at the start of an open path.
829
% This is used if the angle at the start is not specified.
830
%    \begin{macrocode}
831
\fp_new:N \l_hobby_outcurl_fp
832
\fp_set:Nn \l_hobby_outcurl_fp {1}
833
%    \end{macrocode}
834
% \end{macro}
835
%
836
% \begin{macro}{\l_hobby_inang_fp}
837
% \Verb+\l_hobby_inang_fp+ is the angle at the end of an open path.
838
% If this is not specified, it will be computed automatically.
839
% It is set to \Verb+\c_undefined_fp+ to allow easy detection of when it has been specified.
840
%    \begin{macrocode}
841
\fp_new:N \l_hobby_inang_fp
842
\fp_set_eq:NN \l_hobby_inang_fp \c_undefined_fp
843
%    \end{macrocode}
844
% \end{macro}
845
%
846
% \begin{macro}{\l_hobby_outang_fp}
847
% \Verb+\l_hobby_outang_fp+ is the angle at the start of an open path.
848
% If this is not specified, it will be computed automatically.
849
% It is set to \Verb+\c_undefined_fp+ to allow easy detection of when it has been specified.
850
%    \begin{macrocode}
851
\fp_new:N \l_hobby_outang_fp
852
\fp_set_eq:NN \l_hobby_outang_fp \c_undefined_fp
853
%    \end{macrocode}
854
% \end{macro}
855
%
856
% \begin{macro}{\l_hobby_npoints_int}
857
% \Verb+\l_hobby_npoints_int+ is one less than the number of points on the curve.
858
% As our list of points starts at \(0\), this is the index of the last point.
859
% In the algorithm for a closed curve, some points are repeated whereupon this is incremented so that it is always the index of the last point. 
860
%    \begin{macrocode}
861
\int_new:N \l_hobby_npoints_int
862
%    \end{macrocode}
863
% \end{macro}
864
%
865
% A ``point'' is a key-value list setting the x-value, the y-value, and the tensions at that point.
866
% Using keys makes it easier to pass points from the algorithm code to the calling code and vice versa without either knowing too much about the other.
867
%    \begin{macrocode}
868
\keys_define:nn {hobby / read in all} {
869
  x .fp_set:N = \l_hobby_tempa_fp,
870
  y .fp_set:N = \l_hobby_tempb_fp,
871
  tension~out .fp_set:N = \l_hobby_tempc_fp,
872
  tension~in .fp_set:N = \l_hobby_tempd_fp,
873
  excess~angle .fp_set:N = \l_hobby_temps_fp,
874
  tension .meta:n = { tension~out=#1, tension~in=#1 },
875
  tension~out .default:n = 1,
876
  tension~in .default:n = 1,
877
  excess~angle .default:n = 0,
878
}
879
%    \end{macrocode}
880
% There are certain other parameters than can be set for a give curve.
881
%    \begin{macrocode}
882
\keys_define:nn { hobby / read in params} {
883
  in~angle .fp_set:N = \l_hobby_inang_fp,
884
  out~angle .fp_set:N = \l_hobby_outang_fp,
885
  in~curl .fp_set:N = \l_hobby_incurl_fp,
886
  out~curl .fp_set:N = \l_hobby_outcurl_fp,
887
  closed .bool_set:N = \l_hobby_closed_bool,
888
  closed .default:n = true,
889
  disjoint .bool_set:N = \l_hobby_disjoint_bool,
890
  disjoint .default:n = true,
891
}
892
%    \end{macrocode}
893
% \begin{macro}{\hobby_distangle:n}
894
% Computes the distance and angle between successive points.
895
% The argument given is the index of the current point.
896
% Assumptions: the points are in \Verb+\l_hobby_points_x_array+ and \Verb+\l_hobby_points_y_array+ and the index of the last point is \Verb+\l_hobby_npoints_int+.
897
%    \begin{macrocode}
898
\cs_set:Nn \hobby_distangle:n {
899
  \fp_set:Nn \l_hobby_tempa_fp
900
    {
901
      \array_get:Nn \l_hobby_points_x_array { #1 + 1 }
902
      - \array_get:Nn \l_hobby_points_x_array { #1 }
903
    }
904
  \fp_set:Nn \l_hobby_tempb_fp
905
    {
906
      \array_get:Nn \l_hobby_points_y_array { #1 + 1 }
907
      - \array_get:Nn \l_hobby_points_y_array { #1 }
908
    }
909
910
  \fp_atantwo:NNN \l_hobby_tempc_fp \l_hobby_tempb_fp   \l_hobby_tempa_fp
911
  \array_put:Nnx \l_hobby_angles_array {#1} {\fp_to_tl:N \l_hobby_tempc_fp}
912
  \array_put:Nnx \l_hobby_distances_array {#1}
913
    {
914
      \fp_eval:n
915
        { ( \l_hobby_tempa_fp ** 2 + \l_hobby_tempb_fp ** 2) ** .5 }
916
    }
917
  }
918
%    \end{macrocode}
919
% \end{macro}
920
%
921
%
922
% \begin{macro}{\fp_atantwo:NNN}
923
% Computes the angle of the point specified by the latter two arguments, storing the answer in the first.
924
% The inverse tangent function is not yet implemented in \LaTeX3 so for now we use the \Verb+pgfmath+ function.
925
% When this is implemented in \LaTeX3 this should be replaced.
926
%    \begin{macrocode}
927
\cs_new:Nn \fp_atantwo:NNN {
928
  \pgfmathparse{rad(atan2(\fp_use:N #3,\fp_use:N #2))}
929
  \exp_args:NNo \fp_set:Nn #1 {\pgfmathresult}
930
}
931
%    \end{macrocode}
932
% \end{macro}
933
%
934
%
935
% \begin{macro}{\hobby_ctrllen:NN}
936
%   Computes the length of the control point vector from the two angles.
937
%    \begin{macrocode}
938
\cs_new:Nn \hobby_ctrllen:NN {
939
  (2 - (cos(#1) - cos(#2))
940
       * (sin(#2) - sin(#1) / 16)
941
       * (sin(#1) - sin(#2) / 16)
942
       * 2 ** .5)
943
  / ((cos(#1) - cos(#2)) * (3 - 5 ** .5) / 2 + cos(#2) + 1)
944
}
945
%    \end{macrocode}
946
% \end{macro}
947
%
948
% \begin{macro}{\hobby_append_point_copy:n}
949
%   This function adds a copy of the point (numbered by its argument) to
950
%   the end of the list of points, copying all the relevant data
951
%   (coordinates, tension, etc.).
952
%    \begin{macrocode}
953
\cs_new_protected:Npn \hobby_append_point_copy:n #1
954
  {
955
    \int_incr:N \l_hobby_npoints_int
956
    \hobby_append_point_copy_aux:Nn \l_hobby_points_array {#1}
957
    \hobby_append_point_copy_aux:Nn \l_hobby_points_x_array {#1}
958
    \hobby_append_point_copy_aux:Nn \l_hobby_points_y_array {#1}
959
    \hobby_append_point_copy_aux:Nn \l_hobby_tension_in_array {#1}
960
    \hobby_append_point_copy_aux:Nn \l_hobby_tension_out_array {#1}
961
    \hobby_append_point_copy_aux:Nn \l_hobby_excessangle_array {#1}
962
  }
963
\cs_new_protected:Npn \hobby_append_point_copy_aux:Nn #1#2
964
  { \array_put:Nnx #1 { \l_hobby_npoints_int } { \array_get:Nn #1 {#2} } }
965
%    \end{macrocode}
966
% \end{macro}
967
%
968
% \begin{macro}{\hobby_genpath:}
969
% This is the curve generation function.
970
% We assume at the start that we have an array containing all the points that the curve must go through, and the various curve parameters have been initialised.
971
% So these must be set up by a wrapper function which then calls this one.
972
% The list of required information is:
973
% \begin{enumerate}
974
% \item \Verb+\l_hobby_points_x_array+
975
% \item \Verb+\l_hobby_points_y_array+
976
% \item \Verb+\l_hobby_npoints_int+
977
% \item \Verb+\l_hobby_tension_out_array+
978
% \item \Verb+\l_hobby_tension_in_array+
979
% \item \Verb+\l_hobby_excessangle_array+
980
% \item \Verb+\l_hobby_incurl_fp+
981
% \item \Verb+\l_hobby_outcurl_fp+
982
% \item \Verb+\l_hobby_inang_fp+
983
% \item \Verb+\l_hobby_outang_fp+
984
% \item \Verb+\l_hobby_closed_bool+
985
% \end{enumerate}
986
%
987
%    \begin{macrocode}
988
\cs_new:Nn \hobby_genpath:
989
  {
990
%    \end{macrocode}
991
% For much of the time, we can pretend that a closed path is the same as
992
% an open path.  To do this, we need to make the end node an internal
993
% node by repeating the \(z_1\) node as the \(z_{n+1}\)th node.  We also
994
% check that the last (\(z_n\)) and first (\(z_0\)) nodes are the same,
995
% otherwise we repeat the \(z_0\) node as well.  If the first and last
996
% points are different, we need to duplicate the first point, with all
997
% its data.  Once we are sure that the first and last points are
998
% identical, we need to duplicate the first-but-one point (and all of
999
% its data).
1000
%    \begin{macrocode}
1001
    \bool_if:NT \l_hobby_closed_bool
1002
      {
1003
        \bool_if:nF
1004
          {
1005
            \fp_compare_p:n
1006
              {
1007
                \array_get:Nn \l_hobby_points_x_array {0}
1008
                = \array_get:Nn \l_hobby_points_x_array { \l_hobby_npoints_int }
1009
              }
1010
            &&
1011
            \fp_compare_p:n
1012
              {
1013
                \array_get:Nn \l_hobby_points_y_array {0}
1014
                = \array_get:Nn \l_hobby_points_y_array { \l_hobby_npoints_int }
1015
              }
1016
          }
1017
          { \hobby_append_point_copy:n {0} }
1018
        \hobby_append_point_copy:n {1}
1019
      }
1020
%    \end{macrocode}
1021
%
1022
% Our first step is to go through the list of points and compute the distances and angles between successive points.
1023
% Thus \(d_i\) is the distance from \(z_i\) to \(z_{i+1}\) and the angle is the angle of the line from \(z_i\) to \(z_{i+1}\).
1024
%
1025
%    \begin{macrocode}
1026
    \prg_stepwise_function:nnnN
1027
      {0} {1} {\l_hobby_npoints_int - 1}
1028
      \hobby_distangle:n
1029
%    \end{macrocode}
1030
%
1031
% For the majority of the code, we're only really interested in the differences of the angles.
1032
% So for each internal point we compute the differences in the angles.
1033
%    \begin{macrocode}
1034
  \prg_stepwise_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1}
1035
    {
1036
      \fp_set:Nn \l_hobby_tempa_fp
1037
        {
1038
          \array_get:Nn \l_hobby_angles_array {##1}
1039
          - \array_get:Nn \l_hobby_angles_array { ##1 - 1 }
1040
        }
1041
%    \end{macrocode}
1042
% We want to ensure that these angles lie in the range \((-\pi,\pi]\).
1043
% So if the angle is bigger than \(\pi\), we subtract \(2 \pi\).
1044
% (It shouldn't be that we can get bigger than \(3 \pi\) - check this.)
1045
% Similarly, we check to see if the angle is less than \(-\pi\).
1046
%    \begin{macrocode}
1047
      \fp_compare:nTF { \l_hobby_tempa_fp > pi }
1048
        { \fp_sub:Nn \l_hobby_tempa_fp { 2 pi } }
1049
        {
1050
          \fp_compare:nT { \l_hobby_tempa_fp <= - pi }
1051
            { \fp_add:Nn \l_hobby_tempa_fp { 2 pi } }
1052
        }
1053
%    \end{macrocode}
1054
% At the moment, the mixing of \Verb+pgfmath+ and \LaTeX3 that goes into computing the differences means that we can never get \Verb+\c_pi_fp+ so testing for equality is meaningless.
1055
% However, when \Verb+atan2+ is implemented in \LaTeX3 then this might make sense again.
1056
%
1057
% The wrapping routine might not get it right at the edges so we add in the override.
1058
%    \begin{macrocode}
1059
      \array_get:NnNTF \l_hobby_excessangle_array {##1} \l_tmpa_tl
1060
        { \fp_add:Nn \l_hobby_tempa_fp { 2 pi * \l_tmpa_tl } }
1061
        { }
1062
%    \end{macrocode}
1063
%    \begin{macrocode}
1064
      \array_put:Nnx \l_hobby_psi_array {##1}
1065
        { \fp_to_tl:N \l_hobby_tempa_fp }
1066
    }
1067
%    \end{macrocode}
1068
%
1069
% Next, we generate the matrix.
1070
% We start with the subdiagonal.
1071
% This is indexed from \(1\) to \(n-1\).
1072
%    \begin{macrocode}
1073
  \prg_stepwise_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
1074
    \array_put:Nnx \l_hobby_tria_array {##1}
1075
      {
1076
        \fp_eval:n
1077
          {
1078
            ( \array_get:Nn \l_hobby_tension_in_array {##1} ) ** 2
1079
            * \array_get:Nn \l_hobby_distances_array {##1}
1080
            * \array_get:Nn \l_hobby_tension_in_array { ##1 + 1 }
1081
          }
1082
      }
1083
  }
1084
%    \end{macrocode}
1085
%
1086
% Next, we attack main diagonal.
1087
% We might need to adjust the first and last terms, but we'll do that in a minute.
1088
%    \begin{macrocode}
1089
  \prg_stepwise_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1}
1090
    {
1091
      \array_put:Nnx \l_hobby_trib_array {##1}
1092
        {
1093
          \fp_eval:n
1094
            {
1095
              (3 * \array_get:Nn \l_hobby_tension_in_array {##1 + 1} - 1)
1096
              * \array_get:Nn \l_hobby_tension_out_array {##1} ** 2
1097
              * \array_get:Nn \l_hobby_tension_out_array {##1 - 1}
1098
              * \array_get:Nn \l_hobby_distances_array {##1 - 1}
1099
              +
1100
              (3 * \array_get:Nn \l_hobby_tension_out_array {##1 - 1} - 1)
1101
              * \array_get:Nn \l_hobby_tension_in_array {##1} ** 2
1102
              * \array_get:Nn \l_hobby_tension_in_array {##1 + 1}
1103
              * \array_get:Nn \l_hobby_distances_array {##1}
1104
            }
1105
        }
1106
    }
1107
%    \end{macrocode}
1108
%
1109
% Next, the superdiagonal.
1110
%    \begin{macrocode}
1111
  \prg_stepwise_inline:nnnn {1} {1} {\l_hobby_npoints_int - 2} {
1112
    \array_put:Nnx \l_hobby_tric_array {##1}
1113
      {
1114
        \fp_eval:n
1115
          {
1116
            \array_get:Nn \l_hobby_tension_in_array {##1} ** 2
1117
            * \array_get:Nn \l_hobby_tension_in_array {##1 - 1}
1118
            * \array_get:Nn \l_hobby_distances_array {##1 - 1}
1119
          }
1120
      }
1121
  }
1122
%    \end{macrocode}
1123
%
1124
% Lastly (before the adjustments), the target vector.
1125
%    \begin{macrocode}
1126
  \prg_stepwise_inline:nnnn {1} {1} {\l_hobby_npoints_int - 2} {
1127
  \array_get:NnN \l_hobby_psi_array {##1 + 1} \l_tmpa_tl
1128
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1129
1130
  \array_get:NnN \l_hobby_tension_out_array {##1} \l_tmpa_tl
1131
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1132
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1133
1134
  \array_get:NnN \l_hobby_tension_out_array {##1 - 1} \l_tmpa_tl
1135
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1136
1137
  \array_get:NnN \l_hobby_distances_array {##1 - 1} \l_tmpa_tl
1138
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1139
1140
  \fp_neg:N \l_hobby_tempa_fp
1141
1142
  \array_get:NnN \l_hobby_tension_out_array {##1 - 1} \l_tmpa_tl
1143
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1144
  \fp_mul:Nn \l_hobby_tempb_fp {3}
1145
  \fp_sub:Nn \l_hobby_tempb_fp {1}
1146
1147
  \array_get:NnN \l_hobby_psi_array {##1} \l_tmpa_tl
1148
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1149
1150
  \array_get:NnN \l_hobby_tension_in_array {##1} \l_tmpa_tl
1151
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1152
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1153
1154
  \array_get:NnN \l_hobby_tension_in_array {##1 + 1} \l_tmpa_tl
1155
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1156
1157
  \array_get:NnN \l_hobby_distances_array {##1} \l_tmpa_tl
1158
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1159
1160
  \fp_sub:Nn \l_hobby_tempa_fp {\l_hobby_tempb_fp}
1161
1162
  \array_put:Nnx \l_hobby_trid_array {##1} {\fp_to_tl:N \l_hobby_tempa_fp}
1163
}
1164
%    \end{macrocode}
1165
%
1166
% Next, there are some adjustments at the ends.
1167
% These differ depending on whether the path is open or closed.
1168
%    \begin{macrocode}
1169
\bool_if:NTF \l_hobby_closed_bool {
1170
%    \end{macrocode}
1171
% Closed path
1172
%    \begin{macrocode}
1173
\array_get:NnN \l_hobby_distances_array {\l_hobby_npoints_int - 2} \l_tmpa_tl
1174
\fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1175
\array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int - 2} \l_tmpa_tl
1176
\fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1177
\array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1178
\fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1179
\fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1180
\fp_neg:N \l_hobby_tempa_fp
1181
\array_put:Nnx \l_hobby_tric_array {0} {\fp_to_tl:N \l_hobby_tempa_fp}
1182
1183
\array_put:Nnn \l_hobby_trib_array {0} {1}
1184
\array_put:Nnn \l_hobby_trid_array {0} {0}
1185
1186
\array_get:NnN \l_hobby_trib_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1187
\fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1188
\fp_add:Nn \l_hobby_tempa_fp {1}
1189
\array_put:Nnx \l_hobby_trib_array {\l_hobby_npoints_int - 1} {\fp_to_tl:N \l_hobby_tempa_fp}
1190
1191
  \array_get:NnN \l_hobby_psi_array {1} \l_tmpa_tl
1192
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1193
1194
 \array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int -1} \l_tmpa_tl
1195
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1196
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1197
1198
\array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int -2} \l_tmpa_tl
1199
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1200
1201
\array_get:NnN \l_hobby_distances_array {\l_hobby_npoints_int - 2} \l_tmpa_tl
1202
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1203
1204
  \fp_neg:N \l_hobby_tempa_fp
1205
1206
 \array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int - 2} \l_tmpa_tl
1207
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1208
  \fp_mul:Nn \l_hobby_tempb_fp {3}
1209
  \fp_sub:Nn \l_hobby_tempb_fp {1}
1210
1211
\array_get:NnN \l_hobby_psi_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1212
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1213
1214
\array_get:NnN \l_hobby_tension_in_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1215
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1216
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1217
1218
 \array_get:NnN \l_hobby_tension_in_array {\l_hobby_npoints_int} \l_tmpa_tl
1219
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1220
1221
\array_get:NnN \l_hobby_distances_array {\l_hobby_npoints_int -1} \l_tmpa_tl
1222
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1223
1224
  \fp_sub:Nn \l_hobby_tempa_fp {\l_hobby_tempb_fp}
1225
1226
 \array_put:Nnx \l_hobby_trid_array {\l_hobby_npoints_int - 1} {\fp_to_tl:N \l_hobby_tempa_fp}
1227
%    \end{macrocode}
1228
% We also need to populate the \(u\)-vector
1229
%    \begin{macrocode}
1230
  \array_put:Nnn \l_hobby_triu_array {0} {1}
1231
\array_put:Nnn \l_hobby_triu_array {\l_hobby_npoints_int - 1} {1}
1232
  \prg_stepwise_inline:nnnn {1} {1} {\l_hobby_npoints_int - 2} {
1233
  \array_put:Nnn \l_hobby_triu_array {##1} {0}
1234
  }
1235
%    \end{macrocode}
1236
% And define the significant entry in the \(v\)-vector.
1237
%    \begin{macrocode}
1238
\array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int -1} \l_tmpa_tl
1239
\fp_set:Nn \l_hobby_triv_fp {\l_tmpa_tl}
1240
\fp_mul:Nn \l_hobby_triv_fp {\l_tmpa_tl}
1241
 \array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int -2} \l_tmpa_tl
1242
\fp_mul:Nn \l_hobby_triv_fp {\l_tmpa_tl}
1243
 \array_get:NnN \l_hobby_distances_array {\l_hobby_npoints_int -2} \l_tmpa_tl
1244
\fp_mul:Nn \l_hobby_triv_fp {\l_tmpa_tl}
1245
}
1246
{
1247
%    \end{macrocode}
1248
% Open path.
1249
% First, we test to see if \(\theta_0\) has been specified.
1250
%    \begin{macrocode}
1251
\fp_if_undefined:NTF \l_hobby_outang_fp
1252
{
1253
  \array_get:NnN \l_hobby_tension_in_array {1} \l_tmpa_tl
1254
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1255
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1256
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1257
  \fp_mul:Nn \l_hobby_tempa_fp {\l_hobby_incurl_fp}
1258
1259
  \array_get:NnN \l_hobby_tension_in_array {1} \l_tmpa_tl
1260
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1261
  \fp_mul:Nn \l_hobby_tempb_fp {3}
1262
  \fp_sub:Nn \l_hobby_tempb_fp {1}
1263
1264
  \array_get:NnN \l_hobby_tension_out_array {0} \l_tmpa_tl
1265
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1266
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1267
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1268
1269
  \fp_add:Nn \l_hobby_tempa_fp {\l_hobby_tempb_fp}
1270
  
1271
  \array_put:Nnx \l_hobby_trib_array {0}  {\fp_to_tl:N \l_hobby_tempa_fp}
1272
1273
  \array_get:NnN \l_hobby_tension_out_array {0} \l_tmpa_tl
1274
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1275
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1276
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1277
1278
  \array_get:NnN \l_hobby_tension_out_array {0} \l_tmpa_tl
1279
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1280
  \fp_mul:Nn \l_hobby_tempb_fp {3}
1281
  \fp_sub:Nn \l_hobby_tempb_fp {1}
1282
1283
  \array_get:NnN \l_hobby_tension_in_array {1} \l_tmpa_tl
1284
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1285
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1286
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1287
1288
  \fp_mul:Nn \l_hobby_tempb_fp {\l_hobby_incurl_fp}
1289
1290
  \fp_add:Nn \l_hobby_tempa_fp {\l_hobby_tempb_fp}
1291
  
1292
  \array_put:Nnx \l_hobby_tric_array {0} {\fp_to_tl:N \l_hobby_tempa_fp}
1293
1294
  \fp_neg:N \l_hobby_tempa_fp
1295
1296
  \array_get:NnN \l_hobby_psi_array {1} \l_tmpa_tl
1297
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1298
  
1299
  \array_put:Nnx \l_hobby_trid_array {0} {\fp_to_tl:N \l_hobby_tempa_fp}
1300
  
1301
}
1302
{
1303
  \array_put:Nnn \l_hobby_trib_array {0} {1}
1304
  \array_put:Nnn \l_hobby_tric_array {0} {0}
1305
  \fp_set_eq:NN \l_hobby_tempa_fp \l_hobby_outang_fp
1306
  \array_get:NnN \l_hobby_angles_array {0} \l_tmpa_tl
1307
  \fp_add:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1308
  \array_put:Nnx \l_hobby_trid_array {0} {\fp_to_tl:N \l_hobby_tempa_fp}
1309
}
1310
%    \end{macrocode}
1311
%
1312
% Next, if \(\phi_n\) has been given.
1313
%    \begin{macrocode}
1314
\fp_if_undefined:NTF \l_hobby_inang_fp
1315
{
1316
 \array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1317
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1318
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1319
1320
 \array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int - 2} \l_tmpa_tl
1321
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1322
1323
 \array_get:NnN \l_hobby_distances_array {\l_hobby_npoints_int - 2} \l_tmpa_tl
1324
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1325
1326
 \array_get:NnN \l_hobby_tension_in_array {\l_hobby_npoints_int} \l_tmpa_tl
1327
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1328
  \fp_mul:Nn \l_hobby_tempb_fp {3}
1329
  \fp_sub:Nn \l_hobby_tempb_fp {1}
1330
1331
 \array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1332
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1333
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1334
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1335
1336
  \fp_mul:Nn \l_hobby_tempb_fp {\l_hobby_outcurl_fp}
1337
1338
 \array_get:NnN \l_hobby_tension_in_array {\l_hobby_npoints_int } \l_tmpa_tl
1339
  \fp_set:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1340
  \fp_mul:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1341
  \fp_mul:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1342
1343
  \fp_add:Nn \l_hobby_tempb_fp {\l_hobby_tempc_fp}
1344
1345
  \fp_mul:Nn \l_hobby_tempa_fp {\l_hobby_tempb_fp}
1346
1347
 \array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int -2} \l_tmpa_tl
1348
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1349
  \fp_mul:Nn \l_hobby_tempb_fp {3}
1350
  \fp_sub:Nn \l_hobby_tempb_fp {1}
1351
1352
 \array_get:NnN \l_hobby_tension_in_array {\l_hobby_npoints_int} \l_tmpa_tl
1353
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1354
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1355
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1356
1357
 \array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1358
  \fp_set:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1359
  \fp_mul:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1360
  \fp_mul:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1361
1362
  \fp_mul:Nn \l_hobby_tempc_fp {\l_hobby_outcurl_fp}
1363
1364
  \fp_add:Nn \l_hobby_tempb_fp {\l_hobby_tempc_fp}
1365
1366
  \fp_div:Nn \l_hobby_tempa_fp {\l_hobby_tempb_fp}
1367
1368
  \fp_neg:N \l_hobby_tempa_fp
1369
1370
 \array_get:NnN \l_hobby_trib_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1371
1372
  \fp_add:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1373
1374
 \array_put:Nnx \l_hobby_trib_array {\l_hobby_npoints_int - 1} {\fp_to_tl:N \l_hobby_tempa_fp}
1375
1376
1377
 \array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int - 2} \l_tmpa_tl
1378
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1379
  \fp_mul:Nn \l_hobby_tempb_fp {3}
1380
  \fp_sub:Nn \l_hobby_tempb_fp {1}
1381
1382
 \array_get:NnN \l_hobby_psi_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1383
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1384
1385
 \array_get:NnN \l_hobby_tension_in_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1386
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1387
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1388
1389
 \array_get:NnN \l_hobby_tension_in_array {\l_hobby_npoints_int} \l_tmpa_tl
1390
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1391
1392
 \array_get:NnN \l_hobby_distances_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1393
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1394
1395
  \fp_neg:N \l_hobby_tempb_fp
1396
1397
 \array_put:Nnx \l_hobby_trid_array {\l_hobby_npoints_int - 1} {\fp_to_tl:N \l_hobby_tempb_fp}
1398
}
1399
{
1400
  \fp_set_eq:NN \l_hobby_tempa_fp \l_hobby_inang_fp
1401
  \fp_sub:Nn \l_hobby_tempa_fp {\c_pi_fp}
1402
  \array_get:NnN \l_hobby_angles_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1403
  \fp_sub:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1404
  \fp_compare:nNnTF {\l_hobby_tempa_fp} > { \c_pi_fp }
1405
  {
1406
    \fp_sub:Nn \l_hobby_tempa_fp {\c_pi_fp}
1407
    \fp_sub:Nn \l_hobby_tempa_fp {\c_pi_fp}
1408
  }
1409
  {}
1410
  \fp_set_eq:NN \l_hobby_tempb_fp \l_hobby_tempa_fp
1411
  \fp_neg:N \l_hobby_tempb_fp
1412
  \fp_compare:nNnTF {\l_hobby_tempb_fp} > {\c_pi_fp }
1413
  {
1414
    \fp_add:Nn \l_hobby_tempa_fp {\c_pi_fp}
1415
    \fp_add:Nn \l_hobby_tempa_fp {\c_pi_fp}
1416
  }
1417
  {}
1418
1419
 \array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1420
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1421
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1422
1423
 \array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int - 2} \l_tmpa_tl
1424
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1425
1426
 \array_get:NnN \l_hobby_distances_array {\l_hobby_npoints_int - 2} \l_tmpa_tl
1427
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1428
1429
  \fp_neg:N \l_hobby_tempa_fp
1430
1431
  \array_get:NnN \l_hobby_tension_out_array {\l_hobby_npoints_int - 2} \l_tmpa_tl
1432
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1433
  \fp_mul:Nn \l_hobby_tempb_fp {3}
1434
  \fp_sub:Nn \l_hobby_tempb_fp {1}
1435
1436
 \array_get:NnN \l_hobby_psi_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1437
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1438
1439
   \array_get:NnN \l_hobby_tension_in_array  {\l_hobby_npoints_int - 1} \l_tmpa_tl
1440
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1441
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1442
1443
  \exp_args:NNo \array_get:NnN \l_hobby_tension_in_array {\l_hobby_npoints_int} \l_tmpa_tl
1444
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1445
1446
   \array_get:NnN \l_hobby_distances_array  {\l_hobby_npoints_int - 1} \l_tmpa_tl
1447
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1448
1449
  \fp_sub:Nn \l_hobby_tempa_fp {\l_hobby_tempb_fp}
1450
1451
   \array_put:Nnx \l_hobby_trid_array  {\l_hobby_npoints_int - 1} {\fp_to_tl:N \l_hobby_tempa_fp}
1452
}
1453
%    \end{macrocode}
1454
% End of adjustments for open paths.
1455
%    \begin{macrocode}
1456
}
1457
%    \end{macrocode}
1458
%
1459
% Now we have the tridiagonal matrix in place, we implement the solution.
1460
% We start with the forward eliminations.
1461
%    \begin{macrocode}
1462
\prg_stepwise_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
1463
   \array_get:NnN \l_hobby_trib_array {##1 - 1} \l_tmpa_tl
1464
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1465
  \array_get:NnN \l_hobby_trib_array {##1} \l_tmpa_tl
1466
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1467
1468
   \array_get:NnN \l_hobby_tric_array {##1 - 1} \l_tmpa_tl
1469
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1470
  \array_get:NnN \l_hobby_tria_array {##1} \l_tmpa_tl
1471
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1472
1473
  \fp_sub:Nn \l_hobby_tempb_fp {\l_hobby_tempa_fp}
1474
1475
  \array_put:Nnx \l_hobby_trib_array {##1} {\fp_to_tl:N \l_hobby_tempb_fp}
1476
%    \end{macrocode}
1477
% The last time, we don't touch the \(C\)-vector.
1478
%    \begin{macrocode}
1479
  \int_compare:nTF {##1 < \l_hobby_npoints_int - 1} {
1480
1481
   \array_get:NnN \l_hobby_trib_array {##1 - 1} \l_tmpa_tl
1482
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1483
  \array_get:NnN \l_hobby_tric_array {##1} \l_tmpa_tl
1484
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1485
1486
  \array_put:Nnx \l_hobby_tric_array {##1} {\fp_to_tl:N \l_hobby_tempa_fp}
1487
  }
1488
  {}
1489
1490
   \array_get:NnN \l_hobby_trib_array {##1 - 1} \l_tmpa_tl
1491
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1492
  \array_get:NnN \l_hobby_trid_array {##1} \l_tmpa_tl
1493
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1494
1495
   \array_get:NnN \l_hobby_trid_array {##1 - 1} \l_tmpa_tl
1496
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1497
  \array_get:NnN \l_hobby_tria_array {##1} \l_tmpa_tl
1498
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1499
1500
  \fp_sub:Nn \l_hobby_tempb_fp {\l_hobby_tempa_fp}
1501
1502
  \array_put:Nnx \l_hobby_trid_array {##1} {\fp_to_tl:N \l_hobby_tempb_fp}
1503
%    \end{macrocode}
1504
% On a closed path, we also want to know \(M^{-1} u\) so need to do the elimination steps on \(u\) as well.
1505
%    \begin{macrocode}
1506
  \bool_if:NTF \l_hobby_closed_bool {
1507
   \array_get:NnN \l_hobby_trib_array {##1 - 1} \l_tmpa_tl
1508
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1509
  \array_get:NnN \l_hobby_triu_array {##1} \l_tmpa_tl
1510
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1511
1512
   \array_get:NnN \l_hobby_triu_array {##1 - 1} \l_tmpa_tl
1513
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1514
  \array_get:NnN \l_hobby_tria_array {##1} \l_tmpa_tl
1515
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1516
1517
  \fp_sub:Nn \l_hobby_tempb_fp {\l_hobby_tempa_fp}
1518
1519
  \array_put:Nnx \l_hobby_triu_array {##1} {\fp_to_tl:N \l_hobby_tempb_fp}
1520
  }{}
1521
}
1522
%    \end{macrocode}
1523
% Now we start the back substitution.
1524
% The first step is slightly different to the general step.
1525
%    \begin{macrocode}
1526
 \array_get:NnN \l_hobby_trid_array  {\l_hobby_npoints_int - 1} \l_tmpa_tl
1527
\fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1528
1529
 \array_get:NnN \l_hobby_trib_array  {\l_hobby_npoints_int - 1} \l_tmpa_tl
1530
\fp_div:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1531
1532
 \array_put:Nnx \l_hobby_theta_array  {\l_hobby_npoints_int - 1} {\fp_to_tl:N \l_hobby_tempa_fp}
1533
1534
%    \end{macrocode}
1535
% For a closed path, we need to work with \(u\) as well.
1536
%    \begin{macrocode}
1537
\bool_if:NTF \l_hobby_closed_bool {
1538
   \array_get:NnN \l_hobby_triu_array  {\l_hobby_npoints_int - 1} \l_tmpa_tl
1539
\fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1540
1541
 \array_get:NnN \l_hobby_trib_array  {\l_hobby_npoints_int - 1} \l_tmpa_tl
1542
\fp_div:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1543
1544
 \array_put:Nnx \l_hobby_triu_array  {\l_hobby_npoints_int - 1} {\fp_to_tl:N \l_hobby_tempa_fp}
1545
1546
}{}
1547
%    \end{macrocode}
1548
% Now we iterate over the vectors, doing the remaining back substitutions.
1549
%    \begin{macrocode}
1550
\prg_stepwise_inline:nnnn {\l_hobby_npoints_int - 2} {-1} {0} {
1551
   \array_get:NnN \l_hobby_theta_array  {##1 + 1} \l_tmpa_tl
1552
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1553
  \array_get:NnN \l_hobby_tric_array {##1} \l_tmpa_tl
1554
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1555
  \array_get:NnN \l_hobby_trid_array {##1} \l_tmpa_tl
1556
  \fp_neg:N \l_hobby_tempa_fp
1557
  \fp_add:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1558
  \array_get:NnN \l_hobby_trib_array {##1} \l_tmpa_tl
1559
  \fp_div:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1560
  \array_put:Nnx \l_hobby_theta_array {##1} {\fp_to_tl:N \l_hobby_tempa_fp}
1561
}
1562
\bool_if:NTF \l_hobby_closed_bool {
1563
%    \end{macrocode}
1564
% On a closed path, we also need to work out \(M^{-1} u\).
1565
%    \begin{macrocode}
1566
\prg_stepwise_inline:nnnn {\l_hobby_npoints_int - 2} {-1} {0} {
1567
   \array_get:NnN \l_hobby_triu_array  {##1 + 1} \l_tmpa_tl
1568
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1569
  \array_get:NnN \l_hobby_tric_array {##1} \l_tmpa_tl
1570
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1571
  \array_get:NnN \l_hobby_triu_array {##1} \l_tmpa_tl
1572
  \fp_neg:N \l_hobby_tempa_fp
1573
  \fp_add:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1574
  \array_get:NnN \l_hobby_trib_array {##1} \l_tmpa_tl
1575
  \fp_div:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1576
  \array_put:Nnx \l_hobby_triu_array {##1} {\fp_to_tl:N \l_hobby_tempa_fp}
1577
}
1578
%    \end{macrocode}
1579
% Then we compute \(v^\top M^{-1}u\) and \(v^\top M^{-1} \theta\).
1580
% As \(v\) has a particularly simple form, these inner products are easy to compute.
1581
%    \begin{macrocode}
1582
\array_get:NnN \l_hobby_triu_array {1} \l_tmpa_tl
1583
\fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1584
\fp_mul:Nn \l_hobby_tempa_fp {\l_hobby_triv_fp}
1585
 \array_get:NnN \l_hobby_triu_array  {\l_hobby_npoints_int - 1} \l_tmpa_tl
1586
\fp_sub:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1587
\fp_add:Nn \l_hobby_tempa_fp {1}
1588
1589
\array_get:NnN \l_hobby_theta_array {1} \l_tmpa_tl
1590
\fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1591
\fp_mul:Nn \l_hobby_tempb_fp {\l_hobby_triv_fp}
1592
 \array_get:NnN \l_hobby_theta_array  {\l_hobby_npoints_int - 1} \l_tmpa_tl
1593
\fp_sub:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1594
\fp_div:Nn \l_hobby_tempb_fp {\l_hobby_tempa_fp}
1595
1596
\prg_stepwise_inline:nnnn {0} {1} {\l_hobby_npoints_int - 1} {
1597
  \array_get:NnN \l_hobby_triu_array {##1} \l_tmpa_tl
1598
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1599
  \fp_mul:Nn \l_hobby_tempa_fp {\l_hobby_tempb_fp}
1600
  \fp_neg:N \l_hobby_tempa_fp
1601
  \array_get:NnN \l_hobby_theta_array {##1} \l_tmpa_tl
1602
  \fp_add:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1603
  \array_put:Nnx \l_hobby_theta_array {##1} {\fp_use:N \l_hobby_tempa_fp}
1604
}
1605
}{}
1606
%    \end{macrocode}
1607
%
1608
% Now that we have computed the \(\theta_i\)s, we can quickly compute the \(\phi_i\)s.
1609
%
1610
%    \begin{macrocode}
1611
\prg_stepwise_inline:nnnn {1} {1} {\l_hobby_npoints_int - 1} {
1612
    \array_get:NnN \l_hobby_theta_array {##1} \l_tmpa_tl
1613
    \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1614
    \array_get:NnN \l_hobby_psi_array {##1} \l_tmpa_tl
1615
    \fp_add:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1616
    \fp_neg:N \l_hobby_tempa_fp
1617
    \array_put:Nnx \l_hobby_phi_array {##1} {\fp_to_tl:N \l_hobby_tempa_fp}
1618
  }
1619
%    \end{macrocode}
1620
%
1621
% If the path is open, this works for all except \(\phi_n\).
1622
% If the path is closed, we can drop our added point.
1623
% Cheaply, of course.
1624
%    \begin{macrocode}
1625
\bool_if:NTF \l_hobby_closed_bool {
1626
  \int_decr:N \l_hobby_npoints_int
1627
}{
1628
%    \end{macrocode}
1629
% If \(\phi_n\) was given, we simply use that.
1630
% Otherwise, we compute it from \(\theta_{n-1}\).
1631
%    \begin{macrocode}
1632
\fp_if_undefined:NTF \l_hobby_inang_fp
1633
{
1634
  \array_get:NnN \l_hobby_tension_in_array {\l_hobby_npoints_int} \l_tmpa_tl
1635
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1636
  \fp_mul:Nn \l_hobby_tempa_fp {3}
1637
  \fp_sub:Nn \l_hobby_tempa_fp {1}
1638
1639
   \array_get:NnN \l_hobby_tension_out_array      {\l_hobby_npoints_int - 1} \l_tmpa_tl
1640
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1641
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1642
  \fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1643
1644
  \fp_mul:Nn \l_hobby_tempa_fp {\l_hobby_outcurl_fp}
1645
1646
  \array_get:NnN \l_hobby_tension_in_array {\l_hobby_npoints_int } \l_tmpa_tl
1647
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1648
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1649
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1650
1651
  \fp_add:Nn \l_hobby_tempa_fp {\l_hobby_tempb_fp}
1652
1653
   \array_get:NnN \l_hobby_tension_out_array  {\l_hobby_npoints_int -2} \l_tmpa_tl
1654
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1655
  \fp_mul:Nn \l_hobby_tempb_fp {3}
1656
  \fp_sub:Nn \l_hobby_tempb_fp {1}
1657
1658
   \array_get:NnN \l_hobby_tension_in_array {\l_hobby_npoints_int} \l_tmpa_tl
1659
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1660
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1661
  \fp_mul:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1662
1663
   \array_get:NnN \l_hobby_tension_out_array  {\l_hobby_npoints_int - 1} \l_tmpa_tl
1664
  \fp_set:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1665
  \fp_mul:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1666
  \fp_mul:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1667
1668
  \fp_mul:Nn \l_hobby_tempc_fp {\l_hobby_outcurl_fp}
1669
1670
  \fp_add:Nn \l_hobby_tempb_fp {\l_hobby_tempc_fp}
1671
1672
  \fp_div:Nn \l_hobby_tempa_fp {\l_hobby_tempb_fp}
1673
1674
 \array_get:NnN \l_hobby_theta_array  {\l_hobby_npoints_int -1} \l_tmpa_tl
1675
\fp_mul:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1676
1677
 \array_put:Nnx \l_hobby_phi_array {\l_hobby_npoints_int} {\fp_to_tl:N \l_hobby_tempa_fp}
1678
}
1679
{
1680
%    \end{macrocode}
1681
% We were given \(\phi_n\) so use it (adjusting to make it relative to the incoming line).
1682
%    \begin{macrocode}
1683
  \fp_set_eq:NN \l_hobby_tempa_fp \l_hobby_inang_fp
1684
  \fp_sub:Nn \l_hobby_tempa_fp {\c_pi_fp}
1685
  \array_get:NnN \l_hobby_angles_array {\l_hobby_npoints_int - 1} \l_tmpa_tl
1686
  \fp_sub:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1687
  \fp_compare:nNnTF {\l_hobby_tempa_fp} > { \c_pi_fp }
1688
  {
1689
    \fp_sub:Nn \l_hobby_tempa_fp {\c_pi_fp}
1690
    \fp_sub:Nn \l_hobby_tempa_fp {\c_pi_fp}
1691
  }
1692
  {}
1693
  \fp_set_eq:NN \l_hobby_tempb_fp \l_hobby_tempa_fp
1694
  \fp_neg:N \l_hobby_tempb_fp
1695
  \fp_compare:nNnTF {\l_hobby_tempb_fp} > {\c_pi_fp }
1696
  {
1697
    \fp_add:Nn \l_hobby_tempa_fp {\c_pi_fp}
1698
    \fp_add:Nn \l_hobby_tempa_fp {\c_pi_fp}
1699
  }
1700
  {}
1701
  \array_put:Nnx \l_hobby_phi_array {\l_hobby_npoints_int} {\fp_to_tl:N \l_hobby_tempa_fp}
1702
1703
}
1704
}
1705
%    \end{macrocode}
1706
%
1707
% Next task is to compute the \(\rho_i\) and \(\sigma_i\).
1708
%    \begin{macrocode}
1709
\prg_stepwise_inline:nnnn {0} {1} {\l_hobby_npoints_int - 1} {
1710
1711
  \array_get:NnN \l_hobby_theta_array {##1} \l_tmpa_tl
1712
  \fp_set:Nn \l_hobby_tempa_fp {\l_tmpa_tl}
1713
1714
  \array_get:NnN \l_hobby_phi_array  {##1 + 1} \l_tmpa_tl
1715
1716
  \fp_set:Nn \l_hobby_tempb_fp {\l_tmpa_tl}
1717
1718
  \array_put:Nnx \l_hobby_sigma_array {##1 + 1}
1719
    { \fp_eval:n { \hobby_ctrllen:NN \l_hobby_tempa_fp \l_hobby_tempb_fp } }
1720
1721
  \array_put:Nnx \l_hobby_rho_array {##1}
1722
    { \fp_eval:n { \hobby_ctrllen:NN \l_hobby_tempb_fp \l_hobby_tempa_fp } }
1723
1724
  }
1725
%    \end{macrocode}
1726
% Lastly, we generate the coordinates of the control points.
1727
%    \begin{macrocode}
1728
\prg_stepwise_inline:nnnn {0} {1} {\l_hobby_npoints_int - 1} {
1729
    \array_get:NnN \l_hobby_angles_array {##1} \l_tmpa_tl
1730
    \fp_set:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1731
  \array_get:NnN \l_hobby_theta_array {##1} \l_tmpa_tl
1732
    \fp_add:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1733
    \fp_sin:Nn \l_hobby_tempd_fp {\l_hobby_tempc_fp}
1734
    \fp_cos:Nn \l_hobby_tempc_fp {\l_hobby_tempc_fp}
1735
    \array_get:NnN \l_hobby_rho_array {##1} \l_tmpa_tl
1736
    \fp_mul:Nn \l_hobby_tempd_fp {\l_tmpa_tl}
1737
    \fp_mul:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1738
    \array_get:NnN \l_hobby_distances_array {##1} \l_tmpa_tl
1739
    \fp_mul:Nn \l_hobby_tempd_fp {\l_tmpa_tl}
1740
    \fp_mul:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1741
    \fp_div:Nn \l_hobby_tempd_fp {3}
1742
    \fp_div:Nn \l_hobby_tempc_fp {3}
1743
  \array_get:NnN \l_hobby_points_x_array {##1} \l_tmpa_tl
1744
    \fp_add:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1745
  \array_get:NnN \l_hobby_points_y_array {##1} \l_tmpa_tl
1746
    \fp_add:Nn \l_hobby_tempd_fp {\l_tmpa_tl}
1747
   \array_put:Nnx \l_hobby_controla_array  {##1 + 1} {x = \fp_use:N \l_hobby_tempc_fp, y = \fp_use:N \l_hobby_tempd_fp }
1748
  }
1749
1750
\prg_stepwise_inline:nnnn {1} {1} {\l_hobby_npoints_int} {
1751
     \array_get:NnN \l_hobby_angles_array  {##1 - 1} \l_tmpa_tl
1752
    \fp_set:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1753
  \array_get:NnN \l_hobby_phi_array {##1} \l_tmpa_tl
1754
    \fp_sub:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1755
    \fp_sin:Nn \l_hobby_tempd_fp {\l_hobby_tempc_fp}
1756
    \fp_cos:Nn \l_hobby_tempc_fp {\l_hobby_tempc_fp}
1757
    \fp_neg:N \l_hobby_tempc_fp
1758
    \fp_neg:N \l_hobby_tempd_fp
1759
    \array_get:NnN \l_hobby_sigma_array {##1} \l_tmpa_tl
1760
    \fp_mul:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1761
    \fp_mul:Nn \l_hobby_tempd_fp {\l_tmpa_tl}
1762
     \array_get:NnN \l_hobby_distances_array  {##1 - 1} \l_tmpa_tl
1763
    \fp_mul:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1764
    \fp_mul:Nn \l_hobby_tempd_fp {\l_tmpa_tl}
1765
    \fp_div:Nn \l_hobby_tempc_fp {3}
1766
    \fp_div:Nn \l_hobby_tempd_fp {3}
1767
    \array_get:NnN \l_hobby_points_x_array {##1} \l_tmpa_tl
1768
    \fp_add:Nn \l_hobby_tempc_fp {\l_tmpa_tl}
1769
    \array_get:NnN \l_hobby_points_y_array {##1} \l_tmpa_tl
1770
    \fp_add:Nn \l_hobby_tempd_fp {\l_tmpa_tl}
1771
    \array_put:Nnx \l_hobby_controlb_array {##1} {x = \fp_use:N \l_hobby_tempc_fp, y = \fp_use:N \l_hobby_tempd_fp }
1772
  }
1773
}
1774
%    \end{macrocode}
1775
% \end{macro}
1776
%
1777
% \begin{macro}{\hobbyinit}
1778
% Initialise the settings for Hobby's algorithm
1779
%    \begin{macrocode}
1780
\NewDocumentCommand \hobbyinit {m m m} {
1781
\hobby_set_cmds:nnn#1#2#3
1782
\array_clear:N \l_hobby_points_array
1783
\array_clear:N \l_hobby_points_x_array
1784
\array_clear:N \l_hobby_points_y_array
1785
\array_clear:N \l_hobby_angles_array
1786
\array_clear:N \l_hobby_distances_array
1787
\array_clear:N \l_hobby_tension_out_array
1788
\array_clear:N \l_hobby_tension_in_array
1789
\array_clear:N \l_hobby_excessangle_array
1790
\array_clear:N \l_hobby_tria_array
1791
\array_clear:N \l_hobby_trib_array
1792
\array_clear:N \l_hobby_tric_array
1793
\array_clear:N \l_hobby_trid_array
1794
\array_clear:N \l_hobby_triu_array
1795
\array_clear:N \l_hobby_psi_array
1796
\array_clear:N \l_hobby_theta_array
1797
\array_clear:N \l_hobby_phi_array
1798
\array_clear:N \l_hobby_sigma_array
1799
\array_clear:N \l_hobby_rho_array
1800
\array_clear:N \l_hobby_controla_array
1801
\array_clear:N \l_hobby_controlb_array
1802
\bool_set_false:N \l_hobby_closed_bool
1803
\bool_set_false:N \l_hobby_disjoint_bool
1804
1805
  \int_set:Nn \l_hobby_npoints_int {-1}
1806
  \fp_set_eq:NN \l_hobby_inang_fp \c_undefined_fp
1807
  \fp_set_eq:NN \l_hobby_outang_fp \c_undefined_fp
1808
  \fp_set_eq:NN \l_hobby_incurl_fp \c_one_fp
1809
  \fp_set_eq:NN \l_hobby_outcurl_fp \c_one_fp
1810
}
1811
%    \end{macrocode}
1812
% \end{macro}
1813
%
1814
% \begin{macro}{\hobbyaddpoint}
1815
% This adds a point, possibly with tensions, to the current stack.
1816
%    \begin{macrocode}
1817
\NewDocumentCommand \hobbyaddpoint { m } {
1818
    \keys_set:nn { hobby/read in all }
1819
    {
1820
      tension~out,
1821
      tension~in,
1822
      excess~angle,
1823
      #1
1824
    }
1825
    \int_incr:N \l_hobby_npoints_int
1826
    \array_put:Nnx \l_hobby_tension_out_array {\l_hobby_npoints_int } {\fp_to_tl:N \l_hobby_tempc_fp}
1827
    \array_put:Nnx \l_hobby_tension_in_array {\l_hobby_npoints_int } {\fp_to_tl:N \l_hobby_tempd_fp}
1828
    \array_put:Nnx \l_hobby_excessangle_array {\l_hobby_npoints_int } {\fp_to_tl:N \l_hobby_temps_fp}
1829
    \array_put:Nnx \l_hobby_points_array {\l_hobby_npoints_int } {x = \fp_use:N \l_hobby_tempa_fp, y = \fp_use:N \l_hobby_tempb_fp }
1830
    \array_put:Nnx \l_hobby_points_x_array {\l_hobby_npoints_int } {\fp_to_tl:N \l_hobby_tempa_fp}
1831
    \array_put:Nnx \l_hobby_points_y_array {\l_hobby_npoints_int } {\fp_to_tl:N \l_hobby_tempb_fp}
1832
}
1833
%    \end{macrocode}
1834
% \end{macro}
1835
%
1836
% \begin{macro}{\hobbysetparams}
1837
% This sets the parameters for the curve.
1838
%    \begin{macrocode}
1839
\NewDocumentCommand \hobbysetparams { m } {
1840
  \keys_set:nn { hobby / read in params }
1841
  {
1842
    #1
1843
  }
1844
}
1845
%    \end{macrocode}
1846
% \end{macro}
1847
%
1848
% \begin{macro}{\hobby_set_cmds:nnn}
1849
% The path-generation code doesn't know what to actually do with the path so the initialisation code will set some macros to do that.
1850
% This is an auxiliary command that sets these macros.
1851
%    \begin{macrocode}
1852
\cs_new:Nn \hobby_set_cmds:nnn {
1853
  \cs_set_eq:NN \hobby_moveto:n #1
1854
  \cs_set_eq:NN \hobby_curveto:nnn #2
1855
  \cs_set_eq:NN \hobby_close:n #3
1856
}
1857
%    \end{macrocode}
1858
% \end{macro}
1859
% \begin{macro}{\hobbygenpath}
1860
% This is the user (well, sort of) command that generates and uses the curve.
1861
%    \begin{macrocode}
1862
\tl_new:N \l_tmpc_tl
1863
\NewDocumentCommand \hobbygenpath { } {
1864
  \hobby_genpath:
1865
  \bool_if:NTF \l_hobby_disjoint_bool {
1866
    \array_get:NnN \l_hobby_points_array {0} \l_tmpa_tl
1867
    \exp_args:No \hobby_moveto:n {\l_tmpa_tl}
1868
  }{}
1869
  \prg_stepwise_inline:nnnn {1} {1} {\l_hobby_npoints_int} {
1870
    \array_get:NnN \l_hobby_controla_array {##1} \l_tmpa_tl
1871
    \array_get:NnN \l_hobby_controlb_array {##1} \l_tmpb_tl
1872
    \array_get:NnN \l_hobby_points_array {##1} \l_tmpc_tl
1873
    \exp_args:Nooo \hobby_curveto:nnn {\l_tmpa_tl} {\l_tmpb_tl} {\l_tmpc_tl}
1874
}
1875
  \bool_if:NTF \l_hobby_closed_bool {
1876
    \array_get:NnN \l_hobby_points_array {0} \l_tmpa_tl
1877
    \exp_args:No \hobby_close:n {\l_tmpa_tl}
1878
    }{}
1879
}
1880
%    \end{macrocode}
1881
% \end{macro}
1882
%    \begin{macrocode}
1883
\ExplSyntaxOff
1884
%    \end{macrocode}
1885
% \iffalse
1886
%</hobby>
1887
% \fi
1888
%
1889
% \subsection{PGF Library}
1890
%
1891
% \iffalse
1892
%<*pgflibrary>
1893
% \fi
1894
% 
1895
% The PGF level is very simple.
1896
% All we do is set up the path-construction commands that get passed to the path-generation function.
1897
%    \begin{macrocode}
1898
\input{hobby.code.tex}
1899
%    \end{macrocode}
1900
% Points are communicated as key-pairs.
1901
% These keys translate from the \LaTeX3 style points to PGF points.
1902
%    \begin{macrocode}
1903
\pgfkeys{
1904
  /pgf/hobby/.is family,
1905
  /pgf/hobby/.cd,
1906
  x/.code={\pgf@x=#1cm},
1907
  y/.code={\pgf@y=#1cm},
1908
}
1909
%    \end{macrocode}
1910
% \begin{macro}{\hobby@curveto}
1911
% This is passed to the path-generation code to translate the path into a PGF path.
1912
%    \begin{macrocode}
1913
\def\hobby@curveto#1#2#3{%
1914
  \pgfpathcurveto{\hobby@topgf{#1}}{\hobby@topgf{#2}}{\hobby@topgf{#3}}%
1915
}
1916
%    \end{macrocode}
1917
% \end{macro}
1918
%
1919
% \begin{macro}{\hobby@moveto}
1920
% This is passed to the path-generation code to translate the path into a PGF path.
1921
%    \begin{macrocode}
1922
\def\hobby@moveto#1{%
1923
  \pgfpathmoveto{\hobby@topgf{#1}}%
1924
}
1925
%    \end{macrocode}
1926
% \end{macro}
1927
%
1928
% \begin{macro}{\hobby@topgf}
1929
% Translates a \LaTeX3 point to a PGF point.
1930
%    \begin{macrocode}
1931
\def\hobby@topgf#1{%
1932
    \pgfqkeys{/pgf/hobby}{#1}%
1933
}
1934
%    \end{macrocode}
1935
% \end{macro}
1936
%
1937
% \begin{macro}{\hobby@close}
1938
% Closes a path.
1939
%    \begin{macrocode}
1940
\def\hobby@close#1{%
1941
  \pgfpathclose
1942
}
1943
%    \end{macrocode}
1944
% \end{macro}
1945
% \iffalse
1946
%</pgflibrary>
1947
% \fi
1948
%
1949
% \subsection{TikZ Library}
1950
%
1951
% \iffalse
1952
%<*tikzlibrary>
1953
% \fi
1954
% 
1955
%    \begin{macrocode}
1956
\usepgflibrary{hobby}
1957
\let\hobby@opts=\pgfutil@empty
1958
%    \end{macrocode}
1959
%
1960
% We set various TikZ keys.
1961
% These include the \Verb+to path+ constructor and all the various parameters that will eventually get passed to the path-generation code.
1962
%    \begin{macrocode}
1963
\tikzset{
1964
  curve through/.style={
1965
    to path={
1966
      \pgfextra{
1967
        \expandafter\curvethrough\expandafter[\hobby@opts]{(\tikztostart) .. #1 .. (\tikztotarget)}
1968
      }
1969
    }
1970
  },
1971
  tension in/.code = {},
1972
  tension out/.code = {},
1973
  tension/.code = {},
1974
  excess angle/.code = {},
1975
  closed/.code = {%
1976
    \expandafter\def\expandafter\hobby@opts\expandafter{\hobby@opts closed=#1,disjoint=#1}%
1977
  },
1978
  closed/.default = true,
1979
  in angle/.code = {%
1980
    \pgfmathparse{#1*pi/180}%
1981
    \edef\@temp{ in angle=\pgfmathresult,}%
1982
    \expandafter\expandafter\expandafter%
1983
    \def%
1984
    \expandafter\expandafter\expandafter%
1985
    \hobby@opts%
1986
    \expandafter\expandafter\expandafter%
1987
    {\expandafter\hobby@opts\@temp}%
1988
  },
1989
  out angle/.code = {%
1990
    \pgfmathparse{#1*pi/180}%
1991
    \edef\@temp{ out angle=\pgfmathresult,}%
1992
    \expandafter\expandafter\expandafter%
1993
    \def%
1994
    \expandafter\expandafter\expandafter%
1995
    \hobby@opts%
1996
    \expandafter\expandafter\expandafter%
1997
    {\expandafter\hobby@opts\@temp}%
1998
  },
1999
  in curl/.code = {%
2000
    \expandafter\def\expandafter\hobby@opts\expandafter{\hobby@opts in curl=#1,}%
2001
  },
2002
  out curl/.code = {%
2003
    \expandafter\def\expandafter\hobby@opts\expandafter{\hobby@opts out curl=#1,}%
2004
  },
2005
}
2006
%    \end{macrocode}
2007
% \begin{macro}{\curvethrough}
2008
% This is the parent command.
2009
% We initialise the path-generation code, set any parameters, and then hand over control to the point processing macro.
2010
%    \begin{macrocode}
2011
\newcommand\curvethrough[2][]{%
2012
  \hobbyinit\hobby@moveto\hobby@curveto\hobby@close
2013
  \hobbysetparams{#1}%
2014
  \hobby@processpts{#2}%
2015
}
2016
%    \end{macrocode}
2017
% \end{macro}
2018
%
2019
% \begin{macro}{\hobby@processpts}
2020
% This processes a list of points in the format \Verb+(0,0) .. (1,1)+.
2021
% Each point is scanned by TikZ and then added to the stack to be built into the path.
2022
% If there are any remaining points, we call ourself again with them.
2023
% Otherwise, we hand over control to the path-generation code.
2024
%    \begin{macrocode}
2025
\newcommand\hobby@processpts[1]{%
2026
  \pgfutil@in@{..}{#1}%
2027
  \ifpgfutil@in@%
2028
    \hobby@getonepoint #1 \relax
2029
    \let\hobby@next=\hobby@processpts
2030
  \else
2031
    \def\hobby@pt{#1}%
2032
    \def\hobby@rest{}%
2033
    \let\hobby@next=\hobbygenpath
2034
  \fi
2035
  \let\tikz@scan@point@options=\pgfutil@empty
2036
  \expandafter\tikz@scan@one@point\expandafter\pgfutil@firstofone\hobby@pt\relax
2037
  \pgfmathsetmacro\hobby@x{\the\pgf@x/1cm}%
2038
  \pgfmathsetmacro\hobby@y{\the\pgf@y/1cm}%
2039
  \expandafter\hobbyaddpoint\expandafter{\tikz@scan@point@options,x = \hobby@x, y = \hobby@y}%
2040
  \expandafter\hobby@next\expandafter{\hobby@rest}%
2041
}
2042
%    \end{macrocode}
2043
% \end{macro}
2044
%
2045
% \begin{macro}{\hobby@onepoint}
2046
% This strips off the next point.
2047
%    \begin{macrocode}
2048
\def\hobby@getonepoint#1..#2\relax{%
2049
  \def\hobby@pt{#1}%
2050
  \def\hobby@rest{#2}%
2051
}
2052
%    \end{macrocode}
2053
% \end{macro}
2054
%
2055
% There is a ``spare hook'' in the TikZ path processing code.
2056
% If TikZ encounters a path of the form \Verb+(0,0) .. (1,1)+ then it calls a macro \Verb+\tikz@curveto@auto+.
2057
% However, that macro is not defined in the TikZ code.
2058
% The following code provides a suitable definition.
2059
% \begin{macro}{\tikz@curve@auto}
2060
% When we're called by TikZ, we initialise the path generation code and start adding points.
2061
% We want to be sure that we're only called once so we don't had control back to TikZ but use our own parser to process the rest of the curve until we reach a syntax that we don't understand.
2062
%    \begin{macrocode}
2063
\def\tikz@curveto@auto{%
2064
  \hobbyinit\pgfutil@gobble\hobby@curveto\hobby@close
2065
  \pgfmathsetmacro\hobby@x{\the\tikz@lastx/1cm}%
2066
  \pgfmathsetmacro\hobby@y{\the\tikz@lasty/1cm}%
2067
  \pgfutil@ifundefined{tikz@scan@point@options}{%
2068
    \hobbyaddpoint{x = \hobby@x, y = \hobby@y}%
2069
  }{%
2070
  \expandafter\hobbyaddpoint\expandafter{\tikz@scan@point@options,x = \hobby@x, y = \hobby@y}%
2071
  }%
2072
  \let\tikz@scan@point@options=\pgfutil@empty
2073
  \tikz@scan@one@point\hobby@addfromtikz}
2074
%    \end{macrocode}
2075
% \end{macro}
2076
%
2077
% \begin{macro}{\hobby@addfromtikz}
2078
% This adds our current point to the stack.
2079
%    \begin{macrocode}
2080
\def\hobby@addfromtikz#1{%
2081
  #1%
2082
  \tikz@make@last@position{#1}%
2083
  \pgfmathsetmacro\hobby@x{\the\pgf@x/1cm}%
2084
  \pgfmathsetmacro\hobby@y{\the\pgf@y/1cm}%
2085
  \expandafter\hobbyaddpoint\expandafter{\tikz@scan@point@options,x = \hobby@x, y = \hobby@y}%
2086
  \hobby@donext}
2087
%    \end{macrocode}
2088
% \end{macro}
2089
%
2090
% \begin{macro}{\hobby@donext}
2091
% Now we look to see if the next character is a dot.
2092
% If not, we're done so generate the path.
2093
%    \begin{macrocode}
2094
\def\hobby@donext{%
2095
  \pgfutil@ifnextchar.%
2096
  {\hobby@curveto@auto}%
2097
  {\hobby@finish@auto}}%
2098
%    \end{macrocode}
2099
% \end{macro}
2100
%
2101
% \begin{macro}{\hobby@curveto@auto}
2102
% It was a dot, so look at what comes after.
2103
% It might be \Verb+.. c+ in which case we might be done.
2104
% If not, we assume we're still adding points.
2105
%    \begin{macrocode}
2106
\def\hobby@curveto@auto..{%
2107
  \pgfutil@ifnextchar c%
2108
  {\hobby@maybefinish@auto}%
2109
  {\hobby@curveto@continue}}%
2110
%    \end{macrocode}
2111
% \end{macro}
2112
%
2113
% \begin{macro}{\hobby@maybefinish@auto}
2114
% We got \Verb+.. c+.
2115
% It might be \Verb+.. controls+ in which case we're done.
2116
% It might be \Verb+.. cycle+ in which case we have a closed path, after which we're done.
2117
%    \begin{macrocode}
2118
\def\hobby@maybefinish@auto c{%
2119
  \pgfutil@ifnextchar o%
2120
  {\hobby@finish@auto .. c}%
2121
  {\hobby@closeandfinish@auto}}
2122
%    \end{macrocode}
2123
% \end{macro}
2124
%
2125
% \begin{macro}{\hobby@closeandfinish@auto}
2126
% We got \Verb+.. cycle+ so eat it, flag the path as closed, and generate it.
2127
%    \begin{macrocode}
2128
\def\hobby@closeandfinish@auto ycle{%
2129
  \hobbysetparams{closed=true,disjoint=true}%
2130
  \hobby@finish@auto%
2131
}
2132
%    \end{macrocode}
2133
% \end{macro}
2134
%
2135
% \begin{macro}{\hobby@curveto@continue}
2136
% Scan next coordinate and repeat the cycle.
2137
%    \begin{macrocode}
2138
\def\hobby@curveto@continue{%
2139
  \let\tikz@scan@point@options=\pgfutil@empty
2140
  \tikz@scan@one@point\hobby@addfromtikz}
2141
%    \end{macrocode}
2142
% \end{macro}
2143
%
2144
% \begin{macro}{\hobby@finish@auto}
2145
% Generate the path and then hand control back to TikZ.
2146
%    \begin{macrocode}
2147
\def\hobby@finish@auto{%
2148
  \hobbygenpath
2149
  \tikz@scan@next@command%
2150
}
2151
%    \end{macrocode}
2152
% \end{macro}
2153
% \iffalse
2154
%</tikzlibrary>
2155
% \fi
2156
%
2157
% \subsection{Arrays}
2158
%
2159
% \iffalse
2160
%<*array>
2161
% \fi
2162
% 
2163
%
2164
% A lot of our data structures are really arrays.
2165
% These are implemented as \LaTeX3 ``property lists''.
2166
% For ease of use, an array is a property list with numeric entries together with entries ``base'' and ``top'' which hold the lowest and highest indices that have been set.
2167
%
2168
%    \begin{macrocode}
2169
\RequirePackage{expl3}
2170
\ExplSyntaxOn
2171
%    \end{macrocode}
2172
% Some auxiliary variables.
2173
%    \begin{macrocode}
2174
\tl_new:N \l_array_tmp_tl
2175
\tl_new:N \l_array_show_tl
2176
\int_new:N \l_array_base_int
2177
\int_new:N \l_array_top_int
2178
\int_new:N \l_array_tmp_int
2179
%    \end{macrocode}
2180
% The global variable \Verb+\g_array_base_int+ says what index a blank array should start with when pushed or unshifted.
2181
%    \begin{macrocode}
2182
\int_new:N \g_array_base_int
2183
\int_set:Nn \g_array_base_int {0}
2184
%    \end{macrocode}
2185
% \begin{macro}{\array_adjust_ends:Nn}
2186
% This ensures that the ``base'' and ``top'' are big enough to include the given index.
2187
%    \begin{macrocode}
2188
\cs_new:Npn \array_adjust_ends:Nn #1#2 {
2189
  \prop_get:NnNTF #1 {base} \l_tmpa_tl
2190
  {
2191
    \int_compare:nNnTF {\l_tmpa_tl} > {#2}
2192
    {
2193
      \prop_put:Nnx #1 {base} {\int_eval:n {#2}}
2194
    }
2195
    {}
2196
  }
2197
  {
2198
    \prop_put:Nnx #1 {base} {\int_eval:n {#2}}
2199
  }
2200
  \prop_get:NnNTF #1 {top} \l_tmpa_tl
2201
  {
2202
    \int_compare:nNnTF {\l_tmpa_tl} < {#2}
2203
    {
2204
      \prop_put:Nnx #1 {top} {\int_eval:n {#2}}
2205
    }
2206
    {}
2207
  }
2208
  {
2209
    \prop_put:Nnx #1 {top} {\int_eval:n {#2}}
2210
  }
2211
}
2212
%    \end{macrocode}
2213
% \end{macro}
2214
%
2215
% \begin{macro}{\array_put:Nnn}
2216
% When adding a value to an array we have to adjust the ends.
2217
%    \begin{macrocode}
2218
\cs_new:Npn \array_put:Nnn #1#2#3 {
2219
  \exp_args:NNx \prop_put:Nnn #1 {\int_eval:n {#2}} {#3}
2220
  \array_adjust_ends:Nn #1{#2}
2221
}
2222
\cs_generate_variant:Nn \array_put:Nnn {Nnx}
2223
%    \end{macrocode}
2224
% \end{macro}
2225
%
2226
% \begin{macro}{\array_get:NnN}
2227
%    \begin{macrocode}
2228
\cs_new:Npn \array_get:NnN #1#2#3 {
2229
  \exp_args:NNx \prop_get:NnN #1 {\int_eval:n {#2}} #3
2230
}
2231
%    \end{macrocode}
2232
% \end{macro}
2233
%
2234
% \begin{macro}[EXP]{\array_get:Nn}
2235
%    \begin{macrocode}
2236
\cs_new:Npn \array_get:Nn #1#2 {
2237
  \exp_args:NNf \prop_get:Nn #1 { \int_eval:n {#2} }
2238
}
2239
%    \end{macrocode}
2240
% \end{macro}
2241
%
2242
% \begin{macro}{\array_get:NnNTF}
2243
%    \begin{macrocode}
2244
\cs_new:Npn \array_get:NnNTF #1#2#3#4#5 {
2245
  \exp_args:NNx \prop_get:NnNTF #1 {\int_eval:n {#2}} #3 {#4}{#5}
2246
}
2247
%    \end{macrocode}
2248
% \end{macro}
2249
%
2250
% \begin{macro}{\array_if_empty:NTF}
2251
%    \begin{macrocode}
2252
\cs_new_eq:NN \array_if_empty:NTF \prop_if_empty:NTF
2253
%    \end{macrocode}
2254
% \end{macro}
2255
%
2256
% \begin{macro}{\array_new:N}
2257
%    \begin{macrocode}
2258
\cs_new_eq:NN \array_new:N \prop_new:N
2259
%    \end{macrocode}
2260
% \end{macro}
2261
%
2262
% \begin{macro}{\array_clear:N}
2263
%    \begin{macrocode}
2264
\cs_new_eq:NN \array_clear:N \prop_clear:N
2265
%    \end{macrocode}
2266
% \end{macro}
2267
%
2268
% \begin{macro}{\array_map_function}
2269
% When stepping through an array, we want to iterate in order so a simple wrapper to \Verb+\prop_map_function+ is not enough.
2270
% This maps through every value from the base to the top so the function should be prepared to deal with a \Verb+\q_no_value+.
2271
%    \begin{macrocode}
2272
\cs_new:Npn \array_map_function:NN #1#2
2273
{
2274
  \array_if_empty:NTF #1 {} {
2275
    \prop_get:NnNTF #1 {base} \l_array_tmp_tl {
2276
      \int_set:Nn \l_array_base_int {\l_array_tmp_tl}
2277
    }{
2278
      \int_set:Nn \l_array_base_int {0}
2279
    }
2280
    \prop_get:NnNTF #1 {top} \l_array_tmp_tl {
2281
      \int_set:Nn \l_array_top_int {\l_array_tmp_tl}
2282
    }{
2283
      \int_set:Nn \l_array_top_int {0}
2284
    }
2285
    \prg_stepwise_inline:nnnn {\l_array_base_int} {1} {\l_array_top_int} {
2286
  \array_get:NnN #1 {##1} \l_array_tmp_tl
2287
  \exp_args:Nno #2 {##1} \l_array_tmp_tl
2288
}
2289
} {}
2290
}
2291
\cs_generate_variant:Nn \array_map_function:NN {     Nc }
2292
\cs_generate_variant:Nn \array_map_function:NN { c , cc }
2293
%    \end{macrocode}
2294
% \end{macro}
2295
%
2296
% \begin{macro}{\array_reverse_map_function}
2297
% This steps through the array in reverse order.
2298
%    \begin{macrocode}
2299
\cs_new:Npn \array_reverse_map_function:NN #1#2
2300
{
2301
  \array_if_empty:NTF #1 {} {
2302
    \prop_get:NnNTF #1 {base} \l_array_tmp_tl {
2303
      \int_set:Nn \l_array_base_int {\l_array_tmp_tl}
2304
    }{
2305
      \int_set:Nn \l_array_base_int {0}
2306
    }
2307
    \prop_get:NnNTF #1 {top} \l_array_tmp_tl {
2308
      \int_set:Nn \l_array_top_int {\l_array_tmp_tl}
2309
    }{
2310
      \int_set:Nn \l_array_top_int {0}
2311
    }
2312
    \prg_stepwise_inline:nnnn {\l_array_top_int} {-1} {\l_array_base_int} {
2313
  \array_get:NnN #1 {##1} \l_array_tmp_tl
2314
  \exp_args:Nno #2 {##1} \l_array_tmp_tl
2315
}
2316
} {}
2317
}
2318
\cs_generate_variant:Nn \array_reverse_map_function:NN {     Nc }
2319
\cs_generate_variant:Nn \array_reverse_map_function:NN { c , cc }
2320
%    \end{macrocode}
2321
% \end{macro}
2322
%
2323
% \begin{macro}{\array_map_inline:Nn}
2324
% Inline version of the above.
2325
%    \begin{macrocode}
2326
\cs_new_protected:Npn \array_map_inline:Nn #1#2
2327
  {
2328
    \int_gincr:N \g_prg_map_int
2329
    \cs_gset:cpn { array_map_inline_ \int_use:N \g_prg_map_int :nn }
2330
      ##1##2 {#2}
2331
    \exp_args:NNc \array_map_function:NN #1
2332
      { array_map_inline_ \int_use:N \g_prg_map_int :nn }
2333
    \prg_break_point:n { \int_gdecr:N \g_prg_map_int }
2334
  }
2335
\cs_generate_variant:Nn \array_map_inline:Nn { c }
2336
%    \end{macrocode}
2337
% \end{macro}
2338
%
2339
% \begin{macro}{\array_reverse_map_inline:Nn}
2340
% Inline version of the above.
2341
%    \begin{macrocode}
2342
\cs_new_protected:Npn \array_reverse_map_inline:Nn #1#2
2343
  {
2344
    \int_gincr:N \g_prg_map_int
2345
    \cs_gset:cpn { array_map_inline_ \int_use:N \g_prg_map_int :nn }
2346
      ##1##2 {#2}
2347
    \exp_args:NNc \array_reverse_map_function:NN #1
2348
      { array_map_inline_ \int_use:N \g_prg_map_int :nn }
2349
    \prg_break_point:n { \int_gdecr:N \g_prg_map_int }
2350
  }
2351
\cs_generate_variant:Nn \array_reverse_map_inline:Nn { c }
2352
%    \end{macrocode}
2353
% \end{macro}
2354
%
2355
% For displaying arrays, we need some messages.
2356
%    \begin{macrocode}
2357
\msg_kernel_new:nnn { array } { show }
2358
  {
2359
    The~array~\token_to_str:N #1~
2360
    \array_if_empty:NTF #1
2361
      { is~empty }
2362
      { contains~the~items~(without~outer~braces): }
2363
  }
2364
%    \end{macrocode}
2365
%
2366
% \begin{macro}{\array_show:N}
2367
% Mapping through an array isn't expandable so we have to set a token list to its contents first before passing it to the message handler.
2368
%    \begin{macrocode}
2369
\cs_new_protected:Npn \array_show:N #1
2370
  {
2371
    \tl_clear:N \l_array_show_tl
2372
    \array_map_function:NN #1 \array_show_aux:nn
2373
    \msg_aux_show:Nnx
2374
      #1
2375
      { array }
2376
      { \l_array_show_tl }
2377
  }
2378
2379
\cs_new_protected:Npn \array_show_aux:nn #1#2
2380
{
2381
  \tl_if_eq:NNTF {#2} {\q_no_value} {}
2382
  {
2383
  \tl_put_right:No \l_array_show_tl {\msg_aux_show:nn {#1}{#2}}
2384
  }
2385
}
2386
\cs_generate_variant:Nn \array_show:N { c }
2387
%    \end{macrocode}
2388
% \end{macro}
2389
%
2390
% \begin{macro}{\array_push:Nn}
2391
%    \begin{macrocode}
2392
\cs_new_protected:Npn \array_push:Nn #1#2
2393
{
2394
  \prop_get:NnNTF #1 {top} \l_array_tmp_tl
2395
  {
2396
    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
2397
    \int_incr:N \l_array_tmp_int
2398
    \array_put:Nnn #1 {\l_array_tmp_int} {#2}
2399
  }
2400
  {
2401
    \array_put:Nnn #1 {\g_array_base_int} {#2}
2402
  }
2403
}
2404
%    \end{macrocode}
2405
% \end{macro}
2406
%
2407
% \begin{macro}{\array_unshift:Nn}
2408
%    \begin{macrocode}
2409
\cs_new_protected:Npn \array_unshift:Nn #1#2
2410
{
2411
  \prop_get:NnNTF #1 {base} \l_array_tmp_tl
2412
  {
2413
    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
2414
    \int_decr:N \l_array_tmp_int
2415
    \array_put:Nnn #1 {\l_array_tmp_int} {#2}
2416
  }
2417
  {
2418
    \array_put:Nnn #1 {\g_array_base_int} {#2}
2419
  }
2420
2421
}
2422
%    \end{macrocode}
2423
% \end{macro}
2424
%
2425
% \begin{macro}{\array_pop:NN}
2426
%    \begin{macrocode}
2427
\cs_new_protected:Npn \array_pop:NN #1#2
2428
{
2429
  \prop_get:NnN #1 {top} \l_array_tmp_tl
2430
  \array_get:NnN #1 {\l_array_tmp_tl} #2
2431
  \array_del:Nn #1 {\l_array_tmp_tl}
2432
}
2433
%    \end{macrocode}
2434
% \end{macro}
2435
%
2436
% \begin{macro}{\array_shift:NN}
2437
%    \begin{macrocode}
2438
\cs_new_protected:Npn \array_shift:NN #1#2
2439
{
2440
  \prop_get:NnN #1 {base} \l_array_tmp_tl
2441
  \array_get:NnN #1 {\l_array_tmp_tl} #2
2442
  \array_del:Nn #1 {\l_array_tmp_tl}
2443
}
2444
%    \end{macrocode}
2445
% \end{macro}
2446
%
2447
% \begin{macro}{\array_del:Nn}
2448
%    \begin{macrocode}
2449
\cs_new_protected:Npn \array_del:Nn #1#2
2450
{
2451
  \exp_args:NNx \prop_del:Nn #1 {\int_eval:n {#2}}
2452
  \int_set:Nn \l_array_tmp_int {0}
2453
  \array_map_inline:Nn #1 {
2454
    \tl_if_eq:NNTF {##2} {\q_no_value} {}
2455
    {
2456
      \int_incr:N \l_array_tmp_int
2457
    }
2458
  }
2459
  \int_show:N \l_array_tmp_int
2460
  \int_compare:nNnTF {\l_array_tmp_int} = {0}
2461
  {
2462
    \prop_clear:N #1
2463
  }
2464
  {
2465
  \prop_get:NnN #1 {top} \l_array_tmp_tl
2466
  \int_compare:nNnTF {#2} = {\l_array_tmp_tl} {
2467
    \prop_get:NnN #1 {base} \l_array_tmp_tl
2468
    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
2469
    \array_map_inline:Nn #1 {
2470
    \tl_if_eq:NNTF {##2} {\q_no_value} {}
2471
    {
2472
      \int_compare:nNnTF {\l_array_tmp_int} < {##1} {
2473
        \int_set:Nn \l_array_tmp_int {##1}
2474
      }{}
2475
    }
2476
      }
2477
    \prop_put:Nnx #1 {top} {\int_use:N \l_array_tmp_int}
2478
  }{}
2479
  \prop_get:NnN #1 {base} \l_array_tmp_tl
2480
  \int_compare:nNnTF {#2} = {\l_array_tmp_tl} {
2481
p    \prop_get:NnN #1 {top} \l_array_tmp_tl
2482
    \int_set:Nn \l_array_tmp_int {\l_array_tmp_tl}
2483
    \array_map_inline:Nn #1 {
2484
    \tl_if_eq:NNTF {##2} {\q_no_value} {}
2485
    {
2486
      \int_compare:nNnTF {\l_array_tmp_int} > {##1} {
2487
        \int_set:Nn \l_array_tmp_int {##1}
2488
      }{}
2489
    }
2490
      }
2491
    \prop_put:Nnx #1 {base} {\int_use:N \l_array_tmp_int}
2492
  }{}
2493
  }
2494
}
2495
%    \end{macrocode}
2496
% \end{macro}
2497
%
2498
%    \begin{macrocode}
2499
\ExplSyntaxOff
2500
%    \end{macrocode}
2501
%
2502
% \iffalse
2503
%</array>
2504
% \fi
2505
% 
2506
%\Finale