How to reset CreateCanvas() in JS after being called - javascript

When I was writing the code I saw that the code would not reset the canvas to (400, 400) after being changed to (600, 600). It would disorientate the canvas and stretch all the shapes with it in evaporation(). When going through all the screens and trying to go back to reset back.
//Variables
let screen = 0;
let bugs = [];
let amt = 1000;
let xpos=0;
function setup() {
colorMode(HSB);
//Changes Colormode to HSB(Hue-Saturation-Brightness)
//Creates the particles for the evaporation screen
//For loop with increasement that states more of the smoke is created
for (let i = 0; i < amt; i++) {
let x = 200;
let y = 300;
let rad = random(10, 50);
let b = new Bug(x, y, rad);
bugs[i] = b;
}
}
//Says if the value of screen changes so does the screen
function draw() {
if(screen == 0) {
evaporationscreen();
} else if(screen == 1) {
condensation();
} else if(screen == 2) {
presipitation();
}
}
//Evaporation
function evaporationscreen() {
background('#d2b48c');
createCanvas(400, 400);
//NOT RGB, HSB COLOR PALLETE(DO NOT MIX UP);
//HTML Color pallete('e928320');
noStroke();
fill('#3895d3') //Lake
circle(400, 400, 600)
fill('#18bdf0') //Sky
rect(0, 0, 400, 200);
fill('#ffff00'); //Sun
circle(300, 60, 70);
fill('#e9bd15'); //Darkspots on Sun
circle(315, 75, 10);//Forms the Sun and Darkspots
circle(280, 52, 8);//Forms the Sun and Darkspots
circle(299, 60, 13);//Forms the Sun and Darkspots
circle(320, 48, 9);//Forms the Sun and Darkspots
circle(295, 30, 6);//Forms the Sun and Darkspots
circle(284, 79, 9);//Forms the Sun and Darkspots
fill(240, 100, 50);//Forms the Sun and Darkspots
rect(0, 200, 400, 400); //Beach
console.log("EVAPORATION");
//Lots of variables
let stoprip = 45;
let lake = int(dist(400, 400, mouseX, mouseY));
var starx = random(0, 400);
var stary = random (10, 190);
var sun = int(dist(300, 60, mouseX, mouseY));
//If Statement that creates ripples
if (mouseIsPressed && lake < 300) {
if (mouseY >= 200) {
noFill();
stroke('#3895d3');
strokeWeight(4);
circle(mouseX, mouseY, ripple);
ripple = ripple + 5;
console.log('lake rippling');
if (ripple >= stoprip) {
ripple = 10
}
} else {
ripple = 10
}
} else if (mouseIsPressed && sun <=35) {
fill('#fee12b');
circle(starx, stary, 10);
console.log('stars');
}
background(50, 0.25);
//For loop that creates the smoke effect with an increasement
for (let i = 0; i < bugs.length; i++) {
bugs[i].show();
bugs[i].move();
if( bugs[i].radius > 100 ) {
bugs.splice(i, 1);
}
}
let x = 200;
let y = 300;
let rad = random(10, 50);
let b = new Bug(x, y, rad);
bugs.push(b);
}
//Particles for evaporation
class Bug {
constructor(tempX, tempY, tempRadius) {
this.x = tempX;
this.y = tempY;
this.radius = tempRadius;
this.color = color(random(80,100), 0.05);
}
//Creates the sketch for it
show() {
noStroke();
fill(this.color);
ellipse(this.x, this.y, this.radius);
}
//Moves it up at a random pace
move() {
this.x = this.x + random(-5, 5);
this.y = this.y - random(2);
this.radius = this.radius + 0.4;
}
}
function condensation() {
//Changes the canvas from 400,400 to 600,600
createCanvas(600, 600);
//Changes background to grey to imitate storm,
background(50);
console.log("CONDENSATION");
//Colors the cloud and fills it with white
noStroke();
fill(255);
//States where cloud spawns
cloud(xpos,50,1);
cloud(xpos,50,2);
cloud(xpos,80,0.75);
xpos++;
//Has cloud with an increasement
if (xpos>600) {
xpos=0;
}
}
function presipitation() {
createCanvas(600, 600);
ellipseMode(RADIUS);
noFill();
background(211, 69, 93);
console.log("PRESIPITATION")
//If statement that create a for loop that spawns new particles(rain)
if (particles.length < 200) particles.push(new Particle());
for (var i = 0; i < particles.length; i++) {
particles[i].update();
particles[i].display();
}
}
var particles = [];
//Variable
//Rain drop / Particle
class Particle {
constructor() {
this.reset();
}
//Resets Particle after update
reset() {
this.x = random(width);
this.y = random(-150, 0);
this.vy = random(0.1, 2);
this.maxy = this.y + height;
this.r = 0;
this.tr = 50;
this.w = random(0.1, 2);
}
//If statement that creates the gravity for the particle
update() {
if (this.y < this.maxy) {
this.y += this.vy;
} else {
this.r++;
}
if (this.r > this.tr) this.reset();
}
//If statement(says it will remain the raindrop form or change into the puddle
display() {
strokeWeight(this.w);
if (this.y < this.maxy) {
stroke(255);
push();
translate(this.x,this.y);
beginShape();
strokeWeight(1);
vertex(0,-5);
quadraticVertex(3, 0, 0, 1);
quadraticVertex(-3,0, 0, -5);
endShape(CLOSE);
pop();
} else {
stroke(255, map(this.r, 0, this.tr, 255, 0));
ellipse(this.x, this.y, this.r, this.r*.5);
}
}
}
//Changes Screen from evaporation, condensation, precipitation
function mousePressed(){
if(screen==0){
screen=1;
}else if(screen==1){
screen = 2;
} else if(screen == 2) {
screen = 0;
}
}
//Makes cloud
function cloud(x,y,sc){
push()
scale(sc);
ellipse(x,y,50,30);
ellipse(x,y+20,80,30);
ellipse(x+20,y,40,30);
ellipse(x+30,y+10,70,40);
pop()
}
html, body {
margin: 0;
padding: 0;
}
canvas {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sketch</title>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="libraries/p5.min.js"></script>
<script src="libraries/p5.sound.min.js"></script>
</head>
<body>
<script src="sketch.js"></script>
</body>
</html>

From the documentation for createCanvas:
Creates a canvas element in the document, and sets the dimensions of it in pixels. This method should be called only once at the start of setup. Calling createCanvas more than once in a sketch will result in very unpredictable behavior.
Instead of calling createCanvas repeatedly in your drawing functions, you should use resizeCanvas once when transitioning from one screen to another.
I couldn't actually reproduce whatever issue you were describing (partly because I could not make sense of your description). However I did also notice an issue with the variable ripple not being declared anywhere, so I fixed that, and now the sketch appears to be working correctly.
//Variables
let previousScreen = 0;
let screen = 0;
let bugs = [];
let amt = 1000;
let xpos = 0;
let ripple = 0;
function setup() {
createCanvas(400, 400);
colorMode(HSB);
//Changes Colormode to HSB(Hue-Saturation-Brightness)
//Creates the particles for the evaporation screen
//For loop with increasement that states more of the smoke is created
for (let i = 0; i < amt; i++) {
let x = 200;
let y = 300;
let rad = random(10, 50);
let b = new Bug(x, y, rad);
bugs[i] = b;
}
}
//Says if the value of screen changes so does the screen
function draw() {
if (screen == 0) {
if (previousScreen !== screen) {
resizeCanvas(400, 400);
previousScreen = screen;
}
evaporationscreen();
} else if (screen == 1) {
if (previousScreen !== screen) {
resizeCanvas(600, 600);
previousScreen = screen;
}
condensation();
} else if (screen == 2) {
if (previousScreen !== screen) {
resizeCanvas(600, 600);
previousScreen = screen;
}
presipitation();
}
}
//Evaporation
function evaporationscreen() {
background('#d2b48c');
//NOT RGB, HSB COLOR PALLETE(DO NOT MIX UP);
//HTML Color pallete('e928320');
noStroke();
fill('#3895d3') //Lake
circle(400, 400, 600)
fill('#18bdf0') //Sky
rect(0, 0, 400, 200);
fill('#ffff00'); //Sun
circle(300, 60, 70);
fill('#e9bd15'); //Darkspots on Sun
circle(315, 75, 10); //Forms the Sun and Darkspots
circle(280, 52, 8); //Forms the Sun and Darkspots
circle(299, 60, 13); //Forms the Sun and Darkspots
circle(320, 48, 9); //Forms the Sun and Darkspots
circle(295, 30, 6); //Forms the Sun and Darkspots
circle(284, 79, 9); //Forms the Sun and Darkspots
fill(240, 100, 50); //Forms the Sun and Darkspots
rect(0, 200, 400, 400); //Beach
console.log("EVAPORATION");
//Lots of variables
let stoprip = 45;
let lake = int(dist(400, 400, mouseX, mouseY));
var starx = random(0, 400);
var stary = random(10, 190);
var sun = int(dist(300, 60, mouseX, mouseY));
//If Statement that creates ripples
if (mouseIsPressed && lake < 300) {
if (mouseY >= 200) {
noFill();
stroke('#3895d3');
strokeWeight(4);
circle(mouseX, mouseY, ripple);
ripple = ripple + 5;
console.log('lake rippling');
if (ripple >= stoprip) {
ripple = 10
}
} else {
ripple = 10
}
} else if (mouseIsPressed && sun <= 35) {
fill('#fee12b');
circle(starx, stary, 10);
console.log('stars');
}
background(50, 0.25);
//For loop that creates the smoke effect with an increasement
for (let i = 0; i < bugs.length; i++) {
bugs[i].show();
bugs[i].move();
if (bugs[i].radius > 100) {
bugs.splice(i, 1);
}
}
let x = 200;
let y = 300;
let rad = random(10, 50);
let b = new Bug(x, y, rad);
bugs.push(b);
}
//Particles for evaporation
class Bug {
constructor(tempX, tempY, tempRadius) {
this.x = tempX;
this.y = tempY;
this.radius = tempRadius;
this.color = color(random(80, 100), 0.05);
}
//Creates the sketch for it
show() {
noStroke();
fill(this.color);
ellipse(this.x, this.y, this.radius);
}
//Moves it up at a random pace
move() {
this.x = this.x + random(-5, 5);
this.y = this.y - random(2);
this.radius = this.radius + 0.4;
}
}
function condensation() {
//Changes background to grey to imitate storm,
background(50);
console.log("CONDENSATION");
//Colors the cloud and fills it with white
noStroke();
fill(255);
//States where cloud spawns
cloud(xpos, 50, 1);
cloud(xpos, 50, 2);
cloud(xpos, 80, 0.75);
xpos++;
//Has cloud with an increasement
if (xpos > 600) {
xpos = 0;
}
}
function presipitation() {
ellipseMode(RADIUS);
noFill();
background(211, 69, 93);
console.log("PRESIPITATION")
//If statement that create a for loop that spawns new particles(rain)
if (particles.length < 200) particles.push(new Particle());
for (var i = 0; i < particles.length; i++) {
particles[i].update();
particles[i].display();
}
}
var particles = [];
//Variable
//Rain drop / Particle
class Particle {
constructor() {
this.reset();
}
//Resets Particle after update
reset() {
this.x = random(width);
this.y = random(-150, 0);
this.vy = random(0.1, 2);
this.maxy = this.y + height;
this.r = 0;
this.tr = 50;
this.w = random(0.1, 2);
}
//If statement that creates the gravity for the particle
update() {
if (this.y < this.maxy) {
this.y += this.vy;
} else {
this.r++;
}
if (this.r > this.tr) this.reset();
}
//If statement(says it will remain the raindrop form or change into the puddle
display() {
strokeWeight(this.w);
if (this.y < this.maxy) {
stroke(255);
push();
translate(this.x, this.y);
beginShape();
strokeWeight(1);
vertex(0, -5);
quadraticVertex(3, 0, 0, 1);
quadraticVertex(-3, 0, 0, -5);
endShape(CLOSE);
pop();
} else {
stroke(255, map(this.r, 0, this.tr, 255, 0));
ellipse(this.x, this.y, this.r, this.r * .5);
}
}
}
//Changes Screen from evaporation, condensation, precipitation
function mousePressed() {
if (screen == 0) {
screen = 1;
} else if (screen == 1) {
screen = 2;
} else if (screen == 2) {
screen = 0;
}
}
//Makes cloud
function cloud(x, y, sc) {
push();
scale(sc);
ellipse(x, y, 50, 30);
ellipse(x, y + 20, 80, 30);
ellipse(x + 20, y, 40, 30);
ellipse(x + 30, y + 10, 70, 40);
pop();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>

Related

How can I integrate an app made with create-react-app with p5.js, with a specific goal to make sound visualization that responds to played sound?

I have read a good number of related posts, but I feel they are not exactly giving me the required solution.
I want to implement in React, the following p5.js JavaScript code that I have written following this tutorial on YouTube => https://www.youtube.com/watch?v=uk96O7N1Yo0:
let sound;
let fft;
let image;
let particles = [];
function preload() {
sound = loadSound("sound-path.mp3");
image = loadImage("image-path.jpg");
}
function setup() {
createCanvas(windowWidth, windowHeigh);
angleMode(DEGREES);
imageMode(CENTER);
rectMode(CENTER);
fft = new p5.FFT(0.3);
image.filter(BLUR, 12);
noLoop();
}
function draw() {
background(0);
stroke(255);
strokeWeight(3);
noFill();
translate(width / 2, height / 2);
fft.analyze();
amp = fft.getEnergy(20, 200);
push();
if (amp > 230) {
rotate(random(-0.5, 0.5));
}
image(image, 0, 0, width + 100, height + 100);
pop();
let alpha = map(amp, 0, 255, 180, 150);
fill(0, alpha);
noStroke();
rect(0, 0, width, height);
stroke(255);
strokeWeight(3);
noFill();
let wave = fft.waveform();
for (let t = -1; t <= 1; t += 2) {
beginShape();
for (let i = 0; i <= 180; i += 0.5) {
let index = floor(map(i, 0, 180, 0, wave.length - 1));
let radius = map(wave[index], -1, 1, 150, 350);
let x = radius * sin(i) * t;
let y = radius * cos(i);
vertex(x, y);
}
endShape();
}
let particle = new Particle();
particles.push(particle);
for (let i = particles.length - 1; i >= 0; i--) {
if (!particles[i].edges()) {
particles[i].update(amp > 230);
particles[i].show();
} else particles.splice(i, 1);
}
}
function mouseClicked() {
if (sound.isPlaying()) {
sound.pause();
noLoop();
} else {
sound.play();
loop();
}
}
// Particles around the Wave
class Particle {
constructor() {
this.position = p5.Vector.random2D().mult(250);
this.velocity = createVector(0, 0);
this.acceleration = this.position.copy().mult(random(0.0001, 0.00001));
this.width = random(3, 5);
this.color = [random(200, 255), random(200, 255), random(200, 255)];
}
update(condition) {
this.velocity.add(this.acceleration);
this.position.add(this.velocity);
if (condition) {
this.position.add(this.velocity);
this.position.add(this.velocity);
this.position.add(this.velocity);
}
}
edges() {
if (
this.position.x < -width / 2 ||
this.position.x > width / 2 ||
this.position.y < -height / 2 ||
this.position.y > height / 2
)
return true;
else return false;
}
show() {
noStroke();
fill(this.color);
ellipse(this.position.x, this.position.y, this.width);
}
}
I have the following component in which I want that implementation of this sound visualization to happen:
import React from "react";
import { FormInput } from "../Forms/Form";
import "./Visualization.css";
const Visualization = () => {
// TODO: Implement p5.js to create visualization as in the linked video
const handleChange = () => {
// Play the selected audio file in that input
};
return (
<div className="visualizer-canvas-container">
<canvas id="landing-visualizer-canvas"></canvas>
<audio id="landing-audio-controls" controls />
<FormInput
accept="audio/*"
id="file-upload"
onChange={handleChange}
type="file"
/>
</div>
);
};
export default Visualization;

How to create an afterimage for some objects in JavaScript

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)

P5.js ellipse not following mouseX and Y

I am not sure why but the "invisible circle" is not align with the mouseX and Y and i ended up hard coding it. Anyone know why this happens?
If i just wrote (mouseX,mouseY) the ellipse will be around 300 units away from the cusor.
Is there any way fix it/ improve it?
Without setting exact value such as mouseX-300?
Thanks
*not much details i can think of for now.not much details i can think of for now.not much details i can think of for now.
let cubes = [];
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
backCol = color(243, 243, 243);
//background(backCol);
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
let xPos = map(i, 0, 9, 50, width - 50);
let yPos = map(j, 0, 9, 50, height - 50);
cubes.push(new Cubes(xPos, yPos));
}
}
}
function draw() {
background(backCol);
noFill();
for (let a = 0; a < cubes.length; a++) {
cubes[a].update();
}
}
class Cubes {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = 30;
this.stroke = 70;
this.shift1 = color(96);
this.shift2 = color(244);
}
update() {
this.shape();
this.shift_Color();
this.Invisible_Circle();
}
Invisible_Circle () {
push();
stroke(10);
//noFill();
// translate(mouseX,mouseY);
ellipse(mouseX - 280,mouseY - 280,200);
pop();
}
shape() {
push();
stroke(this.stroke);
translate(this.x - width / 2, this.y - height / 2, 0);
this.magnetic()
box(this.size);
pop();
}
shift_Color() {
let distance = dist(mouseX, mouseY, this.x, this.y);
let shiftX = map(mouseX, 0, this.a, 0, 1.0);
let change = lerpColor(this.shift1, this.shift2, shiftX);
if (distance < this.a) {
fill(change);
} else {
noFill();
}
}
magnetic() {
let distance = dist(mouseX, mouseY, this.x, this.y);
if (distance < this.size * 3) {
this.a = atan2(mouseY - this.y, mouseX - this.x);
rotateX(this.a);
rotateY(this.a);
} else {
rotateX(millis() / 1000);
rotateY(millis() / 1000);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js"></script>
In WEBGL mode (createCanvas()) the center of the screen is by default (0, 0). Anyway, the top left coordinate of the window is still (0, 0). Hence you have to translate the mouse position, by (-width/2, -height/2):
ellipse(mouseX - width/2, mouseY - height/2, 200);
Furthermore Invisible_Circle should not a method of Cubes:
function Invisible_Circle () {
stroke(10);
//noFill();
ellipse(mouseX - width/2, mouseY - height/2, 200);
}
class Cubes {
constructor(x, y) {
// [...]
And has to be called after drawing the boxes:
function draw() {
background(backCol);
Invisible_Circle();
noFill();
for (let a = 0; a < cubes.length; a++) {
cubes[a].update();
}
Invisible_Circle();
}
See the example:
let cubes = [];
function setup() {
createCanvas(windowWidth, windowHeight, WEBGL);
backCol = color(243, 243, 243);
//background(backCol);
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
let xPos = map(i, 0, 9, 50, width - 50);
let yPos = map(j, 0, 9, 50, height - 50);
cubes.push(new Cubes(xPos, yPos));
}
}
}
function draw() {
background(backCol);
Invisible_Circle();
noFill();
for (let a = 0; a < cubes.length; a++) {
cubes[a].update();
}
Invisible_Circle();
}
function Invisible_Circle () {
stroke(10);
//noFill();
ellipse(mouseX - width/2, mouseY - height/2, 200);
}
class Cubes {
constructor(x, y) {
this.x = x;
this.y = y;
this.size = 30;
this.stroke = 70;
this.shift1 = color(96);
this.shift2 = color(244);
}
update() {
this.shape();
this.shift_Color();
}
shape() {
push();
stroke(this.stroke);
translate(this.x - width / 2, this.y - height / 2, 0);
this.magnetic()
box(this.size);
pop();
}
shift_Color() {
let distance = dist(mouseX, mouseY, this.x, this.y);
let shiftX = map(mouseX, 0, this.a, 0, 1.0);
let change = lerpColor(this.shift1, this.shift2, shiftX);
if (distance < this.a) {
fill(change);
} else {
noFill();
}
}
magnetic() {
let distance = dist(mouseX, mouseY, this.x, this.y);
if (distance < this.size * 3) {
this.a = atan2(mouseY - this.y, mouseX - this.x);
rotateX(this.a);
rotateY(this.a);
} else {
rotateX(millis() / 1000);
rotateY(millis() / 1000);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js"></script>

p5.js How to stop rotation of certain elements

I am drawing some rotating rectangles in p5.js, and it is all working fine. Then I need some text to be displayed on top of rectangles and I want it to be motionless. However, the text is spinning as well. I have tried some solutions like setting rotate(0) or value negative to the rotation of last drawn rectangle, but nothing works.
class Rectangle {
constructor(size, colorR, colorG, colorB, angle) {
this.size = size;
this.colorR = colorR;
this.colorG = colorG;
this.colorB = colorB;
this.angle = angle;
this.actualAngle = this.angle;
}
draw() {
stroke(0);
strokeWeight(2);
fill(this.colorR, this.colorG, this.colorB);
rotate(this.actualAngle);
rect(0, 0, this.size, this.size);
}
increaseAngle() {
this.actualAngle += this.angle;
}
getActualAngle(){
return this.actualAngle;
}
}
var rectangles = [];
function setup() {
var canvas1 = createCanvas(800, 700);
canvas1.parent("regLog");
var size = 12;
for (var i = 0; i < 10; i++) {
let ranR = random(150);
let ranG = random(150);
let ranB = random(150);
let angle = random(-0.005, 0.005);
rectangles.push(new Rectangle(size, ranR, ranG, ranB, angle));
size += 50;
}
}
function draw() {
background(255);
rectMode(CENTER);
translate(width / 2, height / 2);
for (var i = rectangles.length - 1; i > 0; i--) {
rectangles[i].draw();
}
if (second() % 1 == 0) {
for (let i = 0; i < rectangles.length; i++) {
rectangles[i].increaseAngle();
}
}
rotate(0);
noStroke();
fill(255, 255, 255, 0);
rect(0, 0, 50, 50);
textAlign(CENTER);
fill(255);
textSize(60);
text("Coming soon!", 0, 0);
}
I used push() to "save" the current rotation settings and then pop() to restore them after each time you draw a rectangle. When you use rotate(), it rotates the all of the future shapes you draw, so the changes are "permanent" unless you're very careful to reset.
You can see it work in this jsfiddle -> https://jsfiddle.net/ozsLtxhb/237/
i commented out the part where you assign a parent just for the jsfiddle to work so uncomment it before you copy-paste it into anything
class Rectangle {
constructor(size, colorR, colorG, colorB, angle) {
this.size = size;
this.colorR = colorR;
this.colorG = colorG;
this.colorB = colorB;
this.angle = angle;
this.actualAngle = this.angle;
}
draw() {
/*push here*/
push();
stroke(0);
strokeWeight(2);
fill(this.colorR, this.colorG, this.colorB);
rotate(this.actualAngle);
rect(0, 0, this.size, this.size);
/*pop here*/
pop();
}
increaseAngle() {
this.actualAngle += this.angle;
}
getActualAngle(){
return this.actualAngle;
}
}
var rectangles = [];
function setup() {
var canvas1 = createCanvas(800, 700);
/* canvas1.parent("regLog"); */
var size = 12;
for (var i = 0; i < 10; i++) {
let ranR = random(150);
let ranG = random(150);
let ranB = random(150);
let angle = random(-0.005, 0.005);
rectangles.push(new Rectangle(size, ranR, ranG, ranB, angle));
size += 50;
}
}
function draw() {
background(255);
rectMode(CENTER);
translate(width / 2, height / 2);
for (var i = rectangles.length - 1; i > 0; i--) {
rectangles[i].draw();
}
if (second() % 1 == 0) {
for (let i = 0; i < rectangles.length; i++) {
rectangles[i].increaseAngle();
}
}
rotate(0);
noStroke();
fill(255, 255, 255, 0);
rect(0, 0, 50, 50);
textAlign(CENTER);
fill(255);
textSize(60);
text("Coming soon!", 0, 0);
}

Why does the ball in pong get stuck at the bottom?

I recently made a JS Pong game. It works well, but the ball rarely gets stuck at the bottom or top. It looks like it is halfway through the wall and constantly bouncing. Video of the issue happening. You can try the game here. I do not know why this issue is happening because the logic seems right and works 90% of the time correctly. Here are the main two functions of my program:
function moveAll() {
if (showingWinScreen) {
return;
}
computerMovement();
ballX += ballSpeedX;
ballY += ballSpeedY;
if (ballY <= 10) {
ballSpeedY = -ballSpeedY;
} else if (ballY >= HEIGHT - 10) {
ballSpeedY = -ballSpeedY;
}
if (ballX >= WIDTH - 10) {
if ((ballY > paddleY) && (ballY < paddleY + 100)) {
ballSpeedX = -ballSpeedX;
var deltaY = ballY - paddleY - 50;
ballSpeedY = deltaY / 5;
} else {
player1Score++;
ballReset();
}
} else if (ballX <= 10) {
if ((ballY > mouseY - 50) && (ballY < mouseY + 50)) {
ballSpeedX = -ballSpeedX;
deltaY = ballY - mouseY;
ballSpeedY = deltaY / 6;
} else {
player2Score++;
ballReset();
}
}
}
function drawAll() {
if (showingWinScreen) {
colorRect(0, 0, WIDTH, HEIGHT, "black");
canvas.fillStyle = "yellow";
canvas.fillText("Click to continue!", 300, 300);
if (player1Score == WINNING_SCORE) {
canvas.fillText("You won!", 360, 500);
} else if (player2Score == WINNING_SCORE) {
canvas.fillText("The computer beat you!", 280, 500);
}
return;
}
colorRect(0, 0, WIDTH, HEIGHT, "black");
drawNet();
makeCircle(ballX, ballY, 10, 0, Math.PI * 2, "red");
colorRect(790, paddleY, 10, 100, "cyan");
colorRect(0, mouseY - 50, 10, 100, "yellow");
canvas.fillStyle = "white";
canvas.fillText(player1Score + " " + player2Score, 360, 100);
}
Thank you for your help!
I think there's only one case in which this could happen: when, in a colliding frame, you decrease the speed.
When the speed remains the same, no matter what, your ball will always bounce back to the previous' frames position:
var cvs = document.querySelector("canvas");
var ctx = cvs.getContext("2d");
var balls = [
Ball(50, 50, 0, 5, 5, "red"),
Ball(100, 50, 0, 5, 10, "blue"),
Ball(150, 50, 0, 5, 15, "green"),
Ball(200, 50, 0, 5, 20, "yellow")
];
var next = () => {
updateFrame(balls);
drawFrame(balls);
}
var loop = () => {
requestAnimationFrame(() => {
next();
loop();
});
}
next();
function Ball(x, y, vx, vy, r, color) {
return {
x: x,
y: y,
vx: vx,
vy: vy,
r: r,
color: color
}
};
function updateBall(b) {
b.x += b.vx;
b.y += b.vy;
if (b.y <= b.r ||
b.y >= cvs.height - b.r) {
b.vy *= -1;
}
};
function drawBall(b) {
ctx.beginPath();
ctx.fillStyle = b.color;
ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI, false);
ctx.fill();
}
function updateFrame(balls) {
balls.forEach(updateBall);
}
function drawFrame(balls) {
ctx.clearRect(0, 0, cvs.width, cvs.height);
balls.forEach(drawBall);
};
<canvas width="300" height="150" style="background: #454545"></canvas>
<button onclick="next()">next</button>
<button onclick="loop()">run</button>
But when the speed changes, things get stuck:
var cvs = document.querySelector("canvas");
var ctx = cvs.getContext("2d");
var balls = [
Ball(50, 50, 0, 10, 5, "red"),
Ball(100, 50, 0, 10, 10, "blue"),
Ball(150, 50, 0, 10, 15, "green"),
Ball(200, 50, 0, 10, 20, "yellow")
];
var next = () => {
updateFrame(balls);
drawFrame(balls);
}
var loop = () => {
requestAnimationFrame(() => {
next();
loop();
});
}
next();
function Ball(x, y, vx, vy, r, color) {
return {
x: x,
y: y,
vx: vx,
vy: vy,
r: r,
color: color
}
};
function updateBall(b) {
b.x += b.vx;
b.y += b.vy;
if (b.y <= b.r ||
b.y >= cvs.height - b.r) {
b.vy *= -0.5;
}
};
function drawBall(b) {
ctx.beginPath();
ctx.fillStyle = b.color;
ctx.arc(b.x, b.y, b.r, 0, 2 * Math.PI, false);
ctx.fill();
}
function updateFrame(balls) {
balls.forEach(updateBall);
}
function drawFrame(balls) {
ctx.clearRect(0, 0, cvs.width, cvs.height);
balls.forEach(drawBall);
};
<canvas width="300" height="150" style="background: #454545"></canvas>
<button onclick="next()">next</button>
<button onclick="loop()">run</button>
In your case, I'm thinking this can only happen when there's a paddle collision AND a wall collision simultaneously.
A quick-to-implement solution would be to check if the new position is valid before translating the ball position. If you don't want the precise location, you can place the ball at the point of collision. Note that this will produce a slightly off frame.
E.g.:
var newY = ballY + ballSpeedY;
// Top wall
if(newY <= 10) {
ballY = 10;
ballSpeedY = -ballSpeedY;
}
// Bottom wall
else if(newY >= HEIGHT-10){
ballY = HEIGHT - 10;
ballSpeedY = -ballSpeedY;
}
// No collision
else {
ballY = newY;
}
Update: a more detailed description of what can happen
Let's say your ball collides with the top border of your canvas and with your paddle in the same frame.
First, you move the ball to the colliding position: ballY += ballSpeedY; Say your ballY is 4, and your ballSpeedY is -5, you'll position the ball to -1, inside the wall.
If this were to be the only collision, you should be okay. You flip the speed (ballSpeedY = -ballSpeedY), so in the next frame, your ball should be back at -1 + 5 = 4, so ballY will be 4 again, and your ball will move towards 4 + 5 = 9 in the next frame.
Now a problem arises, when in the -1 positioned frame, you collide with the paddle as well! When the paddle hits the ball, you modify the ballspeed: ballSpeedY = deltaY / 5;. If this turns out to be < 1, your ball won't be able to exit the wall in the next frame. Instead of -1 + 5 = 4, your ball will, for example, move to: -1 + 0.5 = -0.5.
Now, your ball won't be able to get back in to play, since the next frame will, again, calculate a collision and flip the speed. This results in the bouncy, trembling effect you see when the ball gets stuck.
A naive but pretty decent solution, is to only update the position of the ball to a valid position. I.e.: never to a colliding coordinate.
var animate = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60)
};
var canvas = document.createElement("canvas");
var width = 400;
var height = 600;
canvas.width = width;
canvas.height = height;
var context = canvas.getContext('2d');
var player = new Player();
var computer = new Computer();
var ball = new Ball(200, 300);
var keysDown = {};
var render = function () {
context.fillStyle = "#FF00FF";
context.fillRect(0, 0, width, height);
player.render();
computer.render();
ball.render();
};
var update = function () {
player.update();
computer.update(ball);
ball.update(player.paddle, computer.paddle);
};
var step = function () {
update();
render();
animate(step);
};
function Paddle(x, y, width, height) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.x_speed = 0;
this.y_speed = 0;
}
Paddle.prototype.render = function () {
context.fillStyle = "#0000FF";
context.fillRect(this.x, this.y, this.width, this.height);
};
Paddle.prototype.move = function (x, y) {
this.x += x;
this.y += y;
this.x_speed = x;
this.y_speed = y;
if (this.x < 0) {
this.x = 0;
this.x_speed = 0;
} else if (this.x + this.width > 400) {
this.x = 400 - this.width;
this.x_speed = 0;
}
};
function Computer() {
this.paddle = new Paddle(175, 10, 50, 10);
}
Computer.prototype.render = function () {
this.paddle.render();
};
Computer.prototype.update = function (ball) {
var x_pos = ball.x;
var diff = -((this.paddle.x + (this.paddle.width / 2)) - x_pos);
if (diff < 0 && diff < -4) {
diff = -5;
} else if (diff > 0 && diff > 4) {
diff = 5;
}
this.paddle.move(diff, 0);
if (this.paddle.x < 0) {
this.paddle.x = 0;
} else if (this.paddle.x + this.paddle.width > 400) {
this.paddle.x = 400 - this.paddle.width;
}
};
function Player() {
this.paddle = new Paddle(175, 580, 50, 10);
}
Player.prototype.render = function () {
this.paddle.render();
};
Player.prototype.update = function () {
for (var key in keysDown) {
var value = Number(key);
if (value == 37) {
this.paddle.move(-4, 0);
} else if (value == 39) {
this.paddle.move(4, 0);
} else {
this.paddle.move(0, 0);
}
}
};
function Ball(x, y) {
this.x = x;
this.y = y;
this.x_speed = 0;
this.y_speed = 3;
}
Ball.prototype.render = function () {
context.beginPath();
context.arc(this.x, this.y, 5, 2 * Math.PI, false);
context.fillStyle = "#000000";
context.fill();
};
Ball.prototype.update = function (paddle1, paddle2) {
this.x += this.x_speed;
this.y += this.y_speed;
var top_x = this.x - 5;
var top_y = this.y - 5;
var bottom_x = this.x + 5;
var bottom_y = this.y + 5;
if (this.x - 5 < 0) {
this.x = 5;
this.x_speed = -this.x_speed;
} else if (this.x + 5 > 400) {
this.x = 395;
this.x_speed = -this.x_speed;
}
if (this.y < 0 || this.y > 600) {
this.x_speed = 0;
this.y_speed = 3;
this.x = 200;
this.y = 300;
}
if (top_y > 300) {
if (top_y < (paddle1.y + paddle1.height) && bottom_y > paddle1.y && top_x < (paddle1.x + paddle1.width) && bottom_x > paddle1.x) {
this.y_speed = -3;
this.x_speed += (paddle1.x_speed / 2);
this.y += this.y_speed;
}
} else {
if (top_y < (paddle2.y + paddle2.height) && bottom_y > paddle2.y && top_x < (paddle2.x + paddle2.width) && bottom_x > paddle2.x) {
this.y_speed = 3;
this.x_speed += (paddle2.x_speed / 2);
this.y += this.y_speed;
}
}
};
document.body.appendChild(canvas);
animate(step);
window.addEventListener("keydown", function (event) {
keysDown[event.keyCode] = true;
});
window.addEventListener("keyup", function (event) {
delete keysDown[event.keyCode];
});
http://jsfiddle.net/kHJr6/2/

Categories