読者です 読者をやめる 読者になる 読者になる

uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかFregeとかJavaとか -

elmでやってみるシリーズ8: 赤いドットが回っているかのように見えるけど実は直進運動な錯視

elm elm-lang Functional haskell

Elmでやってみるシリーズ8: 赤いドットが回っているかのように見えるけど実は直進運動な錯視

少し前に、「このくるくる回る白いドット、実は真っ直ぐ往復してるだけなんだぜ」という記事がありましたが、これをElmで再現してみようというのが今回のネタ。
(追記 id:waman さんが数学解析をされています→回転しているようにみえる白いドットは単振動している - 倭算数理研究所 。このネタ自体もwamanさんのTwitterで知ったんですけどね。)

以下は画面キャプチャ(静止画)。
f:id:uehaj:20140727232140p:plainf:id:uehaj:20140727232055p:plain

コードは以下のとおり。

fps_=96 -- 画面更新頻度
pi2 = 2*pi -- 2π
deg30 = pi2 / 360 * 30 -- 30°をラジアン単位に変換

-- 1秒間にfps_回、1増加するシグナル値。
ticks : Signal number
ticks = foldp (\it acc->acc+1) 0 (fps fps_)

-- ticksから派生させて、1秒間に一回転に対応する角度値のシグナルを計算。また、元シグナルticksに対してdミリセカンド分、発生するのが遅れるシグナルにする
sig : Float -> Signal Float
sig d = let f x = (x / fps_) * pi2
        in f  <~ (delay (d * millisecond) ticks)

-- 座標(x,y)を原点を中心に左にtだけ回転させた座標を得る
rotate_ : Float -> (Float, Float) -> (Float, Float)
rotate_ t (x,y) = (x*(cos t)-y*(sin t),  x*(sin t)+y*(cos t))

-- 時間に追随するラジアン単位角度tに対するsin tの位置を、特定角度xで全体を傾けてプロットする
dot : Float -> Float -> Form
dot t x =  move (rotate_ x (100 * (sin t), 0))  (filled red (circle 3))

-- 「tick値と、角度を変えたdotを6つはっつけたコラージュ(400x200)を作る純粋関数disp」に、dotの位置を表わすシグナル値sigを6つ作ってリフト適用。これらのシグナル値への引数としてそれぞれディレイ値を適切に設定することで、「遅れ」による位相差が生じ、「回転する」かのように見えることになる。
main : Signal Element
main = let disp t p1 p2 p3 p4 p5 p6 = flow down
                [ asText t
                , collage 400 200
                                  [ dot p1 <| deg30*0
                                  , dot p2 <| deg30*1
                                  , dot p3 <| deg30*2
                                  , dot p4 <| deg30*3
                                  , dot p5 <| deg30*4
                                  , dot p6 <| deg30*5
                                  ]]
       in disp <~ ticks ~ sig 0 ~ sig 100 ~ sig 200 ~ sig 300 ~ sig 400 ~ sig 500

実行画面

上記のソースを変更した上で実行したい場合はこちらからどうぞ。ブラウザ内で実行できます。フル画面表示はこちらから。fps_値を増やすとなめらかになりますが、ブラウザ負荷がたぶん上がります。

気づいたこと・解説

  • Signal.delay関数で元のticksのSignalを「時間的にずらしたシグナル」を作れる(sig dではついでにラジアン単位の角度にしている)
  • おそらくJavaScriptのonTimerイベントの精度の関係で微妙な誤差が出るのですが、なんか有機生命みたいなゆらぎが出てておもろい。
    • 角度でずらせば、正確にずらせるんですが、FRPの面白さを堪能するために、時間でずらしてます。
    • hatena blog記事中のインラインフレーム中で実行させてると、また遅延が違うので動きが違う。味わい深い。
    • 補助線を引いてみると以下のとおり。

関連エントリ


「Elmでやってみる」シリーズのまとめエントリ - uehaj's blog

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

プログラミングHaskell

プログラミングHaskell