悦楽舎 © 2002 hoshi
2020/12/18 (2022/03/13加筆)

JINS MEME +JoyCon +Kinect で少女になる

VR機器はないけどVRしたいとか3Dモデルを全身動かしたいとか思ったらそれっぽい機器を集めてゴリ押ししましょう!情熱で!

キケン!極めて技術的な記事です! C#やNode.jsは飛び出してくるし、ドキュメントは雑に張るし、ライブラリの使い方は一切説明しません!「Unityと和解する少女」 になるまでに乗り越えた試練の記録です。

動画を多用してるけど全画像+動画の合計は7MB、そっちは安全(


(2022/03/13 新型JINS MEME(Gen 2)が発売され、旧型関連のページがごっそり消滅したので修正)

目次

  1. Joy-Con編
  2. MEME ES編
  3. Kinect(v1)編
  4. Unity編(四元数編)
  5. おまけのムーブメント
  6. 完成!(まとめ)

Joy-Con編

お馴染みのJoyconです。BluetoothでWindowsに接続できます(普通に繋がる)、UnityにJoyconLibを入れるとすぐ動きます(ちょっと直す必要あり)


(動画です。346frames、700KB)

動いた!いや~簡単でしたねJoy-Con編、完!……とはならず動かしてると傾きがズレてきます。(動画は修正済)


というわけで、補正してしっかり使える3DoFコントローラとして見直そう!というのがJoy-Con編です。

ジャイロ補正

ちなみに補正機能はSwitch本体にちゃんとあります。……1度だけやった気がする(やってない)

とりあえず、今Windowsに繋いだのでJoy-Con Toolkit直接SPIを書き換えて補正するのが早いですね!(


ジャイロは静止していれば全て0になるので、一番安定するこの状態で確認するのがオススメです。

joyconの安定姿勢

補正終わり!完!……ともいかず、静止したまましばらく待つとじわじわズレてきます。生データを見てみると静止しててもノイズで動いてしまっているようです。

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上の回転軸が違うのはこの修正で直ります。

加速度に時間を2回乗算すれば移動距離が出て手のトラッキングが!

出来ない。(あわよくば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();

……例外があるのでこれも必要です(後述)

MEME ES編

【追記】JINS MEME(Gen 2)発表 & 旧型サポート終了

21年10月に後継機が出ました。在庫なしで放置されてたから撤退秒読みだと思ってた

で、ここに登場するMEME ES本体やES向けSDKはサポート終了済です。記事の公開は約一年前ですが今やおじいちゃんの昔話くらいのアレです。あの頃度なしレンズに替えておけばよかったのぅ

JINS MEME ES本体

外ではなく内を見るメガネ「JINS MEME(ミーム) ES」です。うっかり買いました(中古)(人生初メガネ)(度が入ってて見にくい)

頭にJoycon装着はアレなので、顔に装着しやすそうな形のこっちを頭の3DoFコントローラにします。

で、真っ先に書いておきたいのが、Node.jsの開発環境がある今は!MEME ESでも!Windowsに接続可です!(誰もネットに書いてない!!)

接続できれば「レンズ交換ってどうやってするんだろ」以外で悩むことはほぼありません!(メガネ初心者)

Bluetooth接続までの長い長い道のり

ボケか愚痴か自己責任しかないので次の「データを送る」まで飛ばしてもいい

Q. (研究開発者向けの)MEME ESRじゃなくて、ESでもWin/Macで開発できるの?

先に書きましたが結果的にできました。iOSアプリの作例がtwitterで話題になった当時(2018/05)は無理だったけど、Node.js向けSDK(β)公開(2018/10)で出来るようになったようです。(この大ニュースが10RT21Fav!?)


(ESはSDK、ESRはDevKitと呼び分けているので)上記ツイートはついにESでも使えると発信しているんだと思いますが、読み解けないと長い間Win/Macにつなぎたいなら桁違いの値がするESRを買うしかなかったとか、SDK公開後も開発者向けラインナップの“接続”が更新されてないとか、ESR用の“専用ドングル”の響きからダメそうな印象もあって……


……使えるか分からないまま買ったけど動いてよかった!(

Q. 中古で買ったのでMACアドレスがわかりません(自己責任)

MACアドレスの紙がなかった。保証書は入ってたので前の持ち主の名前はわかった(匿名発送)(無意味)

で、チュートリアルアプリでSCANすると接続待機中のMEMEのアドレスだけが表示されるので楽です。(周囲にBLE機器がたくさんある環境でもバッチリ)


(21/12追記) アプリが動かなくなったら気合でBT機器の一覧から探しましょう(爆) uuidはアプリ内に書いてあります

Q. 中古で買ったので操作方法やLEDの意味がわかりません(自己責任)

ESの取説(PDF)とMT/ESRのも読んだけど長押し2秒(電源ON&待機)と3秒(電源OFF)しか分からない……

ほぼ推測によるMEME ES操作方法(Windowsと接続)

(太字は取説に書いてある操作)

  • 電源OFF時、電源ボタン2秒 → 電源ON → 接続待機(青LEDが点滅し続ける)
  • 接続待機 → 接続確立(青LED点灯) → 稼働中(消灯)
  • 稼働中(消灯)、電源ボタン1秒 → 稼働中サイン(青LED点灯) → 稼働中(消灯)
  • 稼働中(消灯)、電源ボタン1秒 → 稼働中(接続ロスト)サイン(青LED2回点滅) → 稼働中(消灯)
  • 稼働中(接続ロスト)、電源ボタン2秒 → 接続待機


  • 接続待機 → タイムアウト → 終了準備中(橙LED)
  • 稼働中、電源ボタンホールド → 3秒後、終了準備中(橙LED)
  • 終了準備中(橙LED)、電源ボタンから指を離す → 電源OFF
  • 終了準備中(橙LED)、電源ボタンホールド → 消灯後、指を離す → 青LED2回点滅 → 電源ON


  • 充電中(橙LED) → 充電完了(消灯)
  • 充電&終了準備中(橙LED)、電源ボタンホールド → 消灯後、指を離す → 青LED2回点滅 → 電源OFF
  • 電源OFF時&充電中、電源ボタンホールド → 2秒後、接続待機、ホールド → (接続確立せず)計3秒後、不明状態(桃LED点灯)
  • 充電&不明状態(桃LED点灯)、電源ボタンホールド → 消灯後、指を離す → 青LED2回点滅 → 電源OFF
  • 充電&不明状態(桃LED点灯)、USB切断 → 青LED2回点滅 → 電源OFF


  • エラー?(赤LED点滅)、電源ボタンホールド → 消灯後、指を離す → 青LED2回点滅 → 電源ON


(青2回点滅は再起動?、充電中なら再起動しない??)(桃LEDはシリアル通信モード?)


※ 取説「充電中の使用は控えましょう」

Q. Bluetooth接続できない!(公式アプリ)

Bluetooth機器は先ずペアリングと思ってて、ペアリング不要な機器があるなんて知らなかった……(無知)

N64拡張バックがないままドンキーコング64を起動したりして門前払いにあった世代やぞ?

どのOSでもMEMEを待機状態にしてからアプリや黒窓からScan&Connectで繋ぎにいくのが作法です。

ちゃんとお店で買ってたら教えてもらえてたのかな……(自己責任)

Q. Bluetooth接続できない!(Node.js)

simple-sampleのREADMEに、MACアドレスはコロンなし小文字で、って書いてありますね!コロンなしだけ読んで、小文字を見逃す奴なんて……

(上の致命的なアホムーブ2つのせいでWinじゃ使えなさそうやしAndroidでやろとなり、迷走)

迷Q. EclipseのAndroidエミュでBluetoothが使えない

Android x86(on VirtualBox)にドングルを認識させる。※ MEMEの開発ドキュメントは実機推奨してます!

迷Q. VirtualBoxにドングル取られてJoy-Conとペアリングできない

何故か引き出しの中にもう1個ドングルあった、でも2つ同時に認識できないし上手く切り替わらんわワロ

迷Q. Bluetooth接続できない!(仮想Android x86)

なにもしてないのにぶるーてゅーすがこわれた(ガチ)、あらゆる手を尽くしたが二度と仮想環境のAndroidに青い光が灯ることはなかった。完!

(なんとなくmemelib.min.jsの中身を覗いたら小文字のMACアドレスを発見。うおおお接続できるやんけ!ちゃんと書いとけや!(書いてある))


というわけでBluetooth接続できました。READMEはちゃんと読もう! 2日くらい無駄にするぞ!!

データを送る(vJoy/UDP)

最初は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編で対応)

こう90°に

一部のお客様はご注意ください( Are you enjoying the time of EVE ?

Kinect(v1)

けっこう高いところにおいてあるKinect

深度センサーでアレして体の各部位の位置データを送ってくれるKinectです。KinectToVR(VR機器+Kinectで頭両手+腰両足6点トラッキングするソフト)も一時期弄ってたしいけるやろと(適当)

ちなみに取得できるのは15個の座標で、手首など末端が回転しなくて良いならKinectだけでもそこそこ動けます。三角頭巾を付けて手が"うらめしや~"なら超自然に見えるでしょう。ただし手首が回らないとSteamVRのメニュー操作すら困難です(経験談、一番下の余談参照)


今回はJoyconやMEMEの姿勢データと位置データをミックスして使います。4点(頭+両手+腰)の6DoF(位置3軸+傾き3軸)が揃うのでその辺のVR機器と張り合えるかもしれません (HMDなしVRをしたい人には)

準備

まずv1はUSBじゃないので変換ケーブルを輸入、SDK v1.8Kinect with MS-SDKを導入すればUnity上ですぐ動くものの信じられないほど遅いのでOpenNI2を使います。手順は全て省略です(

NiWrapper.NetはUnityでも動く?風な?感じ?があったもののDLL(C#)の参照DLL(C++)の参照DLL(C++)がUnity上だとなんかダメでもうダメでした(5日消滅)(C++はもうこりごりだよトホホ……)

今更ね、Kinectすごいと、気付いたよ(字余り)

ウンザリな気分を払うために、3次元の位置データがどんなん見てみよwとサンプル(NiTE.SkeletonTracker)を拡張してSkeletonViewerを作りました。


(動画です。256frames、797KB)

Kinect視点と3軸平行投影でどんな感じに捉えられてるのかよーく分かってとってもいい感じです(手前味噌)

赤い丸は原点、黄色はSkeletonの重心、BoundingBoxも取得可ですが表示していません。


側面から見ると20°も傾いてるとかが分かって座標補正機能も付けました。(動画は-20°補正有)

  • PosZero(原点位置): 原点の位置を変更してデータ全体を修正します。
  • PosRotX(カメラ角度): X軸で回転してデータ全体を修正します。(狭い部屋で見下ろす配置は前に傾く)
  • SkSmooth(測定値丸め): v2には無い(?)SkeletonSmoothingFactorです。ばらつきを抑えるアレです。
  • CamShift(表示位置): データは弄らず黒窓内の描画位置を変えます。
  • CamScale(表示倍率): データは弄らず黒窓内の拡大率を変えます。(内部で固定)


Smoothingは初期値0.5でガタガタ、1.0で完全停止でアレっぽかったので、音ゲーマーが許せる程度の遅延と引き換えにほぼブレなくなりました。これのおかげで原点と角度の補正だけで済んでます(ケチってv1買ってて良かった)

30fpsなのでフレーム補間してぬるぬるおじさんになる必要が……来年やる

データを送る(UDP)

BitConverterでbyte型にした15個の位置データをバーッと送って、Unity上で受け取ったらfloat型にザッと戻すだけ!省略!準備完了!(


(動画です。60frames、90KB)

MEMEもvJoy経由で送ってたし、KinectもSkeletonViewerから送るのでいいや、と(決めたので補正はこっちに入りました)データ送り出したらSkeletonTransporterじゃん

得体のしれないDLLをたくさん含んでる以上、Unityから切り離すのが互換性問題の根本的解決なんスね~。Unityと和解?知らん

Unity編(四元数編)

4番目はUnityです。4つの機器(Joyconが2つ)のデータから四元数(Quaternion)を算出して3Dモデルが四股(しこ)を踏めるようにします(?)


どすこい!どすこい!

四元数(Quaternion)って何?役満?トンカツ?

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.cs
c2sqt = 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);

zoi

今日も一日Unityと和解するぞい!


スティック下で"ぞい!✊"出来るようになりましたが、上で"メイドッ!🖖"になったりは(まだ)しません

(先に人差し指と親指の間に肉を乗せるとかの修正をしたい)

自分の手を晒したついでに解説。ここの肉がない違和感がすごい!2020

てのにく

左下が若干あるように見えるのは良い錯覚(回転すると無い)

手首と肘の回転連動

手首はJoyconで捻れますが肘はKinectのデータだけで捻れません。このままだと腕がソーセージになる場合があります。

現実の自分の腕を(ソーセージに気をつけながら)捻って観察すると肘は手首の半分程度捻れば良さそうです。

四元数編で計算して強引に突破するつもりでした、が「手のポーズ変更」でHumanPoseを使い始めると勝手に肘もいい感じの角度で回り始めました(

あー、和解だわ和解(白目)

口パク

ココちゃんGBから移植で、首から上のメッシュとブレンドシェイプも雑に修正して……の時間はなかった。来年やる

完成!(まとめ)

(動画、無音、クリックにて……再生。163frames、635KB)

カメラがぐるぐる回るとか、トップ画みたいな凝った構図にするとか、派手な動画作りたかったけど全然間に合わなかったぞい!(上の静止画用に撮ったモーションなので色々ミスが……見なかったことにしよう(超法規的措置))

基本操作

  • 動く: 動く
  • 瞬き: 瞬き
  • スティック上下(Joycon L/R): 手の開閉(左手/右手)
  • HOMEボタン(Joycon R): Joycon再キャリブレーション
  • CAPTUREボタン(Joycon L): モーションの記録開始/停止して保存

(記録はEasyMotionRecorderを改変して使ってます)


CAPTUREボタンが……効かない……JoyconLib君!!

Joycon.cs(JoyconLib)
// 何故かCAPTUREだけ器用に忘れてるので追加 buttons[(int)Button.CAPTURE] = ((report_buf[4] & 0x20) != 0);

出来たもの

メガネ(MEME 39g)を装着してJoycon(左右で100g)を持ってKinectの前に立つだけのフルトラ環境です。 それっぽく言えば「妙ちくりんなメガネJoycon14点トラッキング」とか?変態メガネJC(14)追跡でゎない

ボーン制御についてまとめると、↓

機能

  • Kinectによる全身トラッキング(15の位置データ、胴体の姿勢データ)
  • JINS MEMEによる頭のトラッキング(首の姿勢データ)
  • Joyconによる両手のトラッキング(手首の姿勢データ)
  • 全て回転ベクトルでボーン制御(体格差の完全吸収)

出来ない動作(ボーン未使用)

  • [Spine/Chest]体を丸めるとか体を捻る動作(上半身だけ回転とか、腰振りとか)は出来ない
  • [Shoulder_LR]肩が動かない、"本来"肘が肩より上に行けない(今は人体制約無視&衣装で誤魔化し)
  • [Foot_LR]足首を回せない、一応足にJoycon装着ですぐ解決できる……はず
  • [指ボーン]おまけではないちゃんとした指関節のトラッキング、でも出来たらJoycon持てない(
  • [Eye_LR]視線トラッキング、MEMEで検知できるっぽいけど未知数
  • [乳ボーン]揺れるほどない

やってない補正

  • 首、両手首、胴体以外のボーンの捻り(両肘はHumanPoseで偶然解決、色々と未確認)
  • 体のパーツ同士の距離補正(腰に手を当てようとして貫通とかの対策)
  • 着地判定、地面との摩擦補正、足ボーンの地面の角度/距離による変形

対応案メモ……?

  • [Joycon]某フィットネスで「姿勢を保つ」とかしてそうだしRecenter無くせそうじゃね?
  • [Spine/Chest]SDK1.8やMMD(OpenNI1)はSpine/手先/足先を含む20joint取得可
  • [Chest]着地してて膝の高さが同じ時限定、両膝–両腰の4点を平面に見立ててトラッカー化とか
  • 足の長さ定義&片足は着地の仮定で膝の角度から着地判定を……20°傾き補正で精度が……広い部屋……
  • Unityと和解してちゃんとIK使えや
  • 買収で深度センサーの未来を奪った会社ほんま許さん openni.orgを開くとちょっとした衝撃を味わえるぞ!(

ぶっちゃけ使用感

ポージング、短いモーション撮影、VTuberごっこ、VRゲーは出来そう、ダンスを撮るのは難しそうな感じ。

Kinectで綺麗にターンできないし、Joyconは(WiiコンやVRコン/トラッカーの様な)赤外線の姿勢補正がなくて激しく回すと軸が狂う(適宜JoyconのRecenterでリセット)


問題はそれくらいで(体を捻れないのを我慢して手首の回し過ぎに注意すれば)変な操作や謎の儀式もないし使いにくさや機能不足はない……けど、煌びやかな衣装を着てステージに立つのが目標なら体の捻りは要るか……ははは


Perception Neuron 欲しい!

3Dモデルに動きをつける仕組みが一段落したので記事にしました。かなり削ったのに全然長い!(

Vket5や彗のモデル修正が終わったらまたなんか書くかも(長すぎないのを)

追伸

たわしです^o^

Vket5の頒布物はたわしみかん箱です。たわしショップ(BOOTH)に置いてます。


余談

今年2月(彗の完成前後)にKinect+JoyconでのVRChatプレイ計画も進めてました(Driver4VRの動画HMDなしVRの可能性に気付いた)

KinectToVRとnull_driverを改変してほぼ完成。よし、内容を書き留めておくか!と思った矢先、SteamVRのアプデが来て起動不能になりやる気ごと終わりました……。まぁblog再開のきっかけになったのでアレ


で、そのとき作ったイカれたヤツを紹介するぜ!(イカれてるのでまず最終形が2つ存在する) VR joycon driverとか呼んでたような呼んでなかったような……

最終1はK2VRを改造してKinectだけの6点トラッキングにJoyconのボタン入力を組み合わせたもの、最終2はもうボタンで全部操作すればいいじゃんと間違った方向に突っ走ったものです(

意味が分からん?……\ オレモー /


これでも一応動けて、1は手の位置がブレブレガタガタでUIに照準を合わせるのに数分粘る根気が必要、2は画期的に使いにくいシステムでどっちも実験的……でした。

HMDなしVR計画は今回の頭,両手,腰をVirtualMotionTrackerを使ってOpenVRに流せば達成できる気がします。入力送るだけ……C++のソースは見なくていい……神の上か!?


最後に2月のメモの一行を貼り付けて終わりにします。


C++なんかじゃやってられない