Why circles are vibrating on collision (Canvas)
canvas, javascript
Solution
Separating circles
Your separation code was not correct. Use the vector between them to get the new pos.
The vector between them
To find if two circles are intercepting find the length of the vector from one to the next
The two circles.
var cir1 = {x : 100, y : 100, r : 120}; // r is the radius
var cir2 = {x : 250, y : 280, r : 150}; // r is the radius
The vector from `cir2` to `cir1`
var vx = cir2.x - cir1.x;
var vy = cir2.y - cir1.y;
The length of the vector
var len = Math.sqrt(x * x + y * y);
// or use the ES6 Math.hypot function
/* var len = Math.hypot(x,y); */
The circles overlap if the sum of the radii is greater than the length of the vector between them
if(cir1.r + cir2.r > len){ // circles overlap
Normalise the vector
If they overlap you need to move one away from the other. There are many ways to do this, the simplest way is to move one circle along the line between them.
First normalise the vector from `cir1` to `cir2` by dividing by its (vector) length.
vx \= len;
vy \= len;
Note that the length could be zero. If this happens then you will get `NaN` in further calculations. If you suspect you may get one circle at the same location as another the easiest way to deal with the zero move one circle a little.
// replace the two lines above with
if(len === 0){ // circles are on top of each other
vx = 1; // move the circle (abstracted into the vector)
}else{
vx \= len; // normalise the vector
vy \= len;
}
Move circle/s to just touch
Now you have the normalised vector which is 1 unit long you can make it any length you need by multiplying the two scalars `vx`, `vy` with the desired length which in this case is the sum of the two circles radii.
var mx = vx * (cir1.r + cir2.r); // move distance
var my = vy * (cir1.r + cir2.r);
.Only use one of the following methods.
You can now position one of the circles the correct distance so that they just touch
// move cir1
cir1.x = cir2.x - mx;
cir1.y = cir2.y - my;
Or move the second circle
cir2.x = cir1.x + mx;
cir2.y = cir1.y + my;
Or move both circles but you will have to first find the proportional center between the two
var pLen = cir1.r / (cir1.r + cir2.r); // find the ratio of the radii
var cx = cir1.x + pLen * vx * len; // find the proportional center between
var cy = cir1.y + pLen * vy * len; // the two circles
Then move both circles away from that point by their radii
cir1.x = cx - vx * cir1.r; // move circle 1 away from the shared center
cir1.y = cy - vy * cir1.r;
cir2.x = cx + vx * cir2.r; // move circle 2 away from the shared center
cir2.y = cy + vy * cir2.r;
DEMO
Copy of OP's snippet with mods to fix problem by moving the the first circle `blob1` away from the second `blob2` and assuming they will never be at the same spot (no divide by zero)
var
canvas,
ctx,
width = innerWidth,
height = innerHeight,
mouseX = 0,
mouseY = 0;
var
camera = {
x: 0,
y: 0,
update: function(obj) {
this.x = obj.x - width / 2;
this.y = obj.y - height / 2;
}
},
player = {
defaultMass: 54,
x: 0,
y: 0,
blobs: [],
update: function() {
for (var i = 0; i < this.blobs.length; i++) {
var x = mouseX + camera.x - this.blobs[i].x;
var y = mouseY + camera.y - this.blobs[i].y;
var length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
var speed = 54 / this.blobs[i].mass;
this.blobs[i].velX = x / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].velY = y / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2));
this.blobs[i].x += this.blobs[i].velX;
this.blobs[i].y += this.blobs[i].velY;
for (var j = 0; j < this.blobs.length; j++) {
if (j != i && this.blobs[i] !== undefined) {
var blob1 = this.blobs[i];
var blob2 = this.blobs[j];
var x = blob2.x - blob1.x; // get the vector from blob1 to blob2
var y = blob2.y - blob1.y; //
var dist = Math.sqrt(x * x + y * y); // get the distance between the two blobs
if (dist < blob1.mass + blob2.mass) { // if the distance is less than the 2 radius
// if there is overlap move blob one along the line between the two the distance of the two radius
x /= dist; // normalize the vector. This makes the vector 1 unit long
y /= dist;
// multiplying the normalised vector by the correct distance between the two
// and subtracting that distance from the blob 2 give the new pos of
// blob 1
blob1.x = blob2.x - x * (blob1.mass + blob2.mass);
blob1.y = blob2.y - y * (blob1.mass + blob2.mass);
}
}
}
}
this.x += (mouseX - width / 2) / (width / 2) * 1;
this.y += (mouseY - height / 2) / (height / 2) * 1
},
split: function(cell) {
cell.mass /= 2;
this.blobs.push({
x: cell.x,
y: cell.y,
mass: cell.mass
});
},
draw: function() {
for (var i = 0; i < this.blobs.length; i++) {
ctx.fillStyle = "red";
ctx.beginPath();
ctx.arc(-camera.x + this.blobs[i].x, -camera.y + this.blobs[i].y, this.blobs[i].mass, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
};
function handleMouseMove(e) {
mouseX = e.clientX;
mouseY = e.clientY;
}
function setup() {
canvas = document.getElementById("game");
ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
addEventListener("mousemove", handleMouseMove);
player.blobs.push({
x: 0,
y: 0,
mass: player.defaultMass
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass / 2
});
player.blobs.push({
x: 100,
y: 100,
mass: player.defaultMass * 2
});
var loop = function() {
update();
draw();
requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
}
function update() {
camera.update(player.blobs[0]);
player.update();
}
function draw() {
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, width, height);
player.draw();
}
setup();
body {
margin: 0;
padding: 0;
}
<canvas id="game">kindly update your browser.</canvas>
Problem
I have been creating a clone of agar.io and I don't understand why the circles start vibrating when they touch each other. Below is my code: ``` var canvas, ctx, width = innerWidth, height = innerHeight, mouseX = 0, mouseY = 0; var camera = { x: 0, y: 0, update: function(obj) { this.x = obj.x - width / 2; this.y = obj.y - height / 2; } }, player = { defaultMass: 54, x: 0, y: 0, blobs: [], update: function() { for (var i = 0; i < this.blobs.length; i++) { var x = mouseX + camera.x - this.blobs[i].x; var y = mouseY + camera.y - this.blobs[i].y; var length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); var speed = 54 / this.blobs[i].mass; this.blobs[i].velX = x / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2)); this.blobs[i].velY = y / length * speed * Math.min(1, Math.pow(x / this.blobs[i].mass, 2)); this.blobs[i].x += this.blobs[i].velX; this.blobs[i].y += this.blobs[i].velY; for (var j = 0; j < this.blobs.length; j++) { if (j != i && this.blobs[i] !== undefined) { var blob1 = this.blobs[i]; var blob2 = this.blobs[j]; var dist = Math.sqrt(Math.pow(blob2.x - blob1.x, 2) + Math.pow(blob2.y - blob1.y, 2)); if (dist < blob1.mass + blob2.mass) { if (this.blobs[i].x < this.blobs[j].x) { this.blobs[i].x--; } else if (this.blobs[i].x > this.blobs[j].x) { this.blobs[i].x++; } if (this.blobs[i].y < this.blobs[j].y) { this.blobs[i].y--; } else if ((this.blobs[i].y > this.blobs[j].y)) { this.blobs[i].y++; } } } } } this.x += (mouseX - width / 2) / (width / 2) * 1; this.y += (mouseY - height / 2) / (height / 2) * 1 }, split: function(cell) { cell.mass /= 2; this.blobs.push({ x: cell.x, y: cell.y, mass: cell.mass }); }, draw: function() { for (var i = 0; i < this.blobs.length; i++) { ctx.fillStyle = "red"; ctx.beginPath(); ctx.arc(-camera.x + this.blobs[i].x, -camera.y + this.blobs[i].y, this.blobs[i].mass, 0, Math.PI * 2); ctx.fill(); ctx.closePath(); } } }; function handleMouseMove(e) { mouseX = e.clientX; mouseY = e.clientY; } function setup() { canvas = document.getElementById("game"); ctx = canvas.getContext("2d"); canvas.width = width; canvas.height = height; addEventListener("mousemove", handleMouseMove); player.blobs.push({ x: 0, y: 0, mass: player.defaultMass }); player.blobs.push({ x: 100, y: 100, mass: player.defaultMass / 2 }); player.blobs.push({ x: 100, y: 100, mass: player.defaultMass * 2 }); var loop = function() { update(); draw(); requestAnimationFrame(loop); } requestAnimationFrame(loop); } function update() { camera.update(player.blobs[0]); player.update(); } function draw() { ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, width, height); player.draw(); } setup(); ``` ``` body { margin: 0; padding: 0; } ``` ``` <canvas id="game">kindly update your browser.</canvas> ```