I can reshape polygon with mouse by using code in snippet. After drawing polygon, users can change shape by moving points. But I want to modify shape without changing line lengths. The points will change as possible but length of the lines will remain the same.
How can I do this?
var canvas, ctx;
var canvasIsMouseDown = false;
var radius = 6;
var pointIndex = -1;
var points = [
{ x: 10, y: 10 },
{ x: 100, y: 50 },
{ x: 150, y: 100 },
{ x: 60, y: 110 },
{ x: 30, y: 160 }
];
function start() {
canvas = document.getElementById("cnPolygon");
ctx = canvas.getContext("2d");
canvas.addEventListener("mousemove", canvasMouseMove);
canvas.addEventListener("mousedown", canvasMouseDown);
canvas.addEventListener("mouseup", canvasMouseUp);
draw();
}
function canvasMouseMove(ev) {
if (!canvasIsMouseDown || pointIndex === -1) return;
points[pointIndex].x = ev.pageX - this.offsetLeft;
points[pointIndex].y = ev.pageY - this.offsetTop;
draw();
}
function canvasMouseDown(ev) {
canvasIsMouseDown = true;
var x = ev.pageX - this.offsetLeft;
var y = ev.pageY - this.offsetTop;
pointIndex = -1;
var dist;
for (var i = 0; i < points.length; i++) {
dist = Math.sqrt(Math.pow((x - points[i].x), 2) + Math.pow((y - points[i].y), 2));
if (dist <= radius) {
pointIndex = i;
break;
}
}
}
function canvasMouseUp(ev) {
canvasIsMouseDown = false;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (var i = 0; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.closePath();
ctx.stroke();
for (var i = 0; i < points.length; i++) {
ctx.beginPath();
ctx.arc(points[i].x, points[i].y, radius, 0, Math.PI * 2);
ctx.stroke();
}
}
document.addEventListener("DOMContentLoaded", start);
<canvas id="cnPolygon" width="200" height="200" style="border:solid 1px silver"></canvas>
Take a look at this
var canvas, ctx;
var canvasIsMouseDown = false;
var radius = 3;
var pointIndex = -1;
var points = [
{ x: 10, y: 10 },
{ x: 100, y: 50 },
{ x: 150, y: 100 },
{ x: 60, y: 110 },
{ x: 30, y: 160 }
];
// PHYSICS START ----------------
var stiffness = 0.25 // defines how elastic the contrainst should be
var oscillations = 10 // defines how many iterations should be made, more iterations mean higher precision
function getAngle(x1,y1,x2,y2){
return Math.atan2(y2-y1,x2-x1) + Math.PI/2
}
function getConstraintPos(tx,ty,ox,oy,dist){
var rot = getAngle(tx,ty,ox,oy)
var x = tx+Math.sin(rot)*dist
var y = ty-Math.cos(rot)*dist
return [x,y]
}
function applyContraintForce(point,pos){
point.x += (pos[0] - point.x)*stiffness
point.y += (pos[1] - point.y)*stiffness
}
function defineDistances(){
for (var i = 0; i < points.length; i++) {
var next_point = points[(i+1)%points.length]
points[i].distance = Math.sqrt(Math.pow((next_point.x - points[i].x), 2) + Math.pow((next_point.y - points[i].y), 2))
}
}
function updateContraints(){
// forward pass
for (var i=0;i<points.length;i++)
{
if(i==pointIndex) continue
var j = (+i+1)%points.length
var pos = getConstraintPos(points[j].x,points[j].y,points[i].x,points[i].y,points[i].distance)
applyContraintForce(points[i],pos)
}
//backward pass
for (var i=points.length-1;i>=0;i--)
{
if(i==pointIndex) continue
var j = (i-1)
j = j<0 ? points.length+j : j
var pos = getConstraintPos(points[j].x,points[j].y,points[i].x,points[i].y,points[j].distance)
applyContraintForce(points[i],pos)
}
}
// PHYSICS END ----------------
function start() {
canvas = document.getElementById("cnPolygon");
ctx = canvas.getContext("2d");
canvas.addEventListener("mousemove", canvasMouseMove);
canvas.addEventListener("mousedown", canvasMouseDown);
canvas.addEventListener("mouseup", canvasMouseUp);
defineDistances()
draw();
}
function canvasMouseMove(ev) {
if (!canvasIsMouseDown || pointIndex === -1) return;
points[pointIndex].x = ev.pageX - this.offsetLeft;
points[pointIndex].y = ev.pageY - this.offsetTop;
for(var i=0;i<oscillations;i++){
updateContraints()
}
draw();
}
function canvasMouseDown(ev) {
canvasIsMouseDown = true;
var x = ev.pageX - this.offsetLeft;
var y = ev.pageY - this.offsetTop;
pointIndex = -1;
var dist;
for (var i = 0; i < points.length; i++) {
dist = Math.abs(Math.sqrt(Math.pow((x - points[i].x), 2) + Math.pow((y - points[i].y), 2)));
if (dist <= radius) {
pointIndex = i;
break;
}
}
}
function canvasMouseUp(ev) {
canvasIsMouseDown = false;
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for (var i = 0; i < points.length; i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.closePath();
ctx.stroke();
for (var i = 0; i < points.length; i++) {
ctx.beginPath();
ctx.arc(points[i].x, points[i].y, radius * 2, 0, Math.PI * 2);
ctx.stroke();
}
}
document.addEventListener("DOMContentLoaded", start);
<canvas id="cnPolygon" width="500" height="300" style="border:solid 1px silver"></canvas>
What this does is calculate the angle between two given points and applies a force based on that angle and the distance delta. The force applied is multiplied by the stiffness.
This has to be done forwards (point A -> point B) and backwards (point A <- point B) in order to account for the differences between the last to first point in the chain.
NOTE this is not 100% accurate. The accuracy can be increased by the iterations count, but as #bhspencer already pointed out, there are cases where this is impossible, simply because of geometry.
Related
N-Body gravity simulation seems to be working fine at first glance, and the same is true for body collisions, but once gravitationally attracted objects start to collide, they start to spiral around each other frantically and the collection of them as a whole have very erratic motion... The code (html-javascript) will be included below, and to reproduce what I'm talking about, you can create a new body by clicking in a random location on the screen.
The math for gravitational attraction is done in the Body.prototype.gravityCalc() method of the Body object type (line 261). The math for the collision resolution is found in the dynamic collision section of the bodyHandle() function (line 337).
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// event handling
document.addEventListener('keydown', keyDown);
document.addEventListener('mousedown', mouseDown)
document.addEventListener('mouseup', mouseUp)
document.addEventListener('mousemove', mouseMove);
document.addEventListener('touchstart', touchStart);
document.addEventListener('touchmove', touchMove);
document.addEventListener('touchend', touchEnd);
window.addEventListener('resize', resize);
window.onload = function() {reset()}
mouseDown = false;
nothingGrabbed = true;
mouseX = 0;
mouseY = 0;
function keyDown(data) {
if(data.key == "r") {
clearInterval(loop);
reset();
}
else if(data.key == 'g') {
gravityOn = !gravityOn;
}
else if(data.key == 'Delete') {
for(i = 0; i < bodies.length ; i++) {
if(((mouseX - bodies[i].x)**2 + (mouseY - bodies[i].y)**2) <= bodies[i].radius**2) {
bodies.splice(i, 1);
}
}
}
else if(data.key == 'c') {
gravity_c *= -1;
}
else if(data.key == 'f') {
falling = !falling;
}
else if(data.key == 'a') {
acceleration *= -1;
}
}
function mouseDown(data) {
mouseDown = true;
nothingGrabbed = true;
mouseX = data.clientX;
mouseY = canvas.height - data.clientY;
}
function mouseUp(data) {
mouseDown = false;
nothingGrabbed = true;
for(i = 0; i < bodies.length; i++) {
bodies[i].grabbed = false
}
}
function mouseMove(data) {
mouseX = data.clientX;
mouseY = canvas.height - data.clientY;
}
function touchStart(data) {
mouseDown = true;
nothingGrabbed = true;
mouseX = data.touches[0].clientX;
mouseY = canvas.height - data.touches[0].clientY;
}
function touchMove(data) {
mouseX = data.touches[0].clientX;
mouseY = canvas.height - data.touches[0].clientY;
}
function touchEnd(data) {
mouseDown = false;
nothingGrabbed = true;
for(i=0;i<bodies.length;i++) {
bodies[i].grabbed = false;
}
}
function resize(data) {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialize Variables
function reset() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.color = 'rgb(70, 70, 70)';
scale = Math.min(canvas.width, canvas.height);
fps = 120;
running = true;
loop = setInterval(main, 1000/fps);
gravityOn = true // if true, objects are gravitationally attracted to each other
gravity_c = 334000 // universe's gravitational constant
boundaryCollision = true // if true, objects collide with edges of canvas
wallDampen = 0.7 // number to multiply by when an objects hit a wall
bodyCollision = true // if true, bodies will collide with each other
bodyDampen = 0.4 // number to multiply when two objects collide
falling = false // if true, objects will fall to the bottom of the screen
acceleration = 400
bodies = [] // a list of each Body object
collidingPairs = [] // a list of pairs of colliding bodies
/*
var bounds = 200;
for(i = 0; i<70; i++) { // randomly place bodies
Body.create({
x: Math.floor(Math.random()*canvas.width),
y: Math.floor(Math.random()*canvas.height),
a: Math.random()*Math.PI*2,
xV: Math.floor(Math.random() * (bounds - -bounds)) + -bounds,
yV: Math.floor(Math.random() * (bounds - -bounds)) + -bounds,
mass: Math.ceil(Math.random()*23)
})
} */
/*
Body.create({
x: canvas.width/2 - 50,
xV: 10,
yV: 0,
aV: 3,
y: canvas.height/2 + 0,
mass: 10
});
Body.create({
x: canvas.width/2 + 50,
xV: 0,
aV: 0,
y: canvas.height/2,
mass: 10
});
*/
Body.create({
x: canvas.width/2,
y: canvas.height/2,
mass: 24,
xV: -10.83
});
Body.create({
x: canvas.width/2,
y: canvas.height/2 + 150,
mass: 1,
xV: 260,
color: 'teal'
});
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Body Type Object
function Body(params) {
this.x = params.x || canvas.width/2;
this.y = params.y || canvas.height/2;
this.a = params.a || 0;
this.xV = params.xV || 0;
this.yV = params.yV || 0;
this.aV = params.aV || 0;
this.xA = params.xA || 0;
this.yA = params.yA || 0;
this.aA = params.aA || 0;
this.grabbed = false;
this.edgeBlock = params.edgeBlock || boundaryCollision;
this.gravity = params.gravityOn || gravityOn;
this.mass = params.mass || 6;
this.density = params.density || 0.008;
this.radius = params.radius || (this.mass/(Math.PI*this.density))**0.5;
this.color = params.color || 'crimson';
this.lineWidth = params.lineWidth || 2;
}
Body.create = function(params) {
bodies.push(new Body(params));
}
Body.prototype.move = function() {
this.xV += this.xA/fps;
this.yV += this.yA/fps;
this.aV += this.aA/fps;
this.x += this.xV/fps;
this.y += this.yV/fps;
this.a += this.aV/fps;
if(this.edgeBlock) {
if(this.x + this.radius > canvas.width) {
this.x = canvas.width - this.radius;
this.xV *= -wallDampen
}
else if(this.x - this.radius < 0) {
this.x = this.radius;
this.xV *= -wallDampen;
}
if(this.y + this.radius > canvas.height) {
this.y = canvas.height - this.radius;
this.yV *= -wallDampen;
}
else if(this.y - this.radius < 0) {
this.y = this.radius;
this.yV *= -wallDampen;
}
}
if(this.grabbed) {
this.xA = 0;
this.yA = 0;
this.xV = 0;
this.yV = 0;
this.x = mouseX;
this.y = mouseY;
}
}
Body.prototype.draw = function() {
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = this.lineWidth;
ctx.fillStyle = this.color;
ctx.arc(this.x, canvas.height - this.y, this.radius, 0, Math.PI*2, true);
ctx.fill();
ctx.stroke();
ctx.closePath()
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.lineWidth = this.linewidth;
ctx.moveTo(this.x, canvas.height - this.y);
ctx.lineTo(this.x + this.radius*Math.cos(this.a), canvas.height - (this.y + this.radius*Math.sin(this.a)))
ctx.stroke();
ctx.closePath();
}
// calculates gravitational attraction to 'otherObject'
Body.prototype.gravityCalc = function(otherObject) {
var x1 = this.x;
var y1 = this.y;
var x2 = otherObject.x;
var y2 = otherObject.y;
var distSquare = ((x2-x1)**2 + (y2-y1)**2);
var val = (gravity_c*otherObject.mass)/((distSquare)**(3/2));
var xA = val * (x2 - x1);
var yA = val * (y2 - y1);
return [xA, yA]
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Physics Code
function bodyHandle() {
for(i = 0; i < bodies.length; i++) {
if(mouseDown && nothingGrabbed) {
if(Math.abs((mouseX - bodies[i].x)**2 + (mouseY - bodies[i].y)**2) <= bodies[i].radius**2) {
bodies[i].grabbed = true;
nothingGrabbed = false;
}
}
bodies[i].draw()
if(running) {
if(falling) {
bodies[i].yV -= acceleration/fps;
}
bodies[i].move();
}
bodies[i].xA = 0;
bodies[i].yA = 0;
collidingPairs = []
if(gravityOn || bodyCollision) {
for(b = 0; b < bodies.length; b++) {
if(i != b) {
if(bodyCollision) {
var x1 = bodies[i].x;
var y1 = bodies[i].y;
var x2 = bodies[b].x;
var y2 = bodies[b].y;
var rSum = bodies[i].radius + bodies[b].radius;
var dist = { // vector
i: x2 - x1,
j: y2 - y1,
mag: ((x2-x1)**2 + (y2-y1)**2)**0.5,
norm: {
i: (x2-x1)/(((x2-x1)**2 + (y2-y1)**2)**0.5),
j: (y2-y1)/(((x2-x1)**2 + (y2-y1)**2)**0.5)
}
}
if(dist.mag <= rSum) { // static collision
var overlap = rSum - dist.mag;
bodies[i].x -= overlap/2 * dist.norm.i;
bodies[i].y -= overlap/2 * dist.norm.j;
bodies[b].x += overlap/2 * dist.norm.i;
bodies[b].y += overlap/2 * dist.norm.j;
collidingPairs.push([bodies[i], bodies[b]]);
}
}
if(gravityOn) {
if(bodies[i].gravity) {
var accel = bodies[i].gravityCalc(bodies[b]);
bodies[i].xA += accel[0];
bodies[i].yA += accel[1];
}
}
}
}
}
for(c = 0; c < collidingPairs.length; c++) { // dynamic collision
var x1 = collidingPairs[c][0].x;
var y1 = collidingPairs[c][0].y;
var r1 = collidingPairs[c][0].radius;
var x2 = collidingPairs[c][1].x;
var y2 = collidingPairs[c][1].y;
var r2 = collidingPairs[c][1].radius;
var dist = { // vector from b1 to b2
i: x2 - x1,
j: y2 - y1,
mag: ((x2-x1)**2 + (y2-y1)**2)**0.5,
norm: {
i: (x2-x1)/(((x2-x1)**2 + (y2-y1)**2)**0.5),
j: (y2-y1)/(((x2-x1)**2 + (y2-y1)**2)**0.5)
}
}
var m1 = collidingPairs[c][0].mass;
var m2 = collidingPairs[c][1].mass;
var norm = { // vector normal along 'wall' of collision
i: -dist.j/(((dist.i)**2 + (-dist.j)**2)**0.5),
j: dist.i/(((dist.i)**2 + (-dist.j)**2)**0.5)
}
var perp = { // vector normal pointing from b1 to b2
i: dist.norm.i,
j: dist.norm.j
}
var vel1 = { // vector of b1 velocity
i: collidingPairs[c][0].xV,
j: collidingPairs[c][0].yV,
dot: function(vect) {
return collidingPairs[c][0].xV * vect.i + collidingPairs[c][0].yV * vect.j
}
}
var vel2 = { // vector of b2 velocity
i: collidingPairs[c][1].xV,
j: collidingPairs[c][1].yV,
dot: function(vect) {
return collidingPairs[c][1].xV * vect.i + collidingPairs[c][1].yV * vect.j
}
}
// new velocities along perp^ of b1 and b2
var nV1Perp = (vel1.dot(perp))*(m1-m2)/(m1+m2) + (vel2.dot(perp))*(2*m2)/(m1+m2);
var nV2Perp = (vel1.dot(perp))*(2*m1)/(m1+m2) + (vel2.dot(perp))*(m2-m1)/(m1+m2);
/* testing rotation after collision
// velocities of the points of collision on b1 and b2
var pVel1M = vel1.dot(norm) + collidingPairs[c][0].aV*r1;
var pVel2M = vel2.dot(norm) + collidingPairs[c][1].aV*r2;
// moment of inertia for b1 and b2
var I1 = 1/2 * m1 * r1**2;
var I2 = 1/2 * m2 * r2**2;
// new velocities of the points of collisions on b1 and b2
var newpVel1M = ((I1-I2)/(I1+I2))*pVel1M + ((2*I2)/(I1+I2))*pVel2M;
var newpVel2M = ((2*I1)/(I1+I2))*pVel1M + ((I2-I1)/(I1+I2))*pVel2M;
var vectToCol1 = { // vector from x1,y1 to point of collision on b1
i: r1*perp.i,
j: r1*perp.j
};
var vectToCol2 = { // vector from x2,y2 to point of collision on b2
i: r2*-perp.i,
j: r2*-perp.j
};
// sign of cross product of pVelM and vectToCol
var vCrossR1 = (pVel1M*norm.i)*(vectToCol1.j) - (pVel1M*norm.j)*(vectToCol1.i);
vCrossR1 = vCrossR1/Math.abs(vCrossR1);
var vCrossR2 = (pVel2M*norm.i)*(vectToCol2.j) - (pVel2M*norm.j)*(vectToCol2.i);
vCrossR2 = vCrossR2/Math.abs(vCrossR2);
collidingPairs[c][0].aV = vCrossR1 * (newpVel1M)/r1;
collidingPairs[c][1].aV = vCrossR2 * (newpVel2M)/r2;
/* draw collision point velocity vectors [debugging]
ctx.beginPath();
ctx.strokeStyle = 'black';
ctx.moveTo(x1 + vectToCol1.i, canvas.height - (y1 + vectToCol1.j));
ctx.lineTo((x1+vectToCol1.i) + pVel1M*norm.i, (canvas.height- (y1+vectToCol1.j + pVel1M*norm.j)));
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = 'white';
ctx.moveTo(x2 + vectToCol2.i, canvas.height - (y2 + vectToCol2.j));
ctx.lineTo((x2+vectToCol2.i) + pVel2M*norm.i, (canvas.height- (y2+vectToCol2.j + pVel2M*norm.j)));
ctx.stroke();
ctx.closePath();
console.log(pVel1M, pVel2M);
clearInterval(loop);
*/
collidingPairs[c][0].xV = vel1.dot(norm)*norm.i + nV1Perp*perp.i * bodyDampen;
collidingPairs[c][0].yV = vel1.dot(norm)*norm.j + nV1Perp*perp.j * bodyDampen;
collidingPairs[c][1].xV = vel2.dot(norm)*norm.i + nV2Perp*perp.i * bodyDampen;
collidingPairs[c][1].yV = vel2.dot(norm)*norm.j + nV2Perp*perp.j * bodyDampen;
}
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Main Loop
function main() {
// blank out canvas
ctx.fillStyle = canvas.color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
bodyHandle();
if(nothingGrabbed && mouseDown) {
bodies.push(new Body({x: mouseX,
y: mouseY,
mass: 90}));
bodies[bodies.length-1].move();
bodies[bodies.length-1].draw();
}
}
<html>
<meta name='viewport' content='width=device-width,height=device-height'>
<body>
<canvas id="canvas" width='300px' height='300px'></canvas>
<style>
body {
padding: 0;
margin: 0;
}
canvas {
padding: 0;
margin: 0;
}
</style>
</html>
I cannot tell you much about the code. Personally it seems to me that the animations could be correct.
If you want to test your code you could try to test if laws of conservation of energy and momentum are respected. You could, for example, sum the momentum of every object (mass times velocity) and see if the number are maintained constant when there are no forces from the outside (collisions with the wall). To do this I would suggest to make the free space available larger. Another quantity is the total energy (kinetic plus potential) which is a bit harder, but still easy to compute (to compute tot. pot. energy you have to sum over all pairs).
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>
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>
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>
I'm building a dynamic radar chart, I got the code reviewed and followed the recommendation from fellow SO member.
This is how far I've come, but seem to have hit a roadblock:
var canv = document.getElementById('canvas');
var canv1 = document.getElementById('canvas1');
var point_xy = document.getElementById('point_xy');
var tipCanvas = document.getElementById("tip");
var tipCtx = tipCanvas.getContext("2d");
var point_xy_cords = [
[]
];
var pentagon_one = 24;
var pentagon_two = 18;
var pentagon_three = 12;
var pentagon_four = 6;
var pentagon_five = 0;
var circles = [];
var contx = canv.getContext('2d');
var contx1 = canv1.getContext('2d');
var offsetX = canv1.offsetLeft;
var offsetY = canv1.offsetTop;
contx.clearRect(0, 0, canv.width, canv.height);
function drawShape(ctx, x, y, points, radius1, radius2, alpha0) {
//points: number of points (or number of sides for polygons)
//radius1: "outer" radius of the star
//radius2: "inner" radius of the star (if equal to radius1, a polygon is drawn)
//angle0: initial angle (clockwise), by default, stars and polygons are 'pointing' up
var radius_size = radius1;
var i, angle, radius;
if (radius2 !== radius1) {
points = 2 * points;
}
for (var i = 0; i <= 5; i++) {
var temp = [];
contx1.beginPath();
for (var j = 0; j <= 4; j++) {
angle = j * 2 * Math.PI / points - Math.PI / 2 + alpha0;
radius = j % 2 === 0 ? radius_size : radius_size;
temp[j] = [(x + radius_size * Math.cos(angle)), (y + radius_size * Math.sin(angle))];
ctx.lineTo(temp[j][0], temp[j][1]);
}
ctx.closePath();
style(ctx);
radius_size = radius_size - 20;
point_xy_cords.push(temp);
}
point_xy.textContent = "[1] = " + point_xy_cords[1] + " y = " + point_xy_cords[1][1];
}
function style(ctx, fill) {
ctx.strokeStyle = "rgba(0, 109, 0, 1)";
ctx.lineWidth = 2;
if (fill) {
ctx.fillStyle = "rgba(74, 157, 33, 0.6)";
ctx.fill();
} else {
ctx.stroke()
}
//contx.fill();
}
var radius = 2;
var Circle = function(x, y, radius) {
this.left = x - radius;
this.top = y - radius;
this.right = x + radius;
this.bottom = y + radius;
this.point_clicked = [];
this.clicked = function(){
points[1][0] = x; //hardcoded part
points[1][1] = y; //hardcoded part
contx1.clearRect(0, 0, canv.width, canv.height);
drawBackgroundPentagons(contx1);
drawMainPentagon(contx1, points);
drawPoints();
}
this.draw = function(ctx) {
//Draw all points
ctx.beginPath();
ctx.arc(x, y, 2, 0, 2 * Math.PI, false);
ctx.lineWidth = 1;
ctx.strokeStyle = "rgba(74, 157, 33, 1)";
ctx.fill();
ctx.stroke();
}
this.containsPoint = function(x,y){
return (x < this.right && x > this.left && y > this.top && y < this.bottom);
}
};
//Draw background
function drawBackgroundPentagons(ctx) {
drawShape(ctx, 120, 120, 5, 100, 100, 0);
}
drawBackgroundPentagons(contx1);
//Draw all the points
function drawPoints(){
for (var x = 1; x <= 5; x++){
for (var y = 0; y <= 4; y++){
var circle = new Circle(point_xy_cords[x][y][0], point_xy_cords[x][y][1], 8);
circle.draw(contx1);
circles.push(circle);
}
}
}
drawPoints();
function drawMainPentagon(ctx, points) {
ctx.beginPath();
ctx.moveTo(points[0][0], points[0][1]);
for (var x = 1; x <= 4; x++) {
ctx.lineTo(points[x][0], points[x][1]);
}
style(ctx, "fill");
ctx.closePath();
}
points = point_xy_cords[1];
drawMainPentagon(contx1, points);
function handleMouseDown(e, message) {
point_xy.textContent = (message);
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canv1.onmousedown = function(e) {
var pos = getMousePos(canv1, e);
var clickedX = pos.x;
var clickedY = pos.y;
var tooltipText = "nothing";
for (var i = 0; i < circles.length; i++) {
var circle = circles[i];
if (circle.containsPoint(clickedX, clickedY)) {
circle.clicked();
return;
}
}
tooltip("points[0]", clickedX, clickedY);
};
function tooltip(text, clickedX, clickedY) {
tipCtx.fillStyle = "black";
tipCtx.fillRect(0, 0, canvas.width, canvas.height);
tipCtx.fillStyle = "white";
tipCtx.fillText(text, 5, 10);
tipCanvas.style.left = (clickedX + 15) + "px";
tipCanvas.style.top = (clickedY - 26) + "px";
}
canv1.onmouseover = function(e) {
return null;
}
canv1.onmouseout = function(e) {
return null;
}
canv1.onmousemove = function(e) {
return null;
}
#tip {
left: -200px;
top: 100px;
position: absolute;
float: left;
maxWidth: 200px;
backgroundColor: rgba(0, 0, 0, 0.8);
border: rgba(45, 65, 45, 1);
borderRadius: 5px;
color: #f9f9f9;
fontSize: 14px;
padding: 5px;
textAlign: left;
}
<div id="canvasesdiv" style="position:relative; width:400px; height:300px">
<canvas id="tip" width=100 height=100 style="z-index: 3;"></canvas>
<canvas id="canvas" style="z-index: 1;
position:absolute;
left:10px;
top:10px;
" height="300px" width="400">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
<canvas id="canvas1" style="z-index: 2;
position:absolute;
left:10px;
top:10px;
" height="300px" width="400">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</div>
<div id='point_xy'></div>
If you click a point, it is suppose to move the point of the highlighted pentagon to the clicked point. It works, except I can't figure out what conditions to add in order to move the correct corner of the highlighted pentagon. In the above code I have hardcoded it, so that no matter which point you click, it will move point at index 0.
Any direction would be appreciated.
So what you want to do is let each circle know what spoke or radii it belongs to. Something like this:
var Circle = function(x, y, radius, spoke, value) {
this.x = x;
this.y = y;
this.radius = radius;
this.spoke = spoke;
this.value = value;
Now create them something like:
function drawPoints() {
for (var value = 1; value <= 5; value++){
for (var spoke = 0; spoke <= 4; spoke++){
var circle = new Circle(point_xy_cords[value][spoke][0], point_xy_cords[value][spoke][1], 8, spoke, value);
circle.draw(contx1);
circles.push(circle);
}
}
}
I changed the variable names to something meaningful. One note here is that you mix code to create the circles and code to draw them. You don't want to do this. Create them once on initialization and redraw them as changes are made (clicking). You don't want to re-create the circles every time you redraw.
Lastly change this:
// Circle
this.clicked = function(){
points[this.spoke][0] = this.x;
points[this.spoke][1] = this.y;
updateCanvas();
}
And elsewhere:
function updateCanvas() {
contx1.clearRect(0, 0, canv.width, canv.height);
drawBackgroundPentagons(contx1);
drawMainPentagon(contx1, points);
drawPoints();
}
If I can make a suggestion, start with the simplest code you can. Start just by displaying the circles and pentagons, get that working cleanly and build onto it. Try and keep logic separate in your code. There are several places where you create objects and initialize arrays (like coords) while you are drawing which is both unnecssary but also means that you do it over and over instead of just once. There is a also lot of code here that is unnecessary.
I'm trying to find a way to put as much hexagons in a circle as possible. So far the best result I have obtained is by generating hexagons from the center outward in a circular shape.
But I think my calculation to get the maximum hexagon circles is wrong, especially the part where I use the Math.ceil() and Math.Floor functions to round down/up some values.
When using Math.ceil(), hexagons are sometimes overlapping the circle.
When using Math.floor() on the other hand , it sometimes leaves too much space between the last circle of hexagons and the circle's border.
var c_el = document.getElementById("myCanvas");
var ctx = c_el.getContext("2d");
var canvas_width = c_el.clientWidth;
var canvas_height = c_el.clientHeight;
var PI=Math.PI;
var PI2=PI*2;
var hexCircle = {
r: 110, /// radius
pos: {
x: (canvas_width / 2),
y: (canvas_height / 2)
}
};
var hexagon = {
r: 20,
pos:{
x: 0,
y: 0
},
space: 1
};
drawHexCircle( hexCircle, hexagon );
function drawHexCircle(hc, hex ) {
drawCircle(hc);
var hcr = Math.ceil( Math.sqrt(3) * (hc.r / 2) );
var hr = Math.ceil( ( Math.sqrt(3) * (hex.r / 2) ) ) + hexagon.space; // hexRadius
var circles = Math.ceil( ( hcr / hr ) / 2 );
drawHex( hc.pos.x , hc.pos.y, hex.r ); //center hex ///
for (var i = 1; i<=circles; i++) {
for (var j = 0; j<6; j++) {
var currentX = hc.pos.x+Math.cos(j*PI2/6)*hr*2*i;
var currentY = hc.pos.y+Math.sin(j*PI2/6)*hr*2*i;
drawHex( currentX,currentY, hex.r );
for (var k = 1; k<i; k++) {
var newX = currentX + Math.cos((j*PI2/6+PI2/3))*hr*2*k;
var newY = currentY + Math.sin((j*PI2/6+PI2/3))*hr*2*k;
drawHex( newX,newY, hex.r );
}
}
}
}
function drawHex(x, y, r){
ctx.beginPath();
ctx.moveTo(x,y-r);
for (var i = 0; i<=6; i++) {
ctx.lineTo(x+Math.cos((i*PI2/6-PI2/4))*r,y+Math.sin((i*PI2/6-PI2/4))*r);
}
ctx.closePath();
ctx.stroke();
}
function drawCircle( circle ){
ctx.beginPath();
ctx.arc(circle.pos.x, circle.pos.y, circle.r, 0, 2 * Math.PI);
ctx.closePath();
ctx.stroke();
}
<canvas id="myCanvas" width="350" height="350" style="border:1px solid #d3d3d3;">
If all the points on the hexagon are within the circle, the hexagon is within the circle. I don't think there's a simpler way than doing the distance calculation.
I'm not sure how to select the optimal fill point, (but here's a js snippet proving that the middle isn't always it). It's possible that when you say "hexagon circle" you mean hexagon made out of hexagons, in which case the snippet proves nothing :)
I made the hexagon sides 2/11ths the radius of the circle and spaced them by 5% the side length.
var hex = {x:0, y:0, r:10};
var circle = {x:100, y:100, r:100};
var spacing = 1.05;
var SQRT_3 = Math.sqrt(3);
var hexagon_offsets = [
{x: 1/2, y: -SQRT_3 / 2},
{x: 1, y: 0},
{x: 1/2, y: SQRT_3 / 2},
{x: -1/2, y: SQRT_3 / 2},
{x: -1, y: 0},
{x: -1/2, y: -SQRT_3 / 2}
];
var bs = document.body.style;
var ds = document.documentElement.style;
bs.height = bs.width = ds.height = ds.width = "100%";
bs.border = bs.margin = bs.padding = 0;
var c = document.createElement("canvas");
c.style.display = "block";
c.addEventListener("mousemove", follow, false);
document.body.appendChild(c);
var ctx = c.getContext("2d");
window.addEventListener("resize", redraw);
redraw();
function follow(e) {
hex.x = e.clientX;
hex.y = e.clientY;
redraw();
}
function drawCircle() {
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.arc(circle.x, circle.y, circle.r, 0, 2 * Math.PI, true);
ctx.closePath();
ctx.stroke();
}
function is_in_circle(p) {
return Math.pow(p.x - circle.x, 2) + Math.pow(p.y - circle.y, 2) < Math.pow(circle.r, 2);
}
function drawLine(a, b) {
var within = is_in_circle(a) && is_in_circle(b);
ctx.strokeStyle = within ? "green": "red";
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.closePath();
ctx.stroke();
return within;
}
function drawShape(shape) {
var within = true;
for (var i = 0; i < shape.length; i++) {
within = drawLine(shape[i % shape.length], shape[(i + 1) % shape.length]) && within;
}
if (!within) return false;
ctx.fillStyle = "green";
ctx.beginPath();
ctx.moveTo(shape[0].x, shape[0].y);
for (var i = 1; i <= shape.length; i++) {
ctx.lineTo(shape[i % shape.length].x, shape[i % shape.length].y);
}
ctx.closePath();
ctx.fill();
return true;
}
function calculate_hexagon(x, y, r) {
return hexagon_offsets.map(function (offset) {
return {x: x + r * offset.x, y: y + r * offset.y};
})
}
function drawHexGrid() {
var hex_count = 0;
var grid_space = calculate_hexagon(0, 0, hex.r * spacing);
var y = hex.y;
var x = hex.x;
while (y > 0) {
y += grid_space[0].y * 3;
x += grid_space[0].x * 3;
}
while (y < c.height) {
x %= grid_space[1].x * 3;
while (x < c.width) {
var hexagon = calculate_hexagon(x, y, hex.r);
if (drawShape(hexagon)) hex_count++;
x += 3 * grid_space[1].x;
}
y += grid_space[3].y;
x += grid_space[3].x;
x += 2 * grid_space[1].x;
}
return hex_count;
}
function redraw() {
c.width = window.innerWidth;
c.height = window.innerHeight;
circle.x = c.width / 2;
circle.y = c.height / 2;
circle.r = Math.min(circle.x, circle.y) * 0.9;
hex.r = circle.r * (20 / 110);
ctx.clearRect(0, 0, c.width, c.height);
var hex_count = drawHexGrid();
drawCircle();
ctx.fillStyle = "rgb(0, 0, 50)";
ctx.font = "40px serif";
ctx.fillText(hex_count + " hexes within circle", 20, 40);
}
For the last step of this project, I want the growing circle to stop when it collides with another circle. The isOnCircle function already checks for this successfully when creating a new circle. However, when adding the condition !isOnCircle to my grow() function (line 61) it prevents any new circles from being added.
function grow() {
var a = circles[circles.length - 1];
if (!isOnCircle(a)){
a.radius += 1;
}}
Perhaps the circle is being created first, then in the check for collision, it's colliding with itself. Where else could I put the !isOnCircle check so that it gets checked at every radius increase and stops the grow function then?
check this
//set up canvas
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var circles = [];
//create circle
function create(location) {
circles.push({
x: location.x,
y: location.y,
radius: 10,
color: '#' + Math.floor(Math.random() * 16777215).toString(16)
});
}
//figure out mouse position
var rect = document.getElementById("canvas").getBoundingClientRect();
// Get canvas offset on page
var offset = {
x: rect.left,
y: rect.top
};
function isOnCanvas(a) {
if ((a.x >= 0 && a.x <= rect.width) && (a.y >= 0 && a.y <= rect.height)) {
return true;
}
return false;
}
function isOnCircle(a) {
var i = 0,
l = circles.length,
x, y, d, c;
for (; i < l; ++i) {
c = circles[i];
x = a.x - c.x;
y = a.y - c.y;
d = (a.radius || 10) + c.radius;
if (x * x + y * y <= d * d) {
return true;
}
}
return false;
}
// draw all circles
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < circles.length; i++) {
var p = circles[i];
ctx.beginPath();
ctx.arc(p.x, p.y, p.radius, 0, 2 * Math.PI);
ctx.fillStyle = p.color;
ctx.fill();
}
}
//make last drawn circle 1px bigger
function grow() {
var a = circles[circles.length - 1];
a.radius += 1;
}
//find percentage of canvas filled in
var totalSpace = canvas.width * canvas.height;
var totalFilled = function () {
total = 0;
for (var i = 0; i < circles.length; i++) {
var p = circles[i];
total += Math.PI * Math.pow(p.radius, 2);
}
return total;
console.log(total);
}
function findPercentage() {
return (totalFilled() / totalSpace) * 100;
}
function updateInfo() {
percentage = findPercentage();
document.getElementById("percentage").innerHTML = "You've filled in " + percentage.toFixed(1) + "%";
}
//do all the stuff
var animate = function () {
grow();
draw();
updateInfo();
}
//put this outside function so we can stop it later
var growLoop;
window.onmousedown = function (e) {
// get event location on page offset by canvas location
var location = {
x: e.pageX - offset.x,
y: e.pageY - offset.y
};
if (isOnCanvas(location) && !isOnCircle(location)) {
create(location);
draw();
updateInfo();
growLoop = setInterval(animate, 100);
}
};
window.onmouseup = function () {
clearInterval(growLoop);
}
window.onmouseout = function () {
clearInterval(growLoop);
}
it's colliding with itself.
Probably. You definitely should avoid that in the collision detection:
function isOnCircle(a) {
var l = circles.length,
x, y, d, c;
for (var i = 0; i < l; ++i) {
c = circles[i];
if (a == c) // add these
continue; // two lines!
x = a.x - c.x;
y = a.y - c.y;
d = (a.radius || 10) + c.radius;
if (x * x + y * y <= d * d) {
return true;
}
}
return false;
}
It is colliding with itself. Since you know the current circle will always be the last one in circles you can modify isOnCircle like this:
l = circles.length - 1,
so that it won't check against the current circle.
http://jsfiddle.net/SeAGU/91/