| .claude | ||
| web | ||
| Makefile | ||
| README.md | ||
| vectorgons | ||
| vectorgons.c | ||
| vectorgons.exe | ||
| vectorgons.scr | ||
Vectorgons
A starfield simulator — except instead of stars, colorful vector-drawn platonic solids tumble through space toward the camera.
~530 shape types are rendered as glowing wireframes — each spawns far away at a random size, tumbles on its own random axis, and streams past the camera before being recycled at the render-distance sphere. Solids never overlap.
The shape set includes:
- Platonic solids: tetrahedron, cube, octahedron, dodecahedron, icosahedron
- Other polyhedra: cuboctahedron, truncated octahedron, stella octangula
- Prisms / antiprisms / bipyramids (various base polygons)
- Many-faced solids named for their face count: tridecahedron (13), tetradecahedron (14), pentadecahedron (15), heptadecahedron (17), octadecahedron (18), enneadecahedron (19), icosahedron (20), icositetrahedron (24), triacontahedron (30), hexacontahedron (60), and hecatohedron (100) — built from prism / bipyramid / trapezohedron families so each lands on exactly the requested number of faces
- Star polygons: {5/2}, {6/2}, {7/2}, {7/3}, {8/3}, {9/2}, {9/4}, {12/5}, plus the unicursal hexagram
- Googie / atomic-age shapes — a large set in the jet-age / space-age idiom: bowling-alley twinkles (4- and 8-point), a layered double starburst, atomic bursts with electron caps, ray sunbursts, boomerangs, kidney/amoeba blobs, concentric orbit rings, plus 30+ more truly-3-D objects: radial starbursts and sea-urchins (6/8/12/20/32-spike, some ball-tipped), gyroscope orbit cages (2–6 rings), orbital atoms (2–6 orbits), ringed Saturn planets, ball-and-stick molecules, diabolo hourglasses, ray-stars, and a Sputnik satellite
- Symbols, signs & faces, each in a flat 2-D and an extruded 3-D
form: smiley, frowny, and angry faces, biohazard, peace sign, cross,
question mark, exclamation point, hash/pound (
#), dollar sign ($), pound sterling (£), and the at sign (@) - Object iconography, each in 2-D and 3-D: space invaders, UFOs, pac-man and pac-man ghosts, alien heads, smoking pipes, umbrellas, hands, ice cream cones, light bulbs, mitochondria, windowpanes, bowling balls and pins, baseball bats, beer bottles, nonic pint glasses, whiskey bottles, telephones, telephone booths, radio towers, and atoms with orbiting electrons (the volumetric bowling ball is a true wireframe sphere, and the 3-D atom has real orbits + nucleus + electron balls rather than a flat extrusion)
- More 3-D objects — many built as true solids of revolution (a profile lathed around an axis): a full chess set (pawn, rook, bishop, knight, queen, king), candlesticks with a flame, coffee cups, lanterns, hurricane lamps, quartz crystals, pineapples, and a parametric torus — plus extruded silhouettes of padlocks, antique keys, bananas, electric guitars, tacos, pizza slices, hammers, wrenches, directional and drafting compasses, set-squares, eyeglasses, forks, spoons, fountain pens, the four classical element symbols (fire/water/air/earth), the squared-circle "philosopher's stone", a couple of retro stylized gaming/OS marks, a retro joystick, and a generic starship
- Even more objects in 2-D and 3-D: top hats, baseball caps, noses, hearts, computer keyboards, floppy disks, bells, books, and two more stylized retro computer marks. Every representational object now has both a flat 2-D and a 3-D form — the once-3-D-only chess pieces, lathed solids, torus, and atomic starbursts all gained matching 2-D silhouettes.
- Yet more, all in 2-D and 3-D: IC chip packages (DIP, QFP, PGA, and a generic CPU — the styles behind the 6502/Z80/386/486 and friends), hot dogs, hamburgers, monoliths, ringed planets, unicycles, hula hoops, mechanical gyroscopes, microscopes, telescopes, magnifying glasses that actually magnify the field behind them, thermometers, calculators, staplers, paper clips, palm trees, saguaro cacti, grape bunches, sushi rolls, corkscrews (a real 3-D helix), bottle openers, lightning bolts, and batteries
- An even bigger batch in 2-D and 3-D: everlasting gobstoppers, pants, shirts, jacks, dice, K/Q/J/A playing cards, roulette wheels, slot machines, fire hydrants, bucky balls (a real truncated icosahedron), yo-yos, brains, d6 and d20 dice, thimbles, automobiles, couches, stop signs, office chairs, DNA double helices, skull-and-crossbones, goldfish, squid, pine cones, maple leaves, sea anemones, wagon wheels, submarines, sailing ships, anchors, swiss cheese wedges, and the anarchy symbol
- Animated objects (the 3-D form is rebuilt every frame): a Hoberman sphere that unfolds and folds, a Rubik's cube whose top layer turns, a spinning top and dreidel that precess, a lighthouse with a beam that sweeps around, a blinking eyeball, a skull with a chattering jaw, chattering teeth, an octopus with waving arms, a jellyfish with drifting tentacles, a fish swimming with a flapping tail, scissors that scissor, flickering flames (free and on a zippo lighter), and spinning gears and a spinning fan
- A 3-D refracting prism: a triangular glass prism that takes a white beam in one face and splits it into a full rainbow spectrum fanning out the other — the colours are drawn straight into the shape, independent of the field hue
- A 3-D glass sphere (crystal ball): a real ball lens that grabs the field behind it and shows it inverted and fish-eye-distorted, with a bright fresnel rim and a specular reflection highlight. Like the magnifier it samples the grabbed framebuffer, but with an inverting non-linear radial warp
- An opaque polished mirror sphere (chrome ball): a convex mirror that reflects the surrounding field upright in a wide fish-eye — the optical opposite of the glass ball (which inverts what's behind it). It has metallic sky/ground shading so it reads as a solid metal ball even over empty space, plus a sharp specular highlight. The magnifier, glass-ball and mirror-ball counts are each user-settable (0–1000) — these are extra bodies added on top of the density field, so dialling them up never thins out the normal shapes
- Still more in 2-D and 3-D: cassette tapes, vinyl records, CDs, quarters, kites, baseballs, a police box, roses, shoes, boots, pianos, clouds, soup cans, icebergs, pyramids (a real square pyramid in 3-D), bombs, combs, train crossing signs, mailboxes, ship's wheels, axes, popcorn, teapots, church bells, anvils, book stacks, springs and slinkies (real 3-D helices), extra old keys, and a set of traditional alchemical and occult symbols (sulfur, mercury, salt, antimony, pentagram, ankh, all-seeing eye, triple moon)
- Live clocks (analog and digital, 2-D and 3-D) that show the actual current time — their geometry is rebuilt every frame, so the analog hands sweep and the seven-segment digital display ticks along in real time
- Random 3-D asteroids: lumpy convex-hull rocks generated fresh each run, with random vertex counts (≈7–26) giving each a different number of sides and level of complexity
- 4-, 5- and 6-dimensional polytopes: the 5-cell, tesseract (hypercube), penteract (5-cube), 6-cube, 16-cell, 5- and 6-orthoplexes, the 24-cell, and the 5-simplex. These are rotated in their own dimension and perspective-projected down to 3D every frame, so they morph (the classic "cube-within-a-cube" unfolding) while also tumbling in 3D.
Build & run
Requires a C compiler, GLFW3, and GLU (all detected via pkg-config).
make
./vectorgons # or: make run
Windows 11 (cross-compiled from Linux)
A self-contained vectorgons.exe can be cross-compiled with the
MinGW-w64 toolchain. It statically links GLFW and the GCC/threads runtime,
so on Windows it needs only system DLLs (opengl32, glu32, gdi32,
user32, shell32, kernel32) — just copy vectorgons.exe to a Windows 11
machine and double-click it. No console window, no extra DLLs.
# 1. toolchain: sudo apt install gcc-mingw-w64-x86-64 binutils-mingw-w64-x86-64 mingw-w64-x86-64-dev
# 2. GLFW: download the WIN64 binaries from https://www.glfw.org/download
# 3. build:
make windows WINCC=x86_64-w64-mingw32-gcc GLFWDIR=/path/to/glfw-3.4.bin.WIN64
make screensaver WINCC=x86_64-w64-mingw32-gcc GLFWDIR=/path/to/glfw-3.4.bin.WIN64
The legacy immediate-mode OpenGL it uses (and the glu32 perspective helpers)
are provided by the standard Windows OpenGL driver, so it runs on any Windows 11
box with working GPU drivers.
Screensaver (vectorgons.scr)
make screensaver builds the same program as a Windows screensaver (it's
the same source with -DSCREENSAVER, output named .scr). It handles the
standard screensaver command line:
/s(or no argument) — run full-screen on the primary monitor; any key, mouse click, or real mouse movement exits. The on-screen control panel is hidden./c— show a short configuration dialog. There are no in-saver controls; the saver simply uses whatever settings the main app last saved (see below), so runvectorgons.exe, dial in the look you want, quit, and the saver picks it up./p— preview (the little monitor in Windows' settings); not supported, so it exits cleanly and the preview stays blank.
To install on Windows: right-click vectorgons.scr → Install (or copy it to
C:\Windows\System32 and choose it under Settings → Personalization → Lock
screen → Screen saver). Like the .exe, it needs only Windows system DLLs.
Browser (WebAssembly)
The same source compiles to WebAssembly with Emscripten,
which maps the GLFW3 windowing and the legacy/immediate-mode OpenGL onto WebGL
(-sLEGACY_GL_EMULATION). The retained-mode VBO geometry, the FBO bloom, and the
frustum culling all run unchanged; the browser-specific bits (GLU helpers, the
requestAnimationFrame main loop, and the WebGL-incompatible double/glReadBuffer
calls) are guarded behind #ifdef __EMSCRIPTEN__.
source /path/to/emsdk/emsdk_env.sh # put emcc on PATH
make web # -> web/vectorgons.js + web/vectorgons.wasm
Serve the web/ directory over HTTP (WebAssembly will not load from a
file:// URL) and open index.html:
cd web && python3 -m http.server 8000 # then visit http://localhost:8000/
web/index.html is a ready-made page. To embed in your own page, drop a focusable
canvas with id="canvas", point Emscripten's Module at it, and load the script:
<canvas id="canvas" width="1100" height="760" tabindex="0"></canvas>
<script>
var Module = { canvas: document.getElementById('canvas') };
</script>
<script src="vectorgons.js"></script>
Serve vectorgons.js, vectorgons.wasm, and vectorgons-boot.js from the same
directory. Click the canvas to give it keyboard focus; all the usual controls
then work, and F requests browser fullscreen. (Settings are not persisted in
the browser.)
Content-Security-Policy
WebAssembly needs a couple of CSP allowances. If your server sends a strict CSP
(symptom: the canvas appears but nothing runs, and the console shows
both async and sync fetching of the wasm failed), the page's script-src must
include 'wasm-unsafe-eval' (required to instantiate any WASM — there is no
nonce/hash alternative) and connect-src must allow fetching the .wasm
(e.g. 'self'). Loading the Module config from vectorgons-boot.js (rather
than an inline <script>) means 'unsafe-inline' is not required. A working
policy for the Vectorgons path:
script-src 'self' 'wasm-unsafe-eval'; connect-src 'self';
Commodore 64 (c64/)
A bonus port for the original 1982 hardware lives in c64/. The 1 MHz
6510 has no floating-point unit and a 1-bit, 320×200 hi-res bitmap, so this is a
ground-up reimplementation rather than the OpenGL engine recompiled — but it
keeps the heart of Vectorgons: tumbling wireframe polytopes, cycling through
a small shape library (cube, octahedron, tetrahedron, stellated star, pyramid,
and a 4-D tesseract shadow) in classic monochrome vector-graphics style.
Per the brief, the glass / metal / mirror objects are dropped — there is no shading on a 1-bit bitmap. The compromises that make it fit a C64:
- Integer fixed-point everywhere — a 256-entry signed sine table (scale 128) drives the 3-D rotation; points are perspective-projected with integer divide.
- One object at a time, drawn with a Bresenham line routine straight into the VIC-II hi-res bitmap; each frame erases the previous edges and draws the new ones (no full-screen clear) so it stays flicker-free without double buffering.
- Precomputed bitmap row/column/bit-mask address tables for fast plotting.
- A custom cc65 linker config keeps program code below
$2000, reserves$2000–$3FFFfor the bitmap, and puts BSS/heap/stack above it.
Build it with cc65 and pack it onto a disk image with
VICE's c1541:
cd c64
make d64 # -> vectorgons.prg and vectorgons.d64
make run # build + launch in the VICE x64sc emulator
If cl65/c1541 aren't on your PATH, point the Makefile at them, e.g.
make d64 CL65=/path/to/cl65 CC65_HOME=/path/to/share/cc65. On real hardware (or
an emulator) load it the usual way and it autostarts:
LOAD"VECTORGONS",8,1
RUN
Press any key to return to BASIC.
Settings persistence
Your settings are saved automatically on exit to ~/.vectorgons (a plain
key=value text file) and reloaded on the next launch as the new defaults — so
the speed, tumble, render distance, density, colors, glow, fullscreen, etc. you
last used carry over. Delete the file to return to the built-in defaults; it's
safe to hand-edit (values are range-clamped on load).
On-screen display
A vector-drawn OSD (top-left) lists every setting and its key binding. It stays fully visible for 10 seconds after your last keypress, then fades out over a few seconds. Press any key to bring it back.
Controls
| Setting | Keys | Notes |
|---|---|---|
| Move camera | W/A/S/D |
Pan the camera up / left / down / right (hold to fly) |
| Rotate camera | ←/→/↑/↓ |
Yaw / pitch the camera view (hold to turn) |
| Approach speed | PgUp/PgDn |
How fast solids fly at you; PgDn past 0 reverses (fly backward) |
| Tumble rate | Q / E |
Base rotation speed |
| Tumble variance | T / Y |
Spread of tumble speeds: low = uniform, high = a very wide range (responds live) |
| Render distance | Z / X |
Radius of the field sphere / far plane (40–1520; hold to ramp) |
| Density | + / - |
Baseline number of solids (scales up with render distance; readout shows the live count) |
| Size min | U / J |
Minimum random solid size |
| Size max | I / K |
Maximum random solid size |
| Hue | [ / ] |
Base color |
| Hue cycle | C / V |
Continuously cycle through all hues (0 = off); all objects sweep the spectrum |
| Color mode | M |
Toggle single-hue ⇄ multicolor |
| Glow | O / L |
CRT glow / light bleed around vectors |
| Flicker | G / H |
Vector flicker intensity |
| Magnifier count | B / P |
How many magnifying glasses are on screen (0–1000, ±1 per press; extra, on top of the density field) |
| Glass-ball count | 1 / 2 |
How many refracting glass spheres are on screen (0–1000) |
| Mirror-ball count | 3 / 4 |
How many reflecting mirror spheres are on screen (0–1000) |
| Shapes | N |
Toggle random ⇄ cycling shape spawns |
| Fullscreen | F or F11 |
Toggle fullscreen |
| Perf HUD | F1 |
FPS / frame ms / body & draw-batch counts |
| VSync | F2 |
Toggle VSync (uncap the frame rate) |
| Glow mode | F3 |
Post-process bloom vs. legacy per-vector glow |
| Pause | Space |
|
| Quit | Esc |
Color modes
- Single hue: every solid uses the hue set with
[/]. - Multicolor: each solid gets its own hue (its base offset plus the
global hue, so
[/]rotates the whole palette). - Hue cycle (
C/V): continuously advances the base hue over time (up to ~120°/sec). In single-hue mode every object sweeps through the spectrum together; in multicolor mode the whole palette rotates. Set to0to stop.
How it works
-
Geometry comes from an N-dimensional polytope engine (3–6 dimensions). For regular figures, edges are derived automatically by connecting every vertex pair at the shared minimum distance; parametric families (prisms, stars, ...) set edges explicitly.
-
Higher-dimensional shapes keep their full 4/5/6-D coordinates and are rotated in their own dimension (driven by the tumble angle plus a per-body phase), then perspective-projected down to 3D each frame and renormalized to the unit sphere — which also keeps the no-overlap bound intact.
-
Asteroids scatter random points over a sphere at jittered radii and wire them up as a brute-force convex hull, so each rock is a unique faceted blob.
-
The field is a sphere of radius render distance centered on the camera. Bodies stream along −Z (or +Z when speed is negative) and, once they leave the sphere, are recycled back to the render-distance shell on the incoming side — so objects always appear far away and approach, never popping in close. They ease in over a fraction of a second at the shell. The initial fill spreads bodies through the volume so the field starts populated. Because culling is purely radial, the camera can yaw/pitch a full 360° without revealing an edge.
-
Render distance (
Z/X, 40–1520) sets the sphere radius and the perspective far plane. The active body count scales with it, so a deeper field simply holds proportionally more shapes (constant near-field density) instead of thinning out to empty space — theDENSITYreadout shows the live count, and+/-sets the baseline (the count at the default distance). -
The camera pans in world space via
WASDand yaws/pitches via the arrow keys (both polled per frame so movement is smooth and frame-rate independent); the rest of the field streams past it. -
No overlap: each solid's bounding-sphere radius equals its size, and spawn positions are rejection-sampled so no two spheres intersect (with a margin). Because every solid translates by the same amount each frame, non-overlap at spawn is preserved for the body's whole flight.
-
Tumble variance is applied live as a log-uniform spread (
spin = 4^(seed · variance)), so changing it re-spreads every solid's rotation rate instantly —0makes them all tumble alike,100gives a very wide range. -
CRT glow wraps each sharp vector in a soft phosphor mist. Two implementations, switchable live with
F3:- Bloom (default, when framebuffer objects are available): the field's sharp cores are rendered once, then a dual-filter pyramid (downsample, then tent-filter upsample accumulating each scale into the next finer one) builds a smooth, ever-widening, fading halo around the vectors. A fold factor attenuates the wider scales so the halo fades out and the blacks stay clean. This is one draw per body instead of nine, dramatically faster at high body counts.
- Legacy: each vector is redrawn as many faint additive width layers plus a few enlarged ghost copies. Kept as a fallback for GPUs without FBOs.
-
Performance / retained mode. Geometry is uploaded to vertex/index buffer objects once and drawn with
glDrawElementsinstead of per-vertex immediate mode, so the CPU isn't re-submitting millions of vertices per frame. Rigid shapes use static buffers; morphing 4/5/6-D polytopes stream positions into a shared buffer with a static index buffer; clocks and animated objects (whose topology is rebuilt each frame) stay on the immediate path. GL 1.5/3.0 entry points are resolved at run time viaglfwGetProcAddress, so the same code works on the Linux, Windows, and screensaver builds; both retained mode and bloom fall back gracefully if unavailable. -
Frustum culling. The field is a full sphere around the camera, so most bodies are behind or beside the view. Six view-frustum planes are extracted from the modelview·projection matrix each frame and each body's bounding sphere is tested before the (relatively costly) projection and draw; the cull margin includes the glow spread so edge halos don't pop. Off-screen bodies still advance and recycle — only their drawing is skipped. At a full field (~7200 bodies) this typically draws under ~1000 of them.
-
The
F1HUD shows FPS, frame time, and live body / drawn / draw-batch / vertex counts;F2toggles VSync;F3switches bloom vs. legacy glow. -
Magnifying glasses are real lenses. After the field is drawn, the back buffer is grabbed into a texture (
glCopyTexSubImage2D); each lens body is then projected to its screen position and drawn as a disc that samples that texture with a radial zoom — so the wireframes behind the glass appear genuinely enlarged, with the rim and handle drawn on top. (Pure legacy GL, so it works on the Linux, Windows, and screensaver builds alike.) -
Glass and mirror spheres reuse the same grabbed-framebuffer trick. The glass ball resamples it with an inverting fish-eye warp (refraction). The mirror ball is a convex mirror: it samples outward from its centre with an upright fish-eye, so the surrounding shapes wrap onto its surface the right way up, over an opaque metallic sky/ground gradient with a specular highlight.
-
Flicker randomly dips each solid's brightness per frame, scaled by the flicker setting.
-
Rendering uses legacy OpenGL immediate mode (
GL_LINES) with additive blending, MSAA, and line smoothing; depth-based fading brightens and thickens solids as they approach. The OSD uses a self-contained vector stroke font (no font dependencies).