キケン!極めて技術的な記事です! C#やNode.jsは飛び出してくるし、ドキュメントは雑に張るし、ライブラリの使い方は一切説明しません!「Unityと和解する少女」 になるまでに乗り越えた試練の記録です。
動画を多用してるけど全画像+動画の合計は7MB、そっちは安全(
(2022/03/13 新型JINS MEME(Gen 2)が発売され、旧型関連のページがごっそり消滅したので修正)
お馴染みのJoyconです。BluetoothでWindowsに接続できます(普通に繋がる)、UnityにJoyconLibを入れるとすぐ動きます(ちょっと直す必要あり)
(動画です。346frames、700KB)
動いた!いや~簡単でしたねJoy-Con編、完!……とはならず動かしてると傾きがズレてきます。(動画は修正済)
というわけで、補正してしっかり使える3DoFコントローラとして見直そう!というのがJoy-Con編です。
ちなみに補正機能はSwitch本体にちゃんとあります。……1度だけやった気がする(やってない)
とりあえず、今Windowsに繋いだのでJoy-Con Toolkitで直接SPIを書き換えて補正するのが早いですね!(
ジャイロは静止していれば全て0になるので、一番安定するこの状態で確認するのがオススメです。
補正終わり!完!……ともいかず、静止したまましばらく待つとじわじわズレてきます。生データを見てみると静止しててもノイズで動いてしまっているようです。
Joycon.cs(JoyconLib)// 生データ(補正済)の値を取得(0x0000から0xFFFF) public Vector3 GetGyroRaw(){ return new Vector3(gyr_r[0] - gyr_neutral[0], gyr_r[1] - gyr_neutral[1], gyr_r[2] - gyr_neutral[2]); }
inputTest.cs// Unity上でチェック gyrL = jcL.GetGyro(); gyrRawL = jcL.GetGyroRaw();
整数値で出すとノイズがどれくらいあるのか分かって便利です。で、ノイズ対策にかんたんなハイパスフィルターを追加しました。
Joycon.cs(JoyconLib)// ノイズによるブレはだいたい±4、ただ日によって気まぐれにズレるので再補正回避で2倍くらいに private UInt16 gyr_threshold = 9; // フィルター if (-1 * gyr_threshold < (gyr_r[i] - gyr_neutral[i]) && (gyr_r[i] - gyr_neutral[i]) < gyr_threshold) { gyr_g[i] = 0f; } else { gyr_g[i] = (gyr_r[i] - gyr_neutral[i]) * 0.00122187695f; }
あと、Joyconの回転軸とUnity上の回転軸が違うのはこの修正で直ります。
出来ない。(あわよくば6DoFに……なんて机上の空論です)
人類は「重力」という檻に閉じ込められたどうたら……重力加速度のない宇宙なら使えるかも(遠い目)
それよりジャイロと加速度から姿勢データ(傾き)を算出しているはずなのに、加速度の補正値を無視してるっぽいので念のため追加します。
Joycon.cs(JoyconLib)private Int16[] acc_neutral = { 0, 0, 0 }; // 加速度の補正値取得 buf_ = ReadSPI(0x80, 0x28, 10); acc_neutral[0] = (Int16)(buf_[0] | ((buf_[1] << 8) & 0xff00)); acc_neutral[1] = (Int16)(buf_[2] | ((buf_[3] << 8) & 0xff00)); acc_neutral[2] = (Int16)(buf_[4] | ((buf_[5] << 8) & 0xff00)); PrintArray(acc_neutral, len: 3, d: DebugType.IMU, format: "User accle neutral position: {0:S}"); // 工場出荷データ取得は未チェック(0x20から10にしたけどジャイロのほうが0x29からで被ってる、なんか問題ありそうなので知らない) //if (acc_neutral[0] + acc_neutral[1] + acc_neutral[2] == -3 || Math.Abs(acc_neutral[0]) > 100 || Math.Abs(acc_neutral[1]) > 100 || Math.Abs(acc_neutral[2]) > 100) { // buf_ = ReadSPI(0x60, 0x20, 10); // acc_neutral[0] = (Int16)(buf_[3] | ((buf_[4] << 8) & 0xff00)); // acc_neutral[1] = (Int16)(buf_[5] | ((buf_[6] << 8) & 0xff00)); // acc_neutral[2] = (Int16)(buf_[7] | ((buf_[8] << 8) & 0xff00)); // PrintArray(acc_neutral, len: 3, d: DebugType.IMU, format: "Factory accle neutral position: {0:S}"); //} // 補正値を適用 acc_g[i] = (acc_r[i] - acc_neutral[i]) * 0.00025f;
Joycon.cs(JoyconLib)// 生データ(補正済)の値を取得 public Vector3 GetAccelRaw() { return new Vector3(acc_r[0] - acc_neutral[0], acc_r[1] - acc_neutral[1], acc_r[2] - acc_neutral[2]); }
SPI Flashの格納データ、6軸センサー値の使い方など、資料が揃ってて助かりました。 目視確認……(加速度補正値の使い方が違う気もするけど見た感じうまく動いてるな……)ヨシ!
これでじわじわズレるのを解消できました。机に戻せば必ず水平に戻ります!準備完了!Joy-Con編、完!
inputTest.cs// 再キャリブレーション jcL.Recenter();
……例外があるのでこれも必要です(後述)
21年10月に後継機が出ました。在庫なしで放置されてたから撤退秒読みだと思ってた
で、ここに登場するMEME ES本体やES向けSDKはサポート終了済です。記事の公開は約一年前ですが今やおじいちゃんの昔話くらいのアレです。あの頃度なしレンズに替えておけばよかったのぅ
外ではなく内を見るメガネ「JINS MEME(ミーム) ES」です。うっかり買いました(中古)(人生初メガネ)(度が入ってて見にくい)
頭にJoycon装着はアレなので、顔に装着しやすそうな形のこっちを頭の3DoFコントローラにします。
で、真っ先に書いておきたいのが、Node.jsの開発環境がある今は!MEME ESでも!Windowsに接続可です!(誰もネットに書いてない!!)
接続できれば「レンズ交換ってどうやってするんだろ」以外で悩むことはほぼありません!(メガネ初心者)
ボケか愚痴か自己責任しかないので次の「データを送る」まで飛ばしてもいい
先に書きましたが結果的にできました。iOSアプリの作例がtwitterで話題になった当時(2018/05)は無理だったけど、Node.js向けSDK(β)公開(2018/10)で出来るようになったようです。(この大ニュースが10RT21Fav!?)
(ESはSDK、ESRはDevKitと呼び分けているので)上記ツイートはついにESでも使えると発信しているんだと思いますが、読み解けないと長い間Win/Macにつなぎたいなら桁違いの値がするESRを買うしかなかったとか、SDK公開後も開発者向けラインナップの“接続”が更新されてないとか、ESR用の“専用ドングル”の響きからダメそうな印象もあって……
……使えるか分からないまま買ったけど動いてよかった!(
MACアドレスの紙がなかった。保証書は入ってたので前の持ち主の名前はわかった(匿名発送)(無意味)
で、チュートリアルアプリでSCANすると接続待機中のMEMEのアドレスだけが表示されるので楽です。(周囲にBLE機器がたくさんある環境でもバッチリ)
(21/12追記) アプリが動かなくなったら気合でBT機器の一覧から探しましょう(爆) uuidはアプリ内に書いてあります
ESの取説(PDF)とMT/ESRのも読んだけど長押し2秒(電源ON&待機)と3秒(電源OFF)しか分からない……
(太字は取説に書いてある操作)
(青2回点滅は再起動?、充電中なら再起動しない??)(桃LEDはシリアル通信モード?)
※ 取説「充電中の使用は控えましょう」
Bluetooth機器は先ずペアリングと思ってて、ペアリング不要な機器があるなんて知らなかった……(無知)
N64拡張バックがないままドンキーコング64を起動したりして門前払いにあった世代やぞ?
どのOSでもMEMEを待機状態にしてからアプリや黒窓からScan&Connectで繋ぎにいくのが作法です。
ちゃんとお店で買ってたら教えてもらえてたのかな……(自己責任)
simple-sampleのREADMEに、MACアドレスはコロンなし小文字で、って書いてありますね!コロンなしだけ読んで、小文字を見逃す奴なんて……
Android x86(on VirtualBox)にドングルを認識させる。※ MEMEの開発ドキュメントは実機推奨してます!
何故か引き出しの中にもう1個ドングルあった、でも2つ同時に認識できないし上手く切り替わらんわワロ
なにもしてないのにぶるーてゅーすがこわれた(ガチ)、あらゆる手を尽くしたが二度と仮想環境のAndroidに青い光が灯ることはなかった。完!
(なんとなくmemelib.min.jsの中身を覗いたら小文字のMACアドレスを発見。うおおお接続できるやんけ!ちゃんと書いとけや!(書いてある))
というわけでBluetooth接続できました。READMEはちゃんと読もう! 2日くらい無駄にするぞ!!
最初はvJoyにデータを流して、これが"メガネ型仮想ゲームコントローラ"だ!とウキウキしてました(謎)
meme.js(Node.js)// MEMEのデータをvJoyに流す const { vJoy, vJoyDevice } = require('vjoy'); let device = vJoyDevice.create(1); // ボタンも軸の値も"1"始まりなので注意 device.buttons[1].set(0 != data.blinkSpeed); // 瞬き device.axes.X.set(1 + Math.round(((data.yaw / 180)) * 16384)); //twist 中心180 0 ~ 360 device.axes.Y.set(1 + Math.round(((data.pitch / 180) + 1) * 16384)); //stick.y 中心0 -180 ~ 180 device.axes.Rz.set(1 + Math.round(((data.roll / 180) + 1) * 16384)); //stick.x 中心0 -90 ~ 90 反時計回り
inputTest.cs// vJoyの入力を取得 memeAngle = new Vector3( Joystick.all[0].stick.y.ReadValue() * -180f, // 右手 → 左手系 Joystick.all[0].twist.ReadValue() * 180f, Joystick.all[0].stick.x.ReadValue() * 180f );
ちなみにUnity InputSystemにvJoy特有の問題があって、最初にこの修正を各自行う必要があります(面倒)
……で、遅延が大きく"メガネ型仮想ゲームコントローラ"に拘る意味もないので結局UDPで作り直しました
meme.js(Node.js)// MEMEのデータをUDPで飛ばす const dgram = require('dgram'); const client = dgram.createSocket('udp4'); const msg = Buffer.allocUnsafe(18); client.connect(36705, 'localhost', null); msg.writeFloatLE(data.yaw, 2); msg.writeFloatLE(data.pitch, 6); msg.writeFloatLE(data.roll, 10); msg.writeFloatLE(data.blinkSpeed, 14);
これで頭の姿勢データと瞬きが受け取れるようになりました。JINS MEME ESも準備完了です!迷走が長すぎ
(動画です。290frames、700KB)
ただ、取得できるデータはオイラー角なので首が90°傾くとジンバルロックが起こります(Unity編で対応)
一部のお客様はご注意ください( Are you enjoying the time of EVE ?
深度センサーでアレして体の各部位の位置データを送ってくれるKinectです。KinectToVR(VR機器+Kinectで頭両手+腰両足6点トラッキングするソフト)も一時期弄ってたしいけるやろと(適当)
ちなみに取得できるのは15個の座標で、手首など末端が回転しなくて良いならKinectだけでもそこそこ動けます。三角頭巾を付けて手が"うらめしや~"なら超自然に見えるでしょう。ただし手首が回らないとSteamVRのメニュー操作すら困難です(経験談、一番下の余談参照)
今回はJoyconやMEMEの姿勢データと位置データをミックスして使います。4点(頭+両手+腰)の6DoF(位置3軸+傾き3軸)が揃うのでその辺のVR機器と張り合えるかもしれません (HMDなしVRをしたい人には)
まずv1はUSBじゃないので変換ケーブルを輸入、SDK v1.8とKinect with MS-SDKを導入すればUnity上ですぐ動くものの信じられないほど遅いのでOpenNI2を使います。手順は全て省略です(
NiWrapper.NetはUnityでも動く?風な?感じ?があったもののDLL(C#)の参照DLL(C++)の参照DLL(C++)がUnity上だとなんかダメでもうダメでした(5日消滅)(C++はもうこりごりだよトホホ……)
ウンザリな気分を払うために、3次元の位置データがどんなん見てみよwとサンプル(NiTE.SkeletonTracker)を拡張してSkeletonViewerを作りました。
(動画です。256frames、797KB)
Kinect視点と3軸平行投影でどんな感じに捉えられてるのかよーく分かってとってもいい感じです(手前味噌)
赤い丸は原点、黄色はSkeletonの重心、BoundingBoxも取得可ですが表示していません。
側面から見ると20°も傾いてるとかが分かって座標補正機能も付けました。(動画は-20°補正有)
Smoothingは初期値0.5でガタガタ、1.0で完全停止でアレっぽかったので、音ゲーマーが許せる程度の遅延と引き換えにほぼブレなくなりました。これのおかげで原点と角度の補正だけで済んでます(ケチってv1買ってて良かった)
30fpsなのでフレーム補間してぬるぬるおじさんになる必要が……来年やる
BitConverterでbyte型にした15個の位置データをバーッと送って、Unity上で受け取ったらfloat型にザッと戻すだけ!省略!準備完了!(
(動画です。60frames、90KB)
MEMEもvJoy経由で送ってたし、KinectもSkeletonViewerから送るのでいいや、と(決めたので補正はこっちに入りました)データ送り出したらSkeletonTransporterじゃん
得体のしれないDLLをたくさん含んでる以上、Unityから切り離すのが互換性問題の根本的解決なんスね~。Unityと和解?知らん
4番目はUnityです。4つの機器(Joyconが2つ)のデータから四元数(Quaternion)を算出して3Dモデルが四股(しこ)を踏めるようにします(?)
どすこい!どすこい!
3次元空間上の物体の姿勢を表す回転ベクトルです。四槓子でも大数隣でも大三元でも三元豚でもない
LookRotationを使うと2つの座標(始点と終点)からその姿勢が四元数で得られます。ふーん、簡単そう(
ちょうどUnity Humanoidの例を書いている方がいました、パク参考にしましょう!(
そういえば、「Unityと和解する少女」 とはこの子です。言問異 彗 (こととい すい) と言います。モデリングについて書き始めるとかなり長くなるので今回は自重します(
で、急に紹介したのは参考のコードはUnityちゃんでしか正常に動きません。彗の場合は肘と膝以外ダメでした、別のモデルだとまた別のボーンが別の方向に向きます。謎。
とりあえず、ガチャガチャやってたらよく分からないまま運良く腕と脚も動くようになりました!(
inputTest.cs// ちなみに天地をひっくり返してるこの処理は c2sqt = Quaternion.Inverse(Quaternion.LookRotation(c2svec)); c2sqt = c2sqt * Quaternion.AngleAxis(180, Vector3.forward); // これで書き変え可能 c2sqt = Quaternion.LookRotation(c2svec, Vector3.down); // なんとなく成功した"位置の制限"と"軸回転"と"角度の補正"がある左腰の例(c2svecは左腰–左膝ベクトル) if (c2svec.z >= -15f) { c2svec.z = Math.Min(c2svec.z, -15f); // (実は不要だったけどこれで可動域を制限できる) } c2svec.x += 70f; // 外側に補正 c2sqt = Quaternion.LookRotation(c2svec, Vector3.right); bone.rotation = c2sqt;
次に、humanoidの胴体はHips、Spine、Chestの3ボーンに分離してますが、KinectのデータはTorsoだけです。トランプ柄の不思議の国の兵隊のイメージで胴がまな板の人類を想像してください(
これはただの線で捻りが取れない(=5DoF)腕や脚と違い、形が不変の平面なので2つの直交ベクトルを合成して6DoF腰トラッカーを生成できます。これに気付いた時は目からうろこでした。
inputTest.cs// c2svecは胴体–首、c2svec2は左腰–右腰ベクトル(c2svec2のY軸回転だけ抽出して合成)(Hipは日本語で"尻"ではなく"腰") c2svec2.y = 0; c2sqt = Quaternion.LookRotation(new Vector3(c2svec2.z, c2svec2.y, -c2svec2.x)); if (Math.Abs(c2svec.z) <= 140f) { c2svec.z = -40f; } c2svec *= 20; c2svec.x /= 5; if (c2svec.z <= 0) { c2sqt = Quaternion.Inverse(Quaternion.LookRotation(new Vector3(c2svec.x, -c2svec.z, -c2svec.y), Vector3.back)) * c2sqt; } else { c2sqt = Quaternion.Inverse(Quaternion.LookRotation(new Vector3(c2svec.x, -c2svec.z, -c2svec.y), Vector3.forward)) * c2sqt; }
次は、MEMEの姿勢を首に割り当てます。(頭の位置(Kinect)+傾き(MEME)で6軸あるのでKinectの首–頭ベクトルは未使用。首座標も未使用)
inputTest.cs// ジンバルロック回避とか首の可動域とかでしれっと±50°に制限 if (angle.x < -50) { angle.x = -50f; } else if (angle.x > 50) { angle.x = 50f; } if (angle.z < -50) { angle.z = -50f; } else if (angle.z > 50) { angle.z = 50f; } c2sqt = Quaternion.Euler(angle.x, angle.y, angle.z);
MEMEは常に測定していて現実と同じ方角に向くので実行時に差分を取ってUnity上での前に向かせてます。
忙しくても首が回り続けてしまう時は、Winから切断→スマホに接続→公式アプリでキャリブレーション。そしてスマホから切断→Winに接続、頻発はないけどそこそこ面倒(真面目に使ってないのがバレる)
そして最後に、Joyconの姿勢を手首に割り当てます。
inputTest.csc2sqt = jcL.GetVector();
はい!これで無事、四元数を扱えるようになりましたね!(大嘘)(公式丸暗記で謎のまま解を求める作業も数学の一側面だったよね)(入試の為の学問)
VRC想定でモデリングしてた彗にはブレンドシェイプとかがそのまま残ってるので使ってみます。
(動画です。420frames、171KB)
CreateJSで作ってた目パチアニメを流用してリミテッドアニメーション風目パチ機能を入れました。
MEMEの瞬き検知だけでなく、謎の涙ステータスも流用してるので目がシパシパしてくるとパチパチします。
CreateAnim.cs// アニメーション設定 public class mPtn { public const float m0 = 0f; public const float m1 = 15f; public const float m2 = 50f; public const float m3 = 100f; } public class Anim { public const int mePachiNormal = 0; public const int mePachiDouble = 1; public const int mePachiLong = 2; } private Dictionary<int, float[]> me = new Dictionary<int, float[]>() { { Anim.mePachiNormal, new float[]{ mPtn.m3, mPtn.m3, mPtn.m2, mPtn.m1, mPtn.m0 } }, { Anim.mePachiDouble, new float[]{ mPtn.m3, mPtn.m3, mPtn.m2, mPtn.m1, mPtn.m3, mPtn.m2, mPtn.m1, mPtn.m0 } }, { Anim.mePachiLong, new float[]{ mPtn.m2, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m3, mPtn.m2, mPtn.m1, mPtn.m0 } } }; // 目パチ再生 public void playMePachi(int target) { me.TryGetValue(target, out frames); meCoroutine = play(frames, new int[]{ smr.sharedMesh.GetBlendShapeIndex("blink"), smr.sharedMesh.GetBlendShapeIndex("mayu_sumashi") }); StartCoroutine(meCoroutine); meCoroutine = null; } private IEnumerator play(float[] fs, int[] target) { for (int i = 0; i < fs.Length; i++) { for (var j = 0; j < delayFrameCount; j++) { yield return null; } foreach (var t in target) { smr.SetBlendShapeWeight(t, fs[i]); } } }
(動画です。59frames、99KB)
完成した嬉しさから踊り狂っていましたが"ぞい!"の構えをしたところ、手が開いたままでこれじゃあ "ぞい!" とは言えないな……とみるみる悲しくなってしまいました(童話並の感情変化)
手のウェイトとボーンが雑だったのを雑に修正しつつ、30ボーンを一度に操作できるようにしました。
UnityBoneAnim.cs// HumanPoseでどれだけ"手を開いた状態"か指定 handler.GetHumanPose(ref target); foreach (var (j, i) in joints.Select((v, i) => (v, i))) { target.muscles[start + (i * 4)] = 0 == i % 5 ? 0.8f * (open * j.x) + thump_opt.x : open * j.x + other_opt.x; target.muscles[start + (i * 4) + 1] = 0 == i % 5 ? spread * j.y + thump_opt.y : spread * j.y + other_opt.x; target.muscles[start + (i * 4) + 2] = 0 == i % 5 ? open * j.z + thump_opt.z : open * j.z + other_opt.x; target.muscles[start + (i * 4) + 3] = 0 == i % 5 ? open * j.w + thump_opt.w : open * j.w + other_opt.x; } handler.SetHumanPose(ref target);
今日も一日Unityと和解するぞい!
スティック下で"ぞい!✊"出来るようになりましたが、上で"メイドッ!🖖"になったりは(まだ)しません
自分の手を晒したついでに解説。ここの肉がない違和感がすごい!2020
左下が若干あるように見えるのは良い錯覚(回転すると無い)
手首はJoyconで捻れますが肘はKinectのデータだけで捻れません。このままだと腕がソーセージになる場合があります。
現実の自分の腕を(ソーセージに気をつけながら)捻って観察すると肘は手首の半分程度捻れば良さそうです。
四元数編で計算して強引に突破するつもりでした、が「手のポーズ変更」でHumanPoseを使い始めると勝手に肘もいい感じの角度で回り始めました(
あー、和解だわ和解(白目)
ココちゃんGBから移植で、首から上のメッシュとブレンドシェイプも雑に修正して……の時間はなかった。来年やる
(動画、無音、クリックにて……再生。163frames、635KB)
カメラがぐるぐる回るとか、トップ画みたいな凝った構図にするとか、派手な動画作りたかったけど全然間に合わなかったぞい!(上の静止画用に撮ったモーションなので色々ミスが……見なかったことにしよう(超法規的措置))
(記録はEasyMotionRecorderを改変して使ってます)
CAPTUREボタンが……効かない……JoyconLib君!!
Joycon.cs(JoyconLib)// 何故かCAPTUREだけ器用に忘れてるので追加 buttons[(int)Button.CAPTURE] = ((report_buf[4] & 0x20) != 0);
メガネ(MEME 39g)を装着してJoycon(左右で100g)を持ってKinectの前に立つだけのフルトラ環境です。 それっぽく言えば「妙ちくりんなメガネJoycon14点トラッキング」とか?変態メガネJC(14)追跡でゎない
ボーン制御についてまとめると、↓
ポージング、短いモーション撮影、VTuberごっこ、VRゲーは出来そう、ダンスを撮るのは難しそうな感じ。
Kinectで綺麗にターンできないし、Joyconは(WiiコンやVRコン/トラッカーの様な)赤外線の姿勢補正がなくて激しく回すと軸が狂う(適宜JoyconのRecenterでリセット)
問題はそれくらいで(体を捻れないのを我慢して手首の回し過ぎに注意すれば)変な操作や謎の儀式もないし使いにくさや機能不足はない……けど、煌びやかな衣装を着てステージに立つのが目標なら体の捻りは要るか……ははは
Perception Neuron 欲しい!
3Dモデルに動きをつける仕組みが一段落したので記事にしました。かなり削ったのに全然長い!(
Vket5や彗のモデル修正が終わったらまたなんか書くかも(長すぎないのを)
Vket5の頒布物はたわしとみかん箱です。たわしショップ(BOOTH)に置いてます。
今年2月(彗の完成前後)にKinect+JoyconでのVRChatプレイ計画も進めてました(Driver4VRの動画でHMDなしVRの可能性に気付いた)
KinectToVRとnull_driverを改変してほぼ完成。よし、内容を書き留めておくか!と思った矢先、SteamVRのアプデが来て起動不能になりやる気ごと終わりました……。まぁblog再開のきっかけになったのでアレ
で、そのとき作ったイカれたヤツを紹介するぜ!(イカれてるのでまず最終形が2つ存在する)
最終1はK2VRを改造してKinectだけの6点トラッキングにJoyconのボタン入力を組み合わせたもの、最終2はもうボタンで全部操作すればいいじゃんと間違った方向に突っ走ったものです(
意味が分からん?……\ オレモー /
これでも一応動けて、1は手の位置がブレブレのガタガタでUIに照準を合わせるのに数分粘る根気が必要、2は画期的に使いにくいシステムでどっちもゴ実験的……でした。
HMDなしVR計画は今回の頭,両手,腰をVirtualMotionTrackerを使ってOpenVRに流せば達成できる気がします。入力送るだけ……C++のソースは見なくていい……神の上か!?
最後に2月のメモの一行を貼り付けて終わりにします。
C++なんかじゃやってられない