Matrix4x4
でとてつもなくしょうもないバグにハマったのでメモ。
問題コード
何気なくこんな感じのコードを書いて、「あれ?」となりました。
using UnityEngine; public class MatrixSample : MonoBehaviour { private void Start() { transform.position = Vector3.one; transform.rotation = Quaternion.identity; transform.localScale = Vector3.one; Vector3 result = transform.localToWorldMatrix * Vector3.zero; Debug.Log(result); // (0.0, 0.0, 0.0) } }
なんとなくわかると思いますが、期待していた結果は(1.0, 1.0, 1.0)です。
行列演算であることを考えれば当たり前のことなのですが、
Unityで普段Matrix4x4
で計算することがあまりないので、
ついQuaternion
と同じ感覚で書いてしまいました。
原因
Matrix4x4
とVector3
との間には乗算が定義されていません。
また、Vector3
にはVector4
への暗黙的な型変換が定義されています。
この結果として右辺のVector3
はw値として0が設定されたVector4
に暗黙的に型変換され、
Matrix4x4
とVector4
との間に定義された乗算が呼び出されます。
この演算結果はVector4
ですが、Vector4
からVector3
への暗黙的な型変換も存在するため、
w値が捨てられたVector3
が結果として代入されます。
つまり、
Vector3 result = transform.localToWorldMatrix * Vector3.zero;
は実際には、
Vector3 result = (Vector3)(transform.localToWorldMatrix * (Vector4)Vector3.zero);
として実行されます。
行列演算時のw値が0なので、当然平行移動は一切適用されません。
正しい記述
w値に1が入れば正しく計算できるため、明示的に指定してやれば正しい結果となります。
Vector3 result = transform.localToWorldMatrix * new Vector4(0.0f, 0.0f, 0.0f, 1.0f);
ただ、Matrix4x4
にはMultiplyPoint
という専用のメソッドが用意されているので、そちらを利用した方がいいでしょう。
Vector3 result = transform.localToWorldMatrix.MultiplyPoint(Vector3.zero);
localToWorldMatrix
やworldToLocalMatrix
のように4行目の要素を利用しないのであれば、
MultiplyPoint3x4
を利用すると不要な計算をスキップできます。
Vector3 result = transform.localToWorldMatrix.MultiplyPoint3x4(Vector3.zero);
おわりに
わかってしまえば本当にしょうもないことなのですが、正しいはずと思い込むと結構ハマります。 ベクトルの変換、せめて次元を増やすときは明示的でよかったんじゃないかな・・・。
今までなるべく短い記事というのは避けていたのですが、 特にこれ以上書くこともないし、記事にしないよりはマシかなということで投稿。 今後はちょくちょくこんな感じの記事も書いていくかもしれません。
執筆時のUnityのバージョン:5.6.0f3