Math World Lab

ベクトルの内積

2本の矢印の「向きの近さ」を1つの数値にする

Layer 1 — 直感・シミュレーター

上のシミュレーターで青と赤の矢印の先端をドラッグしてみよう。2本が同じ方向を向くほど内積は大きくなり、直角(90°)でちょうど0になり、反対方向を向くと負になる。まずは数式抜きで、この「向きの近さのメーター」としての感覚をつかもう。

a\vec{a} = (3.00, 1.00) — 大きさ 3.16
b\vec{b} = (1.00, 2.00) — 大きさ 2.24
ab\vec{a} \cdot \vec{b} = 5.00
cosθ\cos\theta = 0.71(θ ≈ 45.0°)

↑ 矢印の先端をドラッグして、2本のベクトルの向きと大きさを自由に変えてみよう。内積の値と、なす角の関係を確認できる。

Layer 2 — 定義・受験・証明

定義

2つのベクトル a,b\vec{a}, \vec{b} の内積(ドット積)は、成分計算では次のように定義される。

ab=axbx+ayby\vec{a} \cdot \vec{b} = a_x b_x + a_y b_y

これは幾何的には、なす角 θ\theta を使って次のようにも表せる。

ab=abcosθ\vec{a} \cdot \vec{b} = |\vec{a}||\vec{b}|\cos\theta

この2つの式が一致することが、内積の最も重要な性質である。

公式

ab=abcosθ=axbx+ayby\vec{a} \cdot \vec{b} = |\vec{a}||\vec{b}|\cos\theta = a_x b_x + a_y b_y

証明・導出

余弦定理を使うと成分表示と幾何表示が一致することを示せる。a,b\vec{a}, \vec{b} の始点をそろえたとき、終点間の距離を c=abc = \vec{a} - \vec{b} とすると余弦定理より

c2=a2+b22abcosθ|\vec{c}|^2 = |\vec{a}|^2 + |\vec{b}|^2 - 2|\vec{a}||\vec{b}|\cos\theta

一方で成分計算からも c2=a2+b22(axbx+ayby)|\vec{c}|^2 = |\vec{a}|^2 + |\vec{b}|^2 - 2(a_x b_x + a_y b_y) が導けるため、両辺を比較すると

abcosθ=axbx+ayby|\vec{a}||\vec{b}|\cos\theta = a_x b_x + a_y b_y

が得られ、幾何表示と成分表示が一致することが証明される。

押さえておきたいポイント

  • 内積が正 → 2つのベクトルは90°未満(似た方向)を向いている
  • 内積が0 → 2つのベクトルは直交している(垂直)
  • 内積が負 → 2つのベクトルは90°より開いた方向を向いている
  • cosθ=abab\cos\theta = \dfrac{\vec{a} \cdot \vec{b}}{|\vec{a}||\vec{b}|} より、内積からなす角を逆算できる

Layer 3 — 工学応用・現場実装

ゲーム制作の視野角判定 (FOV: Field of View)

敵キャラクターが「プレイヤーが自分の視野角の中に入っているか」を判定する処理は、内積を使うと数行で書ける。敵の向きベクトルとプレイヤーへの方向ベクトルの内積からなす角の余弦を求め、視野角の半分の余弦と比較するだけでよい。平方根や三角関数を使わずに高速に判定できるのが実務上のポイント。

1type Vec2 = { x: number; y: number };
2
3function normalize(v: Vec2): Vec2 {
4 const len = Math.hypot(v.x, v.y) || 1;
5 return { x: v.x / len, y: v.y / len };
6}
7
8function dot(a: Vec2, b: Vec2): number {
9 return a.x * b.x + a.y * b.y;
10}
11
12/**
13 * 敵の視野角(fovDegrees, 全体角度)の中にターゲットが入っているか判定する。
14 * 平方根や atan2 を避け、cos の比較だけで済ませるのがポイント。
15 */
16function isInFieldOfView(
17 enemyPos: Vec2,
18 enemyFacing: Vec2, // 正規化済みの向きベクトル
19 targetPos: Vec2,
20 fovDegrees: number,
21): boolean {
22 const toTarget = normalize({
23 x: targetPos.x - enemyPos.x,
24 y: targetPos.y - enemyPos.y,
25 });
26
27 const cosTheta = dot(enemyFacing, toTarget);
28 const cosHalfFov = Math.cos((fovDegrees / 2) * (Math.PI / 180));
29
30 // cosThetaが大きいほど「正面に近い」。しきい値以上ならFOV内。
31 return cosTheta >= cosHalfFov;
32}
33
34// 使用例: 敵は右(1,0)向き、視野角90度
35const enemy = { x: 0, y: 0 };
36const facing: Vec2 = { x: 1, y: 0 };
37const player = { x: 5, y: 3 };
38
39console.log(isInFieldOfView(enemy, facing, player, 90)); // true / false