Water, smoke, cloth, and procedural worlds¶
Run python -m bruhanimate.demos.nature_demo for a six-panel gallery. Each
panel owns its simulation, updates at 30 ticks/sec, and resizes without a restart.
Presentation defaults to 60 FPS. Press q, Escape, or Ctrl+C to quit.
No extra dependencies, audio capture, or external data are needed.
Discover settings and presets¶
Use the typed catalog for editor completion of both settings and preset names.
effects.names() lists registered names; effects.water.presets() returns
independent settings copies keyed by preset name. effects.water.defaults()
returns a fresh WaterSettings. Editing one never changes registry defaults.
from bruhanimate import effects
print(effects.names())
print(effects.water.presets())
effects.water.play(preset="rain", seed=42, duration=10,
fps=60, updates_per_second=30, on_resize="resize")
Effect / settings |
Presets |
Behavior |
|---|---|---|
water / WaterSettings |
pond, rain, storm |
Reflecting waves with interacting, damped ripples. |
diffusion / DiffusionSettings |
coral, spots, stripes, organic |
Gray–Scott reaction–diffusion. Organic uses shaded, scattered seeds. |
smoke / SmokeSettings |
plume, ink, windy |
Advected density; smoke rises, ink sinks. Supports solid obstacle cells. |
metaballs / MetaballsSettings |
lava, orbit, outline |
Implicit blobs merge and separate; filled surfaces or outlines. |
cloth / ClothSettings |
flag, checker, banner |
Pinned, shaded traveling folds, carrying patterns, text, or custom ASCII. |
terrain / TerrainSettings |
hills, mountains, contours |
Perspective terrain flyover with slope lighting or elevation contours. |
CLI discovery uses the same registry:
bruhanimate list
bruhanimate info water
bruhanimate presets terrain
bruhanimate play smoke --preset ink --on-resize resize
bruhanimate play cloth --preset banner --set text=HELLO --on-resize resize
bruhanimate play diffusion --preset organic --seed 42 --on-resize resize
All settings validate types, finite numbers, ranges, and Literal choices before
allocation. color=False gives plain ASCII. Seeds are local to each effect;
equal settings, sizes, seeds, and updates reproduce output in the same dependency
versions. Settings are copied by the new effects; editing them after construction
does not reconfigure a running simulation.
Water and smoke controls¶
WaterSettings.wave_speed is cells per tick (0.05–0.7), damping retains motion
(0–1), drop_chance is a probability per tick (0–1), and strength is random-drop
displacement (0–5). The speed cap keeps the finite-difference scheme stable.
drop(x, y, strength) injects a smooth local disturbance; strength accepts
[-5, 5]. Outside coordinates are ignored. It becomes visible on the next update.
from bruhanimate import Buffer, WaterEffect, WaterSettings, play
def water(buffer: Buffer) -> WaterEffect:
effect = WaterEffect(buffer, " ", WaterSettings(drop_chance=0, seed=42))
effect.drop(buffer.width() // 2, buffer.height() // 2, strength=2)
return effect
play(effect_factory=water, duration=10, updates_per_second=30, on_resize="resize")
SmokeSettings.rise is cells per tick (0–0.9); turbulence controls horizontal
drift (0–0.9), decay retains density (0–1), and diffusion mixes neighboring cells
(0–0.25). source_width is a fraction of the viewport width (0.01–1); intensity
controls source density (0–1). style="ink" reverses the vertical flow.
This is an artistic advected field, not a pressure-solving fluid simulation.
set_obstacles(iterable_of_xy_cells) copies a new solid mask, clips outside
coordinates and clears density there. Passing an empty iterable removes it.
from bruhanimate import Buffer, SmokeEffect, SmokeSettings, play
def smoke(buffer: Buffer) -> SmokeEffect:
effect = SmokeEffect(buffer, " ", SmokeSettings(seed=42))
y = buffer.height() // 2
effect.set_obstacles((x, y) for x in range(buffer.width() // 3,
buffer.width() * 2 // 3))
return effect
play(effect_factory=smoke, duration=10, updates_per_second=30, on_resize="resize")
Cloth carrying your own art¶
ClothSettings.pattern accepts "flag", "checker", or "text"; text
defaults to BRUH. amplitude is a fraction of viewport height, wavelength counts
cycles across the cloth, and speed is radians per tick. The left edge is pinned.
This uses kinematic waves rather than a spring/force solver.
Custom art overrides the selected pattern. Supply text or a sequence of rows;
rows are copied, space-padded, centered at their original size, and clipped.
ANSI escapes and wide/combining glyphs are rejected. set_image(None) restores
the configured pattern; changes appear on the next update or resize.
from bruhanimate import ClothEffect, ClothSettings, play, text_to_image
art = text_to_image("BRUH", font="small")
play(effect_factory=lambda buffer: ClothEffect(
buffer, " ", ClothSettings(amplitude=0.1), image=art),
duration=10, fps=60, updates_per_second=30, on_resize="resize")
Metaballs, terrain, and reaction–diffusion¶
MetaballsSettings.count selects 1–20 blobs; radius is a fraction of the shorter visual viewport axis. Cells are treated as twice as tall as they are wide. speed is radians per tick; threshold sets the visible isosurface. Set filled=False for outlines. Blobs combine their fields, so overlapping surfaces merge.
TerrainSettings.speed is world units per tick; scale is noise frequency per world unit, roughness scales elevation, and horizon is a fraction of viewport height. style selects shaded terrain or contours. A fixed seeded tile provides four noise octaves; forward travel wraps periodically without growing memory. The camera height is fixed, so tall nearby terrain can fill the view.
Diffusion remains the existing reaction–diffusion built-in. New keyword-only
options shading=True and seed_pattern="scattered" add concentration
shading and seeds across the viewport. The organic preset enables both;
older presets retain their configuration. steps_per_frame remains the
historical name for numerical substeps per simulation tick. set_parameters
validates finite feed/kill rates in [0, 1] atomically before changing them.
Lifecycle and resize¶
These effects run indefinitely; use Player’s frames or duration limits to stop. Construction acquires no external resources, draw only copies the last raster, and close is safe before start and on repeated calls. Player/Scene/Panel own their children as usual; do not share an effect between owners. Public controls use local coordinates and never poll input. Wire bindings through Player if needed.
Water preserves top-left displacement and velocity; added cells are still.
Smoke preserves top-left density and obstacle cells; added cells are empty. Its source moves to the new top/bottom on the next tick.
Diffusion preserves concentrations; added cells start at U=1/V=0.
Metaballs and cloth remap at their existing phase; cloth retains its ASCII art.
Terrain keeps its noise tile and camera position and rebuilds the view.
Shrinking discards cropped simulation cells and obstacles. Resize advances no
time and consumes no randomness. See Terminal resize for the shared contract.
Individual demos are available as water_demo, smoke_demo, metaballs_demo,
cloth_demo, terrain_demo, and diffusion_demo under bruhanimate.demos.