Use p5js functions with node, npm minification - javascript

I am trying to convert a sketch to use node and bundle a distribution js file. My guess is that some of the function names in p5js must be reserved (to combat this I was going to use a development flag so as not to obfuscate the code at least for testing).
In order to encapsulate the code I am using this method:
let sketch = function(p) {
p.setup = function() {
...
}
let myp5 = new p5(sketch);
Everything seems to work fine except I cannot call the show function from inside my particle Class (ReferenceError: noStroke is not defined)
Here is the full js:
let canvasBG;
let particles = [];
let audioFile;
let amplitude;
let vol;
let loaded = false;
let x = 0;
let xoff = 0;
let yoff = 0;
let sketch = function(p) {
p.setup = function() {
canvasBG = p.createCanvas(p.windowWidth, p.windowHeight);
audioFile = p.loadSound("audio/mom.mp3", p.audioLoaded);
amplitude = new p5.Amplitude();
p.rectMode(p.CENTER);
p.frameRate(30);
canvasBG.background(255);
}
p.audioLoaded = function() {
audioFile.play();
audioFile.loop();
loaded = true;
}
p.draw = function() {
let nx = p.noise(xoff) * p.windowWidth;
let ny = p.noise(yoff) * p.windowHeight;
xoff = xoff + 0.01;
yoff = yoff + 0.02;
vol = amplitude.getLevel();
let volumeGate = p.map(vol, 0, 1, p.random(5,12), p.random(80, 550));
if (loaded) {
let b = new Particle(nx, ny, volumeGate);
particles.push(b);
if (volumeGate > 75) {
canvasBG.background(p.random(255), p.random(255), p.random(255));
}
for (let particle of particles) {
particle.show();
}
if (particles.length >= 10) {
particles.splice(0, 1);
}
} else {
x+= 0.05;
p.translate (p.windowWidth - 30, 30);
p.rotate(x);
p.noStroke();
p.rect(0, 0, 2, 40, 0.1);
p.fill(0, 0, 0, x * 3);
}
}
}
class Particle {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
}
show() {
noStroke(); // this is throwing an error
fill(random(0, 255), random(0, 255), random(0, 255), random(100,255));
ellipse(this.x, this.y, this.r * 2);
}
}
let myp5 = new p5(sketch);
You can run the current full demo here: https://editor.p5js.org/lharby/sketches/wPF908tqX
I've tried converting the class to a function and passing in arguments, similarly an object and then attaching a function to the object, but can't seem to get anything to work plus I would rather keep the Class as it is.
How is the best way to go about this?

You're super close!
Because you're using p5 in instance mode, p5 functions (such as noStroke(), fill(), ellipse()) won't be available in the global scope, but through the p reference.
In your class you simply need to pass p as well, similar to how you've already used it with the rest of the code:
class Particle {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r;
}
show(p) {
p.noStroke();
p.fill(p.random(0, 255), p.random(0, 255), p.random(0, 255), p.random(100,255));
p.ellipse(this.x, this.y, this.r * 2);
}
}
and in draw pass the reference to p5:
//...
for (let particle of particles) {
particle.show(p);
}
//...

Related

How can I draw an array of objects inside another object in p5.js?

I'm learning JS in p5.js and I need some help with this thing: I have an array of objects (just some lines that keep moving in random patterns) and I want to keep them inside the borders of another object, in this case, a circle. I was trying to figure it out with the createGraphics function, but it won't work.
The array of lines is called "linhas" (the object class is in another file named "linhas.js", the second background "fundo2", the circle is "circulo".
The code is this:
let linhas = [];
let fundo2;
function setup() {
createCanvas(400, 400);
pixelDensity(1);
fundo2 = createGraphics (400,400);
for (let i = 0; i < 100; i++){
let x = random(0, 400);
let y = random(0, 400);
let z = random(0, 400);
let w = random(0, 400);
linhas[i] = new Linha(x, y, z, w);
}
}
function draw() {
sol();
tracos();
}
function tracos() {
for (let linha of linhas) {
linha.display();
linha.tremer();
}
}
function sol() {
image(fundo2,0,0);
fundo2.noStroke();
fundo2.circle(200,200,250);
}
In case you guys wanna see better my attempt, the link to the web editor of p5.js is this: https://editor.p5js.org/dolivetro/sketches/NZ2FlsPwx
Thanks in advance!
my p5.js project
You basically have three options 1) do the math to calculate the start and end points for each line based on the intersections between each line and the circle that they are to be drawn within; 2) draw your lines to a separate context, copy the content to an Image, and then use the p5.Image.mask function; or 3) draw your lines to a separate context, and then use either the erase() function or blendMode(REMOVE) to clear the portion of that context that is not inside the circle (this would require an additional mask shape as well).
Since you are already drawing to a separate context, here is an example that uses an image mask (i.e. #2):
let linhas = [];
let fundo2;
let img;
let maskCtx;
function setup() {
createCanvas(400, 400);
pixelDensity(1);
fundo2 = createGraphics(400, 400);
maskCtx = createGraphics(400, 400);
maskCtx.noStroke();
maskCtx.circle(200, 200, 250);
img = createImage(width, height);
for (let i = 0; i < 100; i++) {
let x = random(0, 400);
let y = random(0, 400);
let z = random(0, 400);
let w = random(0, 400);
linhas[i] = new Linha(x, y, z, w);
}
}
function draw() {
circulo();
tracos();
}
function tracos() {
for (let linha of linhas) {
linha.display();
linha.tremer();
}
}
function circulo() {
img.copy(fundo2, 0, 0, width, height, 0, 0, width, height);
img.mask(maskCtx);
image(img, 0, 0, width, height);
fundo2.noStroke();
fundo2.circle(200, 200, 250);
}
class Linha {
constructor(x, y, z, w) {
this.x = x;
this.y = y;
this.z = z;
this.w = w;
}
display() {
fundo2.stroke(255, 0, 0, 70);
fundo2.strokeWeight(2);
fundo2.line(this.x, this.y, this.z, this.w);
}
tremer() {
this.x = this.x + random(-1, 1);
this.y = this.y + random(-1, 1);
this.z = this.z + random(-1, 1);
this.w = this.w + random(-1, 1);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

Can't animate multiple instances of an element with JavaScript. Only the first animation will be executed

I have created an element called Particle. Single animation of this element also works. But as soon as I try to run multiple animations, only one animation gets performed. I think the problem is the requestAnimationFrame (this.animate.bind(this))-call, but I don't know how to change it to accept multiple animations at once. Any ideas on how to fix this?
Code:
//gloabl vars
let particels = [];
let numberParticels = 120;
let canvas;
let ctx;
let title;
let mesaureTitle;
let boundRadius;
let animations;
window.onload = function () {
this.init();
for(let i = 0; i < numberParticels; i++){
particels[i].update();
particels[i].draw();
particels[i].animate(0);
}
}
function init(){
canvas = document.getElementById("c");
ctx = canvas.getContext("2d");
title = document.getElementById("title");
mesaureTitle = title.getBoundingClientRect();
bound = {
x: mesaureTitle.x,
y: mesaureTitle.y,
width: mesaureTitle.width,
height: mesaureTitle.height,
};
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
for(let i = 0; i < numberParticels; i++){
let x = Math.floor(Math.random() * window.innerWidth);
let y = Math.floor(Math.random() * window.innerHeight);
let size = Math.floor(Math.random() * 25) + 3;
let weight = Math.floor(Math.random() * 11) + 2;
particels.push(new Particel(x, y, size, weight));
}
}
class Particel {
constructor (x,y,size, weight) {
this.x = x;
this.y = y;
this.size = size;
this.directionX = 0.15332;
this.resetWeight = weight;
this.weight = weight;
this.lastTime = 0;
this.interval = 1000/60;
this.timer = 0;
}
update(){
this.weight += 0.02;
this.y = this.y + this.weight;
this.x += this.directionX;
//check for collision with textField
if (this.x < bound.x + bound.width
&& this.x + this.size > bound.x &&
this.y < bound.y + bound.height &&
this.y + this.size > bound.y) {
this.y -= 3;
this.weight *= -0.3;
}
}
draw(){
if(this.y > canvas.height){
this.y = 0 - this.size;
this.weight = this.resetWeight;
//create random start point
this.x = Math.floor(Math.random() * canvas.width);
}
ctx.fillStyle = "rgb(0, 180, 97)";
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
}
animate(timeStamp){
const deltaTime = timeStamp - this.lastTime;
this.lastTime = timeStamp;
if(this.timer > this.interval){
ctx.clearRect(0,0, canvas.width, canvas.height);
this.update();
this.draw();
this.timer = 0;
}else {
this.timer += deltaTime;
}
ctx.fillStyle = "rgba(0, 0, 0, 0.3)";
requestAnimationFrame(this.animate.bind(this));
}
}
The major problem is clearing the canvas inside the animate method of each particle. So if you draw multiple particles, each particle update call clears the canvas, overwriting previous particle data, which only leaves the last particle visible.
You could try removing the
ctx.clearRect(0,0, canvas.width, canvas.height);
line from where it is and create a createAnimationFrame call back in init to clear the canvas before amimating particles:
function init() {
// ....
requestAnimationFrame( ()=> ctx.clearRect(0,0, canvas.width, canvas.height));
// existing for loop:
for(let i = 0; i < numberParticels; i++){
// ... .
}
However this creates (one plus the number of particles) requests for an animation frame. A better solution would be to remove requesting animation frames from the Particel class and create a single requestAnimationFrame callback which goes through the particels array and calls a class method to redraw each particle on the canvas with updated position.
Also the code generates an error in strict mode that bound has not been declared. I suggest declaring it globally rather than relying on sloppy mode JavaScript creating it as a window property for you.

How to make a Object in a class physically move using JS and p5.js

So far, I've created a class which generates a circle. I'm trying to make this circle move/vibrate constantly but I'm not sure what is the best way to add a move function with OOP. Also, when I create the 'face' object in the draw function my page crashes.
function setup() {
createCanvas(350, 350, WEBGL);
background('white')
let face = new Face();
}
class Face {
constructor() {
this.basicFace(100, random(10,30),0,0);
}
basicFace(radius, steps, centerX, centerY) {
var xValues = [];
var yValues = [];
for (var i = 0; i < steps; i++) {
let rad = radius + random(-radius / 50, radius / 50)
xValues[i] = (centerX + rad * cos(2 * PI * i / steps));
yValues[i] = (centerY + rad * sin(2 * PI * i / steps));
}
beginShape();
strokeWeight(4)
for (let i = 0; i < xValues.length; i++) {
curveVertex(xValues[i], yValues[i]);
}
endShape(CLOSE);
}
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.0/lib/p5.js"></script>
First off, one reason why creating new Face instances in your draw() function might cause the page to hang would be because the draw() function runs many times per second, and in JavaScript large numbers of allocations (such as creating new instances of a class) can be bad for performance. However, simply allocating one class instance per call to draw() wouldn't normally cause a problem. If you are adding those instances to a data structure such as an array on the other hand, that could cause you page to use an excessive amount of memory and eventually hang. Without seeing the exact code that is causing the hang it is hard to say.
If you want to implement the above sketch in an Object Oriented way, then you should take the following steps:
Give your class some constructor parameters that define it's properties, such as location and size, and store those as instance fields.
Separate your class behaviors from the constructor (i.e. give it a method that causes it to be displayed.
Instantiate it once and then call that instance method in draw() so that it is displayed each frame.
let face;
function setup() {
createCanvas(350, 350, WEBGL);
face = new Face(100, random(10, 30), 0, 0);
}
function draw() {
background('white');
face.show();
}
class Face {
constructor(radius, steps, centerX, centerY) {
this.radius = radius;
this.steps = steps;
this.centerX = centerX;
this.centerY = centerY;
}
show() {
var xValues = [];
var yValues = [];
for (var i = 0; i < this.steps; i++) {
let rad = this.radius + random(-this.radius / 50, this.radius / 50)
xValues[i] = (this.centerX + rad * cos(2 * PI * i / this.steps));
yValues[i] = (this.centerY + rad * sin(2 * PI * i / this.steps));
}
strokeWeight(4);
beginShape();
for (let i = 0; i < xValues.length; i++) {
curveVertex(xValues[i], yValues[i]);
}
endShape(CLOSE);
}
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.0/lib/p5.js"></script>
Additionally, when you are displaying your curve, it is not necessary to create arrays for the x and y values in one loop and then call curveVertex() in a separate loop. Instead you can combine these loops and avoid unnecessary array allocations.
show() {
strokeWeight(4);
beginShape();
for (let i = 0; i < this.steps; i++) {
let rad = this.radius + random(-this.radius / 50, this.radius / 50);
curveVertex(
this.centerX + rad * cos(2 * PI * i / this.steps),
this.centerY + rad * sin(2 * PI * i / this.steps)
);
}
endShape(CLOSE);
}

Constructor not working properly in p5 js

I am having problems with p5 js.
I am trying to add two ellipse objects that spin around the circumference of a circle.
The code below represents the constructor function of the object :
function Obj(){
this.ang = TWO_PI;
this.x = w/2 + cos(ang)*r;
this.y = h/2 + sin(ang)*r;
this.fil = 255;
this.size = 28;
this.show = function(){
noStroke();
fill(this.fil);
ellipse(this.x,this.y,this.size,this.size);
}
this.update = function(){
this.ang+=.02;
}
}
And this is the main file :
let w = innerWidth;
let h = innerHeight;
let xd = [];
let r = 200;
function setup() {
createCanvas(w, h);
for (let i = 0; i < 2; i++)
xd[i] = new Obj();
}
function draw(){
background(0,70,80);
noFill();
strokeWeight(7);
stroke(255);
ellipse(w/2, h/2, r*2, r*2);
xd[0].update();
xd[0].show();
}
The problem is that it says that ang is not defined even though i did clearly define it with this.ang = TWO_PI;. And if I declare it in the main file and in setup() I say ang = TWO_PI; the object stays in place. Can anyone help ?
Thank you.
The problem in the constructor function in this code, it should be like this :
this.x = w/2 + cos(this.ang)*r;
this.y = h/2 + sin(this.ang)*r;
Because you are using a property from the constructor function itself

Matter.js Collision Not Detecting

I'm trying to practice using matter.js to create top down levels Bomberman style.
Right now I want to get my circle, which is controlled by arrow keys to move and bump into static squares but it is just going through them. Did I set it up incorrectly? I have been coding for three months, so I might be quite slow sorry!
var Engine = Matter.Engine,
World = Matter.World,
Bodies = Matter.Bodies;
var engine = Engine.create();
var world = engine.world;
var player;
var rocks = [];
var cols = 7;
var rows = 7;
function setup() {
createCanvas(750, 750);
Engine.run(engine);
player = new Player(300, 300, 25);
var spacing = width / cols;
for (var j = 0; j < rows; j++) {
for (var i = 0; i < cols; i++) {
var r = new Rocks(i * spacing, j * spacing);
rocks.push(r);
}
}
}
function draw() {
background(51);
Engine.update(engine);
for (var i = 0; i < rocks.length; i++) {
rocks[i].show();
}
player.show();
player.move();
}
function Player(x, y, r) {
this.body = Bodies.circle(x, y, r);
this.r = r;
World.add(world, this.body);
this.show = function () {
ellipse(x, y, this.r * 2);
}
this.move = function () {
if (keyIsDown(RIGHT_ARROW))
x += 10;
if (keyIsDown(LEFT_ARROW))
x -= 10;
if (keyIsDown(UP_ARROW))
y -= 10;
if (keyIsDown(DOWN_ARROW))
y += 10;
x = constrain(x, this.r, height - this.r);
y = constrain(y, this.r, width - this.r);
}
}
function Rocks(x, y, w, h, options) {
var options = {
isStatic: true
}
this.body = Bodies.rectangle(x, y, h, w, options);
this.w = w;
this.h = h;
this.size = player.r * 2;
World.add(world, this.body);
this.show = function () {
rect(x, y, this.size, this.size);
}
}
I think the problem is that you player is not drawn in the same position that the physics engine thinks its at.
in your Player function after the initialization of x and y the rest all need to be this.body.position.x and this.body.position.y. Otherwise you're changing where the picture is drawn at but not where the player actually is.
I'm not entirely sure what all you want me to point out besides that but also I think you want to disable gravity with engine.world.gravity.y = 0 and I was trying to fix the constrain function because as I tested it it wasn't working, I wasn't able to fix it but I'd recommend just making static boundary objects for the walls and just don't draw them.
Also matter.js processes the locations of objects from their centers. When drawing the objects you either have to take that into consideration or switch the mode to ellipseMode(CENTER);, 'rectMode(CENTER);` .. etc.
I hope this helps

Categories