Circle - circle collision response (balls stuck overlapping) - javascript

I'm trying to build something like this in JavaScript using p5.js:
I'm able to detect collision between two circles and I'm trying to calculate the new speeds using these (two-dimensional elastic collision) formulas from Wikipedia:
With these angles for theta and phi:
The problem seems to be that when I have calculated the new speeds, the balls are already collided / overlapping and they are getting stuck. What I think I should do is calculate a new circle position based on the distance that the circles are overlapping. Unfortunately I'm not sure how to do this. I also think that the way I'm calculating is overly complex and inefficient.
This is what my collision handling code looks like:
// takes ball object as input, sets speedvector to new x,y speed
collision(other) {
var newSpeed = createVector();
var phi = Math.atan((this.pos.y - other.pos.y) / this.pos.x - other.pos.x);
var theta1 = this.speed.heading();
var theta2 = other.speed.heading();
newSpeed.x = (this.speed.mag() * Math.cos(theta1 - phi) * (this.mass - other.mass) + 2 * other.mass * other.speed.mag() * Math.cos(theta2 - phi)) / (this.mass + other.mass) * Math.cos(phi) - this.speed.mag() * Math.sin(theta1 - phi) * Math.sin(phi);
newSpeed.y = (this.speed.mag() * Math.cos(theta1 - phi) * (this.mass - other.mass) + 2 * other.mass * other.speed.mag() * Math.cos(theta2 - phi)) / (this.mass + other.mass) * Math.sin(phi) + this.speed.mag() * Math.sin(theta1 - phi) * Math.cos(phi);
this.speed.x = newSpeed.x;
this.speed.y = newSpeed.y;
}
The complete code example:
var balls = [];
var numOfBalls = 5;
var maxSpeed = 2;
function setup() {
createCanvas(500, 500);
for (var i = 0; i < numOfBalls; i++) {
var ball = new Ball(30);
balls.push(ball);
}
}
function draw() {
background(0);
for (var i = 0; i < balls.length; i++) {
balls[i].move();
}
for (var i = 0; i < balls.length; i++) {
balls[i].show();
}
}
class Ball {
constructor(radius) {
this.radius = radius;
this.pos = this.pickLocation();
this.speed = createVector(random(-maxSpeed, maxSpeed), random(-maxSpeed, maxSpeed));
this.mass = 1;
}
pickLocation() {
//spawn within canvas
var xOption = random(this.radius, width - this.radius);
var yOption = random(this.radius, height - this.radius);
// check whether spawning on this location doesn't overlap other circles
for(var i = 0; i < balls.length; i++) {
// don't check for current circle
if(balls[i] != this) {
// get distance to other circle
var d = dist(xOption, yOption, balls[i].pos.x, balls[i].pos.y);
// check whether overlapping
if (d <= this.radius + balls[i].radius) {
// generate new location and rerun check
console.log("overlapping another circle, trying new location");
var xOption = random(this.radius, width - this.radius);
var yOption = random(this.radius, height - this.radius);
i = -1;
}
}
}
return(createVector(xOption, yOption));
}
move() {
for (var i = 0; i < balls.length; i++) {
if(balls[i] != this) {
var d = dist(this.pos.x, this.pos.y, balls[i].pos.x, balls[i].pos.y);
if(d < this.radius + balls[i].radius) {
this.collision(balls[i]);
}
}
}
if(this.pos.x - this.radius < 0 || this.pos.x + this.radius > width) {
this.speed.x *= -1;
}
if(this.pos.y - this.radius < 0 || this.pos.y + this.radius > height) {
this.speed.y *= -1;
}
this.pos.x += this.speed.x;
this.pos.y += this.speed.y;
}
// takes ball object as input, sets speedvector to new x,y speed
collision(other) {
var newSpeed = createVector();
var phi = Math.atan((this.pos.y - other.pos.y) / this.pos.x - other.pos.x);
var theta1 = this.speed.heading();
var theta2 = other.speed.heading();
newSpeed.x = (this.speed.mag() * Math.cos(theta1 - phi) * (this.mass - other.mass) + 2 * other.mass * other.speed.mag() * Math.cos(theta2 - phi)) / (this.mass + other.mass) * Math.cos(phi) - this.speed.mag() * Math.sin(theta1 - phi) * Math.sin(phi);
newSpeed.y = (this.speed.mag() * Math.cos(theta1 - phi) * (this.mass - other.mass) + 2 * other.mass * other.speed.mag() * Math.cos(theta2 - phi)) / (this.mass + other.mass) * Math.sin(phi) + this.speed.mag() * Math.sin(theta1 - phi) * Math.cos(phi);
this.speed.x = newSpeed.x;
this.speed.y = newSpeed.y;
}
show() {
fill(200, 100);
noStroke();
ellipse(this.pos.x, this.pos.y, this.radius * 2);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>

I made this code. I completely changed the collision function but it's working great!
also, constrained the position so it won't get stuck on the walls.
I made the exact thing a few months ago so you can look at it
here
I also divided the canvas into grids so it is a lot more efficient than looping through every ball.
mine can do about 4000 balls before it lags
they way the collision function works is like the newton's cradle. the two objects change speeds(ball A which is moving hits stationary ball B and now ball A doesn't move and ball B moves at the same speed ball A was)
var balls = [];
var numOfBalls = 5;
var maxSpeed = 2;
function setup() {
createCanvas(500, 500);
for (var i = 0; i < numOfBalls; i++) {
var ball = new Ball(30);
balls.push(ball);
}
}
function draw() {
background(0);
for (var i = 0; i < balls.length; i++) {
balls[i].move();
}
for (var i = 0; i < balls.length; i++) {
balls[i].show();
}
}
class Ball {
constructor(radius) {
this.radius = radius;
this.pos = this.pickLocation();
this.speed = createVector(random(-maxSpeed, maxSpeed), random(-maxSpeed, maxSpeed));
this.mass = 1;
}
pickLocation() {
//spawn within canvas
var xOption = random(this.radius, width - this.radius);
var yOption = random(this.radius, height - this.radius);
// check whether spawning on this location doesn't overlap other circles
for(var i = 0; i < balls.length; i++) {
// don't check for current circle
if(balls[i] != this) {
// get distance to other circle
var d = dist(xOption, yOption, balls[i].pos.x, balls[i].pos.y);
// check whether overlapping
if (d <= this.radius + balls[i].radius) {
// generate new location and rerun check
console.log("overlapping another circle, trying new location");
xOption = random(this.radius, width - this.radius);
yOption = random(this.radius, height - this.radius);
i = -1;
}
}
}
return(createVector(xOption, yOption));
}
move() {
for (var i = 0; i < balls.length; i++) {
if(balls[i] != this) {
var d = dist(this.pos.x, this.pos.y, balls[i].pos.x, balls[i].pos.y);
if(d < this.radius + balls[i].radius) {
this.collision(balls[i]);
}
}
}
if(this.pos.x - this.radius < 0 || this.pos.x + this.radius > width) {
this.speed.x *= -1;
}
if(this.pos.y - this.radius < 0 || this.pos.y + this.radius > height) {
this.speed.y *= -1;
}
this.pos.x += this.speed.x;
this.pos.y += this.speed.y;
this.pos.x = constrain(this.pos.x,0,500)
this.pos.y = constrain(this.pos.y,0,500)
}
// takes ball object as input, sets speedvector to new x,y speed
collision(other) {
this.v1 = 0;
this.v2 = 0;
this.direction = p5.Vector.sub(other.pos, this.pos)
this.dist = this.direction.mag()
this.direction.normalize();
//this is 60 because that is the radius you give them times two
this.correction = 60-this.dist;
this.pos.sub(p5.Vector.mult(this.direction,this.correction/2))
other.pos.add(p5.Vector.mult(this.direction,this.correction/2))
this.v1 = this.direction.dot(this.speed)
this.v2 = this.direction.dot(other.speed)
this.direction.mult(this.v1-this.v2)
this.speed.sub(p5.Vector.mult(this.direction,1));
other.speed.add(p5.Vector.mult(this.direction,1))
}
show() {
fill(200, 100);
noStroke();
ellipse(this.pos.x, this.pos.y, this.radius * 2);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.7.3/p5.min.js"></script>

Related

I have an issue in my canvas. I want to change the color of particles. currently they are in red color. How can I change...?

I have a issue in my canvas. i want to change the color of particles. currently they are in black color. How i can change..? here is my code please let me know if u have any solution. I have a issue in my canvas. i want to change the color of particles. currently they are in red color. How i can change..?
Here my source code:
`
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<HTML>
<HEAD>
<TITLE> New Document </TITLE>
<META NAME="Generator" CONTENT="EditPlus">
<META NAME="Author" CONTENT="">
<META NAME="Keywords" CONTENT="">
<META NAME="Description" CONTENT="">
<style>
html, body {
height: 100%;
padding: 0;
margin: 0;
background: rgba(0, 0, 0, 0.879);
}
canvas {
position: absolute;
width: 100%;
height: 100%;
}
</style>
</HEAD>
<BODY>
<canvas id="pinkboard"></canvas>
<script>
/*
* Settings
*/
var settings = {
particles: {
length: 300, // maximum amount of particles
duration: 2, // particle duration in sec
velocity: 100, // particle velocity in pixels/sec
effect: -0.75, // play with this for a nice effect
size: 30, // particle size in pixels
},
};
/*
* RequestAnimationFrame polyfill by Erik Möller
*/
(function(){var b=0;var c=["ms","moz","webkit","o"];for(var a=0;a<c.length&&!window.requestAnimationFrame;++a){window.requestAnimationFrame=window[c[a]+"RequestAnimationFrame"];window.cancelAnimationFrame=window[c[a]+"CancelAnimationFrame"]||window[c[a]+"CancelRequestAnimationFrame"]}if(!window.requestAnimationFrame){window.requestAnimationFrame=function(h,e){var d=new Date().getTime();var f=Math.max(0,16-(d-b));var g=window.setTimeout(function(){h(d+f)},f);b=d+f;return g}}if(!window.cancelAnimationFrame){window.cancelAnimationFrame=function(d){clearTimeout(d)}}}());
/*
* Point class
*/
var Point = (function() {
function Point(x, y) {
this.x = (typeof x !== 'undefined') ? x : 0;
this.y = (typeof y !== 'undefined') ? y : 0;
}
Point.prototype.clone = function() {
return new Point(this.x, this.y);
};
Point.prototype.length = function(length) {
if (typeof length == 'undefined')
return Math.sqrt(this.x * this.x + this.y * this.y);
this.normalize();
this.x *= length;
this.y *= length;
return this;
};
Point.prototype.normalize = function() {
var length = this.length();
this.x /= length;
this.y /= length;
return this;
};
return Point;
})();
/*
* Particle class
*/
var Particle = (function() {
function Particle() {
this.position = new Point();
this.velocity = new Point();
this.acceleration = new Point();
this.age = 0;
}
Particle.prototype.initialize = function(x, y, dx, dy) {
this.position.x = x;
this.position.y = y;
this.velocity.x = dx;
this.velocity.y = dy;
this.acceleration.x = dx * settings.particles.effect;
this.acceleration.y = dy * settings.particles.effect;
this.age = 0;
};
Particle.prototype.update = function(deltaTime) {
this.position.x += this.velocity.x * deltaTime;
this.position.y += this.velocity.y * deltaTime;
this.velocity.x += this.acceleration.x * deltaTime;
this.velocity.y += this.acceleration.y * deltaTime;
this.age += deltaTime;
};
Particle.prototype.draw = function(context, image) {
function ease(t) {
return (--t) * t * t + 1;
}
var size = image.width * ease(this.age / settings.particles.duration);
context.globalAlpha = 1 - this.age / settings.particles.duration;
context.drawImage(image, this.position.x - size / 2, this.position.y - size / 2, size, size);
};
return Particle;
})();
/*
* ParticlePool class
*/
var ParticlePool = (function() {
var particles,
firstActive = 0,
firstFree = 0,
duration = settings.particles.duration;
function ParticlePool(length) {
// create and populate particle pool
particles = new Array(length);
for (var i = 0; i < particles.length; i++)
particles[i] = new Particle();
}
ParticlePool.prototype.add = function(x, y, dx, dy) {
particles[firstFree].initialize(x, y, dx, dy);
// handle circular queue
firstFree++;
if (firstFree == particles.length) firstFree = 0;
if (firstActive == firstFree ) firstActive++;
if (firstActive == particles.length) firstActive = 0;
};
ParticlePool.prototype.update = function(deltaTime) {
var i;
// update active particles
if (firstActive < firstFree) {
for (i = firstActive; i < firstFree; i++)
particles[i].update(deltaTime);
}
if (firstFree < firstActive) {
for (i = firstActive; i < particles.length; i++)
particles[i].update(deltaTime);
for (i = 0; i < firstFree; i++)
particles[i].update(deltaTime);
}
// remove inactive particles
while (particles[firstActive].age >= duration && firstActive != firstFree) {
firstActive++;
if (firstActive == particles.length) firstActive = 0;
}
};
ParticlePool.prototype.draw = function(context, image) {
// draw active particles
if (firstActive < firstFree) {
for (i = firstActive; i < firstFree; i++)
particles[i].draw(context, image);
}
if (firstFree < firstActive) {
for (i = firstActive; i < particles.length; i++)
particles[i].draw(context, image);
for (i = 0; i < firstFree; i++)
particles[i].draw(context, image);
}
};
return ParticlePool;
})();
/*
* Putting it all together
*/
(function(canvas) {
var context = canvas.getContext('2d'),
particles = new ParticlePool(settings.particles.length),
particleRate = settings.particles.length / settings.particles.duration, // particles/sec
time;
// get point on heart with -PI <= t <= PI
function pointOnHeart(t) {
return new Point(
160 * Math.pow(Math.sin(t), 3),
130 * Math.cos(t) - 50 * Math.cos(2 * t) - 20 * Math.cos(3 * t) - 10 * Math.cos(4 * t) + 25
);
}
// creating the particle image using a dummy canvas
var image = (function() {
var canvas = document.createElement('canvas'),
context = canvas.getContext('2d');
canvas.width = settings.particles.size;
canvas.height = settings.particles.size;
// helper function to create the path
function to(t) {
var point = pointOnHeart(t);
point.x = settings.particles.size / 2 + point.x * settings.particles.size / 350;
point.y = settings.particles.size / 2 - point.y * settings.particles.size / 350;
return point;
}
// create the path
context.beginPath();
var t = -Math.PI;
var point = to(t);
context.moveTo(point.x, point.y);
while (t < Math.PI) {
t += 0.01; // baby steps!
point = to(t);
context.lineTo(point.x, point.y);
}
context.closePath();
// create the fill
context.fillStyle = '#ea80b0';
context.fill();
// create the image
var image = new Image();
image.src = canvas.toDataURL();
return image;
})();
// render that thing!
function render() {
// next animation frame
requestAnimationFrame(render);
// update time
var newTime = new Date().getTime() / 1000,
deltaTime = newTime - (time || newTime);
time = newTime;
// clear canvas
context.clearRect(0, 0, canvas.width, canvas.height);
// create new particles
var amount = particleRate * deltaTime;
for (var i = 0; i < amount; i++) {
var pos = pointOnHeart(Math.PI - 2 * Math.PI * Math.random());
var dir = pos.clone().length(settings.particles.velocity);
particles.add(canvas.width / 2 + pos.x, canvas.height / 2 - pos.y, dir.x, -dir.y);
}
// update and draw particles
particles.update(deltaTime);
particles.draw(context, image);
}
// handle (re-)sizing of the canvas
function onResize() {
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
}
window.onresize = onResize;
// delay rendering bootstrap
setTimeout(function() {
onResize();
render();
}, 10);
})(document.getElementById('pinkboard'));
</script>
</BODY>
</HTML>
`
I ended up with a red heart effect, how do I change it to blue

How to color line in canvas?

EDIT: I'm trying to get a single line colored, not the whole grid
So, what I'm trying to do is that when the canvas gets clicked, the lines selected move and get colored.
I already have the lines moving but I cannot get them colored. I know I have to use the strokeStyle property, but how do I get the specific line colored?
I tried putting the strokeStyle under the draw_each method, but that doesn't work, and I don't know why. Here's my code:
//draw each line in array
function draw_each(p1, p2, p3, p4) {
$.moveTo(p1.x, p1.y);
$.lineTo(p2.x, p2.y);
$.moveTo(p1.x, p1.y);
$.lineTo(p4.x, p4.y);
if (p1.ind_x == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p4.x, p4.y);
}
if (p1.ind_y == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p2.x, p2.y);
}
}
https://codepen.io/diazabdulm/pen/qJyrZP?editors=0010
It seems to me that what you want is actually to generate a color per segment based on the force that's been applied to it, so that the wave effect keeps its effect.
What you can do, is to save the median of each node's velocity in both axis as a mean to represent the force they're currently experiencing, and in your drawing part to get the median of all nodes that will compose each segment of your grid and compose a color from it.
But this comes with a drawback: your code was well written enough to compose the whole grid a single sub-path, thus limiting the number of drawings necessary. But now, we have to make each segment its own sub-path (since they'll got their own color), so we'll loose a lot in terms of performances...
var gnum = 90; //num grids / frame
var _x = 2265; //x width (canvas width)
var _y = 1465; //y height (canvas height)
var w = _x / gnum; //grid sq width
var h = _y / gnum; //grid sq height
var $; //context
var parts; //particles
var frm = 0; //value from
var P1 = 0.0005; //point one
var P2 = 0.01; //point two
var n = 0.98; //n value for later
var n_vel = 0.03; //velocity
var ŭ = 0; //color update
var msX = 0; //mouse x
var msY = 0; //mouse y
var msdn = false; //mouse down flag
var Part = function() {
this.x = 0; //x pos
this.y = 0; //y pos
this.vx = 0; //velocity x
this.vy = 0; //velocity y
this.ind_x = 0; //index x
this.ind_y = 0; //index y
};
Part.prototype.frame = function() {
if (this.ind_x == 0 || this.ind_x == gnum - 1 || this.ind_y == 0 || this.ind_y == gnum - 1) {
return;
}
var ax = 0; //angle x
var ay = 0; //angle y
//off_dx, off_dy = offset distance x, y
var off_dx = this.ind_x * w - this.x;
var off_dy = this.ind_y * h - this.y;
ax = P1 * off_dx;
ay = P1 * off_dy;
ax -= P2 * (this.x - parts[this.ind_x - 1][this.ind_y].x);
ay -= P2 * (this.y - parts[this.ind_x - 1][this.ind_y].y);
ax -= P2 * (this.x - parts[this.ind_x + 1][this.ind_y].x);
ay -= P2 * (this.y - parts[this.ind_x + 1][this.ind_y].y);
ax -= P2 * (this.x - parts[this.ind_x][this.ind_y - 1].x);
ay -= P2 * (this.y - parts[this.ind_x][this.ind_y - 1].y);
ax -= P2 * (this.x - parts[this.ind_x][this.ind_y + 1].x);
ay -= P2 * (this.y - parts[this.ind_x][this.ind_y + 1].y);
this.vx += (ax - this.vx * n_vel);
this.vy += (ay - this.vy * n_vel);
//EDIT\\
// store the current velocity (here base on 100 since it will be used with hsl())
this.color = (Math.abs(this.vx)+Math.abs(this.vy)) * 50;
this.x += this.vx * n;
this.y += this.vy * n;
if (msdn) {
var dx = this.x - msX;
var dy = this.y - msY;
var ɋ = Math.sqrt(dx * dx + dy * dy);
if (ɋ > 50) {
ɋ = ɋ < 10 ? 10 : ɋ;
this.x -= dx / ɋ * 5;
this.y -= dy / ɋ * 5;
}
}
};
function go() {
parts = []; //particle array
for (var i = 0; i < gnum; i++) {
parts.push([]);
for (var j = 0; j < gnum; j++) {
var p = new Part();
p.ind_x = i;
p.ind_y = j;
p.x = i * w;
p.y = j * h;
parts[i][j] = p;
}
}
}
//move particles function
function mv_part() {
for (var i = 0; i < gnum; i++) {
for (var j = 0; j < gnum; j++) {
var p = parts[i][j];
p.frame();
}
}
}
//draw grid function
function draw() {
//EDIT
// we unfortunately have to break the drawing part
// since each segment has its own color, we can't have a single sub-path anymore...
ŭ -= .5;
for (var i = 0; i < gnum - 1; i += 1) {
for (var j = 0; j < gnum - 1; j += 1) {
var p1 = parts[i][j];
var p2 = parts[i][j + 1];
var p3 = parts[i + 1][j + 1];
var p4 = parts[i + 1][j];
draw_each(p1, p2, p3, p4);
}
}
}
//draw each in array
function draw_each(p1, p2, p3, p4) {
// for each segment we set the color
$.strokeStyle = `hsl(0deg, ${(p1.color+p2.color+p3.color+p4.color) / 4}%, 50%)`;
// begin a new sub-path
$.beginPath();
$.moveTo(p1.x, p1.y);
$.lineTo(p2.x, p2.y);
$.moveTo(p1.x, p1.y);
$.lineTo(p4.x, p4.y);
if (p1.ind_x == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p4.x, p4.y);
}
if (p1.ind_y == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p2.x, p2.y);
}
// and stroke it
$.stroke();
}
//call functions to run
function calls() {
$.fillStyle = "hsla(0, 0%, 7%, 1)";
$.fillRect(0, 0, _x, _y);
mv_part();
draw();
frm++;
}
var c = document.getElementById('canv');
var $ = c.getContext('2d');
$.fillStyle = "hsla(0, 0%, 7%, 1)";
$.fillRect(0, 0, _x, _y);
function resize() {
if (c.width < window.innerWidth) {
c.width = window.innerWidth;
}
if (c.height < window.innerHeight) {
c.height = window.innerHeight;
}
}
requestAnimationFrame(go);
document.addEventListener('click', MSMV, false);
document.addEventListener('click', MSDN, false);
function MSDN(e) {
msdn = true;
window.setTimeout(function() {
msdn = false;
}, 100);
}
function MSUP(e) {
msdn = false;
}
function MSMV(e) {
var rect = e.target.getBoundingClientRect();
msX = e.clientX - rect.left;
msY = e.clientY - rect.top;
}
window.onload = function() {
run();
function run() {
requestAnimationFrame(calls);
requestAnimationFrame(run);
}
resize();
};
onresize = resize;
body {
width: 100%;
overflow: hidden;
cursor:move;
}
<canvas id="canv" width="150" height="150"></canvas>
Now, a more performant way, but less good looking, would be to draw a radial-gradient where the click happened, with a bit of compositing, you'd be able to get something cheap that might work, but it would be quite a lot of coding actually to get multiple such gradients at the same time...
That works for me:
//draw each line in array
function draw_each(p1, p2, p3, p4) {
$.moveTo(p1.x, p1.y);
$.lineTo(p2.x, p2.y);
$.moveTo(p1.x, p1.y);
$.lineTo(p4.x, p4.y);
if (p1.ind_x == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p4.x, p4.y);
}
if (p1.ind_y == gnum - 2) {
$.moveTo(p3.x, p3.y);
$.lineTo(p2.x, p2.y);
}
$.strokeStyle = '#458920';
}

Canvas animation (network nodes and edges): how to improve its performances, particularly on FireFox?

Having to give the idea of an ever changing network of nodes (each with different impact and possibly more than one color) connecting each other to create something.
I wanted to give it depth perception, so I ended up using two canvases around the title: one in the foreground, even over the words, and the other in background, with slightly larger and blurred elements.
Demo here, full JavaScript code at the moment:
// min and max radius, radius threshold and percentage of filled circles
var radMin = 5,
radMax = 125,
filledCircle = 60, //percentage of filled circles
concentricCircle = 30, //percentage of concentric circles
radThreshold = 25; //IFF special, over this radius concentric, otherwise filled
//min and max speed to move
var speedMin = 0.3,
speedMax = 2.5;
//max reachable opacity for every circle and blur effect
var maxOpacity = 0.6;
//default palette choice
var colors = ['52,168,83', '117,95,147', '199,108,23', '194,62,55', '0,172,212', '120,120,120'],
bgColors = ['52,168,83', '117,95,147', '199,108,23', '194,62,55', '0,172,212', '120,120,120'],
circleBorder = 10,
backgroundLine = bgColors[0];
var backgroundMlt = 0.85;
//min distance for links
var linkDist = Math.min(canvas.width, canvas.height) / 2.4,
lineBorder = 2.5;
//most importantly: number of overall circles and arrays containing them
var maxCircles = 12,
points = [],
pointsBack = [];
//populating the screen
for (var i = 0; i < maxCircles * 2; i++) points.push(new Circle());
for (var i = 0; i < maxCircles; i++) pointsBack.push(new Circle(true));
//experimental vars
var circleExp = 1,
circleExpMax = 1.003,
circleExpMin = 0.997,
circleExpSp = 0.00004,
circlePulse = false;
//circle class
function Circle(background) {
//if background, it has different rules
this.background = (background || false);
this.x = randRange(-canvas.width / 2, canvas.width / 2);
this.y = randRange(-canvas.height / 2, canvas.height / 2);
this.radius = background ? hyperRange(radMin, radMax) * backgroundMlt : hyperRange(radMin, radMax);
this.filled = this.radius < radThreshold ? (randint(0, 100) > filledCircle ? false : 'full') : (randint(0, 100) > concentricCircle ? false : 'concentric');
this.color = background ? bgColors[randint(0, bgColors.length - 1)] : colors[randint(0, colors.length - 1)];
this.borderColor = background ? bgColors[randint(0, bgColors.length - 1)] : colors[randint(0, colors.length - 1)];
this.opacity = 0.05;
this.speed = (background ? randRange(speedMin, speedMax) / backgroundMlt : randRange(speedMin, speedMax)); // * (radMin / this.radius);
this.speedAngle = Math.random() * 2 * Math.PI;
this.speedx = Math.cos(this.speedAngle) * this.speed;
this.speedy = Math.sin(this.speedAngle) * this.speed;
var spacex = Math.abs((this.x - (this.speedx < 0 ? -1 : 1) * (canvas.width / 2 + this.radius)) / this.speedx),
spacey = Math.abs((this.y - (this.speedy < 0 ? -1 : 1) * (canvas.height / 2 + this.radius)) / this.speedy);
this.ttl = Math.min(spacex, spacey);
};
Circle.prototype.init = function() {
Circle.call(this, this.background);
}
//support functions
//generate random int a<=x<=b
function randint(a, b) {
return Math.floor(Math.random() * (b - a + 1) + a);
}
//generate random float
function randRange(a, b) {
return Math.random() * (b - a) + a;
}
//generate random float more likely to be close to a
function hyperRange(a, b) {
return Math.random() * Math.random() * Math.random() * (b - a) + a;
}
//rendering function
function drawCircle(ctx, circle) {
//circle.radius *= circleExp;
var radius = circle.background ? circle.radius *= circleExp : circle.radius /= circleExp;
ctx.beginPath();
ctx.arc(circle.x, circle.y, radius * circleExp, 0, 2 * Math.PI, false);
ctx.lineWidth = Math.max(1, circleBorder * (radMin - circle.radius) / (radMin - radMax));
ctx.strokeStyle = ['rgba(', circle.borderColor, ',', circle.opacity, ')'].join('');
if (circle.filled == 'full') {
ctx.fillStyle = ['rgba(', circle.borderColor, ',', circle.background ? circle.opacity * 0.8 : circle.opacity, ')'].join('');
ctx.fill();
ctx.lineWidth=0;
ctx.strokeStyle = ['rgba(', circle.borderColor, ',', 0, ')'].join('');
}
ctx.stroke();
if (circle.filled == 'concentric') {
ctx.beginPath();
ctx.arc(circle.x, circle.y, radius / 2, 0, 2 * Math.PI, false);
ctx.lineWidth = Math.max(1, circleBorder * (radMin - circle.radius) / (radMin - radMax));
ctx.strokeStyle = ['rgba(', circle.color, ',', circle.opacity, ')'].join('');
ctx.stroke();
}
circle.x += circle.speedx;
circle.y += circle.speedy;
if (circle.opacity < (circle.background ? maxOpacity : 1)) circle.opacity += 0.01;
circle.ttl--;
}
//initializing function
function init() {
window.requestAnimationFrame(draw);
}
//rendering function
function draw() {
if (circlePulse) {
if (circleExp < circleExpMin || circleExp > circleExpMax) circleExpSp *= -1;
circleExp += circleExpSp;
}
var ctxfr = document.getElementById('canvas').getContext('2d');
var ctxbg = document.getElementById('canvasbg').getContext('2d');
ctxfr.globalCompositeOperation = 'destination-over';
ctxfr.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
ctxbg.globalCompositeOperation = 'destination-over';
ctxbg.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
ctxfr.save();
ctxfr.translate(canvas.width / 2, canvas.height / 2);
ctxbg.save();
ctxbg.translate(canvas.width / 2, canvas.height / 2);
//function to render each single circle, its connections and to manage its out of boundaries replacement
function renderPoints(ctx, arr) {
for (var i = 0; i < arr.length; i++) {
var circle = arr[i];
//checking if out of boundaries
if (circle.ttl<0) {}
var xEscape = canvas.width / 2 + circle.radius,
yEscape = canvas.height / 2 + circle.radius;
if (circle.ttl < -20) arr[i].init(arr[i].background);
//if (Math.abs(circle.y) > yEscape || Math.abs(circle.x) > xEscape) arr[i].init(arr[i].background);
drawCircle(ctx, circle);
}
for (var i = 0; i < arr.length - 1; i++) {
for (var j = i + 1; j < arr.length; j++) {
var deltax = arr[i].x - arr[j].x;
var deltay = arr[i].y - arr[j].y;
var dist = Math.pow(Math.pow(deltax, 2) + Math.pow(deltay, 2), 0.5);
//if the circles are overlapping, no laser connecting them
if (dist <= arr[i].radius + arr[j].radius) continue;
//otherwise we connect them only if the dist is < linkDist
if (dist < linkDist) {
var xi = (arr[i].x < arr[j].x ? 1 : -1) * Math.abs(arr[i].radius * deltax / dist);
var yi = (arr[i].y < arr[j].y ? 1 : -1) * Math.abs(arr[i].radius * deltay / dist);
var xj = (arr[i].x < arr[j].x ? -1 : 1) * Math.abs(arr[j].radius * deltax / dist);
var yj = (arr[i].y < arr[j].y ? -1 : 1) * Math.abs(arr[j].radius * deltay / dist);
ctx.beginPath();
ctx.moveTo(arr[i].x + xi, arr[i].y + yi);
ctx.lineTo(arr[j].x + xj, arr[j].y + yj);
var samecolor = arr[i].color == arr[j].color;
ctx.strokeStyle = ["rgba(", arr[i].borderColor, ",", Math.min(arr[i].opacity, arr[j].opacity) * ((linkDist - dist) / linkDist), ")"].join("");
ctx.lineWidth = (arr[i].background ? lineBorder * backgroundMlt : lineBorder) * ((linkDist - dist) / linkDist); //*((linkDist-dist)/linkDist);
ctx.stroke();
}
}
}
}
var startTime = Date.now();
renderPoints(ctxfr, points);
renderPoints(ctxbg, pointsBack);
deltaT = Date.now() - startTime;
ctxfr.restore();
ctxbg.restore();
window.requestAnimationFrame(draw);
}
init();
I asked around and ctx.save() and ctx.restore() are in the top list of suspects, but I wouldn't know how to do this without them.
This is my first animation with canvas, which AFAIK should have been the best option in terms of cross-browser support and (decent) performances, but any advice on this side is still welcome; also, seems to slow down significantly on FF, but just on some machines where hardware acceleration does not work properly (or at all).
From what I read here (and basically everywhere else), FF seems to have serious issues dealing with canvas, but maybe I can optimize things a bit more.
Should I use something other than canvas to do the animation? But also consider that other options (like using SVG) seem to have less support, not to mention it would mean redoing most of the work.
Notes: The first part with the general variables might not be the best practice, but it worked to let a non-technical staff member (UI designer) play on the variables to see different results.

2d collision weird freeze on collide

I have this weird problem. I'm trying to code some 2d collision and stumbled across a collision detection program. I decided to try translate it into javascript and maybe learn something along the way.
The thing is that when I run it in my browser, all circles freeze in place as soon as two of them collide, but the program don't crash and I get no errors in the console.
I've tried to debug it and I think the problem lays within the first if-statement in the checkForCollision-function. Like it's always false.
Here's a link to the original version (Scroll down to "Listing 3" for complete code):
http://compsci.ca/v3/viewtopic.php?t=14897&postdays=0&postorder=asc&start=0
And here is my translation:
var canvas = document.getElementById('canvas01');
var drawFps = document.getElementById('fps');
var context = canvas.getContext('2d');
// The amount of delay between frames
var delay = 50;
// The maximum distance two circles can be apart and still be considered colliding
var epsilon = 10^-9;
// The number of circles
var numCircles = 5;
// We anticipate many circles so we create an array
var circles = new Array();
// Stores the amount of time untill a collision occurs
var t;
// Initialize the circles
createCircle();
function createCircle() {
for(var i = 0; i < numCircles; i++) {
var velX = Math.floor(Math.random() * 5) + 1; // this will get a number between 1 and 5;
velX *= Math.floor(Math.random() * 2) == 1 ? 1 : -1; // this will add minus sign in 50% of cases
var velY = Math.floor(Math.random() * 5) + 1;
velY *= Math.floor(Math.random() * 2) == 1 ? 1 : -1;
var radius = Math.floor(Math.random() * 30) + 15;
var mass = Math.PI * Math.pow(radius, 2);
circleData = {
x : Math.floor(Math.random() * canvas.width),
y : Math.floor(Math.random() * canvas.height),
vx : velX,
vy : velY,
vxp : velX,
vyp : velY,
r : radius,
m : mass
}
circles.push(circleData);
}
}
setInterval(loop, 17);
// Returns the amount of frames untill a collision will occur
function timeToCollision() {
var t = Number.MAX_VALUE;
var A;
var B;
var C;
var D;
var DISC;
// Loop through every pair of circles and calculate when they will collide
for(var i = 0; i < circles.length; i++) {
for(var j = 0; j < circles.length; j++) {
if(movingToCircle (circles[i], circles[j])) {
// Breaking down the formula for t
A = Math.pow(circles[i].vx, 2) + Math.pow(circles[i].vy, 2) - 2 * circles[i].vx * circles[j].vx + Math.pow(circles[j].vx, 2) - 2 * circles[i].vy * circles[j].vy + Math.pow(circles[j].vy, 2);
B = -circles[i].x * circles[i].vx - circles[i].y * circles[i].vy + circles[i].vx * circles[j].x + circles[i].vy * circles[j].y + circles[i].x * circles[j].vx - circles[j].x * circles[j].vx + circles[i].y * circles[j].vy - circles[j].y * circles[j].vy;
C = Math.pow(circles[i].vx, 2) + Math.pow(circles[i].vy, 2) - 2 * circles[i].vx * circles[j].vx + Math.pow(circles[j].vx, 2) - 2 * circles[i].vy * circles[j].vy + Math.pow(circles[j].vy, 2);
D = Math.pow(circles[i].x, 2) + Math.pow(circles[i].y, 2) - Math.pow(circles[i].r, 2) - 2 * circles[i].x * circles[j].x + Math.pow(circles[j].x, 2) - 2 * circles[i].y * circles[j].y + Math.pow(circles[j].y, 2) - 2 * circles[i].r * circles[j].r - Math.pow(circles[j].r, 2);
DISC = Math.pow((-2 * B), 2) - 4 * C * D;
// If the discriminent if non negative, a collision will occur and
// we must compare the time to our current time of collision. We
// udate the time if we find a collision that has occurd earlier
// than the previous one.
if(DISC >= 0) {
// We want the smallest time
t = Math.min(Math.min(t, 0.5 * (2 * B - Math.sqrt(DISC)) / A), 0.5 * (2 * B + Math.sqrt(DISC)) / A)
}
}
}
}
return t;
}
// Draws all the circles to the screen
function drawCircles() {
for(var i = 0; i < circles.length; i++) {
context.fillStyle = '#000000';
context.beginPath();
context.arc(circles[i].x, circles[i].y, circles[i].r, 0, 2 * Math.PI, true);
context.closePath();
context.fill();
}
}
// Updates all the circles attributes. If a collision Occures in between frames,
// the circles will be updated to the point of the collision. We return when the
// collision occurs so that we can adjust the delay in the main loop.
function updateCircles() {
// We want to increment by at most one frame
var t = Math.min(1, timeToCollision());
for(var i = 0; i < circles.length; i++) {
circles[i].x += circles[i].vx * t;
circles[i].y += circles[i].vy * t;
}
return t;
}
// Collision reaction function
function collide(c1, c2) {
var nx = (c1.x - c2.x) / (c1.r + c2.r);
var ny = (c1.y - c2.y) / (c1.r + c2.r);
var a1 = c1.vx * nx + c1.vy * ny;
var a2 = c2.vx * nx + c2.vy * ny;
var p = 2 * (a1 - a2) / (c1.m + c2.m);
c1.vxp = c1.vx - p * nx * c2.m;
c1.vyp = c1.vy - p * ny * c2.m;
c2.vxp = c2.vx + p * nx * c1.m;
c2.vyp = c2.vy + p * ny * c1.m;
}
// Checks if a collision has occured between any of the circles
function checkForCollision() {
for(var i = 0; i < circles.length; i++) {
for(var j = 0; j < circles.length; j++) {
if(movingToCircle(circles[i], circles[j]) && Math.pow((circles[j].x - circles[i].x), 2) + Math.pow((circles[j].y - circles[i].y), 2) <= Math.pow((circles[i].r + circles[j].r + epsilon), 2)) {
collide(circles[i], circles[j]);
}
}
if(circles[i].x < 1 || circles[i].x > canvas.width) {
circles[i].vxp *= -1;
}
if(circles[i].y < 1 || circles[i].y > canvas.height) {
circles[i].vyp *= -1;
}
}
for(var i = 0; i < circles.length; i++) {
circles[i].vx = circles[i].vxp;
circles[i].vy = circles[i].vyp;
}
}
// Tells us if two circles are moving towards each other
function movingToCircle(c1, c2) {
// Position Vector dotted with the Relative Velocity Vector
return (c2.x - c1.x) * (c1.vx - c2.vx) + (c2.y - c1.y) * (c1.vy - c2.vy) > 0;
}
// Main animation loop
function loop() {
// Clear Canvas
context.fillStyle = '#ffffff';
context.fillRect( 0, 0, canvas.width, canvas.height );
drawCircles();
checkForCollision();
t = updateCircles();
}
Note that I've changed balls to circles just because I find it fits better for 2d.
Thank you in advance.

javascript draw particles

How can I draw around 50000 particles in a browser and then just stop, I know how to create unending animations of particles, but how can I create one that once its done drawing the particles it just stops.
Edit
So essintially I want to time the drawing of the particles, however when i attack a timer, it doesnt get the change because the animation doesnt stop.
var scene = new Scene(),
particles = [],
len = 40000,
height = document.body.offsetHeight,
width = document.body.offsetWidth;
function Particle() {
this.x = 0;
this.y = 0;
this.size = 0;
this.depth = 0;
this.vy = 0;
}
Particle.prototype = {
constructor: Particle,
update: function (width, height) {
if (this.y > height) {
this.y = 1 - this.size;
}
this.y += this.vy;
}
};
for (var i = 0; i < len; i++) {
var particle = new Particle();
particle.x = Math.random() * width;
particle.y = Math.random() * height;
particle.depth = Math.random() * 10 | 0;
particle.size = (particle.depth + 1) / 8;
particle.vy = (particle.depth * .25) + 1 / Math.random();
particles.push(particle);
}
function falling_particles(scene) {
for (var i = 0, l = particles.length; i < l; i++) {
var particle = particles[i];
for (var w = 0; w < particle.size; w++) {
for (var h = 0; h < particle.size; h++) {
var pData = (~~(particle.x + w) + (~~(particle.y + h) * scene.width)) * 4;
scene.idata.data[pData] = 255;
scene.idata.data[pData + 1] = 255;
scene.idata.data[pData + 2] = 255;
scene.idata.data[pData + 3] = 255;
}
}
particle.update(scene.width, scene.height);
}
return scene.idata;
}
scene.setup(document.getElementById('canvas'), falling_particles, width, height, !0);
scene.animate();
window.onresize = function () {
height = scene.height = scene.canvas.height = document.body.offsetHeight;
width = scene.width = scene.canvas.width = document.body.offsetWidth;
};
link here: http://jsfiddle.net/MdSP4/
I'm not sure, what exactly you want to do, but if you add
setTimeout(function(){
scene.paused = true;
},1000);
Then all the drawing will stop after a second.

Categories

Resources