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 |