How to have the random function run once in the draw function? - javascript

This is the code
let x = 10;
let y = 0;
let bottomy = 100;
let Speed = 1
function setup() {
createCanvas(windowWidth,600);
}
function draw() {
background(0)
strokeWeight(3)
stroke(255)
for (i = 0; i < width; i += 20) {
water()
line(i,y,i,bottomy)
}
bottomy = bottomy + Speed;
if (bottomy > height) {
bottomy = 100
}
frameRate(1)
}
function water(){
bottomy = random(0,600)
//noLoop()
}
I want to randomise each y2 line coordinate in the for loop. But then have the y2 line coordinate to increment by 1. To create a rain effect.
I can't put the random variable in setup and then call it in the for loop because it won't affect each line in the for loop and I can't put the for loop in setup because I need the line to be drawn.
I've also tried creating a function that loops once and then calling it in draw but it ends up stopping all the code in the draw function.
I've seen examples where they generate like an infinite amount of random lines. But I would like to keep the x position of each line the same if possible. If it's not possible to do this with a for loop and I have to draw each line individually that's fine I was just wondering if this is possible to efficiently do this with a for loop.

I think what you are looking for is individual variables for each line.
Probably a classic:
from-several-variables-to-arrays
from-several-arrays-to-classes
situation. (those were made in Java's processing, but the concept can be easily adapted)
Anyway, i think this is what you tried to make with your code, but it does not work as intended, since you only have one bottomY var for all lines.
let x = 10;
let y = 0;
let bottomY = 100;
let spd = 1; //by convention capitalized names are for classes
function setup() {
createCanvas(windowWidth, 600);
}
function draw() {
background(0);
strokeWeight(3);
stroke(255);
for (i = 0; i < width; i += 20) {
if (bottomY > height) {
bottomY = random(600);
}
line(i, y, i, bottomY);
}
bottomY += spd;
}
What you want is several lines that each has individual x and bottomY
So you could use two arrays for that:
let x = [];
let y = 0;
let bottomY = [];
//why not have individual speeds as well...
let spd = [];
function setup() {
createCanvas(windowWidth, 600);
for (i = 0; i < width; i += 20) {
x.push(i);
bottomY.push(random(height));
spd.push(random(0.6, 2));
}
strokeWeight(3);
stroke(255);
}
function draw() {
background(0);
for (let i = 0; i < x.length; i++) {
line(x[i], y, x[i], bottomY[i]);
if (bottomY[i] < height) {
bottomY[i] += spd[i];
} else {
bottomY[i] = random(height);
}
}
}
And finally a simple class implementation:
let rain = [];
function setup() {
createCanvas(windowWidth, 600);
for (i = 0; i < width; i += 20) {
rain.push(new Drop(i, 0));
}
}
function draw() {
background(0);
for (const d of rain) {
d.run();
}
}
class Drop {
constructor(x, y) {
this.x = x;
this.y = y;
this.btY = random(height);
this.spd = random(0.6, 2);
}
run() {
strokeWeight(3);
stroke(255);
line(this.x, this.y, this.x, this.btY);
if (this.btY < height) {
this.btY += this.spd;
} else {
this.btY = random(height);
}
}
} //class
Did it make sense?

Related

My code is not drawing bars to the screen from an array

I am trying to make one of those sorting algorithms and want to have the bars get randomly generated with height and x position. However, when I run the code, it doesn't draw any bars. I'm also not getting any errors sent so I have no idea what's going wrong.
class Bar {
constructor(x, height) {
this.height = height;
this.size = 10;
this.x = x;
}
draw() {
fill("blue");
rect(this.x * this.size, height - (this.height * this.size), this.size, this.height * this.size);
}
}
let bars = [];
function setup() {
createCanvas(400, 400);
generateBars();
}
function generateBars() {
for (let i = 0; i < 39; i++) {
randomX = random(0, 39);
randomHeight = random(0, 40)
for (let j = 0; j < bars.length; j++) {
if (bars[j].x == randomX) {
randomX = random(0, 39);
} else {
for (let h = 0; h < bars.length; h++) {
if (bars[j].height == randomHeight) {
randomHeight = random(0, 40);
} else {
bars[i] = new Bar(randomX, randomHeight);
}
}
}
}
}
}
function draw() {
background(220);
for (let k = 0; k < bars.length; k++) {
bars[k].draw();
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.min.js"></script>
The logic in generateBars seems convoluted and the intention is unclear.
As far as I see Bar's x property acts more of a value property storing the number to be sorted later because it is multiplied by size in draw(). (e.g. x is not the final x position the bar should be rendered at, as it's name implies).
I would simply intanstiate Bar like so:
class Bar{
constructor(x, height){
this.height = height;
this.size = 10;
this.x = x;
}
draw(){
fill("blue");
rect(this.x * this.size, height - (this.height * this.size), this.size, this.height * this.size);
}
}
let bars = [];
function setup() {
createCanvas(400, 400);
generateBars();
}
function generateBars(){
for(let i = 0; i < 40; i++){
randomHeight = random(0, 40)
bars[i] = new Bar(i, randomHeight);
}
}
function draw() {
background(220);
for(let k = 0; k < bars.length; k++){
bars[k].draw();
}
}
function mouseClicked(){
setup();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.min.js"></script>
(I've added a hacky mouseClicked() handler so it's easy to regenerate the bars for testing/debugging purposes. You may not need it in your final code)
Update Based on your comment, it's the bar height that is tightly coupled to the value to be sorted later.
One idea is to simply pre-generate the list of numbers to sort and shuffle() them first:
class Bar{
constructor(x, height){
this.height = height;
this.size = 10;
this.x = x;
}
draw(){
fill("blue");
rect(this.x * this.size, height - (this.height * this.size), this.size, this.height * this.size);
}
}
let bars = [];
function setup() {
createCanvas(400, 400);
generateBars();
}
function generateBars(){
// array to be sorted
let barValues = [];
for(let i = 0; i < 40; i++){
// values are sorted first
barValues[i] = i;
}
// then we shuffle them: second argument = shuffle in place (replacing the old array)
shuffle(barValues, true);
// assign shuffled values to Bar instances
for(let i = 0; i < 40; i++){
bars[i] = new Bar(i, barValues[i]);
}
}
function draw() {
background(220);
for(let k = 0; k < bars.length; k++){
bars[k].draw();
}
}
function mouseClicked(){
setup();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.5.0/p5.min.js"></script>

How to modify p5js code to work in instance mode

The following code works, but I want to be able to have multiple canvasses with different background colors and sizes etc. - how do I modify the code to turn it in to a p5 instance?- I can't get the instance to recognize the external class 'ellipse' and the drawCircles, and setInterval functions
let circles = [];
let circlesStatic = [];
let counter = 0;
function setup() {
createCanvas(400, 400);
setInterval(expandCircle, 1000);
}
function draw() {
background(0);
drawCircles();
}
/////////////////////////////////////////////////////////////////////////////
function expandCircle() {
let e = new Ellipse(random(100), random(100), counter);
circles.push(e);
//console.log("circlesStatic ", circlesStatic);
//console.log(counter);
counter++;
}
///////////////////////////////////////////////////////////////////////////////
class Ellipse {
// constructor
constructor(x, y, counter) {
this.x = x; // copy x argument value to the instance (this) x property
this.y = y; // copy x argument value to the instance (this) x property
// current size - continuously updated
this.size = 10;
// minimum size
this.minSize = 10;
// maximum size
this.maxSize = 30;
// change speed for size (how much will the size increase/decrease each frame)
this.sizeSpeed = 3;
// internal frameCount replacement
this.tick = 0;
this.counter = counter;
// this.fill=(255,0,0);
}
isStatic() {
return this.sizeSpeed === 0;
}
render() {
// if the size is either too small, or too big, flip the size speed sign (if it was positive (growing) - make it negative (shrink) - and vice versa)
if (this.size < this.minSize || this.size > this.maxSize) {
this.sizeSpeed *= -1;
}
// increment the size with the size speed (be it positive or negative)
this.size += this.sizeSpeed;
//console.log(this.sizeSpeed);
if (this.size <= this.minSize) {
circlesStatic[this.counter] = [];
circlesStatic[this.counter][0] = this.x;
circlesStatic[this.counter][1] = this.y;
this.sizeSpeed = 0;
}
ellipse(this.x, this.y, this.size, this.size);
}
}
////////////////////////////////////////////////////////////////////////////////
function drawCircles() {
for (let i = 0; i < circles.length; i++) {
circles[i].render();
if (circles[i].isStatic()) {
circles.splice(i, 1);
i--;
}
}
for (let i = 0; i < circlesStatic.length; i++) {
strokeWeight(0);
var popColour = (255, 255, 200);
fill(popColour);
circle(circlesStatic[i][0], circlesStatic[i][1], 1);
}
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.js"></script>

Changing direction of the ball after collision

I have written this code to demonstrate a basic visual p5js project. In here there are 10 balls of varying sizes and colors that spawn at random positions, move around in the canvas and might collide with each other. I am not looking for elastic collision or "realistic" collision physics for that matter. I just want the balls to change to a different direction (can be random as long as it works) and work accordingly.
Here is my code :
class Ball {
//create new ball using given arguments
constructor(pos, vel, radius, color) {
this.pos = pos;
this.vel = vel;
this.radius = radius;
this.color = color;
}
//collision detection
collide(check) {
if (check == this) {
return;
}
let relative = p5.Vector.sub(check.pos, this.pos);
let dist = relative.mag() - (this.radius + check.radius);
if (dist < 0) { //HELP HERE! <--
this.vel.mult(-1);
check.vel.mult(-1);
}
}
//give life to the ball
move() {
this.pos.add(this.vel);
if (this.pos.x < this.radius) {
this.pos.x = this.radius;
this.vel.x = -this.vel.x;
}
if (this.pos.x > width - this.radius) {
this.pos.x = width - this.radius;
this.vel.x = -this.vel.x;
}
if (this.pos.y < this.radius) {
this.pos.y = this.radius;
this.vel.y = -this.vel.y;
}
if (this.pos.y > height - this.radius) {
this.pos.y = height - this.radius;
this.vel.y = -this.vel.y;
}
}
//show the ball on the canvas
render() {
fill(this.color);
noStroke();
ellipse(this.pos.x, this.pos.y, this.radius * 2);
}
}
let balls = []; //stores all the balls
function setup() {
createCanvas(window.windowWidth, window.windowHeight);
let n = 10;
//loop to create n balls
for (i = 0; i < n; i++) {
balls.push(
new Ball(
createVector(random(width), random(height)),
p5.Vector.random2D().mult(random(5)),
random(20, 50),
color(random(255), random(255), random(255))
)
);
}
}
function draw() {
background(0);
//loop to detect collision at all instances
for (let i = 0; i < balls.length; i++) {
for (let j = 0; j < i; j++) {
balls[i].collide(balls[j]);
}
}
//loop to render and move all balls
for (let i = 0; i < balls.length; i++) {
balls[i].move();
balls[i].render();
}
}
Here is a link to the project : https://editor.p5js.org/AdilBub/sketches/TNn2OREsN
All I need is the collision to change the direction of the ball to a random direction and not get stuck. Any help would be appreciated. I am teaching kids this program so I just want basic collision, doesnot have to be "realistic".
Any help is appreciated. Thank you.
The issues you are currently encountering with balls being stuck has to do with randomly generating balls that overlap such that after one iteration of movement they still overlap. When this happens both balls will simply oscillate in place repeatedly colliding with each other. You can prevent this simply by checking for collisions before adding new balls:
class Ball {
//create new ball using given arguments
constructor(pos, vel, radius, color) {
this.pos = pos;
this.vel = vel;
this.radius = radius;
this.color = color;
}
isColliding(check) {
if (check == this) {
return;
}
let relative = p5.Vector.sub(check.pos, this.pos);
let dist = relative.mag() - (this.radius + check.radius);
return dist < 0;
}
//collision detection
collide(check) {
if (this.isColliding(check)) {
this.vel.x *= -1;
this.vel.y *= -1;
check.vel.x *= -1;
check.vel.y *= -1;
}
}
//give life to the ball
move() {
this.pos.add(this.vel);
if (this.pos.x < this.radius) {
this.pos.x = this.radius;
this.vel.x = -this.vel.x;
}
if (this.pos.x > width - this.radius) {
this.pos.x = width - this.radius;
this.vel.x = -this.vel.x;
}
if (this.pos.y < this.radius) {
this.pos.y = this.radius;
this.vel.y = -this.vel.y;
}
if (this.pos.y > height - this.radius) {
this.pos.y = height - this.radius;
this.vel.y = -this.vel.y;
}
}
//show the ball on the canvas
render() {
fill(this.color);
noStroke();
ellipse(this.pos.x, this.pos.y, this.radius * 2);
}
}
let balls = []; //stores all the balls
function setup() {
createCanvas(500, 500);
let n = 10;
//loop to create n balls
for (i = 0; i < n; i++) {
let newBall =
new Ball(
createVector(random(width), random(height)),
p5.Vector.random2D().mult(random(5)),
random(20, 40),
color(random(255), random(255), random(255))
);
let isOk = true;
// check for collisions with existing balls
for (let j = 0; j < balls.length; j++) {
if (newBall.isColliding(balls[j])) {
isOk = false;
break;
}
}
if (isOk) {
balls.push(newBall);
} else {
// try again
i--;
}
}
}
function draw() {
background(0);
//loop to detect collision at all instances
for (let i = 0; i < balls.length; i++) {
for (let j = 0; j < i; j++) {
balls[i].collide(balls[j]);
}
}
//loop to render and move all balls
for (let i = 0; i < balls.length; i++) {
balls[i].move();
balls[i].render();
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
That said, fully elastic collisions (which means collisions are instantaneous and involve no loss of energy due to deformation and resulting heat emission) are actually quite simple to simulate. Here's a tutorial I made on OpenProcessing demonstrating the necessary concepts using p5.js: Elastic Ball Collision Tutorial.
Here's the final version of the code from that tutorial:
const radius = 30;
const speed = 100;
let time;
let balls = []
let boundary = [];
let obstacles = [];
let paused = false;
function setup() {
createCanvas(400, 400);
angleMode(DEGREES);
ellipseMode(RADIUS);
boundary.push(createVector(60, 4));
boundary.push(createVector(width - 4, 60));
boundary.push(createVector(width - 60, height - 4));
boundary.push(createVector(4, height - 60));
obstacles.push(createVector(width / 2, height / 2));
balls.push({
pos: createVector(width * 0.25, height * 0.25),
vel: createVector(speed, 0).rotate(random(0, 360))
});
balls.push({
pos: createVector(width * 0.75, height * 0.75),
vel: createVector(speed, 0).rotate(random(0, 360))
});
balls.push({
pos: createVector(width * 0.25, height * 0.75),
vel: createVector(speed, 0).rotate(random(0, 360))
});
time = millis();
}
function keyPressed() {
if (key === "p") {
paused = !paused;
time = millis();
}
}
function draw() {
if (paused) {
return;
}
deltaT = millis() - time;
time = millis();
background('dimgray');
push();
fill('lightgray');
stroke('black');
strokeWeight(2);
beginShape();
for (let v of boundary) {
vertex(v.x, v.y);
}
endShape(CLOSE);
pop();
push();
fill('dimgray');
for (let obstacle of obstacles) {
circle(obstacle.x, obstacle.y, radius);
}
pop();
for (let i = 0; i < balls.length; i++) {
let ball = balls[i];
// update position
ball.pos = createVector(
min(max(0, ball.pos.x + ball.vel.x * (deltaT / 1000)), width),
min(max(0, ball.pos.y + ball.vel.y * (deltaT / 1000)), height)
);
// check for collisions
for (let i = 0; i < boundary.length; i++) {
checkCollision(ball, boundary[i], boundary[(i + 1) % boundary.length]);
}
for (let obstacle of obstacles) {
// Find the tangent plane that is perpendicular to a line from the obstacle to
// the moving circle
// A vector pointing in the direction of the moving object
let dirVector = p5.Vector.sub(ball.pos, obstacle).normalize().mult(radius);
// The point on the perimiter of the obstacle that is in the direction of the
// moving object
let p1 = p5.Vector.add(obstacle, dirVector);
checkCollision(ball, p1, p5.Vector.add(p1, p5.Vector.rotate(dirVector, -90)));
}
// Check for collisions with other balls
for (let j = 0; j < i; j++) {
let other = balls[j];
let distance = dist(ball.pos.x, ball.pos.y, other.pos.x, other.pos.y);
if (distance / 2 < radius) {
push();
let midPoint = p5.Vector.add(ball.pos, other.pos).div(2);
let boundaryVector = p5.Vector.sub(other.pos, ball.pos).rotate(-90);
let v1Parallel = project(ball.vel, boundaryVector);
let v2Parallel = project(other.vel, boundaryVector);
let v1Perpendicular = p5.Vector.sub(ball.vel, v1Parallel);
let v2Perpendicular = p5.Vector.sub(other.vel, v2Parallel);
ball.vel = p5.Vector.add(v1Parallel, v2Perpendicular);
other.vel = p5.Vector.add(v2Parallel, v1Perpendicular);
let bounce = min(radius, 2 * radius - distance);
ball.pos.add(p5.Vector.rotate(boundaryVector, -90).normalize().mult(bounce));
other.pos.add(p5.Vector.rotate(boundaryVector, 90).normalize().mult(bounce));
pop();
}
}
}
// Only draw balls after all position updates are complete
for (let ball of balls) {
circle(ball.pos.x, ball.pos.y, radius);
}
}
function drawLine(origin, offset) {
line(origin.x, origin.y, origin.x + offset.x, origin.y + offset.y);
}
// Handles collision with a plane given two points on the plane.
// It is assumed that given a vector from p1 to p2, roating that vector
// clockwise 90 degrees will give a vector pointing to the in-bounds side of the
// plane (i.e. a "normal").
function checkCollision(ball, p1, p2) {
let boundaryVector = p5.Vector.sub(p2, p1);
let objVector = p5.Vector.sub(ball.pos, p1);
let angle = boundaryVector.angleBetween(objVector);
let distance = objVector.mag() * sin(angle);
if (distance <= radius) {
// Collision
let vParallel = project(ball.vel, boundaryVector);
let vPerpendicular = p5.Vector.sub(ball.vel, vParallel);
ball.vel = p5.Vector.add(vParallel, p5.Vector.mult(vPerpendicular, -1));
let bounce = min(radius, (radius - distance) * 2);
// If the ball has crossed over beyond the plane we want to offset it to be on
// the in-bounds side of the plane.
let bounceOffset = p5.Vector.rotate(boundaryVector, 90).normalize().mult(bounce);
ball.pos.add(bounceOffset);
}
}
// p5.Vector helpers
function project(vect1, vect2) {
vect2 = p5.Vector.normalize(vect2);
return p5.Vector.mult(vect2, p5.Vector.dot(vect1, vect2));
}
function reject(vect1, vect2) {
return p5.Vector.sub(vect1, project(vect1, vect2));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>

How to pass an array of objects to the P5.js draw function?

I have created an array of objects in the P5 setup() function and can draw them without issue. However, when I try to draw the same object in the P5 draw() function it doesn't work.
I am guessing this is because I am not passing the object into the draw() method but I am having an issue because I don't understand how P5 draw() is called.
class Particle {
constructor(x, y, size) {
this.x = x;
this.y = y;
this.size = size;
}
display() {
ellipse(this.x, this.y, this.size, this.size);
}
}
function setup() {
var screenWidth = 720;
var screenHeight = 480;
var numberOfParticles = 10;
var particles = [];
for (var idx = 0; idx < numberOfParticles; idx++) {
size = Math.floor(Math.random() * 40) + 10;
x = Math.floor(Math.random() * (screenWidth - (size * 2))) + size;
y = Math.floor(Math.random() * (screenHeight - (size * 2))) + size;
var p = new Particle(x, y, size);
particles.push(p);
}
createCanvas(screenWidth,screenHeight);
background(100,150,200);
fill("yellow");
// This displays the particles
for (var idx = 0; idx < particles.length; idx++) {
particles[idx].display();
}
}
function draw() {
fill("green");
// This DOESN'T display the particles
for (var idx = 0; idx < particles.length; idx++) {
particles[idx].display();
}
}
You don't pass arguments into the draw() function. The draw() function does not take any arguments.
Instead, just define the variable outside of both functions. Then you can initialize it in your setup() function, and use it inside the draw() function. Like this:
var textToDisplay;
function setup(){
createCanvas(500, 500);
textToDisplay = "hello world";
}
function draw(){
background(64);
text(textToDisplay, 100, 100);
}

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