vectorgons/README.md
2026-06-12 11:36:39 -07:00

344 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 (26 rings), **orbital atoms** (26 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 (01000)** — 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 (≈726) 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`).
```sh
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.
```sh
# 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 run `vectorgons.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](https://emscripten.org),
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__`.
```sh
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`:
```sh
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:
```html
<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';
```
## 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 (401520; 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 (01000, ±1 per press; extra, on top of the density field) |
| Glass-ball count | `1` / `2` | How many refracting glass spheres are on screen (01000) |
| Mirror-ball count | `3` / `4` | How many reflecting mirror spheres are on screen (01000) |
| 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 to `0` to stop.
## How it works
- **Geometry** comes from an N-dimensional polytope engine (36 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`, 401520) 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 — the `DENSITY` readout shows the live
count, and `+`/`-` sets the baseline (the count at the default distance).
- **The camera** pans in world space via `WASD` and 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 — `0` makes them all tumble alike, `100` gives 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 `glDrawElements` instead 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 via `glfwGetProcAddress`, 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 `F1` HUD shows FPS, frame time, and live body / drawn / draw-batch /
vertex counts; `F2` toggles VSync; `F3` switches 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).