Guides
Genie smoke and vortex rings
Both effects are smoke that has to hold its shape. The genie lamp is a warm, glowing jet wound into a slow column that puffs a ring every few seconds. The vortex-ring cannon fires tight, rolling rings that travel across the whole box.
#Genie smoke
genieLamp: blue smoke from the spout, wound into a column by a swirl field 1.84 m above it.const lamp = magic.addEffect('genieLamp', {
position: [0.56, 1.06, 0], // the spout tip
velocity: [0.7, 1.5, 0], // jet velocity; retarget() keeps its magnitude
color: '#4d8cff',
});
lamp.puff(); // launch a smoke ring nowposition is the spout tip, and every other offset is relative to it, so move() carries the whole column with it. The effect adds:
- a jet emitter with
density22,glow6 andtemperature0.55, driven towardvelocitywithblend0.5. The heat makes it rise. - a swirl column centred at
swirl.offset[-0.11, 1.84, 0]above the spout, around a vertical axis. It spins at 1.4, pulls in at 0.3 and lifts at 0.5, over a 1.05 m radius and 1.5 m thickness. - a ring every
rings.interval(3.6 s), launchedrings.offsetabove the spout alongrings.axis[0.15, 1, 0]. A genie ring lays a heavy puff of dye in its first 60 ms (early: 140) and none after that (dyeRate: 0). It has no analytic torus or confinement boost, so it's softer and shorter-lived (2.6 s) than a cannon ring. At most two are in flight. - occasional sparks near the spout (
sparks.rate0.05).
| Option | Default |
|---|---|
position, velocity | [0.56, 1.06, 0], [0.7, 1.5, 0] |
color, radius | [0.3, 0.6, 1], 0.2 |
glow, density, temperature, blend | 6, 22, 0.55, 0.5 |
swirl | { offset: [-0.11, 1.84, 0], axis: [0, 1, 0], radius: 1.05, speed: 1.4, pull: 0.3, thickness: 1.5, lift: 0.5 } |
rings | { interval: 3.6, offset: [0.05, 0.38, 0], axis: [0.15, 1, 0], speed: 1.1, drag: 0.35, life: 2.6, radius: 0.32, core: 0.12, shell: 0.07, dyeRate: 0, early: 140, glow: 1.2, confinement: 0, torus: null } |
sparks | { rate: 0.05, offset: [0, 0.15, 0], radius: 0.18, life: 0.45, jitter: 1.5, colorA, colorB } |
Set rings: { interval: 0 } to puff only when you call puff(), and swirl: { speed: 0 } for a free plume with no column.
#Vortex rings
vortexRings: the cannon fires every 1.05 s. Each ring lives 4.5 s, slows from 2.1 m/s with drag: 0.42, and fades as it nears the open faces of the box.const cannon = magic.addEffect('vortexRing', {
position: [-0.672, 0.948, -0.495], // launch point, just in front of the muzzle
direction: [0.781, 0.28, 0.558],
interval: 1.05, // 0 = only when you call fire()
});
cannon.fire(); // a ring now
cannon.fire({ radius: 0.6, color: '#ff4f8e' }); // one-off overrides for this shot
cannon.rings; // TravellingRing[] in flightA ring isn't a mesh. Each frame, the solver steers the fluid toward a velocity field that circulates around the ring's core and translates along its axis, and the ring lays dye in a thin shell around that core. Three things keep it a ring instead of a blob:
- A local confinement boost.
confinement(4) is added to the vorticity-confinement gain around the core, so the grid keeps the core tight on its own. - An analytic torus.
torusdraws a thin rolling torus in the volume on top of the fluid dye, so the hole never blurs shut at coarse grids.sharpnesssets the profile (2 = gaussian, higher = crisper edge), andstructuresets how much ring-local rolling noise it has. - A fade at the domain faces. As the ring's rim comes within 0.45 m of an open side or the top, the ring's dye, glow and torus fade out, so it never clips into a half-donut.
Over its life the ring slows as speed · e^(−drag · age), grows by up to growth (18 %), wobbles by wobble, and its dye and glow fade. For the first 60 ms it lays early (6) dye per second and triple glow, which gives the launch puff.
| Option | Default | |
|---|---|---|
position, direction | see above | Launch point and travel direction |
color, glow | [1, 0.86, 0.72], 0.16 | |
interval | 1.05 | Seconds between automatic shots (0 = manual) |
speed, drag, life | 2.1, 0.42, 4.5 | Launch speed (m/s), slow-down (1/s), lifetime (s) |
radius, core, shell | 0.44, 0.17, 0.055 | Ring radius, velocity-core radius, dye-shell thickness (m) |
growth, wobble | 0.18, 1 | Radius growth over life; axis wobble amount |
dyeRate, early | 0.6, 6 | Dye per second while travelling, and in the first 60 ms |
confinement | 4 | Extra vorticity-confinement gain around the core |
torus | { core: 0.085, density: 3, spread: 0.35, fadePower: 0.6, sharpness: 2.6, structure: 0.75, roll: 0.5 } | density: 0 turns the overlay off |
maxRings | 4 | The oldest ring is dropped past this |
The 4 ring slots are shared with the genie lamp's rings. See per-frame limits.
#Tuning
- Rings too soft? Raise
torus.sharpnessfor a crisper edge, or raise the quality tier. The core is only a few cells across onlow(0.1 m cells). - Rings breaking up early? The
vortexRingspreset lowersphysics.turbulenceto 0.25 anddampingto 0.02, so the air in the box stays still. Turbulence is noise forcing, and it tears rings apart. - Want to see the flow, not the torus?
set({ torus: { density: 0 } })leaves only the dye the fluid carries. - Rings on a timer from code: set
interval: 0and callfire()from your own events.fire(overrides)merges the overrides for that shot only.