この記事は Agent Grow Advent Calendar 2021 21日目の記事です。
そうだ、Web でダーツ投げよう。
お久しぶりです!江嵜です!
みなさん、ダーツ投げてますか?ダーツ好きですよね?
え?まさかそんな人がいるとは思っていませんでしたが、まだやったことがないのですか?それはもったいない!
ダーツがいかに楽しく、素晴らしい「スポーツ」か、ということについては
(そうなんです、スポーツなんですよ!)
多くのサイトで語られているので、私はそこは一旦置いておきます。
今回、私が話したいのは、ダーツをする人が抱える悩みについて。
ダーツはとても中毒性の高いスポーツなので、一度ハマってしまうと、いつでもどこでもダーツを投げることを考えるようになってしまいます。
具体的には、こんな症状が発症してしまうことでしょう。
- 数十秒の待ち時間で素振りを始めてしまう
- 常にカバンにマイダーツが入っている
- 出先でダーツを投げられる場所(ダーツバー、ネットカフェ、ゲームセンター等々・・・)を探してしまう
とても恐ろしいですね!
残念なことに、ダーツはいつでもどこでもできるスポーツではありません。
(大抵のスポーツがそうですが)
しかし、いつでもダーツを投げたい!と思っている方でこの世はあふれていることもまた事実。
そこで私は考えました。
「そうだ、何でも Web でできる時代なんだから、ダーツも Web で投げられるようにしたらいいじゃん!」
作りました。
https://darts-simulate.netlify.app/
(読み込みに少し時間がかかります)
動画はこんな感じ
はい!おめでとうございます!
これでダーツ中毒のあなたも、いつでもどこでもダーツが投げられますね!
Web で 3D シミュレーションにチャレンジしよう
きちんと技術的な話もしていきましょう。
今回のソースコードを見たい方はこちら。
https://github.com/ezaki/darts-simulate/
ここ数年で Web 技術は本当に進歩していて、できないことはない、と言ってしまえるほどです。
3D 表現も例外ではなく、ほとんど専門的な知識がなくても Web で 3D を作ることができるようになりました。
内容を細かく話すと、各セクション 1 本記事が書けてしまう位の分量になりそうなので、
今回は作成の流れ・ポイントだけを簡単に見ていきましょう。
なんとなく難しそうに見えるシミュレーションも、分解してみていけばやっていること一つ一つは単純です。
「3D シミュレーションって意外に簡単な積み重ねでできるんだな」と思ってもらえたら幸いです。
モデルを作る
3D 表現をする場合に、どうしても避けて通れないのが、モデルの作成。
今回のように、ダーツ・ダーツボードくらいのシンプルな内容であれば、
3D モデルを作る自分で作ることもできます。
Blender という、無料で使えるソフトを使えば、粘土細工を作るような感覚でモデル作成ができます。
今回は、それぞれこんな感じ。
ここは、作りたいものをそのまま用意された箱のなかで表現できるか…というところなので、
ある意味一番センスが問われる、難しいポイントかもしれませんね。
もっと精巧なモデルが欲しい!とか、思ったようなのが作れない!となれば、
自分で頑張るのも良いですが、販売されているものを買ってしまうのもひとつの手ですね。
web 上で threee で描画するための下地を作成する。
モデルを作成したら、それを web 上で描画する必要がありますね。
今回は、3D の表現を threee.js で実施しています。
3D って言うと難しそうですが、大まかに言ってしまえば、3D 表現で考える必要があるものは
- カメラから、ものがどう(形・大きさ)見えるのか
- ライトがものにどうあたるのか
の二つです。
本来であれば「こう回転させると、こっちから光が当たって、大きさはこういう風にみえるから…」
というのを数学で考えなければならないのですが、
そこのところを、あまり数学を考えなくてもうまいことやってくれるやつが threee.js です。
具体的には、このファイルの start メソッド以下が、準備をしているところ。
https://github.com/ezaki/darts-simulate/blob/master/libs/visualise/DartScene.ts
カメラを用意して…
camera = new PerspectiveCamera( 75, renderer.domElement.width / renderer.domElement.height, 0.1, 1000 ) camera.position.set(0.2, 1.73, 2) ...
光源を用意して…
const ambient = new HemisphereLight(0xFFFFFF, 0x999999, 0.7) scene.add(ambient) const light = new DirectionalLight(0xFFFFFF, 0.3) ...
地面を準備して…
const baseGeometry = new PlaneGeometry(100, 100, 10, 10) const baseMaterial = new MeshLambertMaterial({ color: 0xCFD8DC }) const base = new Mesh(baseGeometry, baseMaterial) ...
さっき作ったダーツとかを読み込んで…
const gltfLoader = new GLTFLoader() dart = (await gltfLoader.loadAsync('/assets/dart.glb')).scene ...
最後に、画面上に上の設定で絵を描くように指定!
onRender(renderer, scene, camera, setCurrent)
はい OK !
3D 表現って難しそうですが、今は優秀なライブラリがあるので、
本当に実世界で撮影スタジオを整えるように、カメラとかライトを用意して設置していくだけで
簡単に環境が用意できてしまうのですね。
挙動を計算する
最後に、シミュレーションのキモ。実際の挙動を計算していきましょう。
ここは、しっかりやるとどれだけでも時間をかけられるような、数学の世界ですが、
今回の計算ではけっこうシンプルな考え方で実現しています。
https://github.com/ezaki/darts-simulate/blob/master/libs/SimulateDart.ts
計算には三角関数・逆三角関数を使っていますが、そういう話は本題ではないので、
これもそのあたりは飛ばして、今回のシミュレーションの考え方だけを簡単に。
ここを最初に決めておくと、案外それを実現するコード自体はさっくりかけたりします。
今回、ダーツが飛んでいく時のルールは以下。
- ダーツは重力の影響を受ける
- ダーツに対する空気抵抗の大きさは、ダーツの進行方向が矢先の方向から垂直に近づくにつれて、大きくなる
- ダーツは、空気抵抗が小さくなるように回転しようとする
これだけです。
これだけを決めて、そのルールに従うように動きを計算していきます。
ルールの意味を簡単に解説。
ダーツは重力の影響を受ける
これは簡単ですね。
ダーツが投げられた後、重力にしたがって下方向に移動しようとします。
これはダーツに限らず、重力の影響を受けるもの全てに言えることですね。
ダーツに対する空気抵抗の大きさは、ダーツの進行方向が矢先の方向から垂直に近づくにつれて、大きくなる
これはちょっと難しい表現ですが、イメージはしやすい現象を表しています。
「ダーツの羽がたくさん空気を受ける向きになっている時は、より大きな空気抵抗を受ける」
というと、確かにそんな気がする!となるかと。
もうちょっと数学チックに、これを分解してみると
- ダーツの進行方向を算出して
- ダーツの向いている方向を算出して
- ダーツが向いている方向と、進行方向の角度の差を出して
- その角度の差が大きければ大きいほど、進行方向に対する速度を減速させる
ということをすれば、実現できます。
ダーツは、空気抵抗が小さくなるように回転しようとする
これもイメージは簡単です。
ダーツは常に進行方向に対して、針を向けて飛んでいく気がしますよね?
そういうことです。
これは実は計算も簡単。
上でダーツの進行方向と向いている方向を算出しているので、
向いている方向を進行方向に近づけていくようにしたら良いだけですね。
実際の計算では、上の三つを、0.01秒ごとに計算して、
- 投げた瞬間のダーツの状態
- 0.01 秒後のダーツの状態
- 0.02 秒後の…
という感じで、次の瞬間についてダーツの状態を計算しつづけていきます。
こうすることで、パラパラ漫画の要領で、ダーツの位置や向きを算出していくことができます。
もちろん、この「何秒ごとか」という部分を、短く設定すると、より挙動もリアルに近づいてきますし、
逆に長くすると、計算量が少なく済みます。
あとは、それをつなげていって、先ほど用意した threee.js の環境でお絵かきしてもらったら
もうシミュレーションの完成!
シミュレーションは、やることを分解してひとつずつこなしていけば、意外と簡単
ちょっと駆け足すぎたので、さすがにこれを読んだだけで
「よーし、わたしも 3D でシミュレーションやっちゃうぞ!」とはならないかもしれません。
ですが、やることを分解して考えれば、意外と簡単な作業の積み重ねで出来てしまうことが理解いただけたでしょう。
もし、自分でもやってみたい!と言うことがあれば、今回わたしが作成したサンプルを動かしながら、
挙動計算の部分をおさらいしてみてください。
よく見ると、確かに書いてあるように、単純な挙動の組み合わせだけで
それっぽい動きになっていることがご理解いただけるかと思いますよ。
さぁ!あなたもレッツシミュレーション!