Related
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>
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>
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
I would like to draw a spring in a HTML5 canvas, and show if that spring is at its rest length or not.
My spring is attached to a rectangular shape to some X-Y coordinates and defined as follows:
function Spring(restLenght, width, numRounds){
this.x1 = 0;
this.y1 = 0;
this.x2 = 0;
this.y2 = 0;
this.restLenght = restLenght;
this.width = width;
this.numRounds = numRounds;
this.color = "green";
this.lineWidth = 6;
}
The parameters are explained in the picture below:
When the spring is at its rest length, the lines shall be parallel to each other, otherwise this means the spring is stretched or compressed. Then it will be immediately clear what state the spring is.
I'm stuck now with the bezierCurveTo() Method:
Here is my Fiddle: https://jsfiddle.net/df3mm8kz/1/
var cv = document.getElementById('cv'),
ctx = cv.getContext('2d'),
mouse = capture(cv),
box = new Box(120, 80, 0, 16),
spring = new Spring(160, 20, 2, 0.03, 0.9),
vx = 0,
vy = 0;
function Spring(restLenght, width, numRounds, k, f){
this.x1 = 0;
this.y1 = 0;
this.x2 = 0;
this.y2 = 0;
this.restLenght = restLenght;
this.width = width;
this.numRounds = numRounds;
this.k = k;
this.f = f;
this.color = "green";
this.lineWidth = 6;
}
Spring.prototype.draw = function(ctx) {
var sPX, sPY, cP1X, cP1Y, cP2X, cP2Y, ePX, ePY;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.color;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.moveTo(this.x1, this.y1);
// length of one spring's round
var l = this.restLenght/(this.numRounds + 2);
// Initial segment, from spring anchor point to the first round
sPX = this.x1+l; sPY = this.y2;
ctx.lineTo(sPX, sPY);
// half width of spring's rounds
var hw = 0.5*this.width;
// half length of one spring's round
var hl = 0.5*l;
for(var i=0, n=this.numRounds; i<n; i++) {
cP1X = sPX + hl*i; cP1Y = sPY + hw;
cP2X = sPX + l*i; cp2Y = sPY + hw;
ePX = sPX + l*i; ePY = sPY;
ctx.bezierCurveTo(cP1X,cP1Y,cP2X,cp2Y,ePX,ePY);
cP1X = sPX + hl*i; cP1Y = sPY - hw;
cP2X = sPX + l*i; cp2Y = sPY - hw;
ePX = sPX + l*i; ePY = sPY;
ctx.bezierCurveTo(cP1X,cP1Y,cP2X,cp2Y,ePX,ePY);
}
// Final segment, from last springs round to the center of mass
ctx.lineTo(this.x2, this.y2);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
};
function Box(w, h, mx, my) {
this.x = 0;
this.y = 0;
this.w = w;
this.h = h;
this.mx = mx;
this.my = my;
this.vx = 0;
this.vy = 0;
this.rotation = 0;
this.color = "red";
this.lineWidth = 1;
}
Box.prototype.draw = function(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = "black";
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.rect(-0.5*this.w, -0.5*this.h, this.w, this.h);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "yellow";
ctx.fillStyle = "yellow";
ctx.arc(this.mx, 0.5*this.h-this.my, 6, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.closePath();
ctx.fill();
ctx.restore();
};
window.requestAnimFrame = (
function(callback) {
return window.setTimeout(callback, 1000/30);
});
(function drawFrame() {
window.requestAnimFrame(drawFrame, cv);
ctx.clearRect(0, 0, cv.width, cv.height);
var dx = box.x - mouse.x,
dy = box.y - mouse.y,
angle = Math.atan2(dy, dx),
boxAngle = angle + 0.5*Math.PI,
targetX = mouse.x + Math.cos(angle) * spring.restLenght,
targetY = mouse.y + Math.sin(angle) * spring.restLenght;
vx += (targetX - box.x) * spring.k;
vy += (targetY - box.y) * spring.k;
vx *= spring.f;
vy *= spring.f;
box.rotation = boxAngle;
box.x += vx;
box.y += vy;
box.draw(ctx);
spring.x1 = mouse.x;
spring.y1 = mouse.y;
spring.x2 = box.x;
spring.y2 = box.y;
spring.draw(ctx);
}());
function capture(element) {
var mouse = {
x: 0,
y: 0,
event: null
},
body_scrollLeft = document.body.scrollLeft,
element_scrollLeft = document.documentElement.scrollLeft,
body_scrollTop = document.body.scrollTop,
element_scrollTop = document.documentElement.scrollTop,
offsetLeft = element.offsetLeft,
offsetTop = element.offsetTop;
element.addEventListener('mousemove', function(event) {
var x, y;
if (event.pageX || event.pageY) {
x = event.pageX;
y = event.pageY;
} else {
x = event.clientX + body_scrollLeft + element_scrollLeft;
y = event.clientY + body_scrollTop + element_scrollTop;
}
x -= offsetLeft;
y -= offsetTop;
mouse.x = x;
mouse.y = y;
mouse.event = event;
}, false);
return mouse;
}
<canvas id="cv" width="600" height="400"></canvas>
Drawing a spring
Rather than use bezier curves which do not actually fit the curve of a spring (but close) I just use a simple path and use trig functions to draw each winding. the function has a start x1,y1 and end x2, y2, windings (should be an integer), width of spring, the offset (bits at ends), Dark colour, and light colour, and the stroke width (width of the wire).
The demo draws an extra highlight to give the spring a little more depth. It can easily be removed.
The code came from this answer that has a simpler version of the same function
function drawSpring(x1, y1, x2, y2, windings, width, offset, col1, col2, lineWidth){
var x = x2 - x1;
var y = y2 - y1;
var dist = Math.sqrt(x * x + y * y);
var nx = x / dist;
var ny = y / dist;
ctx.strokeStyle = col1
ctx.lineWidth = lineWidth;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.beginPath();
ctx.moveTo(x1,y1);
x1 += nx * offset;
y1 += ny * offset;
x2 -= nx * offset;
y2 -= ny * offset;
var x = x2 - x1;
var y = y2 - y1;
var step = 1 / (windings);
for(var i = 0; i <= 1-step; i += step){ // for each winding
for(var j = 0; j < 1; j += 0.05){
var xx = x1 + x * (i + j * step);
var yy = y1 + y * (i + j * step);
xx -= Math.sin(j * Math.PI * 2) * ny * width;
yy += Math.sin(j * Math.PI * 2) * nx * width;
ctx.lineTo(xx,yy);
}
}
ctx.lineTo(x2, y2);
ctx.lineTo(x2 + nx * offset, y2 + ny * offset)
ctx.stroke();
ctx.strokeStyle = col2
ctx.lineWidth = lineWidth - 4;
var step = 1 / (windings);
ctx.beginPath();
ctx.moveTo(x1 - nx * offset, y1 - ny * offset);
ctx.lineTo(x1, y1);
ctx.moveTo(x2, y2);
ctx.lineTo(x2 + nx * offset, y2 + ny * offset)
for(var i = 0; i <= 1-step; i += step){ // for each winding
for(var j = 0.25; j <= 0.76; j += 0.05){
var xx = x1 + x * (i + j * step);
var yy = y1 + y * (i + j * step);
xx -= Math.sin(j * Math.PI * 2) * ny * width;
yy += Math.sin(j * Math.PI * 2) * nx * width;
if(j === 0.25){
ctx.moveTo(xx,yy);
}else{
ctx.lineTo(xx,yy);
}
}
}
ctx.stroke();
}
function display() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
ctx.lineWidth = 8;
drawSpring(canvas.width / 2,10, mouse.x,mouse.y,8,100,40,"green","#0C0",15);
}
// Boiler plate code from here down and not part of the answer
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true;
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var createCanvas, resizeCanvas, setGlobals, resizeCount = 0;
createCanvas = function () {
var c,
cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === undefined) {
canvas = createCanvas();
}
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onResize === "function") {
if(firstRun){
onResize();
firstRun = false;
}else{
resizeCount += 1;
setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
}
}
}
function debounceResize() {
resizeCount -= 1;
if (resizeCount <= 0) {
onResize();
}
}
setGlobals = function () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) {
e.preventDefault();
}
var mouse = {
x : 0,
y : 0,
w : 0,
alt : false,
shift : false,
ctrl : false,
buttonRaw : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
crashRecover : null,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left;
m.y = e.pageY - m.bounds.top;
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") {
m.buttonRaw |= m.bm[e.which - 1];
} else if (t === "mouseup") {
m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") {
m.buttonRaw = 0;
m.over = false;
} else if (t === "mouseover") {
m.over = true;
} else if (t === "mousewheel") {
m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") {
m.w = -e.detail;
}
if (m.callbacks) {
m.callbacks.forEach(c => c(e));
}
if ((m.buttonRaw & 2) && m.crashRecover !== null) {
if (typeof m.crashRecover === "function") {
setTimeout(m.crashRecover, 0);
}
}
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === undefined) {
m.callbacks = [callback];
} else {
m.callbacks.push(callback);
}
}
}
m.start = function (element) {
if (m.element !== undefined) {
m.removeMouse();
}
m.element = element === undefined ? document : element;
m.mouseEvents.forEach(n => {
m.element.addEventListener(n, mouseMove);
});
m.element.addEventListener("contextmenu", preventDefault, false);
m.active = true;
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => {
m.element.removeEventListener(n, mouseMove);
});
m.element.removeEventListener("contextmenu", preventDefault);
m.element = m.callbacks = undefined;
m.active = false;
}
}
return mouse;
})();
// Clean up. Used where the IDE is on the same page.
var done = function () {
window.removeEventListener("resize", resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = undefined;
}
function update(timer) { // Main update loop
if(ctx === undefined){
return;
}
globalTime = timer;
display(); // call demo code
if (!(mouse.buttonRaw & 2)) {
requestAnimationFrame(update);
} else {
done();
}
}
setTimeout(function(){
resizeCanvas();
mouse.start(canvas, true);
mouse.crashRecover = done;
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();
/** SimpleFullCanvasMouse.js end **/
To make drawing easier, use .translate() and .rotate() to move into an aligned coordinate system.
ctx.translate(this.x1, this.y1);
ctx.rotate(Math.atan2(this.y2 - this.y1, this.x2 - this.x1));
You can then draw the spring along the local x-axis, and it will appear in the correct place and rotation.
Your spacing of the segments were wrong. hl*i is half the distance from the spring's starting point, not the segment's starting point.
var cv = document.getElementById('cv'),
ctx = cv.getContext('2d'),
mouse = capture(cv),
box = new Box(120, 80, 0, 16),
spring = new Spring(160, 50, 2, 0.03, 0.9),
vx = 0,
vy = 0;
function Spring(restLenght, width, numRounds, k, f) {
this.x1 = 0;
this.y1 = 0;
this.x2 = 0;
this.y2 = 0;
this.restLenght = restLenght;
this.width = width;
this.numRounds = numRounds;
this.k = k;
this.f = f;
this.color = "green";
this.lineWidth = 6;
}
Spring.prototype.draw = function(ctx) {
var sPX, sPY, cP1X, cP1Y, cP2X, cP2Y, ePX, ePY;
ctx.save();
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.color;
ctx.fillStyle = this.color;
var vx = this.x2 - this.x1;
var vy = this.y2 - this.y1;
var vm = Math.sqrt(vx * vx + vy * vy);
ctx.translate(this.x1, this.y1);
ctx.rotate(Math.atan2(vy, vx));
ctx.beginPath();
ctx.moveTo(0, 0);
// length of one spring's round
var l = vm / (this.numRounds + 2);
// Initial segment, from spring anchor point to the first round
sPX = l;
sPY = 0;
ctx.lineTo(sPX, sPY);
// half width of spring's rounds
var hw = 0.5 * this.width;
for (var i = 0, n = this.numRounds; i < n; i++) {
cP1X = sPX + l * (i + 0.0);
cP1Y = sPY + hw;
cP2X = sPX + l * (i + 0.5);
cp2Y = sPY + hw;
ePX = sPX + l * (i + 0.5);
ePY = sPY;
ctx.bezierCurveTo(cP1X, cP1Y, cP2X, cp2Y, ePX, ePY);
cP1X = sPX + l * (i + 0.5);
cP1Y = sPY - hw;
cP2X = sPX + l * (i + 1.0);
cp2Y = sPY - hw;
ePX = sPX + l * (i + 1.0);
ePY = sPY;
ctx.bezierCurveTo(cP1X, cP1Y, cP2X, cp2Y, ePX, ePY);
}
// Final segment, from last springs round to the center of mass
ctx.lineTo(vm, 0);
//ctx.closePath();
//ctx.fill();
ctx.stroke();
ctx.restore();
};
function Box(w, h, mx, my) {
this.x = 0;
this.y = 0;
this.w = w;
this.h = h;
this.mx = mx;
this.my = my;
this.vx = 0;
this.vy = 0;
this.rotation = 0;
this.color = "red";
this.lineWidth = 1;
}
Box.prototype.draw = function(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = "black";
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.rect(-0.5 * this.w, -0.5 * this.h, this.w, this.h);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = "yellow";
ctx.fillStyle = "yellow";
ctx.arc(this.mx, 0.5 * this.h - this.my, 6, 0, 2 * Math.PI, false);
ctx.stroke();
ctx.closePath();
ctx.fill();
ctx.restore();
};
window.requestAnimFrame = (
function(callback) {
return window.setTimeout(callback, 1000 / 30);
});
(function drawFrame() {
window.requestAnimFrame(drawFrame, cv);
ctx.clearRect(0, 0, cv.width, cv.height);
var dx = box.x - mouse.x,
dy = box.y - mouse.y,
angle = Math.atan2(dy, dx),
boxAngle = angle + 0.5 * Math.PI,
targetX = mouse.x + Math.cos(angle) * spring.restLenght,
targetY = mouse.y + Math.sin(angle) * spring.restLenght;
vx += (targetX - box.x) * spring.k;
vy += (targetY - box.y) * spring.k;
vx *= spring.f;
vy *= spring.f;
box.rotation = boxAngle;
box.x += vx;
box.y += vy;
box.draw(ctx);
spring.x1 = mouse.x;
spring.y1 = mouse.y;
spring.x2 = box.x;
spring.y2 = box.y;
spring.draw(ctx);
}());
function capture(element) {
var mouse = {
x: 0,
y: 0,
event: null
},
body_scrollLeft = document.body.scrollLeft,
element_scrollLeft = document.documentElement.scrollLeft,
body_scrollTop = document.body.scrollTop,
element_scrollTop = document.documentElement.scrollTop,
offsetLeft = element.offsetLeft,
offsetTop = element.offsetTop;
element.addEventListener('mousemove', function(event) {
var x, y;
if (event.pageX || event.pageY) {
x = event.pageX;
y = event.pageY;
} else {
x = event.clientX + body_scrollLeft + element_scrollLeft;
y = event.clientY + body_scrollTop + element_scrollTop;
}
x -= offsetLeft;
y -= offsetTop;
mouse.x = x;
mouse.y = y;
mouse.event = event;
}, false);
return mouse;
}
<canvas id="cv" width="600" height="400"></canvas>
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);
}