Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

73 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

cub5d

A raycasting 3D game engine in the style of Wolfenstein 3D, built with C and MLX42.


d3bvstack

gamorcil

Features

  • Textured walls with directional mapping (NO / SO / EA / WE)
  • Configurable floor and ceiling colors (RGB)
  • Real-time first-person movement (WASD + arrow keys)
  • Collision detection against walls
  • Minimap overlay showing player position and orientation
  • Slow-walk modifier (Shift)
  • DDA (Digital Differential Analyzer) raycasting algorithm
  • Fisheye correction via perpendicular distance
  • Comprehensive .cub map validation (enclosure, characters, format)

Requirements

Dependency Debian / Ubuntu macOS (Homebrew)
C compiler build-essential Xcode CLT
make make included
cmake cmake cmake
GLFW libglfw3-dev glfw

Clone & Build

Fresh clone:

git clone --recurse-submodules https://github.com/d3bvstack/cub5d.git
cd cub5d
make

Already cloned (update submodules):

git submodule update --init --recursive
make

The cub3D binary is created at the project root.

Dependencies (submodules)

The project vendors three libraries as git submodules under libs/:

Submodule Repository Description
libft d3bvstack/libft Custom C standard-library replacement built during the 42 cursus provides ft_split, ft_strdup, ft_calloc, get_next_line, ft_printf, and dozens of other utility functions used throughout cub3D.
MLX42 codam-coding-college/MLX42 The simple graphics library used for windowing, input handling, image creation, and pixel manipulation.
ft_printf d3bvstack/ft_printf A custom printf implementation used for formatted error messages.

libft and ft_printf are previous 42 school projects where the student builds their own versions of standard C functions from scratch a core part of the curriculum's pedagogy.

Usage

./cub3D map.cub

An example map (map.cub) and textures (textures/) are included.

Map format

A .cub file defines textures, colors, and the map grid. Lines can appear in any order before the map begins, and blank lines are allowed.

Texture paths (4 directions)

NO ./textures/north.png
SO ./textures/south.png
EA ./textures/east.png
WE ./textures/west.png

Each path is validated: extension must be .png, file must exist and be readable, and the header must match the PNG magic bytes (\x89PNG\r\n\x1a\n).

Floor and ceiling colors

F 77,110,59
C 107,197,250

RGB values 0–255. Format: letter (F or C) followed by comma-separated RGB.

Map grid

A rectangular grid fully enclosed by walls (1). The map begins when a line contains only valid map characters and no config identifier.

Char Meaning
1 Wall
0 Empty floor
N / S / E / W Player start position + facing direction (exactly one required)
Outside / void (triggers "not closed" error if reachable)

Example

NO ./textures/north1.png
SO ./textures/south1.png
WE ./textures/west1.png
EA ./textures/east1.png
F 77,110,59
C 107,197,250

   111111111111111111111
   100000000000000011001
   100111100110000111001
   1001  1001 1000000001
 11100111100110000111001
 110000000000000011110011111111111
 111101111111111111000000100010001
 111101111       11010100100000001
 100000111111111110000000000111101
 110000000000000000000000000010001
 10000011110101011111010010W011111
 1011101  1010101   1010010001
 1000001  1000001   1011010001
 1111111  1111111   1111111111

Controls

Key Action
W / A / S / D Move forward / strafe left / backward / strafe right
← / → Rotate view
Shift Slow walk (40% speed)
ESC Exit

Program Architecture

The program runs in four sequential phases, then enters a frame-based game loop.

High-level flow

%% Diagram 1: Top-level program flow 4 sequential phases then game loop
flowchart TD
    classDef entry fill:#1e88e5,stroke:#1565c0,color:#fff
    classDef phase1 fill:#43a047,stroke:#2e7d32,color:#fff
    classDef phase2 fill:#00897b,stroke:#00695c,color:#fff
    classDef phase3 fill:#fb8c00,stroke:#ef6c00,color:#fff
    classDef phase4 fill:#8e24aa,stroke:#6a1b9a,color:#fff
    classDef loop fill:#6d4c41,stroke:#4e342e,color:#fff
    classDef exit fill:#e53935,stroke:#c62828,color:#fff

    A["./cub3D map.cub"] --> B[Phase 1: Parse .cub file]
    B --> C[Phase 2: Assemble scene]
    C --> D[Phase 3: Init MLX42 & resources]
    D --> E[Phase 4: Game loop]

    subgraph Game Loop
        F[Movement & rotation] --> G[Collision detection]
        G --> H[Draw minimap]
        H --> I[Raycast & render]
        I -->|next frame| F
    end

    E --> F
    E -->|ESC| J[Cleanup & exit]

    class A entry
    class B phase1
    class C phase2
    class D phase3
    class E,F,G,H,I loop
    class J exit
Loading

Phase 1: Parse

Entry: parse_scene_file(argc, argv) in src/parse/parse.c

The parser validates CLI arguments, checks the file extension and accessibility, reads every line from the file into a t_lines struct (a dynamic array of trimmed strings with a position cursor), and returns the result.

%% Diagram 2: Phase 1 Parse .cub file validation pipeline
flowchart LR
    classDef entry fill:#1e88e5,stroke:#1565c0,color:#fff
    classDef decision fill:#fb8c00,stroke:#ef6c00,color:#fff
    classDef process fill:#43a047,stroke:#2e7d32,color:#fff
    classDef error fill:#e53935,stroke:#c62828,color:#fff

    A["argc, argv"] --> B{argc < 2?}
    B -->|Yes| C["error_argc()\nreturn NULL"]
    B -->|No| D["validate_extension()\n.cub check"]
    D --> E{Extension .cub?}
    E -->|No| F["error_extension()\nreturn NULL"]
    E -->|Yes| G{"validate_fileaccess()\nfile readable?"}
    G -->|No| H["perror_generic()\nreturn NULL"]
    G -->|Yes| I["retrieve_lines()\nGNL → t_lines"]
    I --> J[t_lines struct]

    class A entry
    class B,E,G decision
    class D process
    class I process
    class C,F,H error
    click D "src/parse/validate_file_helpers.c#L20-L32"
    click I "src/parse/retrieve_lines.c#L88-L101"
Loading

Key functions:

File Function Purpose
parse.c parse_scene_file Orchestrates validation and reading
validate_file.c validate_file Checks .cub extension + file access
validate_file_helpers.c validate_extension Checks suffix matches .cub
validate_file_helpers.c validate_fileaccess Ensures file is not a directory and is readable
validate_file_helpers.c validate_png Validates PNG magic bytes
file_helpers.c open_file_fd Opens file descriptor
retrieve_lines.c retrieve_lines Reads all lines via GNL into t_lines
file_helpers.c cleanup_fd Closes FD, resets GNL

Phase 2: Assemble

Entry: assemble_scene(&lines) in src/assemble/assemble.c

The assembly phase creates a t_scene struct, then processes config lines (texture paths and colors) followed by the map grid. After reading the map, it locates the player, normalizes the grid to a rectangle, checks size limits, and runs flood-fill to verify the map is fully enclosed.

%% Diagram 3: Phase 2 Assemble scene from parsed lines
flowchart TD
    classDef entry fill:#1e88e5,stroke:#1565c0,color:#fff
    classDef process fill:#43a047,stroke:#2e7d32,color:#fff
    classDef decision fill:#fb8c00,stroke:#ef6c00,color:#fff
    classDef config fill:#00897b,stroke:#00695c,color:#fff
    classDef map fill:#6d4c41,stroke:#4e342e,color:#fff
    classDef validate fill:#8e24aa,stroke:#6a1b9a,color:#fff
    classDef error fill:#e53935,stroke:#c62828,color:#fff

    A[t_lines] --> B[create_t_scene]

    subgraph Config Parsing
        C{set_config}
        C --> D["Parse NO/SO/EA/WE\nvalidate & store paths"]
        C --> E["Parse F/C\nRGB → uint32"]
        D --> F{All 4 textures +\n2 colors present?}
        E --> F
        F -->|Missing| G[missing_config_element]
    end

    subgraph Map Setup
        F -->|OK| H{set_map}
        H --> I["Read remaining lines\nas map rows"]
        I --> J["set_player()\nfind N/S/E/W, calc dir"]
        J --> K["normalize_map()\npad to rectangle"]
    end

    subgraph Validation
        K --> L["map_size()\nlimit 200×200"]
        L --> M["flood_map()\nenclosure check"]
        M -->|Leak detected| N[not_closed_map]
        M -->|OK| O[t_scene ready]
    end

    G --> P["free & return NULL"]
    N --> P

    class A entry
    class B process
    class C,D,E config
    class F,H decision
    class I,J,K map
    class L,M validate
    class G,N error
    click D "src/assemble/set_config.c#L116-L138"
    click J "src/assemble/set_player.c#L43-L70"
    click M "src/assemble/flood_map.c#L67-L77"
Loading

Key sub-steps:

2a. Config parsing (set_config in src/assemble/set_config.c):

  • Lines before the map grid are classified by their first token (NO/SO/EA/WE/F/C)
  • Textures: path validated for .png extension, file access, and PNG magic bytes
  • Colors: RGB string parsed via ft_split on commas, each value validated 0–255
  • Duplicate config entries are rejected

2b. Map reading (set_map in src/assemble/set_map.c):

  • Lines are dynamically appended to map->matrix via realloc_array
  • Each line is validated: only characters 1, 0, N, S, E, W, and space are allowed
  • Lines containing config keywords are rejected (ensures config ends before map begins)

2c. Player setup (set_player in src/assemble/set_player.c):

  • Scans entire grid for N/S/E/W
  • Exactly one player start required (no duplicates, no missing)
  • Player position set to center of tile (col + 0.5, row + 0.5)
  • Direction vector computed from orientation angle using cos/sin:
    • N: 270° → dir = (0, -1)
    • E: 0° → dir = (1, 0)
    • S: 90° → dir = (0, 1)
    • W: 180° → dir = (-1, 0)

2d. Map normalization (normalize_map in src/assemble/map_normalize.c):

  • Finds the widest row, pads all others to that width with spaces

2e. Map size check (map_size in src/assemble/map_size.c):

  • Rejects maps with height or width ≥ 200

2f. Flood-fill validation (flood_map in src/assemble/flood_map.c):

  • Starts from player position, recursively marks reachable empty tiles (0_, player char → P)
  • If a space character (void) is reached, the map is reported as not fully enclosed
  • If the edge of the allocated matrix is reached, the map is also reported as open

Phase 3: Init Game

Entry: game(scene)init_game(&game, scene) in src/game/game_start.c

Creates the MLX42 window, loads all resources, and registers hook callbacks.

%% Diagram 4: Phase 3 Initialize MLX42, load resources, register hooks
flowchart LR
    classDef entry fill:#1e88e5,stroke:#1565c0,color:#fff
    classDef mlx fill:#fb8c00,stroke:#ef6c00,color:#fff
    classDef asset fill:#43a047,stroke:#2e7d32,color:#fff
    classDef hook fill:#8e24aa,stroke:#6a1b9a,color:#fff

    A[t_scene] --> B["init_game()\nmlx_init(800, 600, #quot;cub3D#quot;, false)"]
    B --> C["init_game_resources()\nmlx_load_png ×4"]
    C --> D["init_game_background()\nfloor / ceiling split (solid fill)"]
    D --> E["mlx_new_image()\nwalls canvas"]
    E --> F["mlx_new_image()\nminimap overlay"]
    F --> G["set_loop_hooks()\nmlx_loop_hook ×5 + mlx_key_hook"]

    class A entry
    class B,C mlx
    class D,E,F asset
    class G hook
    click B "src/game/init_game.c#L41-L54"
    click C "src/game/init_game_resources.c#L30-L39"
    click G "src/game/set_hooks.c#L26-L35"
Loading

Key initialization:

Step File Detail
Window init_game.c:19 mlx_init(800, 600, "cub3D", false)
Textures init_game_resources.c:18 Loads 4 PNGs into mlx_texture_t array
Background init_game_resources.c:39 Fills MLX image: top half = ceiling, bottom half = floor color
Walls init_game_resources.c:101 Blank image drawn on top of background, cleared and redrawn each frame
Minimap init_game_resources.c:80 Quarter-size overlay with configurable tile size

Hook registration (set_hooks.c:26):

%% Diagram 5: Hook registration per-frame loop hooks + ESC key hook
flowchart TD
    classDef hook_func fill:#1e88e5,stroke:#1565c0,color:#fff
    classDef per_frame fill:#43a047,stroke:#2e7d32,color:#fff
    classDef event fill:#fb8c00,stroke:#ef6c00,color:#fff

    subgraph "mlx_loop_hook (every frame, in order)"
        direction LR
        L1["movement()\nWASD → next_x/y"]
        L2["rotation()\nArrow keys → orientation"]
        L3["execute_move()\ncollision check + apply"]
        L4["minimap()\ndraw overlay"]
        L5["render()\nraycast & paint"]
    end

    subgraph "mlx_key_hook (on key event)"
        K["exit_key()\nESC → mlx_close_window"]
    end

    L1 --> L2 --> L3 --> L4 --> L5
    A["set_loop_hooks()"] --> L1
    A --> K

    class A hook_func
    class L1,L2,L3,L4,L5 per_frame
    class K event
    click A "src/game/set_hooks.c#L26-L35"
Loading

Phase 4: Game Loop

Each frame, MLX42 fires the loop hooks in order: movement → rotation → execute move → minimap → render.

%% Diagram 6: Per-frame game loop (hooks fire in registration order)
flowchart TD
    classDef loop fill:#6d4c41,stroke:#4e342e,color:#fff
    classDef timing fill:#00897b,stroke:#00695c,color:#fff
    classDef exit fill:#e53935,stroke:#c62828,color:#fff

    subgraph Per-Frame Loop
        A["delta_time update\nΔt between frames"] --> B["movement()\nWASD → next_x/y"]
        B --> C["rotation()\nArrow → orientation"]
        C --> D["execute_move()\ncollision + apply"]
        D --> E["minimap()\ndraw overlay"]
        E --> F["render()\nraycast & paint"]
    end

    F -->|"next iteration"| A
    G[ESC key] --> H["mlx_close_window()"]
    H --> I["cleanup_game()"]
    I --> J["mlx_terminate()"]

    class A,B,C,D,E,F loop
    class G timing
    class H,I,J exit
    click I "src/game/game_cleanup.c#L51-L56"
Loading

4a. Movement (hook_move_rotation.c:46):

  • Reads WASD key states
  • Computes next position (next_x, next_y) using direction vector × speed
  • Speed = 5 × delta_time (or 5 × 0.4 × delta_time when Shift held)
  • Strafe: A/D move perpendicular to direction vector

4b. Rotation (hook_move_rotation.c:69):

  • Left/Right arrows change player->orientation by 180 × delta_time degrees
  • Orientation wrapped to [0, 360) via fmod
  • Direction vector recalculated: dir_x = cos(orientation), dir_y = sin(orientation)

4c. Collision detection (hook_move_rotation.c:88):

  • Checks if next_x/next_y with a 0.1-unit margin lands on a walkable tile (_ or P)
  • X and Y axes checked independently for smooth wall sliding

4d. Rendering DDA Raycasting (render.c:76):

%% Diagram 7: DDA Raycasting per-frame rendering pipeline
flowchart TD
    classDef frame fill:#1e88e5,stroke:#1565c0,color:#fff
    classDef loop fill:#43a047,stroke:#2e7d32,color:#fff
    classDef dda fill:#fb8c00,stroke:#ef6c00,color:#fff
    classDef draw fill:#8e24aa,stroke:#6a1b9a,color:#fff
    classDef decision fill:#e53935,stroke:#c62828,color:#fff

    A["render()\nentry point"] --> B["init_plane()\ncamera plane ⊥ direction"]
    B --> C["clean_walls()\nclear previous frame"]

    subgraph "For each column x = 0..799"
        D["x = 0"] --> E["ray_dir_calc()\ncamera_x + ray direction"]
        E --> F["setup_dda()\ndeltaDist, step, sideDist"]
        F --> G["dda()\nstep grid → wall hit"]
        G --> H["fix_eye()\nperpWallDist (fisheye fix)"]
        H --> I["wall_size = WIN_H / perpWallDist"]
        I --> J["paint_pixels()\nsample texture → draw column"]
        J --> K["x++"]
        K --> L{x < WIN_WIDTH?}
        L -->|Yes| E
    end

    class A frame
    class B,C frame
    class D,E loop
    class F,G dda
    class H,I draw
    class J draw
    class L decision
    click A "src/render/render.c#L76-L95"
    click G "src/render/dda.c#L54-L78"
    click H "src/render/raycast.c#L40-L45"
Loading

Raycasting detail:

%% Diagram 8: DDA raycasting math per-column algorithm detail
flowchart TD
    classDef calc fill:#1e88e5,stroke:#1565c0,color:#fff
    classDef step fill:#43a047,stroke:#2e7d32,color:#fff
    classDef decision fill:#fb8c00,stroke:#ef6c00,color:#fff
    classDef output fill:#8e24aa,stroke:#6a1b9a,color:#fff

    A["camera_x = 2x / WIN_WIDTH - 1"] --> B["ray_dir = dir + plane × camera_x"]
    B --> C["deltaDist_x = |1 / ray_dir_x|"]
    B --> D["deltaDist_y = |1 / ray_dir_y|"]
    C --> E{sideDist_x < sideDist_y?}
    D --> E
    E -->|Yes| F["step x\nsideDist_x += deltaDist_x\nmap_x += step_x"]
    E -->|No| G["step y\nsideDist_y += deltaDist_y\nmap_y += step_y"]
    F --> H{Hit wall '1'?}
    G --> H
    H -->|No| E
    H -->|Yes| I["perpWallDist\n= sideDist − deltaDist"]
    I --> J["wall_height\n= WIN_HEIGHT / perpWallDist"]
    J --> K["draw_start = −wall_h/2 + WIN_H/2"]
    J --> L["draw_end = +wall_h/2 + WIN_H/2"]
    K --> M["Sample texture\n@ (tex_x, tex_y) → paint column"]

    class A calc
    class B,C,D calc
    class E decision
    class F,G step
    class H decision
    class I,J output
    class K,L output
    class M output
Loading

Key rendering functions:

Function File Purpose
init_plane raycast.c:16 Computes camera plane perpendicular to direction (FOV ≈ 62°)
ray_dir_calc raycast.c:27 Calculates ray direction for column x
setup_dda dda.c:33 Initializes DDA variables: deltaDist, step, sideDist
dda dda.c:54 Steps through grid cells until a wall (1) is hit
fix_eye raycast.c:40 Returns perpendicular wall distance to correct fisheye
get_current_texture textures.c:27 Selects wall-facing texture based on side + ray direction
calculate_tex_x textures.c:50 Computes x-coordinate within texture, mirror-corrected
paint_pixels render.c:29 Draws the textured vertical strip for the column

4e. Minimap (hook_minimap.c:106):

  • Clears the minimap image each frame
  • Draws tiles within a 5-tile radius of the player
  • Color coding: wall = semi-transparent white, floor = solid white, void = semi-transparent white (walls color)
  • Player drawn as a small blue square with a blue direction line

Cleanup

Entry: cleanup_game(&game) in src/game/game_cleanup.c

%% Diagram 9: Cleanup tear down MLX resources and free scene
flowchart LR
    classDef exit fill:#e53935,stroke:#c62828,color:#fff
    classDef cleanup fill:#43a047,stroke:#2e7d32,color:#fff
    classDef resource fill:#fb8c00,stroke:#ef6c00,color:#fff

    A["mlx_loop ends"] --> B["cleanup_game()"]
    B --> C["mlx_delete_texture() ×4"]
    B --> D["mlx_delete_image()\nbackground"]
    B --> E["mlx_delete_image()\nwalls"]
    B --> F["mlx_delete_image()\nminimap"]
    C & D & E & F --> G["mlx_terminate()"]
    G --> H["free_t_scene()\nfrom main"]

    class A exit
    class B cleanup
    class C,D,E,F resource
    class G cleanup
    class H cleanup
    click B "src/game/game_cleanup.c#L51-L56"
Loading

Build targets

Command Effect
make Build cub3D
make clean Remove object files
make fclean Remove objects, libraries, and binary
make re Full rebuild

Notes

MLX42 (the graphics library) reports memory allocations on exit; these are not leaks from cub3D.

About

No description or website provided.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages