MetaPost中的各种事情

用什么画矢量图片? 对于我和其他许多人来说,答案是显而易见的:最有可能在插画家中。 好吧,还是在Inskape。 我还以为当我被命令为一本物理教科书画八百张时。 并非如此,只有黑白技术插图带有各种块,球,弹簧,透镜,汽车,拖拉机等。 有人认为这本书是用乳胶制成的,为我提供了带有插入图片的Word文件(无论是铅笔素描还是其他书籍的扫描图片),手稿似乎是某种形式。 在这种情况下,第一个想法-吸引他人进入-陷入了关于“这将如何使一切自动化?”的幻想。 由于某种原因, MetaPost当时似乎是最好的选择。





这种解决方案最重要的优点是,每张图片可以是几个变量的一个小函数。 这样的图片很容易更改,例如,在不影响重要比例的情况下改变大小并在以前未知的特定情况下适合条纹,而这是用传统方法很难实现的。 重复元素(相同的球和弹簧)的行为比“人类”矢量编辑器所允许的行为有趣得多。

我想用阴影线制作照片,就像旧书中那样。



首先,我们必须获得可变厚度的线。 这里的主要困难是构造一条大致平行于给定曲线的曲线,并根据需要更改到给定曲线的距离。 我依靠最可能是最原始的方式 ,将连接贝塞尔曲线中间点的线段简单地以给定的距离平行传输。 不同之处在于该距离可以沿着曲线变化。



在大多数情况下,这可以得到不错的结果。



范例程式码
在下文中,假定已下载了该库,并且在其中有input fiziko.mp;input fiziko.mp; 。 在ConTeXt中启动和查找的最快方法(然后就beginfigendfig ):

\starttext
\startMPcode
input fiziko.mp;

\stopMPcode
\stoptext


或在LuaLaTeX中:

\documentclass{article}
\usepackage{luamplib}
\begin{document}
\begin{mplibcode}
input fiziko.mp;

\end{mplibcode}
\end{document}


beginfig(3);
path p, q; % , , ,
p := (0,-1/4cm){dir(30)}..(5cm, 0)..{dir(30)}(10cm, 1/4cm);
q := offsetPath(p)(1cm*sin(offsetPathLength*pi)); % — , — (offsetPathLength, 0 1), ,
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)); %
q2 := offsetPath(p)(-1/2pt*(sin(offsetPathLength*pi)**2)); %
fill q1--reverse(q2)--cycle;
endfig;



厚度应该从下面进行一些限制,否则在打印时,栅格将占据线条的太细部分,这通常不是很漂亮。 一种选择是用相同的最小厚度的虚线绘制所有厚度小于某个值的线,以使平均每单位长度的涂料总量与目标厚度的线相对应。 也就是说,与其减少线条两侧的油漆量,不如开始用横向条纹transverse它。



范例程式码
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)); % , ,
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); % — ,
endfig;



如果厚度发生变化,则必须相应地更改笔划数,同时保持平均填充密度不变,并且在计算照度时还要考虑厚度变化。



范例程式码
beginfig(8);
path p;
p := pathSubdivide(fullcircle, 2) scaled 3cm;
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); %
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); %
p := pathSubdivide(p, 6);
draw p -- reverse(p xscaled -1) -- cycle;
tubeGenerateAlt(p, p xscaled -1, p rotated -90); % , tube.t, — — , — .
endfig;



库中可以使用这些部分构建一些内容。 假设地球仪基本上是一个球。



范例程式码
beginfig(11);
draw globe(1cm, -15, 0) shifted (-6/2cm, 0); % , ,
draw globe(3/2cm, -30.28367, 59.93809);
draw globe(4/3cm, -140, -30) shifted (10/3cm, 0);
endfig;



尽管不是:这里的影线是平行进行的,并且控制行程的厚度以保持填充密度比在软管上进行横向影线的情况更加困难,因此这是一种单独的球。



范例程式码
beginfig(12);
draw sphere.l(2cm, -60); %
draw sphere.l(3cm, 45) shifted (3cm, 0);
endfig;



重量是两种可变厚度软管的直接设计。



范例程式码
beginfig(13);
draw weight.s(1cm); %
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"); % primeOne , p 1/4cm, "l" ( tube.l, tube.t ) «» "1, -1, 1" p
draw knotFromStrands (primeOne); % .
endfig;



节点的阴影在光照模型中有些复杂。 原则上,没有人会在其他情况下使用它们,但是我并没有设定深入研究该卷的目标,因此虽然这不是很方便,但并不能在任何地方使用。



范例程式码
beginfig(15);
path shadowPath[];
boolean shadowsEnabled;
numeric numberOfShadows;
shadowsEnabled := true; %
numberOfShadows := 1; %
shadowPath0 := (-1cm, -2cm) -- (-1cm, 2cm) -- (-1cm +1/6cm, 2cm) -- (-1cm + 1/8cm, -2cm) -- cycle; % , ,
shadowDepth0 := 4/3cm; % - «» ,
shadowPath1 := shadowPath0 rotated -60;
shadowDepth1 := 4/3cm;
draw sphere.c(2.4cm); % sphere.c tube.l
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--A'--B'--B--cycle, 0
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; %
draw eye(150) shifted 1/2[A,C]; % 150
endfig;



上图中的眼睛可以稍微张开,然后斜视,并且瞳孔的宽度会改变。 在这方面没有特殊的含义,但是比起这些琐事在任何地方在机械上都是相同的,事实更加生动。



范例程式码
beginfig(17);
eyescale := 2/3cm; % 1/2cm
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;



通常情况下,图片不是很复杂,但是如果您认真地处理问题,则需要解决许多任务才能有意义地说明它们。 这里说,洛皮塔尔(Lopital)关于一块的任务(我不知道如何正确地用俄语来称呼它,它在教科书中没有,仅作为示例):挂在一根长度为l的绳索上,悬挂在A点,该块被钩在另一根绳索上货物C以相同的高度悬挂在B点,然后将C悬挂在第二条绳索上,问题是,如果绳索和滑轮没有重量,货物将放在哪里? 出人意料的是,问题的解决方案和构造都不是那么简单,但是,通过使用多个变量,您可以轻松地使图片精确地显示在条带上,而仍然保持真实。



范例程式码
vardef lHopitalPulley (expr AB, l, m) = % AB l, m . ? : , , arithmetic overflow.
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; % ,
y1 := l+-+x1; %
y2 := m - ((AB-x1)++y1); %
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; % ,
w := 2/3cm; h := 1/3cm; % . ,
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]; % ,
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); % ,
draw pulley (d1, a - 90) shifted E; %
draw image(
draw A -- D -- B withpen thickpen;
draw D -- C withpen thickpen;
) maskedWith (pulleyOutline shifted E); %
draw sphere.c(d2) shifted C shifted (0, -1/2d2); %
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); % ,
draw lHopitalPulley (3, 5/2, 3) shifted (8cm, 0);
endfig;



什么是教科书? ,嗯,当几乎所有的插图和布局都准备好了时,那里发生了什么事,他没有出来。 因此,大概在一段时间后,我再次重写了结果库中的所有主要内容, 并将代码发布到了github上 。 某些kunshtyuki并未进入该区域:例如,电路或绘制汽车和拖拉机的功能。 一些-添加:例如节点。

整个厨房无法快速运行:在我的i5-4200U 1.6 GHz笔记本电脑上使用LuaLaTeX收集本文的所有图片大约需要一分钟。 对于很多事情,都使用了伪随机数生成器,因此类似的图片不仅在一次运行中看起来有所不同(这是一项功能),而且每次下一次运行都将产生与前一次不同的图片。 但是,您始终可以在序言中将randomseed := - 设置randomseed := - ,并且所有相同的运行都会产生相同的图像。

Source: https://habr.com/ru/post/zh-CN423571/


All Articles