أشياء مختلفة في MetaPost

ما هي أفضل أداة لاستخدامها في رسم الصور المتجهة؟ بالنسبة لي وربما بالنسبة للكثيرين الآخرين ، الجواب واضح للغاية: Illustrator ، أو ربما Inkscape. على الأقل هذا ما اعتقدت عندما طُلب مني رسم حوالي ثمانمائة رسم بياني لكتاب الفيزياء. لا شيء استثنائي ، مجرد مجموعة من الرسوم التوضيحية بالأبيض والأسود مع الكرات والينابيع والبكرات والعدسات ، إلخ. بحلول ذلك الوقت ، كان من المعروف بالفعل أن الكتاب سوف يصنع في LaTeX وقد أعطيت لي عددًا من مستندات MS Word مع الصور المدمجة. تم مسح بعضها ضوئيًا للصور من كتب أخرى ، بعضها رسومات بالقلم الرصاص. إن تصوير الأيام والليالي لإدخال هذه الأشياء جعلني أشعر بالدوار ، لذلك سرعان ما وجدت نفسي أتخيل حلاً أكثر آلية. لسبب ما أصبح MetaPost محور هذه الأوهام.





الميزة الرئيسية لاستخدام حل MetaPost (أو ما شابه ذلك) هي أن كل صورة يمكن أن تكون وظيفة من عدة متغيرات. يمكن ضبط هذه الصورة بسرعة لأي ظروف غير متوقعة من التخطيط ، دون تعطيل العلاقات الداخلية الهامة في الرسم التوضيحي (شيء كنت مهتمًا به حقًا) ، وهو ما لا يمكن تحقيقه بسهولة باستخدام أدوات أكثر تقليدية. أيضا ، يمكن جعل العناصر المتكررة ، كل هذه المجالات والينابيع ، أكثر إثارة للاهتمام بصريا مما تسمح به الأدوات التقليدية مع قيود الوقت نفسها.

كنت أرغب في التقاط صور مع نوع من الفقس ، على عكس ما تصادفه في الكتب القديمة.



أولاً ، كنت بحاجة إلى أن أكون قادرًا على إنتاج بعض منحنيات مختلفة السماكة. المضاعفات الرئيسية هنا هي بناء منحنى يتبع المنحنى الأصلي على مسافة مختلفة. ربما استخدمت طريقة العمل الأكثر بدائية ، والتي تتلخص ببساطة في تحريك قطاعات الخطوط التي تربط نقاط التحكم في منحنى بيزير بمسافة معينة ، باستثناء أن هذه المسافة تختلف على طول المنحنى.



في معظم الحالات ، عملت بشكل جيد.



رمز المثال
من هنا فصاعدا ، يُفترض تنزيل المكتبة input fiziko.mp; موجود في كود MetaPost. أسرع طريقة هي استخدام ConTeXt (ثم لن تحتاج إلى beginfig و endfig ):

\starttext
\startMPcode
input fiziko.mp;
% the code goes here
\stopMPcode
\stoptext


أو 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;



يمكن دمج اثنين من الخطوط العريضة لإنشاء خط كفاف لسكتة دماغية متغيرة السُمك.



رمز المثال
beginfig(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;



يجب أن يكون سمك الخط أقل حدًا ، وإلا ستكون بعض الخطوط رفيعة جدًا بحيث لا يمكن طباعتها بشكل صحيح وهذا لا يبدو رائعًا. أحد الخيارات (الخيار الذي اخترته) هو جعل الخطوط رفيعة جدًا متقطعة ، بحيث يظل إجمالي كمية الحبر لكل وحدة طول كما هو في خط المرقق المقصود تقريبًا. بمعنى آخر ، بدلاً من تقليل كمية الحبر على جانبي الخط ، تأخذ الخوارزمية بعض الحبر من الخط نفسه.



رمز المثال
beginfig(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;



بمجرد الانتهاء من عمل خطوط متغيرة السُمك ، يمكنك رسم كرات. يمكن تصوير الكرة على أنها سلسلة من الدوائر متحدة المركز مع اختلاف سمك الخط وفقًا لإخراج دالة تحسب خفة نقطة معينة على الكرة.



رمز المثال
beginfig(6);
draw sphere.c(1.2cm);
draw sphere.c(2.4cm) shifted (2cm, 0);
endfig;



كتلة البناء المريحة الأخرى هي "أنبوب". تحدث تقريبا أنها اسطوانة والتي يمكنك ثني. طالما أن القطر ثابت ، فهو بسيط ومباشر.



رمز المثال
beginfig(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;



إذا لم يكن القطر ثابتًا ، تصبح الأمور أكثر تعقيدًا: يجب أن يتغير عدد السكتات الدماغية وفقًا لسُمك الأنبوب من أجل الحفاظ على مقدار الحبر لكل وحدة مساحة قبل أن تضيء المصابيح.



رمز المثال
beginfig(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;



هناك أيضا أنابيب مع الفقس عرضية. أصبحت مشكلة الحفاظ على مقدار الحبر ثابتًا أكثر تعقيدًا في هذه الحالة ، لذلك في كثير من الأحيان تبدو هذه الأنابيب أشعثًا.



رمز المثال
beginfig(9);
path p;
p := pathSubdivide(fullcircle, 2) scaled 3cm;
draw tube.t(p)(1/2cm + 1/6cm*sin(offsetPathLength*10pi));
endfig;



يمكن استخدام الأنابيب لإنشاء مجموعة واسعة من الكائنات: من المخاريط والأسطوانات إلى الدرابزينات.



رمز المثال
beginfig(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;



يتم تضمين بعض المنشآت التي يمكن أن تكون مصنوعة من هذه البدائية في المكتبة. على سبيل المثال ، الكرة الأرضية هي في الأساس كرة.



رمز المثال
beginfig(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;



ومع ذلك ، فإن الفقس هنا خطي وكثافة التحكم في الخط أصعب بكثير من الأجواء العادية ذات الفقس "المركز" ، لذلك فهو نوع مختلف من المجال.



رمز المثال
beginfig(12);
draw sphere.l(2cm, -60); % diameter and latitude
draw sphere.l(3cm, 45) shifted (3cm, 0);
endfig;



الوزن عبارة عن بناء بسيط مصنوع من أنابيب من نوعين.



رمز المثال
beginfig(13);
draw weight.s(1cm); % weight height
draw weight.s(2cm) shifted (2cm, 0);
endfig;



هناك أيضا أداة لعقد الأنابيب.



رمز المثال. من أجل الإيجاز في عقدة واحدة فقط.
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;



أنابيب في عقدة إسقاط الظلال على بعضها البعض كما ينبغي. من الناحية النظرية ، يمكن استخدام هذه الميزة في سياقات أخرى ، لكن بما أنه ليس لدي أي خطط للتعمق في البعد الثالث ، فإن واجهة المستخدم تفتقر إلى حد ما وتعمل الظل بشكل صحيح لبعض الكائنات فقط.



رمز المثال
beginfig(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;



بالتأكيد ، ستحتاج إلى نسيج خشب (تحديث: منذ نشر النسخة الروسية من هذه المقالة ، حدثت الحالة الأولى من هذه المكتبة في مشروع حقيقي أعلم به ، وكانت البنية الخشبية التي جاءت في متناول اليدين ، لذلك انتهى الأمر إلى أن لا تكون مزحة بعد كل شيء). كيف تؤثر الأغصان ونموها على نمط حلقات السنة هو موضوع لدراسة جادة. أبسط نموذج عمل يمكن أن أتوصل إليه هو كما يلي: حلقات السنة هي أسطح مستوية متوازية ، مشوهة بأغصان متنامية ؛ وبالتالي يتم تعديل السطح من خلال سلسلة من "وظائف الغصين" غير المعقدة للغاية في أماكن مختلفة ويتم أخذ عزل السطح كنمط حلقة العام.



رمز المثال
beginfig(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;



تفتح العين من الصورة أعلاه عريضًا أو ترتطم قليلاً ويغير تلميذها حجمها أيضًا. قد لا يكون له أي معنى عملي ، لكن العيون المتشابهة ميكانيكيا تبدو مملة.



رمز المثال
beginfig(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;



في معظم الوقت ، لم تكن الرسوم التوضيحية معقدة على هذا النحو ، ولكن النهج الأكثر صرامة يتطلب حل العديد من المشكلات في الكتاب المدرسي لتوضيحها بشكل صحيح. قل مشكلة بكرة L'Hôpital (لم تكن موجودة في هذا الكتاب المدرسي ، ولكن على أي حال): على الحبل مع الطول لعلقت في هذه النقطة دولابكرة معلقة. انها مدمن مخدرات إلى حبل آخر ، مع وقف التنفيذ في هذه النقطة Bمع الوزن Cفي نهايته. والسؤال هو: أين يذهب الوزن إذا لم يزن كل من البكرة والحبال شيئًا؟ من المستغرب أن الحل والبناء لهذه المشكلة ليست بهذه البساطة. ولكن من خلال اللعب مع العديد من المتغيرات ، يمكنك جعل الصورة تبدو مناسبة للصفحة مع الحفاظ على الدقة.



رمز المثال
vardef 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;



وماذا عن الكتاب المدرسي؟ للأسف ، عندما كانت جميع الرسوم التوضيحية والتصميم جاهزًا تقريبًا ، حدث شيء ما وتم إلغاء الكتاب المدرسي. ربما لهذا السبب ، قررت إعادة كتابة معظم وظائف المكتبة الأصلية من نقطة الصفر (اخترت عدم استخدام أي من الكود الأصلي ، والذي ، على الرغم من أنه غير مباشر ، دفعت مقابل) ووضعه على GitHub . بعض الأشياء ، الموجودة في المكتبة الأصلية ، مثل وظائف رسم السيارات والجرارات ، لم أدرج هناك ، وتمت إضافة بعض الميزات الجديدة ، مثل العقد.

لا يعمل بسرعة: يستغرق إنتاج جميع الصور لهذه المقالة حوالي دقيقة مع LuaLaTeX على جهاز الكمبيوتر المحمول الذي يعمل بنظام i5-4200U بسرعة 1.6 جيجاهرتز. يتم استخدام مولد الأرقام العشوائية المزيفة هنا وهناك ، لذا لا توجد صورتان متماثلتان متطابقتان تمامًا (هذه ميزة) ويؤدي كل تشغيل إلى إنتاج صور مختلفة قليلاً. لتجنب المفاجآت ، يمكنك ببساطة تعيين randomseed := some number والاستمتاع بالنتائج نفسها في كل مرة يتم فيها التشغيل.

شكراً جزيلاً للدكتور أورد وميكائيل سوندكفيست لمساعدتهما في النسخة الإنجليزية من هذا النص

Source: https://habr.com/ru/post/ar454376/


All Articles