第4章:テクスチャマッピング

はじめに

本章では、壁にテクスチャを貼り付ける方法を学びます。

---

1. XPMファイル

1.1 XPM形式

XPM(X PixMap)は、C言語で直接読み込めるテキストベースの画像形式です。

/* XPM */
static char * example_xpm[] = {
"8 8 3 1",          // 幅 高さ 色数 文字/色
"  c #000000",      // 黒
". c #FFFFFF",      // 白
"# c #FF0000",      // 赤
"        ",
"  ....  ",
" ...... ",
" .####. ",
" .####. ",
" ...... ",
"  ....  ",
"        "
};

1.2 miniLibXでの読み込み

typedef struct s_texture {
    void    *img;
    char    *addr;
    int     width;
    int     height;
    int     bpp;
    int     line_length;
    int     endian;
} t_texture;

int load_texture(t_game *game, t_texture *tex, char *path) {
    tex->img = mlx_xpm_file_to_image(game->mlx, path,
                                      &tex->width, &tex->height);
    if (!tex->img)
        return (error_exit("Failed to load texture"));

    tex->addr = mlx_get_data_addr(tex->img, &tex->bpp,
                                   &tex->line_length, &tex->endian);
    return (0);
}

1.3 テクスチャからピクセル取得

unsigned int get_texture_pixel(t_texture *tex, int x, int y) {
    char *dst;

    if (x < 0 || x >= tex->width || y < 0 || y >= tex->height)
        return (0);

    dst = tex->addr + (y * tex->line_length + x * (tex->bpp / 8));
    return (*(unsigned int *)dst);
}

---

2. テクスチャ座標の計算

2.1 壁のX座標

レイが壁に当たった位置のX座標(0.0〜1.0)を計算:

double calc_wall_x(t_ray *ray, t_player *player) {
    double wallX;

    if (ray->side == 0) {
        // 東西の壁
        wallX = player->posY + ray->perpWallDist * ray->dirY;
    } else {
        // 南北の壁
        wallX = player->posX + ray->perpWallDist * ray->dirX;
    }

    wallX -= floor(wallX);  // 小数部分のみ
    return (wallX);
}

2.2 テクスチャのX座標

int calc_tex_x(double wallX, t_texture *tex, t_ray *ray) {
    int texX = (int)(wallX * tex->width);

    // 壁の向きによって反転
    if (ray->side == 0 && ray->dirX > 0)
        texX = tex->width - texX - 1;
    if (ray->side == 1 && ray->dirY < 0)
        texX = tex->width - texX - 1;

    return (texX);
}

2.3 テクスチャのY座標

void draw_textured_wall(t_game *game, int x, t_ray *ray, t_draw *draw) {
    double wallX = calc_wall_x(ray, &game->player);
    t_texture *tex = get_wall_texture(game, ray);
    int texX = calc_tex_x(wallX, tex, ray);

    // テクスチャのステップ(1ピクセルあたりのテクスチャ移動量)
    double step = (double)tex->height / draw->lineHeight;
    double texPos = (draw->drawStart - game->screenHeight / 2 +
                     draw->lineHeight / 2) * step;

    for (int y = draw->drawStart; y < draw->drawEnd; y++) {
        int texY = (int)texPos & (tex->height - 1);  // ビットANDで高速に
        texPos += step;

        unsigned int color = get_texture_pixel(tex, texX, texY);

        // 影をつける(南北の壁)
        if (ray->side == 1)
            color = (color >> 1) & 0x7F7F7F;

        put_pixel(game, x, y, color);
    }
}

---

3. 4方向のテクスチャ

3.1 テクスチャの選択

t_texture *get_wall_texture(t_game *game, t_ray *ray) {
    if (ray->side == 0) {
        if (ray->stepX > 0)
            return (&game->tex_east);   // 東向きの壁
        else
            return (&game->tex_west);   // 西向きの壁
    } else {
        if (ray->stepY > 0)
            return (&game->tex_south);  // 南向きの壁
        else
            return (&game->tex_north);  // 北向きの壁
    }
}

3.2 テクスチャの管理

typedef struct s_game {
    // ...
    t_texture tex_north;
    t_texture tex_south;
    t_texture tex_east;
    t_texture tex_west;
} t_game;

int load_all_textures(t_game *game) {
    if (load_texture(game, &game->tex_north, game->north_path))
        return (1);
    if (load_texture(game, &game->tex_south, game->south_path))
        return (1);
    if (load_texture(game, &game->tex_east, game->east_path))
        return (1);
    if (load_texture(game, &game->tex_west, game->west_path))
        return (1);
    return (0);
}

---

4. 床と天井の色

4.1 色の解析

// "220,100,0" → 0xDC6400
int parse_color(char *str) {
    int r, g, b;

    r = ft_atoi(str);
    while (*str && *str != ',') str++;
    if (*str) str++;

    g = ft_atoi(str);
    while (*str && *str != ',') str++;
    if (*str) str++;

    b = ft_atoi(str);

    if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255)
        return (-1);

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

4.2 描画

void draw_ceiling_floor(t_game *game, int x, t_draw *draw) {
    int y;

    // 天井
    for (y = 0; y < draw->drawStart; y++)
        put_pixel(game, x, y, game->ceiling_color);

    // 床
    for (y = draw->drawEnd; y < game->screenHeight; y++)
        put_pixel(game, x, y, game->floor_color);
}

---

5. テクスチャの床と天井(ボーナス)

5.1 床のレイキャスティング

void draw_floor_ceiling(t_game *game) {
    for (int y = game->screenHeight / 2 + 1; y < game->screenHeight; y++) {
        // 現在の行のレイ方向
        float rayDirX0 = game->player.dirX - game->player.planeX;
        float rayDirY0 = game->player.dirY - game->player.planeY;
        float rayDirX1 = game->player.dirX + game->player.planeX;
        float rayDirY1 = game->player.dirY + game->player.planeY;

        // 現在の行の高さ位置
        int p = y - game->screenHeight / 2;
        float posZ = 0.5 * game->screenHeight;
        float rowDistance = posZ / p;

        // 床の座標
        float floorStepX = rowDistance * (rayDirX1 - rayDirX0) / game->screenWidth;
        float floorStepY = rowDistance * (rayDirY1 - rayDirY0) / game->screenWidth;
        float floorX = game->player.posX + rowDistance * rayDirX0;
        float floorY = game->player.posY + rowDistance * rayDirY0;

        for (int x = 0; x < game->screenWidth; x++) {
            int cellX = (int)floorX;
            int cellY = (int)floorY;

            // テクスチャ座標
            int tx = (int)(game->tex_floor.width * (floorX - cellX)) & (game->tex_floor.width - 1);
            int ty = (int)(game->tex_floor.height * (floorY - cellY)) & (game->tex_floor.height - 1);

            floorX += floorStepX;
            floorY += floorStepY;

            // 床
            unsigned int color = get_texture_pixel(&game->tex_floor, tx, ty);
            color = (color >> 1) & 0x7F7F7F;  // 暗くする
            put_pixel(game, x, y, color);

            // 天井(反転)
            color = get_texture_pixel(&game->tex_ceiling, tx, ty);
            put_pixel(game, x, game->screenHeight - y - 1, color);
        }
    }
}

---

6. 色の操作

6.1 色の分解と合成

typedef struct s_color {
    unsigned char r;
    unsigned char g;
    unsigned char b;
    unsigned char a;
} t_color;

t_color int_to_color(unsigned int c) {
    t_color color;
    color.a = (c >> 24) & 0xFF;
    color.r = (c >> 16) & 0xFF;
    color.g = (c >> 8) & 0xFF;
    color.b = c & 0xFF;
    return (color);
}

unsigned int color_to_int(t_color c) {
    return ((c.a << 24) | (c.r << 16) | (c.g << 8) | c.b);
}

6.2 明るさの調整

unsigned int darken_color(unsigned int color, double factor) {
    t_color c = int_to_color(color);
    c.r = (unsigned char)(c.r * factor);
    c.g = (unsigned char)(c.g * factor);
    c.b = (unsigned char)(c.b * factor);
    return (color_to_int(c));
}

// 距離による減衰
unsigned int apply_fog(unsigned int color, double distance) {
    double factor = 1.0 / (1.0 + distance * 0.1);
    return (darken_color(color, factor));
}

---

まとめ

本章で学んだこと:

  • XPMファイル: 形式と読み込み
  • テクスチャ座標: 壁の位置からテクスチャ座標へ
  • 4方向のテクスチャ: 壁の向きによる選択
  • 床と天井: 色と テクスチャ
  • 色の操作: 影、フォグ

次章では、マップのパースと実装を学びます。