This browser has no WebGPU, so the sketch cannot run here. The program is below, and it runs in a current Chrome, Edge or Safari.

Sand 3D

Open in new tab ↗

&sandboxSz = 32;

&FROM_NX = mlt(
   lrg(#(1,-1,0), 0),
   lrg(#(1,0,0), 0),
   lrg(#(2,0,0), 0)
);

&FROM_PZ = mlt(
   lrg(#(0,-1,-1), 0),
   lrg(#(0,0,-1), 0),
   lrg(#(1,0,-1), 0),
   lrg(#(-1,0,-1), 0)
);

&FROM_NZ = mlt(
   lrg(#(0,-1,1), 0),
   lrg(#(0,0,1), 0),
   lrg(#(1,0,1), 0),
   lrg(#(-1,0,1), 0),
   lrg(#(0,0,2), 0)
);

&UP = mlt(
   lrg(#(0,1,0), 0),
   lrg(#(1,1,0), 0),
   lrg(#(-1,1,0), 0),
   lrg(#(0,1,1), 0),
   lrg(#(0,1,-1), 0)
);


#randRot{
   dim = (&sandboxSz,&sandboxSz,1);
   set = mlt(srand(1,2,3), PI);
}

#newGrain{
   dim = (&sandboxSz,&sandboxSz,1);
   run = mlt(sml(rand(step), 0.01), sml(dst(C, 0), 0.3));
   val_dom = UNIT;
}

&newGrain = mlt( eql(mod(step, 2),0), mlt(sml(C.y,-0.9), #newGrain[C.x, C.z , 0]));

&NOT_TOP = lrg(C.y, -0.9);

#sandbox{
   dim = (&sandboxSz,&sandboxSz,&sandboxSz);
   bound = CLAMP;
   val_dom = UNIT;
   set = (0);
   run = 
   add(
      &newGrain,
      
      // ==================================================
      // KEEP CURRENT GRAIN
      //
      // Stay if:
      // - we reached the floor
      // - or every possible downward route is blocked
      // ==================================================

      mlt(
         lrg(#, 0),
         max(
            lrg(C.y, 0.9),
            &UP
         )
      ),

      // ==================================================
      // STRAIGHT DOWN
      //
      // Grain directly above falls into this empty voxel.
      // ==================================================

      mlt(
         eql(#, 0),
         lrg(#(0,-1,0), 0)
      ),

      // ==================================================
      // +X
      //
      // Source is (-1,-1,0).
      // Its straight-down voxel is blocked.
      // ==================================================
      mlt(
         eql(#, 0),
         lrg(C.y, -0.9),
         lrg(#(-1,-1,0), 0),
         lrg(#(-1,0,0), 0)
      ),

      // ==================================================
      // -X
      //
      // Straight down and +X were blocked.
      // ==================================================
      mlt(eql(#, 0),&FROM_NX),

      // ==================================================
      // +Z
      //
      // Straight down, +X and -X were blocked.
      // ==================================================
      mlt(eql(#, 0), &FROM_PZ),

      // ==================================================
      // -Z
      //
      // Straight down, +/-X and +Z were blocked.
      // ==================================================
      mlt(eql(#, 0), &FROM_NZ)
   );
}



~A{
   dim = (&sandboxSz,&sandboxSz,&sandboxSz);
   i_scl = mlt(lrg(#sandbox, 0), 1.7);
   i_pos = C;
   i_rot = #randRot;
   shape = ICO;
   col = (1);
   rot = (0, 0, PI);
   pos = (0, 0.5, 0);
}

^cam{
   light = (0.9);
   fill = (0.3);
}