-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path2d-transform.tex
249 lines (199 loc) · 15.7 KB
/
2d-transform.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
\documentclass{article}% ===> this file was generated automatically by noweave --- better not edit it
\usepackage{amsfonts}
\usepackage{amsmath}
\usepackage{noweb}
\font\biggrm=cmr10 scaled \magstep2
\font\bigbf=cmbx10 scaled \magstep1
\def\R{\mathbb{R}}
\def\refcode#1{$\langle\hbox{\em #1}\rangle$}
\def\heading#1{\noindent\bigbf #1 \rm\par\smallskip}
\def\img from #1 width #2 caption #3{\par\bigskip\bigskip\vbox{\centerline{\pdfximage width #2{#1}\pdfrefximage\pdflastximage}\smallskip\centerline{#3}}\par\bigskip\bigskip}
\begin{document}
\centerline{\biggrm 2d-transform}
\smallskip
\centerline{A program by Way Yan}
\bigskip
\nwfilename{2d-transform.nw}\nwbegindocs{1}\heading{Introduction}
{\Tt{}2d-transform\nwendquote} was developed while I was learning multivariable calculus. It is used to visualise functions $f$ from $\R^2$ to $\R^2$. It does so by applying $f$ to the gridlines in the domain, then drawing the result as a pdf file. I use pdf instead of other file formats like png and svg, because pdfs can be scaled without losing quality unlike pngs, and they are natively supported by {\Tt{}pdftex\nwendquote} unlike svgs.
\bigskip
\nwenddocs{}\nwbegindocs{2}\heading{Usage}
There should be two shell scripts in the same directory as this file, {\Tt{}compile.sh\nwendquote} and {\Tt{}build.sh\nwendquote}. {\Tt{}build.sh\nwendquote} generates the \LaTeX\ documentation and C source from {\Tt{}2d-transform.nw\nwendquote} and additionally compiles the C source into the executable {\Tt{}2d-transform\nwendquote}. {\Tt{}compile.sh\nwendquote} only does the second step.
Initially, {\Tt{}2d-transform\nwendquote} will generate an output pdf with a set of default parameters. To run {\Tt{}2d-transform\nwendquote} with different parameters, the user first has to edit the \refcode{parameters} section in {\Tt{}2d-transform.c\nwendquote}. Then, the user should run {\Tt{}compile.sh\nwendquote} to generate a new executable. This executable, when run, produces a pdf file based on the parameters specified earlier.
The main reason for requiring the parameters to be specified in the source code is to make my life as a programmer easier. If the function $f$ were to be specified instead as a command-line argument to the executable, I would have to add code to parse the definition of $f$ as a mathematical expression. Though it is certainly not impossible to implement, I find it an unwanted distraction.
The following code outlines the top-level structure of {\Tt{}2d-transform\nwendquote}:
\nwenddocs{}\nwbegincode{3}\moddef{2d-transform.c}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
\LA{}headers\RA{}
\LA{}parameters\RA{}
\LA{}internal state\RA{}
\LA{}cartesian-to-cairo-coords\RA{}
int main() \{
\LA{}setup\RA{}
\LA{}draw\RA{}
\LA{}cleanup\RA{}
\}
\nwendcode{}\nwbegindocs{4}\heading{Headers}
{\Tt{}2d-transform\nwendquote} has very few dependencies, hence this section is small. {\Tt{}stdio\nwendquote} consists of standard input/output functions (mainly {\Tt{}printf\nwendquote} for debugging), {\Tt{}math\nwendquote} allows the user to input functions like {\Tt{}sin\nwendquote} in \refcode{parameters}, and {\Tt{}cairo\nwendquote} is the graphics library responsible for drawing. I use {\Tt{}cairo\nwendquote} primarily because it supports pdf output, unlike other libraries.
\nwenddocs{}\nwbegincode{5}\moddef{headers}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
#include <stdio.h>
#include <math.h>
#include <cairo.h>
\nwendcode{}\nwbegindocs{6}\heading{Parameters}
These are the things that the user has to specify before compiling {\Tt{}2d-transform.c\nwendquote} and running the executable to obtain the pdf.
Of course, the main thing we want to specify is the actual function to draw. These are provided through the parameters {\Tt{}f1\nwendquote} and {\Tt{}f2\nwendquote}, which represent the $x$ and $y$ components of $f$. If the source code is not modified, then the default function is the identity.
\nwenddocs{}\nwbegincode{7}\moddef{parameters}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
#define f1(x,y) sin(x)
#define f2(x,y) y+sin(x)
\LA{}gridline parameters\RA{}
\LA{}test square parameters\RA{}
\LA{}misc parameters\RA{}
\nwendcode{}\nwbegindocs{8}Next are the parameters for the drawing of gridlines. Essentially, the domain is defined to be $${\Tt{}[dom{\_}xmin\nwendquote}, {\Tt{}dom{\_}xmax]\nwendquote} \times {\Tt{}[dom{\_}ymin\nwendquote},{\Tt{}dom{\_}ymax]\nwendquote}.$$ Only the portions of the gridlines lying in the domain are considered for the drawing. Similarly, the codomain is defined to be $${\Tt{}[codom{\_}xmin\nwendquote},{\Tt{}codom{\_}xmax]\nwendquote} \times {\Tt{}[codom{\_}ymin\nwendquote},{\Tt{}codom{\_}ymax]\nwendquote}.$$ If you imagine the codomain to be a rectangle in $\R^2$ containing the transformed gridlines from the domain, then the pdf output will look like that rectangle, but scaled to the dimension specified by {\Tt{}pdfsize\nwendquote}. Also, {\Tt{}gridlinegap\nwendquote} specifies the distance between consecutive horizontal and vertical gridlines in the domain.
Now, each gridline $L$ has infinitely many points---hence one cannot hope to fully determine $f(L)$ by computing $f(x)$ for every single point $x \in L$. Rather, in the case where $L$ is a vertical line, the program will go from top to bottom, sampling a finite number of uniformly spaced points $x_i$ along $L$ (see \refcode{draw one vertical gridline} for more details). The spacing between the points $x_i$ is governed by {\Tt{}samplegap\nwendquote}. The smaller {\Tt{}samplegap\nwendquote} is, the more accurate the output will be. Both {\Tt{}gridlinegap\nwendquote} and {\Tt{}samplegap\nwendquote} have no units, because they represent lengths in the coordinate system of the domain.
\nwenddocs{}\nwbegincode{9}\moddef{gridline parameters}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
#define dom_xmin -5
#define dom_xmax 5
#define dom_ymin -5
#define dom_ymax 5
#define codom_xmin -5
#define codom_xmax 5
#define codom_ymin -5
#define codom_ymax 5
#define gridlinegap 0.5
#define samplegap 0.1
\nwendcode{}\nwbegindocs{10}{\Tt{}2d-transform\nwendquote} also has a feature to visualise the image of a specified square under $f$. It is enabled by setting {\Tt{}tsenable\nwendquote} to 1 and disabled by setting it to 0. The difference between this and gridlines is that the area of the square is filled in with points, and also the sides and corners of the square are drawn in such a way that they can be distinguished. Hence this feature reveals some features of $f$ that are otherwise hard to tell, such as orientation. It is also helpful when there are many gridlines mapping to the same region in the domain.
The square, as a subset of the domain, is defined as
$${\Tt{}[ts{\_}xmin\nwendquote},{\Tt{}ts{\_}xmax]\nwendquote} \times {\Tt{}[ts{\_}ymin\nwendquote},{\Tt{}ts{\_}ymax]\nwendquote}.$$
Also, {\Tt{}tsgap\nwendquote} specifies the gap to leave between points when filling up the area of the square. As with {\Tt{}gridlinegap\nwendquote} and {\Tt{}samplegap\nwendquote}, the parameter {\Tt{}tsgap\nwendquote} has no units because it is relative to the coordinate system of the codomain.
\nwenddocs{}\nwbegincode{11}\moddef{test square parameters}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
#define tsenable 1
#define ts_xmin -2
#define ts_xmax 2
#define ts_ymin -2
#define ts_ymax 2
#define tsgap 0.4
\nwendcode{}\nwbegindocs{12}The original and transformed test square is shown in Figures 1 and 2. Note that as one goes from left to the right, the size of the circles transitions from {\Tt{}ts{\_}startsize\nwendquote} to {\Tt{}ts{\_}endsize\nwendquote}. Likewise as one goes from bottom to top, the colour transitions from $({\Tt{}ts{\_}startr\nwendquote}, {\Tt{}ts{\_}startg\nwendquote}, {\Tt{}ts{\_}startb\nwendquote})$ to $({\Tt{}ts{\_}endr\nwendquote}, {\Tt{}ts{\_}endg\nwendquote}, {\Tt{}ts{\_}endb\nwendquote})$.
\img from tsoriginal.pdf width 2in caption {Figure 1: How the square looks like before transformation.}
\img from tstransformed.pdf width 2in caption {Figure 2: How the square looks like after the transformation $(\sin x,\, y+\sin x)$.}
\nwenddocs{}\nwbegincode{13}\moddef{test square parameters}\plusendmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
#define ts_startsize 1
#define ts_endsize 3
#define ts_startr 255
#define ts_startg 0
#define ts_startb 0
#define ts_endr 0
#define ts_endg 0
#define ts_endb 255
\nwendcode{}\nwbegindocs{14}Lastly we have miscellaneous parameters. {\Tt{}pdfsize\nwendquote} is width of the pdf, in points. (For reference, an A4-sized pdf is 595 points wide and 842 points high.) Actually, {\Tt{}pdfsize\nwendquote} is also the height of the pdf, because I make the width and height equal for simplicity. {\Tt{}outputfile\nwendquote} is the name of the pdf file to save the drawing to.
\nwenddocs{}\nwbegincode{15}\moddef{misc parameters}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
#define pdfsize 100
#define outputfile "out.pdf"
\nwendcode{}\nwbegindocs{16}\heading{Internal state}
These are variables that are used internally, and should be not be touched by the user. Firstly, I provide the function definition of {\Tt{}cairo{\_}pdf{\_}surface{\_}create\nwendquote}; otherwise, the compiler throws an {\Tt{}Implicit\ function\ declaration\nwendquote} error (I'm not fully sure why). Then, {\Tt{}surface\nwendquote} and {\Tt{}cr\nwendquote} is used by {\Tt{}cairo\nwendquote} for drawing.
Lastly, {\Tt{}pair\nwendquote} is effectively a register variable that stores the output after calling {\Tt{}cartesian{\_}to{\_}cairo{\_}coords\nwendquote}. It is implemented this way, because C does not allow {\Tt{}cartesian{\_}to{\_}cairo{\_}coords\nwendquote} to return an array directly. It can return a pointer to an array, but I find it completely unnecessary to deal with pointers for such a small array. Hence I resorted to the simplest solution.
\nwenddocs{}\nwbegincode{17}\moddef{internal state}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
cairo_surface_t *cairo_pdf_surface_create(const char *filename,
double width_in_points, double height_in_points);
cairo_surface_t *surface;
cairo_t *cr;
double pair[2];
\nwendcode{}\nwbegindocs{18}\heading{Changing coordinate systems}
The coordinate system that {\Tt{}cairo\nwendquote} uses is different from the Cartesian coordinate system: the top-left corner is {\Tt{}(0,0)\nwendquote} while the bottom-right corner is {\Tt{}(pdfsize,pdfsize)\nwendquote}. In Cartesian coordinates, {\Tt{}(0,0)\nwendquote} would be at the center.
The function takes in the Cartesian coordinates $(x,y)$ as input. It computes the values $t_x,t_y \in [0,1]$ such that
\begin{align*}
x &= (1-t_x)*{\Tt{}codom{\_}xmin\nwendquote} \,+\, t_x*{\Tt{}codom{\_}xmax\nwendquote}\quad\hbox{and} \\
y &= (1-t_y)*{\Tt{}codom{\_}ymax\nwendquote} \,+\, t_y*{\Tt{}codom{\_}ymin\nwendquote}.
\end{align*}
Essentially, $t_x$ measures how far right $(x,y)$ should be in the pdf file from the left; $t_y$ measures how far down it should be from the top. Note that going up in a pdf file corresponds to decreasing the $y$ coordinate. Then, we simply scale $t_x$ and $t_y$ by {\Tt{}pdfsize\nwendquote}.
\nwenddocs{}\nwbegincode{19}\moddef{cartesian-to-cairo-coords}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
void cartesian_to_cairo_coords(double x, double y) \{
double tx = (x-codom_xmin)/(codom_xmax-codom_xmin);
double ty = (y-codom_ymax)/(codom_ymin-codom_ymax);
pair[0] = tx * pdfsize;
pair[1] = ty * pdfsize;
\}
\nwendcode{}\nwbegindocs{20}\heading{Setup and cleanup}
Nothing much to say really.
\nwenddocs{}\nwbegincode{21}\moddef{setup}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
surface = cairo_pdf_surface_create(outputfile, pdfsize, pdfsize);
cr = cairo_create(surface);
cairo_set_source_rgb(cr, 0, 0, 0);
cairo_set_line_width(cr, 0.1);
\nwendcode{}\nwbegincode{22}\moddef{cleanup}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
cairo_destroy(cr);
cairo_surface_destroy(surface);
return 0;
\nwendcode{}\nwbegindocs{23}\heading{Drawing}
This is the real meat of the program.
\nwenddocs{}\nwbegincode{24}\moddef{draw}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
double xcur, ycur;
\LA{}draw transformed vertical gridlines\RA{}
\LA{}draw transformed horizontal gridlines\RA{}
if (tsenable) \{
\LA{}draw test square\RA{}
\}
\nwendcode{}\nwbegincode{25}\moddef{draw transformed vertical gridlines}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
\LA{}find leftmost vertical gridline\RA{}
\LA{}iterate through vertical gridlines\RA{}
\nwendcode{}\nwbegindocs{26}Besides being equally spaced according to {\Tt{}gridlinegap\nwendquote}, I require that one of the vertical gridlines be the $y$-axis. Doing so fixes all the other vertical gridlines in $\R^2$ and hence $D$.
With this, the leftmost vertical gridline is the {\Tt{}n\nwendquote}th one from the $y$-axis. If {\Tt{}n>0\nwendquote}, then it lies to the right of the $y$-axis. If {\Tt{}n<0\nwendquote} then it lies to the left.
\nwenddocs{}\nwbegincode{27}\moddef{find leftmost vertical gridline}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
int n = dom_xmin / gridlinegap;
xcur = n * gridlinegap;
\nwendcode{}\nwbegindocs{28}The vertical gridlines which intersect $D$ are drawn.
\nwenddocs{}\nwbegincode{29}\moddef{iterate through vertical gridlines}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
while (xcur <= dom_xmax) \{
\LA{}draw one vertical gridline\RA{}
cairo_stroke(cr);
xcur += gridlinegap;
\}
\nwendcode{}\nwbegindocs{30}The point {\Tt{}(xcur,ycur)\nwendquote} iterates through the sampled points $x_i$ (see \refcode{parameters}). Lines are drawn connecting each pair $(f(x_i),f(x_{i+1}))$, the idea being that the resulting collection of line segments closely resembles $f(L)$.
\nwenddocs{}\nwbegincode{31}\moddef{draw one vertical gridline}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
ycur = dom_ymin;
cartesian_to_cairo_coords(f1(xcur,ycur), f2(xcur,ycur));
cairo_move_to(cr, pair[0], pair[1]);
while (ycur <= dom_ymax) \{
cartesian_to_cairo_coords(f1(xcur,ycur), f2(xcur,ycur));
cairo_line_to(cr, pair[0], pair[1]);
ycur += samplegap;
\}
\nwendcode{}\nwbegindocs{32}The code for drawing horizontal gridlines is essentially the same except the characters {\Tt{}x\nwendquote} and {\Tt{}y\nwendquote} are swapped. Hence I will not divide it further.
\nwenddocs{}\nwbegincode{33}\moddef{draw transformed horizontal gridlines}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
n = dom_ymin / gridlinegap;
ycur = n * gridlinegap;
while (ycur <= dom_ymax) \{
xcur = dom_xmin;
cartesian_to_cairo_coords(f1(xcur,ycur), f2(xcur,ycur));
cairo_move_to(cr, pair[0], pair[1]);
while (xcur <= dom_xmax) \{
cartesian_to_cairo_coords(f1(xcur,ycur), f2(xcur,ycur));
cairo_line_to(cr, pair[0], pair[1]);
xcur += samplegap;
\}
cairo_stroke(cr);
ycur += gridlinegap;
\}
\nwendcode{}\nwbegindocs{34}Next, the test square is drawn.
\nwenddocs{}\nwbegincode{35}\moddef{draw test square}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
double curx, cury;
double tx, ty, r, g, b, size;
curx = ts_xmin;
while (curx <= ts_xmax) \{
cury = ts_ymin;
while (cury <= ts_ymax) \{
\LA{}draw circle\RA{}
cury += tsgap;
\}
curx += tsgap;
\}
\nwendcode{}\nwbegincode{36}\moddef{draw circle}\endmoddef\nwstartdeflinemarkup\nwenddeflinemarkup
tx = (curx-ts_xmin)/(ts_xmax-ts_xmin);
ty = (cury-ts_ymin)/(ts_ymax-ts_ymin);
r = ((1-ty)*ts_startr + ty*ts_endr) / 255;
g = ((1-ty)*ts_startg + ty*ts_endg) / 255;
b = ((1-ty)*ts_startb + ty*ts_endb) / 255;
size = (1-tx)*ts_startsize + tx*ts_endsize;
cairo_set_source_rgba(cr, r, g, b, 0.5);
cartesian_to_cairo_coords(f1(curx,cury), f2(curx, cury));
cairo_arc(cr, pair[0], pair[1], size, 0, 2*M_PI);
cairo_fill(cr);
\nwendcode{}\nwbegindocs{37}\end{document}
\nwenddocs{}