4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Unityで“外積”を使い倒す

Last updated at Posted at 2025-09-08

超要約

  • 外積(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;
}

LookRotationforward ≈ 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軸前提の取り扱い徹底で堅牢で読みやすい実装に。
4
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?