sketch_210806a

Bomb.pde

class Bomb {
  
  int lifeTime;
  int time;
  PVector burstPos;
  
  color fireColor;
  
  float velocity;
  
  Particle[] particles;
  
  Bomb(int nparticles, int lifeTime) {
    this(nparticles, lifeTime, new PVector(0.0, 0.0));
  }
  
  Bomb(int nparticles, int lifeTime, PVector burstPos) {
    this(nparticles, lifeTime, burstPos, color(255, 0, 0, 128));  
  }
  
  Bomb(int nparticles, int lifeTime, PVector burstPos, color fireColor) {
    this(nparticles, lifeTime, burstPos, fireColor, 9.0);
  }

  Bomb(int nparticles, int lifeTime, PVector burstPos, color fireColor, float velocity) {
    this.lifeTime = lifeTime;
    this.time = 0;
    this.burstPos = burstPos;
    this.fireColor = fireColor;
    
    float randomRot = random(TWO_PI);
    
    particles = new Particle[nparticles];
    for (int k = 0; k < particles.length; k++) {
      float randomRadius = 0.25;
      float t = (float)(k + 0.5) / particles.length * TWO_PI + randomRot;
      PVector vel = new PVector(cos(t), sin(t));
      PVector randomVel = new PVector(random(0.0, 1.0) - 0.5, random(0.0, 1.0) - 0.5);
      PVector acl = new PVector(0.0, 0.25);
      
      randomVel.mult(randomRadius);
      vel.mult(velocity);
      vel.add(randomVel);
      particles[k] = new Particle(10, burstPos.copy(), vel, acl);
    }
  }
  
  void update() {
    if (time >= lifeTime) {
      return;
    }
    
    for (Particle particle : particles) {
      particle.update();
    }
    
    time = time + 1;
  }
  
  void draw() {
    if (time >= lifeTime) {
      return;
    }
    
    float a = alpha(fireColor);
    
    noFill();
    stroke(fireColor, a * (1.0 - smoothstep(lifeTime*0.75, lifeTime, time)));
    strokeWeight(2.5);
    for (Particle particle : particles) {
      particle.draw();
    }
  }
  
  boolean isAlive() {
    return time < lifeTime;
  }
}

Firework.pde

class Firework {
  
  int time;
  
  PVector burstPos;
  PVector startPos;
  PVector pos;
  
  boolean bursted;
  
  color[] palette;
  
  Bomb[] bombs;
  Band[] bands;
  int bandpt;
  
  Firework(PVector burstPos) {
    this(burstPos, new color[] {#FF0000, #00FF00, #0000FF});
  }
  
  Firework(PVector burstPos, color[] palette) {
    
    this.time = 0;
    
    this.burstPos = burstPos;
    this.startPos = new PVector(burstPos.x, height * 1.15);
    this.pos = startPos.copy();
    
    this.bursted = false;
    
    this.palette = palette;
    
    bombs = new Bomb[palette.length];
    float vel = random(9.0, 12.0);
    for (int k = 0; k < bombs.length; k++) {
      bombs[k] = new Bomb(32, 60, burstPos, palette[k], vel * (1.25 - (float)k / bombs.length));
    }
    
    bands = new Band[20];
    bandpt = 0;
  }
  
  void update() {
    if (bursted) {
      for (Bomb bomb : bombs) {
        bomb.update();
      }
    } else {
      pos = lerpVector(startPos, burstPos, 1.0 - pow(time / 60.0 - 1.0, 2));
      addBand(pos.copy());
    }
    
    for (Band band : bands) {
      if (band != null) {
        band.update();
      }
    }
    
    if (time >= 60) {
      bursted = true;
    }
    
    time = time + 1;
  }
  
  void draw() {
    if (bursted) {
      for (Bomb bomb : bombs) {
        bomb.draw();
      }
    }
    
    for (Band band : bands) {
      if (band != null) {
        band.draw();
      }
    }
  }
  
  void addBand(PVector p) {
    PVector r = new PVector(
      width / 120.0 * randomGaussian(),
      width / 120.0 * randomGaussian()
    );
    p.add(r);
    bands[bandpt] = new Band(p);
    bandpt = (bandpt + 1) % bands.length;
  }
}

class Band {
  
  int time;
  int lifeTime;
  
  PVector pos;
  float radius;
  
  Band(PVector pos) {
    this.pos = pos;
    this.radius = width / 20.0;
    lifeTime = 20;
  }
  
  void update() {
    if (time >= lifeTime) {
      return;
    }
    
    time = time + 1;
  }
  
  void draw() {
    if (time >= lifeTime) {
      return;
    }
    
    noStroke();
    fill(255, 240);
    circle(pos.x, pos.y, lerp(radius, 0, 1.0 - pow((float)time / lifeTime - 1.0, 2)));
  }
}

Particle.pde

class Particle {
  
  int time;
  
  PVector[] buffer;
  int bufpt;
  
  int len;
  PVector pos;
  PVector vel;
  PVector acl;
  
  Particle(PVector pos) {
    this(60, pos);
  }
  
  Particle(PVector pos, PVector vel, PVector acl) {
    this(60, pos, vel, acl);
  }
  
  Particle(int len, PVector pos) {
    this(len, pos, new PVector(0.0, 0.0), new PVector(0.0, 0.0));
  }
  
  Particle(int len, PVector pos, PVector vel, PVector acl) {
    this.len = len;
    this.pos = pos;
    this.vel = vel;
    this.acl = acl;
    
    this.time = 0;
    
    buffer = new PVector[len];
    for (int k = 0; k < buffer.length; k++) {
      buffer[k] = null;
    }
    bufpt = 0;
  }
  
  void update() {
    this.pos.add(this.vel);
    
    if (time > 10) {
      this.vel.add(this.acl);
    }
    
    buffer[bufpt] = this.pos.copy();
    bufpt = (bufpt + 1) % len;
    
    time = time + 1;
  }
  
  void draw() {
    beginShape();
    for (int k = 0; k < len; k++) {
      PVector pos = buffer[(bufpt + k) % len];
      if (pos == null) {
        continue;
      }
      vertex(pos.x, pos.y);
    }
    endShape();
  }
}

Utils.pde


PVector lerpVector(PVector start, PVector end, float t) {
  PVector p = new PVector(
    lerp(start.x, end.x, t),
    lerp(start.y, end.y, t),
    lerp(start.z, end.z, t)
  );
  return p;
}

color randomChoice() {
  color[] cls = new color[] {
    #D9333F, #C3D825, #2CA9E1, #EE7800, #BBBCDE, #DDDCD6, #68BE8D, #A59ACA, #FFDB4F, #00A381, #B44C97
  };
  return cls[(int)random(cls.length)];
}

color[][] palettes = new color[][] {
  {#D9333F, #C3D825, #2CA9E1},
  {#e2be2d, #d6da1f, #22bc79, #1a75a9},
  {#ff2f20, #f0762b, #f0aa3a, #219fd5},
};

color[] randomPalette() {
  return palettes[(int)random(palettes.length)];
}

// https://en.wikipedia.org/wiki/Smoothstep
float smoothstep(float edge0, float edge1, float x) {
  // Scale, bias and saturate x to 0..1 range
  x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); 
  // Evaluate polynomial
  return x * x * (3 - 2 * x);
}

float clamp(float x, float lowerlimit, float upperlimit) {
  if (x < lowerlimit)
    x = lowerlimit;
  if (x > upperlimit)
    x = upperlimit;
  return x;
}

sketch_210806a.pde

Firework[] fireworks;
int pt;

void setup() {
  size(800, 800);
  background(32);
  
  fireworks = new Firework[100];
}

void draw() {
  background(32);
  translate(width / 2.0, height / 2.0);
  scale(0.75);
  
  for (Firework fw : fireworks) {
    if (fw == null) {
      break;
    }
    fw.update();
    fw.draw();
  }
}

void mousePressed() {
  fireworks[pt] = new Firework(
    new PVector((mouseX - width / 2.0) * 1.25, (mouseY - height / 2.0) * 1.25),
    randomPalette()
  );
  pt = (pt + 1) % fireworks.length;
}

void keyPressed() {
  saveFrame("cover.png");
}