この記事はNeosVR Advent Calendar2022 15日目の記事です。
NeosVRで任意のアイテムのコンポーネントを取得したり、その中のフィールドにアクセスしたい!そう思ったこと、一度はありますよね。
NeosVRでは、一般的な方法でではslot(Unityで言うGameObjectのこと)にアタッチされているコンポーネントの一覧やそれぞれの参照、 そして、そのコンポーネント内のフィールドにアクセスするためには、事前に自らLogiXツールチップを使ってインターフェイスノードを出したり、インスペクターから ReferenceProxyを介してドラッグアンドドロップしておく必要があります。
大抵の場合はこれで十分ではあるのですが、アバターを自動でカスタマイズしたり、外からインポートしたアイテムなどを一括で大量に編集したいなどのユースケースにおいてはこれができないと不便です。
…なのですが、実は、“RefID"を駆使するとこれが可能になります。
実はMMC22に出していた「Easy Auto Poser」ではこのハックを使っていて、 アバターのVRIKのProxyを勝手にOn/Offしたり、ValueCopyの対象に勝手にProxyTargetのPos/Rotの参照を追加したりしていました。
ハック的な操作になるのでLogiXはどうしても複雑になってしまいがちなのですが、今回、複雑なRefID操作をモデル化して、箱に詰めました。
本記事では最初にちょっとしたサンプルを作ってみてこのハックの流れをざっと説明した後、箱の中身についてもざっくり説明します。
本記事で使っている箱とサンプルは、僕のパブリックフォルダ( neosrec:///U-thotgamma/R-f5211804-4242-41a2-acea-43ebdde0f44f )の中のtotegammaRefKitsに入っています。
今回作るサンプル
NeosUIのCheckboxの値を外から操作してみましょう
CheckboxをON/OFFするためには、IsCheckedフィールドにBooleanの値を書き込む必要があります。 本来なら次のようにWriteノードの書き込み先をInterfaceノードのIsCheckedフィールドに指すことで、Refノードを獲得する必要があります。
しかし、このRef自体をどうにかして変数に取得できれば、それをもとに書き込むことができます。
このRefノード相当の参照を、LogiXのみでSlotから獲得してみましょう。
全体像はこんな感じです。
気持ち複雑かもしれませんが、紐解いていけば極めてシンプルです!
Slotを取得します
これは簡単ですね。 FindSlotByNameで今回のCheckBoxのスロットを探します。 名前はデフォルトのままにしちゃうので"NeosCheckBox"でSlotを探します。
Slotの中にあるコンポーネントを取得します
さて、急に難しくなりました。
これは本来不可能なのですが、可能にしておきました。白い箱を使います。
白い箱は任意のSlotについているコンポーネントの一覧を生成する機能と、 その一覧から名前でマッチするコンポーネントの参照を呼び出す機能があります。
まず前節で取得したSlotを渡して、コンポーネント一覧を生成しましょう
パルスを送ると、コンポーネント一覧がDVとして生成されます。
続いて、今回ほしいのは"NeosCheckBox"コンポーネントなので、これを名前で検索します(写真の2つ目のDynamic impluse)
一覧から生成から名前検索まで直接繋げれば、NeosCheckboxコンポーネントの参照が出力されるLogiXができました。
コンポーネント中のフィールドを取得
次にコンポーネントのフィールドの参照を求めましょう。 ここで、RefIDのオフセットを使います。
Neosでは、コンポーネントとその中のフィールドの値のRefIDには一定の規則があり、 基本的にはコンポーネントのRefID + 定数を計算すると、目標フィールドのRefIDを求めることができます。
そして、この定数なのですが、厄介なことに最適化のために「セッションごと」にバラバラの値になっています。 なので、セッションが変わったらそのセッションでの値が取れるように、細工をする必要があります。
オフセットを取得
まずは緑の箱を使って定数を求めましょう。 緑の箱に、オフセットを計算したいコンポーネントをアタッチしておきます。今回の場合はNeosCheckBoxですね。
そして、FromのSlotにコンポーネントのRefを、 ToのSlotにアクセスしたいフィールドのRefを代入します。
すると結果がルートにあるValueコンポーネントから出力されます
今回の場合は、オフセットが'5376’であることが分かりました。
オフセットの足し算を行う
まず、取得したNeosCheckBoxのリファレンスのRefIDを、足し算できるように整数値に変換しましょう。 これはNeosあるあるのToStringゴリ押し変換です。
そしたら、前節で求めたオフセットを足しましょう。
RefIDをIFieldに再解釈する
最後に、この整数をまたRefIDに変換し、IFieldへの再解釈を行います。 これは紫の箱を使います。
紫の箱のTagに書き込んで、2フレーム待つと、IFieldのリファレンスが得られます。
紫の箱のTagにRefIDの整数値'490757376’が書き込まれた結果、ReferenceField<IField>に"IsChecked"のフィールド参照が得られていますね。
ちなみに、書き込むRefIDの対象がSlotだった場合はReferenceField<Slot>に、Componentだった場合はReferenceField<IComponent>に結果が現れます。
フィールドに書き換えを行う
後はこのIFieldにWriteするだけです!
なんですが、ちょっとコツが必要で、 というのも、本来はIField<bool>であるべき部分がIField(無印)に省略されているので、 そのままWriteノードにつなぐとWriteノードの型が決まらず、なんとも言えないキャストノードが生成されて なんとも言えない状態になってしまいます。
なので、先にWriteノードにInputをつなぐなどして型を所望のものに確定してから、Refにつなぎます。
これで、ワールド上にある好きなNeosCheckboxのOn/Off状態を自由に切り替えられるようになりました!!!
より詳しい説明
このように、オフセットを用いることでワールド上の任意のIWorldElementにアクセスすることが可能ですが、まだ「どのSlotにどのコンポーネントがアタッチされているか」を完璧な方法で調べる事ができません。 今のところ妥協として2つの方法が考案されています。
slotの中のコンポーネントを取る方法
Inspectorを使う
今回使った白い箱です。
内部的にインスペクターを開いて、インスペクターの中の情報を今回のRefIDハックを用いて読み上げることでコンポーネント一覧を取得しています。 取得自体は(次の紹介する方法よりも)基本的に高速なのですが、たとえばVRIKとかVRIKAvatarとかめっちゃ重いコンポーネントがたくさんついているslotに対して使うと普通に重いです。 あと、ギズモが一瞬出てきます(最悪)。
Linear Search
黄色い箱です。
これは、「Slotにアタッチしてあるコンポーネントは、だいたいSlotのRefIDの後ろにありがち」という法則にもとづいて、RefIDを少しづつ増やして順番に走査していく方法です。 アタッチされているコンポーネントが少なくて小さい場合は、高速に求められるのですが、大体の場合白い箱よりも遅くなります。 また、component attacherを使って後からアタッチしたコンポーネントは意味不明なRefIDが付与されるのでそれは検出できません。
整数をRefIDにキャストする方法
現状は紫の箱でやっているように、Editorを経由してキャストを行っています。 そのため、値を書き込んでからキャストされるまで2f待つ必要があり、非同期Logixを書かないと行けなくてやりづらいです。
本当はLinearSearchとかを並列でやれたら速いのですが、並列のぶんだけEditorを容易しておく必要があり、ちょっとめんどいですよね…。
もっとソリッドで良い方法を模索しています。
応用
「duplicateした後に値を書き換えたい!」というシーンは結構たくさんあると思うんですが、作り終わった後に一度保存して出し直した後のアイテムと(comopnent attacherを使った後のslotからのオフセットはメチャクチャなので)、それをduplicateしたアイテムのアイテムルートから目的のフィールドへのオフセットはだいたい一致する気がするので、そこの最終的なオフセットのみを使って書き換えを行うのは相当使えるハックな気がします(要追加検証)
ていうかInspector開くならコンポーネントだけじゃなくてフィールドもオフセットとかじゃなくて直接出したらいいんじゃないですか?
超ごもっともです。近いうちに箱に詰めておきます……。
(とは言え、本当はインスペクターを開かずに判定したいので理想としてはオフセットを使ったほうが良さそう!)
以上、NeosVR Advent Calendar2022 15日目の記事でした。
NeosVR Advent Calendarにはもう一つの記事、NeosVR最大ケモノコミュニティ「ケモノhub」を支える技術も投稿しているのでぜひ併せてどうぞ。