/*
* Copyright 2011 Christian Thiemann <christian@spato.net>
* Developed at Northwestern University <http://rocs.northwestern.edu>
*
* This file is part of the SPaTo Visual Explorer (SPaTo).
*
* SPaTo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* SPaTo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with SPaTo. If not, see <http://www.gnu.org/licenses/>.
*/
package net.spato.sve.app;
import processing.core.PApplet;
public class Fireworks {
PApplet app = null;
public Fireworks(PApplet app) { this.app = app; }
Launcher ll[] = null;
float t0, tt, t, dt, t0max, tFinish = Float.NaN;
boolean started = false, finished = false;
float scale = 4;
float alpha = 0;
float grav = -9.81f;
int colors[] = new int[] {
app.color(255, 0, 0), // red
app.color(255, 150, 20), // orange
app.color(255, 225, 20), // yellow
//color(160, 250, 160), // light green
app.color( 50, 255, 50), // solid green
//color( 15, 250, 160), // cyan
app.color( 0, 127, 255), // light blue
app.color(180, 130, 250), // light purple
app.color(200, 40, 220), // solid purple
};
// int T = 0;
// int millis() { return super.millis() + T*1000; }
public void setup() {
app.randomSeed(app.second() + 60*app.minute() + 3600*app.hour());
// size(1280, 720);
app.size(800, 800*9/16);
// frameRate(30);
// smooth();
t0 = tt = app.millis()/1000.f;
// T = 40;
ll = new Launcher[] {
new RocketLauncher(30, t0 + 2, 18),
new TrailingRocketLauncher(10, t0 + 27, 8, 4, 75, true),
new TrailingRocketLauncher(11, t0 + 35, 6, 3, 75, true),
new TrailingRocketLauncher(11, t0 + 35, 6, 3, 75, false),
new ShellLauncher(10, t0 + 36, 5, true, app.color(250)),//, 240, 200)),
new ShellLauncher(20, t0 + 42, 20),
new ShellLauncher(5, t0 + 47, 15, true),
new ShellLauncher(30, t0 + 52, 10),
new ShellLauncher(1, t0 + 61, 1, true),
};
t0max = 0; for (Launcher l : ll) t0max = app.max(t0max, l.t0);
}
public void draw() {
t = app.millis()/1000.f; dt = t - tt; tt = t;
if (alpha == 1)
app.background(0);
else {
//background(255);
app.noStroke(); app.fill(0, 255*alpha); app.rect(0, 0, app.width, app.height);
}
//if (frameCount % 30 == 0) println((t - t0) + " " + frameRate);
float tAlpha = alpha;
if (!started) {
if (alpha < .999f) tAlpha = 1; else { alpha = tAlpha = 1; started = true; }
} else if (started && !finished) {
app.noFill();
app.pushMatrix();
app.translate(app.width/2, app.height);
app.scale(scale*app.width/1280, -scale*app.height/720);
finished = (t > t0max); // will be overriden if any of the launchers is still busy
for (Launcher l : ll) {
l.draw();
if (!l.finished)
finished = false;
}
app.popMatrix();
} else { // finished
if (Float.isNaN(tFinish)) tFinish = t;
if (t > tFinish + 5) tAlpha = 0; // fade out five seconds after last shell burned out
if (alpha < 0.001f) alpha = tAlpha = 0;
}
alpha += 3*(tAlpha - alpha)*app.min(dt, 1/3.f);
}
class Particle {
float t0, x, y, vx, vy, ax, ay; // creation time, position, velocity, acceleration
float f = .1f; // friction
int c; // color
float alpha = 1;
boolean solid = false, slowDecay = false;
boolean sparkling = false, isSparkling = false;
Particle(float t0, float x0, float y0, float vx0, float vy0, int c) {
this(t0, x0, y0, vx0, vy0, 0, 0, c); }
Particle(float t0, float x0, float y0, float vx0, float vy0, float ax0, float ay0, int c) {
this.t0 = t0; x = x0; y = y0; vx = vx0; vy = vy0; ax = ax0; ay = ay0; this.c = c; }
public void draw() {
int cc = c;
vx += ax*dt; vy += (ay + grav)*dt;
vx += -f*vx*dt; vy += -f*vy*dt;
x += vx*dt; y += vy*dt;
if (!solid) {
alpha -= (t - t0)*app.random(slowDecay ? .25f : 1)*dt;
cc = app.lerpColor(app.color(255), c, app.min(1, (.5f + .5f*app.noise(x, y, t/10))*(t - t0)/.75f));
}
if (sparkling) {
if (t - t0 > app.random(1, 2)) isSparkling = true;
if (alpha < 0) alpha = app.random(0, 1);
if (isSparkling && (app.random(1) < .25f)) cc = app.color(255);
if (t - t0 > app.random(3.5f, 4.5f)) sparkling = false;
}
alpha = app.max(0, alpha);
app.stroke(cc, 255*alpha);
app.point(x, y);
}
}
class Launcher {
Shell rr[] = new Shell[0];
boolean finished = true;
float t0;
Launcher(float t0) { this.t0 = t0; }
public void draw() {
finished = true;
for (Shell r : rr) {
if (t < r.t0) continue;
r.draw();
if (!r.finished)
finished = false;
}
}
}
class ShellLauncher extends Launcher {
ShellLauncher(int N, float t0, float deltat) { this(N, t0, deltat, false); }
ShellLauncher(int N, float t0, float deltat, boolean sparkling) { this(N, t0, deltat, sparkling, 0); }
ShellLauncher(int N, float t0, float deltat, boolean sparkling, int c) {
super(t0);
rr = new Shell[N];
for (int i = 0; i < N; i++) {
float x0 = 3*app.round((sparkling ? 5 : 10)*app.random(-1, 1)) + app.random(-1,1);
float vx = (sparkling ? 5 : 10)*app.random(-1, 1);
rr[i] = new Shell(app.random(t0, t0 + deltat), x0, 0, vx, app.random(45, 55), sparkling, c);
}
}
}
class RocketLauncher extends Launcher {
RocketLauncher(int N, float t0, float deltat) {
super(t0);
rr = new Shell[N];
for (int i = 0; i < N; i++)
rr[i] = new Skyrocket(app.random(t0, t0 + deltat), app.random(-2, 2), 0,
app.random(-2, 2), app.random(15, 25), -1.5f*grav);
}
}
class TrailingRocketLauncher extends Launcher {
TrailingRocketLauncher(int N, float t0, float deltat, int Ni, float v0) {
this(N, t0, deltat, Ni, v0, true); }
TrailingRocketLauncher(int N, float t0, float deltat, int Ni, float v0, boolean ltr) {
super(t0);
rr = new Shell[N*Ni]; int index = 0;
float dt = deltat/(Ni*(N + 1)); // time between individual rocket launches
float dx = 10;
float x0max = dx*(N-1)/2.f;
for (int ii = 0; ii < Ni; ii++) {
for (int i = 0; i < N; i++) {
float rt0 = t0 + (ii*(N-1) + i)*dt;
float x0 = dx*((ltr ? i : (N-1-i)) - (N-1)/2.f);
if (Ni > 1) x0 += (ltr ? -1 : 1)*.25f*dx;
float angle = PApplet.PI/5 * x0/x0max;
float v = v0*app.random(0.95f, 1.05f);
rr[index] = new TrailingSkyrocket(rt0, x0, 0, v*app.sin(angle), v*app.cos(angle), -.5f*grav);
rr[index].t1 = .5f;
index++;
}
ltr = !ltr;
}
}
}
class Shell extends Particle {
boolean mortarLaunch = true;
Particle pp[] = null;
float t1, x0;
boolean exploded = false;
boolean finished = false;
Shell(float t0, float x0, float y0, float vx0, float vy0) {
this(t0, x0, y0, vx0, vy0, false); }
Shell(float t0, float x0, float y0, float vx0, float vy0, boolean sparkling) {
this(t0, x0, y0, vx0, vy0, sparkling, 0); }
Shell(float t0, float x0, float y0, float vx0, float vy0, boolean sparkling, int c) {
super(t0, x0, y0, vx0, vy0, app.color(20)); f /= 10; solid = true;
t1 = app.random(3, 4);
this.x0 = x0;
createParticles(sparkling, c);
}
public void createParticles(boolean sparkling, int c) {
pp = new Particle[app.floor(sparkling ? app.random(150, 200) : app.random(100, 150))];
if (c == 0) c = colors[app.floor(app.random(0, colors.length))];
float size = sparkling ? app.random(30, 31) : app.random(8, 10);
for (int i = 0; i < pp.length; i++) {
float theta = app.random(-PApplet.PI, PApplet.PI);
float phi = app.random(0, PApplet.PI);
float nv = size*app.random(0.9f, 1.0f);
float nvx = nv*app.sin(theta)*app.cos(phi);
float nvy = nv*app.cos(theta);//sin(theta)*sin(phi);
// c = app.random(0, 1) < 0.8 ? colors[0] : app.color(255);
pp[i] = new Particle(0, 0, -100, nvx, nvy, c);
pp[i].sparkling = sparkling;
if (sparkling) pp[i].f *= 5;
}
}
public void draw() {
// shell physics
alpha = 1;
super.draw();
// trigger explosion
if (!exploded && (t - t0 > t1)) {
for (Particle p : pp) { p.t0 = t; p.x = x; p.y = y; p.vx += vx; p.vy += vy; }
exploded = true;
}
// draw crown particles
if (exploded) {
finished = true;
for (Particle p : pp) {
p.draw();
if (p.alpha > 1/255.f) finished = false;
}
}
// draw launch
if (mortarLaunch && (t - t0 < .25f)) {
float nu = (1 - (t - t0)/.25f);
app.stroke(app.lerpColor(app.color(255), app.color(255, 127, 0), nu), 255*nu);
app.point(x0, 2/scale);
}
}
}
class Skyrocket extends Shell {
Particle trail[] = new Particle[50]; int trailPointer = 0; float trailLast = 0;
float a;
class TrailParticle extends Particle {
float tau = .1f;
TrailParticle(float t0, float x0, float y0, float vx0, float vy0) {
super(t0, x0, y0, vx0, vy0, app.color(255)); alpha = 0.3f; f *= 10; }
public void draw() {
if (t - t0 < 1*tau) c = app.lerpColor(app.color(255, 255, 255), app.color(255, 255, 0), (t - t0)/tau);
else if (t - t0 < 2*tau) c = app.lerpColor(app.color(255, 255, 0), app.color(255, 0, 0), (t - t0)/tau - 1);
else if (t - t0 < 3*tau) c = app.lerpColor(app.color(255, 0, 0), app.color(127, 127, 127), (t - t0)/tau - 2);
else c = app.color(127, 127, 127);
alpha -= 0.2f*dt;
if (app.random(0, 10) < t - t0) alpha -= 0.05f;
alpha = app.max(0, app.min(1, alpha));
vx += app.random(-1, 1)*20*dt;
vx += ax*dt; vy += (ay + grav)*dt;
vx += -f*vx*dt; vy += -f*vy*dt;
x += vx*dt; y += vy*dt;
app.stroke(c, 255*alpha); app.noFill();
app.point(x, y);
}
}
Skyrocket(float t0, float x0, float y0, float vx0, float vy0, float a) {
super(t0, x0, y0, vx0, vy0); this.a = a;
mortarLaunch = false;
createParticles();
}
public void createParticles() {
pp = new Particle[app.floor(app.random(50, 150))];
int c = colors[app.floor(app.random(0, colors.length))];
float size = app.random(5, 10);
for (int i = 0; i < pp.length; i++) {
float theta = app.random(-PApplet.PI, PApplet.PI);
float phi = app.random(0, PApplet.PI);
float nv = size*app.random(0.5f, 1.5f);
float nvx = nv*app.sin(theta)*app.cos(phi);
float nvy = nv*app.cos(theta);//sin(theta)*sin(phi);
// c = app.random(0, 1) < 0.8 ? colors[0] : app.color(255);
pp[i] = new Particle(0, 0, -100, nvx, nvy, c);
}
}
public void generateTrail() {
if ((a > 0) && (t > trailLast + .05f)) {
trail[trailPointer] = new TrailParticle(t, x, y, vx, vy);
trailPointer = (trailPointer + 1) % trail.length;
trailLast = t;
}
}
public void draw() {
// draw tail
for (int i = 0; i < trail.length; i++) {
if (trail[i] == null) continue;
trail[i].draw();
if (trail[i].alpha < 1/255.f) trail[i] = null;
}
// propulsion
if (!exploded) {
if (t - t0 > t1 - 1) a = 0;
float v = app.sqrt(vx*vx + vy*vy);
ax = a*vx/v; ay = a*vy/v;
}
// generate trail
generateTrail();
// physics & draw
super.draw();
}
}
// does not explode; the trail is the effect
class TrailingSkyrocket extends Skyrocket {
float initTrailRate, trailRate = -1;
TrailingSkyrocket(float t0, float x0, float y0, float vx0, float vy0, float a) {
super(t0, x0, y0, vx0, vy0, a); f = 0;
pp = new Particle[0]; // no crown
trail = new Particle[500];
trailRate = initTrailRate = 7*app.sqrt(app.sq(vx0) + app.sq(vy0));
}
public void generateTrail() {
if (a == 0) trailRate = app.max(0, trailRate - .66f*initTrailRate*dt);
int N = app.floor(trailRate*dt);
if (app.random(1) < trailRate*dt - N) N++;
for (int i = 0; i < N; i++) {
float vx0 = 5*app.sqrt(app.sqrt(t - t0))*app.random(-1, 1);
float vy0 = 5*app.sqrt(app.sqrt(t - t0))*app.random(-1, 1);
trail[trailPointer] = new Particle(t, x - vx*dt*i/N, y - vy*dt*i/N, vx0, vy0, 0, -0.9f*grav,
(app.random(1) < .25f) ? app.color(255) : app.color(255, 225, 64));
trail[trailPointer].alpha = 0.3f;
trail[trailPointer].slowDecay = true;
trail[trailPointer].f *= 10;
trailPointer = (trailPointer + 1) % trail.length;
trailLast = t;
}
}
public void draw() {
super.draw();
finished = (a == 0);
for (Particle p : trail)
if ((p != null) && (p.alpha > 1/255.f))
finished = false;
}
}
}