アイテムの運動と回転について(2)　アイテムの公転運動

この記事はVCIアドベントカレンダーの記事です。


#内容を三行で

1. 単純にあるものの周りを回る（例：太陽の周りを地球が回る
）のをスクリプトでやると、SetPositionを毎フレーム適用することになる
2. それだと重くなるためカクカクになってしまう
3. 空のオブジェクトを用意してSetAngularVelocityをするとなめらかで軽い


#地球を回したい！（公転させたい）
太陽系モデルを作ろうと思ったとき、公転させなきゃねということで、とりあえず作ってみた際の試行錯誤を記事にしてみました。
以下のように回したときに、いかに軽くするかというお話です。
<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://twitter.com/hashtag/VCsakkinonana?src=hash&amp;ref_src=twsrc%5Etfw">#VCsakkinonana</a><a href="https://t.co/3ItSmatSrM">https://t.co/3ItSmatSrM</a><a href="https://twitter.com/hashtag/%E3%83%90%E3%83%BC%E3%83%81%E3%83%A3%E3%83%AB%E3%82%AD%E3%83%A3%E3%82%B9%E3%83%88?src=hash&amp;ref_src=twsrc%5Etfw">#バーチャルキャスト</a> <a href="https://twitter.com/hashtag/virtualcast?src=hash&amp;ref_src=twsrc%5Etfw">#virtualcast</a> <a href="https://t.co/d49I7tbrud">pic.twitter.com/d49I7tbrud</a></p>&mdash; さっきのなな☆彡vket5 デフォルトキューブ (@sakkinonana) <a href="https://twitter.com/sakkinonana/status/1338845158645764097?ref_src=twsrc%5Etfw">December 15, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
前提条件として、太陽系の惑星は８個あり、また地球の月を始めとしたメジャーな惑星や準惑星が十個ほどあります。
したがって全アイテムを出した場合の公転する（自転も）天体は２０個ほどになります。

アニメーションでやることもできるとは思いますが、シミュレーターなので、位置を指定できたほうが良いということでスクリプトで実現することにしました。
とりあえず思いついた以下のようなコードで回してみました。実際は軌道半径や時間はちゃんと計算して出しますが、今回の記事では重要ではないので簡略化してあります。
###コード
```main.lua
sph = vci.assets.GetTransform("Sphere")
cub = vci.assets.GetSubItem("Cube")
local radius = 4
local time = 0
cub.SetLocalPosition(Vector3.up)
cub.SetAngularVelocity(Vector3.zero)
sph.SetLocalPosition(Vector3.zero)
function update()
    time = vci.me.FrameCount/2000
    sph.SetLocalPosition(Vector3.__new(math.sin(time)*radius,1,math.cos(time)*radius))
end
```

#数が多いと重くなる
ところが、問題が置きました。いざ全部実装して並べてみると案外重い（当時）。色々調べるとsetpositionが多すぎて重くなっている面もありました。せっかく毎フレーム移動させても、フレーム落ちしては結果として動きがカクカクになってしまいます。泣く泣くsetpositionを間引きしましたがそうすると結局なめらかではありません。
<blockquote class="twitter-tweet"><p lang="und" dir="ltr"><a href="https://twitter.com/hashtag/VCsakkinonana?src=hash&amp;ref_src=twsrc%5Etfw">#VCsakkinonana</a><a href="https://t.co/3ItSmatSrM">https://t.co/3ItSmatSrM</a><a href="https://twitter.com/hashtag/%E3%83%90%E3%83%BC%E3%83%81%E3%83%A3%E3%83%AB%E3%82%AD%E3%83%A3%E3%82%B9%E3%83%88?src=hash&amp;ref_src=twsrc%5Etfw">#バーチャルキャスト</a> <a href="https://twitter.com/hashtag/virtualcast?src=hash&amp;ref_src=twsrc%5Etfw">#virtualcast</a> <a href="https://t.co/6SdeizDdgH">pic.twitter.com/6SdeizDdgH</a></p>&mdash; さっきのなな☆彡vket5 デフォルトキューブ (@sakkinonana) <a href="https://twitter.com/sakkinonana/status/1338845061023375362?ref_src=twsrc%5Etfw">December 15, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

#考察
いつフレーム落ちするのかを事前に予知するのは難しく、そのせいでフレームごとに位置を指定したくなるのですが、そうするとその分計算が増え余計に負担をかけてしまいます。また、前回のアイテムの運動と回転について（１）でも述べたように、時間とPosition系の関数のタイミングがどうも合ったり合わなかったりしているようなので、これが原因かと考えました。


#空オブジェクトを挟んでSetAngularVelocityを使う
一方で、fixedupdateという物があって、コチラは何があっても定間隔で更新してくれるではないですか。じゃあunityの物理演算に任せたほうがどういう場合に計算を細かくしてどういう場合にサボれるのかよく作られているはずだから、そちらのほうがいい結果が出るじゃないかと思いました。
###VCIの階層構造
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494837/f7f6b4ce-8701-6fd2-0a0f-5456d07c33f8.png)
Cubeが空オブジェクト（わかりやすくするため立方体をいれているけど、なくてOK）です。

###Cubeの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494837/96d404ed-58b8-1b4a-b39a-ade12dcf3af2.png)
Angular Dragは0にするのを忘れずに。

cubeは透明化してあるけど、実際はこんな感じ![2020121523321194.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494837/3cd2a1ae-8092-ca02-4249-a325cf00e221.png)


###コード
```main.lua
sph = vci.assets.GetTransform("Sphere")
cub = vci.assets.GetSubItem("Cube")
-- 軌道半径を適当に入れる
local radius=4 

-- -- velocityを使う方法
-- 初期化
cub.SetLocalRotation(Quaternion.Euler(0,0,0))
cub.SetVelocity(Vector3.zero)
cub.SetLocalPosition(Vector3.up)

-- 回す
sph.SetLocalPosition(Vector3.left*radius)
cub.SetAngularVelocity(Vector3.up)
```
#結果
ではデバッグコンソールの統計情報を見てみましょう。
今回は最初に紹介した愚直パターンと、SetAngularVelocityを使う方法、加えてsubitemだと重くなりがちなので非subitemで同じく愚直パターンを行った場合の3パターンで比較してみました。

それぞれの数値が大きいほど重く、小さいほど軽いです。筆者はoculus rift-sのためmax80fpsです。
###愚直パターンSetPosition(subitem)
![愚直パターンSetPosition(subitem)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494837/aa1da262-2e6d-1fe0-4d32-4a27575eca0a.png)

###SetPosition(非subitem)
![SetPosition(非subitem)](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494837/97a6deca-56e9-ce54-c337-e389895cfbad.png)

###提案手法（SetAngularVelocity）
![提案手法](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494837/245a00e3-b6ce-a785-be24-7c75b3a2ac8c.png)
大分減らせたのではないでしょうか。ActionCountPerSecはほぼゼロに、また、三角関数の計算とそもそも関数の実行がないためかluaProcessingTimePerFrameもほぼ0です。


|  | 愚直パターン | SetPosition(非subitem) |提案手法（SetAngularVelocity）|
|:-----------|------------:|:------------:|:------------:|
| This       | 79.1        | 76.68         |0.01|
| column     | 0.03      | 0.02       |0|
| will       | 0.16        | 0.16         ||0.01

##余談１
ちなみに、スクリプトを更新直後はレートが落ちていて数値がやや暴れますが時間が立つとActionCountPerSec(ALL)が落ちてきてほぼ0になります。
![途中経過](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/494837/d0ec5445-0d20-9c57-6cc6-7baa161abdd3.png)

##余談２
回転中心と回転体の動きを分けて記述する際にも便利です。ルービックキューブのような偏心回転体や、螺旋運動などは空オブジェクトを利用すると便利ですね。