class Snowfall {
constructor(width, height, particleSize=5, particleCount=4000) {
this.width = width;
this.height = height;
this.particleSize = particleSize;
this.particleCount = particleCount;
this.particles = [];
this.gravity = 0.2;
this.drag = 0.5;
this.noise = new noisejs.Noise(0);
this.noiseScale = 60;
this.noiseStrengthFactor = 0.4;
this.timer = 0;
}
makeParticle() {
return {
x: Math.random() * this.width,
y: Math.random () * this.height * -1,
z: Math.random() * this.height,
r: (Math.random() * this.particleSize / 2) + 0.1,
dx: (Math.random() - 0.5) * 10,
dy: 0
}
}
drift(particle, options) {
const windDirection = (this.noise.perlin3(
particle.x / options.noiseScale,
particle.y / options.noiseScale,
particle.z / options.noiseScale + this.timer * options.noiseSpeed,
) + Math.random() * 0.1) * Math.PI;
const windStrength = this.noise.perlin2(
particle.x / options.noiseScale,
particle.y / options.noiseScale
) * options.noiseStrength;
const cursorDistance = Math.sqrt(
Math.pow(mutable cursor.x - particle.x, 2) +
Math.pow(mutable cursor.y - particle.y , 2)
);
const cursorDirection = Math.atan2(
mutable cursor.x - particle.x,
mutable cursor.y - particle.y
)
const cursorStrength = options.cursorStrength / Math.pow(1 + cursorDistance / 100, 2);
return {
z: particle.z,
x: particle.x + particle.dx,
dx: (
particle.dx
+ Math.sin(windDirection) * windStrength
- Math.sin(cursorDirection) * cursorStrength
- (particle.dx ** 2 * options.drag * Math.sign(particle.dx) / particle.r)
)
,
y: particle.y + particle.dy,
dy: (
particle.dy
+ Math.cos(windDirection) * windStrength
- Math.cos(cursorDirection) * cursorStrength
- (particle.dy ** 2 * options.drag * Math.sign(particle.dy) / particle.r)
+ options.gravity
)
,
r: particle.r
}
}
tick(options) {
this.timer += 1;
const newParticles = this.particles.filter(
d => d.y < (this.height + this.particleSize) && d.x > -50 && d.x < width + 50
).map(
d => this.drift(d, options)
);
while (newParticles.length < options.particleCount) {
newParticles.push(this.makeParticle())
}
this.particles = newParticles;
}
draw(context, options) {
context.save()
context.fillStyle = 'black'
context.globalAlpha = 0.5
context.fillRect(0, 0, this.width, this.height);
if (options.showCursor) {
context.beginPath();
context.arc(mutable cursor.x, mutable cursor.y, options.cursorRadius, 0, TWOPI);
context.fillStyle = 'green'
context.fill()
}
for (let i = 0; i < this.particles.length; ++i) {
const p = this.particles[i];
const fadeOutFactor = (options.fadeOut ? 1 - Math.min(p.y / this.height, 1) : 1) ** 0.5;
context.beginPath();
context.fillStyle = 'white'
context.globalAlpha = 1 * fadeOutFactor;
context.arc(p.x, p.y, p.r * fadeOutFactor, 0, TWOPI);
context.fill();
}
context.restore()
}
}