Skip to content

Flow Fields

A flow field is a vector field that assigns a direction and speed to every point in space. In ivf2, FlowField uses curl noise to generate these fields — a mathematical construction that guarantees particles neither cluster into piles nor spread out to nothing. The result is organic, fluid-like streams that evolve slowly over time.

How Curl Noise Works

Curl noise derives a velocity field from the curl (rotation) of a scalar potential. Because the curl of any field is divergence-free, the resulting flow has no sources or sinks. Particles steered by curl noise meander continuously without accumulating or dispersing.

Under the hood, FlowField layers multiple octaves of Perlin noise (fBm) to add detail at different scales, then takes finite differences to approximate the curl. The field also evolves over time so the streams shift gradually.

Setup

#include <ivf/flow_field.h>   // included via <ivf/gl.h>

auto field = ivf::FlowField::create();
field->setScale(0.25f);                            // spatial feature size
field->setStrength(3.5f);                          // peak velocity (world units/sec)
field->setOctaves(2);                              // fBm detail layers
field->setTimeScale(0.06f);                        // how fast the field evolves
field->setOffset(glm::vec3(31.4f, 17.3f, 57.1f)); // move away from noise origin

Why set an offset?

Perlin noise is nearly zero near the origin. An offset places your field in a well-varied region of the noise domain, giving diverse flow directions across the scene.

With a Particle System

The simplest integration is applyToParticleSystem(). It installs a steering function that blends each living particle's velocity toward the local curl-noise velocity each frame.

// In onSetup():
m_ps = ivf::ParticleSystem::create(8000);
m_ps->setEmitRate(400.0f);
m_ps->setLifetime(6.0f, 12.0f);
m_ps->setGravity(glm::vec3(0.0f));          // no gravity — let the field do the work
m_ps->setEmitFromBox(
    glm::vec3(-8.0f, -0.5f, -8.0f),
    glm::vec3( 8.0f,  0.5f,  8.0f)
);
m_ps->start();
add(m_ps);

field->applyToParticleSystem(m_ps, 3.0f);  // blendRate: higher = snappier response

The blendRate parameter controls how quickly particles align with the field:

blendRate Character
0.5 Sluggish — inertia dominates, flow is subtle
3–5 Responsive — particles follow the field closely
10+ Instant — particles move exactly with the field

Driving with TimeController

Call setTime() every frame so the field evolves:

void onUpdate() override
{
    double t  = ivf::TimeController::instance()->elapsed();
    double dt = ivf::TimeController::instance()->delta();

    m_field->setTime(static_cast<float>(t));
    m_ps->update(static_cast<float>(dt));
}

Because TimeController::elapsed() respects pause and scale, pausing the app freezes the field and the particles simultaneously.

Manual Sampling

Sample any position in the field without a particle system:

glm::vec3 pos = {x, y, z};
glm::vec3 vel = field->sampleVelocity(pos);

// For flat (XZ-plane) scenes:
glm::vec2 vel2d = field->sampleVelocity2D(x, z);  // y component is 0

Useful for steering custom objects, driving deformers, or visualising the field with line primitives.

Parameter Guide

Parameter Method Effect
Scale setScale(float) Smaller → broader, slower swirls; larger → tight, fast eddies
Strength setStrength(float) Peak particle speed in world units per second
Octaves setOctaves(int) More octaves add fine vortex detail (1–4 is practical)
Lacunarity setLacunarity(float) Frequency multiplier per octave (default 2.0)
Gain setGain(float) Amplitude decay per octave (default 0.5)
Time scale setTimeScale(float) Field evolution speed — 0 freezes the field pattern
Offset setOffset(glm::vec3) Domain shift — escape the near-zero noise origin

Live-Tweak Sliders

Use ImGui sliders to explore parameters in real time:

void onDrawUi() override
{
    ImGui::Begin("Flow Field");
    bool changed = false;
    changed |= ImGui::SliderFloat("Scale",      &m_scale,     0.05f, 2.0f);
    changed |= ImGui::SliderFloat("Strength",   &m_strength,  0.1f, 10.0f);
    changed |= ImGui::SliderInt  ("Octaves",    &m_octaves,   1, 4);
    changed |= ImGui::SliderFloat("Time speed", &m_timeScale, 0.0f, 0.5f);
    if (changed) {
        m_field->setScale(m_scale);
        m_field->setStrength(m_strength);
        m_field->setOctaves(m_octaves);
        m_field->setTimeScale(m_timeScale);
    }
    ImGui::End();
}

See the full working example at Flow Field Examples.