Make a triangle and label it using Canvas in Node.js - javascript

I am trying to make something that looks like this:
I've written this so far:
const { createCanvas } = require('canvas');
class Trigonometry {
constructor({ sides, angles }) {
this.sides = sides;
this.angles = angles;
}
drawCanvas() {
const { a, b, c } = this.sides;
const [SA, SB, SC] = this.scaleNumbers(a, b, c);
const { A: AA, B: AB, C: AC } = this.angles;
const canvas = createCanvas(300, 300);
const ctx = canvas.getContext('2d');
const ax = 0, ay = 0;
const bx = SC, by = 0;
const cx = (SB * SA + SC * SC - SA * SA) / (2 * SC);
const cy = Math.sqrt(SB * SA + SC + cx * cx);
const ox = canvas.width / 2 - bx / 2;
const oy = canvas.height / 2 + cy / 2;
// Draw triangle
ctx.beginPath();
ctx.moveTo(ox + ax, oy - ay);
ctx.lineTo(ox + bx, oy - by);
ctx.lineTo(ox + cx, oy - cy);
ctx.lineTo(ox + ax, oy - ay);
ctx.lineWidth = 2; ctx.fillStyle = 'grey';
ctx.stroke(); ctx.fill();
ctx.closePath();
// Draw angle labels
ctx.font = 'bold 15px Arial';
ctx.fillStyle = 'blue';
this.drawLabel(ctx, `A=${AA}°`, ox + ax, oy - ay);
this.drawLabel(ctx, `B=${AB}°`, ox + bx, oy - by);
this.drawLabel(ctx, `C=${AC}°`, ox + cx, oy - cy);
// Draw line labels
ctx.fillStyle = 'green';
this.drawLabel(ctx, `a=${a}`, bx + cx / 2, by - cy / 2);
this.drawLabel(ctx, `b=${b}`, ox + cx / 2, oy - cy / 2);
this.drawLabel(ctx, `c=${c}`, ox + bx / 2, oy - by / 2);
return canvas.toDataURL()
}
drawLabel(ctx, txt, x, y) {
const { width } = ctx.measureText(txt);
ctx.fillText(txt, x - width / 2, y);
}
scaleNumbers(...numbers) {
if (numbers.every(n => n > 200 && n < 250)) return numbers;
if (numbers.some(n => n > 250))
return numbers.map(n => n / (Math.max(...numbers) / 250))
return numbers.map(n => n * (250 / Math.min(...numbers)));
}
}
const trig = new Trigonometry({
sides: { a: 800, b: 750, c: 700 },
angles: { A: 66.868, B: 59.556, C: 53.576 },
});
const data = trig.toDataURL();
But it doesn't result in exactly what I want, the triangle is simply wrong and it breaks a lot and simply looks bad.
All the line lengths and angles get calculated before hand so I have access to all of those. Maybe there is another way to do this without using canvas?

Related

How to move point with shape's changing rotation and position

I have a web app, where I am drawing a triangle and also drawing points on top of it to show the vertices that it has. (fig 1 at 0 radians)
The circle and triangle rotate fine, its just the blue point that I cant seem to move appropriately. When I rotate the triangle (along with circle), the X and Y does not translate to either corners of the triangle besides the red dot. (fig 2 at .75 radians)
The shape as a whole is drawn with the following vertices for display points.
this.transform = ctx.getTransform();
this.boundPoints[0] = { //red point
x: (this.transform.a + this.x)+(this.radius)* Math.cos(this.rotation),
y: (this.transform.d + this.y)+(this.radius)* Math.sin(this.rotation)
}
this.boundPoints[1] = { //blue point
x: (this.transform.a + this.x)+(this.radius+ this.range)* Math.cos(this.rotation),
y: (this.transform.d + this.y)+(this.radius)* Math.sin(this.rotation)
}
What I want to have happen, is this
Where that point keeps its position relative to the triangle regardless of its position and rotation in the canvas. Without rotating, I can keep it there with its Y being
y: (this.transform.d + this.y+this.range)
but now I can't rotate or move the shape without the dot losing its placement. (Note: this.rotation is angle in radians)
My way of keeping track of all point of any shape is to create any array in my class that stores those values separate from the actual points that you are drawing to. I use those stored point mainly for collision detection with an odd shape that has been transformed/rotated/scaled.
Without your code it's hard to see how I would implement this technique but here's an example of a rotating triangle that you can scale and transform and the points are always tracked. This example also includes a commented out piece of code showing how to use the centroid to rotate from the center is needed.
this.position is the trasnlate
this.size is scale
this.r is rotate
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 300;
canvas.height = 300;
let ptACopy, ptBCopy, ptCCopy;
class Triangle {
constructor(ptA, ptB, ptC) {
this.type = "tri";
this.ptA = ptACopy = ptA;
this.ptB = ptBCopy = ptB;
this.ptC = ptCCopy = ptC;
this.position = { x: 100, y: 100 }; //use this to position
this.size = { x: 2, y: 1 };
this.centroid = {
ox: (this.ptA.x + this.ptB.x + this.ptC.x) / 3,
oy: (this.ptA.y + this.ptB.y + this.ptC.y) / 3
};
this.c = "red";
this.a = 0;
this.r = this.a * (Math.PI / 180);
this.points = [];
for (let i = 0; i < 3; i++) {
this.points.push({ x: 0, y: 0 });
}
}
draw() {
//updates the points to counter the translating of the canvas to the centroid
//this is used to rotate from center if wanted
/*this.ptA = {
x: ptACopy.x - this.centroid.ox,
y: ptACopy.y - this.centroid.oy
};
this.ptB = {
x: ptBCopy.x - this.centroid.ox,
y: ptBCopy.y - this.centroid.oy
};
this.ptC = {
x: ptCCopy.x - this.centroid.ox,
y: ptCCopy.y - this.centroid.oy
};*/
let cos = Math.cos(this.r);
let sin = Math.sin(this.r);
ctx.save();
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.setTransform(cos * this.size.x, sin * this.size.x, -sin * this.size.y, cos * this.size.y, this.position.x, this.position.y);
ctx.moveTo(this.ptA.x, this.ptA.y);
ctx.lineTo(this.ptB.x, this.ptB.y);
ctx.lineTo(this.ptC.x, this.ptC.y);
ctx.lineTo(this.ptA.x, this.ptA.y);
ctx.fill();
ctx.closePath();
ctx.restore();
}
updateCorners() {
this.a += 0.1;
this.r = this.a * (Math.PI / 180);
let cos = Math.cos(this.r);
let sin = Math.sin(this.r);
this.points[0].x =
this.ptA.x * this.size.x * cos -
this.ptA.y * this.size.y * sin +
this.position.x;
this.points[0].y =
this.ptA.x * this.size.x * sin +
this.ptA.y * this.size.y * cos +
this.position.y;
this.points[1].x =
this.ptB.x * this.size.x * cos -
this.ptB.y * this.size.y * sin +
this.position.x;
this.points[1].y =
this.ptB.x * this.size.x * sin +
this.ptB.y * this.size.y * cos +
this.position.y;
this.points[2].x =
this.ptC.x * this.size.x * cos -
this.ptC.y * this.size.y * sin +
this.position.x;
this.points[2].y =
this.ptC.x * this.size.x * sin +
this.ptC.y * this.size.y * cos +
this.position.y;
}
drawPoints() {
ctx.fillStyle = "blue";
this.points.map((x) => {
ctx.beginPath();
ctx.arc(x.x, x.y, 3, 0, Math.PI * 2);
ctx.fill();
});
}
}
let triangle = new Triangle(
{ x: 10, y: 20 },
{ x: 50, y: 60 },
{ x: 30, y: 100 }
);
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
triangle.draw();
triangle.updateCorners();
triangle.drawPoints();
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>
To clarify some things in the code:
The purpose of using the following code is to ensure the shape is accurately drawn when translating it.
this.ptA = {
x: ptACopy.x - this.centroid.ox,
y: ptACopy.y - this.centroid.oy
};
If is were to not make a copy of the points and try to do
this.ptA = {
x: ptA.x - this.centroid.ox,
y: ptA.y - this.centroid.oy
};
then I would just get undefined because I'm trying to use ptA to calculate ptA.
Also when creating the triangle if I wanted ptA to be at (0,0) then I could set it there and then use the above function to offset the triangle for rotation purposes. example using that to have it rotate around ptA with a 20px radius:
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 300;
canvas.height = 300;
let ptACopy, ptBCopy, ptCCopy;
class Triangle {
constructor(ptA, ptB, ptC) {
this.type = "tri";
this.ptA = ptACopy = ptA;
this.ptB = ptBCopy = ptB;
this.ptC = ptCCopy = ptC;
this.position = { x: 100, y: 100 }; //use this to position
this.size = { x: 1, y: 1 };
this.centroid = {
ox: (this.ptA.x + this.ptB.x + this.ptC.x) / 3,
oy: (this.ptA.y + this.ptB.y + this.ptC.y) / 3
};
this.c = "red";
this.a = 0;
this.r = this.a * (Math.PI / 180);
this.points = [];
for (let i = 0; i < 3; i++) {
this.points.push({ x: 0, y: 0 });
}
}
draw() {
//updates the points to counter the translating of the canvas to the centroid
this.ptA = {
x: ptACopy.x + 20,
y: ptACopy.y + 20
};
this.ptB = {
x: ptBCopy.x + 20,
y: ptBCopy.y + 20
};
this.ptC = {
x: ptCCopy.x + 20,
y: ptCCopy.y + 20
};
let cos = Math.cos(this.r);
let sin = Math.sin(this.r);
ctx.save();
ctx.beginPath();
ctx.fillStyle = this.c;
ctx.setTransform(cos * this.size.x, sin * this.size.x, -sin * this.size.y, cos * this.size.y, this.position.x, this.position.y);
ctx.moveTo(this.ptA.x, this.ptA.y);
ctx.lineTo(this.ptB.x, this.ptB.y);
ctx.lineTo(this.ptC.x, this.ptC.y);
ctx.lineTo(this.ptA.x, this.ptA.y);
ctx.fill();
ctx.closePath();
ctx.fillStyle = 'rgba(0,0,0,0.2)';
ctx.fillRect(0,0,canvas.width, canvas.height);
ctx.restore();
}
updateCorners() {
this.a += 0.5;
this.r = this.a * (Math.PI / 180);
let cos = Math.cos(this.r);
let sin = Math.sin(this.r);
this.points[0].x =
this.ptA.x * this.size.x * cos -
this.ptA.y * this.size.y * sin +
this.position.x;
this.points[0].y =
this.ptA.x * this.size.x * sin +
this.ptA.y * this.size.y * cos +
this.position.y;
this.points[1].x =
this.ptB.x * this.size.x * cos -
this.ptB.y * this.size.y * sin +
this.position.x;
this.points[1].y =
this.ptB.x * this.size.x * sin +
this.ptB.y * this.size.y * cos +
this.position.y;
this.points[2].x =
this.ptC.x * this.size.x * cos -
this.ptC.y * this.size.y * sin +
this.position.x;
this.points[2].y =
this.ptC.x * this.size.x * sin +
this.ptC.y * this.size.y * cos +
this.position.y;
}
drawPoints() {
ctx.fillStyle = "blue";
this.points.map((x) => {
ctx.beginPath();
ctx.arc(x.x, x.y, 3, 0, Math.PI * 2);
ctx.fill();
});
}
}
let triangle = new Triangle(
{ x: 0, y: 0 },
{ x: 50, y: 60 },
{ x: 30, y: 100 }
);
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
triangle.draw();
triangle.updateCorners();
triangle.drawPoints();
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>
I gave the canvas a background for better visualization.
UPDATE:
I am adding an example using getTransform() which returns the transformation matrix.
We can then use those values to calculate each point by passing them to a function. This shortens the code slightly inside the class and makes things look cleaner IMO.
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;
let t;
class Rect {
constructor(x, y, w, h) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.scale = {x: 1, y: 1}
this.cx = this.x + this.w / 2;
this.cy = this.y + this.h / 2;
this.color = "red";
this.angle = 0;
this.rotation = (this.angle * Math.PI) / 180;
this.pts = []
}
draw() {
this.angle += 0.5;
this.rotation = (this.angle * Math.PI) / 180;
const cos = Math.cos(this.rotation)
const sin = Math.sin(this.rotation)
ctx.save();
ctx.setTransform(cos * this.scale.x, sin * this.scale.x, -sin * this.scale.y, cos * this.scale.y, this.x, this.y);
t = ctx.getTransform();
ctx.fillStyle = this.color;
ctx.fillRect(-this.w / 2, -this.h / 2, this.w, this.h);
ctx.restore();
}
drawVertices() {
for (let i=0; i < this.pts.length; i++) {
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.arc(this.pts[i].x, this.pts[i].y, 3, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
updateVertices() {
this.pts[0] = calcVertices(t['a'], t['b'], t['c'], t['d'], t['e'], t['f'], 0, 0, this.cx, this.cy)//top left width and height are passed as 0.
this.pts[1] = calcVertices(t['a'], t['b'], t['c'], t['d'], t['e'], t['f'], this.w, 0, this.cx, this.cy) //top right only passes width. Height is 0.
this.pts[2] = calcVertices(t['a'], t['b'], t['c'], t['d'], t['e'], t['f'], this.w, this.h, this.cx, this.cy) //bottom right passes both wodth and height.
this.pts[3] = calcVertices(t['a'], t['b'], t['c'], t['d'], t['e'], t['f'], 0, this.h, this.cx, this.cy)//bottom left only passes height. Width is 0.
}
}
let rect1 = new Rect(100, 100, 50, 75);
let rect2 = new Rect(250, 150, 100, 25);
function calcVertices(a, b, c, d, e, f, w, h, cx, cy) {
let x, y;
x = (e + w - cx) * a + (f + h - cy) * c + (e);
y = (e + w - cx) * b + (f + h - cy) * d + (f);
return {x: x, y: y}
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
rect1.draw();
rect1.updateVertices();
rect1.drawVertices();
rect2.draw();
rect2.updateVertices();
rect2.drawVertices();
requestAnimationFrame(animate);
}
animate();
<canvas id="canvas"></canvas>
I will answer with complex numbers for convenience, but every complex expression can be rewritten with reals only.
You did not specify the rotation center (presumably the origin) and for generality I will assume the point c. To rotate any point p around this center, we apply the equation
p' = r.(p - c) + c
where r = cos Θ + i sin Θ implements a rotation by angle Θ.
Now to rotate any shape, assuming that the shape has a reference point (say its center or some other remarkable point such as a corner), you need to apply the rotation to this reference point, and assign the angle Θ to the shape (if the shape already had an angle τ assigned to it, the new angle is τ + Θ.)
If you also want to rescale the shape, use r = s (cos Θ + i sin Θ) where s is the desired scaling factor and apply this transformation to the reference point. Also apply the factor s to all dimension parameters of the shapes (or t . s is the parameters already had a factor t applied.)

How to get an angle by using tangent in javascript?

The red circle is at a known angle of 130°, then I want to draw the navy line from the center to 130° using x and y of the red circle but it looks like I missed the calculation.
Currently, the angle of the Navy line is a reflection to the angle of the red line and if I add minus sign ➖ to *diffX * at line13, it'll work as expected but Why do I need to do that by myself, why can't the Calculations at line 10 and 13 figured out if x should be minus ➖ or plus.
I couldn't figure out where I was wrong..any help/suggestions are appreciated!
let ctx, W = innerWidth,
H = innerHeight;
// params for the red circle
let hypothenus = 100;
let knownAngle = (-130 * Math.PI) / 180;
let x = (W / 2) + Math.cos(knownAngle) * hypothenus;
let y = (H / 2) + Math.sin(knownAngle) * hypothenus;
// params for navy line
let diffX = x - (W / 2);
let diffY = (H / 2) - y;
let dist = Math.hypot(diffX, diffY); // pythagoras
let unknownAngle = -Math.atan2(diffY, diffX);
let newX = (W / 2) + Math.cos(unknownAngle) * dist;
let newY = (H / 2) + Math.sin(unknownAngle) * dist;
let angInDegree1 = ~~Math.abs(knownAngle * 180 / Math.PI);
let angInDegree2 = ~~Math.abs(unknownAngle * 180 / Math.PI) | 0;
const msg = document.getElementById("msg")
msg.innerHTML = `Hypothenus1: ${hypothenus}, angle: ${angInDegree1}<br>`;
msg.innerHTML +=`Hypothenus2: ${dist}, angle: ${angInDegree2}`;
// everything to be rendered to the screen
const update = () => {
if (ctx == null) return;
// drawing the red line
draw.line([W / 2, 0], [W / 2, H], 6, "red");
draw.line([0, H / 2], [W, H / 2], 6, "red");
// the red circle
draw.circle([x, y], 10, "red");
// draw line
draw.line([W / 2, H / 2], [newX, newY], 4, "navy");
}
// utility object for drawing
const draw = {
line(from, to, width, color) {
with(ctx) {
beginPath();
lineWidth = width;
strokeStyle = color;
moveTo(...from);
lineTo(...to);
stroke();
closePath();
}
},
circle(pos, radius, color) {
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(...pos, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}
}
// init function
const init = () => {
ctx = document.querySelector("#cvs").getContext("2d");
W = ctx.canvas.width = innerWidth;
H = ctx.canvas.height = innerHeight;
update();
}
window.addEventListener("load", init);
<div id="msg"></div>
<canvas id="cvs"></canvas>
Seems you are using too much minuses.
At first, you define angle -130 degrees, close to -3Pi/4. Cosine and sine values for this angle are about -0.7, using hypothenus = 100, we get x =W/2-70, y = H/2-70
diffX = x - W/2 = -70
diffY = y - H/2 = -70
atan2(-70, -70) gives -2.3561 radians = -3/4*Pi = -135 degrees
When you change sign of diffY (note - diffY formula is wrong, not difX one!), you make reflection against OX axis, and change angle sign - that is why another minus before Math.atan2 is required
Corrected code:
let diffX = x - (W / 2);
let diffY = y - (H / 2);
let dist = Math.hypot(diffX, diffY); // pythagoras
let unknownAngle = Math.atan2(diffY, diffX);

Change color of Paticle(circle) when collision detected in canvas

// Initial Setup
const canvas = document.querySelector('canvas');
const c = canvas.getContext('2d');
// Canvas size
canvas.width = innerWidth;
canvas.height = innerHeight;
//Color
const colors = [
{r: 51, g: 99, b: 252}
//{r: 77, g: 57, b: 206},
// {r: 0, g: 189, b: 255},
];
// Utility Functions
function randomIntFromRange(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
}
function randomColor(colors) {
return colors[Math.floor(Math.random() * colors.length)];
}
function distance(x1, y1, x2, y2) {
const xDist = x2 - x1;
const yDist = y2 - y1;
return Math.sqrt(Math.pow(xDist, 2) + Math.pow(yDist, 2));
}
function rotateVelocities(velocity, theta) {
const rotatedVelocity = {
x: velocity.x * Math.cos(theta) - velocity.y * Math.sin(theta),
y: velocity.x * Math.sin(theta) + velocity.y * Math.cos(theta)
};
return rotatedVelocity;
}
// Objects
function Particle(x, y, radius, rgb) {
this.x = x;
this.y = y;
this.velocity = {
x: (Math.random() - 0.5) * 3,
y: (Math.random() - 0.5) * 3
};
this.radius = radius;
this.mass = 1;
this.opacity = 0;
this.r = rgb.r;
this.g = rgb.g;
this.b = rgb.b;
this.update = particles => {
this.draw();
for (let i = 0; i < particles.length; i++) {
const otherParticle = particles[i];
if (this.x === otherParticle.x) continue;
if (distance(this.x, this.y, otherParticle.x, otherParticle.y) - this.radius * 2 < 0) {
const res = {
x: this.velocity.x - otherParticle.velocity.x,
y: this.velocity.y - otherParticle.velocity.y
};
if (res.x * (otherParticle.x - this.x) + res.y * (otherParticle.y - this.y) >= 0) {
const m1 = this.mass;
const m2 = otherParticle.mass;
const theta = -Math.atan2(otherParticle.y - this.y, otherParticle.x - this.x);
const rotatedVelocity1 = rotateVelocities(this.velocity, theta);
const rotatedVelocity2 = rotateVelocities(otherParticle.velocity, theta);
const swapVelocity1 = { x: rotatedVelocity1.x * (m1 - m2) / (m1 + m2) + rotatedVelocity2.x * 2 * m2 / (m1 + m2), y: rotatedVelocity1.y };
const swapVelocity2 = { x: rotatedVelocity2.x * (m1 - m2) / (m1 + m2) + rotatedVelocity1.x * 2 * m2 / (m1 + m2), y: rotatedVelocity2.y };
const u1 = rotateVelocities(swapVelocity1, -theta);
const u2 = rotateVelocities(swapVelocity2, -theta);
this.velocity.x = u1.x;
this.velocity.y = u1.y;
otherParticle.velocity.x = u2.x;
otherParticle.velocity.y = u2.y;
}
}
if (distance(this.x, this.y, otherParticle.x, otherParticle.y) - this.radius * 2 < 0 ) {
this.opacity = 0.1;
}
}
if (this.x + this.radius >= canvas.width || this.x - this.radius <= 0)
this.velocity.x = -this.velocity.x;
if (this.y + this.radius >= canvas.height || this.y - this.radius <= 0)
this.velocity.y = -this.velocity.y;
this.x += this.velocity.x;
this.y += this.velocity.y;
};
this.draw = () => {
c.beginPath();
c.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
c.strokeStyle = `rgba(${this.r}, ${this.g}, ${this.b}, 1)`;
c.stroke();
c.fillStyle = `rgba(${this.r}, ${this.g}, ${this.b}, ${this.opacity}`;
c.fill();
c.closePath();
};
}
// Implementation
let particles;
function init() {
particles = [];
let radius = 80
for (let i = 0; i < 10; i++) {
let x = randomIntFromRange(radius, innerWidth - radius);
let y = randomIntFromRange(radius, innerHeight - radius);
if (particles.length >= 1) {
for (let j = 0; j < particles.length; j++) {
if (distance(x, y, particles[j].x, particles[j].y) - radius * 2 < 0) {
x = randomIntFromRange(radius, innerWidth - radius);
y = randomIntFromRange(radius, innerHeight - radius);
j = -1;
continue;
}
}
}
particles.push(new Particle(x, y, radius, randomColor(colors)));
}
}
//I would like to initiate one circle out of all as red and whenever the red circle collide with other circle, other circle got red and at last all circle become red. but when two white circle collide it should not become red. basically one infected circle making other circle infected by touching.

Raycasting with maximum distance

I'm writing a simple raycast in htm5l and the main issue with the raycast is that the line goes off in a direction of probably infinite length.. I would like to limit that length to a specific radius but I'm not having any luck. If someone could guide me that'd be great.
window.addEventListener('DOMContentLoaded', (event) => {
let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let coord = {
x: 0,
y: 0
}
function line(x1, y1, x2, y2) {
ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
}
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
class Boundery {
constructor(x1, y1, x2, y2) {
this.a = new Vector(x1, y1);
this.b = new Vector(x2, y2);
}
show() {
ctx.strokeStyle = '#000000'
line(this.a.x, this.a.y, this.b.x, this.b.y);
}
}
class Ray {
constructor(x, y) {
this.pos = new Vector(x, y);
this.dir = new Vector(Math.cos(1), Math.sin(0));
}
show() {
ctx.strokeStyle = '#000000';
ctx.beginPath();
ctx.ellipse(this.pos.x, this.pos.y, 5, 5, 0, 0, Math.PI * 2);
ctx.stroke();
}
cast(wall) {
let x1 = wall.a.x;
let y1 = wall.a.y;
let x2 = wall.b.x;
let y2 = wall.b.y;
let x3 = this.pos.x;
let y3 = this.pos.y;
let x4 = this.pos.x + this.dir.x;
let y4 = this.pos.y + this.dir.y;
let den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
if (den == 0) {
return;
}
let t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;
let u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;
if (t > 0 && t < 1 && u > 0) {
let point = new Vector(x1 + t * (x2 - x1), y1 + t * (y2 - y1));
return point;
} else {
return;
}
}
}
let wall = new Boundery(300, 100, 300, 300);
let ray = new Ray(100, 200);
function tick(timestamp) {
ctx.clearRect(0, 0, canvas.clientWidth, canvas.clientHeight);
wall.show();
ray.show();
let r = ray.cast(wall);
if (r) {
ctx.fillStyle = 'red';
ctx.ellipse(r.x, r.y, 10, 10, 0, 0, 2 * Math.PI);
ctx.fill();
}
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
});
<canvas id="canvas" width="2000" height="1000"></canvas>
So the ray currently fires to the right (1,0) of the small red circle but it's distance just goes on forever so I'm trying to limit that distance. In the example the position the ray hits the wall is the red circle that's drawn on the wall
Ray length and direction
Modify the ray to have the start position, a direction, and a length. as follows
class Ray {
constructor(pos, direction, length) {
this.pos = pos;
this.dir = direction;
this.length = length;
}
You can get the end point with
get end() {
return new Vector(
this.pos.x + Math.cos(this.dir) * this.length,
this.pos.y + Math.sin(this.dir) * this.length
);
}
When you cast the ray you convert the ray to a line segment and then check against any wall segments for the intercept. Only points withing the length of the ray will be found.
Example.
The example uses a ray to check against many walls. It finds the closest intercept to the to the start of the ray and within the rays length.
Note (FOR example only) the walls are random so if a wall gets to close to the ray, click the canvas to randomize the walls.
I have re-organised it somewhat with Vector as a point, Line (2 vectors) as a line segment with an intercept function (also represents a wall), And Ray as a vector, direction and length. The Ray.cast finds the intercept of an array of line, returning undefined if no intercept found.
const ctx = canvas.getContext("2d");
Math.TAU = Math.PI * 2;
Math.rand = (min, max) => Math.random() * (max - min) + min;
var myRay;
const WALL_COUNT = 30;
const WALL_STYLE = {radius: 0, lineWidth: 1, strokeStyle: "#000"};
const RAY_STYLE_A = {radius: 2, lineWidth: 1, strokeStyle: "#0FF", fillStyle: "#F00"};
const RAY_STYLE_B = {radius: 5, lineWidth: 3, strokeStyle: "#00F", fillStyle: "#F00"};
const RAY_INTERCEPT_STYLE = {radius: 5, lineWidth: 1, strokeStyle: "#000", fillStyle: "#FF0"};
const ROTATE_RAY = 10; // seconds per rotation
const walls = [];
setTimeout(init, 0);
canvas.addEventListener("click",init);
class Vector {
constructor(x, y) {
this.x = x;
this.y = y;
}
draw(ctx, {radius = 5, lineWidth = 2, strokeStyle = "#000", fillStyle = "#F00"} = {}) {
ctx.strokeStyle = strokeStyle;
ctx.fillStyle = fillStyle;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.arc(this.x, this.y, radius, 0, Math.TAU);
ctx.fill();
ctx.stroke();
}
}
class Line {
constructor(start, end) {
this.start = start;
this.end = end;
}
draw(ctx, {radius = 5, lineWidth = 2, strokeStyle = "#000", fillStyle = "#F00"} = {}) {
if (radius > 0) {
this.start.draw(ctx, {radius, lineWidth, strokeStyle, fillStyle});
this.end.draw(ctx, {radius, lineWidth, strokeStyle, fillStyle});
}
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(this.start.x, this.start.y);
ctx.lineTo(this.end.x, this.end.y);
ctx.stroke();
}
intercept(line) {
var x1, y1, x2, y2, x3, y3, c, u;
x1 = line.end.x - line.start.x;
y1 = line.end.y - line.start.y;
x2 = this.end.x - this.start.x;
y2 = this.end.y - this.start.y;
c = x1 * y2 - y1 * x2;
if (c) {
x3 = line.start.x - this.start.x;
y3 = line.start.y - this.start.y;
u = (x1 * y3 - y1 * x3) / c;
if (u >= 0 && u <= 1) {
u = (x2 * y3 - y2 *x3) / c;
if (u >= 0 && u <= 1) { return [u, line.start.x + x1 * u, line.start.y + y1 * u] }
}
}
}
}
class Ray {
constructor(pos, direction, length) {
this.pos = pos;
this.dir = direction;
this.length = length;
}
draw(ctx, {radius = 5, lineWidth = 2, strokeStyle = "#000", fillStyle = "#F00"} = {}) {
this.pos.draw(ctx, {radius, lineWidth, strokeStyle, fillStyle});
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = lineWidth;
ctx.beginPath();
ctx.moveTo(this.pos.x, this.pos.y);
ctx.lineTo(
this.pos.x + Math.cos(this.dir) * this.length,
this.pos.y + Math.sin(this.dir) * this.length
);
ctx.stroke();
}
get end() {
return new Vector(
this.pos.x + Math.cos(this.dir) * this.length,
this.pos.y + Math.sin(this.dir) * this.length
);
}
get line() {
return new Line(this.pos, this.end);
}
cast(lines) {
const tLine = this.line;
var minDist = 1, point;
for (const line of lines) {
const result = line.intercept(tLine);
if (result) {
const [u, x, y] = result;
if (u <= minDist) {
minDist = u;
if (!point) { point = new Vector(x, y) }
else {
point.x = x;
point.y = y;
}
point.u = u;
}
}
}
return point;
}
}
function init() {
walls.length = 0;
for (let i = 0; i < WALL_COUNT / 2; i++) {
walls.push(new Ray(
new Vector(Math.rand(0, canvas.width * 0.4), Math.rand(0, canvas.height)),
(Math.rand(0, 8) | 0) / 4 * Math.PI, 100
).line);
walls.push(new Ray(
new Vector(Math.rand(canvas.width * 0.6, canvas.width), Math.rand(0, canvas.height)),
(Math.rand(0, 8) | 0) / 4 * Math.PI, 100
).line);
}
if(!myRay) {
myRay = new Ray(new Vector(canvas.width / 2, canvas.height / 2), 0, Math.max(canvas.width, canvas.height) * 0.485);
requestAnimationFrame(mainLoop);
}
}
function mainLoop(time) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
myRay.dir = (time / (ROTATE_RAY * 1000)) * Math.TAU;
const point = myRay.cast(walls)
myRay.draw(ctx, RAY_STYLE_A);
for(const w of walls) { w.draw(ctx, WALL_STYLE) }
if (point) {
const len = myRay.length;
myRay.length = point.u * len;
myRay.draw(ctx, RAY_STYLE_B);
myRay.length = len;
point.draw(ctx, RAY_INTERCEPT_STYLE);
}
requestAnimationFrame(mainLoop);
}
#canvas {
border: 2px solid black;
}
<canvas id="canvas" width="500" height="500"> </canvas>

given 2 center coordinates, how to find all Rectangle axes?

for a game I'm building, I need to draw a rectangle on two sides of a line made from two coordinates.
I have an image illustrating this "hard to ask" question.
given coordinates (-4,3) and (3, -4)
given that the width of the rectangle will be 4 (for example)
I need to find all (x1, y1), (x2, y2), (x3, y3), (x4, y4)
** I need to write this in Javascript eventually.
your help is much appreciated.
I've tried to solve this using javascript & canvas. The problem is that the coordinates in canvas are upside down, I suppose you already know this. Also since your rect would be extremely small, I've multiplied your numbers by 10.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 300,
cx = cw / 2;
let ch = canvas.height = 300,
cy = ch / 2;
const rad = Math.PI / 180;
ctx.translate(cx,cy)
//axis
ctx.strokeStyle = "#d9d9d9";
ctx.beginPath();
ctx.moveTo(-cx,0);
ctx.lineTo(cx,0);
ctx.moveTo(0,-cy);
ctx.lineTo(0,cy);
ctx.stroke();
// your data
let p1={x:-40,y:30};
let p2={x:30,y:-40};
// the angle of the initial line
let angle = Math.atan2(p2.y-p1.y, p2.x-p1.x);
// the center of the line
let c =
{ x: p1.x + (p2.x - p1.x)/2,
y: p1.y + (p2.y - p1.y)/2
}
let w = dist(p1, p2);//the width of the rect
let h = 60;//the height of the rect
// draw the initial line
line(p1,p2);
// draw the center as a red point
marker(c);
// calculate the opoints of the rect
function rectPoints(w,h){
let p1 = {
x : c.x -w/2,
y : c.y -h/2
}
let p2 = {
x : c.x + w/2,
y : c.y -h/2
}
let p3 = {
x : c.x + w/2,
y : c.y +h/2
}
let p4 = {
x : c.x -w/2,
y : c.y +h/2
}
// this rotate all the points relative to the center c
return [
rotate(p1,c, angle),
rotate(p2,c, angle),
rotate(p3,c, angle),
rotate(p4,c, angle)
]
}
// draw the rect
ctx.strokeStyle = "blue";
drawRect(rectPoints(w,h));
// some helpful functions
function line(p1,p2){
ctx.beginPath();
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(p2.x,p2.y);
ctx.stroke();
}
function dist(p1, p2) {
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
function marker(p,color){
ctx.beginPath();
ctx.fillStyle = color || "red";
ctx.arc(p.x,p.y,4,0,2*Math.PI);
ctx.fill();
}
function rotate(p,c, angle){
let cos = Math.cos(angle);
let sin = Math.sin(angle);
return {
x: c.x + (p.x - c.x) * cos - (p.y - c.y) * sin,
y: c.y + (p.x - c.x) * sin + (p.y - c.y) * cos
}
}
function drawRect(points){
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
ctx.lineTo(points[1].x,points[1].y);
ctx.lineTo(points[2].x,points[2].y);
ctx.lineTo(points[3].x,points[3].y);
ctx.lineTo(points[0].x,points[0].y);
ctx.closePath();
ctx.stroke();
}
canvas{border:1px solid #d9d9d9}
<canvas></canvas>
Points A, B form vector
M.X = B.X - A.X
M.Y = B.Y - A.Y
Perpendicular vector
P.X = -M.Y
P.Y = M.X
Length of P:
Len = Math.sqrt(P.X*P.X + P.Y*P.Y)
Normalized (unit) perpendicular:
uP.X = P.X / Len
uP.Y = P.Y / Len
Points
X1 = A.X + uP.X * HalfWidth
Y1 = A.Y + uP.Y * HalfWidth
(X4, Y4) = (A.X - uP.X * HalfWidth, A.Y - uP.Y * HalfWidth)
and similar for points 2 and 3 around B

Categories

Resources