Force fields#

A force field applies a spatially varying acceleration to the particles of an entity every substep. Use one to push a fluid with wind, swirl a liquid into a vortex, add drag, or drive turbulence, without scripting per-particle forces yourself.

Despite the name, a force field is an acceleration field: it contributes an acceleration in m/s², not a force in newtons. Acceleration is the physically meaningful quantity here, because force has no notion of spatial density and a field returns a value at every point in space. Genesis World integrates the acceleration into each affected particle’s velocity as vel += acc * substep_dt.

Force fields act only on particle-based entities stepped by the PBD and SPH solvers (fluids, cloth, and other particle materials). They do not affect rigid bodies, MPM, or FEM entities. To apply a world force to a rigid body, use its entity API instead.

Note

No runnable example ships for this feature yet, so the snippet below is illustrative. Every symbol and argument in it is taken from the live API (gs.force_fields) and the Force field API, but the script has not been added to examples/.

Minimal example#

Add a field to a scene, activate it, build, and step. Here a constant crosswind blows a column of SPH liquid sideways as it falls:

import genesis as gs

gs.init(backend=gs.cpu)

scene = gs.Scene(
    sim_options=gs.options.SimOptions(dt=4e-3, substeps=10),
    sph_options=gs.options.SPHOptions(
        lower_bound=(-0.5, -0.5, 0.0),
        upper_bound=(0.5, 0.5, 1.0),
        particle_size=0.01,
    ),
    show_viewer=True,
)

scene.add_entity(gs.morphs.Plane())
liquid = scene.add_entity(
    material=gs.materials.SPH.Liquid(),
    morph=gs.morphs.Box(pos=(0.0, 0.0, 0.65), size=(0.4, 0.4, 0.4)),
)

# Instantiate, then register with the scene *before* building.
wind = gs.force_fields.Constant(direction=(1, 0, 0), strength=5.0)  # 5 m/s² along +X
scene.add_force_field(wind)

scene.build()

wind.activate()  # fields start inactive; turn it on to apply acceleration
for i in range(1000):
    scene.step()

The lifecycle#

A force field goes through four stages. Getting the order right matters: adding after build() or forgetting to activate are the two most common mistakes.

  1. Instantiate a field from gs.force_fields.*, passing its parameters:

    wind = gs.force_fields.Constant(direction=(1, 0, 0), strength=5.0)
    
  2. Register it with the scene using scene.add_force_field(field), which returns the same field. This must happen before scene.build(): the number of registered fields is baked into the compiled solver kernels, so a field added afterward is never evaluated.

  3. Activate it with field.activate(). A field is inactive when created (field.active is False) and contributes zero acceleration until activated. You can toggle activate() and deactivate() at any time after building, including mid-simulation, and read field.active to check the current state.

  4. Step the scene. On each substep the active field’s acceleration is added to every affected particle.

Available field types#

All fields subclass gs.force_fields.ForceField and return an acceleration in m/s². Unless noted, a positive strength scales the effect and vectors follow the right-handed, Z-up frame.

Field

Effect

Key parameters

Constant

Uniform acceleration everywhere, like a steady gravity or wind.

direction (normalized), strength (m/s²)

Wind

Constant acceleration, but only inside an infinite cylinder; zero outside.

direction (cylinder axis, normalized), strength (m/s²), radius (m), center (m)

Point

Attracts particles toward a point or repels them, set by the sign of strength, with distance falloff.

strength (m/s²), position (m), falloff_pow, flow

Drag

Acceleration opposing velocity, combining a linear and a quadratic term.

linear (1/s), quadratic (1/m)

Noise

Independent random acceleration per particle per step, uniform in [-strength, strength] on each axis.

strength (m/s²)

Vortex

Swirls particles around an axis with tangential and radial components, with distance falloff.

direction (axis), center (m), strength_perpendicular (+ counterclockwise), strength_radial (+ inward), falloff_pow, falloff_min (m), falloff_max (m), damping

Turbulence

Smoothly varying acceleration sampled from 3D Perlin noise.

strength (m/s²), frequency (spatial), flow, seed

Custom

Your own acceleration function of position, velocity, time, and particle index.

func

For Custom, func is a Quadrants kernel function (a Python callable wrapped with @qd.func) with the signature f(pos, vel, t, i) -> vec3, returning the acceleration in m/s²:

import quadrants as qd


@qd.func
def swirl(pos, vel, t, i):
    return qd.Vector([-pos[1], pos[0], 0.0], dt=gs.qd_float)  # m/s²


scene.add_force_field(gs.force_fields.Custom(swirl))

Turbulence is built from gs.force_fields.PerlinNoiseField, a standalone 3D Perlin noise generator (parameters wrap_size, frequency, seed, seed_offset). You rarely construct it directly; reach for it only when writing a Custom field that needs coherent noise.

Notes and gotchas#

Warning

Register before building. scene.add_force_field(...) must be called before scene.build(). The active-field count is compiled into the solver kernels, so a field added after building is silently ignored.

  • It is acceleration, not force. The value is in m/s² and is independent of particle mass, so heavy and light particles gain the same velocity per step. Multiply by mass yourself if you are reasoning about forces.

  • Only PBD and SPH particles are affected. Rigid bodies, MPM, and FEM entities ignore force fields entirely. Add a particle-based entity (an SPH liquid, a PBD cloth or fluid) for the field to have anything to act on.

  • Fields stack. Every active field is summed, so you can combine, for example, a Constant gravity offset with a Drag term and a Turbulence gust.

  • Activation is free to toggle. Because activation flips a runtime flag rather than the compiled kernel, switching a field on or off mid-simulation costs nothing and needs no rebuild.

See also#