Goo-like particle system simulation running on the GPU.
Something similar to Sebastian Lague's Ant and Slime Simulations - particles following one another's trails, but without looking at the code.
Runs as a window (goo) or as an animated desktop wallpaper: goo-macwp on macOS, goo-wlwp on wayland (layer-shell), goo-x11wp on X11. Still being tinkered with, so expect the tunables to move around.
Via Homebrew (macOS and Linuxbrew):
brew install lczyk/pixel-goo/gooThis installs the goo binary (plus goo-macwp, the desktop-wallpaper variant, on macOS).
If Homebrew refuses to load the tap with a trust error, it's the third-party tap gate (HOMEBREW_REQUIRE_TAP_TRUST). Trust it once and retry:
brew trust --tap lczyk/pixel-goo./nob.c # show usage (default target)
./nob.c build # compile bin/goo
./nob.c run # build and run
./nob.c clean # remove bin/ and the generated headers
./nob.c build-macwp # macos wallpaper -> bin/goo-macwp
./nob.c build-wlwp # wayland wallpaper -> bin/goo-wlwp
./nob.c build-x11wp # x11 wallpaper -> bin/goo-x11wp
./nob.c shaders # expand shader includes into shaders_pp/ and embed them, nothing elseOnly goo is compiled in by default. The other sims are opt-in at build time:
GOO_SIM_POLLOCK=1 ./nob.c build # + the three-family sim
GOO_SIM_POLLOCK=1 GOO_SIM_BLANK=1 ./nob.c build # + the blank reference sim as wellThere is more than one simulation, though a default build only carries goo -- see the build flags
above. --core.sim picks between whichever ones were compiled in, and --help lists them.
pollock (GOO_SIM_POLLOCK=1) is three particle families that each sense only their own density
field -- goo's force law three times over, differing in nothing but hue.
blank (GOO_SIM_BLANK=1) is one density channel, goo's repulsion on it, white particles, nothing
else. It is there to be copied when starting a new sim (sim_blank.c implements the least a SimDef
can and still draw), and as a control: if a change to core moves blank too, the change was core's.
With more than one compiled in, tab switches between them at runtime -- the physics cross-fades
over --core.switch-frames rather than cutting. --core.switch-every auto-cycles, and on the
wallpaper builds, which never take keyboard focus, SIGUSR1 does the same job. With only goo
built, all three are no-ops.
Flags are namespaced by what owns them:
--core.*-- the shared simulation: particle count, render scale, the density field, the repel/exclusion field, drag, dither, colormap exposure, switching.--goo.*-- the goo simulation itself: its trail field and the force it exerts.--pollock.*-- the pollock simulation. Empty for now: its physics is entirely--core.*and its colours are fixed in the shader.--window.*-- windowing and output: geometry, monitor, vsync, dump/headless.
Each simulation contributes its own flags, and every compiled-in sim's set is present whichever one
is running -- picking a sim is a runtime decision, so they all have to resolve at startup. A sim left
out at build time takes its flags with it. The
density field is a core service each sim instantiates -- goo with one channel, pollock with one per
family -- so every --core.dens-* parameter means the same thing in all of them. --core.dens-sub is
defined per channel, which is what keeps a three-channel field at the same particle count, and so
the same magnitude, as a single-channel one.
Short flags (-p, -d, -r, -m, -N) are unprefixed, as are --help, --version and --dump-params.
default-params.ini is the source of truth for the defaults and is baked into the binary at build time. Its [section] headers are the flag prefixes, so [goo] trail-tau = 6.15 is --goo.trail-tau=6.15 on the command line. Edit a value there, rebuild, and the default changes.
The resolved config can be printed and fed back in:
goo --dump-params # print every resolved value as ini
goo --dump-params | goo - # ... and run with it (a bare - reads config from stdin)Layering is defaults, then stdin config, then command line -- later wins.
./nob.c format # clang-format the top-level *.c/*.h (skips lib/)- automagically include shader files at compile time
- fullscreen mode is still a bit janky
- no acceleration and velocity shaders
- no velocity double buffer
- fix segfault buggs
- ? add frame rendering
- trail buffer colormap sampling
- screen rendering shader could blend between density and trail colormap
- ? better lerp in screen rendering shader