kazumalab tech log

流行りとリラックマと嵐が大好きです。技術的ログ。

Unity SteamVR Pluginのソースを読む Part2

かずまです。

今日も少しSteamVRのAPIを読んでいこうと思います。
前回は後半寝ぼけながら書いて...いました。

f:id:kazumalab:20170704070157j:plain

SteamControllerが有効になったところの処理を追っかけて終わりました。
今日はその続きから。

読むのもちろんこちら!


前提

まず、Controllerを動かすための分類分けをしておきます。

APIを提供している部分

  • SteamVR_Controller.cs

Controllerの実装を行っている部分

  • SteamVR_TrackedObject.cs
  • SteamVR_TrackedController.cs
  • open_api.cs
  • SteamVR_Utils.cs

基本開発で使うものはSteamVR_Controllerだけをスクリプトで呼び出して、ボタンが押された!などと言ったCallbackを受け取ることになります。
前回の読み会はわかりにくかったので、今回はサンプルを出して、そこから辿っていこうと思います。

// 常にControllerの位置を取るサンプルコード
void Start () {
	if (trackedController == null) {
		trackedController = this.gameObject.GetComponent<SteamVR_TrackedController>();
	}
}

void Update () {
	device = SteamVR_Controller.Input ((int)trackedController.controllerIndex);

	Debug.Log(device.transform.pos);
}

上記コードのDebugの部分、Controllerの位置を常に表示しているスクリプトについて辿って行きます。

device.transform

まずはtransformを構成している部分のお話です。
通常のMonobehaviorのtransformとは型が違うようです。

f:id:kazumalab:20170704054557p:plain

SteamVR_Controller.csの49行目に来ました。
ここではまずSteamVR_Utils.RigidTransformがどんなものなのかを見て、その後ゲッターのインスタンス生成時の引数poseが何かについて見ていきます。

SteamVR_Utils.RigidTransform

f:id:kazumalab:20170704055607p:plain

RigidTransformメソッドが何をしているのか?

ここでは4 × 4のMatrixを受け取ってVector3に変換しています。
Matrix4x4.identityは恒等行列を返しています。

恒等行列はこれです。

 {
Matrix4x4.identity =
\left(
 \begin{array}{ccc}
   1 & 0 & 0 & 0 \\\
   0 & 1 & 0 & 0 \\\
   0 & 0 & 1 & 0 \\\
   0 & 0 & 0 & 1
 \end{array}
\right)

}

UnityC#のリファレンスにも載っています。
Unity - スクリプトリファレンス: Matrix4x4.identity

// 
public RigidTransform(HmdMatrix34_t pose) {
	var m = Matrix4x4.identity;

	m[0, 0] =  pose.m0;
	m[0, 1] =  pose.m1;
	m[0, 2] = -pose.m2;
	m[0, 3] =  pose.m3;

	m[1, 0] =  pose.m4;
	m[1, 1] =  pose.m5;
	m[1, 2] = -pose.m6;
	m[1, 3] =  pose.m7;

	m[2, 0] = -pose.m8;
	m[2, 1] = -pose.m9;
	m[2, 2] =  pose.m10;
	m[2, 3] = -pose.m11;

	this.pos = m.GetPosition();
	this.rot = m.GetRotation();
}

ControllerとかHMDの座標はMatrixで返って来てるのかな?という所感です。
次にここを常に読んでいる場所があるはずです。そこを見ていきます。
座標ゴニョゴニョはわかりやすいものを参考文献に乗せています。

値がマイナスになっている箇所は位置の場合はZ軸に対して反転させている、という解釈ですが間違っていればご指摘ください。
参考にした資料にも載っていますが、GetPositionメソッドに書かれている通りです。

// SteamVR_Utils.cs 117行目
public static Vector3 GetPosition(this Matrix4x4 matrix)
	{
		var x = matrix.m03;
		var y = matrix.m13;
		var z = matrix.m23;

		return new Vector3(x, y, z);
	}

三列目を使っていることがわかります。
matrixにはGetColumn(int)というメソッドがあるのでそれで取ってきても同じことができそうですね。

ん?あれ?おかしい。
Rubyのメタプロっぽくなってるのはなんで?

  var m = Matrix4x4.identity;

この行ではMatrixの型が入るはず、、、なのになんでSteamVR_Utils.GetPositionが呼び出されてるんだろうか。
詳細を見てみるとExtension method from SteamVR_Utilsと書いてあります。

とりあえず、"Extension method from" でググってみたところ、C#には拡張メソッドがあるみたいです。
それを使えばRubyのメタプロっぽい書き方ができますね。ただし、新たにメソッド名を定義しては使えないみたいですね。

ufcpp.net

なので、ここでGetPositionの引数にthisが入っているのはそういうことだったんですね。
このクラス内だけで呼び出せる拡張メソッドということですね。
気になるのでこの話題は今度プログラム書いてみようと思います。

pose(TrackedDevicePose_t)

次にSteamVR_Utils.RigidTransformのインスタンスを生成する時に使用しているposeについて見ていきます。

f:id:kazumalab:20170704055500p:plain

poseはTrackedDevicePose_tという型を持っていて、それはstruct型になっています。
Trackingするもの(HMDとかControllerとかTracker)の情報を構造的に持たせるために作っています。
次のopen_api.csにかかれているTrackedDevicePose_tです。

// open_api.cs 3944行目
[StructLayout(LayoutKind.Sequential)] public struct TrackedDevicePose_t
{
	public HmdMatrix34_t mDeviceToAbsoluteTracking;
	public HmdVector3_t vVelocity;
	public HmdVector3_t vAngularVelocity;
	public ETrackingResult eTrackingResult;
	[MarshalAs(UnmanagedType.I1)]
	public bool bPoseIsValid;
	[MarshalAs(UnmanagedType.I1)]
	public bool bDeviceIsConnected;
}

StructLayout

最適なレイアウト(メモリレイアウトの話)をするために利用します。
そのためにまずはメモリのアラインメントを知る必要があるらしい。

前提としてメモリの読み書きは4、8の倍数になっている方が読み書きが早いので
プログラミング言語ではアドレスがきれいな倍数になるようにフィールドを並び替えます。

C#ではStructLayoutを使うことでそのフィールドをカスタムできるらしいです。
そもそも個人でゲーム作ってる時にアラインメントを気にしたことはなかった、と言うより初めて知りました。(恥ずかしい)

LayoutKind.Sequential

これは宣言した順番に並べていく手法。
未使用であってもきちんとそのメモリを確保します。

他にも

  • Auto: コンパイラー裁量で並び替えを認める
  • Explicit: 複合型の作者が明示的に位置を指定する

もしかすると今後使うところがあるかもしれないのでメモします。

MarshalAs(UnmanagedType.I1)

マネージ型とアンマネージ型

マネージ型はGC(ガーベージコレクション)が自動的にいらなくなったオブジェクトを破壊してくれたりする。
ココらへんは自動でメモリ管理を行ってくれないようになっているの管理しないと行けないのだろうと予測している。
MarshalAsを使うことで、マネージ型からアンマネージ型への変換が可能になるそうです。
ココらへんは個人的に若干難しく感じます。

I1

1 バイト符号付き整数。 このメンバーを使用すると、Boolean 値を 1 バイトの C スタイル bool (true = 1、false = 0) に変換することができるみたいです。ってかC#ではそもそも1と0のboolはできなかったのか....普段は使わないけど。

d.hatena.ne.jp

あとbool型をそのまま使うと8bitあるそうです。
bbs.wankuma.com
若干信憑性に欠けるソースかもしれませんが...。

今日はここまで!
これでtransformに何が入っていて、どのように値が生成されて...までが読めたはずです、はず。

最後に

今日はControllerが動いているところのソースを分解してみました。
このような感じでControllerが動いているんだなーという所感です。
若干これでいいのか?という感じで終わりますが、読みにくくはないかな〜という感じです。
正直openvr_apiについては5000行近くあるのに少ししか読めてないので結構つらそうだなーという...。

気になる点があればご指摘くださいませ〜。