第2章:フラクタル描画の実装
2.1 プロジェクト構成
2.1.1 ディレクトリ構造
fractol/
├── Makefile
├── includes/
│ └── fractol.h
├── srcs/
│ ├── main.c
│ ├── init.c
│ ├── fractals/
│ │ ├── mandelbrot.c
│ │ ├── julia.c
│ │ └── burning_ship.c
│ ├── render/
│ │ ├── render.c
│ │ ├── color.c
│ │ └── pixel.c
│ ├── hooks/
│ │ ├── key_hooks.c
│ │ └── mouse_hooks.c
│ └── utils/
│ ├── error.c
│ ├── parse.c
│ └── cleanup.c
├── libft/
└── minilibx/
2.1.2 ヘッダーファイル
/* fractol.h */
#ifndef FRACTOL_H
# define FRACTOL_H
# include <mlx.h>
# include <stdlib.h>
# include <unistd.h>
# include <math.h>
# include "libft.h"
/*
** ================== 定数 ==================
*/
# define WIN_WIDTH 800
# define WIN_HEIGHT 800
# define WIN_TITLE "fract-ol"
/* フラクタルタイプ */
# define MANDELBROT 1
# define JULIA 2
# define BURNING_SHIP 3
/* キーコード(macOS) */
# define KEY_ESC 53
# define KEY_UP 126
# define KEY_DOWN 125
# define KEY_LEFT 123
# define KEY_RIGHT 124
# define KEY_PLUS 24
# define KEY_MINUS 27
# define KEY_R 15
# define KEY_C 8
# define KEY_1 18
# define KEY_2 19
# define KEY_3 20
/* マウス */
# define MOUSE_SCROLL_UP 4
# define MOUSE_SCROLL_DOWN 5
/* デフォルト値 */
# define DEFAULT_ZOOM 200.0
# define DEFAULT_MAX_ITER 100
# define ZOOM_FACTOR 1.2
/*
** ================== 構造体 ==================
*/
typedef struct s_complex
{
double real;
double imag;
} t_complex;
typedef struct s_img
{
void *ptr;
char *addr;
int bpp;
int line_len;
int endian;
} t_img;
typedef struct s_fractol
{
void *mlx;
void *win;
t_img img;
int type; /* フラクタルタイプ */
double zoom; /* ズームレベル */
double offset_x; /* X方向オフセット */
double offset_y; /* Y方向オフセット */
int max_iter; /* 最大反復回数 */
t_complex julia_c; /* ジュリア集合のパラメータ */
int color_scheme; /* カラースキーム */
} t_fractol;
/*
** ================== 関数プロトタイプ ==================
*/
/* 初期化 */
int init_fractol(t_fractol *f);
void init_defaults(t_fractol *f);
/* フラクタル計算 */
int mandelbrot(double cr, double ci, int max_iter);
int julia(double zr, double zi, t_complex c, int max_iter);
int burning_ship(double cr, double ci, int max_iter);
/* 描画 */
void render(t_fractol *f);
void my_mlx_pixel_put(t_img *img, int x, int y, int color);
/* 色 */
int get_color(int n, int max_iter, int scheme);
int interpolate_color(int c1, int c2, double ratio);
/* フック */
int key_hook(int keycode, t_fractol *f);
int mouse_hook(int button, int x, int y, t_fractol *f);
int close_hook(t_fractol *f);
/* ユーティリティ */
void error_exit(const char *msg);
void print_usage(void);
int parse_args(int argc, char **argv, t_fractol *f);
double ft_atof(const char *str);
#endif
2.2 マンデルブロ集合の実装
2.2.1 コア計算関数
/* mandelbrot.c */
/*
* mandelbrot - 1点のマンデルブロ計算
*
* @cr: 複素数cの実部
* @ci: 複素数cの虚部
* @max_iter: 最大反復回数
* @return: 発散までの反復回数(収束なら max_iter)
*
* 反復式: z_{n+1} = z_n² + c
* 初期値: z_0 = 0
*/
int mandelbrot(double cr, double ci, int max_iter)
{
double zr;
double zi;
double zr_sq;
double zi_sq;
int n;
zr = 0.0;
zi = 0.0;
n = 0;
while (n < max_iter)
{
zr_sq = zr * zr;
zi_sq = zi * zi;
/* 脱出判定: |z|² > 4 */
if (zr_sq + zi_sq > 4.0)
return (n);
/* z = z² + c */
zi = 2.0 * zr * zi + ci;
zr = zr_sq - zi_sq + cr;
n++;
}
return (max_iter); /* 収束(集合内) */
}
/*
* 最適化のポイント:
*
* 1. zr_sq, zi_sq を事前計算
* - 二乗の計算を重複して行わない
* - zr*zr は脱出判定と z² の両方で必要
*
* 2. 脱出判定を先に
* - 早期脱出で無駄な計算を省く
*
* 3. sqrt() を避ける
* - |z| > 2 の代わりに |z|² > 4 で判定
*/
2.2.2 スムース版(滑らかな色付け用)
/*
* mandelbrot_smooth - スムースカラーリング用
*
* @return: 小数点精度の反復回数
*/
double mandelbrot_smooth(double cr, double ci, int max_iter)
{
double zr;
double zi;
double zr_sq;
double zi_sq;
int n;
zr = 0.0;
zi = 0.0;
n = 0;
while (n < max_iter)
{
zr_sq = zr * zr;
zi_sq = zi * zi;
if (zr_sq + zi_sq > 256.0) /* 大きめの閾値 */
{
/* スムージング計算 */
double log_zn = log(zr_sq + zi_sq) / 2.0;
double nu = log(log_zn / log(2.0)) / log(2.0);
return (n + 1.0 - nu);
}
zi = 2.0 * zr * zi + ci;
zr = zr_sq - zi_sq + cr;
n++;
}
return ((double)max_iter);
}
2.3 ジュリア集合の実装
2.3.1 基本実装
/* julia.c */
/*
* julia - 1点のジュリア集合計算
*
* @zr: 初期値zの実部(ピクセル位置から変換)
* @zi: 初期値zの虚部
* @c: ジュリア集合のパラメータ(固定)
* @max_iter: 最大反復回数
*/
int julia(double zr, double zi, t_complex c, int max_iter)
{
double zr_sq;
double zi_sq;
int n;
n = 0;
while (n < max_iter)
{
zr_sq = zr * zr;
zi_sq = zi * zi;
if (zr_sq + zi_sq > 4.0)
return (n);
/* z = z² + c */
zi = 2.0 * zr * zi + c.imag;
zr = zr_sq - zi_sq + c.real;
n++;
}
return (max_iter);
}
2.3.2 美しいパラメータセット
/* 美しいジュリア集合のプリセット */
typedef struct s_preset
{
const char *name;
double real;
double imag;
} t_preset;
static const t_preset g_julia_presets[] = {
{"Dendrite", 0.0, 1.0},
{"Rabbit", -0.123, 0.745},
{"Sea Horse", -0.74543, 0.11301},
{"Siegel Disk", -0.391, -0.587},
{"Spiral", 0.285, 0.01},
{"Star", -0.4, 0.6},
{"Frost", -0.7, 0.27015},
{"Lightning", -0.8, 0.156},
{NULL, 0, 0}
};
void cycle_julia_preset(t_fractol *f)
{
static int index = 0;
f->julia_c.real = g_julia_presets[index].real;
f->julia_c.imag = g_julia_presets[index].imag;
index++;
if (g_julia_presets[index].name == NULL)
index = 0;
}
2.4 バーニングシップの実装
/* burning_ship.c */
/*
* burning_ship - バーニングシップ・フラクタル
*
* 反復式: z_{n+1} = (|Re(z_n)| + i|Im(z_n)|)² + c
*
* マンデルブロとの違い:
* 各成分の絶対値を取ってから二乗する
*/
int burning_ship(double cr, double ci, int max_iter)
{
double zr;
double zi;
double zr_sq;
double zi_sq;
int n;
zr = 0.0;
zi = 0.0;
n = 0;
while (n < max_iter)
{
zr_sq = zr * zr;
zi_sq = zi * zi;
if (zr_sq + zi_sq > 4.0)
return (n);
/* 絶対値を取る */
zr = fabs(zr);
zi = fabs(zi);
/* z = z² + c */
double new_zi = 2.0 * zr * zi + ci;
zr = zr_sq - zi_sq + cr;
zi = new_zi;
n++;
}
return (max_iter);
}
/*
* バーニングシップの表示:
* - Y軸を反転させると「燃える船」の形に見える
* - ci = -ci として計算するか、表示時に反転
*/
2.5 描画エンジン
2.5.1 メインレンダリングループ
/* render.c */
void render(t_fractol *f)
{
int px;
int py;
double cr;
double ci;
int color;
int n;
py = 0;
while (py < WIN_HEIGHT)
{
px = 0;
while (px < WIN_WIDTH)
{
/* ピクセル座標 → 複素座標 */
cr = (px - WIN_WIDTH / 2.0) / f->zoom + f->offset_x;
ci = (py - WIN_HEIGHT / 2.0) / f->zoom + f->offset_y;
/* フラクタル計算 */
n = calculate_point(f, cr, ci);
/* 色の取得と描画 */
color = get_color(n, f->max_iter, f->color_scheme);
my_mlx_pixel_put(&f->img, px, py, color);
px++;
}
py++;
}
mlx_put_image_to_window(f->mlx, f->win, f->img.ptr, 0, 0);
}
int calculate_point(t_fractol *f, double cr, double ci)
{
if (f->type == MANDELBROT)
return (mandelbrot(cr, ci, f->max_iter));
else if (f->type == JULIA)
return (julia(cr, ci, f->julia_c, f->max_iter));
else if (f->type == BURNING_SHIP)
return (burning_ship(cr, -ci, f->max_iter)); /* Y反転 */
return (0);
}
2.5.2 最適化版レンダリング
/* 増分計算による最適化 */
void render_optimized(t_fractol *f)
{
int px;
int py;
double cr;
double ci;
double x_start;
double y_start;
double x_inc;
double y_inc;
/* 増分を事前計算 */
x_inc = 1.0 / f->zoom;
y_inc = 1.0 / f->zoom;
x_start = f->offset_x - (WIN_WIDTH / 2.0) / f->zoom;
y_start = f->offset_y - (WIN_HEIGHT / 2.0) / f->zoom;
ci = y_start;
py = 0;
while (py < WIN_HEIGHT)
{
cr = x_start;
px = 0;
while (px < WIN_WIDTH)
{
int n = calculate_point(f, cr, ci);
int color = get_color(n, f->max_iter, f->color_scheme);
my_mlx_pixel_put(&f->img, px, py, color);
cr += x_inc; /* 乗算→加算 */
px++;
}
ci += y_inc;
py++;
}
mlx_put_image_to_window(f->mlx, f->win, f->img.ptr, 0, 0);
}
2.6 カラーリング
2.6.1 基本カラースキーム
/* color.c */
/* カラースキーム定義 */
# define SCHEME_CLASSIC 0
# define SCHEME_FIRE 1
# define SCHEME_OCEAN 2
# define SCHEME_RAINBOW 3
# define SCHEME_GRAYSCALE 4
int get_color(int n, int max_iter, int scheme)
{
double ratio;
/* 集合内部は黒 */
if (n == max_iter)
return (0x000000);
ratio = (double)n / max_iter;
switch (scheme)
{
case SCHEME_CLASSIC:
return (color_classic(ratio));
case SCHEME_FIRE:
return (color_fire(ratio));
case SCHEME_OCEAN:
return (color_ocean(ratio));
case SCHEME_RAINBOW:
return (color_rainbow(ratio));
case SCHEME_GRAYSCALE:
return (color_grayscale(ratio));
default:
return (color_classic(ratio));
}
}
2.6.2 カラースキーム実装
/* クラシック(青→白) */
int color_classic(double ratio)
{
int r, g, b;
r = (int)(9 * (1 - ratio) * ratio * ratio * ratio * 255);
g = (int)(15 * (1 - ratio) * (1 - ratio) * ratio * ratio * 255);
b = (int)(8.5 * (1 - ratio) * (1 - ratio) * (1 - ratio) * ratio * 255);
return ((r << 16) | (g << 8) | b);
}
/* ファイアー(黒→赤→黄→白) */
int color_fire(double ratio)
{
if (ratio < 0.33)
return (interpolate_color(0x000000, 0xFF0000, ratio * 3));
else if (ratio < 0.66)
return (interpolate_color(0xFF0000, 0xFFFF00, (ratio - 0.33) * 3));
else
return (interpolate_color(0xFFFF00, 0xFFFFFF, (ratio - 0.66) * 3));
}
/* オーシャン(深青→シアン→白) */
int color_ocean(double ratio)
{
if (ratio < 0.5)
return (interpolate_color(0x000033, 0x00FFFF, ratio * 2));
else
return (interpolate_color(0x00FFFF, 0xFFFFFF, (ratio - 0.5) * 2));
}
/* レインボー(HSV変換) */
int color_rainbow(double ratio)
{
double h = ratio * 360.0; /* 色相 */
double s = 1.0; /* 彩度 */
double v = 1.0; /* 明度 */
return (hsv_to_rgb(h, s, v));
}
/* グレースケール */
int color_grayscale(double ratio)
{
int gray = (int)(ratio * 255);
return ((gray << 16) | (gray << 8) | gray);
}
2.6.3 HSV→RGB変換
/*
* HSV to RGB 変換
*
* H: 色相 (0-360)
* S: 彩度 (0-1)
* V: 明度 (0-1)
*/
int hsv_to_rgb(double h, double s, double v)
{
double c;
double x;
double m;
double r, g, b;
int hi;
c = v * s;
hi = (int)(h / 60.0) % 6;
x = c * (1.0 - fabs(fmod(h / 60.0, 2.0) - 1.0));
m = v - c;
if (hi == 0)
{ r = c; g = x; b = 0; }
else if (hi == 1)
{ r = x; g = c; b = 0; }
else if (hi == 2)
{ r = 0; g = c; b = x; }
else if (hi == 3)
{ r = 0; g = x; b = c; }
else if (hi == 4)
{ r = x; g = 0; b = c; }
else
{ r = c; g = 0; b = x; }
return (((int)((r + m) * 255) << 16) |
((int)((g + m) * 255) << 8) |
(int)((b + m) * 255));
}
2.7 インタラクション
2.7.1 ズーム操作
/* mouse_hooks.c */
int mouse_hook(int button, int x, int y, t_fractol *f)
{
double mouse_re;
double mouse_im;
/* マウス位置を複素座標に変換 */
mouse_re = (x - WIN_WIDTH / 2.0) / f->zoom + f->offset_x;
mouse_im = (y - WIN_HEIGHT / 2.0) / f->zoom + f->offset_y;
if (button == MOUSE_SCROLL_UP)
{
/* ズームイン(マウス位置を中心に) */
f->zoom *= ZOOM_FACTOR;
f->offset_x = mouse_re - (x - WIN_WIDTH / 2.0) / f->zoom;
f->offset_y = mouse_im - (y - WIN_HEIGHT / 2.0) / f->zoom;
adjust_iterations(f, 1);
}
else if (button == MOUSE_SCROLL_DOWN)
{
/* ズームアウト */
f->zoom /= ZOOM_FACTOR;
f->offset_x = mouse_re - (x - WIN_WIDTH / 2.0) / f->zoom;
f->offset_y = mouse_im - (y - WIN_HEIGHT / 2.0) / f->zoom;
adjust_iterations(f, -1);
}
render(f);
return (0);
}
/*
* ズームレベルに応じて反復回数を調整
*/
void adjust_iterations(t_fractol *f, int direction)
{
if (direction > 0)
f->max_iter = (int)(f->max_iter * 1.1);
else
f->max_iter = (int)(f->max_iter / 1.1);
/* 範囲制限 */
if (f->max_iter < 50)
f->max_iter = 50;
if (f->max_iter > 5000)
f->max_iter = 5000;
}
2.7.2 キーボード操作
/* key_hooks.c */
int key_hook(int keycode, t_fractol *f)
{
if (keycode == KEY_ESC)
return (close_hook(f));
/* 移動 */
if (keycode == KEY_LEFT)
f->offset_x -= 50.0 / f->zoom;
else if (keycode == KEY_RIGHT)
f->offset_x += 50.0 / f->zoom;
else if (keycode == KEY_UP)
f->offset_y -= 50.0 / f->zoom;
else if (keycode == KEY_DOWN)
f->offset_y += 50.0 / f->zoom;
/* ズーム */
else if (keycode == KEY_PLUS)
{
f->zoom *= ZOOM_FACTOR;
adjust_iterations(f, 1);
}
else if (keycode == KEY_MINUS)
{
f->zoom /= ZOOM_FACTOR;
adjust_iterations(f, -1);
}
/* リセット */
else if (keycode == KEY_R)
init_defaults(f);
/* カラースキーム切り替え */
else if (keycode == KEY_C)
f->color_scheme = (f->color_scheme + 1) % 5;
/* フラクタル切り替え */
else if (keycode == KEY_1)
f->type = MANDELBROT;
else if (keycode == KEY_2)
f->type = JULIA;
else if (keycode == KEY_3)
f->type = BURNING_SHIP;
render(f);
return (0);
}
2.7.3 ジュリアのマウス追従(ボーナス)
/* ジュリアパラメータをマウスで操作 */
int mouse_move_hook(int x, int y, t_fractol *f)
{
if (f->type != JULIA)
return (0);
/* マウス位置をパラメータに変換 */
f->julia_c.real = (x - WIN_WIDTH / 2.0) / (WIN_WIDTH / 4.0);
f->julia_c.imag = (y - WIN_HEIGHT / 2.0) / (WIN_HEIGHT / 4.0);
render(f);
return (0);
}
/* フック登録 */
void setup_mouse_move(t_fractol *f)
{
mlx_hook(f->win, 6, 1L << 6, mouse_move_hook, f);
}
2.8 引数処理
2.8.1 コマンドライン解析
/* parse.c */
int parse_args(int argc, char **argv, t_fractol *f)
{
if (argc < 2)
{
print_usage();
return (0);
}
/* フラクタルタイプの判定 */
if (ft_strcmp(argv[1], "mandelbrot") == 0)
f->type = MANDELBROT;
else if (ft_strcmp(argv[1], "julia") == 0)
{
f->type = JULIA;
if (!parse_julia_params(argc, argv, f))
return (0);
}
else if (ft_strcmp(argv[1], "burningship") == 0 ||
ft_strcmp(argv[1], "burning_ship") == 0)
f->type = BURNING_SHIP;
else
{
ft_putendl_fd("Error: Unknown fractal type", 2);
print_usage();
return (0);
}
return (1);
}
int parse_julia_params(int argc, char **argv, t_fractol *f)
{
/* デフォルトのジュリアパラメータ */
f->julia_c.real = -0.7;
f->julia_c.imag = 0.27015;
/* カスタムパラメータが指定された場合 */
if (argc >= 4)
{
f->julia_c.real = ft_atof(argv[2]);
f->julia_c.imag = ft_atof(argv[3]);
/* 範囲チェック */
if (fabs(f->julia_c.real) > 2.0 || fabs(f->julia_c.imag) > 2.0)
{
ft_putendl_fd("Error: Julia parameters out of range (-2 to 2)", 2);
return (0);
}
}
return (1);
}
2.8.2 使用方法表示
void print_usage(void)
{
ft_putendl_fd("\nUsage:", 1);
ft_putendl_fd(" ./fractol <fractal_type> [options]\n", 1);
ft_putendl_fd("Fractal types:", 1);
ft_putendl_fd(" mandelbrot - Mandelbrot set", 1);
ft_putendl_fd(" julia [re] [im] - Julia set (optional params)", 1);
ft_putendl_fd(" burningship - Burning Ship fractal\n", 1);
ft_putendl_fd("Examples:", 1);
ft_putendl_fd(" ./fractol mandelbrot", 1);
ft_putendl_fd(" ./fractol julia", 1);
ft_putendl_fd(" ./fractol julia -0.7 0.27015", 1);
ft_putendl_fd(" ./fractol burningship\n", 1);
ft_putendl_fd("Controls:", 1);
ft_putendl_fd(" Scroll - Zoom in/out", 1);
ft_putendl_fd(" Arrows - Move", 1);
ft_putendl_fd(" +/- - Zoom", 1);
ft_putendl_fd(" C - Change color scheme", 1);
ft_putendl_fd(" R - Reset view", 1);
ft_putendl_fd(" ESC - Exit\n", 1);
}
2.8.3 ft_atof実装
/* 文字列を浮動小数点数に変換 */
double ft_atof(const char *str)
{
double result;
double decimal;
int sign;
int i;
result = 0.0;
decimal = 0.1;
sign = 1;
i = 0;
/* 空白スキップ */
while (str[i] == ' ' || (str[i] >= '\t' && str[i] <= '\r'))
i++;
/* 符号 */
if (str[i] == '-' || str[i] == '+')
{
if (str[i] == '-')
sign = -1;
i++;
}
/* 整数部 */
while (str[i] >= '0' && str[i] <= '9')
{
result = result * 10.0 + (str[i] - '0');
i++;
}
/* 小数部 */
if (str[i] == '.')
{
i++;
while (str[i] >= '0' && str[i] <= '9')
{
result += (str[i] - '0') * decimal;
decimal *= 0.1;
i++;
}
}
return (result * sign);
}
2.9 メイン関数
/* main.c */
int main(int argc, char **argv)
{
t_fractol f;
/* デフォルト初期化 */
init_defaults(&f);
/* 引数解析 */
if (!parse_args(argc, argv, &f))
return (1);
/* MiniLibX初期化 */
if (!init_fractol(&f))
return (1);
/* 初期描画 */
render(&f);
/* イベントフック設定 */
mlx_key_hook(f.win, key_hook, &f);
mlx_mouse_hook(f.win, mouse_hook, &f);
mlx_hook(f.win, 17, 0, close_hook, &f);
/* イベントループ */
mlx_loop(f.mlx);
return (0);
}
void init_defaults(t_fractol *f)
{
f->zoom = DEFAULT_ZOOM;
f->offset_x = 0.0;
f->offset_y = 0.0;
f->max_iter = DEFAULT_MAX_ITER;
f->color_scheme = SCHEME_CLASSIC;
f->julia_c.real = -0.7;
f->julia_c.imag = 0.27015;
}
int init_fractol(t_fractol *f)
{
f->mlx = mlx_init();
if (!f->mlx)
return (error_return("MLX init failed", 0));
f->win = mlx_new_window(f->mlx, WIN_WIDTH, WIN_HEIGHT, WIN_TITLE);
if (!f->win)
return (error_return("Window creation failed", 0));
f->img.ptr = mlx_new_image(f->mlx, WIN_WIDTH, WIN_HEIGHT);
if (!f->img.ptr)
return (error_return("Image creation failed", 0));
f->img.addr = mlx_get_data_addr(f->img.ptr, &f->img.bpp,
&f->img.line_len, &f->img.endian);
/* フラクタル固有の初期位置 */
if (f->type == MANDELBROT)
f->offset_x = -0.5;
else if (f->type == BURNING_SHIP)
{
f->offset_x = -0.4;
f->offset_y = -0.5;
}
return (1);
}
2.10 本章のまとめ
fract-ol 実装チェックリスト:
必須機能:
□ マンデルブロ集合の表示
□ ジュリア集合の表示
□ 追加フラクタル(バーニングシップ等)
□ マウスホイールでズーム
□ 矢印キーで移動
□ ESCで終了
□ 色付け
ボーナス機能:
□ ズーム位置がマウス中心
□ カラースキーム切り替え
□ ジュリアパラメータの動的変更
□ スムースカラーリング
□ 反復回数の動的調整
コード品質:
□ メモリリークなし
□ Normチェック通過
□ 42ヘッダー
□ 適切なエラー処理
fract-olは、数学とプログラミングの美しい融合です。無限の複雑さを秘めたフラクタルの世界を探索しながら、グラフィックスプログラミングと最適化のスキルを磨きましょう。