第6章:最適化とボーナス機能

はじめに

本章では、パフォーマンス最適化とボーナス機能の実装を学びます。

---

1. パフォーマンス最適化

1.1 ダブルバッファリング

// 画面のちらつきを防ぐ
void render_frame(t_game *game) {
    // バックバッファに描画
    clear_image(game);
    render_walls(game);
    render_sprites(game);  // ボーナス

    // 描画完了後に一度に表示
    mlx_put_image_to_window(game->mlx, game->win, game->img, 0, 0);
}

1.2 固定小数点演算

// 浮動小数点 → 固定小数点(8ビット小数部)
#define FP_BITS 8
#define FP_SCALE (1 << FP_BITS)

typedef int fixed_t;

fixed_t float_to_fixed(double f) {
    return (fixed_t)(f * FP_SCALE);
}

double fixed_to_float(fixed_t fp) {
    return (double)fp / FP_SCALE;
}

fixed_t fixed_mul(fixed_t a, fixed_t b) {
    return (a * b) >> FP_BITS;
}

1.3 ルックアップテーブル

#define TABLE_SIZE 4096
double sin_table[TABLE_SIZE];
double cos_table[TABLE_SIZE];

void init_trig_tables(void) {
    for (int i = 0; i < TABLE_SIZE; i++) {
        double angle = 2.0 * PI * i / TABLE_SIZE;
        sin_table[i] = sin(angle);
        cos_table[i] = cos(angle);
    }
}

double fast_sin(double angle) {
    // 角度を0〜2πに正規化
    while (angle < 0) angle += 2 * PI;
    while (angle >= 2 * PI) angle -= 2 * PI;

    int index = (int)(angle * TABLE_SIZE / (2 * PI)) % TABLE_SIZE;
    return sin_table[index];
}

1.4 テクスチャキャッシュ

// テクスチャのピクセルをキャッシュフレンドリーに並べ替え
void optimize_texture(t_texture *tex) {
    // 列優先で再配置(垂直方向のアクセスが多いため)
    unsigned int *optimized = malloc(tex->width * tex->height * sizeof(unsigned int));

    for (int x = 0; x < tex->width; x++) {
        for (int y = 0; y < tex->height; y++) {
            optimized[x * tex->height + y] = get_texture_pixel(tex, x, y);
        }
    }

    // 元のテクスチャデータを置き換え
    // ...
}

---

2. ミニマップ(ボーナス)

2.1 ミニマップの描画

#define MINIMAP_SCALE 8
#define MINIMAP_SIZE 200

void draw_minimap(t_game *game) {
    int startX = 10;
    int startY = 10;

    // 背景
    draw_rect(game, startX, startY, MINIMAP_SIZE, MINIMAP_SIZE, 0x333333);

    // マップを描画
    for (int y = 0; y < game->map_height; y++) {
        for (int x = 0; x < game->map_width; x++) {
            int color = (game->map[y][x] == '1') ? 0xFFFFFF : 0x666666;
            int px = startX + x * MINIMAP_SCALE;
            int py = startY + y * MINIMAP_SCALE;

            if (px < MINIMAP_SIZE && py < MINIMAP_SIZE) {
                draw_rect(game, px, py, MINIMAP_SCALE - 1, MINIMAP_SCALE - 1, color);
            }
        }
    }

    // プレイヤー位置
    int playerPx = startX + (int)(game->player.posX * MINIMAP_SCALE);
    int playerPy = startY + (int)(game->player.posY * MINIMAP_SCALE);
    draw_circle(game, playerPx, playerPy, 3, 0xFF0000);

    // プレイヤーの向き
    int dirEndX = playerPx + (int)(game->player.dirX * 10);
    int dirEndY = playerPy + (int)(game->player.dirY * 10);
    draw_line(game, playerPx, playerPy, dirEndX, dirEndY, 0xFF0000);
}

2.2 レイの視覚化

void draw_minimap_rays(t_game *game) {
    int startX = 10;
    int startY = 10;
    int playerPx = startX + (int)(game->player.posX * MINIMAP_SCALE);
    int playerPy = startY + (int)(game->player.posY * MINIMAP_SCALE);

    // 視野内のレイを描画(間引いて表示)
    for (int x = 0; x < game->screenWidth; x += 10) {
        double cameraX = 2 * x / (double)game->screenWidth - 1;
        double rayDirX = game->player.dirX + game->player.planeX * cameraX;
        double rayDirY = game->player.dirY + game->player.planeY * cameraX;

        // レイの長さを計算(簡略化)
        double len = 50;  // または実際の壁までの距離

        int endX = playerPx + (int)(rayDirX * len);
        int endY = playerPy + (int)(rayDirY * len);

        draw_line(game, playerPx, playerPy, endX, endY, 0x00FF00);
    }
}

---

3. ドア(ボーナス)

3.1 ドアの状態管理

typedef struct s_door {
    int     x;
    int     y;
    double  open_amount;  // 0.0 = 閉, 1.0 = 開
    int     is_opening;
} t_door;

#define MAX_DOORS 100
t_door doors[MAX_DOORS];
int door_count = 0;

void update_doors(t_game *game) {
    for (int i = 0; i < door_count; i++) {
        if (doors[i].is_opening) {
            doors[i].open_amount += 0.02;
            if (doors[i].open_amount >= 1.0) {
                doors[i].open_amount = 1.0;
                doors[i].is_opening = 0;
            }
        }
    }
}

3.2 ドアのレンダリング

void render_door(t_game *game, t_ray *ray, int x) {
    // ドアまでの距離
    double doorDist = ray->perpWallDist;

    // ドアの開き具合を考慮
    t_door *door = get_door_at(ray->mapX, ray->mapY);
    if (door) {
        // 開いているドアはレイを通過させる
        if (door->open_amount >= 1.0) {
            // ドアを通過して次の壁を探す
            continue_dda(ray, game);
        } else {
            // 部分的に開いているドアをレンダリング
            // テクスチャのオフセットを計算
        }
    }
}

---

4. スプライト(ボーナス)

4.1 スプライトデータ

typedef struct s_sprite {
    double x;
    double y;
    int texture_id;
} t_sprite;

t_sprite sprites[MAX_SPRITES];
int sprite_count = 0;

4.2 スプライトのソート

void sort_sprites(t_game *game) {
    // プレイヤーからの距離でソート(遠い順)
    for (int i = 0; i < sprite_count - 1; i++) {
        for (int j = i + 1; j < sprite_count; j++) {
            double dist_i = pow(sprites[i].x - game->player.posX, 2) +
                           pow(sprites[i].y - game->player.posY, 2);
            double dist_j = pow(sprites[j].x - game->player.posX, 2) +
                           pow(sprites[j].y - game->player.posY, 2);

            if (dist_i < dist_j) {
                t_sprite temp = sprites[i];
                sprites[i] = sprites[j];
                sprites[j] = temp;
            }
        }
    }
}

4.3 スプライトのレンダリング

void render_sprites(t_game *game) {
    sort_sprites(game);

    for (int i = 0; i < sprite_count; i++) {
        // スプライトの相対位置
        double spriteX = sprites[i].x - game->player.posX;
        double spriteY = sprites[i].y - game->player.posY;

        // カメラ空間への変換
        double invDet = 1.0 / (game->player.planeX * game->player.dirY -
                               game->player.dirX * game->player.planeY);

        double transformX = invDet * (game->player.dirY * spriteX -
                                      game->player.dirX * spriteY);
        double transformY = invDet * (-game->player.planeY * spriteX +
                                      game->player.planeX * spriteY);

        // 画面上の位置
        int spriteScreenX = (int)((game->screenWidth / 2) *
                                  (1 + transformX / transformY));

        // スプライトの高さ
        int spriteHeight = abs((int)(game->screenHeight / transformY));
        int drawStartY = -spriteHeight / 2 + game->screenHeight / 2;
        int drawEndY = spriteHeight / 2 + game->screenHeight / 2;

        // スプライトの幅
        int spriteWidth = abs((int)(game->screenHeight / transformY));
        int drawStartX = -spriteWidth / 2 + spriteScreenX;
        int drawEndX = spriteWidth / 2 + spriteScreenX;

        // スプライトを描画
        for (int stripe = drawStartX; stripe < drawEndX; stripe++) {
            if (transformY > 0 && stripe > 0 && stripe < game->screenWidth &&
                transformY < z_buffer[stripe]) {
                // テクスチャを描画
                // ...
            }
        }
    }
}

---

5. マウス操作(ボーナス)

5.1 マウスでの視点回転

int mouse_move(int x, int y, t_game *game) {
    static int last_x = -1;
    (void)y;

    if (last_x == -1) {
        last_x = x;
        return (0);
    }

    int dx = x - last_x;
    double rot_speed = dx * 0.002;

    rotate_player(&game->player, rot_speed);

    // マウスを中央に戻す(macOSでは動作しない場合あり)
    // mlx_mouse_move(game->mlx, game->win, game->screenWidth / 2, game->screenHeight / 2);
    // last_x = game->screenWidth / 2;

    last_x = x;
    return (0);
}

void setup_mouse_hooks(t_game *game) {
    mlx_hook(game->win, 6, 1L<<6, mouse_move, game);  // MotionNotify
}

---

6. FPS表示

6.1 フレームレート計算

#include <sys/time.h>

typedef struct s_fps {
    struct timeval last_time;
    int frame_count;
    double fps;
} t_fps;

void update_fps(t_fps *fps) {
    struct timeval current_time;
    gettimeofday(&current_time, NULL);

    double elapsed = (current_time.tv_sec - fps->last_time.tv_sec) +
                    (current_time.tv_usec - fps->last_time.tv_usec) / 1000000.0;

    fps->frame_count++;

    if (elapsed >= 1.0) {
        fps->fps = fps->frame_count / elapsed;
        fps->frame_count = 0;
        fps->last_time = current_time;
    }
}

void draw_fps(t_game *game) {
    char fps_str[32];
    sprintf(fps_str, "FPS: %.1f", game->fps.fps);
    mlx_string_put(game->mlx, game->win, 10, game->screenHeight - 20,
                   0xFFFFFF, fps_str);
}

---

まとめ

本章で学んだこと:

  • パフォーマンス最適化: ダブルバッファ、固定小数点、LUT
  • ミニマップ: 2D表示とレイ可視化
  • ドア: 開閉状態の管理
  • スプライト: ソートとレンダリング
  • マウス操作: 視点回転

これでcub3Dガイドは完了です。