I'm currently learning about the html5 canvas, and I was making a program that rotates a triangle to face wherever the mouse is. it partially works but skips half of it, and i was wondering if there was a better way to do it and also what is wrong with my current code
thanks!
const ctx = document.getElementById("canvas").getContext("2d")
ctx.canvas.style.backgroundColor = "#303030"
ctx.canvas.width = 500
ctx.canvas.height = 500
const w = ctx.canvas.width
const h = ctx.canvas.height
let x = 0;
let y = 0;
let degrees = 0;
triAngle = 60
ctx.canvas.addEventListener("mousemove", mouseMove, false)
function mouseMove(evt) {
x = evt.clientX
y = evt.clientY
let diffX = x - w / 2;
let diffY = y - h / 2;
console.log(diffX, diffY)
degrees = Math.floor(Math.atan(diffY / diffX) * 57.2958);
//Math.atan(diffY/ diffX)
console.log(degrees)
}
function draw() {
debugger ;
ctx.clearRect(0, 0, w, h)
ctx.fillStyle = "#fff";
ctx.save()
ctx.translate(w / 2, h / 2)
ctx.rotate(degree(degrees + triAngle / 2))
ctx.beginPath()
ctx.moveTo(0, 0)
ctx.lineTo(0, 100)
ctx.rotate(degree(triAngle))
ctx.lineTo(0, 100)
ctx.closePath()
ctx.fill()
ctx.restore()
requestAnimationFrame(draw)
}
function degree(input) {
return Math.PI / 180 * input
}
draw()
https://jsfiddle.net/tus5nxpb/
Math.atan2
The reason that Math.atan skips half the directions is because of the sign of the fraction. The circle has 4 quadrants, the lines from {x: 0, y: 0} to {x: 1, y: 1}, {x: -1, y: 1}, {x: -1, y: -1} and {x: 1, y: -1} result in only two values (1, and -1) if you divide y by x eg 1/1 === 1, 1/-1 === -1, -1/1 === -1, and -1/-1 === 1 thus there is no way to know which one of the 2 quadrants each value 1 and -1 is in.
You can use Math.atan2 to get the angle from a point to another point in radians. In the range -Math.PI to Math.PI (-180deg to 180deg)
BTW there is no need to convert radians to deg as all math functions in JavaScript use radians
requestAnimationFrame(mainLoop);
const ctx = canvas.getContext("2d")
canvas.height = canvas.width = 300;
canvas.style.backgroundColor = "#303030";
const mouse = {x: 0, y: 0};
canvas.addEventListener("mousemove", e => {
mouse.x = e.clientX;
mouse.y = e.clientY;
});
const shape = {
color: "lime",
x: 150,
y: 150,
size: 50,
path: [1, 0, -0.5, 0.7, -0.5, -0.7],
};
function draw(shape) {
var i = 0;
const s = shape.size, p = shape.path;
ctx.fillStyle = shape.color;
const rot = Math.atan2(mouse.y - shape.y, mouse.x - shape.x);
const xa = Math.cos(rot);
const ya = Math.sin(rot);
ctx.setTransform(xa, ya, -ya, xa, shape.x, shape.y);
ctx.beginPath();
while (i < p.length) { ctx.lineTo(p[i++] * s, p[i++] * s) }
ctx.fill();
}
function mainLoop() {
ctx.setTransform(1, 0, 0, 1, 0, 0); // set default transform
ctx.clearRect(0, 0, canvas.width, canvas.height);
draw(shape);
requestAnimationFrame(mainLoop);
}
body { margin: 0px; }
canvas { position: absolute; top: 0px; left: 0px; }
<canvas id="canvas"></canvas>
Related
To get straight to the point, what I hope to achieve is to be able to create a connecting line between two elements with this shape:
DBDIAGRAM.IO
When the elements move, the line resets but always maintains a 90 degree angle, instead of being a straight or diagonal line between [x,y] to [x,y].
Is there some kind of algorithm for this? Maybe a grid with some kind of A* implementation?
I don't know how to make rounded corners easy, but the easiest example will be this:
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
// define the points
const p1 = {
x: 30,
y: 50
}
const p2 = {
x: 150,
y: 130
}
ctx.strokeStyle = 'red'
// draw the points
ctx.beginPath()
ctx.arc(p1.x, p1.y, 5, 0, Math.PI * 2)
ctx.stroke()
ctx.beginPath()
ctx.arc(p2.x, p2.y, 5, 0, Math.PI * 2)
ctx.stroke()
// get distance between
const horizontalDistance = p2.x - p1.x
ctx.strokeStyle = 'black'
// draw left part
ctx.beginPath()
ctx.moveTo(p1.x, p1.y)
ctx.lineTo(p1.x + horizontalDistance / 2, p1.y)
ctx.stroke()
// draw vertical part
ctx.beginPath()
ctx.moveTo(p1.x + horizontalDistance / 2, p1.y)
ctx.lineTo(p1.x + horizontalDistance / 2, p2.y)
ctx.stroke()
// draw right part
ctx.beginPath()
ctx.moveTo(p1.x + horizontalDistance / 2, p2.y)
ctx.lineTo(p2.x, p2.y)
ctx.stroke()
canvas {
border: 1px solid black;
}
<canvas></canvas>
Real-time version:
const canvas = document.querySelector('canvas')
const ctx = canvas.getContext('2d')
const p1 = {
x: canvas.width / 2,
y: canvas.height / 2
}
const p2 = {
x: 150,
y: 130
}
canvas.addEventListener('mousemove', e => {
const mousePos = getMousePos(canvas, e)
p2.x = mousePos.x
p2.y = mousePos.y
})
loop()
function loop() {
draw()
requestAnimationFrame(loop)
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
ctx.strokeStyle = 'red'
ctx.beginPath()
ctx.arc(p1.x, p1.y, 5, 0, Math.PI * 2)
ctx.stroke()
ctx.beginPath()
ctx.arc(p2.x, p2.y, 5, 0, Math.PI * 2)
ctx.stroke()
const horizontalDistance = p2.x - p1.x
ctx.strokeStyle = 'black'
ctx.beginPath()
ctx.moveTo(p1.x, p1.y)
ctx.lineTo(p1.x + horizontalDistance / 2, p1.y)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(p1.x + horizontalDistance / 2, p1.y)
ctx.lineTo(p1.x + horizontalDistance / 2, p2.y)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(p1.x + horizontalDistance / 2, p2.y)
ctx.lineTo(p2.x, p2.y)
ctx.stroke()
}
function getMousePos(canvas, evt) {
const rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas {
border: 1px solid black;
}
<canvas></canvas>
So I looked at the answer here: Draw arrow head in canvas using Angle
But it didn't seem to do it the way I wanted. I definitely do not want to use rotate, what I would like, is based on an Angle in degrees (0 being up on the screen, 180 being down, etc) is draw an arrow pointing in that direction.
Now I slept through trig in highschool so the correct usage of Rads, Sin and Cos are... well, they elude me :(.
Anyways, I have the angle already computed, and based on that I want to draw like the following:
The top one is at 0 degrees in my computation, the lower one 90 degrees.
I'm using a 2d canvas as my draw surface.
Inspired by the trigonometry of this answer I made a line + two smaller lines for the arrow part.
const size = 200;
var canvas = document.querySelector("canvas")
canvas.width = size;
canvas.height = size;
var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, size, size);
ctx.strokeStyle = "red"
function lineToAngle(ctx, x1, y1, length, angle) {
angle = (angle - 90) * Math.PI / 180;
var x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
ctx.fill();
return {
x: x2,
y: y2
};
}
function draw_arrow(ctx, x1, y1, length, angle) {
var pos = lineToAngle(ctx, x1, y1, length, angle);
lineToAngle(ctx, pos.x, pos.y, 10, angle - 135);
lineToAngle(ctx, pos.x, pos.y, 10, angle + 135);
}
var pos = draw_arrow(ctx, 50, 50, 50, 30);
ctx.strokeStyle = "blue"
for (var angle = 0; angle <= 360; angle += 30) {
draw_arrow(ctx, 100, 100, 60, angle);
}
<canvas></canvas>
Overkill maybe?
This may help or may not.
Rather than create the arrow (shape) with a set of draw calls you can create a
Path2D to hold the shape with the helper function Path (in demo code below) that converts a set of arrays (each array a sub path) to a Path2D object. For example. const arrow = Path([[0, 0, 180, 0], [160, -10, 180, 0, 160, 10]]); a simple line arrow. To draw the path call ctx.stroke(arrow) or ctx.fill(arrow);
Define a style using Style eg const red = Style(...["#F00", , 2, "round"])
Position, rotate, and scale
Then the function drawPath(ctx, path, style, centerX, centerY, deg, scale) where...
ctx context to draw on.
path path to draw
style style to use,
centerX, centerY point to rotate around
deg angle in degrees from 0 deg pointing up, and positive values moving clockwize
scale lets you set the scale and defaults to 1.
Example use
// draw red arrow pointing up
const arrow = Path([[0, 0, 180, 0], [160, -10, 180, 0, 160, 10]]);
const red = Style(...["#F00", , 2, "round"]);
drawPath(ctx, arrow, red, 200, 200, 0);
Demo
Working example draw 6 arrows using 3 styles rotating around center of canvas.
const Path = (paths) => {
var j = 0, xx, yy, path = new Path2D();;
for (const subPath of paths) {
j = 0;
while (j < subPath.length) {
const [x, y] = [subPath[j++], subPath[j++]];
j === 2 ?
path.moveTo(...([xx, yy] = [x, y])) :
xx === x && yy === y ? path.closePath() : path.lineTo(x, y);
}
}
return path;
}
const Style = (strokeStyle, fillStyle, lineWidth, lineJoin = "bevel") => ({strokeStyle, fillStyle, lineWidth, lineJoin});
const styles = {
redLine: Style(...["#F00", , 6, "round"]),
greenFill: Style(...[ , "#0A0"]),
fillAndLine: Style(...["#000", "#0AF", 2, "round"]),
};
const paths = {
lineArrow: Path([ [10, 0, 180, 0], [160, -10, 180, 0, 160, 10] ]),
fatArrow: Path([ [10, -5, 180, -5, 170, -15, 200, 0, 170, 15, 180, 5, 10, 5, 10, -5] ]),
diamondArrow: Path([ [60, 0, 100, -15, 150, 0, 100, 15, 60, 0] ]),
};
requestAnimationFrame(mainLoop);
const [W, H, ctx] = [can.width, can.height, can.getContext("2d")];
const DEG2RAD = Math.PI / 180, DEG_0_OFFSET = -90;
function drawPath(ctx, path, style, centerX, centerY, deg, scale = 1) {
const rad = (deg + DEG_0_OFFSET) * DEG2RAD;
const [ax, ay] = [Math.cos(rad) * scale, Math.sin(rad) * scale];
ctx.setTransform(ax, ay, -ay, ax, centerX, centerY);
Object.assign(ctx, style);
style.fillStyle && ctx.fill(path);
style.strokeStyle && ctx.stroke(path);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
function mainLoop(time) {
ctx.clearRect(0, 0, W, H);
drawPath(ctx, paths.fatArrow, styles.fillAndLine, W * 0.5, H * 0.5, (time / 15000) * 360, 0.5);
drawPath(ctx, paths.fatArrow, styles.greenFill, W * 0.5, H * 0.5, (time / 20000) * 360);
drawPath(ctx, paths.diamondArrow, styles.fillAndLine, W * 0.5, H * 0.5, (time / 30000) * 360, 0.5);
drawPath(ctx, paths.diamondArrow, styles.greenFill, W * 0.5, H * 0.5, (time / 60000) * 360);
drawPath(ctx, paths.lineArrow, styles.redLine, W * 0.5, H * 0.5, (time / 5000) * 360, 0.9);
drawPath(ctx, paths.lineArrow, styles.redLine, W * 0.5, H * 0.5, (time / 10000) * 360);
requestAnimationFrame(mainLoop);
}
<canvas id="can" width="400" height="400"></canvas>
When the program runs the mouse is clicked creating a projectile from the center of the screen moving with every frame in the direction it was fired (mouse position on click).
When N+1 projectiles have fired all projectiles on-screen move to the new clicked location instead of continuing their path.
I am can not figure out why the current projectiles change direction when the New projectile's velocity should have no effect on prior projectiles.
index.html
<canvas></canvas>
<script src="./guns.js"></script>
<script src="./indexh.js"></script>
<script src="./runh.js"></script>
runh.html
const projectilesArray = [];
let frameCount = 0;
function animate() {
animationID = requestAnimationFrame(animate);
c.fillStyle = "rgba(0, 0, 0, 1)";
c.fillRect(0, 0, canvas.width, canvas.height);
projectilesArray.forEach((Projectile, pIndex) => {
Projectile.update();
console.log(Projectile)
if (
Projectile.x + Projectile.radius < 0 ||
Projectile.x - Projectile.radius > canvas.width ||
Projectile.y + Projectile.radius < 0 ||
Projectile.y - Projectile.radius > canvas.height
) {
setTimeout(() => {
projectilesArray.splice(pIndex, 1);
}, 0);
}
});
frameCount++;
if (frameCount > 150) {
}
}
var fire = 1;
let fireRate = 1;
const mouse = {
x: 0,
y: 0,
click: true,
};
canvas.addEventListener('mousedown', (event) => {
if (fire % fireRate == 0) {
if (mouse.click == true) {
mouse.x = event.x;
mouse.y = event.y;
const angle = Math.atan2(mouse.y - (canvas.height / 2), mouse.x - (canvas.width / 2));
const fireY = Math.sin(angle);
const fireX = Math.cos(angle);
//score -= 0;
//scoreL.innerHTML = score;
var weapon = new Projectile(cannon);
weapon.velocity.x = fireX * 9;
weapon.velocity.y = fireY * 9;
projectilesArray.push(weapon);
//var gun = object.constructor()
}
}
});
animate();
indexh.js
const canvas = document.querySelector("canvas");
const c = canvas.getContext("2d");
canvas.width = innerWidth;
canvas.height = innerHeight;
class Projectile {
constructor(config) {
this.color = config.color || "rgb(60, 179, 113)";
this.radius = config.radius || 1;
this.speed = config.speed || 5;
this.rounds = config.rounds || 2;
this.x = config.x || canvas.width / 2;
this.y = config.y || canvas.height /2;
this.velocity = config.velocity;
}
draw() {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.fillStyle = this.color;
c.fill();
}
update() {
this.draw();
this.x = this.x + this.velocity.x * this.speed;
this.y = this.y + this.velocity.y * this.speed;
}
}
gums.js
let pistol = {
color : "rgb(255, 0, 0)",
radius : 10,
speed : 1,
rounds : 1,
velocity : {
x: 1,
y: 1
}
}
let cannon = {
color : "rgb(0, 0, 255)",
radius : 30,
speed : .5,
rounds : 1,
velocity : {
x: 1,
y: 1
}
}
Thanks
The issue is that you have this cannon object used as a configuration object:
let cannon = {
color : "rgb(0, 0, 255)",
radius : 30,
speed : .5,
rounds : 1,
velocity : {
x: 1,
y: 1
}
}
And in your Projectile constructor you assign this.velocity = config.velocity; You are assigning this.velocity to be the same object instance for all Projectiles. To fix it, try copying the object, like:
this.velocity = {...config.velocity};
That will make a copy of the object rather than sharing the same object instance.
Also note that you have a bug that you didn't ask about in this line:
projectilesArray.splice(pIndex, 1);
If two timeouts are queued in the same loop, the array will shift when the first timeout fires, and the second timeout will be operating on the wrong index.
I want to animate a rectangle on html canvas. when the user will click the canvas, the rectangle will start it's animation, and go to the clicked position. I used delta x and y to add and subtract pixels from the x and y position of the rectangle.
But the problem with this solution is, I can't find a way to make the rectangle animate in a straight path.
My code:
'use strict'
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
const ratio = Math.ceil(window.devicePixelRatio)
let height = window.innerHeight
let width = window.innerWidth
canvas.height = height * ratio
canvas.width = width * ratio
canvas.style.height = `${height}px`
canvas.style.width = `${width}px`
ctx.setTransform(ratio, 0, 0, ratio, 0, 0)
let position = {
x: 0,
y: 0,
deltaX: 0,
deltaY: 0,
size: 20
}
let move = {
x: 0,
y: 0,
}
function animate() {
if (position.x === move.x && position.y === move.y) {
cancelAnimationFrame()
}
if (position.x !== move.x) {
ctx.fillRect(position.x, position.y, position.size, position.size)
position.x += position.deltaX
}
if (position.y !== move.y) {
ctx.fillRect(position.x, position.y, position.size, position.size)
position.y += position.deltaY
}
requestAnimationFrame(animate)
}
function moveTo(x, y) {
move.x = x
move.y = y
position.deltaX = position.x > x ? -1 : 1
position.deltaY = position.y > y ? -1 : 1
animate()
}
canvas.addEventListener('click', (event) => {
moveTo(event.clientX, event.clientY)
})
ctx.fillRect(position.x, position.y, position.size, position.size)
<canvas id="canvas">
</canvas>
If you click in the canvas the rectangle will start moving but it'll go in a weird path, I can't find a way to properly go straight at the clicked position.
see demo at Github page
Here is the sample Math from something I did a while ago...
If there is something you don't understand there ask
In your code it was moving "weird" because your delta values where always 1 or -1, no fractions, that limits the way the object can travel, instead we do our calculations using the angle.
const canvas = document.getElementById('canvas')
const ctx = canvas.getContext('2d')
let player = {
x: 0, y: 0, size: 10,
delta: { x: 0, y: 0 },
move: { x: 0, y: 0 }
}
function animate() {
let a = player.x - player.move.x;
let b = player.y - player.move.y;
if (Math.sqrt( a*a + b*b ) < 2) {
player.delta = { x: 0, y: 0 }
}
player.x += player.delta.x
player.y += player.delta.y
ctx.fillRect(player.x, player.y, player.size, player.size)
requestAnimationFrame(animate)
}
function moveTo(x, y) {
player.move.x = x
player.move.y = y
let angle = Math.atan2(y - player.y, x - player.x)
player.delta.x = Math.cos(angle)
player.delta.y = Math.sin(angle)
}
canvas.addEventListener('click', (event) => {
moveTo(event.clientX, event.clientY)
})
animate()
<canvas id="canvas"></canvas>
I need to draw a circle and i have only two points.Now i need to find center point and radius of the circle? You can form the circle in clock wise direction.
Thanks in advance
Here is a Brute Force approach to the problem.
EDIT
Added a max iterations limit to cut off calculations if the line between the two points is almost straight along x (meaning a radius would be nearing Infinity)
Also animations, because that makes everything better :)
var canvas = document.body.appendChild(document.createElement("canvas"));
var ctx = canvas.getContext("2d");
canvas.width = 1000;
canvas.height = 1000;
var points = [
{ x: parseInt(prompt("x1", "110")), y: parseInt(prompt("y1", "120")), r: 5 },
{ x: parseInt(prompt("x2", "110")), y: parseInt(prompt("y2", "60")), r: 5 },
];
function calculateRemainingPoint(points, x, precision, maxIteration) {
if (x === void 0) { x = 0; }
if (precision === void 0) { precision = 0.001; }
if (maxIteration === void 0) { maxIteration = 100000; }
var newPoint = {
x: x,
y: (points[0].y + points[1].y) / 2,
r: 50
};
var d0 = distance(points[0].x, points[0].y, x, newPoint.y);
var d1 = distance(points[1].x, points[1].y, x, newPoint.y);
var iteration = 0;
//Bruteforce approach
while (Math.abs(d0 - d1) > precision && iteration < maxIteration) {
var oldDiff = Math.abs(d0 - d1);
var oldY = newPoint.y;
iteration++;
newPoint.y += oldDiff / 10;
d0 = distance(points[0].x, points[0].y, x, newPoint.y);
d1 = distance(points[1].x, points[1].y, x, newPoint.y);
var diff_1 = Math.abs(d0 - d1);
if (diff_1 > oldDiff) {
newPoint.y = oldY - oldDiff / 10;
d0 = distance(points[0].x, points[0].y, x, newPoint.y);
d1 = distance(points[1].x, points[1].y, x, newPoint.y);
}
}
var diff = (points[0].x + points[1].x) / points[0].x;
newPoint.r = d0;
return newPoint;
}
points.push(calculateRemainingPoint(points));
function distance(x1, y1, x2, y2) {
var a = x1 - x2;
var b = y1 - y2;
return Math.sqrt(a * a + b * b);
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.moveTo(-canvas.width, canvas.height / 2);
ctx.lineTo(canvas.width, canvas.height / 2);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(canvas.width / 2, -canvas.height);
ctx.lineTo(canvas.width / 2, canvas.height);
ctx.stroke();
ctx.closePath();
for (var pointIndex = 0; pointIndex < points.length; pointIndex++) {
var point = points[pointIndex];
ctx.beginPath();
ctx.arc(point.x + canvas.width / 2, canvas.height / 2 - point.y, point.r, 0, Math.PI * 2);
ctx.arc(point.x + canvas.width / 2, canvas.height / 2 - point.y, 2, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
}
}
setInterval(function () {
points = points.slice(0, 2);
points[Math.floor(Math.random() * points.length) % points.length][Math.random() > 0.5 ? 'x' : 'y'] = Math.random() * canvas.width - canvas.width / 2;
setTimeout(function () {
points.push(calculateRemainingPoint(points));
requestAnimationFrame(draw);
}, 1000 / 60);
}, 1000);
draw();
No that is impossible.
Create two circles with the same radius at centerpoints A + B. At the intersection of these two circles create an circle with the same radius....
Then make the same with an other radius....