Rでアニメーションその1

注:本記事は「趣味」の範囲で作成しております。多分、アニメーションを作成したいなら、あえてRでやらなくてもよいと思います。

目次

なぜアニメーション?

ワシントンポストのシミュレーションに感動したのと、Pythonで再現してみたという記事を見かけて、どこまでできるかわからないけどやってみようと思い立ちました。

情報検索

そもそも、アニメーションをRで作ることができるのか・・・

Googleで検索すると、GGanimate等の有名どころのパッケージはでてくるが、どうもぴんと来ない。

そんな中、こんなサイトを発見

まずは、このサイトの写経を(若干Tidyverseで書き換えながら)行いつつ、「ボールが跳ねる」動画を作成することを本記事では目指すことにします。

ボールをはねさせよう!

データの準備

library(tidyverse)

#ボールの特徴と位置を表すtibbleを作成
df <- tibble(
  v0      = 100,         #初期速度
  theta   = 1.4,         #角度(ラジアン)
  gravity = 5,           #重力
  adj     = 0,           #跳ねる効果を作成
  decay   = 0.8,         #ボールの跳ね具合
  color   = "steelblue", #ボールの色
  cex     = 2,           #ボールのサイズ
  t       = 0,           #ボールの時刻
  xpos    = 0,           #現在のx軸上の位置(更新される)
  ypos    = 0,           #現在のy軸上の位置(更新される)
)

このtibbleを時間経過とともに更新して、pngで画像を保存する関数

snapshot <- function(df, ct, outdir = "bouncing") {
  dir.create(outdir)
  tval <- 0.3

  # open PNG device
  png(filename = sprintf("%s/bounce%04d.png", outdir, ct), 
      width = 960, 
      height = 540)

  # remove any margin
  par(mar = c(0, 0, 0, 0))
  # create blank canvas
  plot(c(0, 0), 
       type = "n", 
       col = "white", 
       xlim = c(-1, 960), 
       ylim = c(-5, 540), 
       yaxt = "n", 
       ann = FALSE, 
       xaxt = "n", 
       bty = "n")

  # add baseline calculate new position using projectile formula
  # df$ypos <- df$v0 * df$t * sin(df$theta) - (df$gravity * (df$t^2))
  # df$xpos <- df$v0 * df$t * cos(df$theta) + df$adj

  df <- df %>% 
    mutate(
      ypos = v0 * t * sin(theta) - (gravity * t^2),
      xpos = v0 * t * cos(theta) + adj
    )

  # draw the point(s)
  points(df$xpos, df$ypos, type = "p", cex = df$cex, pch = 16, col = df$color)

  # check for anything bouncing
  for (x in seq(nrow(df))) {
      if (df$ypos[x] < 0) {
          # reset the bounce
          df$adj[x] <- df$xpos[x]
          df$v0[x] <- df$v0[x] * df$decay[x]
          df$t[x] <- -tval
      }
  }
  # if stuck, settle it.
  df$v0 <- ifelse(df$v0 < 0.01, 0, df$v0)
  df$t <- df$t + tval
  dev.off()
  df
}

#snapshotを実行
for (i in seq(280)) {
    df <- snapshot(df, i, outdir = "oneball")
    print(str_glue("{i}/280"))
}

おお、oneballフォルダの中に、280個のpngファイルができた!

これを、動画形式に変換するためには、参考にしたWebサイトではavconvという
プログラムでpngファイルをmp4に変換していたんですが、ここもRのパッケージで
完結したいところ・・・

(参考にした記事では、システム関数を利用して、コマンドラインでの処理を実施している。)

system("avconv -f image2 -y -i oneball/bounce%04d.png  -r 25 -b 50000000 -s 1920x1080 -an oneball.mp4")

ググってみると・・・

発見しました。FFMPEG(有名どころ)のラッパーとなる、avパッケージ。

普通にCRANにあるので、簡単にインストールできます。

install.packages("av")
library(av)

input_files <- list.files("oneball", full.names = TRUE)

av_encode_video(input = input_files, output = "test1.mp4")

できた!

pngを出力せずにmp4を出力してみる。

ということで、png多量に生成して、mp4を生成するという手順は問題なくできることが
確認できたので、次は、avパッケージにある、直接アニメーションを生成できる関数と、
ggplotを使って描画することを試みる。

これは特段難しい話ではないので、さくっと。
今回写経したsnapshot関数は、点の時間経過に伴うシミュレーションと、pngの出力を同時に賄っていたので、その部分も切り分ける。

reset_df <- function(){
  df <- tibble(
    v0      = 100,         #initial velocity
    theta   = 1.4,         #angle in radians
    gravity = 5,           #this is just picked for the sclale
    adj     = 0,           #used in the bouncing effect
    decay   = 0.8,         #the bounciness of the ball
    color   = "steelblue", #color of the ball
    cex     = 2,           #size of the ball
    t       = 0,           #time position of this ball
    xpos    = 0,           #current x position (will be updated)
    ypos    = 0,           #current y position (will be updated)
  )
 return(df) 
}

generate_picture <- function(df, xrange = 1200, yrange = 900){

  range_xy_ratio <- xrange/yrange

  gg <- ggplot(df) + 
    geom_point(aes(x = xpos, y = ypos, color = color, size = cex)) +
    labs(x = NULL, y = NULL) +
    scale_x_continuous(breaks = NULL, minor_breaks = NULL) +
    scale_y_continuous(breaks = NULL, minor_breaks = NULL) +
    theme(legend.position = "none", 
          panel.background = element_rect(fill = "white", colour = "white")) +
    coord_cartesian(xlim = c(0,xrange), ylim = c(0,yrange))

  return(gg)
}

generate_picture(df)

これで、グラフの描画はOK。シミュレーション部分を書いてみる。

df <- reset_df()

simulate_ball <- function(df, time_lapse=0.3){
  #new t value
  df <- df %>% mutate(t = t + time_lapse)

  #calculate new position
  df <- df %>% 
    mutate(
      ypos = v0 * t * sin(theta) - (gravity * t^2),
      xpos = v0 * t * cos(theta) + adj
    )

  # check for anything bouncing
  for (x in seq(nrow(df))) {
      if (df$ypos[x] < 0) {
          # reset the bounce
          df$adj[x] <- df$xpos[x]
          df$v0[x] <- df$v0[x] * df$decay[x]
          df$t[x] <- -time_lapse
      }
  }
  # if stuck, settle it.
  df$v0 <- ifelse(df$v0 < 0.01, 0, df$v0)
  df$t <- df$t + time_lapse

  return(df)
}

for(i in 1:5){
  print(i)
  df <- df %>% simulate_ball(.,time_lapse = 0.3)
  generate_picture(df) %>% print()
}

OK!あとは、これをav関数で直接mp4に変換する!
(と意気込んだのは良いものの、フレームレートの設定等を色々といじくる必要があったので、
結局、pngでまず吐き出しておいて、それを処理する方がよさげでした。次のようなスクリプトでも動画はできるはできましたけどね。)

av::av_capture_graphics(expr = {
  df <- reset_df()
  for(i in 1:280){
    print(i)
    df <- df %>% simulate_ball(.,time_lapse = 0.3)
    generate_picture(df) %>% print()
  }
}, output = "test2.mp4", framerate = 40)

次は、ワシントンポストのシミュレーションを再現できるかこころみます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次