Flow Field
Particles flow through a field which influences their velocity.
- Grid Size controls how far a particle needs to travel before being influenced by a new flow vector.
- Noise Factor controls how smooth the changes in flow are. A higher value means smoother transitions.
- Particle Speed controls how quickly particles move by influencing the overall strength of the field.
- Redistribute Particles randomizes the position of each particle.
- Randomize Flow Field re-seeds the noise function which controls flow vectors.
Source Code for this Sketch
let field
let gridsize = 20 // grid size
let drawVectors = true
let flowSmoothness = 10
let particles = []
let particleCount = 300
let particle_size = gridsize/2
let speed = 1
let flowSeed = 0
class Particle {
constructor(c) {
// initialize in a random position on screen
this.v = createVector(random(0, width), random(0, height));
if (c) {
this.color = c
} else {
colorMode(HSL)
this.color = color(random(0, 360), 100, 50)
}
}
get x() {
return this.v.x
}
get y() {
return this.v.y
}
reset() {
this.v.x = random(0, width)
// reset to the top.
// -particle_size/2 reduces the "popping" effect during reset
this.v.y = -particle_size/2
}
random() {
this.v.x = random(0, width)
this.v.y = random(0, height)
}
flow(vec) {
// If the vector is a zero value, re-initialize the particle
if (vec.x === 0 && vec.y === 0 || vec.x > width || vec.y > height) {
this.reset()
return
}
this.v.x += vec.x*speed
this.v.y += vec.y*speed
}
draw() {
stroke(this.color)
point(this.v.x, this.v.y)
}
}
function initializeParticles() {
// Particle initialization
for (let i = 0; i < particleCount; i++) {
if (particles[i]) {
continue
}
// Bias towards cooler colors
let hue = (360+randomGaussian(160, 85))%360
let sat = randomGaussian(80, 10)
let lit = randomGaussian(52, 5)
let c = color(hue, sat, lit)
particles[i] = new Particle(c)
}
particles = particles.slice(0, particleCount)
}
function randomFlowSeed() {
flowSeed = random(0, Number.MAX_SAFE_INTEGER)
noiseSeed(flowSeed)
let flowSeedValue = document.getElementById("flowSeedValue")
flowSeedValue.innerText = flowSeed.toFixed(0)
}
function initializeFlowField(x, y) {
return createVector(noise(x / flowSmoothness, y/flowSmoothness)*2-1,
noise(x / flowSmoothness, y / flowSmoothness))
}
function setup() {
createCanvas(800, 600, P2D, canvas)
chrisDefaults()
randomFlowSeed()
field = new FlowField(20, 20, gridsize, initializeFlowField)
field.resizeToCanvas()
initializeParticles()
}
function draw() {
background(100)
// Draw flow field
if (drawVectors) {
strokeWeight(.5)
stroke(0)
noFill()
field.draw()
}
strokeWeight(particle_size)
for (let i = 0; i < particles.length; i++) {
particles[i].flow(field.at(particles[i].x, particles[i].y))
particles[i].draw()
}
}
document.addEventListener("DOMContentLoaded", (e) => {
let p5ui = document.getElementById("p5ui")
p5ui.innerHTML = `
<div>
<label for="gridSizeInput">Grid Size</label>
<input id="gridSizeInput" type="range"
min="10" max="40" step="5" value="20">
<span id="gridSizeValue" class="mono">20</span>
<br>
<label for="flowSmoothnessInput">Flow Smoothness</label>
<input id="flowSmoothnessInput" type="range"
min="3" max="30" step=".1" value="10">
<span id="flowSmoothnessValue" class="mono">10.0</span>
<br>
<label for="speedInput">Particle Speed</label>
<input id="speedInput" type="range"
min=".5" max="5" step=".1" value="1.0">
<span id="speedValue" class="mono">1.0</span>
<br>
<label for="particleCountInput">Particle Count</label>
<input id="particleCountInput" type="range"
min="30" max="3000" step="10" value="300">
<span id="particleCountValue" class="mono">300</span>
<br>
<button id="redistribute">Redistribute Particles</button>
<button id="flowSeedInput">Randomize Flow Field</button>
<span id="flowSeedValue" class="mono"></span>
</div>
`
let gridSizeInput = document.getElementById("gridSizeInput")
let gridSizeValue = document.getElementById("gridSizeValue")
let flowSmoothnessInput = document.getElementById("flowSmoothnessInput")
let flowSmoothnessValue = document.getElementById("flowSmoothnessValue")
let speedInput = document.getElementById("speedInput")
let speedValue = document.getElementById("speedValue")
let particleCountInput = document.getElementById("particleCountInput")
let particleCountValue = document.getElementById("particleCountValue")
let redistribute = document.getElementById("redistribute")
let flowSeedInput = document.getElementById("flowSeedInput")
gridSizeInput.addEventListener("input", (e) => {
field.scale = Math.round(Number(e.target.value))
field.resizeToCanvas()
gridSizeValue.innerText = String(field.scale)
})
flowSmoothnessInput.addEventListener("input", (e) => {
// TODO get numeric type with 1 decimal precision (10.0) then cast to
// string. Using a string for the smoothness can result in weird behavior
flowSmoothnessValue.innerText = Number(e.target.value).toFixed(1)
flowSmoothness = e.target.value
field.visit(initializeFlowField)
})
speedInput.addEventListener("input", (e) => {
// TODO get numeric type with 1 decimal precision (10.0) then cast to
// string. Using a string for the speed can result in weird behavior
speedValue.innerText = Number(e.target.value).toFixed(1)
speed = e.target.value
})
particleCountInput.addEventListener("input", (e) => {
// TODO get numeric type with 1 decimal precision (10.0) then cast to
// string. Using a string for the particle count can result in weird behavior
particleCountValue.innerText = Number(e.target.value).toFixed(1)
particleCount = e.target.value
initializeParticles()
})
redistribute.addEventListener("click", (e) => {
for(let i = particles.length - 1; i >= 0; i--) {
particles[i].random()
}
})
flowSeedInput.addEventListener("click", (e) => {
randomFlowSeed()
field.visit(initializeFlowField)
})
})
You may use all or part of the p5 code seen on this page for your own creations, without restriction. Attribution is appreciated but not required.
This sketch also includes the following sources: p5common.js . Sources are available under their respective licenses.