{
let {
radius, trailFade, maxSpeed, particlesPerSecond, gridStep, colorAssignment, colorScheme, seed
} = params;
AA.random.seed(seed / 100);
const roundedWidth = Math.floor(width / 100) * 100;
const sim = new AA.Simulation({
width: roundedWidth,
height: roundedWidth / 2,
gridStep,
beforeTick: () => {
addParticles();
for (let ac of sim.actors) {
if (!ac.isCentroidWithin(sim)) ac.remove();
}
}
}).vis({
baseColor: 0xffffff,
background: trailFade > 0 && trailFade < 1,
alpha: trailFade
});
invalidation.then(() => sim.end());
const angleShift = AA.random.uniform_01() * 2 * Math.PI;
const angleMultiplier = (21 - params.smoothness) * 2 * Math.PI;
q5.noiseSeed(params.seed);
for (let sq of sim.squares) {
sq.state.fieldVector = AA.Vector.fromPolar(1, q5.noise(
sq.x / sim.width - sim.width / 2,
sq.y / sim.height - sim.height / 2
) * angleMultiplier + angleShift);
}
const particlesThisTick = AA.random.poisson(particlesPerSecond / 60);
const simDiagonal = Math.hypot(sim.width, sim.height);
const scheme = d3[`interpolate${colorScheme}`];
function addParticles() {
for (let i = 0, n = particlesThisTick(); i < n; i++) {
const x = sim.randomX(radius);
const y = sim.randomY(radius);
const color = rgbToHex(scheme(colorAssignment === 'random'
? AA.random.uniform_01()
: Math.hypot(x, y) / simDiagonal
));
new AA.Actor({
x,
y,
radius,
maxSpeed
}).vis({tint: color})
.addTo(sim);
}
}
sim.interaction.set('noise-field', {
group1: sim,
group2: sim.actors,
behavior: 'custom',
force: (_, ac) => ac.squareOfCentroid().state.fieldVector
});
const options = {
stats: true,
backParticles: true,
basicCircleRadius: 8
};
if (trailFade < 1) {
options.clearBeforeRender = false;
options.preserveDrawingBuffer = true;
}
return AV.visObs(sim, options);
}