Galton Board

Galton Board Setup

library(chipmunkcore)
library(ggplot2)
set.seed(1)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Initialize a simulation space
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cm <- Chipmunk$new(time_step = 0.005)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Add funnel segments
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gap <- 3
cm$add_static_segment( -70, 150, -gap, 100)
cm$add_static_segment(  70, 150,  gap, 100)
cm$add_static_segment(-gap, 100, -gap,  90)
cm$add_static_segment( gap, 100,  gap,  90)


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Add pins
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (i in 1:15) {
  y <- 90 - i * 3
  if (i %% 2 == 1) {
    xs <- seq(0, 40, 2)
  } else {
    xs <- seq(1, 40, 2)
  }
  xs <- 1.0 * sort(unique(c(xs, -xs)))

  w <- 0.05
  xstart <- xs - w
  xend   <- xs + w

  for (xi in seq_along(xs)) {
    cm$add_static_segment(xstart[xi], y,  xend[xi],  y)
  }
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Add slots 
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
floor <- 0
width <- 50
for (x in seq(-width, width, 5)) {
    cm$add_static_segment(x, floor,  x,  40)
}

cm$add_static_segment(-width, floor, width, floor)
cm$add_static_segment(-width, floor-0.2, width, floor-0.2)



#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Fetch all the segments. Use for plotting
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
segments_df <- cm$get_static_segments()

ggplot() +
  geom_segment(data = segments_df, aes(x = x1, y = y1, xend = x2, yend = y2)) +
  coord_fixed() +
  theme_void() +
  theme(legend.position = 'none')

Add some bodies to fall through the board

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Add some bodies. Currently only circular bodies supported
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (i in 1:400) {
  cm$add_circle(
    x        = runif(1,  -20,  20),
    y        = runif(1,  120, 150),
    radius   = 0.8,
    friction = 0.01
  )
}

Animate!

  • Advance the state of the simulation
  • get the positions of all the bodies
  • Plot everything.
  • Repeat
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Clear out the target directory for all the frames
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
unlink(list.files("man/figures/png", "*.png", full.names = TRUE))


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# (1) advance the simulation (2) plot the bodies. (3) Repeat.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (i in 1:1000) {

  if (i  %% 10 == 0) message(i)

  cm$advance(5)

  bodies <- cm$get_circles()

  p <- ggplot(bodies) +
    geom_point(aes(x, y), size = 1.6) +
    geom_segment(data = segments_df, aes(x = x1, y = y1, xend = x2, yend = y2)) +
    coord_fixed() +
    theme_void() +
    theme(legend.position = 'none') +
    NULL


  outfile <- sprintf("man/figures/png/%04i.png", i)
  ggsave(outfile, p, width = 7, height = 7)
}


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# ffmpeg - create mp4 from PNG files
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# system("ffmpeg -y -framerate 30 -pattern_type glob -i 'man/figures/png/*.png' -c:v libx264 -pix_fmt yuv420p -s 800x800 man/figures/galton.mp4")

# mp4 to gif
# ffmpeg -i galton.mp4 -filter_complex 'fps=30,scale=800:-1:flags=lanczos,split [o1] [o2];[o1] palettegen [p]; [o2] fifo [o3];[o3] [p] paletteuse' out.gif

# gifsicle -O99 -o out2.gif -k 16 out.gif