・・・ということが分かってなかったので、備忘録として。
下の画像は(見苦しくてすみませんが)とあるVector3ベクトルの変化をDebug.Logしたものです。
オレンジ線はゼロベクトルと見なされる値と見なされない値の境界線。
値は左から順に、
vector: 通常のVector3.ToString値
raw vector: 微小値を丸めない生ベクトル値
mag: magnitude(長さのスカラー値)
normalized vec: Normalizeした場合の値
==Vector3.zero?: Vector3.zeroとイコール判定をした結果
をあらわしています。
コードがこれ
Debug.Log
("<b>[vector]</b>" + vec +
" <b>[raw vector]</b>" + vec.ToStringRawFloat() +
" <b>[mag]</b>" + vec.magnitude +
" <b>[normalized vec]</b>" + vec.normalized +
" <b>[ == Vector3.zero?]</b>" + (vec == Vector3.zero));
ちなみにToStringRawFloat()は自前の拡張メソッドで、通常のVector3.ToString()では小数点3桁以下が表示されないので、生の値をそのまま表示するようにするためのものです。
public static string ToStringRawFloat(this Vector3 v) => "(" + v.x + ", " + v.y + ", " + v.z + ")";
このログから次のことが分かります。
長さがE-06以下の小さいベクトルは、
Vector3.zeroとのイコール判定でtrue(ゼロベクトルと見なされる)
Normalizedすると完全なゼロベクトルになる
E-05以上のはゼロと見なされていません。
言い換えると、
Vector3.zeroとのイコール判定の結果がtrueでも、必ずしもベクトル自体が完全なゼロであるとは限らない(微小値が入ってるかも)
ベクトルの軸スカラー値やmagnitudeをベクトルのゼロ判定に使ってしまうと、Normalize結果と整合性が取れなくなる可能性がある
ということです。
ここが分かっていなかったため、次のような処理をしてしまってデバッグに時間を取られました
// 何らかのベクトルをここで取ってくる
var vec = GetMyVector();// <-これはダミーメソッド
// ゼロベクトルだったら適当なベクトルに置換する(ゼロベクトルを除外したい)
if(vec.magnitude < Mathf.Epsilon) vec = Vector3.forward;
// ベクトルを正規化(ゼロじゃないからちゃんと正規化されるよね?)
vec = vec.normalized;
// ベクトルを使ってLookRotationを計算(ゼロじゃないから問題なく通るよね?)
Quaternion result = Quaternion.LookRotation(vec,Vector3.up);
// でも、最後のクォータニオンの行で"Look rotation viewing vector is zero"メッセージが出たりする。
// ゼロベクトルはここまで入ってこないはずなのに・・・!
Quaternion.LookRotationはゼロベクトルが入るとデバッグメッセージが出るため、予めゼロベクトルを除外しようと考えました。
その際に、ゼロベクトルか否かの判定にmagnitudeを使ってしまったのが間違い。
長さが微小値Epsilonより小さいベクトルはゼロと見なして別のベクトルにすり替える作戦だったのですが、EpsilonはE-06よりももっと小さいため、「Epsilon以上、E-06以下」の長さのベクトルはチェックをすり抜けてしまい、Normalizeされた段階でゼロベクトルになってしまっていたというわけです。
Vector3のゼロベクトル判定にはmagnitudeを使わない
代わりにVector3.zeroとのイコール判定を使う