I'm working on a firework display with the p5.js library (although I doubt this will effect answers). My current program uses the p5.js background function with can create an afterimage for the entire project. You can view the project here: https://editor.p5js.org/KoderM/sketches/WsluEg00h
But you can also view the code here:
let particles, FPS;
function setup() {
createCanvas(1000, 800);
background(220);
particles = [];
FPS = new FPSMonitor(50, 50);
}
function draw() {
colorMode(RGB);
background(0, 0, 0, 25);
for(let p in particles){
if(particles[p].isDead()){
particles.splice(p, 1);
continue;
}
particles[p].update();
}
FPS.update();
}
function mousePressed(){
particles.push(new firework(mouseX, mouseY, random(0, 255)));
particles[particles.length-1].velocity = p5.Vector.random2D();
particles[particles.length-1].velocity.mult(random(0.5, 10));
}
class FPSMonitor {
constructor(x, y){
this.x = x;
this.y = y;
this.size = 100;
this.delay = millis();
this.mouse = [0, 0];
}
update(){
this.checkMouse();
if(this.mouse[0] !== 0){
this.x = mouseX - this.mouse[0];
this.y = mouseY - this.mouse[1];
}
textAlign(LEFT, TOP);
textSize(this.size/6);
rectMode(CORNER);
strokeWeight(3);
stroke("red");
fill("white");
rect(this.x, this.y, this.size*1.2, this.size);
strokeWeight(4);
stroke("black");
text("FPS: " + round(1/(millis()-this.delay)*1000, 3), this.x+5, this.y+5);
text("Average FPS:", this.x+5, this.y+25);
text(round(frameCount/millis()*1000, 3), this.x+5, this.y+48);
text("MS: " + round(millis()-this.delay), this.x+5, this.y+72);
this.delay = millis();
}
checkMouse(){
if(mouseIsPressed && this.mouse[0] !== 0){
return;
}
if(this.x < mouseX && (this.x + this.size) > mouseX &&
this.y < mouseY && (this.y + this.size) > mouseY && mouseIsPressed){
if(this.mouse[0] == 0){
this.mouse = [ mouseX - this.x, mouseY - this.y ]
}
return;
}
this.mouse = [0, 0];
}
}
Particle.js:
class particle {
constructor(x, y, hue, gravity, life, weight, renderFunction){
if(!hue){ throw new TypeError(this + " : hue is not defined") }
this.defaults = {
x: 0,
y: 0,
gravity: createVector(0, 0),
life: 100,
weight: 1,
renderFunction: (self) => {colorMode(HSB);strokeWeight(2);stroke(this.hue, 255, 255, this.life/this.maxLife);point(this.position.x, this.position.y)}
}
this.position = x && y ? createVector(x, y) : createVector(this.defaults.x, this.defaults.y);
this.gravity = gravity || this.defaults.gravity;
this.life = life || this.defaults.life;
this.maxLife = this.life;
this.acceleration = createVector(0, 0);
this.velocity = createVector(0, 0);
this.weight = weight || this.defaults.weight;
this.renderFunction = renderFunction || this.defaults.renderFunction;
this.hue = hue;
this.otherInfo = {
mouseAtStart: createVector(mouseX, mouseY),
}
}
isDead(){
return this.life < 0;
}
applyForce(force){
this.acceleration.add(force);
}
update(){
this.life--;
this.acceleration.add(this.gravity);
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
this.velocity.mult(this.weight*0.96>1?0.96:this.weight*0.96);
this.acceleration.mult(0.1);
this.renderFunction(this);
}
}
And finally, firework.js:
class firework {
constructor(x, y, hue){
this.renderFunction = self => {
colorMode(HSB);
strokeWeight(3);
stroke(self.hue, 255, 255, (self.life+self.maxLife*0.5)/self.maxLife)
//line(self.otherInfo.mouseAtStart.x, self.otherInfo.mouseAtStart.y, self.position.x, self.position.y);
point(self.position.x, self.position.y);
};
this.explodeRenderFunction = self => {
colorMode(HSB);
strokeWeight(3);
stroke(self.hue, 255, 255, self.life/self.maxLife)
//line(self.otherInfo.mouseAtStart.x, self.otherInfo.mouseAtStart.y, self.position.x, self.position.y);
point(self.position.x, self.position.y);
}
this.particle = new particle(x, y, hue, createVector(0, 0.1), height/53.3 * 4, 1, this.renderFunction);
this.particle.applyForce(createVector(random(-3, 3), random(height/-53.3, height/-43.3)));
this.explodeParticles = [];
this.exploded = false;
this.hue = hue;
}
update(){
this.particle.update();
if(this.particle.isDead() && !this.exploded){
this.particle.renderFunction = (self) => {};
this.exploded = true;
for(let p = 0; p < 500; p++){
this.explodeParticles.push(new particle(this.particle.position.x, this.particle.position.y, this.hue, createVector(0, 0.1), 100, 1, this.explodeRenderFunction));
this.explodeParticles[this.explodeParticles.length-1].velocity = p5.Vector.random2D();
this.explodeParticles[this.explodeParticles.length-1].velocity.mult(random(0.5, 10));
}
}
if(this.exploded){
for(let p in this.explodeParticles){
if(this.explodeParticles[p].isDead()){
this.explodeParticles.splice(p, 1);
continue;
}
this.explodeParticles[p].update();
}
}
}
isDead(){
return this.explodeParticles.length == 0 && this.exploded;
}
}
The fireworks DON'T look at all like fire works without the trail the afterimage provides, but I've also implemented an FPS monitor I created, which also blurs because of the background function (this effect is unwanted.)
A bit more information on the background function:
I'm using the syntax: background(v1, v2, v3, [a]) v1, v2, and v3 are HSB variables. The optional variable [a] is defined as: Opacity of the background relative to current color range (default is 0-255)
The full background website: https://p5js.org/reference/#/p5/background
THE QUESTION
How do I have the fireworks look the same, WITHOUT having other things in the canvas be effected? For example, the FPS monitor in the project can move by dragging your mouse, but it also has that afterimage effect that comes from the background function. I want the fireworks to stay the same, but anything else that renders needs to be "not" blurry.
Really appreciate any help.
You could use separate "layers" in p5.js using createGraphics().
For example, the fireworks class could hold it's p5.Graphics instance which it can use to render the effect into, then in the main sketch's draw() you'd call image(), passing the p5.Graphics instance (as if it was an image) to display into the main p5.js canvas.
(Off-topic, you could look into object pooling to reset/reuse "dead" particles instead of deleting/re-allocating new ones)
Update
Seems to work, here's what an approach to what I meant:
let particles, FPS;
// independent layers to render graphics into
let particlesLayer;
let fpsLayer;
function setup() {
createCanvas(1000, 800);
background(0);
particles = [];
FPS = new FPSMonitor(50, 50);
// particles will take up the whole sketch
particlesLayer = createGraphics(width, height);
// we can get away with a smaller frame buffer for the FPS meter
fpsLayer = createGraphics(256, 216);
}
function draw() {
particlesLayer.colorMode(RGB);
particlesLayer.background(0, 0, 0, 25);
for(let p in particles){
if(particles[p].isDead()){
particles.splice(p, 1);
continue;
}
particles[p].update();
}
// render the fireworks layers into the main sketch
image(particlesLayer, 0, 0);
// pass the layer to render the fps meter into and display it
FPS.update(fpsLayer);
}
function mousePressed(){
// pass the particles layer to each firework instance to render into
particles.push(new firework(mouseX, mouseY, random(0, 255), particlesLayer));
particles[particles.length-1].velocity = p5.Vector.random2D();
particles[particles.length-1].velocity.mult(random(0.5, 10));
}
function mouseDragged(){
FPS.x = mouseX - FPS.size * 0.5;
FPS.y = mouseY - FPS.size * 0.5;
}
class FPSMonitor {
constructor(x, y){
this.x = x;
this.y = y;
this.size = 100;
this.delay = millis();
}
update(g){
g.textAlign(LEFT, TOP);
g.textSize(this.size/6);
g.rectMode(CORNER);
g.strokeWeight(3);
g.stroke("red");
g.fill("white");
g.rect(0, 0, this.size * 1.2, this.size);
g.noStroke();
g.fill(0);
g.text("FPS: " + round(1/(millis()-this.delay)*1000, 3), 5, 5);
g.text("Average FPS:", 5, 25);
g.text(round(frameCount/millis()*1000, 3), 5, 48);
g.text("MS: " + round(millis()-this.delay), 5, 72);
this.delay = millis();
// render the graphics
image(g, this.x, this.y);
}
}
class particle {
constructor(x, y, hue, gravity, life, weight, renderFunction){
if(!hue){ throw new TypeError(this + " : hue is not defined") }
this.defaults = {
x: 0,
y: 0,
gravity: createVector(0, 0),
life: 100,
weight: 1,
renderFunction: (self) => {colorMode(HSB);strokeWeight(2);stroke(this.hue, 255, 255, this.life/this.maxLife);point(this.position.x, this.position.y)}
}
this.position = x && y ? createVector(x, y) : createVector(this.defaults.x, this.defaults.y);
this.gravity = gravity || this.defaults.gravity;
this.life = life || this.defaults.life;
this.maxLife = this.life;
this.acceleration = createVector(0, 0);
this.velocity = createVector(0, 0);
this.weight = weight || this.defaults.weight;
this.renderFunction = renderFunction || this.defaults.renderFunction;
this.hue = hue;
this.otherInfo = {
mouseAtStart: createVector(mouseX, mouseY),
}
}
isDead(){
return this.life < 0;
}
applyForce(force){
this.acceleration.add(force);
}
update(){
this.life--;
this.acceleration.add(this.gravity);
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
this.velocity.mult(this.weight*0.96>1?0.96:this.weight*0.96);
this.acceleration.mult(0.1);
this.renderFunction(this);
}
}
class firework {
constructor(x, y, hue, graphicsLayer){
// store the reference to the same particle layers
this.g = graphicsLayer;
this.renderFunction = self => {
// use the layer to draw into, not the global p5.js graphics
this.g.colorMode(HSB);
this.g.strokeWeight(3);
this.g.stroke(self.hue, 255, 255, (self.life+self.maxLife*0.5)/self.maxLife)
//line(self.otherInfo.mouseAtStart.x, self.otherInfo.mouseAtStart.y, self.position.x, self.position.y);
this.g.point(self.position.x, self.position.y);
};
this.explodeRenderFunction = self => {
this.g.colorMode(HSB);
this.g.strokeWeight(3);
this.g.stroke(self.hue, 255, 255, self.life/self.maxLife)
//line(self.otherInfo.mouseAtStart.x, self.otherInfo.mouseAtStart.y, self.position.x, self.position.y);
this.g.point(self.position.x, self.position.y);
}
this.particle = new particle(x, y, hue, createVector(0, 0.1), height/53.3 * 4, 1, this.renderFunction);
this.particle.applyForce(createVector(random(-3, 3), random(height/-53.3, height/-43.3)));
this.explodeParticles = [];
this.exploded = false;
this.hue = hue;
}
update(){
this.particle.update();
if(this.particle.isDead() && !this.exploded){
this.particle.renderFunction = (self) => {};
this.exploded = true;
for(let p = 0; p < 500; p++){
this.explodeParticles.push(new particle(this.particle.position.x, this.particle.position.y, this.hue, createVector(0, 0.1), 100, 1, this.explodeRenderFunction));
this.explodeParticles[this.explodeParticles.length-1].velocity = p5.Vector.random2D();
this.explodeParticles[this.explodeParticles.length-1].velocity.mult(random(0.5, 10));
}
}
if(this.exploded){
for(let p in this.explodeParticles){
if(this.explodeParticles[p].isDead()){
this.explodeParticles.splice(p, 1);
continue;
}
this.explodeParticles[p].update();
}
}
}
isDead(){
return this.explodeParticles.length == 0 && this.exploded;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.min.js"></script>
And here's a screenshot:

The the fireworks have trails, but the text is clear (witout blur/trails)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With