超要約
-
外積(Cross)は2本に直交するベクトルを返す。大きさは
|a||b|sinθ
、平行なら 0。 - Unity は左手系。
Vector3.Cross(a,b)
の向きと、Vector3.SignedAngle(from,to,axis)
の符号は引数順・軸向きに依存。 - 使いどころ:左右判定 / 法線 / 面積 / 回転軸 / トルク / スライド方向など“向きの符号”が要る場面。
使いどころ
1) 右/左判定(XZ平面)
- 何が決まる? 自機 forward に対してターゲットが右か左かの符号。
- どんなシーン? 旋回方向の決定、UI矢印、回避方向。
public static bool IsRightOnXZ(Vector3 selfPos, Vector3 selfForward, Vector3 targetPos)
{
Vector3 f = selfForward; Vector3 t = (targetPos - selfPos);
f.y = 0f; t.y = 0f;
if (f.sqrMagnitude < 1e-8f || t.sqrMagnitude < 1e-8f) return false;
// 符号判定に正規化は不要(計算量削減)
return Vector3.Cross(f, t).y > 0f; // +Y なら右(左手系)
}
XZへ投影し
Cross(f, t).y
の符号で左右判定。Cross の引数順に依存。零/極小ベクトルは事前ガード。
Unityの便利メソッドで書くなら(Vector3.SignedAngle
)
public static bool IsRightOnXZ_SignedAngle(Vector3 selfPos, Vector3 selfForward, Vector3 targetPos)
{
Vector3 f = selfForward; f.y = 0f;
Vector3 t = (targetPos - selfPos); t.y = 0f;
if (f.sqrMagnitude < 1e-8f || t.sqrMagnitude < 1e-8f) return false;
return Vector3.SignedAngle(f, t, Vector3.up) < 0f; // 右=負(XZ, axis=+Y, from=f→to=t)
}
SignedAngle
の符号は from/to と axis の向きに依存。この記事は XZ+axis=+Y
前提で「右が負」。
2) 三角形の法線ベクトル(面の向き)
- 何が決まる? 面の向き(法線)と頂点順序に応じた表裏。
- どんなシーン? ライティング、裏表判定、当たり判定の面向き。
public static Vector3 FaceNormal(Vector3 a, Vector3 b, Vector3 c, bool normalize = true)
{
Vector3 n = Vector3.Cross(b - a, c - a);
return normalize ? n.normalized : n;
}
n = Cross(b-a, c-a)
。大きさは平行四辺形の面積、正規化で単位法線。
Unityの便利メソッドで書くなら
public static Vector3 FaceNormalWithPlane(Vector3 a, Vector3 b, Vector3 c)
{
Plane pl = new Plane(a, b, c); // a→b→c の並び依存
return pl.normal;
}
頂点順序(巻き方向)で法線向きが決まる。上から見て時計回り/反時計回りの違いに注意。
3) 三角形の面積
- 何が決まる? 三角形の面積(スカラー)。
- どんなシーン? デカール密度、LOD/UV密度の見積、ナビメッシュの簡易評価。
public static float TriangleArea(Vector3 a, Vector3 b, Vector3 c)
{
return 0.5f * Vector3.Cross(b - a, c - a).magnitude;
}
外積ノルムは平行四辺形の面積。三角形は半分。
Unityの便利メソッドで書くなら(角度利用)
public static float TriangleAreaWithAngle(Vector3 a, Vector3 b, Vector3 c)
{
Vector3 u = b - a;
Vector3 v = c - a;
float ang = Vector3.Angle(u, v) * Mathf.Deg2Rad;
return 0.5f * u.magnitude * v.magnitude * Mathf.Sin(ang);
}
|u||v|sinθ
は外積ノルムと同値。実装は外積の方が簡潔。
4) 点が三角形内にあるか(エッジ関数 / XZ)
- 何が決まる? 点の内外判定(bool)。
- どんなシーン? クリック位置の足場判定、到達可否チェック。
public static bool IsPointInTriangleXZ(Vector3 p, Vector3 a, Vector3 b, Vector3 c)
{
const float eps = 1e-6f;
bool SameSide(Vector3 p1, Vector3 p2, Vector3 e1, Vector3 e2)
{
Vector3 d1 = (e2 - e1);
Vector3 c1 = Vector3.Cross(d1, p1 - e1);
Vector3 c2 = Vector3.Cross(d1, p2 - e1);
// 両方が非負(または非正)なら同じ側、境界は許容
return (c1.y >= -eps && c2.y >= -eps) || (c1.y <= eps && c2.y <= eps);
}
return SameSide(p, a, b, c) && SameSide(p, b, c, a) && SameSide(p, c, a, b);
}
各辺に対する外積の y 符号が同じなら内部。浮動小数点のブレ吸収に
eps
を導入。XZ前提で判定。
5) ローカル基底(Right/Up/Forward)を組む
- 何が決まる? 直交基底(right, up)。
- どんなシーン? 入力のローカル変換、カメラ/ビルボード軸、ギズモ基準。
public static void BuildBasis(Vector3 forward, out Vector3 right, out Vector3 up)
{
forward = forward.normalized;
Vector3 arbitraryUp = (Mathf.Abs(forward.y) > 0.999f) ? Vector3.forward : Vector3.up;
right = Vector3.Cross(arbitraryUp, forward).normalized;
up = Vector3.Cross(forward, right); // 直交を保証
}
forward ≈ up
での特異ケースを回避するため、代替 Up を選択してから直交化。
Unityの便利メソッドで書くなら
public static void BuildBasisWithLookRotation(Vector3 forward, out Vector3 right, out Vector3 up)
{
forward = forward.normalized;
Vector3 safeUp = (Mathf.Abs(Vector3.Dot(forward, Vector3.up)) > 0.999f) ? Vector3.forward : Vector3.up;
Quaternion q = Quaternion.LookRotation(forward, safeUp);
right = q * Vector3.right;
up = q * Vector3.up;
}
LookRotation
はforward ≈ up
で不安定になり得るためsafeUp
を用意。
6) 斜面に沿ってスライドさせる方向
- 何が決まる? 接地面上の接線方向(移動ベクトル)。
- どんなシーン? 自然な滑り、段差回避の流し込み。
Vector3 moveDir = Vector3.ProjectOnPlane(forward, groundNormal).normalized;
controller.Move(moveDir * speed * Time.deltaTime);
ProjectOnPlane(v, n)
は「法線 n を除去」。正規化してスカラー速度を掛ける。
7) 点と直線の距離(3D)
- 何が決まる? 最短距離(スカラー)。
- どんなシーン? レーザーからの距離評価、IK距離コスト、近接イベント。
public static float DistancePointToLine(Vector3 p, Vector3 a, Vector3 dir)
{
if (dir.sqrMagnitude < 1e-8f) return 0f;
Vector3 pa = p - a;
return Vector3.Cross(pa, dir).magnitude / dir.magnitude;
}
|pa × dir| / |dir|
。dir
が極小だと不安定なのでガード。
Unityの便利メソッドで書くなら(投影差分)
public static float DistancePointToLine_Project(Vector3 p, Vector3 a, Vector3 dir)
{
if (dir.sqrMagnitude < 1e-8f) return 0f;
Vector3 pa = p - a;
Vector3 along = Vector3.Project(pa, dir);
Vector3 perp = pa - along;
return perp.magnitude;
}
投影して直交成分の長さを測るアプローチ。数値的にも直感的。
8) 回転軸の算出(From→To)
- 何が決まる? 最小回転の軸ベクトル(と角度)。
- どんなシーン? LookAtの補正、ジョイント回転、補間の基準軸。
public static Vector3 RotationAxis(Vector3 from, Vector3 to)
{
Vector3 a = from.normalized, b = to.normalized;
return Vector3.Cross(a, b); // 平行/反平行では ~zero
}
平行/反平行で軸がゼロに近づく。閾値以下のときのフォールバック(任意軸)を設計。
Unityの便利メソッドで書くなら
public static void FromTo_AngleAxis(Vector3 from, Vector3 to, out float angleDeg, out Vector3 axis)
{
Quaternion q = Quaternion.FromToRotation(from, to);
q.ToAngleAxis(out angleDeg, out axis);
}
FromToRotation
は最小回転を返す。角度と軸で後段へ渡せる。
9) 軸計算:Right = Up × Forward
- 何が決まる? カメラの右(横)方向ベクトル。
- どんなシーン? ストレイフ、カメラ操作、スクリーンスペース投影。
Vector3 right = Vector3.Cross(Vector3.up, cameraForward).normalized;
// 代替: Quaternion.LookRotation(cameraForward, Vector3.up) * Vector3.right
Cross(up, forward)
の順序に注意(左手系)。用途ごとに順序を固定しておくと符号混乱を防げる。
10) トルクの向き(r × F)
- 何が決まる? 回転方向ベクトル(外積の向き)。
- どんなシーン? ドアの開閉、車輪/プロペラの回転、物理挙動の可視化。
Vector3 Torque(Vector3 r, Vector3 F) => Vector3.Cross(r, F);
// 使用例(力点と力で剛体を回す)
var rb = GetComponent<Rigidbody>();
rb.AddForceAtPosition(force, position);
// 向きのデバッグ(CM基準のトルク方向)
Vector3 torqueDir = Vector3.Cross(position - rb.worldCenterOfMass, force);
r × F
の符号は引数順に依存。可視化用にギズモ矢印を出すとデバッグしやすい。
11) 2Dでの90°回転ベクトル
- 何が決まる? 入力に直交する接ベクトル/法線。
- どんなシーン? 2D壁沿い移動、ポリゴン辺の法線生成、スクロール方向変換。
public static Vector2 PerpCCW(Vector2 v) => new Vector2(-v.y, v.x);
public static Vector2 PerpCW (Vector2 v) => new Vector2( v.y, -v.x);
スクリーン座標(+Y上)では
PerpCCW
が「反時計回りに90°」。
Unityの便利メソッドで書くなら
Vector2 n = Vector2.Perpendicular(v); // 反時計回りに90°
Perpendicular
の向きは CCW(反時計回り)。右/左の定義と混同しない。
API早見表(外積 ⇔ Unity標準)
目的/用途 | 数式・概念 | Unity標準API | 注意点 |
---|---|---|---|
右/左判定(XZ, up=Y) | 右: Cross(f,t).y > 0 / 左: < 0
|
右: SignedAngle(f,t,up) < 0 / 左: > 0
|
XZ投影、up を明示。符号は from/to/axis 前提に依存。 |
法線ベクトル | n = Cross(b-a, c-a) |
new Plane(a,b,c).normal |
頂点順序で法線向きが決まる。 |
三角形の面積 | 0.5 * |Cross(u,v)| |
0.5 * |u||v| * sinθ |
同値。外積ノルムの方が実装簡潔。 |
点と直線の距離 | |Cross(p-a, d)| / |d| |
|(p-a) - Project(p-a,d)| |
d が微小/ゼロでないかチェック。 |
回転軸の算出 | axis = Cross(from̂, tô) |
FromToRotation → ToAngleAxis |
平行/反平行時のフォールバックを用意。 |
基底構築 | right = Cross(up, f) |
LookRotation(f, up) / OrthoNormalize
|
forward ≈ up 特異ケースに注意。 |
パフォーマンスと落とし穴
-
ゼロ/平行チェック:
Cross
が微小なら符号や正規化が不安定。閾値分岐を。 -
正規化は必要な時だけ:多用時は
sqrMagnitude
比較を活用。 - 座標系の前提:左右判定では Up を何に取るか を明示(傾き/BANKでの意図ズレを防ぐ)。
-
便利メソッドの活用:
SignedAngle / ProjectOnPlane / LookRotation / OrthoNormalize
は堅実(ただし特異ケース注意)。
まとめ
- 外積は“符号付きの向き”と“平面幾何”の即戦力。
- 直感重視なら
Cross
、安定重視や読みやすさなら便利メソッドも併用。 - ゼロ/平行/Up軸前提の取り扱い徹底で堅牢で読みやすい実装に。