如何用 LaTex 绘制如下图所示的多米诺骨牌倒下的场景?
答案1
这是一个 Asymptote 版本,它使用半现实模型来计算倒下的多米诺骨牌,并给出矢量输出:
还有动画版本(一半——完整的 200 帧的 gif 太大,无法上传):
两个版本都需要一段时间来编译。
静态图像的代码(保存foo.asy
并运行asy foo
):
settings.outformat="pdf";
settings.render=0;
settings.prc=false;
import three;
unitsize(1cm);
currentprojection=perspective(
camera=(-10,0,5),
target=(48,2,-1),
angle=5,
autoadjust=false);
real height = 1;
real width = 0.5;
real depth = 0.08;
real separation = 0.5; //This is the interval from start to start.
surface domino = scale(depth, width, height) * shift(-1,-1/2,0) * unitcube;
triple labelposition = (-depth, 0, 0.7*height);
surface labelfor(string s) {
static transform3 T = shift(labelposition)*rotate(90,Y)*rotate(90,Z)*scale3(0.016)*scale(-1,1,1);
return T*surface(Label(s, p=fontsize(32)));
}
path receeding = scale(separation) * yscale(-1) * ( (0,-7) .. (7,0) .. (25,-6) .. (60,2) .. (95,-3) :: (140, -1) :: (200,0));
struct pointAndAngle {
triple point;
real angle;
}
pointAndAngle dominoPosition(int n) {
pointAndAngle toreturn;
real t = arctime(receeding, n*separation);
toreturn.point = XYplane(point(receeding,t));
pair tangent = dir(receeding, t);
toreturn.angle = degrees(atan2(tangent.y, tangent.x));
return toreturn;
}
transform3 dominoUpright(int n) {
pointAndAngle info = dominoPosition(n);
return shift(info.point) * rotate(info.angle, Z);
}
transform3 lyingDown(int n) {
return dominoUpright(n) * rotate(90, Y);
}
int nDominoes = 200;
draw(dominoUpright(0) * domino, invisible);
draw(dominoUpright(nDominoes-1) * domino, invisible);
draw(lyingDown(nDominoes-1) * domino, invisible);
int nToppled = 8;
write("Computing image with " + (string)nToppled + " dominoes toppled.");
surface currentdomino;
for (int n = nDominoes-1; n >= 0; --n) {
pointAndAngle position = dominoPosition(n);
transform3 T = shift(position.point) * rotate(position.angle, Z);
if (n <= nToppled-1) {
if (currentdomino.s.length == 0) T = T * rotate(85,Y);
else {
path3 toisectleft = T * circle(c=(0, interp(-width/2, width/2, 1/3), 0),normal=Y,r=height);
path3 toisectright = T* circle(c=(0, interp(-width/2, width/2, 2/3), 0),normal=Y,r=height);
triple[] isectionpointsleft = intersectionpoints(toisectleft, currentdomino);
triple[] isectionpointsright = intersectionpoints(toisectright, currentdomino);;
real zleft=0, zright=0;
for (triple pt : isectionpointsleft) {
if (pt.z >= zleft) zleft = pt.z;
}
for (triple pt : isectionpointsright) {
if (pt.z >= zright) zright = pt.z;
}
real angle1 = aSin(zleft / height);
real angle2 = aSin(zright / height);
if (angle1 > angle2) {
real tmp = angle2;
angle2 = angle1;
angle1 = tmp;
}
real angle = interp(angle1, angle2, 2);
T = T * rotate(90-angle, Y);
}
}
currentdomino = T * domino;
draw(currentdomino, gray(0.5));
if (n < 80)
draw( T*labelfor((string)(n+1)), emissive(white), meshpen=white );
}
动画版代码:
settings.outformat="gif";
settings.render=0;
import three;
import animation;
unitsize(1cm);
currentprojection=perspective(
camera=(-10,0,5),
target=(48,2,-1),
angle=5,
autoadjust=false);
real height = 1;
real width = 0.5;
real depth = 0.08;
real separation = 0.5; //This is the interval from start to start.
surface domino = scale(depth, width, height) * shift(-1,-1/2,0) * unitcube;
path3[] dominoOutline = scale(depth,width,height) * shift(-1,-1/2,0) * unitbox;
path receeding = scale(separation) * yscale(-1) * ( (0,-7) .. (7,0) .. (25,-6) .. (60,2) .. (95,-3) :: (140, -1) :: (200,0));
struct pointAndAngle {
triple point;
real angle;
}
pointAndAngle dominoPosition(int n) {
pointAndAngle toreturn;
real t = arctime(receeding, n*separation);
toreturn.point = XYplane(point(receeding,t));
pair tangent = dir(receeding, t);
toreturn.angle = degrees(atan2(tangent.y, tangent.x));
return toreturn;
}
transform3 dominoUpright(int n) {
pointAndAngle info = dominoPosition(n);
return shift(info.point) * rotate(info.angle, Z);
}
transform3 lyingDown(int n) {
return dominoUpright(n) * rotate(90, Y);
}
int nDominoes = 200;
animation a;
draw(dominoUpright(0) * domino, invisible);
draw(dominoUpright(nDominoes-1) * domino, invisible);
draw(lyingDown(nDominoes-1) * domino, invisible);
for (int nToppled = 0; nToppled < 100; ++nToppled) {
save();
write("Computing image with " + (string)nToppled + " dominoes toppled.");
surface currentdomino;
for (int n = nDominoes-1; n >= 0; --n) {
pointAndAngle position = dominoPosition(n);
transform3 T = shift(position.point) * rotate(position.angle, Z);
if (n <= nToppled) {
if (currentdomino.s.length == 0) T = T * rotate(85,Y);
else {
path3 toisectleft = T * circle(c=(0, interp(-width/2, width/2, 1/3), 0),normal=Y,r=height);
path3 toisectright = T* circle(c=(0, interp(-width/2, width/2, 2/3), 0),normal=Y,r=height);
triple[] isectionpointsleft = intersectionpoints(toisectleft, currentdomino);
triple[] isectionpointsright = intersectionpoints(toisectright, currentdomino);;
real zleft=0, zright=0;
for (triple pt : isectionpointsleft) {
if (pt.z >= zleft) zleft = pt.z;
}
for (triple pt : isectionpointsright) {
if (pt.z >= zright) zright = pt.z;
}
real angle1 = aSin(zleft / height);
real angle2 = aSin(zright / height);
if (angle1 > angle2) {
real tmp = angle2;
angle2 = angle1;
angle1 = tmp;
}
real angle = interp(angle1, angle2, 2);
T = T * rotate(90-angle, Y);
}
}
currentdomino = T * domino;
draw(currentdomino, emissive(white), meshpen=black + linewidth(1pt));
}
a.add();
restore();
}
a.movie(delay=50);
答案2
由于我找不到原始代码,所以这不会产生与上面的评论中链接的完全相同的图像,但这是非常相同的想法并使用相同的原理。
直立多米诺骨牌的“波浪”排列相当简单。末端(或起点 - 取决于你如何看待它)的四张倒下的多米诺骨牌形成了一个令人不满意的大杂烩。
\documentclass[border=0.125cm]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\tikzset{3D/.cd,
x/.store in=\xx, x=0,
y/.store in=\yy, y=0,
z/.store in=\zz, z=0
}
\tikzdeclarecoordinatesystem{3D}{%
\tikzset{3D/.cd,#1}%
\pgfpoint{sin(\yy)*(\xx)}{-((\xx)/75)^2+(\zz)/100*(\xx)}%
}
\begin{document}
\begin{tikzpicture}[line join=round, very thin]
\def\e{1260}
\foreach \x [evaluate={\i=mod(\x+90,360); \j=int((\i<180)*2-1); \t=3; \sc=\x/\e; \n=int((\e-\x)/15+5); \X=\x/\e;}] in {10,25,...,\e}{
\path [shift={(3D cs:x=\x-\t,y={3*sin(\x-\t)})}, yslant=cos(\x)/5]
(-\X/2, 0) coordinate (A') ( \X/2, 0) coordinate (B')
( \X/2,2*\X) coordinate (C') (-\X/2,2*\X) coordinate (D');
\path [shift={(3D cs:x=\x,y=3*sin \x)}, yslant=cos(\x)/5]
(-\X/2, 0) coordinate (A) ( \X/2, 0) coordinate (B)
( \X/2,2*\X) coordinate (C) (-\X/2,2*\X) coordinate (D);
\filldraw [black!90] (B) -- (B') -- (C') -- (C) -- cycle;
\filldraw [black!80] (A) -- (A') -- (D') -- (D) -- cycle;
\filldraw [black!70] (C) -- (D) -- (D') -- (C') -- cycle;
\filldraw [black] (A) -- (B) -- (C) -- (D) -- cycle;
\node [text=white, shift={($(C)!0.5!(D)$)}, anchor=north, yslant=cos(\x)/5, font=\sf, scale=\sc*1.5]
at (0,-.33*\X) {\n};
}
%
\foreach \i [evaluate={\x=\i*30-10; \X=1; \n=int(5-\i);\xsl=\x/180}]in {1,...,4}{
\path [shift={(3D cs:x=\x+\e,y=-3*\x/90)}, yslant=cos \e/5, xslant=\xsl]
(-\X/2, 0) coordinate (A) ( \X/2, 0) coordinate (B)
( \X/2, \X*2-\x/360) coordinate (C) (-\X/2, \X*2-\x/360) coordinate (D);
\path [shift={(3D cs:x=\x+\e,y=-3*\x/90)}, shift={(5/50,5/50-\i*2/50)}, yslant=cos \e/5, xslant=\xsl]
(-\X/2, 0) coordinate (A') ( \X/2, 0) coordinate (B')
( \X/2, \X*2-\x/330) coordinate (C') (-\X/2, \X*2-\x/330) coordinate (D');
\filldraw [black!70] (C) -- (D) -- (D') -- (C') -- cycle;
\filldraw [black!70] (A) -- (B) -- (B') -- (A') -- cycle;
\filldraw [black!90] (B) -- (B') -- (C') -- (C) -- cycle;
\filldraw [black] (A) -- (B) -- (C) -- (D) -- cycle;
\node [text=white, shift={($(C)!0.5!(D)$)}, anchor=north, xslant=\xsl,yslant=cos \e/5, font=\sf, scale=1.5]
at (0,-.33*\X) {\n};
}
\end{tikzpicture}
\end{document}