假设我有两条路径:
beginfig(0)
u:=10pt;
vardef randomcirc(expr O, r, w) =
save p,i; pair p; numeric i;
p=O+right*r;
p for i=1 upto 9: .. (p rotatedaround(O, 36i))+(uniformdeviate w, uniformdeviate w) endfor .. cycle
enddef;
path pat[];
pair p[];
p0:=(0,5u); p3:=(-8u,0);
pat0:=randomcirc(p0,8u,.5u);
pat3:=randomcirc(p3,5u,0.4u);
fill buildcycle(pat0, pat3) withcolor red;
draw pat0; draw pat3;
endfig;
为什么它们的交点没有填充红色?
我认为这是由于 MetaPost 的精度太低造成的,如何解决?
答案1
如果其中一条路径的起点 ( ) 位于另一条路径内,plain.mp
则 的实现无法找到两条循环路径之间的正确重叠。例如:buildcycle
point 0
beginfig(3);
path A, B; picture p[];
A = fullcircle scaled 2.5cm;
B = fullcircle scaled 2cm shifted (1cm,0);
p1 = image(fill buildcycle(A,B) withcolor .8[blue,white]; drawarrow A; drawarrow B;);
A := A rotated 180;
p2 = image(fill buildcycle(A,B) withcolor .8[blue,white]; drawarrow A; drawarrow B;);
B := B rotatedabout(center B,180);
p3 = image(fill buildcycle(A,B) withcolor .8[blue,white]; drawarrow A; drawarrow B;);
A := A rotated 180;
p4 = image(fill buildcycle(A,B) withcolor .8[blue,white]; drawarrow A; drawarrow B;);
for i=1 upto 4: draw p[i] shifted (120i,0); label(decimal i, (7mm+120i,0)); endfor
endfig;
生产
在每个子图中,箭头指向路径的起点。你可以看到,这只buildcycle
适用于情况 2,即两者都不point 0
在另一个循环路径内。请注意,在情况 4 中,我们得到了联盟两条路径。
以下是纠正此错误的方法。首先,您需要一个函数来确定给定点是否位于循环路径内。遵循 Sedgewick 的C 语言算法, 你可以写:
% is point "p" inside cyclic path "ring" ?
vardef inside(expr p, ring) =
save t, count, test_line;
count := 0;
path test_line;
test_line = p -- (infinity, ypart p);
for i = 1 upto length ring:
t := xpart(subpath(i-1,i) of ring intersectiontimes test_line);
if ((0<=t) and (t<1)): count := count + 1; fi
endfor
odd(count)
enddef;
这是一个在两个重叠循环路径的特殊情况下overlap
进行替换的函数。buildcycle
vardef front_half primary p = subpath(0, 1/2 length p) of p enddef;
vardef back_half primary p = subpath(1/2 length p, length p) of p enddef;
% a and b should be cyclic paths...
vardef overlap(expr a, b) =
save p, q;
boolean p, q;
p = inside(point 0 of a, b);
q = inside(point 0 of b, a);
if ((not p) and (not q)):
buildcycle(a,b)
elseif (not p):
buildcycle(front_half b, a, back_half b)
elseif (not q):
buildcycle(front_half a, b, back_half a)
else:
buildcycle(front_half a, back_half b, front_half b, back_half a)
fi
enddef;
基本思想是,如果两者都不point 0
在另一条路径内,则只需调用buildcycle
,否则将两个循环拆分为适当的半循环序列。将buildcycle
上例中的 替换为overlap
可得出以下结果:
buildcycle
在 OP 示例中替换overlap
为:
这可能更接近最初想要的结果。
此外,如果两个循环路径运行在相反方向的行为buildcycle
又不同了。这是第一个例子的扩展版本;在第二行中,较大的路径是向后运行的:
为了解决这种可能性,您可以overlap
使用有用的counterclockwise
函数(来自plain.mp
)来提高鲁棒性,该函数返回逆时针运行的循环路径的副本。这是该overlap
函数的改进版本。
vardef overlap(expr a, b) =
save p, q, A, B;
boolean p, q;
p = not inside(point 0 of a, b);
q = not inside(point 0 of b, a);
path A, B;
A = counterclockwise a;
B = counterclockwise b;
if (p and q):
buildcycle(A,B)
elseif p:
buildcycle(front_half B, A, back_half B)
elseif q:
buildcycle(front_half A, B, back_half A)
else:
buildcycle(front_half A, back_half B, front_half B, back_half A)
fi
enddef;
这在我的扩展示例中产生了这一点。
答案2
如果我稍微改变 的定义pat3
,使其pat3
从圆的“左边”(可以这么说)开始,而不是从“右边”(与 相反pat0
)开始,它就会在这里起作用。为此,我定义了另一个vardef
宏(randomcirc_bis
)
beginfig(0)
u:=10pt;
vardef randomcirc(expr O, r, w) =
save p,i; pair p; numeric i;
p=O+right*r;
p for i=1 upto 9: .. (p rotatedaround(O, 36i))+(uniformdeviate w, uniformdeviate w)
endfor .. cycle
enddef;
%
vardef randomcirc_bis(expr O, r, w) =
save p,i; pair p; numeric i;
p=O+left*r;
p for i=1 upto 9: .. (p rotatedaround(O, 36i))+(uniformdeviate w, uniformdeviate w)
endfor .. cycle
enddef;
%
path pat[];
pair p[];
p0:=(0,5u); p3:=(-8u,0);
pat0:=randomcirc(p0,8u,.5u);
pat3:=randomcirc_bis(p3,5u,0.4u);
pat4 = buildcycle(pat0, pat3);
fill pat4 withcolor red;
draw pat0; draw pat3 dashed evenly;
%
dotlabel.ulft("1", pat0 intersectionpoint pat3);
dotlabel.lrt("2", pat3 intersectionpoint pat0);
label.rt("aa", point 0 of pat0);
label.lft("b", point 0 of pat3);
endfig;
end.
buildcycle
在这幅图中,我试图准确重现MetaPost 手册第 30 页(第 9.1 节“构建周期”)中解释的运作方式。引用:
宏
buildcycle
检测到图 24b 中标记为 1 和 2 的两个交叉点。然后,它构建了图中粗体显示的循环路径,即沿着路径 aa 从交叉点 1 前进到交叉点 2,然后沿着逆时针路径 b 前进回到交叉点 1。
如果我理解intersectionpoint
正确的话,第 1 点实际上aa intersectionpoint b
是第一的aa
( pat0
) 与b
( )的交点pat3
。
类似地,第 2 点是b intersectiopoint aa
,它也是第一的b
( pat3
) 与aa
( )的交点pat0
。
要重现此方案,必须从pat3
左侧开始路径。否则,第一个pat3=b
会遇到的交叉点将pat0=aa
是点 1。两个交叉点将是相同的,在这种情况下buildcycle
会失败。
我希望我没有太让人困惑:英语不是我的母语……
更新
我简化了代码。我的新 vardef 宏randomcircbis
不是必需的。pat3:=randomcirc(p3,5u,0.4u);
只需将原始代码中的行替换为 即可pat3:=randomcirc(p3,-5u,0.4u);
。当然,结果图是相同的。
beginfig(0)
u:=10pt;
vardef randomcirc(expr O, r, w) =
save p,i; pair p; numeric i;
p=O+right*r;
p for i=1 upto 9: .. (p rotatedaround(O, 36i))+(uniformdeviate w, uniformdeviate w)
endfor .. cycle
enddef;
%
path pat[];
pair p[];
p0:=(0,5u); p3:=(-8u,0);
pat0:=randomcirc(p0,8u,.5u);
pat3:=randomcirc(p3,-5u,0.4u);
pat4 = buildcycle(pat0, pat3);
fill pat4 withcolor red;
draw pat0; draw pat3 dashed evenly;
%
dotlabel.ulft("1", pat0 intersectionpoint pat3);
dotlabel.lrt("2", pat3 intersectionpoint pat0);
label.rt("aa", point 0 of pat0);
label.lft("b", point 0 of pat3);
endfig;
end.
答案3
这是与 fpast 的答案中相同想法的另一种实现。您不必从左侧的点开始,只需将原始圆旋转 180 度即可。(我还稍微简化了随机圆的代码)。
\startMPdefinitions
vardef randomcircle(expr O, r, w, a) =
((fullcircle rotated a scaled 2r) randomized w shifted O)
enddef;
\stopMPdefinitions
\starttext
\startMPpage[offset=2mm]
u:=10pt;
path pat[];
pair p[];
p0:=(0,5u); p3:=(-8u,0);
pat0 = randomcircle(p0, 8u, 0.4u, 0);
pat3 = randomcircle(p3, 5u, 0.4u, 180);
fill buildcycle(pat0, pat3) withcolor red;
draw pat0;
draw pat3;
dotlabel.ulft("1", pat0 intersectionpoint pat3);
dotlabel.lrt("2", pat3 intersectionpoint pat0);
label.rt("aa", point 0 of pat0);
label.lft("b", point 0 of pat3);
\stopMPpage
\stoptext
这使