第2章:MiniLibXグラフィックスプログラミング
2.1 MiniLibXとは
2.1.1 グラフィックスライブラリの役割
アプリケーションがウィンドウを表示し、ユーザーからの入力を受け取るためには、オペレーティングシステム(OS)と通信する必要があります。しかし、OSのAPIは複雑で、プラットフォームごとに異なります。
OSとアプリケーションの関係:
┌───────────────────────────────────────────────────────┐
│ アプリケーション (FdF) │
├───────────────────────────────────────────────────────┤
│ MiniLibX │
├────────────────┬──────────────────┬──────────────────┤
│ macOS │ Linux │ Windows │
│ (Cocoa) │ (X11/Wayland) │ (WinAPI) │
├────────────────┴──────────────────┴──────────────────┤
│ オペレーティングシステム │
├───────────────────────────────────────────────────────┤
│ ハードウェア │
│ (GPU, ディスプレイ, キーボード) │
└───────────────────────────────────────────────────────┘
MiniLibXの役割:
- OS間の差異を吸収
- シンプルなAPIを提供
- 42のプロジェクトに最適化
2.1.2 MiniLibXの設計思想
MiniLibXは、42の教育目的のために設計されたシンプルなグラフィックスライブラリです。
MiniLibXの特徴:
✅ シンプルなAPI
- 学習曲線が緩やか
- 必要最小限の関数
✅ クロスプラットフォーム
- macOS(42 Parisオリジナル)
- Linux X11版
- OpenGL版(新しい)
✅ 教育目的
- グラフィックスの基礎を学ぶ
- 低レベル操作の理解
❌ 制限事項
- 高度な機能は限定的
- パフォーマンス最適化は自己責任
- ドキュメントが少ない
2.1.3 代替ライブラリとの比較
グラフィックスライブラリの比較:
┌──────────────┬─────────────┬────────────┬────────────┐
│ ライブラリ │ 複雑さ │ 機能性 │ 42適合度 │
├──────────────┼─────────────┼────────────┼────────────┤
│ MiniLibX │ ⭐ │ ⭐⭐ │ ⭐⭐⭐⭐⭐ │
│ SDL2 │ ⭐⭐⭐ │ ⭐⭐⭐⭐ │ ⭐⭐⭐ │
│ OpenGL │ ⭐⭐⭐⭐⭐ │ ⭐⭐⭐⭐⭐ │ ⭐⭐ │
│ SFML │ ⭐⭐⭐ │ ⭐⭐⭐⭐ │ ❌ │
└──────────────┴─────────────┴────────────┴────────────┘
42のプロジェクトでは MiniLibX の使用が必須
(一部ボーナスでSDL2が許可される場合あり)
2.2 MiniLibXのセットアップ
2.2.1 インストールと設定
# macOS(42のMac)
# 通常はプリインストール済み
# ヘッダー: /usr/local/include/mlx.h
# ライブラリ: /usr/local/lib/libmlx.a
# Linux
# リポジトリからクローン
git clone https://github.com/42Paris/minilibx-linux.git
cd minilibx-linux
make
# 必要なパッケージ(Debian/Ubuntu)
sudo apt-get install libx11-dev libxext-dev libbsd-dev
2.2.2 Makefileの設定
# 基本的なMakefile
NAME = fdf
# ソースファイル
SRCS = main.c \
parse.c \
draw.c \
transform.c
OBJS = $(SRCS:.c=.o)
# コンパイラとフラグ
CC = cc
CFLAGS = -Wall -Wextra -Werror
# MiniLibX設定(macOS)
MLX_DIR = minilibx
MLX = $(MLX_DIR)/libmlx.a
MLX_FLAGS = -L$(MLX_DIR) -lmlx -framework OpenGL -framework AppKit
# MiniLibX設定(Linux)
# MLX_FLAGS = -L$(MLX_DIR) -lmlx -lX11 -lXext -lm
# インクルードパス
INCLUDES = -I$(MLX_DIR) -Iincludes
# ターゲット
all: $(NAME)
$(NAME): $(MLX) $(OBJS)
$(CC) $(CFLAGS) $(OBJS) $(MLX_FLAGS) -o $(NAME)
$(MLX):
make -C $(MLX_DIR)
%.o: %.c
$(CC) $(CFLAGS) $(INCLUDES) -c ___CODE_BLOCK_4___lt; -o $@
clean:
rm -f $(OBJS)
make -C $(MLX_DIR) clean
fclean: clean
rm -f $(NAME)
re: fclean all
.PHONY: all clean fclean re
2.2.3 ヘッダーファイルの構成
/* fdf.h */
#ifndef FDF_H
# define FDF_H
# include <mlx.h>
# include <stdlib.h>
# include <fcntl.h>
# include <unistd.h>
# include <math.h>
/* 定数定義 */
# define WIN_WIDTH 1920
# define WIN_HEIGHT 1080
# define WIN_TITLE "FdF - Wireframe Viewer"
/* キーコード(macOS) */
# define KEY_ESC 53
# define KEY_W 13
# define KEY_A 0
# define KEY_S 1
# define KEY_D 2
# define KEY_Q 12
# define KEY_E 14
# define KEY_PLUS 24
# define KEY_MINUS 27
# define KEY_UP 126
# define KEY_DOWN 125
# define KEY_LEFT 123
# define KEY_RIGHT 124
/* キーコード(Linux) */
/*
# define KEY_ESC 65307
# define KEY_W 119
...
*/
/* 構造体定義 */
typedef struct s_img
{
void *ptr; /* イメージポインタ */
char *addr; /* ピクセルデータアドレス */
int bits_per_pixel; /* 1ピクセルのビット数 */
int line_length; /* 1行のバイト数 */
int endian; /* エンディアン */
} t_img;
typedef struct s_fdf
{
void *mlx; /* MLXインスタンス */
void *win; /* ウィンドウ */
t_img img; /* 描画用イメージ */
t_map *map; /* マップデータ */
t_camera cam; /* カメラ設定 */
} t_fdf;
/* 関数プロトタイプ */
int init_fdf(t_fdf *fdf);
void cleanup_fdf(t_fdf *fdf);
int key_handler(int keycode, t_fdf *fdf);
int close_handler(t_fdf *fdf);
void render(t_fdf *fdf);
#endif
2.3 ウィンドウとイメージの管理
2.3.1 MLX初期化の流れ
/*
* MiniLibXの初期化シーケンス
*
* 1. mlx_init() - MLXインスタンス作成
* 2. mlx_new_window() - ウィンドウ作成
* 3. mlx_new_image() - イメージバッファ作成
* 4. イベントフック設定
* 5. mlx_loop() - イベントループ開始
*/
int init_fdf(t_fdf *fdf)
{
/* MLXインスタンスの初期化 */
fdf->mlx = mlx_init();
if (!fdf->mlx)
return (error_exit("MLX initialization failed"));
/* ウィンドウ作成 */
fdf->win = mlx_new_window(fdf->mlx, WIN_WIDTH, WIN_HEIGHT, WIN_TITLE);
if (!fdf->win)
return (error_exit("Window creation failed"));
/* イメージバッファ作成 */
fdf->img.ptr = mlx_new_image(fdf->mlx, WIN_WIDTH, WIN_HEIGHT);
if (!fdf->img.ptr)
return (error_exit("Image creation failed"));
/* イメージデータへのアクセス情報取得 */
fdf->img.addr = mlx_get_data_addr(
fdf->img.ptr,
&fdf->img.bits_per_pixel,
&fdf->img.line_length,
&fdf->img.endian
);
return (0);
}
2.3.2 イメージバッファの理解
mlx_get_data_addr() の返り値:
┌─────────────────────────────────────────────────────────┐
│ イメージメモリ │
├─────────────────────────────────────────────────────────┤
│ Line 0 (y=0): │
│ ┌────┬────┬────┬────┬─────────────────────┬────────────┤
│ │P0,0│P1,0│P2,0│P3,0│ ... │ パディング │
│ └────┴────┴────┴────┴─────────────────────┴────────────┤
│ ←───────────── line_length (バイト) ──────────────────→│
├─────────────────────────────────────────────────────────┤
│ Line 1 (y=1): │
│ ┌────┬────┬────┬────┬─────────────────────┬────────────┤
│ │P0,1│P1,1│P2,1│P3,1│ ... │ パディング │
│ └────┴────┴────┴────┴─────────────────────┴────────────┤
│ ... │
└─────────────────────────────────────────────────────────┘
各ピクセル (32ビットカラーの場合):
┌────────┬────────┬────────┬────────┐
│ B │ G │ R │ A │ ← リトルエンディアン
└────────┴────────┴────────┴────────┘
byte 0 byte 1 byte 2 byte 3
bits_per_pixel = 32 (4バイト × 8)
endian = 0 (リトルエンディアン) or 1 (ビッグエンディアン)
2.3.3 ピクセル描画関数
/*
* my_mlx_pixel_put - イメージバッファにピクセルを描画
*
* mlx_pixel_put() より高速な理由:
* - mlx_pixel_put(): 毎回ウィンドウに直接描画 → 遅い
* - my_mlx_pixel_put(): メモリに書き込み → 高速
*
* 最後に mlx_put_image_to_window() で一括転送
*/
void my_mlx_pixel_put(t_img *img, int x, int y, int color)
{
char *dst;
/* 画面外チェック(重要!) */
if (x < 0 || x >= WIN_WIDTH || y < 0 || y >= WIN_HEIGHT)
return ;
/* ピクセルのアドレス計算 */
dst = img->addr + (y * img->line_length + x * (img->bits_per_pixel / 8));
/* 色を書き込み */
*(unsigned int *)dst = color;
}
/*
* アドレス計算の詳細:
*
* y * line_length: y行目の先頭アドレスへのオフセット
* x * (bpp / 8): x列目のピクセルへのオフセット
*
* 例: (100, 50) のピクセル、line_length=7680, bpp=32
* offset = 50 * 7680 + 100 * 4
* = 384000 + 400
* = 384400 バイト目
*/
2.3.4 画面クリア
/*
* clear_image - イメージを背景色でクリア
*
* 方法1: memset使用(最速)
*/
void clear_image(t_img *img, int color)
{
int i;
int total_pixels;
total_pixels = WIN_WIDTH * WIN_HEIGHT;
i = 0;
while (i < total_pixels)
{
((unsigned int *)img->addr)[i] = color;
i++;
}
}
/*
* 方法2: ft_bzeroを使用(黒でクリアする場合)
*/
void clear_image_black(t_img *img)
{
ft_bzero(img->addr, WIN_HEIGHT * img->line_length);
}
/*
* 方法3: 各ピクセルをループ(遅いが分かりやすい)
*/
void clear_image_loop(t_img *img, int color)
{
int x;
int y;
y = 0;
while (y < WIN_HEIGHT)
{
x = 0;
while (x < WIN_WIDTH)
{
my_mlx_pixel_put(img, x, y, color);
x++;
}
y++;
}
}
2.4 イベント処理
2.4.1 イベント駆動プログラミング
イベント駆動モデル:
従来の逐次処理:
┌────────────────────────────────────────┐
│ main() { │
│ initialize(); │
│ while (running) { │
│ process_input(); ← 入力待ち │
│ update(); │
│ render(); │
│ } │
│ } │
└────────────────────────────────────────┘
イベント駆動処理(MiniLibX):
┌────────────────────────────────────────┐
│ main() { │
│ initialize(); │
│ setup_hooks(); ← コールバック登録│
│ mlx_loop(); ← イベント待機 │
│ } (無限ループ) │
│ │
│ key_handler() { ← イベント発生時 │
│ handle_key(); 呼び出される │
│ } │
└────────────────────────────────────────┘
2.4.2 イベントフックの設定
/*
* イベントハンドラの登録
*/
void setup_hooks(t_fdf *fdf)
{
/* キー押下イベント */
mlx_key_hook(fdf->win, key_handler, fdf);
/* ウィンドウ閉じるイベント */
mlx_hook(fdf->win, 17, 0, close_handler, fdf);
/* ループ毎の処理(アニメーション用) */
mlx_loop_hook(fdf->mlx, render_loop, fdf);
/* マウスイベント(オプション) */
mlx_mouse_hook(fdf->win, mouse_handler, fdf);
}
/*
* イベント番号(X11):
* 2 = KeyPress
* 3 = KeyRelease
* 4 = ButtonPress
* 5 = ButtonRelease
* 6 = MotionNotify
* 17 = DestroyNotify (ウィンドウ閉じる)
*/
2.4.3 キーボードハンドラ
/*
* key_handler - キー入力の処理
*
* @keycode: 押されたキーのコード
* @fdf: FdFの状態構造体
* @return: 0(継続)
*/
int key_handler(int keycode, t_fdf *fdf)
{
/* ESCキーで終了 */
if (keycode == KEY_ESC)
return (close_handler(fdf));
/* 回転操作 */
if (keycode == KEY_W)
fdf->cam.rot_x += 0.1;
else if (keycode == KEY_S)
fdf->cam.rot_x -= 0.1;
else if (keycode == KEY_A)
fdf->cam.rot_y -= 0.1;
else if (keycode == KEY_D)
fdf->cam.rot_y += 0.1;
else if (keycode == KEY_Q)
fdf->cam.rot_z -= 0.1;
else if (keycode == KEY_E)
fdf->cam.rot_z += 0.1;
/* ズーム操作 */
else if (keycode == KEY_PLUS)
fdf->cam.zoom *= 1.1;
else if (keycode == KEY_MINUS)
fdf->cam.zoom /= 1.1;
/* 移動操作 */
else if (keycode == KEY_UP)
fdf->cam.offset_y -= 10;
else if (keycode == KEY_DOWN)
fdf->cam.offset_y += 10;
else if (keycode == KEY_LEFT)
fdf->cam.offset_x -= 10;
else if (keycode == KEY_RIGHT)
fdf->cam.offset_x += 10;
/* 再描画 */
render(fdf);
return (0);
}
2.4.4 クリーンアップ処理
/*
* close_handler - ウィンドウ閉じる時の処理
*
* メモリリークを防ぐため、確実にリソースを解放
*/
int close_handler(t_fdf *fdf)
{
/* マップデータの解放 */
if (fdf->map)
free_map(fdf->map);
/* イメージの解放 */
if (fdf->img.ptr)
mlx_destroy_image(fdf->mlx, fdf->img.ptr);
/* ウィンドウの解放 */
if (fdf->win)
mlx_destroy_window(fdf->mlx, fdf->win);
/* MLXの終了処理(Linux版の場合) */
#ifdef __linux__
if (fdf->mlx)
mlx_destroy_display(fdf->mlx);
#endif
/* プログラム終了 */
exit(0);
return (0);
}
/*
* 注意: mlx_destroy_display() はLinux版のみ存在
* macOS版では不要(かつ関数が存在しない)
*
* 条件付きコンパイルで対応:
* #ifdef __linux__
* linux_specific_code();
* #elif __APPLE__
* macos_specific_code();
* #endif
*/
2.5 ライン描画アルゴリズム
2.5.1 なぜラインアルゴリズムが必要か
ピクセルは離散的(整数座標)ですが、数学的な直線は連続的です。この差を埋めるのがライン描画アルゴリズムです。
理想の直線 vs ピクセル表現:
理想(連続):
╱
╱───────────────
ピクセル(離散):
■
■
■
■
■
■
問題: どのピクセルを点灯させるか?
2.5.2 DDAアルゴリズム
DDA(Digital Differential Analyzer)は、最も直感的なライン描画アルゴリズムです。
DDAアルゴリズムの原理:
始点 (x0, y0) から終点 (x1, y1) への線分:
1. 差分を計算:
dx = x1 - x0
dy = y1 - y0
2. ステップ数を決定:
steps = max(|dx|, |dy|)
3. 増分を計算:
x_inc = dx / steps
y_inc = dy / steps
4. ピクセルを描画:
for i in 0..steps:
plot(round(x), round(y))
x += x_inc
y += y_inc
視覚化:
(0,0) (10,4)
●─────────────────────────────●
↘ x_inc = 1.0
↘ y_inc = 0.4
↘
↘
↘──────────────────→
/*
* DDAアルゴリズムの実装
*/
void draw_line_dda(t_img *img, t_point p0, t_point p1)
{
float dx;
float dy;
float x;
float y;
int steps;
int i;
dx = p1.x - p0.x;
dy = p1.y - p0.y;
/* ステップ数は大きい方の差分 */
if (fabs(dx) > fabs(dy))
steps = fabs(dx);
else
steps = fabs(dy);
/* 増分計算 */
float x_inc = dx / (float)steps;
float y_inc = dy / (float)steps;
x = p0.x;
y = p0.y;
i = 0;
while (i <= steps)
{
my_mlx_pixel_put(img, round(x), round(y), p0.color);
x += x_inc;
y += y_inc;
i++;
}
}
2.5.3 ブレゼンハムのアルゴリズム
ブレゼンハムのアルゴリズムは、浮動小数点演算を使わずに高速にラインを描画します。
ブレゼンハムの原理(傾き0〜1の場合):
各xステップで、yを増やすかどうかを判断:
●──────● (x+1, y+1)
╱│ ╱│
╱ │ ╱ │
╱ │ ╱ │
●───┼──● │ ← 理想の直線
│ │ │ │
│ │ │ │
│ ●──│───● (x+1, y)
│ ╱ │ ╱
● ╱ ● ╱
(x,y) 判断ポイント
決定パラメータ d:
d > 0 → 上のピクセル (y+1)
d ≤ 0 → 下のピクセル (y)
/*
* ブレゼンハムのラインアルゴリズム
*
* 利点: 整数演算のみで高速
*/
void draw_line_bresenham(t_img *img, t_point p0, t_point p1)
{
int dx = abs(p1.x - p0.x);
int dy = abs(p1.y - p0.y);
int sx = (p0.x < p1.x) ? 1 : -1; /* x方向 */
int sy = (p0.y < p1.y) ? 1 : -1; /* y方向 */
int err = dx - dy;
int x = p0.x;
int y = p0.y;
while (1)
{
my_mlx_pixel_put(img, x, y, p0.color);
if (x == p1.x && y == p1.y)
break ;
int e2 = 2 * err;
if (e2 > -dy)
{
err -= dy;
x += sx;
}
if (e2 < dx)
{
err += dx;
y += sy;
}
}
}
2.5.4 グラデーション付きライン描画
FdFでは、高さに応じて色が変化するラインが必要です。
/*
* 色の線形補間
*
* ratio: 0.0(始点)〜 1.0(終点)
*/
int interpolate_color(int color1, int color2, float ratio)
{
int r1, g1, b1;
int r2, g2, b2;
int r, g, b;
/* 色成分の分解 */
r1 = (color1 >> 16) & 0xFF;
g1 = (color1 >> 8) & 0xFF;
b1 = color1 & 0xFF;
r2 = (color2 >> 16) & 0xFF;
g2 = (color2 >> 8) & 0xFF;
b2 = color2 & 0xFF;
/* 線形補間 */
r = r1 + (r2 - r1) * ratio;
g = g1 + (g2 - g1) * ratio;
b = b1 + (b2 - b1) * ratio;
return ((r << 16) | (g << 8) | b);
}
/*
* グラデーション付きライン描画
*/
void draw_line_gradient(t_img *img, t_point p0, t_point p1)
{
int dx = abs(p1.x - p0.x);
int dy = abs(p1.y - p0.y);
int steps = (dx > dy) ? dx : dy;
float x_inc = (float)(p1.x - p0.x) / steps;
float y_inc = (float)(p1.y - p0.y) / steps;
float x = p0.x;
float y = p0.y;
int i = 0;
while (i <= steps)
{
float ratio = (float)i / steps;
int color = interpolate_color(p0.color, p1.color, ratio);
my_mlx_pixel_put(img, round(x), round(y), color);
x += x_inc;
y += y_inc;
i++;
}
}
2.6 レンダリングパイプライン
2.6.1 描画の流れ
FdFレンダリングパイプライン:
┌───────────────────────────────────────────────────────┐
│ 1. マップデータ読み込み │
│ ┌─────────────────────────────────────────────────┐│
│ │ 0 0 0 0 0 ││
│ │ 0 1 2 1 0 ││
│ │ 0 2 4 2 0 → t_point配列に格納 ││
│ │ 0 1 2 1 0 ││
│ └─────────────────────────────────────────────────┘│
├───────────────────────────────────────────────────────┤
│ 2. 座標変換 │
│ ┌─────────────────────────────────────────────────┐│
│ │ 各点に対して: ││
│ │ a) スケーリング(ズーム) ││
│ │ b) 回転(X, Y, Z軸) ││
│ │ c) 投影(アイソメトリック等) ││
│ │ d) 平行移動(画面中心へ) ││
│ └─────────────────────────────────────────────────┘│
├───────────────────────────────────────────────────────┤
│ 3. ワイヤーフレーム描画 │
│ ┌─────────────────────────────────────────────────┐│
│ │ 隣接する点をラインで接続: ││
│ │ - 水平方向: (x,y) → (x+1,y) ││
│ │ - 垂直方向: (x,y) → (x,y+1) ││
│ └─────────────────────────────────────────────────┘│
├───────────────────────────────────────────────────────┤
│ 4. 画面表示 │
│ mlx_put_image_to_window() │
└───────────────────────────────────────────────────────┘
2.6.2 メイン描画関数
/*
* render - メイン描画関数
*
* この関数はイベント発生時に呼び出される
*/
void render(t_fdf *fdf)
{
/* 画面クリア */
clear_image(&fdf->img, 0x1E1E2E); /* 背景色 */
/* ワイヤーフレーム描画 */
draw_wireframe(fdf);
/* UI描画(オプション) */
draw_ui(fdf);
/* 画面に転送 */
mlx_put_image_to_window(fdf->mlx, fdf->win, fdf->img.ptr, 0, 0);
}
/*
* draw_wireframe - ワイヤーフレームを描画
*/
void draw_wireframe(t_fdf *fdf)
{
int x;
int y;
y = 0;
while (y < fdf->map->height)
{
x = 0;
while (x < fdf->map->width)
{
/* 右方向への線 */
if (x < fdf->map->width - 1)
draw_line(fdf, x, y, x + 1, y);
/* 下方向への線 */
if (y < fdf->map->height - 1)
draw_line(fdf, x, y, x, y + 1);
x++;
}
y++;
}
}
2.6.3 座標変換の適用
/*
* project_point - 3D座標を2D画面座標に変換
*/
t_screen_point project_point(t_fdf *fdf, int x, int y)
{
t_screen_point result;
float fx, fy, fz;
float temp;
/* グリッド座標から3D座標へ */
fx = (x - fdf->map->width / 2.0) * fdf->cam.zoom;
fy = (y - fdf->map->height / 2.0) * fdf->cam.zoom;
fz = fdf->map->points[y][x].z * fdf->cam.zoom * 0.1; /* 高さスケール */
/* X軸回転 */
temp = fy;
fy = fy * cos(fdf->cam.rot_x) - fz * sin(fdf->cam.rot_x);
fz = temp * sin(fdf->cam.rot_x) + fz * cos(fdf->cam.rot_x);
/* Y軸回転 */
temp = fx;
fx = fx * cos(fdf->cam.rot_y) + fz * sin(fdf->cam.rot_y);
fz = -temp * sin(fdf->cam.rot_y) + fz * cos(fdf->cam.rot_y);
/* Z軸回転 */
temp = fx;
fx = fx * cos(fdf->cam.rot_z) - fy * sin(fdf->cam.rot_z);
fy = temp * sin(fdf->cam.rot_z) + fy * cos(fdf->cam.rot_z);
/* アイソメトリック投影 */
if (fdf->cam.projection == ISOMETRIC)
{
result.x = (fx - fy) * cos(0.523599); /* cos(30°) */
result.y = fz + (fx + fy) * sin(0.523599); /* sin(30°) */
}
else /* 平行投影 */
{
result.x = fx;
result.y = fy;
}
/* 画面中心へオフセット */
result.x += WIN_WIDTH / 2 + fdf->cam.offset_x;
result.y += WIN_HEIGHT / 2 + fdf->cam.offset_y;
/* 色の設定 */
result.color = get_color(fdf, fdf->map->points[y][x].z);
return (result);
}
2.7 最適化テクニック
2.7.1 ダブルバッファリング
ダブルバッファリングの概念:
シングルバッファ(ちらつきあり):
┌─────────────┐
│ 画面表示 │ ← 直接描画中に表示
│ (描画中) │ → ちらつく
└─────────────┘
ダブルバッファ(ちらつきなし):
┌─────────────┐ ┌─────────────┐
│ バッファA │ │ バッファB │
│ (表示中) │ ←→ │ (描画中) │
└─────────────┘ └─────────────┘
↓
描画完了後に切り替え
MiniLibXでの実現:
- mlx_new_image() でオフスクリーンバッファ作成
- my_mlx_pixel_put() でバッファに描画
- mlx_put_image_to_window() で一括転送
→ 自動的にダブルバッファリングになる
2.7.2 クリッピング
/*
* 画面外のピクセル描画をスキップ
*/
void my_mlx_pixel_put(t_img *img, int x, int y, int color)
{
char *dst;
/* クリッピング: 画面外なら何もしない */
if (x < 0 || x >= WIN_WIDTH || y < 0 || y >= WIN_HEIGHT)
return ;
dst = img->addr + (y * img->line_length + x * (img->bits_per_pixel / 8));
*(unsigned int *)dst = color;
}
/*
* ライン描画時のクリッピング(Cohen-Sutherland)
*
* 完全に画面外のラインは描画をスキップ
*/
int clip_line(t_point *p0, t_point *p1)
{
/* 両端点が画面外の同じ側にある場合 */
if ((p0->x < 0 && p1->x < 0) ||
(p0->x >= WIN_WIDTH && p1->x >= WIN_WIDTH) ||
(p0->y < 0 && p1->y < 0) ||
(p0->y >= WIN_HEIGHT && p1->y >= WIN_HEIGHT))
return (0); /* 描画不要 */
return (1); /* 描画必要 */
}
2.7.3 事前計算
/*
* 変換行列の事前計算
*
* カメラが変更された時のみ行列を再計算
*/
void update_transform_matrix(t_fdf *fdf)
{
t_mat4 scale;
t_mat4 rot_x, rot_y, rot_z;
t_mat4 translate;
t_mat4 projection;
/* 各変換行列を作成 */
scale = mat4_scale(fdf->cam.zoom, fdf->cam.zoom, fdf->cam.zoom);
rot_x = mat4_rotate_x(fdf->cam.rot_x);
rot_y = mat4_rotate_y(fdf->cam.rot_y);
rot_z = mat4_rotate_z(fdf->cam.rot_z);
translate = mat4_translate(
fdf->cam.offset_x + WIN_WIDTH / 2,
fdf->cam.offset_y + WIN_HEIGHT / 2,
0
);
/* 合成行列を計算 */
fdf->transform = mat4_multiply(translate,
mat4_multiply(projection,
mat4_multiply(rot_z,
mat4_multiply(rot_y,
mat4_multiply(rot_x, scale)))));
}
/*
* 描画時は事前計算済み行列を使用
*/
t_screen_point project_point_fast(t_fdf *fdf, int x, int y)
{
t_vec3 point;
t_vec3 result;
point.x = x - fdf->map->width / 2.0;
point.y = y - fdf->map->height / 2.0;
point.z = fdf->map->points[y][x].z;
result = mat4_transform(fdf->transform, point);
return ((t_screen_point){result.x, result.y, get_color(...)});
}
2.8 デバッグとトラブルシューティング
2.8.1 よくある問題
問題1: ウィンドウが真っ黒
原因チェックリスト:
□ mlx_put_image_to_window() を呼んでいるか?
□ 描画座標が画面内に収まっているか?
□ 色が0x000000(黒)になっていないか?
□ イメージが正しく初期化されているか?
デバッグ方法:
- 単純な図形(四角形)を描画してテスト
- printf で座標値を確認
- 固定色(白 0xFFFFFF)でテスト
問題2: セグメンテーション違反
原因チェックリスト:
□ 配列の境界を超えてアクセスしていないか?
□ ポインタがNULLでないか確認しているか?
□ イメージアドレス計算が正しいか?
デバッグ方法:
- Valgrind で実行: valgrind ./fdf map.fdf
- AddressSanitizer: cc -fsanitize=address ...
- gdb でバックトレース確認
問題3: 図形が変な形に見える
原因チェックリスト:
□ 座標変換の順序は正しいか?
□ 角度の単位(度/ラジアン)は正しいか?
□ Y軸の方向(上向き/下向き)を考慮しているか?
デバッグ方法:
- 単純な形(立方体)でテスト
- 変換を一つずつ適用して確認
- 座標値をログ出力
2.8.2 デバッグ用ヘルパー
/*
* デバッグ用: 座標軸を描画
*/
void draw_debug_axes(t_fdf *fdf)
{
t_screen_point origin = project_point(fdf, 0, 0);
t_screen_point x_axis = project_point_raw(fdf, 50, 0, 0);
t_screen_point y_axis = project_point_raw(fdf, 0, 50, 0);
t_screen_point z_axis = project_point_raw(fdf, 0, 0, 50);
/* X軸: 赤 */
draw_line_color(&fdf->img, origin, x_axis, 0xFF0000);
/* Y軸: 緑 */
draw_line_color(&fdf->img, origin, y_axis, 0x00FF00);
/* Z軸: 青 */
draw_line_color(&fdf->img, origin, z_axis, 0x0000FF);
}
/*
* デバッグ用: 情報表示
*/
void draw_debug_info(t_fdf *fdf)
{
char str[100];
sprintf(str, "Rot X: %.2f", fdf->cam.rot_x);
mlx_string_put(fdf->mlx, fdf->win, 10, 20, 0xFFFFFF, str);
sprintf(str, "Rot Y: %.2f", fdf->cam.rot_y);
mlx_string_put(fdf->mlx, fdf->win, 10, 40, 0xFFFFFF, str);
sprintf(str, "Zoom: %.2f", fdf->cam.zoom);
mlx_string_put(fdf->mlx, fdf->win, 10, 60, 0xFFFFFF, str);
}
2.9 本章のまとめ
MiniLibXプログラミングの要点:
┌─────────────────────────────────────────────────────────┐
│ 初期化フロー │
│ mlx_init() → mlx_new_window() → mlx_new_image() │
│ ↓ │
│ mlx_get_data_addr() でピクセルバッファにアクセス │
├─────────────────────────────────────────────────────────┤
│ 描画の基本 │
│ - my_mlx_pixel_put(): 高速なピクセル描画 │
│ - ブレゼンハム: 整数演算による高速ライン描画 │
│ - グラデーション: 色の線形補間 │
├─────────────────────────────────────────────────────────┤
│ イベント処理 │
│ - mlx_key_hook(): キーボード入力 │
│ - mlx_hook(win, 17, ...): ウィンドウ閉じる │
│ - mlx_loop(): イベントループ開始 │
├─────────────────────────────────────────────────────────┤
│ 最適化 │
│ - ダブルバッファリング(自動) │
│ - クリッピングで無駄な描画を削減 │
│ - 変換行列の事前計算 │
└─────────────────────────────────────────────────────────┘
次章では、ファイルパースとマップデータの管理について詳しく学びます。