I am making a basketball physics simulator. I am am using a parametric equation to calculate the path of the ball. I am having a hard time with collision detection with the front of the rim, backboard, pole, and the court floor (bottom of canvas). Additionally I want to make the ball bounce when hitting these objects but I am having a hard time. Can anyone offer some help with this problem?
Here is the code snippet:
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.transform(1, 0, 0, -1, 0, canvas.height)
ctx.translate(canvas.width / 2, canvas.height / 2);
var speed = 5;
var gravity = 16;
var bounce = 10;
var mouseX = 0;
var mouseY = 0;
var stage = 1;
var x = 0;
var y = 0;
var xOrgn = 0;
var yOrgn = 0;
var xClk = 175;
var yClk = 100;
var mag = 0;
var ang = 0;
var xVel = 0;
var yVel = 0;
var time = 0;
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, 12, 0, Math.PI * 2);
ctx.fillStyle = "#FF8C00";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(x, y + 12);
ctx.lineTo(x, y - 12);
ctx.strokeStyle = 'black';
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(x - 12, y);
ctx.lineTo(x + 12, y);
ctx.strokeStyle = 'black';
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(x - 8, y - 8);
ctx.bezierCurveTo(x - 2, y - 4, x - 2, y + 4, x - 8, y + 8);
ctx.strokeStyle = 'black';
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(x + 8, y - 8);
ctx.bezierCurveTo(x + 2, y - 4, x + 2, y + 4, x + 8, y + 8);
ctx.strokeStyle = 'black';
ctx.stroke();
}
function drawHoop() {
ctx.beginPath();
ctx.rect(228, -160, 12, 172);
ctx.fillStyle = "#191919";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(222, -12, 6, 80);
ctx.fillStyle = "#666666";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(171, -6, 51, 6);
ctx.fillStyle = "#e50000";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(171, -3, 3, 0, Math.PI * 2);
ctx.fillStyle = "#e50000";
ctx.fill();
ctx.closePath();
}
function drawCursor() {
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(mouseX - 12, mouseY);
ctx.lineTo(mouseX + 12, mouseY);
ctx.strokeStyle = '#00cd00';
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(mouseX, mouseY - 12);
ctx.lineTo(mouseX, mouseY + 12);
ctx.strokeStyle = '#00cd00';
ctx.stroke();
ctx.closePath();
}
function calcVel() {
mag = Math.sqrt((Math.pow(xClk - xOrgn, 2) + Math.pow(yClk - yOrgn, 2)) / 4);
ang = Math.atan((yClk - yOrgn) / (xClk - xOrgn));
xVel = mag * Math.cos(ang);
yVel = mag * Math.sin(ang);
}
function draw() {
ctx.clearRect(-(canvas.width / 2), -(canvas.height / 2), canvas.width, canvas.height);
ctx.canvas.addEventListener('mousemove', function(event) {
mouseX = event.clientX - ctx.canvas.offsetLeft - canvas.width / 2;
mouseY = -event.clientY + ctx.canvas.offsetTop + canvas.height / 2;
});
drawBall();
drawHoop();
if (stage === 1) {
x = mouseX;
y = mouseY;
ctx.canvas.addEventListener('click', function(event) {
xOrgn = x;
yOrgn = y;
stage = 2;
});
} else if (stage === 2) {
drawCursor();
ctx.canvas.addEventListener('click', function(event) {
xClk = mouseX;
yclk = mouseY;
calcVel();
time = 0;
stage = 3;
});
} else if (stage === 3) {
x = xVel * time + xOrgn;
y = -gravity * Math.pow(time, 2) + yVel * time + yOrgn;
time = time + speed * 0.01;
}
}
setInterval(draw, 10);
canvas {
background: white;
}
<canvas id="myCanvas" width="480" height="320"></canvas>
Here is the code in jsfiddle: JSfiddle
You have to add collision detection in "Stage 3" only. Since the locations and sizes of everything are hardcoded, it's pretty simple to add in collision detection item by item. (but if you make this much more complicated, I would add in some variables or objects that make it easy to get the positions of things as variables, etc).
I added in a horizontal bounce when the ball hits the backboard, and a vertical bounce off the floor. Any others are similar.
(Note, what made these possible were updating x and y (and xVel, yVel) iteratively rather than from a determined equation. The results are the same, but the calculation and dynamic behaviour are easier).
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.transform(1, 0, 0, -1, 0, canvas.height)
ctx.translate(canvas.width / 2, canvas.height / 2);
var speed = 5;
var gravity = 16;
var bounce = 10;
var mouseX = 0;
var mouseY = 0;
var stage = 1;
var x = 0;
var y = 0;
var xOrgn = 0;
var yOrgn = 0;
var xClk = 175;
var yClk = 100;
var mag = 0;
var ang = 0;
var xVel = 0;
var yVel = 0;
var time = 0;
function drawBall() {
ctx.beginPath();
ctx.arc(x, y, 12, 0, Math.PI * 2);
ctx.fillStyle = "#FF8C00";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(x, y + 12);
ctx.lineTo(x, y - 12);
ctx.strokeStyle = 'black';
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(x - 12, y);
ctx.lineTo(x + 12, y);
ctx.strokeStyle = 'black';
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(x - 8, y - 8);
ctx.bezierCurveTo(x - 2, y - 4, x - 2, y + 4, x - 8, y + 8);
ctx.strokeStyle = 'black';
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(x + 8, y - 8);
ctx.bezierCurveTo(x + 2, y - 4, x + 2, y + 4, x + 8, y + 8);
ctx.strokeStyle = 'black';
ctx.stroke();
}
function drawHoop() {
ctx.beginPath();
ctx.rect(228, -160, 12, 172);
ctx.fillStyle = "#191919";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(222, -12, 6, 80);
ctx.fillStyle = "#666666";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.rect(171, -6, 51, 6);
ctx.fillStyle = "#e50000";
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(171, -3, 3, 0, Math.PI * 2);
ctx.fillStyle = "#e50000";
ctx.fill();
ctx.closePath();
}
function drawCursor() {
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(mouseX - 12, mouseY);
ctx.lineTo(mouseX + 12, mouseY);
ctx.strokeStyle = '#00cd00';
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = 2;
ctx.moveTo(mouseX, mouseY - 12);
ctx.lineTo(mouseX, mouseY + 12);
ctx.strokeStyle = '#00cd00';
ctx.stroke();
ctx.closePath();
}
function calcVel() {
mag = Math.sqrt((Math.pow(xClk - xOrgn, 2) + Math.pow(yClk - yOrgn, 2)) / 4);
ang = Math.atan((yClk - yOrgn) / (xClk - xOrgn));
xVel = mag * Math.cos(ang);
yVel = mag * Math.sin(ang);
}
function draw() {
ctx.clearRect(-(canvas.width / 2), -(canvas.height / 2), canvas.width, canvas.height);
ctx.canvas.addEventListener('mousemove', function(event) {
mouseX = event.clientX - ctx.canvas.offsetLeft - canvas.width / 2;
mouseY = -event.clientY + ctx.canvas.offsetTop + canvas.height / 2;
});
drawBall();
drawHoop();
if (stage === 1) {
x = mouseX;
y = mouseY;
ctx.canvas.addEventListener('click', function(event) {
xOrgn = x;
yOrgn = y;
stage = 2;
});
} else if (stage === 2) {
drawCursor();
ctx.canvas.addEventListener('click', function(event) {
xClk = mouseX;
yclk = mouseY;
calcVel();
time = 0;
stage = 3;
});
} else if (stage === 3) {
//x = xVel * time + xOrgn;
// update x from it's own value so we can vary the xVel.
x += xVel * speed * 0.01;
//y = -gravity * Math.pow(time, 2) + yVel * time + yOrgn;
// update yVel and y iteratively instead of this determined calculation
//y = -gravity * Math.pow(time, 2) + yVel * time + yOrgn;
yVel -= gravity * 0.1;
y += yVel * speed * 0.01;
// do a collision check: the backboard.
if (x > 222 - 12 && y > -12 && y < 62) {
xVel *= -1;
}
// with floor
if(y <= -142) {
y = -142;
yVel *= -1;
}
time = time + speed * 0.01;
}
}
setInterval(draw, 10);
canvas {
background: white;
}
<canvas id="myCanvas" width="480" height="320"></canvas>
You can handle any other collisions you want similarly
Related
I have the problem that my Donut Chart isn't working as I want it to do. I want to create a Donut Chart like this one:
But my Donut Chart looks like this at the moment:
As you can see the strokes don't overlap in the right direction. I think this might be, because I start to draw the strokes from right to left. Instead it should draw them from left to right, so the left "rounded end" is visible not the right rounded end.
This is what I have tried so far:
//function to draw the donut chart, ctx = context, cx - cy = position, radius and arcwith
dmbChart(ctx, cx, cy, radius, arcwidth) {
var tot = 0;
var accum = 0;
var PI = Math.PI;
var PI2 = PI * 2;
var offset = -PI/2;
for(var i = 0; i < this.canvasValues.length; i++) {
tot += this.canvasValues[i];
}
//Donut Sectors Color: Draw each stroke based on the value (canvasValues) and Color (canvasColors)
for(var i = 0; i < this.canvasValues.length; i++) {
ctx.lineWidth = arcwidth;
ctx.beginPath();
ctx.lineCap = "round";
ctx.arc(cx, cy, radius, offset + PI2 * (accum/tot), offset + PI2 * ((accum + this.canvasValues[i]) / tot));
ctx.strokeStyle = this.canvasColors[i];
ctx.stroke();
accum += this.canvasValues[i];
}
}
As you can see I get the values which are the percentages how long the each stroke should be and the color. Starting on top I draw each one from top -> right -> bottom -> left and this is the result. But how can I modify it to get the result on top?
Edit:
With the help of #Helder Sepulveda I created it like this now. I changed a lot of the calculations fixed some bugs which came with the changes. The only problem now is that it doesn't start to draw at the top. As you can see the green stroke should start on the top:
function dmbChart(ctx, cx, cy, radius, arcwidth) {
var canvasValues = [30, 5, 15, 10, 10, 10, 10, 10];
var canvasColors = ["#10dc60", "#DDDDDD", "#0cd1e8", "#ffce00", "#7044ff", "#f04141", "#ffea00", "#ee82ee"];
ctx.lineWidth = arcwidth;
ctx.lineCap = "round";
var accum = canvasValues.reduce((a,b) => a + b, 0);
for (var i = canvasValues.length-1; i >= 0; i--) {
var radians = canvasValues[i] / 100 * 360 * Math.PI / 180
ctx.beginPath();
ctx.arc(cx, cy, radius, accum, accum - radians, true);
ctx.strokeStyle = canvasColors[i];
ctx.stroke();
accum -= radians;
}
ctx.beginPath();
ctx.arc(cx, cy, radius, accum, accum - (0.1 / 100 * 360 * Math.PI / 180), true);
ctx.strokeStyle = canvasColors[canvasColors.length - 1];
ctx.stroke();
}
const canvas = document.getElementById("c");
canvas.width = canvas.height = 140;
const ctx = canvas.getContext("2d");
dmbChart(ctx, 70, 70, 50, 30)
<canvas id="c"></canvas>
I'm making some assumptions on canvasValues and canvasColors to show something working:
function dmbChart(ctx, cx, cy, radius, arcwidth) {
var accum = 0;
var PI = Math.PI;
var PI2 = PI * 2;
var offset = -PI / 2;
var canvasValues = [10, 10, 10]
var canvasColors = ["red", "green", "blue"]
var tot = canvasValues.reduce((a, b) => a + b, 0)
ctx.lineWidth = arcwidth;
ctx.lineCap = "round";
for (var i = 0; i < canvasValues.length; i++) {
ctx.beginPath();
ctx.arc(cx, cy, radius, offset + PI2 * (accum / tot), offset + PI2 * ((accum + canvasValues[i]) / tot));
ctx.strokeStyle = canvasColors[i];
ctx.stroke();
accum += canvasValues[i];
}
ctx.beginPath();
ctx.arc(cx, cy, radius, offset, offset);
ctx.strokeStyle = canvasColors[0];
ctx.stroke();
}
const canvas = document.getElementById("c");
canvas.width = canvas.height = 140;
const ctx = canvas.getContext("2d");
dmbChart(ctx, 70, 70, 50, 30)
<canvas id="c"></canvas>
The idea is to draw one last "short" arc with the first value(and color) at the end of the loop
I also moved a couple of lines out of the loop:
ctx.lineWidth = arcwidth;
ctx.lineCap = "round";
that could be set just once before the loop
And here is what we talked about in the comments inverting the direction
function dmbChart(ctx, cx, cy, radius, arcwidth) {
var PI = Math.PI;
var PI2 = PI * 2;
var offset = -PI / 2;
var canvasValues = [10, 10, 10]
var canvasColors = ["red", "green", "blue"]
var tot = canvasValues.reduce((a,b) => a + b, 0)
var accum = tot;
ctx.lineWidth = arcwidth;
ctx.lineCap = "round";
for (var i = canvasValues.length-1; i >= 0; i--) {
ctx.beginPath();
ctx.arc(cx, cy, radius, offset + PI2 * (accum / tot), offset + PI2 * ((accum + canvasValues[i]) / tot));
ctx.strokeStyle = canvasColors[i];
ctx.stroke();
accum -= canvasValues[i];
}
ctx.beginPath();
p = offset + PI2 * ((tot + canvasValues[canvasValues.length-1]) / tot)
ctx.arc(cx, cy, radius, p, p);
ctx.strokeStyle = canvasColors[canvasColors.length-1];
ctx.stroke();
}
const canvas = document.getElementById("c");
canvas.width = canvas.height = 140;
const ctx = canvas.getContext("2d");
dmbChart(ctx, 70, 70, 50, 30)
<canvas id="c"></canvas>
I want to display a windmill on js canvas. For now, I display a green line on a cube. I have a problem with rotating the line around it's middle point. Also, I don't want my cube to move. Save() doesn't seem to work? I don't know what I'm doing wrong. I tried looking the answer online but they don't seem to work or I don't understand them. Elements in my canvas somehow disappear.
var x = 600;
function init()
{
window.requestAnimationFrame(draw);
}
function draw()
{
var ctx = document.getElementById('canvas').getContext('2d');
ctx.clearRect(0, 0, 600, 600);
// sciana przednia
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.strokeRect(x/2,x/2,x/4,x/4);
//sciana gorna
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.moveTo(x/2,x/2);
ctx.lineTo(x-x/3,x/4+x/6);
ctx.lineTo(x-x/8,x/4+x/6);
ctx.lineTo(x/2+x/4,x/2);
ctx.closePath();
ctx.stroke();
//sciana prawa
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.moveTo(x/2+x/4,x/2+x/4);
ctx.lineTo(x-x/8,x/2+x/7);
ctx.lineTo(x-x/8,x/4+x/6);
ctx.stroke();
ctx.closePath();
//raczka
ctx.beginPath();
ctx.lineWidth = 5;
ctx.strokeStyle = "#808080";
ctx.moveTo(x/2+x/5,x/2-x/5);
ctx.lineTo(x/2+x/5,x/2-x/8+50);
ctx.stroke();
ctx.closePath();
ctx.save();
//smiglo
ctx.beginPath();
ctx.translate(x/2, x/2);
ctx.rotate( (Math.PI / 180) * 25);
ctx.translate(-x/2, -x/2);
ctx.fillStyle = "#00cc00";
ctx.fillRect(x/2+x/5-100,x/2-x/5,200,10);
ctx.closePath();
ctx.restore();
window.requestAnimationFrame(draw);
}
init();
var canvas = document.getElementById('canvas')
var ctx = canvas.getContext('2d');
var x = 600;
function init() {
window.requestAnimationFrame(draw);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// sciana przednia
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.strokeRect(x / 2, x / 2, x / 4, x / 4);
//sciana gorna
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.moveTo(x / 2, x / 2);
ctx.lineTo(x - x / 3, x / 4 + x / 6);
ctx.lineTo(x - x / 8, x / 4 + x / 6);
ctx.lineTo(x / 2 + x / 4, x / 2);
ctx.closePath();
ctx.stroke();
//sciana prawa
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "#000000";
ctx.moveTo(x / 2 + x / 4, x / 2 + x / 4);
ctx.lineTo(x - x / 8, x / 2 + x / 7);
ctx.lineTo(x - x / 8, x / 4 + x / 6);
ctx.stroke();
ctx.closePath();
//raczka
ctx.beginPath();
ctx.lineWidth = 5;
ctx.strokeStyle = "#808080";
ctx.moveTo(x / 2 + x / 5, x / 2 - x / 5);
ctx.lineTo(x / 2 + x / 5, x / 2 - x / 8 + 50);
ctx.stroke();
ctx.closePath();
ctx.save();
//smiglo
ctx.beginPath();
ctx.translate(x / 2, x / 2);
ctx.rotate((Math.PI / 180) * 25);
ctx.translate(-x / 2, -x / 2);
ctx.fillStyle = "#00cc00";
ctx.fillRect(x / 2 + x / 5 - 100, x / 2 - x / 5, 200, 10);
ctx.closePath();
ctx.restore();
window.requestAnimationFrame(draw);
}
init();
<canvas id="canvas" width=600 height=600></canvas>
Edit: I understand now how to rotate around a certain point. I still don't know how to rotate only the line not the whole thing.
Talking about the rotation, I think that you did well: the green line is rotated by 25 degrees in your example. You just need to rotate its middle point.
But to do so, I think it's better if you make other changes in your code: the part that draws the cube is difficult to handle, whenever you want to edit your code, this part will cause issues. I suggest to isolate it in a drawCube() function and to use proper (x,y) coordinates.
According to me, it should look like this:
function draw() {
angle += 5;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(Math.PI / 180 * angle);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
// It rotates
drawLine();
ctx.restore();
// It doesn't rotate
drawCube();
}
Then, your code will work. All you will have to do is to make the line rotate around its middle point, but you said that you know how to do it.
Good luck!
EDIT: I added a snippet with an working example, and with a cube like yours as well, may help.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var btnExample = document.getElementById('btnExample');
var btnCube = document.getElementById('btnCube');
var angle = 0;
var fps = 1000 / 60;
var propellerWidth = 100;
var propellerHeight = 10;
var towerWidth = 2;
var towerHeight = 100;
var mode = {
CUBE: 'CUBE',
EXAMPLE: 'EXAMPLE'
}
var currentMode = mode.EXAMPLE;
btnExample.onclick = function() {
currentMode = mode.EXAMPLE;
}
btnCube.onclick = function() {
currentMode = mode.CUBE;
}
setInterval(function() {
angle += 3;
draw(canvas, ctx, (canvas.width - propellerWidth) / 2, (canvas.height - propellerWidth) / 2, angle);
}, fps);
function draw(canvas, ctx, cx, cy, angle) {
var towerX = cx + propellerWidth / 2 - towerWidth / 2;
var towerY = cy + propellerWidth / 2;
var propellerX = (canvas.width - propellerWidth) / 2;
var propellerY = (canvas.height - propellerHeight) / 2;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw other things that don't rotate
if (currentMode === mode.EXAMPLE) {
drawHelp(canvas, ctx);
}
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.rotate(Math.PI / 180 * angle);
ctx.translate(-canvas.width / 2, -canvas.height / 2);
// Draw things that rotate
drawPropeller(ctx, propellerX, propellerY, propellerWidth, propellerHeight);
ctx.restore();
// Draw other things that don't rotate
if (currentMode === mode.EXAMPLE) {
drawTower(ctx, towerX, towerY, towerWidth, towerHeight);
} else if (currentMode === mode.CUBE) {
drawCube(ctx, towerX, towerY, 30);
}
}
function drawPropeller(ctx, propellerX, propellerY, propellerWidth, propellerHeight) {
ctx.fillStyle = 'black';
ctx.fillRect(propellerX, propellerY, propellerWidth, propellerHeight);
}
function drawTower(ctx, towerX, towerY, towerWidth, towerHeight) {
ctx.fillStyle = 'red';
ctx.fillRect(towerX, towerY, towerWidth, towerHeight);
}
function drawCube(ctx, x, y, size) {
ctx.strokeStyle = 'black';
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x, y + size);
ctx.closePath();
ctx.stroke();
var x1 = x - size + (size / 4) + (size / 16);
var y1 = y + (size * 1.25);
var x2 = x1 + (size / 3);
var y2 = y1 - (size * 2 / 3);
var x3 = x2 + size;
var y3 = y2;
var x4 = x3 - (size / 3);
var y4 = y1;
var x5 = x4;
var y5 = y4 + size;
var x6 = x3;
var y6 = y3 + size;
ctx.strokeRect(x1, y1, size, size);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x2, y2);
ctx.lineTo(x3, y3);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x3, y3);
ctx.lineTo(x4, y4);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x3, y3);
ctx.lineTo(x6, y6);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x5, y5);
ctx.lineTo(x6, y6);
ctx.closePath();
ctx.stroke();
}
function drawHelp(canvas, ctx) {
ctx.globalAlpha = 0.2;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(canvas.width, canvas.height);
ctx.moveTo(canvas.width, 0);
ctx.lineTo(0, canvas.height);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, propellerWidth / 2, 0, 2 * Math.PI);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, propellerWidth / 2, 0, 2 * Math.PI);
ctx.stroke();
ctx.closePath();
ctx.globalAlpha = 1;
}
canvas {
border: 1px solid black;
}
<canvas id="canvas" width=200 height=200></canvas>
<br>
<button id="btnExample">Example</button>
<button id="btnCube">Cube</button>
I am trying to draw a group of shapes using canvas.
I have referenced below SO threads:
Draw a parallel line
How to draw parallel line using three.js?
but not able to figure out how to calculate points for the rectangles parallel in as we stretch the line.
Any reference for stretching shapes with canvas is appreciated.
//Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//Variables
var canvasx = $(canvas).offset().left;
var canvasy = $(canvas).offset().top;
var last_mousex = last_mousey = 0;
var mousex = mousey = 0;
var mousedown = false;
// grid parameters
var gridSpacing = 20; // pixels
var gridWidth = 1;
//var gridColor = "#f1f1f1";
var gridColor = "lightgray";
/** */
var originX = 0;
/** */
var originY = 0;
drawGrid();
//Mousedown
$(canvas).on('mousedown', function(e) {
last_mousex = parseInt(e.clientX-canvasx);
last_mousey = parseInt(e.clientY-canvasy);
mousedown = true;
});
//Mouseup
$(canvas).on('mouseup', function(e) {
mousedown = false;
});
//Mousemove
$(canvas).on('mousemove', function(e) {
mousex = parseInt(e.clientX-canvasx);
mousey = parseInt(e.clientY-canvasy);
if(mousedown) {
ctx.clearRect(0,0,canvas.width,canvas.height); //clear canvas
drawGrid();
ctx.setLineDash([5, 15]);
ctx.beginPath();
ctx.moveTo(last_mousex,last_mousey);
ctx.lineTo(mousex,mousey);
//ctx.lineTo(mousex,mousey);
ctx.strokeStyle = 'blue';
ctx.lineDashOffset = 2;
ctx.lineWidth = 5;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
startx = last_mousex;
starty = last_mousey;
drawPolygon([last_mousex, mousex, mousex, last_mousex, last_mousex],
[last_mousey-10, mousey-10, mousey-60, last_mousey-60],true, 'gray', false, 'black', 2);
drawPolygon([last_mousex, mousex, mousex, last_mousex, last_mousex],
[last_mousey+10, mousey+10, mousey+60, last_mousey+60],true, 'gray', false, 'black', 2);
}
//Output
$('#output').html('current: '+mousex+', '+mousey+'<br/>last: '+last_mousex+', '+last_mousey+'<br/>mousedown: '+mousedown);
});
/** */
function drawLine(startX, startY, endX, endY, width, color) {
// width is an integer
// color is a hex string, i.e. #ff0000
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.lineWidth = width;
ctx.strokeStyle = color;
ctx.stroke();
}
function drawPolygon(xArr, yArr, fill, fillColor, stroke, strokeColor, strokeWidth) {
// fillColor is a hex string, i.e. #ff0000
fill = fill || false;
stroke = stroke || false;
ctx.beginPath();
ctx.moveTo(xArr[0], yArr[0]);
for (var i = 1; i < xArr.length; i++) {
ctx.lineTo(xArr[i], yArr[i]);
}
ctx.closePath();
if (fill) {
ctx.fillStyle = fillColor;
ctx.fill();
}
if (stroke) {
ctx.lineWidth = strokeWidth;
ctx.strokeStyle = strokeColor;
ctx.stroke();
}
//console.log(xArr);
//console.log(yArr);
}
/** returns n where -gridSize/2 < n <= gridSize/2 */
function calculateGridOffset(n) {
if (n >= 0) {
return (n + gridSpacing / 2.0) % gridSpacing - gridSpacing / 2.0;
} else {
return (n - gridSpacing / 2.0) % gridSpacing + gridSpacing / 2.0;
}
}
/** */
function drawGrid() {
var offsetX = calculateGridOffset(-originX);
var offsetY = calculateGridOffset(-originY);
var width = canvas.width;
var height = canvas.height;
for (var x = 0; x <= (width / gridSpacing); x++) {
drawLine(gridSpacing * x + offsetX, 0, gridSpacing * x + offsetX, height, gridWidth, gridColor);
}
for (var y = 0; y <= (height / gridSpacing); y++) {
drawLine(0, gridSpacing * y + offsetY, width, gridSpacing * y + offsetY, gridWidth, gridColor);
}
}
canvas {
cursor: crosshair;
border: 1px solid #000000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas" width="800" height="500"></canvas>
<div id="output"></div>
I'm guessing this is what you wanted.
Instead of trying to manually draw your line rotated, instead, move the origin of the canvas to the start of the line,
// save the canvas state
ctx.save();
// move origin to start of line
ctx.translate(last_mousex, last_mousey);
then rotate the origin so it points toward the end of the line in the positive X direction
// compute direction of line from start to end
const dx = mousex - last_mousex;
const dy = mousey - last_mousey;
const angle = Math.atan2(dy, dx);
// rotate to point to end of line
ctx.rotate(angle);
then compute the length of the line from the start to the end
// compute length of line
const length = Math.sqrt(dx * dx + dy * dy);
and just draw an arrow in the positive x direction of that length
ctx.setLineDash([5, 15]);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(length, 0);
ctx.strokeStyle = 'blue';
ctx.lineDashOffset = 2;
ctx.lineWidth = 5;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
drawPolygon([0, length, length, 0, 0],
[-10, -10, -60, -60],true, 'gray', false, 'black', 2);
drawPolygon([0, length, length, 0, 0],
[+10, +10, +60, +60],true, 'gray', false, 'black', 2);
// restore the canvas state
ctx.restore();
while we're at it your code for calculating the mouse position didn't work if the page is scrolled. This will get the mouse position relative to the pixels in the canvas.
const rect = canvas.getBoundingClientRect();
mousex = (e.clientX - rect.left) * canvas.width / canvas.clientWidth;
mousey = (e.clientY - rect.top ) * canvas.height / canvas.clientHeight;
//Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//Variables
var last_mousex = last_mousey = 0;
var mousex = mousey = 0;
var mousedown = false;
// grid parameters
var gridSpacing = 20; // pixels
var gridWidth = 1;
//var gridColor = "#f1f1f1";
var gridColor = "lightgray";
/** */
var originX = 0;
/** */
var originY = 0;
drawGrid();
//Mousedown
$(canvas).on('mousedown', function(e) {
const rect = canvas.getBoundingClientRect();
last_mousex = (e.clientX - rect.left) * canvas.width / canvas.clientWidth;
last_mousey = (e.clientY - rect.top ) * canvas.height / canvas.clientHeight;
mousedown = true;
});
//Mouseup
$(canvas).on('mouseup', function(e) {
mousedown = false;
});
//Mousemove
$(canvas).on('mousemove', function(e) {
const rect = canvas.getBoundingClientRect();
mousex = (e.clientX - rect.left) * canvas.width / canvas.clientWidth;
mousey = (e.clientY - rect.top ) * canvas.height / canvas.clientHeight;
if(mousedown) {
ctx.clearRect(0,0,canvas.width,canvas.height); //clear canvas
drawGrid();
// save the canvas state
ctx.save();
// move origin to start of line
ctx.translate(last_mousex, last_mousey);
// compute direction of line from start to end
const dx = mousex - last_mousex;
const dy = mousey - last_mousey;
const angle = Math.atan2(dy, dx);
// rotate to point to end of line
ctx.rotate(angle);
// compute length of line
const length = Math.sqrt(dx * dx + dy * dy);
ctx.setLineDash([5, 15]);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(length, 0);
ctx.strokeStyle = 'blue';
ctx.lineDashOffset = 2;
ctx.lineWidth = 5;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
drawPolygon([0, length, length, 0, 0],
[-10, -10, -60, -60],true, 'gray', false, 'black', 2);
drawPolygon([0, length, length, 0, 0],
[+10, +10, +60, +60],true, 'gray', false, 'black', 2);
// restore the canvas state
ctx.restore();
}
//Output
$('#output').html('current: '+mousex+', '+mousey+'<br/>last: '+last_mousex+', '+last_mousey+'<br/>mousedown: '+mousedown);
});
/** */
function drawLine(startX, startY, endX, endY, width, color) {
// width is an integer
// color is a hex string, i.e. #ff0000
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.lineWidth = width;
ctx.strokeStyle = color;
ctx.stroke();
}
function drawPolygon(xArr, yArr, fill, fillColor, stroke, strokeColor, strokeWidth) {
// fillColor is a hex string, i.e. #ff0000
fill = fill || false;
stroke = stroke || false;
ctx.beginPath();
ctx.moveTo(xArr[0], yArr[0]);
for (var i = 1; i < xArr.length; i++) {
ctx.lineTo(xArr[i], yArr[i]);
}
ctx.closePath();
if (fill) {
ctx.fillStyle = fillColor;
ctx.fill();
}
if (stroke) {
ctx.lineWidth = strokeWidth;
ctx.strokeStyle = strokeColor;
ctx.stroke();
}
//console.log(xArr);
//console.log(yArr);
}
/** returns n where -gridSize/2 < n <= gridSize/2 */
function calculateGridOffset(n) {
if (n >= 0) {
return (n + gridSpacing / 2.0) % gridSpacing - gridSpacing / 2.0;
} else {
return (n - gridSpacing / 2.0) % gridSpacing + gridSpacing / 2.0;
}
}
/** */
function drawGrid() {
var offsetX = calculateGridOffset(-originX);
var offsetY = calculateGridOffset(-originY);
var width = canvas.width;
var height = canvas.height;
for (var x = 0; x <= (width / gridSpacing); x++) {
drawLine(gridSpacing * x + offsetX, 0, gridSpacing * x + offsetX, height, gridWidth, gridColor);
}
for (var y = 0; y <= (height / gridSpacing); y++) {
drawLine(0, gridSpacing * y + offsetY, width, gridSpacing * y + offsetY, gridWidth, gridColor);
}
}
canvas {
cursor: crosshair;
border: 1px solid #000000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas" width="800" height="500"></canvas>
<div id="output"></div>
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>
I am creating a canvas dynamically and then drawing a circle.
However, as shown, the circle appears stretched and offset:
var max = 50;
var canvas = document.createElement('canvas');
canvas.id = "mazecanvas";
var size = (document.documentElement.clientWidth / 100) * max;
canvas.style.width = size + "px";
canvas.style.height = size + "px";
canvas.style.position = "absolute";
canvas.style.left = size / 2 + "px";
canvas.style.top = (document.documentElement.clientHeight / 2) - (size / 2) + "px";
document.body.appendChild(canvas);
var x, y;
var ctx = canvas.getContext('2d');
function circle() {
ctx.globalCompositeOperation = "destination-in";
ctx.beginPath();
console.log(x, y);
ctx.arc(x, y, 10, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.fill();
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas.addEventListener('mousemove', function(evt) {
var mousePos = getMousePos(canvas, evt);
x = mousePos.x;
y = mousePos.y;
}, false);
function draw() {
ctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < max; i++) {
for (var j = 0; j < max; j++) {
ctx.fillStyle = 'rgb(' + Math.floor(255 - 5.5 * i) + ',' + Math.floor(255 - 5.5 * j) + ',0)';
ctx.fillRect(j * (canvas.width / max), i * (canvas.height / max), canvas.width / max, canvas.height / max);
}
}
circle();
setTimeout(draw, 10);
}
draw();
I do not understand what I am doing wrong, I know it is to do with the canvas creation as when I remove it, and replace it with a static one, the problem is gone:
var max = 50;
var canvas = document.getElementById('canvas');
var x, y;
var ctx = canvas.getContext('2d');
function circle() {
ctx.globalCompositeOperation = "destination-in";
ctx.beginPath();
console.log(x, y);
ctx.arc(x, y, 10, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.fill();
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas.addEventListener('mousemove', function(evt) {
var mousePos = getMousePos(canvas, evt);
x = mousePos.x;
y = mousePos.y;
}, false);
function draw() {
ctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < max; i++) {
for (var j = 0; j < max; j++) {
ctx.fillStyle = 'rgb(' + Math.floor(255 - 5.5 * i) + ',' + Math.floor(255 - 5.5 * j) + ',0)';
ctx.fillRect(j * (canvas.width / max), i * (canvas.height / max), canvas.width / max, canvas.height / max);
}
}
circle();
setTimeout(draw, 10);
}
draw();
<canvas id="canvas"></canvas>
Setting the canvas's CSS style will stretch and squish the pixels. That's why your circle becomes an oval.
Instead, set the canvas element's width and height. This actually adds (or removes) pixels to the canvas to become the specified size. This will keep your circle circular. ;-)
canvas.width=500; // no need to add "px"
canvas.height=400;