milvinae

web & product design.

どうしたら写真をキレイにバラバラに配置できるのか?

前回、写真をばらまくアニメーションを作ったものの、なぜか写真が重なることが多くてキレイなランダム感が出ないことがありました。
位置決めに使っていたanime.random関数に偏りが出やすいのかもしれません。

今回は写真をうまくばらまくという目的のために、”ランダムとは何か?”を掘り下げていこうと思います。



…と思っていましたが、あまりにランダムの海は広く深く底が見えないことがわかりました。
ランダム-Wikipedia
乱数生成-Wikipedia
擬似乱数-Wikipedia
暗号論的擬似乱数生成器-Wikipedia
カオス理論-Wikipedia


サイコロですらカオス理論的側面により初期値鋭敏性が云々…
心を落ち着けるためにモンハン起動しちゃいましたよ。




ちょっと理屈を理解して完全なる乱数を目指すことは論文レベルになってしまうみたいです。
バラバラに見えるように数枚の写真を配置するだけでこのレベルはやりすぎでした。


目標を変えて、”一見ランダムに見えるという乱数をデザインする”という形を目指しましょう。

まず現状の問題を把握をしましょう。

前述のとおり、何度も試行しているとどうも写真が重なる確率が高いように感じられました。
位置決めにはanime.random関数しかかかわっていないので、重なる原因はanime.randomにあるように思えます。

anime.random内部では実質Math.random関数が支配しており、これが偏ることがあったりするのでしょうか?
Math.random()

MDNによると、Math.random関数は実装された時点で乱数の種が固定されてしまっている、とのことでした。
これはつまり乱数の現れ方がある程度決まってしまっていることを意味しています。
だからといって乱数があらわれる周期が不十分、ということを意味しません。

可能性があることは分かりました。
実際のところ本当に偏っているのでしょうか?

以下にCodepenで作成したMath.randomのランダム性テストをお見せします。

See the Pen Untitled by keiyashi (@keiyashi) on CodePen.


さて、サンプルコードでは5万回試行して、1から100まで(正確には0から99まで)の値の出た回数を表示します。

出現率の低いものと高いもので100回以上の差があるものもあります。
これは偏っている、といってもいいのでしょうか?
この時点では私は偏っていると考えていました。


次に、”従来にない長周期, 高次元均等分布”な擬似乱数生成器であるメルセンヌ・ツイスターでランダムしてみます。
http://www.math.sci.hiroshima-u.ac.jp/m-mat/MT/what-is-mt.html

See the Pen is Math.random true random? by keiyashi (@keiyashi) on CodePen.


あれ?
意外と…
Math.randomと同じくらい差がある…?

それぞれの方法を数回試行してみた感じ、どちらも散り方は似たようなものでした。
ということは、Math.randomは偏っていない&ランダムを使って写真を配置した場合複数枚が重なってしまうことは大いにあり得る、ということになるわけですね。

※Math.randomは実装方法が違うものがあり、(暗号以外の用途では)十分に使用に耐えうるXorShift乱数発生器が使われている場合があるそうです。
 私が使用しているブラウザはどうやら偏りがないMath.randomを使っていた可能性が高いです。



では、ランダムっぽい配置にする位置決め方法をデザインしていきます。
【要件】
1.1度に表示する写真は6枚
2.写真を貼り付けるボードの上下左右に一様に散る
3.ある程度の重なりがある

つまり、6枚の写真のうち4枚は四隅方向に散らばり、残り2枚がいい感じに散ってくれればOK、という感じです。
四隅のざっくりとした位置を決めておいてほんの少しランダムで動かすこともできますが、あんまりキレイな感じがないので気が進みません。
なるべくキレ~な形にしたいです。

X・Y方向にわけて、それぞれ上下・左右に寄りやすい確率を考えると、三角関数が使えそうな気がします。
0→360度変化したときのsinΘをX成分だけ・Y成分だけ取り出すと、1・ー1側に偏ります。
Θを0~359度の中から無作為に6つ取り出せば、うち2つは1寄りに、うち2つは‐1寄りに、うち2枚は0あたりに散らばりやすいはずです。

方針はいい感じです。

var value = Math.floor((Math.sin(Math.random() * 360) + 1) / 2 * 100);

おぉ~!
いい感じに偏ってる気がしますね。
中心付近もバラツキがあっていい結果になりそうな気がしますね。
そもそもSinで大きい偏りがあるので、Randomをつかっても影響は少ないでしょう。

ではこれを使って写真を配置してみます。


あれぇ?
なんだか結構偏っちゃいましたねぇ…。
理屈では行けそうな気がしたんです。

もう画像枚数で360度を均等割りして割り振っちゃいましょう。
これなら確実に要件通りになるはずです。
初期値だけランダムにすれば同じようなばらけ方はぱっと見わからないようになりますね。


時々固まっちゃうこともありますが、印象的には以前よりばらけてる感じになりました。

今回はこれで良しとしましょう!