Two different shaped objects collision detection in p5.js - javascript

I'm trying to have my Particle object collide and reflect off my Slate object.
If I wanted to use an ellipse, it would be simple because I could just create a radius variable - can't do that with a rectangle.
It's something to do with the distance variable, I just can't figure it out.
var div;
var movers;
function setup() {
createCanvas(windowWidth,windowHeight);
background("#FDEDEB");
div = new Slate();
movers = new Particle();
}
function draw() {
background("#FDEDEB");
div.display();
movers.display();
movers.update();
movers.move();
if (movers.hits(div)) {
console.log("hit");
}
}
function Slate() {
this.x = 30;
this.y = height/2;
this.display = function() {
noStroke();
fill("#DF4655");
rect(this.x,this.y, 700, 200);
}
}
function Particle() {
this.pos = createVector(10,0);
this.vel = createVector(0,0);
this.acc = createVector(0.01,0.01);
this.history = [];
this.display = function() {
fill("#DF4655");
point(this.pos.x,this.pos.y);
//beginShape();
for(var j = 0; j < this.history.length; j++) {
var pos = this.history[j];
ellipse(pos.x,pos.y, 5, 3);
}
//endShape();
}
this.update = function() {
var v = createVector(this.pos.x,this.pos.y);
this.history.push(v);
if (this.history.length > 10) {
this.history.splice(0,1);
}
}
this.hits = function(div) {
// BOUNCE OFF SLATE
var d = dist(this.pos.x,this.pos.y,div.x,div.y);
if (d < 0) {
console.log('hits');
}
}
this.move = function() {
this.pos.add(this.vel);
this.vel.add(this.acc);
this.vel.limit(10);
for (var i = 0; i < this.history.length; i++) {
this.history[i].x += random(-2,2);
this.history[i].y += random(-2,2);
}
}
}

If the particle is a point (or can be represented as a point), you need to use point-rectangle collision detection. Basically you would need to check whether the point is between the left and right edges and the top and bottom edges of the rectangle.
if(pointX > rectX && pointX < rectX + rectWidth && pointY > rectY && pointY < rectY + rectHeight){
//the point is inside the rectangle
}
If the particle is an ellipse and you need to factor in the radius of that ellipse, then you're better off representing the particle as a rectangle, just for collision purposes. Then you can use rectangle-rectangle collision detection. This is also called a bounding box, and it's one of the most common ways to handle collision detection.
if(rectOneRight > rectTwoLeft && rectOneLeft < rectTwoRight && rectOneBottom > rectTwoTop && rectOneTop < rectTwoBottom){
//the rectangles are colliding
}
Shameless self-promotion: I wrote a tutorial on collision detection available here. It's for Processing, but everything is the same for P5.js.

Related

Is there a way to space correctly a string of text in p5.js?

I saw many times really cool examples of kinetic typography. In these examples every letter is a particle. The text is a particle system and it may be subject to various forces such gravity or even centrifugal force. These systems are made in Processing and in p5.js.
I am building an interactive screen for the web filled with text using p5.js, inspired by these example of kinetic typography.
When the user moves the cursor on the text this start bounce all around the screen.
I translated the sketch from Processing to p5.js and I have noticed this problem related to the spacing of the text in the setup() function.
In Processing the code looks like this and it function correctly.
I want to focus on this section of the Processing code:
void setup() {
size(640, 360);
//load the font
f = createFont("Arial", fontS, true);
textFont(f);
// Create the array the same size as the String
springs = new Spring[message.length()];
// Initialize Letters (Springs) at the correct x location
int locx = 40;
//initialize Letters (Springs) at the correct y location
int locy = 100;
for (int i = 0; i < message.length(); i++) {
springs[i] = new Spring(locx, locy, 40, springs, i, message.charAt(i));
locx += textWidth(message.charAt(i));
//boudaries of text just to make it a nice "go to head"
if (locx >= 360) {
locy+=60;
locx = 40;
}
}
}
You can see the result
As you can see the parameter
springs[i] = new Spring(locx, locy, 40, springs, i, message.charAt(i));
locx += textWidth(message.charAt(i));
does its job, spacing the letters.
However when I translate this sketch in P5.js, I don't get the same nice spacing.
This is the same section but is the p5.js code:
function setup() {
createCanvas(640, 360);
noStroke();
textAlign(LEFT);
// Create the array the same size as the String
springs = new Array(message.length);
// Initialize Letters (Springs) at the correct x location
var locx = 10;
//initialize Letters (Springs) at the correct y location
var locy = 120;
for (var i = 0; i < message.length; i++) {
springs[i] = new Spring(locx, locy, 40, springs, i, message.charAt(i));
locx += textWidth(message.charAt(i));
//boudaries of text just to make it a nice "go to head"
if(locx>= 390){
locy+= 60;
locx= 40;
}
}
}
The result is show
I am sure, in the p5js code, that there is a problem regarding this part:
springs[i] = new Spring(locx, locy, 40, springs, i, message.charAt(i));
locx += textWidth(message.charAt(i));
Because I tried to fix it multiplying the value of the locx as shown here:
springs[i] = new Spring(locx*5, locy, 40, springs, i, message.charAt(i));
I then got
which can seems correct, but I'm sure it is not.
At this point I have no idea how to fix it, it must be something of p5.js I'm unaware of.
Any help is greatly appreciate.
//Edit
As suggested in the comment here you can find the Spring class written in p5.js:
// Spring class
class Spring {
constructor (_x, _y, _s, _others, _id, letter_) {
// Screen values
this.x_pos = this.tempxpos = _x;
this.y_pos = this.tempypos = _y;
this.size = _s;
this.over = false;
this.move = false;
// Spring simulation constants
this.mass = 8.0; // Mass
this.k = 0.2; // Spring constant
this.damp =0.98; // Damping
this.rest_posx = _x; // Rest position X
this.rest_posy = _y; // Rest position Y
// Spring simulation variables
//float pos = 20.0; // Position
this.velx = 0.0; // X Velocity
this.vely = 0.0; // Y Velocity
this.accel = 0; // Acceleration
this.force = 0; // Force
this.friends = _others;
this.id = _id;
this.letter = letter_;
}
update() {
if (this.move) {
this.rest_posy = mouseY;
this.rest_posx = mouseX;
}
this.force = -this.k * (this.tempypos - this.rest_posy); // f=-ky
this.accel = this.force / this.mass; // Set the acceleration, f=ma == a=f/m
this.vely = this.damp * (this.vely + this.accel); // Set the velocity
this.tempypos = this.tempypos + this.vely; // Updated position
this.force = -this.k * (this.tempxpos - this.rest_posx); // f=-ky
this.accel = this.force / this.mass; // Set the acceleration, f=ma == a=f/m
this.velx = this.damp * (this.velx + this.accel); // Set the velocity
this.tempxpos = this.tempxpos + this.velx; // Updated position
if ((this.overEvent() || this.move) && !(this.otherOver()) ) {
this.over = true;
} else {
this.over = false;
}
}
// Test to see if mouse is over this spring
overEvent() {
let disX = this.x_pos - mouseX;
let disY = this.y_pos - mouseY;
let dis = createVector(disX, disY);
if (dis.mag() < this.size / 2 ) {
return true;
} else {
return false;
}
}
// Make sure no other springs are active
otherOver() {
for (let i = 0; i < message.length; i++) {
if (i != this.id) {
if (this.friends[i].over == true) {
this.velx = -this.velx;
return true;
}
}
}
return false;
}
//the springs collides with the edges of the screen
box_collision() {
if(this.tempxpos+this.size/2>width){
this.tempxpos = width-this.size/2;
this.velx = -this.velx;
} else if (this.tempxpos - this.size/2 < 0){
this.tempxpos = this.size/2;
this.velx = -this.velx;
}
if (this.tempypos+this.size/2>height) {
this.tempypos = height-this.size/2;
this.vely = -this.vely;
} else if (this.tempypos- this.size/2 < 0) {
this.tempypos = this.size/2;
this.vely = -this.vely;
}
}
//the springs collides with each other
collide() {
for (var i = this.id + 1; i < message.length; i++) {
var dx = this.friends[i].tempxpos - this.tempxpos;
var dy = this.friends[i].tempypos - this.tempypos;
var distance = sqrt(dx*dx + dy*dy);
var minDist = this.friends[i].size/2 + this.size/2;
if (distance < minDist) {
var angle = atan2(dy, dx);
var targetX = this.tempxpos + cos(angle) * minDist;
var targetY = this.tempypos + sin(angle) * minDist;
var ax = (targetX - this.friends[i].tempxpos) * 0.01;
var ay = (targetY - this.friends[i].tempypos) * 0.01;
this.velx -= ax;
this.vely -= ay;
this.friends[i].velx += ax;
this.friends[i].vely += ay;
}
}
}
//display the letter Particle
display() {
if (this.over) {
fill(255, 0, 0);
} else {
fill(255);
}
noStroke();
textSize(fontS);
//for debugging
// ellipse(this.tempxpos, this.tempypos, this.size, this.size);
text(this.letter, this.tempxpos, this.tempypos);
}
pressed() {
if (this.over) {
this.move = true;
} else {
this.move = false;
}
}
released() {
this.move = false;
this.rest_posx = this.x_pos;
this.rest_posy = this.y_pos;
}
}
And here you can find the link to the P5.js editor with the code:
Spring text p5js code
note: I had to fix the Spring class because I didn't realize that all of my functions where initialized in the constructor. Now the functions that compose the Class are outside the constructor. I still haven't figured out how to fix the spacing problem.
I managed to resolve the issue.
As far as I understood the code was correct from the beginning.
What I missed through the journey was that I have to set the font size in the function setup() if I want to display the text on the screen correctly.
You can still see the result by checking the link to the p5.js editor I posted previously.

How can i simulate a rigid body at micro level using p5.js?

I just learned that any particle of a rigid body when viewed from another particle's reference frame (of the same body) performs only circular motion, no matter what chaotic path the body is following! So I tried to simulate the same.
I created 100 particles closely located to form a body, and made them follow any arbitrary path , but now i can't get how to stick to one of the particle's reference frame & watch others.
This is what i have done (which i think is of no use):
var path = [];
var particles = [];
class Particle {
constructor(x_, y_) {
this.x_ = x_;
this.y_ = y_;
this.x = 0;
this.y = 0;
this.pathIndex = 0;
this.draw = () => {
fill(360, 100, 100);
circle(this.x, this.y, 1);
}
this.update = () => {
if (this.pathIndex >= path.length) {
this.pathIndex = 0;
} else {
this.x = (path[this.pathIndex].x + this.x_);
this.y = (path[this.pathIndex].y + this.y_);
this.pathIndex++;
}
this.draw();
}
}
}
function setup() {
createCanvas(400, 400);
colorMode(HSB);
for (let i = 0; i < 10; i++) {
for (let j = 0; j < 10; j++) {
particles.push(new Particle(i * 2, j * 2));
}
}
}
function draw() {
background(50);
fill(0);
noStroke();
for (let i = 0; i < path.length; i++) {
circle(path[i].x, path[i].y, 5);
}
for (let i = 0; i < particles.length; i++) {
particles[i].update();
}
}
function mouseDragged() {
path.push({
x: mouseX,
y: mouseY
});
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.9.0/p5.min.js"></script>
Click and drag to create a path.
For a real rigid body, each particle can have it's own velocity, and i think i can use relative velocity concept to move to a single particle's reference frame & then i can set other's velocity as:
v(1/2) = v1 - v2
Where v1, v2 are there original velocity and v(1/2) is velocity of 1 w.r.t. 2.
But again i don't have any idea to get particle's velocity.
How can i proceed with this, any help is greatly appreciated.

Changing color of intersecting area of squares

I am working on a project these days. My goal is to change the color of the intersecting areas of the two squares. I have written the code which detects whenever two squares intersect but I cant figure out how to change the color of the intersecting area. Kindly help me with this.
var sketch = function (p) {
with(p) {
let squares = [];
let dragObject = null; // variable to hold the object being dragged
p.setup = function() {
createCanvas(600, 520);
button1 = createButton("Alpha");
button2 = createButton("Bravo");
button3 = createButton("Charlie");
button4 = createButton("Delta");
button5 = createButton("Echo");
button6 = createButton("Foxtrot");
button7 = createButton("Golf");
button8 = createButton("Hotel");
button9 = createButton("India");
button10 = createButton("Juliet");
button1.mousePressed(doSomething);
};
p.draw = function() {
background(25, 240, 255);
// if a square is being dragged, update its position
if (this.dragObject != null) {
this.dragObject.position.x = mouseX;
this.dragObject.position.y = mouseY;
}
//draw all squares
for (let i = 0; i < squares.length; i++) {
let s = squares[i];
s.show();
}
for (let i = 0; i < squares.length; i++) {
for (let j = i + 1; j < squares.length; j++) {
if (i != j && squares[i].collides(squares[j])) {
squares[i].changecolor();
}
}
}
};
p.mousePressed = function () {
if (this.dragObject == null) {
//ask every square if they are being "hit"
for (let i = 0; i < squares.length; i++) {
let s = squares[i];
if (s.hitTest()) {
//if so, set the drag object as this square and return
this.dragObject = s;
return;
}
}
//no squares are hit, create a new square.
let square = new Square(mouseX, mouseY);
squares.push(square);
}
};
//mouse is released, release the current dragged object if there is one
p.mouseReleased = function () {
this.dragObject = null;
};
class Square {
constructor(InitialX, InitialY) {
this.w = 60;
this.h = 60;
this.position = {
x: InitialX,
y: InitialY
};
}
//basic test of mouse position against square position and if its inside the rectangle
hitTest() {
let x = mouseX - this.position.x;
let y = mouseY - this.position.y;
return (x > 0 && x < this.w) && (y > 0 && y < this.h);
}
show() {
fill(50);
rect(this.position.x, this.position.y, this.w, this.h);
}
collides(sqr) {
if (this.position.x < sqr.position.x + sqr.w &&
this.position.x + this.w > sqr.position.x &&
this.position.y < sqr.position.y + sqr.h &&
this.position.y + this.h > sqr.position.y) {
return true;
}
return false;
}
changecolor() {
fill(random(255), random(255), random(255));
background(200, 255, 100);
for (let i = 0; i < squares.length; i++) {
let s = squares[i];
s.show();
}
}
}
function doSomething() {
// fill(230, 170, 90);
// ellipse(random(600), random(410), 30, 30);
squares.pop();
}
}
};
let node = document.createElement('div');
window.document.getElementById('p5-container').appendChild(node);
new p5(sketch, node);
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.1.9/p5.js"></script>
<div id="p5-container"></div>
Lets think a bit how we could represent the intersecting area between two squares. Surely, one of the ways to do is simply to represent it as another rectangle, whose color we simply change based on the intercepting area. To draw a rectangle, we need to know the coordinates of the upper left corner, the width and the height. Therefore the challenge is to calculate those as we drag our squares around. This should be done in the draw() function. You already have the intersection check implemented, whats left is to calculate the new rectangle upper left point (newX, newY), width (newW) and height (newH).
In order to calculate the upper left corner, the width and height, we can add this to the block where we check for collision:
...
//block checking collision
if (i != j && squares[i].collides(squares[j])) {
squares[i].changecolor();
//set intersection color
fill(50);
//calculate parameters
newX = Math.max(squares[i].position.x, squares[j].position.x);
newY = Math.max(squares[i].position.y, squares[j].position.y);
newW = Math.min(squares[i].position.x + squares[i].w, squares[j].position.x + squares[j].w) - newX;
newH = Math.min(squares[i].position.y + squares[i].h, squares[j].position.y + squares[j].h) - newY;
//draw rectangle
rect(newX, newY, newW, newH);
}
Result:

How do you make a circle shaped button in p5.js?

I am making a p5.js project. In it I am generating a list (with 8 elements) and setting them to 1/0. Each one represents a bit (1,2,4,8,16,32,64,128) and if the element is 1, I add the index of the bit array.
For example i = 3, states[i] = 1, bit[i] = 8 so I add 8 to a number because the current state of that bit is 1.
Another thing is that it draws a circle that is red/green based on bit state.
Now that you know the basic idea, I want to add the ability for the user to press a circle to change its state (from 1 to 0 and from 0 to 1). I know how to change the state, but how do i test if the user has actually pressed the button (notice that the button is a circle)?
Here is my code so far:
var array = [0,1,0,1,1,1,1,1];
var values = [128,64,32,16,8,4,2,1];
function setup(){
//console.log(array);
createCanvas(600,600);
textStyle(BOLDITALIC);
textSize(50);
}
function draw(){
clear();
var a = calculate(array);
background(51);
fill(255);
text(a,250,500);
let crcl = 50;
let d = 20;
let r = d/2;
for (let i = 0; i < 8; i++){
}
for (let i = 0; i < 8; i++){
if (array[i] === 1){
fill(0,255,0);
circle(crcl, 50, d);
} else {
fill(255,0,0);
circle(crcl, 50, d);
}
crcl += 50;
}
}
function calculate(array){
let a = 0;
for (let i = 0; i < 8; i++){
if (array[i] === 1){
a += values[i];
}
}
return a;
}
My finished code for everyone who just wants to see the code!:
var array = [0,1,0,1,1,1,1,1];
var values = [128,64,32,16,8,4,2,1];
var positonsX = [50,100,150,200,250,300,350,400];
var crcl = 50;
var d = 20;
var r = d/2;
function setup(){
//console.log(array);
createCanvas(600,600);
textStyle(BOLDITALIC);
textSize(50);
}
function draw(){
clear();
let crcl = 50;
d = 20;
r = d/2;
a = calculate(array);
background(51);
fill(255);
text(a,250,500);
for (let i = 0; i < 8; i++){
if (array[i] === 1){
fill(0,255,0);
circle(crcl, 50, d);
} else {
fill(255,0,0);
circle(crcl, 50, d);
}
crcl += 50;
}
}
function calculate(array){
let a = 0;
for (let i = 0; i < 8; i++){
if (array[i] === 1){
a += values[i];
}
}
return a;
}
function mouseClicked(){
for (let i = 0; i < 8; i++){
if (dist(mouseX,mouseY,positonsX[i],50) <= d){
array[i] = 1 - array[i];
}
}
}
You can detect whether a point (in your case, mouseX, mouseY) is in a circle by comparing the distance between the point and the center of the circle, and comparing it to the radius of the circle. If the distance is less than the radius, then the point is inside the circle.
You can google "detect if point is inside circle" for a ton of results. Shameless self-promotion: this tutorials explains collision detection, including point-circle collision detection.
I'd check for mouseClicked (https://p5js.org/reference/#/p5/mouseClicked), and then mouseX, mouseY to see if a circle got clicked.
I had created this class to add circular buttons for one of my projects. Sharing a link to the sketch, might save others some time: p5.js web editor sketch
To check if the mouse pointer is within a circle:
Get the distance between the center of the circle and the mouse pointer
const distance = dist(circleX, circleY, mouseX, mouseY)
Mouse is within the circle if that distance is less that the circle's radius
const isInside = (distance < circleRadius)

How to avoid repeated?

Good day,
I am generating some circles with colors, sizes and positions. All of this things randomly.
But, my problem is that I do not want them to collide, so that no circle is inside another, not even a little bit.
The logic explained in detail within the code, I would like to know why the failure and why the infinite loop.
The important functions are:
checkSeparation and setPositions
window.addEventListener("load", draw);
function draw() {
var canvas = document.getElementById("balls"), // Get canvas
ctx = canvas.getContext("2d"); // Context
canvas.width = document.body.clientWidth; // Set canvas width
canvas.height = document.documentElement.scrollHeight; // Height
var cW = canvas.width, cH = canvas.height; // Save in vars
ctx.fillStyle = "#fff022"; // Paint background
ctx.fillRect(0, 0, cW, cH); // Coordinates to paint
var arrayOfBalls = createBalls(); // create all balls
setPositions(arrayOfBalls, cW, cH);
arrayOfBalls.forEach(ball => { // iterate balls to draw
ctx.beginPath(); // start the paint
ctx.fillStyle = ball.color;
ctx.arc(ball.x, ball.y, ball.radius, 0, (Math.PI/180) * 360, false); // draw the circle
ctx.fill(); // fill
ctx.closePath(); // end the paint
});
}
function Ball() {
this.x = 0; // x position of Ball
this.y = 0; // y position of Ball
this.radius = Math.floor(Math.random() * ( 30 - 10 + 1) + 10);
this.color = "";
}
Ball.prototype.setColor = function(){
for(var j = 0, hex = "0123456789ABCDEF", max = hex.length,
random, str = ""; j <= 6; j++, random = Math.floor(Math.random() * max), str += hex[random])
this.color = "#" + str;
};
function random(val, min) {
return Math.floor(Math.random() * val + min); // Random number
}
function checkSeparation(value, radius, toCompare) {
var min = value - radius, // Min border of circle
max = value + radius; // Max border of circle
// Why ? e.g => x position of circle + this radius it will be its right edge
for(; min <= max; min++) {
if(toCompare.includes(min)) return false;
/*
Since all the positions previously obtained, I add them to the array, in order to have a reference when verifying the other positions and that they do NOT collide.
Here I check if they collide.
In the range of:
[pos x - its radius, pos x + its radius]
*/
}
return true; // If they never collided, it returns true
}
function createBalls() {
var maxBalls = 50, // number of balls
balls = []; // array of balls
for(var j = 0; j < maxBalls; j++) { // create 50 balls
var newBall = new Ball(); // create ball
newBall.setColor(); // set the ball color
balls.push(newBall); //push the ball to the array of balls
}
return balls; // return all balls to draw later
}
function setPositions(balls, canvasW, canvasH) {
var savedPosX = [], // to save x pos of balls
savedPosY = []; // to save y pos of balls
for(var start = 0, max = balls.length; start < max; start++) {
var current = balls[start], // current ball
randomX = random(canvasW, current.radius), // get random value for x pos
randomY = random(canvasH, current.radius); // get random value for y pos
if(checkSeparation(randomX, current.radius, savedPosX)) {
current.x = randomX; // If it position, along with your radio does not touch another circle, I add the position
} else {
// start--; continue;
console.log("X: The above code causes an infinite loop");
}
if(checkSeparation(randomY, current.radius, savedPosY)) {
current.y = randomY;
} else {
// start--; continue;
console.log("Y: The above code causes an infinite loop");
}
}
}
body,html {
margin: 0; border: 0; padding: 0; overflow: hidden;
}
<canvas id="balls"></canvas>
In your code, you test possible collisions by means of arrays of already used x and y positions, but you never add new positions to these arrays. You also check the x and y coordinates separately, which means you are really testing a collision of a bounding box.
Two circles collide when the distance between their centres is smaller than the sum of their radii, so you could use:
function collides(balls, n, x, y, r) {
for (let i = 0; i < n; i++) {
let ball = balls[i];
let dx = ball.x - x;
let dy = ball.y - y;
let dd = dx*dx + dy*dy;
let rr = r + ball.radius;
if (dd < rr * rr) return true;
}
return false;
}
function setPositions(balls, canvasW, canvasH) {
for (let i = 0, max = balls.length; i < max; i++) {
let ball = balls[i],
r = ball.radius,
maxTries = 20;
ball.x = -canvasW;
ball.y = -canvasH;
for (let tries = 0; tries = maxTries; tries++) {
let x = random(canvasW - 2*r, r),
y = random(canvasH - 2*r, r);
if (!collides(balls, i, x, y, r)) {
ball.x = x;
ball.y = y;
break;
}
}
}
}
This is reasonably fast for 50 balls, but will be slow if you have more balls. In that case, some spatial data structures can speed up the collision search.
You must also guard against the case that no good place can be found. The code above gives up after 20 tries and moves the ball outside the visible canvas. You can improve the chances of placing balls by sorting the balls by radius and plaing the large balls first.
Finally, you add one hex digit too many to your random colour. (That for loop, where everything happens in the loop control is horrible, by the way.)

Categories

Resources