第2章:線形代数の基礎
はじめに
本章では、レイキャスティングに必要な線形代数の基礎を学びます。
---
1. ベクトル
1.1 ベクトルの定義
ベクトルは、大きさと方向を持つ量です。
typedef struct s_vec2 {
double x;
double y;
} t_vec2;
// ベクトルの作成
t_vec2 vec2_new(double x, double y) {
return (t_vec2){x, y};
}
1.2 ベクトル演算
// 加算
t_vec2 vec2_add(t_vec2 a, t_vec2 b) {
return vec2_new(a.x + b.x, a.y + b.y);
}
// 減算
t_vec2 vec2_sub(t_vec2 a, t_vec2 b) {
return vec2_new(a.x - b.x, a.y - b.y);
}
// スカラー倍
t_vec2 vec2_scale(t_vec2 v, double s) {
return vec2_new(v.x * s, v.y * s);
}
// 長さ(ノルム)
double vec2_length(t_vec2 v) {
return sqrt(v.x * v.x + v.y * v.y);
}
// 正規化(単位ベクトル)
t_vec2 vec2_normalize(t_vec2 v) {
double len = vec2_length(v);
if (len == 0) return vec2_new(0, 0);
return vec2_new(v.x / len, v.y / len);
}
1.3 内積
// 内積
double vec2_dot(t_vec2 a, t_vec2 b) {
return a.x * b.x + a.y * b.y;
}
// 用途:
// - 2つのベクトルの角度を求める
// - 投影の計算
// cos(θ) = (a · b) / (|a| * |b|)
1.4 外積(2D)
// 2D外積(擬似的、結果はスカラー)
double vec2_cross(t_vec2 a, t_vec2 b) {
return a.x * b.y - a.y * b.x;
}
// 用途:
// - 左右の判定
// - 面積の計算
---
2. 回転
2.1 回転行列
2Dでの回転は、以下の行列で表現できます:
|cos(θ) -sin(θ)| |x| |x'|
| | × | | = | |
|sin(θ) cos(θ)| |y| |y'|
2.2 実装
// ベクトルを回転
t_vec2 vec2_rotate(t_vec2 v, double angle) {
double cos_a = cos(angle);
double sin_a = sin(angle);
return vec2_new(
v.x * cos_a - v.y * sin_a,
v.x * sin_a + v.y * cos_a
);
}
// プレイヤーの回転
void rotate_player(t_player *p, double angle) {
// 方向ベクトルを回転
p->dir = vec2_rotate(p->dir, angle);
// カメラ平面も回転
p->plane = vec2_rotate(p->plane, angle);
}
2.3 マウスによる回転
#define MOUSE_SENSITIVITY 0.003
void mouse_move(int x, int y, t_game *game) {
static int prev_x = -1;
if (prev_x == -1) {
prev_x = x;
return;
}
int dx = x - prev_x;
double angle = dx * MOUSE_SENSITIVITY;
rotate_player(&game->player, angle);
prev_x = x;
}
---
3. レイの計算
3.1 カメラ平面
カメラ平面は、視野角(FOV)を決定します:
// FOV 66度の場合(tan(33°) ≈ 0.66)
player.planeX = 0.0;
player.planeY = 0.66;
// 各レイの方向を計算
for (int x = 0; x < screenWidth; x++) {
// カメラ平面上の位置 (-1 to 1)
double cameraX = 2 * x / (double)screenWidth - 1;
// レイの方向
double rayDirX = dirX + planeX * cameraX;
double rayDirY = dirY + planeY * cameraX;
}
3.2 視野角の関係
FOV = 2 * atan(planeLength)
例:
planeLength = 0.66
FOV = 2 * atan(0.66) ≈ 66度
|planeLength| < 1 → FOV < 90度
|planeLength| = 1 → FOV = 90度
|planeLength| > 1 → FOV > 90度
---
4. 距離計算
4.1 ユークリッド距離 vs 垂直距離
ユークリッド距離(使わない):
dist = sqrt((wallX - posX)² + (wallY - posY)²)
問題: 魚眼効果が発生
垂直距離(使う):
perpDist = (mapX - posX + (1 - stepX) / 2) / rayDirX
または
perpDist = (mapY - posY + (1 - stepY) / 2) / rayDirY
4.2 魚眼効果の回避
+--------------------+
| 魚眼効果 |
| /‾‾‾‾‾‾‾‾‾\ |
| | | |
| \_________/ |
| |
+--------------------+
修正後:
+--------------------+
| 正しいレンダリング |
| _______________ |
| | | |
| | | |
| |_______________| |
+--------------------+
垂直距離を使うことで、壁が正しく直線に見える
4.3 実装
double get_perp_wall_dist(t_ray *ray, t_player *player) {
if (ray->side == 0) { // 東西の壁
return (ray->mapX - player->posX +
(1 - ray->stepX) / 2) / ray->dirX;
} else { // 南北の壁
return (ray->mapY - player->posY +
(1 - ray->stepY) / 2) / ray->dirY;
}
}
---
5. 座標変換
5.1 マップ座標とピクセル座標
// マップ座標 → ピクセル座標
int pixelX = (int)(mapX * TILE_SIZE);
int pixelY = (int)(mapY * TILE_SIZE);
// ピクセル座標 → マップ座標
double mapX = (double)pixelX / TILE_SIZE;
double mapY = (double)pixelY / TILE_SIZE;
5.2 画面座標
// 壁の高さを計算
int lineHeight = (int)(screenHeight / perpWallDist);
// 描画開始・終了位置
int drawStart = -lineHeight / 2 + screenHeight / 2;
int drawEnd = lineHeight / 2 + screenHeight / 2;
if (drawStart < 0) drawStart = 0;
if (drawEnd >= screenHeight) drawEnd = screenHeight - 1;
---
6. 三角関数
6.1 基本関数
#include <math.h>
// 正弦・余弦
double sin_val = sin(angle); // ラジアン
double cos_val = cos(angle);
// 逆三角関数
double angle = atan2(y, x); // 4象限対応
double angle = acos(cos_val);
double angle = asin(sin_val);
// tan
double tan_val = tan(angle);
6.2 よく使う値
#define PI 3.14159265358979323846
#define PI_2 (PI / 2) // 90度
#define PI_4 (PI / 4) // 45度
#define DEG_90 PI_2
#define DEG_180 PI
#define DEG_270 (3 * PI / 2)
#define DEG_360 (2 * PI)
---
7. 衝突判定の数学
7.1 点と矩形
bool point_in_rect(double px, double py,
double rx, double ry,
double rw, double rh) {
return px >= rx && px < rx + rw &&
py >= ry && py < ry + rh;
}
7.2 線分と線分
// 2つの線分が交差するか判定
bool lines_intersect(t_vec2 p1, t_vec2 p2,
t_vec2 p3, t_vec2 p4) {
double d1 = direction(p3, p4, p1);
double d2 = direction(p3, p4, p2);
double d3 = direction(p1, p2, p3);
double d4 = direction(p1, p2, p4);
if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) &&
((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) {
return true;
}
return false;
}
double direction(t_vec2 a, t_vec2 b, t_vec2 c) {
return vec2_cross(vec2_sub(c, a), vec2_sub(b, a));
}
---
まとめ
本章で学んだこと:
- ベクトル演算: 加算、減算、内積、外積
- 回転: 回転行列、プレイヤー回転
- レイ計算: カメラ平面、レイ方向
- 距離計算: 垂直距離、魚眼効果の回避
- 座標変換: マップ座標とピクセル座標
次章では、レイキャスティングの実装を学びます。