How to make dominoes falling?

As I can't find the original code this doesn't produce quite the same image that was linked in the comments above but this is much the same idea and uses the same principles.

The "wavy" arrangement of the standing dominoes is quite straightforward. The four falling dominoes at the end (or start - depending on how you look at it) form one big unsatisfactory kludge.

\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}

enter image description here


Here's an Asymptote version that uses a semi-realistic model to compute the falling dominoes, giving vector output:

And, the animated version (halfway--the gif with a full 200 frames was too big to upload):

enter image description here

Both versions take a while to compile.

Code for the still picture (save in foo.asy and run 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 );
}

Code for the animated version:

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);