はじめに
Debugが有効なもので「Matrix4x4」がなかなか使われているものが無いのと、
使い方が分からないなど、用途が分かりにくいのかほぼ使われているのを見ません。
実は回転行列(物の回転)においてかなり便利な構造体なのです。
特定のオブジェクトの向きに向ける
例えば、「Object_A」と「Object_B」があるとします。
「Object_A」のオブジェクトを「Object_B」に向けることにします。
まずは「Matrix4x4」を使わずに書いてみます
function LookAt(TargetPos,CenterPos)
local forward = TargetPos - CenterPos
forward = forward.normalized
local rot = Quaternion.LookRotation(forward, Vector3.up)
rot = rot * Quaternion.AngleAxis(0, Vector3.left) --向き補正
return rot
end
Object_A.SetRotation(LookAt(Object_A.GetPosition(),Object_A.GetPosition()))
2点の「Vector3」の座標から「Quaternion」で向きのベクトル(力の向き)を取って目的の向きに向けます。
「Unity」ではObjectに対して「transform」の「LookAt」に向けたい方向の座標を入れるだけなので簡単ですね。
transform.LookAt(Vector3.zero);
単純な回転なら上記の関数でできます。
では、条件を付けましょう。
ローカルZ軸以外を固定しプラネタリウムの投影機の重力シャッターを動かそうと思った時に、軸が2軸固定された状態の回転をしないといけません。
この重力シャッターではローカルX軸を地上に向けると良い構造になっています。
プログラム全体
下記でプログラム全体なのですが6行ほどでできてしまいます。
これはグローバル座標系を元に計算しているため、アイテムを向けている方向に動作が左右されません。
local LensUnit = vci.assets.GetTransform("LensUnit")
local currentAngle = LensUnit.GetLocalRotation().eulerAngles
local localDown = LensUnit.GetLocalToWorldMatrix().inverse.MultiplyVector(Vector3.down)
local localDown.z = 0;
local angleDiff = Vector3.SignedAngle(Vector3.__new(1, 0, 0), localDown, Vector3.__new(0, 0, 1))
local LensUnit.SetLocalRotation(Quaternion.Euler(0, 0, currentAngle + angleDiff))
まずは現在のシャッター回転の向きを取得します
local currentAngle = LensUnit.GetLocalRotation().eulerAngles
次にやりたいのが、地面がどっちの方向にあるのかを、取得しないと
地面にローカルX軸を向ける事が出来ません。
ここで必要な情報はシャッターから見てどっち向きにグローバル座標系の-Y軸方があるのかが分かりません、
なのでグローバル座標系をローカル座標系に変更して、ローカル座標系基準の-Y軸のベクトルを知る必要があります。
ここで、本当は下記のように「InverseTransformDirection」を使うと簡単にグローバル座標系から
ローカル座標系の回転の向きを取得するものがUnityにはあるのですが、
バーチャルキャストにはありません。
Vector3 localDown = LensUnit.transform.InverseTransformDirection(Vector3.down);
バーチャルキャストでは、「transform」はローカル座標系からグローバル座標系のみあります。
仕方ないのでかなりマイナーな逆行列を駆使して「InverseTransformDirection」と同等の
動作を作ります。
最初に「GetLocalToWorldMatrix」でシャッターの向きをローカル座標系から
グローバル座標系にして取得します。
そうすると「Matrix4x4」で取得出来るので、そのまま「inverse」で逆行列でローカル座標系に変換した後に「MultiplyVector」を使い任意のベクトル方向の回転要素を取得します。
local localDown = LensUnit.GetLocalToWorldMatrix().inverse.MultiplyVector(Vector3.down)
ローカルZ軸以外の方を知りたいので「localDown.z」を「0」にして、X軸方向と取得したベクトル間の角度を取得しZ軸から見た角度を下記で計算します。
local localDown.z = 0;
local angleDiff = Vector3.SignedAngle(Vector3.__new(1, 0, 0), localDown, Vector3.__new(0, 0, 1))
最後に取得した、角度と求めた角度を足し合わせてローカル座標系のZ軸にセットします。
local LensUnit.SetLocalRotation(Quaternion.Euler(0, 0, currentAngle + angleDiff))
するとあら不思議グローバル座標系でどの方向向いても問題なくローカル座標系のZ軸のみ回転させてX軸を極力下になるように制御できちゃいます。
因みに何も考えずに行うとグローバル座標系で+X軸方向で作ってたとして、動作した後に「ウッキウキ」で別のスタジオやルームに行って違う方向を向き始めるという「ショック」な事が無くなるのです。
上下左右どこ向けても正しくオブジェクト基準で座標も取れるのです。
世界地図をタッチして緯度経度の座標位置も算出できちゃいますね!
摘み付きのVolumeを実装してみる
- 次にVolumeを実装するので、下記オブジェクトを用意します。
VolObject:摘みの見た目
VolumeObject_Ref:位置固定の基準となるオブジェクト(空の動かさないもの)
Grb_ject:掴む用のSubItem(緑の球)
ここで行う事
- 一回シャッターと同様にローカル座標系でグローバル座標系を取得した後に、
掴む用のSubItemをVector4に変換した座標と足し合わせて座標の差分を出します。 - 次にあまり数値が大きすぎても使いにくいので基準のオブジェクトから±5で数値をクリッピングします。
- 求めたx軸とz軸180で使いやすい値にした後にこの2軸の平面として扱います。
- VolumeObject_Ref基準でx軸とz軸の位置する座標がどっち方向の角度にあるのかを求めます。
- 求めた結果を、摘みの見た目を回転に反映します。
下記がプログラムになります。
function clamp(value, min, max)
--クリップ
if value < min then
value = min
elseif value > max then
value = max
end
return value
end--END_クリップ
local SetVolPos = (VolumeObject_Ref.GetLocalToWorldMatrix().inverse *
Vector4.__new(Grb_ject.GetPosition().x,
Grb_ject.GetPosition().y,
Grb_ject.GetPosition().z,
1))
local Pos_z = clamp(SetVolPos.z , -5, 5) * 180
local Pos_x = clamp(SetVolPos.x , -5, 5) * 180
local LookRot = Quaternion.LookRotation(Vector3.__new(Pos_x , 0, Pos_z), Vector3.up)
local Euler = LookRot.eulerAngles.y + 180
VolObject.SetLocalRotation(Quaternion.Euler(0 , -Euler,0))
これでしっかり、どの方向向けても関係なく動作します。
下記動画は持ち手用のオブジェクトをそのままSetPosition()で一定のずれた場所に持っていくとフレーム差で勝手に回転します。
さらに回転角に規制を入れると下記のような動作もできます。
まとめ
Matrix4x4はどこで使うのか、使い方や場面なんてわかりませんでしたが、
一度言われ使ってみると、今まで悩みがちだったアイテムがグローバル座標系で特定の方向向いていれば動作しそれ以外にVCIを向けると動作しなくなる事や、2軸を固定し1軸のみで特定の方向に常に向くなどの回転が上手くいかないのが、解決するなんて思ってもいませんでした。
またTransformのVector3とRotationが同時に計算できたり、かなり便利な構造体になっています。
最初の取っ掛かりは難しいかもしれないですが、便利なのでぜひ触ってみてください。