An experimental, DOOM-style BSP engine, written in multiple languages.
  • Rust 19.6%
  • Zig 18.2%
  • C++ 16.7%
  • Go 16.6%
  • Python 15.2%
  • Other 13.7%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-07-14 04:04:50 +02:00
cpp Encapsulate C++ port: Level, Bsp, and Visplane classes 2026-07-12 17:43:10 -07:00
go Encapsulate Go port: Level, BSP type, and interface-based node 2026-07-12 18:02:41 -07:00
python Encapsulate Python port: Level, Bsp class, union node 2026-07-12 18:05:12 -07:00
rust Encapsulate Rust port: Level, Bsp wrapper, sealed Visplane 2026-07-12 18:02:41 -07:00
swift Encapsulate Swift port: Level struct and Bsp wrapper 2026-07-12 17:58:45 -07:00
zig Encapsulate Zig port: Level struct (zero-cost comptime) 2026-07-12 18:05:12 -07:00
.gitignore Add Zig port (SDL3 via @cImport) 2026-05-24 21:11:50 -07:00
LICENSE Initial commit 2026-04-16 21:01:59 -07:00
README.md Add Zig port (SDL3 via @cImport) 2026-05-24 21:11:50 -07:00

BSPRenderer

A DOOM-style software renderer, ported to six languages. Walls render with flat colors and distance-shaded per-column light falloff; floors and ceilings use perspective-correct inverse-projected checkerboard via a visplane system. Same engine, same hand-authored level, same controls — six implementations of the same architecture, side by side.

Ports

Language Path Library / framework Build & run
Swift swift/ AppKit (NSView + CGImage) cd swift && swift run
C++ cpp/ SDL3 (callback main + streaming texture) cd cpp && make run
Go go/ Ebitengine v2 cd go && go run .
Python python/ pygame-ce (SDL2) + numpy cd python && uv run main.py
Rust rust/ winit + pixels (wgpu under the hood) cd rust && cargo run --release
Zig zig/ SDL3 via @cImport cd zig && zig build run

Each port has its own README with build prerequisites, file layout, and notes on language/library specifics. The Swift version is the original; the others are line-for-line ports of the same architecture.

Controls (every port)

Key Action
W / Forward
S / Backward
A / D Strafe left / right
/ Turn
Tab Toggle slow-render mode (watch the BSP walk emit columns)
Esc Quit

Architecture (every port)

The same six-stage pipeline regardless of language:

  1. Level → segs. Each one-sided linedef produces one seg; each two-sided linedef produces two segs (one per side, with the back sector tracked for portal handling).
  2. BSP build. At startup, the BSP builder recursively selects a partition that keeps both sides populated and minimizes straddle splits; straddling segs are split at the intersection. Leaves are subsectors.
  3. Front-to-back BSP traversal. Per frame, walks the tree from the player's side outward, handing segs to the renderer in near-to-far order.
  4. Per-seg rasterization. Back-face cull, view-space transform, near + L + R frustum clip, screen-X projection, then per-column rendering with perspective-correct 1/d interpolation.
  5. Per-column clip. yTop[x] / yBot[x] arrays act as DOOM's ceilingclip / floorclip — each seg narrows the open region for its columns, so no depth buffer is needed. Solid walls mark columns fully occluded; two-sided segs open portals bounded by the back sector's floor and ceiling projections, with upper / lower walls drawn where sector heights differ.
  6. Visplane pass. Floor and ceiling spans are accumulated per sector into Visplane structs during the BSP walk, then rasterized after by inverse-projecting each pixel back to its world (X, Y), sampling a procedural checkerboard, and depth-shading.

The level itself is identical across all five ports: 5 sectors with varied floor and ceiling heights, two pairs of non-axis-aligned walls (a diagonal corridor and a trapezoidal south chamber), four two-sided portals that exercise every upper/lower-wall combination.

Why six?

Started in Swift on macOS for fun. The other ports translate that engine into idiomatic forms in each language without changing any behavior, which makes them an interesting reference for what a software-rendered 2.5-D engine looks like across:

  • a value-typed, retain-counted, native-compiled language (Swift, the original)
  • a manual-memory, low-level systems language (C++)
  • a GC'd systems language designed for fast compilation (Go)
  • a slow-but-vectorized scripting language (Python + numpy for the hot floor/ceiling loop)
  • a borrow-checked, native-compiled systems language (Rust)
  • a manual-memory systems language with explicit allocators and compile-time evaluation, no destructors or borrow checker (Zig — uses comptime to bake the seg list into .rodata)

The Swift / C++ / Go / Rust / Zig versions hit 60 FPS comfortably; the Python port lands around 3045 FPS at 480×300 because per-column work in pure Python dominates even with numpy-vectorized inner loops.

Repo layout

BSPRenderer/
├── README.md     ← you are here
├── LICENSE       ← GPLv2, applies to all six ports
├── .gitignore    ← covers all six languages' build artifacts
├── swift/
├── cpp/
├── go/
├── python/
├── rust/
└── zig/

License

GNU General Public License v2 — the same license the original DOOM source code is released under.