第4章:実装ガイドとベストプラクティス

4.1 プロジェクト構成

4.1.1 ディレクトリ構造

fdf/
├── Makefile
├── includes/
│   └── fdf.h
├── srcs/
│   ├── main.c
│   ├── init.c
│   ├── parse/
│   │   ├── parse_file.c
│   │   ├── parse_line.c
│   │   └── validate.c
│   ├── draw/
│   │   ├── draw.c
│   │   ├── line.c
│   │   └── color.c
│   ├── transform/
│   │   ├── project.c
│   │   ├── rotate.c
│   │   └── matrix.c
│   ├── hooks/
│   │   ├── key_hooks.c
│   │   └── mouse_hooks.c
│   └── utils/
│       ├── error.c
│       └── cleanup.c
├── libft/
│   └── ...
├── minilibx/
│   └── ...
└── maps/
    ├── 42.fdf
    └── test_maps/

4.1.2 ヘッダーファイル

/* fdf.h */
#ifndef FDF_H
# define FDF_H

/*
** ================== インクルード ==================
*/
# include <mlx.h>
# include <stdlib.h>
# include <unistd.h>
# include <fcntl.h>
# include <math.h>
# include "libft.h"

/*
** ================== 定数定義 ==================
*/
# define WIN_WIDTH      1920
# define WIN_HEIGHT     1080
# define WIN_TITLE      "FdF"

/* キーコード(macOS) */
# define KEY_ESC        53
# define KEY_W          13
# define KEY_A          0
# define KEY_S          1
# define KEY_D          2
# define KEY_PLUS       24
# define KEY_MINUS      27
# define KEY_LEFT       123
# define KEY_RIGHT      124
# define KEY_UP         126
# define KEY_DOWN       125

/* デフォルト値 */
# define DEFAULT_ZOOM   20
# define DEFAULT_COLOR  0xFFFFFF
# define BG_COLOR       0x1E1E2E

/* 投影タイプ */
# define ISOMETRIC      0
# define PARALLEL       1
# define PERSPECTIVE    2

/*
** ================== 構造体定義 ==================
*/

/* 3D点 */
typedef struct s_point
{
    int     x;
    int     y;
    int     z;
    int     color;
    int     has_color;
}   t_point;

/* 画面座標 */
typedef struct s_screen
{
    int     x;
    int     y;
    int     color;
}   t_screen;

/* イメージバッファ */
typedef struct s_img
{
    void    *ptr;
    char    *addr;
    int     bpp;
    int     line_len;
    int     endian;
}   t_img;

/* マップ */
typedef struct s_map
{
    int         width;
    int         height;
    int         z_min;
    int         z_max;
    t_point     **points;
}   t_map;

/* カメラ */
typedef struct s_camera
{
    float   rot_x;
    float   rot_y;
    float   rot_z;
    float   zoom;
    int     offset_x;
    int     offset_y;
    int     projection;
    float   z_scale;
}   t_camera;

/* メイン構造体 */
typedef struct s_fdf
{
    void        *mlx;
    void        *win;
    t_img       img;
    t_map       *map;
    t_camera    cam;
}   t_fdf;

/*
** ================== 関数プロトタイプ ==================
*/

/* 初期化 */
int         init_fdf(t_fdf *fdf, char *filename);
void        init_camera(t_camera *cam, t_map *map);

/* パース */
t_map       *parse_map(const char *filename);
int         parse_line(const char *line, int width, t_point *points);
int         validate_file(const char *filename);

/* 描画 */
void        render(t_fdf *fdf);
void        draw_wireframe(t_fdf *fdf);
void        draw_line(t_fdf *fdf, t_screen p0, t_screen p1);
void        my_mlx_pixel_put(t_img *img, int x, int y, int color);

/* 変換 */
t_screen    project(t_fdf *fdf, int x, int y);
void        apply_rotation(float *x, float *y, float *z, t_camera *cam);
void        apply_isometric(float *x, float *y, float z);

/* 色 */
int         get_color(t_fdf *fdf, int z);
int         interpolate_color(int c1, int c2, float ratio);

/* フック */
int         key_hook(int keycode, t_fdf *fdf);
int         close_hook(t_fdf *fdf);

/* ユーティリティ */
void        error_exit(const char *msg);
void        cleanup(t_fdf *fdf);
void        free_map(t_map *map);

#endif

4.2 段階的実装ガイド

4.2.1 ステップ1: 基本フレームワーク

/* main.c - 最小限の動作確認 */
#include "fdf.h"

int main(int argc, char **argv)
{
    t_fdf   fdf;

    if (argc != 2)
    {
        ft_putendl_fd("Usage: ./fdf <filename.fdf>", 2);
        return (1);
    }

    /* MLX初期化 */
    fdf.mlx = mlx_init();
    if (!fdf.mlx)
        error_exit("MLX init failed");

    /* ウィンドウ作成 */
    fdf.win = mlx_new_window(fdf.mlx, WIN_WIDTH, WIN_HEIGHT, WIN_TITLE);
    if (!fdf.win)
        error_exit("Window creation failed");

    /* イメージ作成 */
    fdf.img.ptr = mlx_new_image(fdf.mlx, WIN_WIDTH, WIN_HEIGHT);
    fdf.img.addr = mlx_get_data_addr(fdf.img.ptr, &fdf.img.bpp,
                                      &fdf.img.line_len, &fdf.img.endian);

    /* テスト: 画面中央に点を描画 */
    my_mlx_pixel_put(&fdf.img, WIN_WIDTH / 2, WIN_HEIGHT / 2, 0xFFFFFF);
    mlx_put_image_to_window(fdf.mlx, fdf.win, fdf.img.ptr, 0, 0);

    /* ESCで終了 */
    mlx_hook(fdf.win, 2, 1L << 0, key_hook, &fdf);
    mlx_hook(fdf.win, 17, 0, close_hook, &fdf);

    mlx_loop(fdf.mlx);
    return (0);
}

/* ステップ1の確認事項:
 * □ ウィンドウが表示される
 * □ 中央に白い点が見える
 * □ ESCで終了できる
 * □ ウィンドウ閉じるボタンで終了できる
 */

4.2.2 ステップ2: ライン描画

/* line.c - ブレゼンハムアルゴリズム */

void draw_line(t_fdf *fdf, t_screen p0, t_screen p1)
{
    int     dx;
    int     dy;
    int     sx;
    int     sy;
    int     err;
    int     e2;

    dx = abs(p1.x - p0.x);
    dy = abs(p1.y - p0.y);
    sx = (p0.x < p1.x) ? 1 : -1;
    sy = (p0.y < p1.y) ? 1 : -1;
    err = dx - dy;

    while (1)
    {
        my_mlx_pixel_put(&fdf->img, p0.x, p0.y, p0.color);
        if (p0.x == p1.x && p0.y == p1.y)
            break ;
        e2 = 2 * err;
        if (e2 > -dy)
        {
            err -= dy;
            p0.x += sx;
        }
        if (e2 < dx)
        {
            err += dx;
            p0.y += sy;
        }
    }
}

/* ステップ2の確認:
 * 画面に線を描画するテストコード
 */
void test_line(t_fdf *fdf)
{
    t_screen    p0;
    t_screen    p1;

    p0 = (t_screen){100, 100, 0xFF0000};
    p1 = (t_screen){300, 200, 0x00FF00};
    draw_line(fdf, p0, p1);

    p0 = (t_screen){300, 100, 0x0000FF};
    p1 = (t_screen){100, 300, 0xFFFF00};
    draw_line(fdf, p0, p1);
}

4.2.3 ステップ3: ファイルパース

/* parse_file.c */

t_map *parse_map(const char *filename)
{
    int     fd;
    char    *line;
    t_list  *lines;
    t_map   *map;

    if (!validate_file(filename))
        return (NULL);

    fd = open(filename, O_RDONLY);
    if (fd < 0)
        return (NULL);

    /* 行を収集 */
    lines = NULL;
    while ((line = get_next_line(fd)) != NULL)
        ft_lstadd_back(&lines, ft_lstnew(line));
    close(fd);

    /* マップを構築 */
    map = build_map(lines);

    /* クリーンアップ */
    ft_lstclear(&lines, free);

    return (map);
}

/* ステップ3の確認:
 * マップデータをパースして内容を表示
 */
void test_parse(char *filename)
{
    t_map   *map;
    int     x, y;

    map = parse_map(filename);
    if (!map)
    {
        printf("Parse failed\n");
        return ;
    }

    printf("Map: %d x %d\n", map->width, map->height);
    y = 0;
    while (y < map->height)
    {
        x = 0;
        while (x < map->width)
        {
            printf("%3d ", map->points[y][x].z);
            x++;
        }
        printf("\n");
        y++;
    }
    free_map(map);
}

4.2.4 ステップ4: 座標変換

/* project.c */

t_screen project(t_fdf *fdf, int grid_x, int grid_y)
{
    t_screen    result;
    float       x;
    float       y;
    float       z;

    /* グリッド座標を中心基準に変換 */
    x = (grid_x - fdf->map->width / 2.0) * fdf->cam.zoom;
    y = (grid_y - fdf->map->height / 2.0) * fdf->cam.zoom;
    z = fdf->map->points[grid_y][grid_x].z * fdf->cam.zoom * fdf->cam.z_scale;

    /* 回転適用 */
    apply_rotation(&x, &y, &z, &fdf->cam);

    /* 投影 */
    if (fdf->cam.projection == ISOMETRIC)
        apply_isometric(&x, &y, z);

    /* 画面座標に変換 */
    result.x = (int)x + WIN_WIDTH / 2 + fdf->cam.offset_x;
    result.y = (int)y + WIN_HEIGHT / 2 + fdf->cam.offset_y;
    result.color = get_color(fdf, fdf->map->points[grid_y][grid_x].z);

    return (result);
}

void apply_rotation(float *x, float *y, float *z, t_camera *cam)
{
    float   tmp;

    /* X軸回転 */
    tmp = *y;
    *y = *y * cos(cam->rot_x) - *z * sin(cam->rot_x);
    *z = tmp * sin(cam->rot_x) + *z * cos(cam->rot_x);

    /* Y軸回転 */
    tmp = *x;
    *x = *x * cos(cam->rot_y) + *z * sin(cam->rot_y);
    *z = -tmp * sin(cam->rot_y) + *z * cos(cam->rot_y);

    /* Z軸回転 */
    tmp = *x;
    *x = *x * cos(cam->rot_z) - *y * sin(cam->rot_z);
    *y = tmp * sin(cam->rot_z) + *y * cos(cam->rot_z);
}

void apply_isometric(float *x, float *y, float z)
{
    float   prev_x;
    float   prev_y;

    prev_x = *x;
    prev_y = *y;
    *x = (prev_x - prev_y) * cos(0.523599);  /* cos(30°) */
    *y = -z + (prev_x + prev_y) * sin(0.523599);  /* sin(30°) */
}

4.2.5 ステップ5: ワイヤーフレーム描画

/* draw.c */

void render(t_fdf *fdf)
{
    /* 背景をクリア */
    clear_image(&fdf->img, BG_COLOR);

    /* ワイヤーフレーム描画 */
    draw_wireframe(fdf);

    /* 画面に転送 */
    mlx_put_image_to_window(fdf->mlx, fdf->win, fdf->img.ptr, 0, 0);
}

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_segment(fdf, x, y, x + 1, y);
            /* 下への線 */
            if (y < fdf->map->height - 1)
                draw_segment(fdf, x, y, x, y + 1);
            x++;
        }
        y++;
    }
}

void draw_segment(t_fdf *fdf, int x0, int y0, int x1, int y1)
{
    t_screen    p0;
    t_screen    p1;

    p0 = project(fdf, x0, y0);
    p1 = project(fdf, x1, y1);
    draw_line(fdf, p0, p1);
}

4.3 カメラコントロール

4.3.1 キーボード操作

/* key_hooks.c */

int key_hook(int keycode, t_fdf *fdf)
{
    /* 終了 */
    if (keycode == KEY_ESC)
        return (close_hook(fdf));

    /* 回転 */
    if (keycode == KEY_W)
        fdf->cam.rot_x += 0.05;
    else if (keycode == KEY_S)
        fdf->cam.rot_x -= 0.05;
    else if (keycode == KEY_A)
        fdf->cam.rot_z -= 0.05;
    else if (keycode == KEY_D)
        fdf->cam.rot_z += 0.05;

    /* ズーム */
    else if (keycode == KEY_PLUS)
        fdf->cam.zoom *= 1.1;
    else if (keycode == KEY_MINUS)
        fdf->cam.zoom /= 1.1;

    /* 移動 */
    else if (keycode == KEY_LEFT)
        fdf->cam.offset_x -= 20;
    else if (keycode == KEY_RIGHT)
        fdf->cam.offset_x += 20;
    else if (keycode == KEY_UP)
        fdf->cam.offset_y -= 20;
    else if (keycode == KEY_DOWN)
        fdf->cam.offset_y += 20;

    /* 再描画 */
    render(fdf);
    return (0);
}

/* 追加操作(オプション) */

void handle_projection(int keycode, t_fdf *fdf)
{
    if (keycode == KEY_I)  /* Isometric */
        fdf->cam.projection = ISOMETRIC;
    else if (keycode == KEY_P)  /* Parallel */
        fdf->cam.projection = PARALLEL;
}

void handle_height_scale(int keycode, t_fdf *fdf)
{
    if (keycode == KEY_H)  /* Increase height */
        fdf->cam.z_scale *= 1.1;
    else if (keycode == KEY_L)  /* Decrease height */
        fdf->cam.z_scale /= 1.1;
}

void reset_camera(t_fdf *fdf)
{
    init_camera(&fdf->cam, fdf->map);
}

4.3.2 マウス操作(ボーナス)

/* mouse_hooks.c */

/* マウスボタン定義 */
# define MOUSE_LEFT     1
# define MOUSE_RIGHT    2
# define MOUSE_MIDDLE   3
# define MOUSE_SCROLL_UP    4
# define MOUSE_SCROLL_DOWN  5

typedef struct s_mouse
{
    int     pressed;
    int     x;
    int     y;
}   t_mouse;

int mouse_press(int button, int x, int y, t_fdf *fdf)
{
    if (button == MOUSE_LEFT)
    {
        fdf->mouse.pressed = 1;
        fdf->mouse.x = x;
        fdf->mouse.y = y;
    }
    else if (button == MOUSE_SCROLL_UP)
    {
        fdf->cam.zoom *= 1.1;
        render(fdf);
    }
    else if (button == MOUSE_SCROLL_DOWN)
    {
        fdf->cam.zoom /= 1.1;
        render(fdf);
    }
    return (0);
}

int mouse_release(int button, int x, int y, t_fdf *fdf)
{
    (void)x;
    (void)y;
    if (button == MOUSE_LEFT)
        fdf->mouse.pressed = 0;
    return (0);
}

int mouse_move(int x, int y, t_fdf *fdf)
{
    int     dx;
    int     dy;

    if (fdf->mouse.pressed)
    {
        dx = x - fdf->mouse.x;
        dy = y - fdf->mouse.y;

        fdf->cam.rot_z += dx * 0.01;
        fdf->cam.rot_x += dy * 0.01;

        fdf->mouse.x = x;
        fdf->mouse.y = y;

        render(fdf);
    }
    return (0);
}

4.4 色の実装

4.4.1 高さベースの色付け

/* color.c */

int get_color(t_fdf *fdf, int z)
{
    float   ratio;
    int     range;

    range = fdf->map->z_max - fdf->map->z_min;
    if (range == 0)
        return (DEFAULT_COLOR);

    ratio = (float)(z - fdf->map->z_min) / range;

    /* 低い→高い: 青→緑→黄→赤 */
    if (ratio < 0.25)
        return (interpolate_color(0x0000FF, 0x00FF00, ratio * 4));
    else if (ratio < 0.5)
        return (interpolate_color(0x00FF00, 0xFFFF00, (ratio - 0.25) * 4));
    else if (ratio < 0.75)
        return (interpolate_color(0xFFFF00, 0xFF8000, (ratio - 0.5) * 4));
    else
        return (interpolate_color(0xFF8000, 0xFF0000, (ratio - 0.75) * 4));
}

int interpolate_color(int c1, int c2, float ratio)
{
    int     r;
    int     g;
    int     b;

    if (ratio <= 0)
        return (c1);
    if (ratio >= 1)
        return (c2);

    r = ((c1 >> 16) & 0xFF) + (((c2 >> 16) & 0xFF)
        - ((c1 >> 16) & 0xFF)) * ratio;
    g = ((c1 >> 8) & 0xFF) + (((c2 >> 8) & 0xFF)
        - ((c1 >> 8) & 0xFF)) * ratio;
    b = (c1 & 0xFF) + ((c2 & 0xFF) - (c1 & 0xFF)) * ratio;

    return ((r << 16) | (g << 8) | b);
}

4.4.2 グラデーション付きライン

void draw_line_gradient(t_fdf *fdf, t_screen p0, t_screen p1)
{
    int     steps;
    float   x_inc;
    float   y_inc;
    float   x;
    float   y;
    int     i;

    steps = MAX(abs(p1.x - p0.x), abs(p1.y - p0.y));
    if (steps == 0)
    {
        my_mlx_pixel_put(&fdf->img, p0.x, p0.y, p0.color);
        return ;
    }

    x_inc = (float)(p1.x - p0.x) / steps;
    y_inc = (float)(p1.y - p0.y) / steps;
    x = p0.x;
    y = p0.y;
    i = 0;

    while (i <= steps)
    {
        int color = interpolate_color(p0.color, p1.color, (float)i / steps);
        my_mlx_pixel_put(&fdf->img, (int)x, (int)y, color);
        x += x_inc;
        y += y_inc;
        i++;
    }
}

4.5 エラー処理とクリーンアップ

4.5.1 安全な終了

/* cleanup.c */

void cleanup(t_fdf *fdf)
{
    if (!fdf)
        return ;

    if (fdf->map)
        free_map(fdf->map);

    if (fdf->img.ptr && fdf->mlx)
        mlx_destroy_image(fdf->mlx, fdf->img.ptr);

    if (fdf->win && fdf->mlx)
        mlx_destroy_window(fdf->mlx, fdf->win);

    #ifdef __linux__
    if (fdf->mlx)
        mlx_destroy_display(fdf->mlx);
    free(fdf->mlx);
    #endif
}

int close_hook(t_fdf *fdf)
{
    cleanup(fdf);
    exit(0);
    return (0);
}

void free_map(t_map *map)
{
    int i;

    if (!map)
        return ;

    if (map->points)
    {
        i = 0;
        while (i < map->height)
        {
            if (map->points[i])
                free(map->points[i]);
            i++;
        }
        free(map->points);
    }
    free(map);
}

4.5.2 エラーメッセージ

/* error.c */

void error_exit(const char *msg)
{
    ft_putstr_fd("Error: ", 2);
    ft_putendl_fd(msg, 2);
    exit(1);
}

void *error_null(const char *msg)
{
    ft_putstr_fd("Error: ", 2);
    ft_putendl_fd(msg, 2);
    return (NULL);
}

4.6 Makefile

# **************************************************************************** #
#                                                                              #
#    FdF Makefile                                                             #
#                                                                              #
# **************************************************************************** #

NAME = fdf

# Directories
SRCS_DIR = srcs
OBJS_DIR = objs
INC_DIR = includes
LIBFT_DIR = libft
MLX_DIR = minilibx

# Source files
SRCS = main.c \
       init.c \
       parse/parse_file.c \
       parse/parse_line.c \
       parse/validate.c \
       draw/draw.c \
       draw/line.c \
       draw/color.c \
       transform/project.c \
       transform/rotate.c \
       hooks/key_hooks.c \
       utils/error.c \
       utils/cleanup.c

OBJS = $(addprefix $(OBJS_DIR)/, $(SRCS:.c=.o))

# Compiler and flags
CC = cc
CFLAGS = -Wall -Wextra -Werror
INCLUDES = -I$(INC_DIR) -I$(LIBFT_DIR) -I$(MLX_DIR)

# Libraries
LIBFT = $(LIBFT_DIR)/libft.a
MLX = $(MLX_DIR)/libmlx.a

# OS detection
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
    MLX_FLAGS = -L$(MLX_DIR) -lmlx -lX11 -lXext -lm
else
    MLX_FLAGS = -L$(MLX_DIR) -lmlx -framework OpenGL -framework AppKit
endif

# Colors
GREEN = \033[0;32m
RESET = \033[0m

# Rules
all: $(NAME)

$(NAME): $(LIBFT) $(MLX) $(OBJS)
	@echo "$(GREEN)Linking $(NAME)...$(RESET)"
	@$(CC) $(CFLAGS) $(OBJS) $(LIBFT) $(MLX_FLAGS) -o $(NAME)
	@echo "$(GREEN)Done!$(RESET)"

$(OBJS_DIR)/%.o: $(SRCS_DIR)/%.c
	@mkdir -p $(dir $@)
	@$(CC) $(CFLAGS) $(INCLUDES) -c ___CODE_BLOCK_13___lt; -o $@

$(LIBFT):
	@make -C $(LIBFT_DIR)

$(MLX):
	@make -C $(MLX_DIR)

clean:
	@rm -rf $(OBJS_DIR)
	@make -C $(LIBFT_DIR) clean
	@make -C $(MLX_DIR) clean

fclean: clean
	@rm -f $(NAME)
	@make -C $(LIBFT_DIR) fclean

re: fclean all

.PHONY: all clean fclean re

4.7 評価対策

4.7.1 必須要件チェックリスト

基本機能:
□ 42.fdfマップを正しく表示できる
□ アイソメトリック投影が正しい
□ ワイヤーフレームが表示される
□ ESCキーで終了できる
□ ウィンドウ閉じるボタンで終了

入力処理:
□ 引数エラー(引数なし、複数)に対応
□ 存在しないファイルでエラー表示
□ 不正なファイル形式でエラー表示
□ 空ファイルでエラー表示

メモリ管理:
□ Valgrindでメモリリークなし
□ 正常終了時にリークなし
□ エラー時にリークなし

コード品質:
□ Normチェッククリア
□ 42ヘッダー付き
□ 禁止関数を使用していない

4.7.2 ボーナス要件

ボーナス機能:
□ ズーム(+/-キー)
□ 移動(矢印キー)
□ 回転(複数軸)
□ 投影タイプ切り替え
□ 色のグラデーション
□ マウスでの回転(ドラッグ)
□ マウスホイールでズーム
□ 高さスケール調整

4.7.3 よくある問題と対策

問題1: 大きなマップでクラッシュ
対策:
- メモリ確保のエラーチェック
- 極端なズーム時の数値オーバーフロー対策
- クリッピングの実装

問題2: 回転がおかしい
対策:
- 回転の順序を確認(X→Y→Z等)
- 回転中心が正しいか確認
- ラジアン/度の変換確認

問題3: 色が正しくない
対策:
- エンディアンの確認
- 色成分の順序確認(ARGB vs BGRA)
- 補間計算の確認

問題4: Linux/macの互換性
対策:
- 条件付きコンパイルの使用
- キーコードの分離
- Makefileの分岐

4.8 本章のまとめ

実装の優先順位:

1. 基本動作(必須)
   ├── MLX初期化
   ├── ウィンドウ表示
   ├── ファイルパース
   ├── 座標変換
   └── ワイヤーフレーム描画

2. 操作機能(必須)
   ├── ESC終了
   └── ウィンドウ閉じる

3. インタラクション(ボーナス)
   ├── ズーム
   ├── 移動
   ├── 回転
   └── マウス操作

4. 視覚効果(ボーナス)
   ├── 色のグラデーション
   ├── 投影切り替え
   └── 高さスケール

FdFは、3Dグラフィックスプログラミングの基礎を学ぶ素晴らしいプロジェクトです。数学的な概念をコードに落とし込む経験は、今後のcub3dやminiRTプロジェクトに直結します。