Circle moving around the edge of the canvas - javascript

I need a circle moving around the edge of the canvas. Moving right then down is working properly, but when it needs to go left it's jumping to the bottom-right and starts moving right again and again. I don't exactly know how to fix that.
var can = document.getElementById('C4');
var ctx = can.getContext('2d');
var x = 5, y = 20;
ctx.fillStyle = "black";
ctx.fillRect(700, 100, 100, 100);
function draw() {
ctx.beginPath();
ctx.arc(x, y, 20, 0, 2 * Math.PI);
ctx.fillStyle = 'rgba(250,0,0,0.4)';
ctx.fill();
do {//moving right
x += 2;
} while (x <! 281);
if (x >= 280){//down
do {
x = 280;
y += 2;
} while (y <! 130);
}
if(y >= 130 && x >= 280){//left
do {
x = x - 2;
y = 130;
} while (x >! 20);
}
if (x <= 20) {//up
do {
x = 20;
y = y-2;
} while (y <! 20);
}
ctx.fillStyle = "rgba(34,45,23,0.4)";
ctx.fillRect(0, 0, can.width, can.height);
requestAnimationFrame(draw);
}
draw();
canvas { border: 1px solid black}
<canvas id="C4"></canvas>

Since you're using a recursion you don't need the do while, the loops are just making the circle jump from one edge to another. You can achive your goal with if conditions, like this:
var can = document.getElementById('C4');
var ctx = can.getContext('2d');
var x = 5, y = 20;
ctx.fillStyle = "black";
ctx.fillRect(700, 100, 100, 100);
function draw() {
ctx.beginPath();
ctx.arc(x, y, 20, 0, 2 * Math.PI);
ctx.fillStyle = 'rgba(250,0,0,0.4)';
ctx.fill();
if (x < 280 && y == 20) {
x += 2;
}
if (x >= 280 && y < 130){//down
x = 280;
y += 2;
}
if(y >= 130 && x > 20){//left
x = x - 2;
y = 130;
}
if (x == 20 && y > 20) {//up
x = 20;
y = y-2;
}
ctx.fillStyle = "rgba(34,45,23,0.4)";
ctx.fillRect(0, 0, can.width, can.height);
requestAnimationFrame(draw);
}
draw();
canvas { border: 1px solid black}
<canvas id="C4"></canvas>

Isolate the ball as an object.
Use the balls direction to check for change in direction.
Clear the canvas first line inside the draw function rather than create a path at the end of the draw function.
Example.
var ctx = canvas.getContext('2d');
const radius = 20;
const speed = 2;
const ball = {
style: "rgba(250,0,0,0.4)",
radius: radius,
pos: {x: radius, y: radius},
vel: {x: speed, y: 0},
step() {
const w = ctx.canvas.width, h = ctx.canvas.height, r = ball.radius;
var x = ball.pos.x, y = ball.pos.y
if(ball.vel.x === speed && x >= w - r ) {
ball.vel.x = 0;
ball.vel.y = speed;
ball.pos.x = w - r;
} else if(ball.vel.y === speed && y >= h - r) {
ball.vel.x = -speed;
ball.vel.y = 0;
ball.pos.y = h - r;
} else if(ball.vel.x === -speed && x <= r) {
ball.vel.x = 0;
ball.vel.y = -speed;
ball.pos.x = r;
} else if(ball.vel.y === -speed && y <= r) {
ball.vel.x = speed;
ball.vel.y = 0;
ball.pos.y = r;
}
ball.pos.x += ball.vel.x;
ball.pos.y += ball.vel.y;
},
draw() {
ctx.fillStyle = ball.style;
ctx.beginPath();
ctx.arc(ball.pos.x, ball.pos.y, ball.radius, 0, 2 * Math.PI);
ctx.fill();
},
}
function draw() {
ctx.fillStyle = "rgba(34,45,23,0.4)";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ball.step();
ball.draw();
requestAnimationFrame(draw);
}
draw();
canvas { border: 1px solid black}
<canvas id="canvas"></canvas>

Related

How do I represent exact coordinates if the Canvas size and each pixel are different sizes?

I have configured Canvas like this to represent some kind of graph with Canvas tag.
<canvas id="canvas"></canvas>
The width and height of Canvas are 650, 300.
Also, I used transform to start the coordinate position with (0, 0) from the bottom left.
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
canvas.width = 650;
canvas.height = 300;
let maxXCoord = 250;
let xCoordRate = canvas.width / maxXCoord;
context.strokeStyle = 'rgba(255, 255, 255, 0.2)';
context.transform(-1, 0, 0, 1, 650, 0);
// Draw canvas grid line for x
for(let x = 25 * xCoordRate; x < canvas.width; x += 25 * xCoordRate) {
context.beginPath();
context.lineWidth = 1;
context.setLineDash([2, 4]);
context.moveTo(x, 0);
context.lineTo(x, canvas.height);
context.stroke();
};
// Draw canvas grid line for y
for(let y = 50; y < canvas.height; y += 50) {
if(y === 150) {
context.beginPath();
context.lineWidth = 1;
context.setLineDash([]);
context.moveTo(0, y);
context.lineTo(canvas.width - 1, y);
context.stroke();
} else {
context.beginPath();
context.lineWidth = 1;
context.setLineDash([2, 4]);
context.moveTo(0, y);
context.lineTo(canvas.width, y);
context.stroke();
}
}
Result
Through the code above, the actual size of the Canvas is 650 and 300, but when I draw a line for some coordinate value, I want to change the coordinate value as if it were up to 250 and 60.
If the following coordinate values are received, how should the coordinates converted?
point = [
{
x : 0.00000,
y : 0.00000
},
{
x : 303.655197,
y : 101.073993
},
{
x : 306.341,
y : 130.2344
},
{
x : 101.3495,
y : 91.96
}
...
]
If the following code is executed, the result is strange as shown below.
for(let index = 0; index < array.length; index++) {
context.beginPath();
context.lineWidth = 2;
context.setLineDash([]);
context.strokeStyle = 'green';
context.moveTo(array[index].point[0].x, array[index].point[0].y);
for(let _index = 0; _index < array[index].point.length; _index++) {
context.lineTo(array[index].point[_index].x * coordRate, array[index].point[_index].y * coordRate + (canvas.height / 2));
}
context.stroke();
}
Result
I get a feeling that you are over complicating things with the transform.
If all you need is to start the coordinate position with (0, 0) from the bottom left you can do that with a simple subtraction of the height.
Here is an example:
const canvas = document.getElementById('c');
const context = canvas.getContext('2d');
canvas.width = canvas.height = 160;
point = [
{ x: 0, y: 0 },
{ x: 40, y: 101 },
{ x: 80, y: 130 },
{ x: 120, y: 91 }
]
//(0, 0) from the bottom left
context.lineWidth = 2;
context.strokeStyle = 'green';
context.moveTo(point[0].x, canvas.height - point[0].y);
for (let index = 0; index < point.length; index++) {
context.lineTo(point[index].x, canvas.height - point[index].y)
}
context.stroke();
//(0, 0) from the top left
context.beginPath()
context.lineWidth = 1;
context.strokeStyle = 'red';
context.moveTo(point[0].x, point[0].y);
for (let index = 0; index < point.length; index++) {
context.lineTo(point[index].x, point[index].y)
}
context.stroke();
<canvas id="c"></canvas>

How to draw polygon when last point clicked?

I want to complete polygon when starting point of polygon clicked, i think i need to hide or set opacity of last line which is linking to first point of line.
goal is to achieve exactly what is happening in this GIF.
also i want to show angles and length on each line while drawing just like exactly same as GIF.
var bw = window.innerWidth -20;
var bh = window.innerHeight -20;
var p = 10;
var cw = bw + (p*2) + 1;
var ch = bh + (p*2) + 1;
var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var context = canvas.getContext("2d");
function drawBoard(){
for (var x = 0; x <= bw; x += 30) {
context.moveTo(0.5 + x + p, p);
context.lineTo(0.5 + x + p, bh + p);
}
for (var x = 0; x <= bh; x += 30) {
context.moveTo(p, 0.5 + x + p);
context.lineTo(bw + p, 0.5 + x + p);
}
context.lineWidth = 0.5;
context.strokeStyle = 'rgba(0, 122, 204,0.7)';
context.stroke();
}
drawBoard();
//---------------------------------------------
requestAnimationFrame(update)
mouse = {x : 0, y : 0, button : 0, lx : 0, ly : 0, update : true};
function mouseEvents(e){
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
mouse.update = true;
}
["mousedown","mouseup","mousemove"].forEach(name => document.addEventListener(name,mouseEvents));
context.lineWidth = 2;
context.strokeStyle = "red";
const point = (x,y) => ({x,y});
const poly = () => ({
points : [],
addPoint(p){ this.points.push(point(p.x,p.y)) },
draw() {
context.lineWidth = 2;
context.strokeStyle = "red";
context.fillStyle = 'rgba(255, 0, 0, 0.3)';
context.beginPath();
for (const p of this.points) { context.lineTo(p.x,p.y) }
context.closePath();
context.stroke();
context.fill();
context.fillStyle = 'rgba(0, 0, 0, 0.3)';
context.strokeStyle = 'rgba(0, 0, 0, 0.8)';
for (const p of this.points) {
context.beginPath();
context.moveTo(p.x + 10,p.y);
context.arc(p.x,p.y,10,0,Math.PI *2);
context.fill();
context.stroke();
}
},
closest(pos, dist = 8) {
var i = 0, index = -1;
dist *= dist;
for (const p of this.points) {
var x = pos.x - p.x;
var y = pos.y - p.y;
var d2 = x * x + y * y;
if (d2 < dist) {
dist = d2;
index = i;
}
i++;
}
if (index > -1) { return this.points[index] }
}
});
function drawCircle(pos,color="black",size=8){
context.strokeStyle = color;
context.beginPath();
context.arc(pos.x,pos.y,size,0,Math.PI *2);
context.stroke();
}
const polygon = poly();
var activePoint,cursor;
var dragging= false;
function update(){
if (mouse.update) {
cursor = "crosshair";
context.clearRect(0,0,canvas.width,canvas.height);
drawBoard();
if (!dragging) { activePoint = polygon.closest(mouse) }
if (activePoint === undefined && mouse.button) {
polygon.addPoint(mouse);
mouse.button = false;
} else if(activePoint) {
if (mouse.button) {
if(dragging) {
activePoint.x += mouse.x - mouse.lx;
activePoint.y += mouse.y - mouse.ly;
} else { dragging = true }
} else { dragging = false }
}
polygon.draw();
if (activePoint) {
drawCircle(activePoint);
cursor = "move";
}
mouse.lx = mouse.x;
mouse.ly = mouse.y;
canvas.style.cursor = cursor;
mouse.update = false;
}
requestAnimationFrame(update)
}
<html>
<body style=" background: lightblue;">
<canvas id="canvas" style="background: #fff; magrin:20px;"></canvas>
</body>
</html>
Drawing part of code i copied from: https://stackoverflow.com/a/53437943/3877726
You can use ctx.setTransform to to align text to a line.
First normalize the vector between the end points and use that normalized vector to build the transform. See example.
To prevent text from reading back to front you need to check the x component of the normalized vector. If it is < 0 then reverse the vector.
Almost the same for the angle. See example.
Example
Snippets contain the functions drawLineText and drawAngleText (near top) that implement the additional features.
var bw = innerWidth - 20, bh = innerHeight - 20;
var cw = bw + (p * 2) + 1, ch = bh + (p * 2) + 1;
var p = 10;
var activePoint, cursor, dragging = false;
const mouse = {x: 0, y: 0, button: 0, lx: 0, ly: 0, update: true};
const TEXT_OFFSET = 5;
const TEXT_COLOR = "#000";
const TEXT_SIZE = 16;
const FONT = "arial";
const TEXT_ANGLE_OFFSET = 25;
const DEG = "°";
canvas.width = bw;
canvas.height = bh;
var ctx = canvas.getContext("2d");
function drawLineText(p1, p2, text, textOffset = TEXT_OFFSET, textColor = TEXT_COLOR, textSize = TEXT_SIZE, font = FONT) {
var x = p1.x, y = p1.y;
var nx = p2.x - x, ny = p2.y - y, len = (nx * nx + ny * ny) ** 0.5;
nx /= len;
ny /= len;
ctx.font = textSize + "px " + font;
ctx.textAlign = "center";
ctx.fillStyle = textColor;
if (nx < 0) {
ctx.textBaseline = "top";
x = p2.x;
y = p2.y;
nx = -nx;
ny = -ny;
textOffset = -textOffset;
} else { ctx.textBaseline = "bottom" }
len /= 2;
ctx.setTransform(nx, ny, -ny, nx, x, y);
ctx.fillText(text, len, -textOffset);
}
// angle between p2-p1 and p2-p3
function drawAngleText(p1, p2, p3, textAngleOffset = TEXT_ANGLE_OFFSET, textColor = TEXT_COLOR, textSize = TEXT_SIZE, font = FONT) {
var ang;
var x = p2.x, y = p2.y;
var nx1 = p1.x - x, ny1 = p1.y - y, len1 = (nx1 * nx1 + ny1 * ny1) ** 0.5;
var nx2 = p3.x - x, ny2 = p3.y - y, len2 = (nx2 * nx2 + ny2 * ny2) ** 0.5;
nx1 /= len1;
ny1 /= len1;
nx2 /= len2;
ny2 /= len2;
const cross = nx1 * ny2 - ny1 * nx2;
const dot = nx1 * nx2 + ny1 * ny2;
if (dot < 0) {
ang = cross < 0 ? -Math.PI - Math.asin(cross) : Math.PI - Math.asin(cross);
} else {
ang = Math.asin(cross);
}
const angDeg = Math.abs(ang * (180 / Math.PI)).toFixed(0) + DEG;
ctx.font = textSize + "px " + font;
ctx.fillStyle = textColor;
ctx.textBaseline = "middle";
const centerAngle = Math.atan2(ny1, nx1) + ang / 2;
const nx = Math.cos(centerAngle);
const ny = Math.sin(centerAngle);
if (nx < 0) {
ctx.textAlign = "right";
ctx.setTransform(-nx, -ny, ny, -nx, x, y);
textAngleOffset = -textAngleOffset;
} else {
ctx.textAlign = "left";
ctx.setTransform(nx, ny, -ny, nx, x, y);
}
ctx.fillText(angDeg, textAngleOffset, 0);
}
//---------------------------------------------
requestAnimationFrame(update)
function mouseEvents(e) {
const bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
mouse.update = true;
}
["mousedown", "mouseup", "mousemove"].forEach(name => document.addEventListener(name, mouseEvents));
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
const point = (x, y) => ({x, y});
const poly = () => ({
points: [],
closed: false,
addPoint(p) { this.points.push(point(p.x, p.y)) },
draw() {
ctx.lineWidth = 2;
ctx.strokeStyle = "red";
ctx.fillStyle = 'rgba(255, 0, 0, 0.3)';
ctx.beginPath();
for (const p of this.points) { ctx.lineTo(p.x, p.y) }
this.closed && ctx.closePath();
ctx.stroke();
this.closed && ctx.fill();
ctx.fillStyle = 'rgba(0, 0, 0, 0.3)';
ctx.strokeStyle = 'rgba(0, 0, 0, 0.8)';
for (const p of this.points) {
ctx.beginPath();
ctx.moveTo(p.x + 10, p.y);
ctx.arc(p.x, p.y, 10, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
}
this.points.length > 1 && this.drawLengthText();
this.points.length > 2 && this.drawAngleText();
},
drawLengthText() {
const len = this.points.length;
var p1, i = 0;
p1 = this.points[i];
while (i < len -(this.closed ? 0 : 1)) {
const p2 = this.points[((i++) + 1) % len];
const lineLength = ((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2) ** 0.5
drawLineText(p1, p2, lineLength.toFixed(0) + "px");
if (len < 3) { break }
p1 = p2;
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
},
drawAngleText() {
const len = this.points.length;
var p1, p2, i = this.closed ? 0 : 1;
p1 = this.points[(i + len - 1) % len];
p2 = this.points[i];
while (i < len -(this.closed ? 0 : 1)) {
const p3 = this.points[((i++) + 1) % len];
drawAngleText(p1, p2, p3);
p1 = p2;
p2 = p3;
}
ctx.setTransform(1, 0, 0, 1, 0, 0);
},
closest(pos, dist = 8) {
var i = 0,
index = -1;
dist *= dist;
for (const p of this.points) {
var x = pos.x - p.x;
var y = pos.y - p.y;
var d2 = x * x + y * y;
if (d2 < dist) {
dist = d2;
index = i;
}
i++;
}
if (index > -1) { return this.points[index] }
}
});
const polygon = poly();
function drawCircle(pos, color = "black", size = 8) {
ctx.strokeStyle = color;
ctx.beginPath();
ctx.arc(pos.x, pos.y, size, 0, Math.PI * 2);
ctx.stroke();
}
function update() {
if (mouse.update) {
cursor = "crosshair";
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (!dragging) { activePoint = polygon.closest(mouse) }
if (activePoint === undefined && mouse.button) {
polygon.addPoint(mouse);
mouse.button = false;
} else if (activePoint) {
if (mouse.button) {
if (dragging) {
activePoint.x += mouse.x - mouse.lx;
activePoint.y += mouse.y - mouse.ly;
} else {
if (!polygon.closed && polygon.points.length > 2 && activePoint === polygon.points[0]) {
polygon.closed = true;
}
dragging = true
}
} else { dragging = false }
}
polygon.draw();
if (activePoint) {
drawCircle(activePoint);
cursor = "move";
}
mouse.lx = mouse.x;
mouse.ly = mouse.y;
canvas.style.cursor = cursor;
mouse.update = false;
}
requestAnimationFrame(update)
}
Click to add points
<canvas id="canvas" style="background: #fff; magrin:20px;"></canvas>
Note though not mandatory it is customarily considered polite to include attributions when copying code.

Canvas - separate 2 arrays of lines onclick

Button Left - pushes blue lines with X's
Button Right - pushes red lines with O's
Button Delete - removes last line in array
So, when I click on Left - blue lines with X's are pushed into an array.
Problem is... if I click on Right and start adding red lines, the blue lines that are already drawn on canvas also change in red lines with O's.
How can I separate the 2 buttons from interfering with each other ?
Thank you.
function drawGrid() {
ctx.strokeStyle = "black";
ctx.lineWidth = 0.1;
ctx.beginPath();
for (x = 15; x <= w; x += 60) {
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
for (y = 20; y <= h; y += 20) {
ctx.moveTo(0, y);
ctx.lineTo(w, y);
}
}
ctx.stroke();
}
var ry = [[]];
var canvas = document.querySelector("#myCanvas");
var w = (canvas.width = 450);
var h = (canvas.height = 280);
var ctx = canvas.getContext("2d");
drawGrid();
// Right Button <--------------------------------------------------
document.getElementById('right').onclick = function () {
ry.push([]);
myCanvas.addEventListener("click", e => {
var offset = canvas.getBoundingClientRect();
var x = e.clientX - offset.left;
var y = e.clientY - offset.top;
ry[ry.length - 1].push({ x: x, y: y });
ctx.clearRect(0, 0, w, h);
drawGrid();
drawChart();
});
deletes.addEventListener("click", e => {
if (ry[ry.length - 1].length > 0) {
ry[ry.length - 1].pop();
} else {
ry.pop();
ry[ry.length - 1].pop();
}
ctx.clearRect(0, 0, w, h);
drawGrid();
drawChart();
});
function drawGrid() {
ctx.strokeStyle = "black";
ctx.lineWidth = 0.1;
ctx.beginPath();
for (x = 15; x <= w; x += 60) {
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
for (y = 20; y <= h; y += 20) {
ctx.moveTo(0, y);
ctx.lineTo(w, y);
}
}
ctx.stroke();
}
function drawChart() {
ctx.lineWidth = 1;
for (let index = 0; index < ry.length; index++) {
for (let i = 0; i < ry[index].length; i++) {
let l = ry[index][i];
drawCircle(l.x, l.y);
if (i > 0) {
let last = ry[index][i - 1];
ctx.beginPath();
ctx.moveTo(last.x, last.y);
ctx.lineTo(l.x, l.y);
ctx.strokeStyle = "red";
ctx.stroke();
}
}
}
}
function drawCircle(x, y) {
ctx.beginPath();
ctx.arc(x, y, 6, 0, Math.PI * 2);
ctx.strokeStyle = "red";
ctx.stroke();
}
};
// Left Button <--------------------------------------------------
document.getElementById('left').onclick = function () {
ry.push([]);
myCanvas.addEventListener("click", e => {
var offset = canvas.getBoundingClientRect();
var x = e.clientX - offset.left;
var y = e.clientY - offset.top;
ry[ry.length - 1].push({ x: x, y: y });
ctx.clearRect(0, 0, w, h);
drawGrid();
drawChart();
});
function drawGrid() {
ctx.strokeStyle = "black";
ctx.lineWidth = 0.1;
ctx.beginPath();
for (x = 15; x <= w; x += 60) {
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
for (y = 20; y <= h; y += 20) {
ctx.moveTo(0, y);
ctx.lineTo(w, y);
}
}
ctx.stroke();
}
function drawChart() {
ctx.lineWidth = 1;
for (let index = 0; index < ry.length; index++) {
for (let i = 0; i < ry[index].length; i++) {
let l = ry[index][i];
drawCross(l.x, l.y);
if (i > 0) {
let last = ry[index][i - 1];
ctx.beginPath();
ctx.moveTo(last.x, last.y);
ctx.lineTo(l.x, l.y);
ctx.strokeStyle = "blue";
ctx.stroke();
}
}
}
}
function drawCross(x, y) {
ctx.beginPath();
ctx.moveTo(x - 6, y - 6);
ctx.lineTo(x + 6, y + 6);
ctx.moveTo(x + 6, y - 6);
ctx.lineTo(x - 6, y + 6);
ctx.strokeStyle = "blue";
ctx.stroke();
}
};
A nice thing to know is that you can call a function as a window method. For example if you have a function test() you can call this function as window["test"]()
In your case inside the function drawChart() you can call either drawCircle() or drawCross()
When I push the points into the points array, besides the x and the y I add a f (for function). The value of f is a string with the name of the function: drawCircle or drawCross
Inside the function drawChart you'll find this line of code: window[l.f](l.x, l.y) This is calling either drawCircle() or drawCross() depending on the value of l.f: the name of the function.
The name of the function is a global variable declared at the beginning of my code and is set to drawCircle: let theFunction = "drawCircle";
You change the value of theFunction when you click the right or left buttons.
//this is an array of arrays
//when I click on the canvas a new point is pushed on the last array of this array
var ry = [[]];
var canvas = document.querySelector("#myCanvas");
let w = (canvas.width = 450);
let h = (canvas.height = 280);
var ctx = canvas.getContext("2d");
let theFunction = "drawCircle";
drawGrid();
function drawCircle(x, y) {
ctx.beginPath();
ctx.arc(x, y, 6, 0, Math.PI * 2);
ctx.strokeStyle = "red";
ctx.stroke();
}
function drawCross(x, y) {
ctx.beginPath();
ctx.moveTo(x - 6, y - 6);
ctx.lineTo(x + 6, y + 6);
ctx.moveTo(x + 6, y - 6);
ctx.lineTo(x - 6, y + 6);
ctx.strokeStyle = "blue";
ctx.stroke();
}
myCanvas.addEventListener("click", e => {
var offset = canvas.getBoundingClientRect();
var x = e.clientX - offset.left;
var y = e.clientY - offset.top;
//a new point is pushed on the last array of ry
ry[ry.length - 1].push({ x: x, y: y, f:theFunction });
// delete everything
ctx.clearRect(0, 0, w, h);
// draw everything
drawGrid();
drawChart();
});
sterge.addEventListener("click", e => {
//when sterge is clicked the last point from the last array is deleted
if (ry[ry.length - 1].length > 0) {
ry[ry.length - 1].pop();
} else {
//if the last array is empty I delete this array
ry.pop();
//and then I delete the last point from the last array
ry[ry.length - 1].pop();
}
// delete everything
ctx.clearRect(0, 0, w, h);
// draw everything
drawGrid();
drawChart();
});
dreapta.addEventListener("click", e => {
theFunction = "drawCircle"
//when dreapta is clicked, a new array is pushed into the ry
ry.push([]);
});
stanga.addEventListener("click", e => {
theFunction = "drawCross"
//when dreapta is clicked, a new array is pushed into the ry
ry.push([]);
});
function drawGrid() {
ctx.strokeStyle = "black";
ctx.lineWidth = 0.1;
ctx.beginPath();
for (x = 15; x <= w; x += 60) {
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
for (y = 20; y <= h; y += 20) {
ctx.moveTo(0, y);
ctx.lineTo(w, y);
}
}
ctx.stroke();
}
function drawChart() {
ctx.lineWidth = 1;
// for every array in the ry array
for (let index = 0; index < ry.length; index++) {
// for every point in the ry[index]
for (let i = 0; i < ry[index].length; i++) {
let l = ry[index][i];
// draw the circle or the cross
window[l.f](l.x, l.y)
// draw the line
if (i > 0) {
let last = ry[index][i - 1];
ctx.beginPath();
ctx.moveTo(last.x, last.y);
ctx.lineTo(l.x, l.y);
ctx.strokeStyle = "blue";
ctx.stroke();
}
}
}
}
<canvas id="myCanvas"></canvas>
<p><input type="button" value="dreapta" id="dreapta" />
<input type="button" value="stanga" id="stanga" />
<input type="button" value="sterge" id="sterge" />
</p>

How to run multiple instances of a single object JavaScript

Currently attempting to make a physics simulation for elastic collisions of circles. I am having an issue where I do not know how to run the simulation with two circles interacting at the same time. I am not yet looking to create the interaction between the circles just to have them both running simultaneously. Any help is much appreciated. This is my first post so I apologize if I formatted something incorrectly.
var width = 400;
var height = 400;
var canvas = ctx = false;
var frameRate = 1 / 60; // Seconds
var frameDelay = frameRate * 1000; // ms
var loopTimer = false;
var ball = {
position: {
x: width / 2,
y: height / 2
},
velocity: {
x: 0,
y: 0
},
radius: 15, // 1px = 1cm
restitution: -1
};
var mouse = {
x: 0,
y: 0,
isDown: false
};
function getMousePosition(event) {
mouse.x = event.pageX - canvas.offsetLeft;
mouse.y = event.pageY - canvas.offsetTop;
}
var mouseDown = function(event) {
if (event.which == 1) {
getMousePosition(event);
mouse.isDown = true;
ball.position.x = mouse.x;
ball.position.y = mouse.y;
}
}
var mouseUp = function(event) {
if (event.which == 1) {
mouse.isDown = false;
ball.velocity.y = (ball.position.y - mouse.y) / 10;
ball.velocity.x = (ball.position.x - mouse.x) / 10;
}
}
var setup = function() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
canvas.onmousemove = getMousePosition;
canvas.onmousedown = mouseDown;
canvas.onmouseup = mouseUp;
ctx.fillStyle = 'blue';
ctx.strokeStyle = '#000000';
loopTimer = setInterval(loop, frameDelay);
}
var loop = function() {
if (!mouse.isDown) {
ball.position.x += ball.velocity.x * frameRate * 100;
ball.position.y += ball.velocity.y * frameRate * 100;
}
if (ball.position.y > height - ball.radius) {
ball.velocity.y *= ball.restitution;
ball.position.y = height - ball.radius;
}
if (ball.position.x > width - ball.radius) {
ball.velocity.x *= ball.restitution;
ball.position.x = width - ball.radius;
}
if (ball.position.x < ball.radius) {
ball.velocity.x *= ball.restitution;
ball.position.x = ball.radius;
}
if (ball.position.y < ball.radius) {
ball.velocity.y *= ball.restitution;
ball.position.y = ball.radius;
}
ctx.clearRect(0, 0, width, height);
ctx.save();
ctx.translate(ball.position.x, ball.position.y);
ctx.beginPath();
ctx.arc(0, 0, ball.radius, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
ctx.restore();
if (mouse.isDown) {
ctx.beginPath();
ctx.moveTo(ball.position.x, ball.position.y);
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
ctx.closePath();
}
}
setup();
#canvas {
border: solid 1px #ccc;
}
<canvas id="canvas"></canvas>
Here is how I would do it:
Instead of making the ball a kind of static object I made a constructor function (More about that here).
Then I made a ball array to store all the balls.
To make the dragging possible I store a seperate ball, which is not being moved by "physics" in the newBall variable. This ball is either invisible or is the ball currently being dragged.
In mouseDown() the newBall gets positioned under the cursor.
In mouseUp() it gets it's velocity and gets added to the array of animated balls. Also a new newBall gets created.
In loop() I loop two times through the array of animated balls. Once for the physics, once for the painting.
(Normally you would use two different methods with different tickRates to make animation more smooth, because physics calculation doesn't need to happen 60 times per second.
var width = 400;
var height = 400;
var canvas = ctx = false;
var frameRate = 1 / 60; // Seconds
var frameDelay = frameRate * 1000; // ms
var loopTimer = false;
function ball() {
this.position = {
x: width / 2,
y: height / 2
};
this.velocity = {
x: 0,
y: 0
};
this.radius = 15; // 1px = 1cm
this.restitution = -1
};
var balls = [];
var newBall = new ball();
var mouse = {
x: 0,
y: 0,
isDown: false
};
function getMousePosition(event) {
mouse.x = event.pageX - canvas.offsetLeft;
mouse.y = event.pageY - canvas.offsetTop;
}
var mouseDown = function(event) {
if (event.which == 1) {
getMousePosition(event);
mouse.isDown = true;
newBall.position.x = mouse.x;
newBall.position.y = mouse.y;
}
}
var mouseUp = function(event) {
if (event.which == 1) {
mouse.isDown = false;
newBall.velocity.y = (newBall.position.y - mouse.y) / 10;
newBall.velocity.x = (newBall.position.x - mouse.x) / 10;
balls.push(newBall);
newBall = new ball();
}
}
var setup = function() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
canvas.onmousemove = getMousePosition;
canvas.onmousedown = mouseDown;
canvas.onmouseup = mouseUp;
ctx.fillStyle = 'blue';
ctx.strokeStyle = '#000000';
loopTimer = setInterval(loop, frameDelay);
}
var loop = function() {
for (var ball of balls) {
ball.position.x += ball.velocity.x * frameRate * 100;
ball.position.y += ball.velocity.y * frameRate * 100;
if (ball.position.y > height - ball.radius) {
ball.velocity.y *= ball.restitution;
ball.position.y = height - ball.radius;
}
if (ball.position.x > width - ball.radius) {
ball.velocity.x *= ball.restitution;
ball.position.x = width - ball.radius;
}
if (ball.position.x < ball.radius) {
ball.velocity.x *= ball.restitution;
ball.position.x = ball.radius;
}
if (ball.position.y < ball.radius) {
ball.velocity.y *= ball.restitution;
ball.position.y = ball.radius;
}
}
ctx.clearRect(0, 0, width, height);
for (var ball of balls) {
ctx.save();
ctx.translate(ball.position.x, ball.position.y);
ctx.beginPath();
ctx.arc(0, 0, ball.radius, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
ctx.restore();
}
ctx.save();
ctx.translate(newBall.position.x, newBall.position.y);
ctx.beginPath();
ctx.arc(0, 0, newBall.radius, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
ctx.restore();
if (mouse.isDown) {
ctx.beginPath();
ctx.moveTo(newBall.position.x, newBall.position.y);
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
ctx.closePath();
}
}
setup();
#canvas {
border: solid 1px #ccc;
}
<canvas id="canvas"></canvas>
Now to get a bit more complex:
I added tickDelay and tickTimer to use them in a tickLoop
The ball constructor now has two methods:
show() draws the ball on the canvas
tick() does the pysics stuff (dt= deltaTime: time since last tick)
newBall is now null if the mouse isn't pressed
setup() initializes the width and height according to the <canvas> elements real size
tick() loops through the balls and calls .tick() tickDelay is in milliseconds so it gets divided by 1000
drawFrame() is your former loop() and does the drawing stuff
var width = 400;
var height = 400;
var canvas = ctx = false;
var frameRate = 1 / 60; // Seconds
var frameDelay = frameRate * 1000; // ms
var tickDelay = frameDelay * 2; //ticks 2 times slower than frames
var frameTimer;
var tickTimer;
function ball() {
this.position = {
x: width / 2,
y: height / 2
};
this.velocity = {
x: 0,
y: 0
};
this.radius = 15; // 1px = 1cm
this.restitution = -.99;
this.show = function() {
ctx.save();
ctx.translate(this.position.x, this.position.y);
ctx.beginPath();
ctx.arc(0, 0, this.radius, 0, Math.PI * 2, true);
ctx.fill();
ctx.closePath();
ctx.restore();
};
this.tick = function(dt) {
this.position.x += this.velocity.x * dt;
this.position.y += this.velocity.y * dt;
if (this.position.y > height - this.radius) {
this.velocity.y *= this.restitution;
this.position.y = height - this.radius;
}
if (this.position.x > width - this.radius) {
this.velocity.x *= this.restitution;
this.position.x = width - this.radius;
}
if (this.position.x < this.radius) {
this.velocity.x *= this.restitution;
this.position.x = this.radius;
}
if (this.position.y < this.radius) {
this.velocity.y *= this.restitution;
this.position.y = this.radius;
}
}
};
var balls = [];
var newBall;
var mouse = {
x: 0,
y: 0,
isDown: false
};
function getMousePosition(event) {
mouse.x = event.pageX - canvas.offsetLeft;
mouse.y = event.pageY - canvas.offsetTop;
}
function mouseDown(event) {
if (event.which == 1) {
getMousePosition(event);
mouse.isDown = true;
if (!newBall) newBall = new ball();
newBall.position.x = mouse.x;
newBall.position.y = mouse.y;
}
}
function mouseUp(event) {
if (event.which == 1) {
mouse.isDown = false;
newBall.velocity.y = (newBall.position.y - mouse.y);
newBall.velocity.x = (newBall.position.x - mouse.x);
balls.push(newBall);
newBall = null;
}
}
function setup() {
canvas = document.getElementById("canvas");
width = canvas.getBoundingClientRect().width;
height = canvas.getBoundingClientRect().height;
ctx = canvas.getContext("2d");
canvas.onmousemove = getMousePosition;
canvas.onmousedown = mouseDown;
canvas.onmouseup = mouseUp;
ctx.fillStyle = 'blue';
ctx.strokeStyle = '#000000';
requestAnimationFrame(drawFrame);
frameTimer = setInterval(drawFrame, frameDelay);
tickTimer = setInterval(tick, tickDelay);
}
function tick() {
for (var ball of balls) ball.tick(tickDelay * .001);
}
function drawFrame() {
ctx.clearRect(0, 0, width, height);
for (var ball of balls) ball.show();
if (newBall) newBall.show(ctx);
if (mouse.isDown && newBall) {
ctx.beginPath();
ctx.moveTo(newBall.position.x, newBall.position.y);
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
ctx.closePath();
}
}
setup();
#canvas {
border: solid 1px #ccc;
}
<canvas id="canvas"></canvas>
A really simple way would to do exactly the same as you do now, but not initiate all functions as a variable. Change all the variables that are functions to just functions, and where you call them. At least the variable called ball. Then after that you could make two variables like this
ball1 = new ball();
ball2 = new ball();
Your script is kind of messy so hard for me to say if this will go through without any errors, but if it does, I am more than happy to help. This is not the very best solution if you only go for the way i presented now so please do not use this as you solution, but more as a way to get started. Also you will not really learn anything of it if we just gave you the answer
Edit:
Another thing to mark is that using setInterval for games and graphical projects may be a bad idea since JavaScript is single threaded. A better solution is using requestAnimationFrame()
It would look something like this
function mainLoop() {
update();
draw();
requestAnimationFrame(mainLoop);
}
// Start things off
requestAnimationFrame(mainLoop);

filling circle with hexagons

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);
}

Categories