function randomXToY(minVal,maxVal,floatVal)
{
var randVal = minVal+(Math.random()*(maxVal-minVal));
return typeof floatVal=='undefined'?Math.round(randVal):randVal.toFixed(floatVal);
}
Ball = (function() {
// constructor
function Ball(x,y,radius,color){
this.center = {x:x, y:y};
this.radius = radius;
this.color = color;
this.dx = 2;
this.dy = 2;
this.boundaryHeight = $('#ground').height();
this.boundaryWidth = $('#ground').width();
this.dom = $('<p class="circle"></p>').appendTo('#ground');
// the rectange div a circle
this.dom.width(radius*2);
this.dom.height(radius*2);
this.dom.css({'border-radius':radius,background:color});
this.placeAtCenter(x,y);
}
// Place the ball at center x, y
Ball.prototype.placeAtCenter = function(x,y){
this.dom.css({top: Math.round(y- this.radius), left: Math.round(x - this.radius)});
this.center.x = Math.round(x);
this.center.y = Math.round(y);
};
Ball.prototype.setColor = function(color) {
if(color) {
this.dom.css('background',color);
} else {
this.dom.css('background',this.color);
}
};
// move and bounce the ball
Ball.prototype.move = function(){
var diameter = this.radius * 2;
var radius = this.radius;
if (this.center.x - radius < 0 || this.center.x + radius > this.boundaryWidth ) {
this.dx = -this.dx;
}
if (this.center.y - radius < 0 || this.center.y + radius > this.boundaryHeight ) {
this.dy = -this.dy;
}
this.placeAtCenter(this.center.x + this.dx ,this.center.y +this.dy);
};
return Ball;
})();
var number_of_balls = 5;
var balls = [];
$('document').ready(function(){
for (i = 0; i < number_of_balls; i++) {
var boundaryHeight = $('#ground').height();
var boundaryWidth = $('#ground').width();
var y = randomXToY(30,boundaryHeight - 50);
var x = randomXToY(30,boundaryWidth - 50);
var radius = randomXToY(15,30);
balls.push(new Ball(x,y,radius, '#'+Math.floor(Math.random()*16777215).toString(16)));
}
loop();
});
loop = function(){
for (var i = 0; i < balls.length; i++){
balls[i].move();
}
setTimeout(loop, 8);
};
I have never used in oops concepts in javascript. How do I change the ball color when the balls touches each other?
This is the link : http://jsbin.com/imofat/1/edit
You currently don't have any interaction with the balls. What you can do is checking whether two balls are "inside" each other, and change colors in that case: http://jsbin.com/imofat/1491/.
// calculates distance between two balls
var d = function(a, b) {
var dx = b.center.x - a.center.x;
var dy = b.center.y - a.center.y;
return Math.sqrt(dx*dx + dy*dy);
};
and:
// for each ball
for(var i = 0; i < balls.length; i++) {
// check against rest of balls
for(var j = i + 1; j < balls.length; j++) {
var a = balls[i];
var b = balls[j];
// distance is smaller than their radii, so they are inside each other
if(d(a, b) < a.radius + b.radius) {
// set to some other color using your random color code
a.setColor('#'+Math.floor(Math.random()*16777215).toString(16));
b.setColor('#'+Math.floor(Math.random()*16777215).toString(16));
}
}
}
Still, there are things for improvement:
Balls are changing colors as long as they are inside each other, not just once.
If you want them to "touch", you might want to implement some kind of bouncing effect to make it more realistic.
Related
So far, I've done research into the design by Armin Hoffman (rubber band design). The objective is to create a grid of circles which can be selected to change from white to black and connect these circles in a natural fluid shape that avoids white circles but never traps them. I've been using Codecademy to learn the basics while copying and studying examples on the p5.js website and additionally following The Coding Train on Youtube (specifically: 7.4 mouse interaction and 7.6 clicking on objects).
Check #MauriceMeilleur on Twitter between Feb 2021 and Aug 2021
Also: https://discourse.processing.org/t/shape-generator-help-armin-hofmanns-rubber-band-shape-generator/33190
Every time I try to make a matrix or array of x and y values stored and to be used in the for loop within the draw/setup functions, it becomes invalid and it doesn't show the shapes I would like.
I know I'm missing info.
This is my latest trial:
class boundingBox {
createBox() {
stroke(0);
strokeWeight(3);
fill(255, 255, 255, 255);
rect(100, 100, 700, 700);
}
}
function Bubble(x, y, x1, y1, x2, y2, x3, y3) {
// creating a distinction from x/y private and x/y public
this.x = x;
this.y = y;
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
this.x3 = x3;
this.y3 = y3;
//this.x = x = [225,350,475,600];
//this.y = y = [225,350,475,600];
// creating color public for later use
this.col = color(255);
// this expression works to make a public function within a private function
this.display = function() {
stroke(0);
fill(this.col);
// creating the ability to display the circles within the parameters set
//ellipse(this.x, this.y, 48,48);
// in theory this should loop through the x array position values against all the y values in it's array creating multiple circles
for (i = 0; i < 4; i++) {
ellipse(this.x /*[i]*/ , this.y, 100, 100);
ellipse(this.x1, this.y1, 100, 100);
ellipse(this.x2, this.y2, 100, 100);
ellipse(this.x3, this.y3, 100, 100);
}
}
//this.move = function() {
// //making the circles change position and act like they are moving within air
// this.x = this.x + random(-0.5, 0.5);
// this.y = this.y + random(-0.5, 0.5);
// this.x1 = this.x1 + random(-0.5, 0.5);
// this.y1 = this.y1 + random(-0.5, 0.5);
// //this.x = this.x + random(1, 0.5);
// //this.y = this.y + random(1, 0.5);
//}
// again expression to make function but this time implementing a clicking function
this.clicked = function() {
// calculating the distance from the circle center to help fill it with color and nothing else
var d = dist(mouseX, mouseY, this.x, this.y)
// diameter of the circle
if (d < 50) {
this.col = color(0, 0, 0)
}
}
//joinBubbles(bubbles); {
// bubbles.forEach(element => {
// let dis = dist(this.x, this.y, element.x, element.y);
// stroke(200, 200, 200, 127);
// line(this.x, this.y, element.x, element.y);
// });
//}
//}
//class lines {
// constructor(){
// this.x4 = x4;
// this.y4 = y4;
// this.x5 = x5;
// this.y5 = y5;
// }
// this.displayLine = function(){
// stroke(0)
// }
}
let bubbles = [];
let boxii = [];
function setup() {
createCanvas(1536, 1250);
for (let i = 0; i < 1; i++) {
boxii.push(new boundingBox());
}
for (let i = 200; i < 860; i += 165) {
//var x = random (width);
var x = 200;
var y = i;
bubbles.push(new Bubble(x, y /*x1,y1*/ ));
}
for (let i = 200; i < 860; i += 165) {
//var x = random (width);
var x1 = 375;
var y1 = i;
bubbles.push(new Bubble(x1, y1 /*x1,y1*/ ));
}
for (let i = 200; i < 860; i += 165) {
//var x = random (width);
var x2 = 550;
var y2 = i;
bubbles.push(new Bubble(x2, y2 /*x1,y1*/ ));
}
for (let i = 200; i < 860; i += 165) {
//var x = random (width);
var x3 = 700;
var y3 = i;
bubbles.push(new Bubble(x3, y3 /*x1,y1*/ ));
}
//for (let i = 0; i < 1; i++) {
//var x1 = 400;
//var y1 = 200;
//bubbles.push(new Bubble(x1,y1));
//}
//for (let i = 200; i < 1000; i+=200){
//var x2 = 200;
//var y2 = i;
//bubbles.push(new Bubble(x2,y2));
//}
}
function mousePressed() {
// checking where the mouse presses so that clicking outside the circle does nothing
for (let i = 0; i < bubbles.length; i++) {
//for (let i = 0; i < 4; i++) {
// using this function on all the circles and again using .this to bring properties from /bubble
bubbles[i].clicked();
}
}
function draw() {
background(140);
for (let i = 0; i < 1; i++) {
boxii[i].createBox();
}
for (let i = 0; i < bubbles.length; i++) {
//bubbles[i].move();
bubbles[i].display();
}
if (mousePressed) {
for (let i = 0; i < bubbles.length; i++) {
bubbles[i].display(fill(0))
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.1/p5.js"></script>
//Editable Vars
let cols = 35;
let rows = 35;
let fps = 5;
//Declarations
let canvas;
let ctx;
let background;
let grid = new Array(cols);
let w;
let h;
let pathfinder;
let target;
let timer;
let renderQueue = [];
let canPathfind = true;
//Space Class
class Space{
constructor(x,y,c='lightgrey'){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.c = c;
}
draw(){
ctx.fillStyle = this.c;
ctx.strokeStyle = 'black';
ctx.fillRect(this.x * w, this.y * h, this.w, this.h);
ctx.rect(this.x * w, this.y * h, this.w, this.h);
ctx.stroke();
}
move(x,y){
if(x < 0 || x > cols-1 || y < 0|| y > rows-1){
return;
}
grid[this.x][this.y] = new Space(this.x,this.y);
renderQueue.push(grid[this.x][this.y]);
this.x = x;
this.y = y;
grid[this.x][this.y] = this;
renderQueue.push(grid[this.x][this.y]);
}
}
//Game-Run code
function gameStart(){
canvas = document.getElementById('gameCanvas');
ctx = canvas.getContext('2d');
w = canvas.width / cols;
h = canvas.height / rows;
createGrid();
pathfinder = new Space(randomInt(cols-1),randomInt(rows-1),'green');
grid[pathfinder.x][pathfinder.y] = pathfinder;
target = new Space(randomInt(cols-1),randomInt(rows-1),'red');
grid[target.x][target.y] = target;
drawGrid();
timer = setInterval(updateScreen, 1000/fps);
}
function restartGame(){
clearInterval(timer);
gameStart();
}
//Starts loading process on windows load
window.onload = gameStart();
//Checks all 8 possible move directions and calls pathfinder.move for best move
function pathfind(pathfinder, target){
if(!canPathfind) return;
let p = {x: pathfinder.x, y: pathfinder.y};
let t = {x: target.x, y: target.y};
let move = {x : 0, y : 0};
// 0,0 9,9
//if(p.x == t.x && p.y == t.y){
//restartGame();
//}
if(t.x - p.x >= 1){
move.x = 1;
}else if(t.x - p.x <= -1){
move.x = -1;
}else{
move.x = 0;
}
if(t.y - p.y >= 1){
move.y = 1;
}else if(t.y - p.y <= -1){
move.y = -1;
}else{
move.y = 0;
}
pathfinder.move(pathfinder.x + move.x, pathfinder.y + move.y);
}
function updateScreen(){
pathfind(pathfinder,target);
drawUpdatedSpaces();
}
function drawUpdatedSpaces(){
for(let i = 0; i < renderQueue.length; i++){
renderQueue[i].draw();
}
renderQueue = [];
}
function drawGrid(){
for(let i = 0; i < grid.length; i++){
for(let j = 0; j < grid[i].length; j++){
grid[i][j].draw();
}
}
}
//Creates grid and instantiates Space in every cell
function createGrid(){
for(let i = 0; i < grid.length; i++){
grid[i] = new Array(rows);
}
for(let i = 0; i < grid.length; i++){
for(let j = 0; j < grid[i].length; j++){
grid[i][j] = new Space(i,j);
}
}
}
// Returns distance to target from specified coords
function distanceFromTarget(x, y) {
return (Math.sqrt(Math.pow(Math.abs(x - target.x), 2) + (Math.pow(Math.abs(y - target.y), 2))));
}
// Returns random Integer between 0 and Max
function randomInt(max) {
return Math.floor(Math.random() * max);
}
It runs as expected which is great, but performance is super slow. That may be because I'm using jsfiddle to work on this while away from my personal PC setup, but is there a way to make this perform better? As of right now I can't move the grid size to >50 without it running extremely slow. I would love to eventually move to a 4k x 4k grid and create an auto-generating maze for the 'pathfinder' to pathfind through.
Thoughts/things I'm considering for performance:
Using HTML grid and updating via CSS instead of HTML5 Canvas
Only re-drawing cells that have changed (Implemented in the Space.move() function with array renderQueue)
literally re-doing everything in python :D
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>
I've been working on a simulation of a magnetic pendulum (Magnetic Pendulum for reference). Now, I have a question concerning how you calculate the forces/acceleration:
In examples you find online of the magnetic pendulum (such as the one provided above), and other physics force and acceleration calculations, the forces are calculated at a starting position, the position updated according to time-step dt and then new forces at time t+dt calculated to get a new position and so on. Basic numerical integration, makes sense.
However, I noticed, that if I calculate the forces this way, things don't go as one would expect. For example: when the pendulum is attracted by a magnet it just stops at the particluar magnet even tough you would expect it to "overswing" a bit. Therefore, I introduced three variables for each force which is all previous forces added together, and now it somehow seems to work correctly.
I'm wondering if
the way of calculating the forces I used makes sense in a physics simulation and
if not, then what am I doing wrong? Because it seems to work for other people.
Here's my code:
p5.disableFriendlyErrors = true;
let magnete = [];
let particles = [];
var maganzahl = 3; //number of magnets
var magradius = 100; //radius of magnets from mid-point
var G = 0.002; //coefficient of force towards middle (gravity)
var R = 0.2; //friction coefficient
var h = 50; //height of bob over magnets
var M = 150000; //strength of magnets
var m = 1; //mass (might not work)
var dt = 0.25; //timestep
var d = 2; //pixel density
var counter2 = 0;
var Reset;
var end = false;
//--------------------------------------------
function setup() {
createCanvas(600, 600);
pixelDensity(d);
background(60);
Reset = createButton('Reset');
Reset.position(width + 19, 49);
Reset.mousePressed(resetcanvas);
//construction of magnets
for (var i = 0; i < maganzahl; i++) {
magnete.push(new Magnet((width / 2) + magradius * cos(TWO_PI * (i / maganzahl)), (height / 2) + magradius * sin(TWO_PI * (i / maganzahl)), i));
}
}
//construction of a new "starting position" by mouse-click
function mousePressed() {
if (mouseX < width && mouseY < height) {
particles.push(new Particle(mouseX, mouseY));
}
}
function draw() {
for (var i = 0; i < particles.length; i++) {
if (particles[i].counter < 1000) {
//5 updates per frame(to speed it up)
for (var k = 0; k < 5; k++) {
for (var j = 0; j < magnete.length; j++) {
particles[i].attracted(magnete[j]);
}
particles[i].update();
particles[i].show2();
}
} else if (particles[i].counter < 1001) {
particles[i].counter += 1;
var nearest = [];
for (var j = 0; j < magnete.length; j++) {
nearest.push(particles[i].near(magnete[j]));
}
if (nearest.indexOf(min(nearest)) == 0) {
var c = color("green");
}
if (nearest.indexOf(min(nearest)) == 1) {
var c = color("purple");
}
if (nearest.indexOf(min(nearest)) == 2) {
var c = color("orange");
}
if (nearest.indexOf(min(nearest)) == 3) {
var c = color("blue");
}
if (nearest.indexOf(min(nearest)) == 4) {
var c = color("red");
}
if (nearest.indexOf(min(nearest)) == 5) {
var c = color("yellow");
}
//show particle trace according to nearest magnet
particles[i].show(c);
}
}
//displaying magnets
for (var i = 0; i < magnete.length; i++) {
magnete[i].show();
}
//displaying mid-point
stroke(255);
circle(width / 2, height / 2, 3);
}
function resetcanvas() {
background(60);
}
function Particle(x, y) {
this.orgpos = createVector(x, y);
this.pos = createVector(x, y);
this.prev = createVector(x, y);
this.vel = createVector();
this.acc = createVector();
this.accpre = createVector();
this.accprepre = createVector();
this.velprediction = this.vel;
this.magnetf = createVector();
this.gravity = createVector();
this.friction = createVector();
this.shape = new Array();
this.counter = 0;
//calculating new positions
this.update = function() {
//predictor for velocity -> Beeman's algorithm
this.velprediction.add(this.accpre.mult(3 / 2 * dt).add(this.accprepre.mult(-1 / 2 * dt)));
var momgrav = createVector(width / 2 - this.pos.x, height / 2 - this.pos.y);
var momfric = createVector(this.velprediction.x, this.velprediction.y);
momgrav.mult(G * m); //force due to gravity
momfric.mult(-R); //force due to friction
this.gravity.add(momgrav);
this.friction.add(momfric);
//a = F/m
this.acc = createVector((this.magnetf.x + this.gravity.x + this.friction.x) / m, (this.magnetf.y + this.gravity.y + this.friction.y) / m);
//-=Beeman's Algorithm=-
this.vel.add(this.acc.mult(dt * 1 / 3).add(this.accpre.mult(dt * 5 / 6)).add(this.accprepre.mult(-1 / 6 * dt)));
this.pos.add(this.vel.mult(dt).add(this.accpre.mult(dt * dt * 2 / 3)).add(this.accprepre.mult(-1 / 6 * dt * dt)));
this.accprepre = createVector(this.accpre.x, this.accpre.y);
this.accpre = createVector(this.acc.x, this.acc.y);
this.velprediction = createVector(this.vel.x, this.vel.y);
this.counter += 1;
this.shape.push(new p5.Vector(this.pos.x, this.pos.y));
}
//calculating force due to magnets -> attracted called earlier than update in sketch.js
this.attracted = function(target) {
var magn = createVector(target.pos.x - this.pos.x, target.pos.y - this.pos.y);
var dist = sqrt(sq(h) + sq(magn.x) + sq(magn.y)); //distance bob - magnet
strength = M / (Math.pow(dist, 3));
magn.mult(strength);
this.magnetf.add(magn);
}
//calculating distance to target
this.near = function(target) {
var dist = sqrt(sq(h) + sq(this.pos.x - target.pos.x) + sq(this.pos.y - target.pos.y));
return (dist);
}
//trace
this.show = function(col) {
beginShape();
stroke(col);
for (var i = 0; i < this.shape.length - 1; i++) {
line(this.shape[i].x, this.shape[i].y, this.shape[i + 1].x, this.shape[i + 1].y);
strokeWeight(2);
noFill();
}
endShape();
}
//dots
this.show2 = function() {
strokeWeight(1)
point(this.pos.x, this.pos.y);
}
}
function Magnet(x, y, n) {
this.pos = createVector(x, y);
this.n = n;
this.show = function() {
noStroke();
//color for each magnet
if (n == 0) {
fill("green");
}
if (n == 1) {
fill("purple");
}
if (n == 2) {
fill("orange");
}
if (n == 3) {
fill("blue");
}
if (n == 4) {
fill("red");
}
if (n == 5) {
fill("yellow");
}
strokeWeight(4);
circle(this.pos.x, this.pos.y, 10);
}
}
Any help would be greatly appreciated!
I found the issue! So apparently in p5.js you have to be careful with your vector calculations. If you for example do something like:
[some variable] = [Vector].add([anotherVector].mult(2));
both [Vector] and [anotherVector] change their value. Makes sense now that I think about it...
The fact that it still seemed to work somewhat "realistically" and that I managed to generate some beautiful pictures using it (Such as this one 1 and this one 2) is still quite mysterious to me but I guess sometimes numbers just do their magic ;)
Run it:
Code Preview
If you want to change some variables/edit:
p5.js Web Editor
Ultimately I like to know which object was being clicked in a canvas, and I wrote the script:
dist.push(Math.abs(x-ball1.x-88.5) + Math.abs(y-ball1.y-110));
dist.push(Math.abs(x-ball2.x-88.5) + Math.abs(y-ball2.y-110));
dist.push(Math.abs(x-ball3.x-88.5) + Math.abs(y-ball3.y-110));
function sortNumber(a,b) {
return a - b;
}
dist.sort(sortNumber);
Obviously this only give me the sort of the number but I need it to connect with ball1, ball2, and ball3. I figure I could nest an array for this but I haven't figured out the logic...
Or perhaps my approach was wrong to begin with?
P.S., obviously if I only have three balls I can do this:
var b1d = Math.abs(x-ball1.x-88.5) + Math.abs(y-ball1.y-110);
var b2d = Math.abs(x-ball1.x-88.5) + Math.abs(y-ball1.y-110);
var b3d = Math.abs(x-ball1.x-88.5) + Math.abs(y-ball1.y-110);
dist.push(b1d, b2d, b3d);
function sortNumber(a,b) {
return a - b;
}
dist.sort(sortNumber);
if (dist[0] == b1d) {
alert('b1');
} else if (dist[0] == b2d) {
alert('b2');
} else if (dist[0] == d3d) {
alert('b3');
} else {
alert('####');
}
But if I have hundreds of balls this probably isn't the best way...
How you search depends on what you do with the balls, and how you place them.
Here a simple example that you can click a ball and bring it in the foreground.
We create 200 balls.
To find the correct ball we start searching in an one time sorted array, based on the z-index of the ball (from the balls on the back to the balls on the frond), as you can not click the balls on the back, we search starting from the last element of the array.
In this example, this is a good solution, but in your applications it may not be, it depends on many things, like if the balls overlap, or if the possition is not random.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var getRandomColor = function() {
// Code from : http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
var createRandomBall = function(){
// The ball object
var ball = {};
// Random radius (min 5 max 30)
ball.radius = Math.floor((Math.random() * 25) + 5);
ball.radius2 = Math.pow(ball.radius, 2);
// Random x position
ball.x = Math.floor((Math.random() * (canvas.width - ball.radius*2)) + ball.radius);
// Random y position
ball.y = Math.floor((Math.random() * (canvas.height - ball.radius*2)) + ball.radius);
// Random color
ball.color = getRandomColor();
return ball;
}
// Create many balls
var ballList = [];
var tmp_ball;
for (var i = 0; i < 200; i++) {
// Make a random ball
tmp_ball = createRandomBall();
// Add to the list
ballList.push(tmp_ball);
}
// Render the balls
var renderBalls = function(){
var ball;
// For each ball
for (var i = 0; i < ballList.length; i++) {
ball = ballList[i];
// Stroke ball
context.beginPath();
context.arc(ball.x, ball.y, ball.radius - 1, 0, 2 * Math.PI, false);
context.fillStyle = ball.color;
context.fill();
context.lineWidth = 1;
context.strokeStyle = '#000000';
context.stroke();
}
}
// Render balls
renderBalls();
// Add click event
canvas.addEventListener('click', function(event){
// Get x and y of click
var click = {
x : event.clientX - canvas.offsetLeft,
y : event.clientY - canvas.offsetTop
};
var ball = null;
// Find clicked ball
// we search the array from the back,
// because on the back balls are over the frond balls
for (var i = ballList.length - 1; i >= 0; i--) {
if( Math.pow(click.x - ballList[i].x, 2) + Math.pow(click.y - ballList[i].y, 2) <= ballList[i].radius2 ){
ball = i;
break;
}
}
// If no ball found return
if(ball == null){
console.log("No ball clicked");
return;
}
// else ball found
ball = ballList.splice(ball, 1)[0];
// Else position ball on the frond
ballList.push(ball);
// Re-render
renderBalls();
}, false);
*{
padding: 0px;
margin: 0px;
}
#myCanvas{
position: absolute;
top: 10px;
right: 10px;
bottom: 10px;
left: 10px;
}
<canvas id="myCanvas" width="600" height="200"></canvas>
A general solution is to make a map table, a grid of your canvas, and on each cell add the corresponding balls, so that you can match in which grid box the click was made and check a smaller group of balls.
So for example, lets say that you want when you click, all the balls under the click to change color. Here is an example with mapping the balls on smaller groups, we make a grid of 10 columns and 5 lines. Each ball may be in more than 1 group. We create 400 balls.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var getRandomColor = function() {
// Code from : http://stackoverflow.com/questions/1484506/random-color-generator-in-javascript
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++ ) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
var gridSize = {x:10, y:5};
var gridList = [];
// Rows
for (var i = 0; i < gridSize.y; i++) {
gridList.push([]);
// Columns
for (var j = 0; j < gridSize.x; j++) {
gridList[i].push([]);
}
}
var createRandomBall = function(){
// The ball object
var ball = {};
// Random radius (min 5 max 30)
ball.radius = Math.floor((Math.random() * 25) + 5);
ball.radius2 = Math.pow(ball.radius, 2);
// Random x position
ball.x = Math.floor((Math.random() * (canvas.width - ball.radius*2)) + ball.radius);
// Random y position
ball.y = Math.floor((Math.random() * (canvas.height - ball.radius*2)) + ball.radius);
// Random color
ball.color = getRandomColor();
// Map ball - find cells that the circle overlap
grid = {
x : {
min : Math.floor((ball.x - ball.radius)*gridSize.x/canvas.width),
max : Math.floor((ball.x + ball.radius)*gridSize.x/canvas.width)
},
y : {
min : Math.floor((ball.y - ball.radius)*gridSize.y/canvas.height),
max : Math.floor((ball.y + ball.radius)*gridSize.y/canvas.height)
}
}
for (var y = grid.y.min; y <= grid.y.max; y++) {
for (var x = grid.x.min; x <= grid.x.max; x++) {
gridList[y][x].push(ball);
}
}
return ball;
}
// Create many balls
var ballList = [];
var tmp_ball;
for (var i = 0; i < 400; i++) {
// Make a random ball
tmp_ball = createRandomBall();
// Add to the list
ballList.push(tmp_ball);
}
// Render the balls
var renderBalls = function(){
var ball;
// For each ball
for (var i = 0; i < ballList.length; i++) {
ball = ballList[i];
// Stroke ball
context.beginPath();
context.arc(ball.x, ball.y, ball.radius - 1, 0, 2 * Math.PI, false);
context.fillStyle = ball.color;
context.fill();
context.lineWidth = 1;
context.strokeStyle = '#000000';
context.stroke();
}
for (var i = 0; i < gridSize.x + 1; i++) {
context.beginPath();
context.moveTo((canvas.width/gridSize.x)*i, 0);
context.lineTo((canvas.width/gridSize.x)*i, canvas.height);
context.stroke();
}
for (var i = 0; i < gridSize.y + 1; i++) {
context.beginPath();
context.moveTo(0, (canvas.height/gridSize.y)*i);
context.lineTo(canvas.width, (canvas.height/gridSize.y)*i);
context.stroke();
}
}
// Render balls
renderBalls();
// Add click event
canvas.addEventListener('click', function(event){
// Get x and y of click
var click = {
x : event.clientX - canvas.offsetLeft,
y : event.clientY - canvas.offsetTop
};
var grid = {
x : Math.floor(click.x*gridSize.x/canvas.width),
y : Math.floor(click.y*gridSize.y/canvas.height)
};
var ball = 0;
var smallerList = gridList[grid.y][grid.x];
// Find clicked ball
for (var i = smallerList.length - 1; i >= 0; i--) {
if( Math.pow(click.x - smallerList[i].x, 2) + Math.pow(click.y - smallerList[i].y, 2) <= smallerList[i].radius2 ){
ball++;
smallerList[i].color = getRandomColor();
}
}
console.log("Group["+grid.y+"]["+grid.x+"], " + smallerList.length + " balls in group, clicked " + ball + " balls");
// If no ball found return
if(ball == 0){
return;
}
// Re-render
renderBalls();
}, false);
*{
padding: 0px;
margin: 0px;
}
#myCanvas{
position: absolute;
top: 10px;
right: 10px;
bottom: 10px;
left: 10px;
}
<canvas id="myCanvas" width="600" height="200"></canvas>