Apa alat terbaik untuk digunakan untuk menggambar gambar vektor? Bagi saya dan mungkin bagi banyak orang lain, jawabannya cukup jelas: Illustrator, atau, mungkin, Inkscape. Setidaknya itulah yang saya pikirkan ketika saya diminta menggambar sekitar delapan ratus diagram untuk buku teks fisika. Tidak ada yang luar biasa, hanya sekelompok ilustrasi hitam dan putih dengan bola, mata air, katrol, lensa dan sebagainya. Pada saat itu sudah diketahui bahwa buku itu akan dibuat di LaTeX dan saya diberi sejumlah dokumen MS Word dengan gambar yang disematkan. Beberapa di antaranya adalah gambar yang dipindai dari buku lain, beberapa gambar pensil. Membayangkan berhari-hari melakukan inkscaping hal ini membuat saya merasa pusing, jadi segera saya mendapati diri saya berfantasi tentang solusi yang lebih otomatis. Entah mengapa
MetaPost menjadi fokus fantasi ini.

Keuntungan utama menggunakan solusi MetaPost (atau serupa) adalah bahwa setiap gambar bisa menjadi semacam fungsi dari beberapa variabel. Gambar seperti itu dapat dengan cepat disesuaikan untuk keadaan tata letak yang tidak terduga, tanpa mengganggu hubungan internal penting dari ilustrasi (sesuatu yang saya benar-benar khawatirkan), yang tidak mudah dicapai dengan alat yang lebih tradisional. Juga, elemen berulang, semua bidang dan pegas ini, dapat dibuat lebih menarik secara visual daripada yang diizinkan oleh alat konvensional dengan batasan waktu yang sama.
Saya ingin membuat foto dengan semacam penetasan, tidak seperti apa yang Anda temui di buku-buku lama.

Pertama, saya harus dapat menghasilkan beberapa kurva dengan ketebalan yang bervariasi. Komplikasi utama di sini adalah membuat kurva yang mengikuti kurva asli pada jarak yang bervariasi. Saya mungkin menggunakan
metode kerja yang paling primitif, yang bermuara hanya dengan menggeser segmen garis yang menghubungkan titik kontrol kurva Bezier dengan jarak tertentu, kecuali jarak ini bervariasi di sepanjang kurva.

Dalam kebanyakan kasus, itu berfungsi dengan baik.

Kode contohDari sini diasumsikan bahwa perpustakaan
diunduh dan
input fiziko.mp;
hadir dalam kode MetaPost. Metode tercepat adalah dengan menggunakan ConTeXt (maka Anda tidak perlu
beginfig
dan
endfig
):
\starttext
\startMPcode
input fiziko.mp;
% the code goes here
\stopMPcode
\stoptext
atau LuaLaTeX:
\documentclass{article}
\usepackage{luamplib}
\begin{document}
\begin{mplibcode}
input fiziko.mp;
% the code goes here
\end{mplibcode}
\end{document}
beginfig(3);
path p, q; % MetaPost's syntax is reasonably readable, so I'll comment mostly on my stuff
p := (0,-1/4cm){dir(30)}..(5cm, 0)..{dir(30)}(10cm, 1/4cm);
q := offsetPath(p)(1cm*sin(offsetPathLength*pi)); % first argument is the path itself, second is a function of the position along this path (offsetPathLength changes from 0 to 1), which determines how far the outline is from the original line
draw p;
draw q dashed evenly;
endfig;
Dua garis besar dapat digabungkan untuk membuat garis kontur untuk goresan dengan ketebalan variabel.

Kode contohbeginfig(4);
path p, q[];
p := (0,-1/4cm){dir(30)}..(5cm, 0)..{dir(30)}(10cm, 1/4cm);
q1 := offsetPath(p)(1/2pt*(sin(offsetPathLength*pi)**2)); % outline on one side
q2 := offsetPath(p)(-1/2pt*(sin(offsetPathLength*pi)**2)); % and on the other
fill q1--reverse(q2)--cycle;
endfig;
Ketebalan garis harus memiliki beberapa batas bawah, jika tidak, beberapa garis akan terlalu tipis untuk dicetak dengan benar dan ini tidak terlihat bagus. Salah satu opsi (yang saya pilih) adalah membuat garis-garis yang terlalu tipis putus-putus, sehingga jumlah total tinta per satuan panjang tetap kira-kira sama dengan garis tipis yang dimaksud. Dengan kata lain, alih-alih mengurangi jumlah tinta pada sisi garis, algoritme mengambil sebagian tinta dari garis itu sendiri.

Kode contohbeginfig(5);
path p;
p := (0,-1/4cm){dir(30)}..(5cm, 0)..{dir(30)}(10cm, 1/4cm);
draw brush(p)(1pt*(sin(offsetPathLength*pi)**2)); % the arguments are the same as for the outline
endfig;
Setelah Anda memiliki garis ketebalan variabel yang berfungsi, Anda bisa menggambar bola. Sebuah bola dapat digambarkan sebagai serangkaian lingkaran konsentris dengan ketebalan garis yang bervariasi sesuai dengan output dari fungsi yang menghitung cahaya titik tertentu pada bola.

Kode contohbeginfig(6);
draw sphere.c(1.2cm);
draw sphere.c(2.4cm) shifted (2cm, 0);
endfig;
Blok konstruksi lain yang nyaman adalah "tabung." Secara kasar itu adalah sebuah silinder yang dapat Anda bengkokkan. Selama diameternya konstan, itu cukup mudah.

Kode contohbeginfig(7);
path p;
p := subpath (1,8) of fullcircle scaled 3cm;
draw tube.l(p)(1/2cm); % arguments are the path itself and the tube radius
endfig;
Jika diameternya tidak konstan, hal-hal menjadi lebih rumit: jumlah goresan harus berubah sesuai dengan ketebalan tabung untuk menjaga jumlah tinta per unit area konstan sebelum memperhitungkan lampu.

Kode contohbeginfig(8);
path p;
p := pathSubdivide(fullcircle, 2) scaled 3cm; % this thing splits every segment between the points of a path (here—fullcircle) into several parts (here—2)
draw tube.l(p)(1/2cm + 1/6cm*sin(offsetPathLength*10pi));
endfig;
Ada juga tabung dengan penetasan melintang. Masalah menjaga jumlah tinta konstan ternyata lebih rumit dalam kasus ini, sehingga seringkali tabung seperti itu terlihat sedikit shaggy.

Kode contohbeginfig(9);
path p;
p := pathSubdivide(fullcircle, 2) scaled 3cm;
draw tube.t(p)(1/2cm + 1/6cm*sin(offsetPathLength*10pi));
endfig;
Tabung dapat digunakan untuk membangun berbagai objek: dari kerucut dan silinder ke langkan.

Kode contohbeginfig(10);
draw tube.l ((0, 0) -- (0, 3cm))((1-offsetPathLength)*1cm) shifted (-3cm, 0); % a very simple cone
path p;
p := (-1/2cm, 0) {dir(175)} .. {dir(5)} (-1/2cm, 1/8cm) {dir(120)} .. (-2/5cm, 1/3cm) .. (-1/2cm, 3/4cm) {dir(90)} .. {dir(90)}(-1/4cm, 9/4cm){dir(175)} .. {dir(5)}(-1/4cm, 9/4cm + 1/5cm){dir(90)} .. (-2/5cm, 3cm); % baluster's envelope
p := pathSubdivide(p, 6);
draw p -- reverse(p xscaled -1) -- cycle;
tubeGenerateAlt(p, p xscaled -1, p rotated -90); % a more low-level stuff than tube.t, first two arguments are tube's sides and the third is the envelope. The envelope is basically a flattened out version of the outline, with line length along the x axis and the distance to line at the y. In the case of this baluster, it's simply its side rotated 90 degrees.
endfig;
Beberapa konstruksi yang dapat dibuat dari primitif ini termasuk dalam perpustakaan. Sebagai contoh, bola bumi pada dasarnya adalah sebuah bola.

Kode contohbeginfig(11);
draw globe(1cm, -15, 0) shifted (-6/2cm, 0); % radius, west longitude, north latitude, both decimal
draw globe(3/2cm, -30.280577, 59.939461);
draw globe(4/3cm, -140, -30) shifted (10/3cm, 0);
endfig;
Namun, penetasan di sini bersifat latitudinal dan mengendalikan kerapatan garis jauh lebih sulit daripada di bola biasa dengan penetasan “konsentris”, jadi ini adalah jenis bola yang berbeda.

Kode contohbeginfig(12);
draw sphere.l(2cm, -60); % diameter and latitude
draw sphere.l(3cm, 45) shifted (3cm, 0);
endfig;
Bobot adalah konstruksi sederhana yang terbuat dari tabung dari dua jenis.

Kode contohbeginfig(13);
draw weight.s(1cm); % weight height
draw weight.s(2cm) shifted (2cm, 0);
endfig;
Ada juga alat untuk mengikat tabung.

Kode contoh. Demi singkatnya, hanya satu simpul.beginfig(14);
path p;
p := (dir(90)*4/3cm) {dir(0)} .. tension 3/2 ..(dir(90 + 120)*4/3cm){dir(90 + 30)} .. tension 3/2 ..(dir(90 - 120)*4/3cm){dir(-90 - 30)} .. tension 3/2 .. cycle;
p := p scaled 6/5;
addStrandToKnot (primeOne) (p, 1/4cm, "l", "1, -1, 1"); % first, we add a strand of width 1/4cm going along the path p to the knot named primeOne. its intersections along the path go to layers "1, -1, 1" and the type of tube is going to be "l".
draw knotFromStrands (primeOne); % then the knot is drawn. you can add more than one strand.
endfig;
Tabung di simpul menjatuhkan bayangan satu sama lain sebagaimana mestinya. Secara teori, fitur ini dapat digunakan dalam konteks lain, tetapi karena saya tidak punya rencana untuk masuk jauh ke dimensi ketiga, antarmuka pengguna agak kurang dan bayangan bekerja dengan baik hanya untuk beberapa objek.

Kode contohbeginfig(15);
path shadowPath[];
boolean shadowsEnabled;
numeric numberOfShadows;
shadowsEnabled := true; % shadows need to be turned on
numberOfShadows := 1; % number of shadows should be specified
shadowPath0 := (-1cm, -2cm) -- (-1cm, 2cm) -- (-1cm +1/6cm, 2cm) -- (-1cm + 1/8cm, -2cm) -- cycle; % shadow-dropping object should be a closed path
shadowDepth0 := 4/3cm; % it's just this high above the object on which the shadow falls
shadowPath1 := shadowPath0 rotated -60;
shadowDepth1 := 4/3cm;
draw sphere.c(2.4cm); % shadows work ok only with sphere.c and tube.l with constant diameter
fill shadowPath0 withcolor white;
draw shadowPath0;
fill shadowPath1 withcolor white;
draw shadowPath1;
endfig;
Tentu saja, Anda akan memerlukan tekstur kayu (pembaruan: sejak versi Rusia artikel ini diterbitkan, kasus pertama perpustakaan ini digunakan dalam proyek nyata yang saya sadari
telah terjadi , dan itu adalah tekstur kayu yang datang berguna, jadi ini akhirnya tidak menjadi lelucon sama sekali). Bagaimana ranting dan pertumbuhannya mempengaruhi pola cincin tahun adalah topik untuk beberapa penelitian serius. Model kerja paling sederhana yang bisa saya buat adalah sebagai berikut: cincin tahun adalah permukaan datar paralel, terdistorsi oleh ranting yang tumbuh; dengan demikian permukaannya dimodifikasi oleh serangkaian “fungsi ranting” yang tidak terlalu rumit di tempat yang berbeda dan isolasi permukaan diambil sebagai pola cincin tahun.

Kode contohbeginfig(16);
numeric w, b;
pair A, B, C, D, A', B', C', D';
w := 4cm;
b := 1/2cm;
A := (0, 0);
A' := (b, b);
B := (0, w);
B' := (b, wb);
C := (w, w);
C' := (wb, wb);
D := (w, 0);
D' := (wb, b);
draw woodenThing(A--A'--B'--B--cycle, 0); % a piece of wood inside the A--A'--B'--B--cycle path, with wood grain at 0 degrees
draw woodenThing(B--B'--C'--C--cycle, 90);
draw woodenThing(C--C'--D'--D--cycle, 0);
draw woodenThing(A--A'--D'--D--cycle, 90);
eyescale := 2/3cm; % scale for the eye
draw eye(150) shifted 1/2[A,C]; % the eye looks in 150 degree direction
endfig;
Mata dari gambar di atas terbuka lebar atau sedikit menyipit dan pupilnya juga berubah ukurannya. Ini mungkin tidak masuk akal secara praktis, tetapi mata yang secara mekanis mirip terlihat membosankan.

Kode contohbeginfig(17);
eyescale := 2/3cm; % 1/2cm by default
draw eye(0) shifted (0cm, 0);
draw eye(0) shifted (1cm, 0);
draw eye(0) shifted (2cm, 0);
draw eye(0) shifted (3cm, 0);
draw eye(0) shifted (4cm, 0);
endfig;
Sebagian besar ilustrasinya tidak terlalu rumit, tetapi pendekatan yang lebih ketat akan membutuhkan penyelesaian banyak masalah dalam buku teks untuk menggambarkannya dengan benar. Katakanlah, masalah katrol L'Hôpital (tidak ada di buku teks itu, tapi bagaimanapun juga): di atas tali dengan panjang
ditangguhkan pada saat itu
katrol tergantung; itu terhubung ke tali lain, tergantung pada titik
dengan berat
pada akhirnya. Pertanyaannya adalah: kemana berat pergi jika katrol dan tali tidak menimbang apa pun? Anehnya, solusi dan konstruksi untuk masalah ini tidak sesederhana itu. Tetapi dengan bermain dengan beberapa variabel, Anda dapat membuat gambar terlihat tepat untuk halaman dengan tetap menjaga akurasi.

Kode contohvardef lHopitalPulley (expr AB, l, m) = % distance AB between the suspension points of the ropes and their lengths l and m. “Why no units of length?”, you may ask. It's because some calculations inside can cause arithmetic overflow in MetaPost.
save A, B, C, D, E, o, a, x, y, d, w, h, support;
image(
pair A, B, C, D, E, o[];
path support;
numeric a, x[], y[], d[], w, h;
x1 := (l**2 + abs(l)*((sqrt(8)*AB)++l))/4AB; % the solution
y1 := l+-+x1; % second coordinate is trivial
y2 := m - ((AB-x1)++y1); % as well as the weight's position
A := (0, 0);
B := (AB*cm, 0);
D := (x1*cm, -y1*cm);
C := D shifted (0, -y2*cm);
d1 := 2/3cm; d2 := 1cm; d3 := 5/6d1; % diameters of the pulley, weight and the pulley wheel
w := 2/3cm; h := 1/3cm; % parameters of the wood block
o1 := (unitvector(CD) rotated 90 scaled 1/2d3);
o2 := (unitvector(DB) rotated 90 scaled 1/2d3);
E := whatever [D shifted o1, C shifted o1]
= whatever [D shifted o2, B shifted o2]; % pulley's center
a := angle(AD);
support := A shifted (-w, 0) -- B shifted (w, 0) -- B shifted (w, h) -- A shifted (-w, h) -- cycle;
draw woodenThing(support, 0); % wood block everything is suspended from
draw pulley (d1, a - 90) shifted E; % the pulley
draw image(
draw A -- D -- B withpen thickpen;
draw D -- C withpen thickpen;
) maskedWith (pulleyOutline shifted E); % ropes should be covered with the pulley
draw sphere.c(d2) shifted C shifted (0, -1/2d2); % sphere as a weight
dotlabel.llft(btex $A$ etex, A);
dotlabel.lrt(btex $B$ etex, B);
dotlabel.ulft(btex $C$ etex, C);
label.llft(btex $l$ etex, 1/2[A, D]);
)
enddef;
beginfig(18);
draw lHopitalPulley (6, 2, 11/2); % now you can choose the right parameters
draw lHopitalPulley (3, 5/2, 3) shifted (8cm, 0);
endfig;
Dan bagaimana dengan buku teks? Sayangnya, ketika hampir semua ilustrasi dan tata letak sudah siap, sesuatu terjadi dan buku teks dibatalkan. Mungkin karena itu, saya memutuskan untuk menulis ulang sebagian besar fungsi perpustakaan asli dari awal (saya memilih untuk tidak menggunakan salah satu kode asli, yang, meskipun secara tidak langsung, saya dibayar) dan
meletakkannya di GitHub . Beberapa hal, ada di perpustakaan asli, seperti fungsi untuk menggambar mobil dan traktor, saya tidak memasukkan di sana, beberapa fitur baru, misalnya simpul, ditambahkan.
Itu tidak berjalan cepat: dibutuhkan sekitar satu menit untuk menghasilkan semua gambar untuk artikel ini dengan LuaLaTeX di laptop saya dengan i5-4200U 1,6 GHz. Generator nomor pseudorandom digunakan di sana-sini, jadi tidak ada dua gambar yang sama yang benar-benar identik (itu fitur) dan setiap proses menghasilkan gambar yang sedikit berbeda. Untuk menghindari kejutan, Anda cukup mengatur
randomseed := some number
dan menikmati hasil yang sama setiap kali berlari.
Terima kasih banyak kepada
dr ord dan
Mikael Sundqvist atas bantuan mereka dengan versi bahasa Inggris dari teks ini.