Polygon Generator - javascript

I'm trying to generate a polygon with circles inside (JS canvas). Here's a sample expected output:
It's basically a 4-sided polygon (square) with circles next to the vertices. Here is what I tried:
However, I don't get the expected outcome.
Note: I want this to work for any sized polygon and not just a square. Also, stopping the draw() function to execute gives me a proper square. I believe there's a problem in the draw() function. Any help is appreciated :)
function draw(x, y, ctx){
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.fillStyle = "#283149";
ctx.fill(); // create circle
}
function createPolygon(n){
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext('2d');
ctx.reset();
var size = 60, Xcenter = 80, Ycenter = 80;
ctx.beginPath();
ctx.moveTo (Xcenter + size * Math.cos(0), Ycenter + size * Math.sin(0));
for (var i = 1; i <= n; i++) {
ctx.lineTo (Xcenter + size * Math.cos(i * 2 * Math.PI / n), Ycenter + size * Math.sin(i * 2 * Math.PI / n));
draw(Xcenter + Math.cos(i * 2 * Math.PI / n), Ycenter + Math.sin(i * 2 * Math.PI / n), ctx);
}
ctx.fillStyle = "#00818A"
ctx.fill();
}
<button onclick="createPolygon(4)">Create 4</button>
<canvas id="canvas"></canvas>

Focus on drawing the polygon first. Then you can add the circles relative to the points of the polygon.
const main = () => {
const ctx = document.getElementById('poly').getContext('2d');
Object.assign(ctx.canvas, { width: 350, height: 150 });
Object.assign(ctx, { strokeStyle: 'red', lineWidth: 1 });
drawPolygon(ctx, 60, 75, 6, 50, {
nodeSize: 5,
nodeInset: 10
});
drawPolygon(ctx, 180, 75, 4, 50, {
nodeInset: 25,
nodeSize: 10,
rotation: Math.PI / 4
});
drawPolygon(ctx, 290, 75, 3, 50, {
nodeSize: 4,
rotation: Math.PI / 3
});
};
const defaultPolygonOptions = {
nodeInset: 0,
nodeSize: 0,
rotation: 0,
};
/**
* #param {CanvasRenderingContext2D} ctx - Canvas 2D context
* #param {Number} x - origin x-position
* #param {Number} y - origin y-position
* #param {Number} n - number of points
* #param {Number} radius - radius of polygon
* #param {Object} [options] - configuration options
* #param {Number} [options.nodeInset] - insets for nodes
* #param {Number} [options.nodeSize] - size of node
* #param {Number} [options.rotation] - polygon rotation
*/
const drawPolygon = (ctx, x, y, n, radius, options = {}) => {
const opts = { ...defaultPolygonOptions, ...options };
ctx.beginPath();
ctx.moveTo(
x + radius * Math.cos(opts.rotation),
y + radius * Math.sin(opts.rotation)
);
for (let i = 1; i <= n; i += 1) {
const angle = (i * (2 * Math.PI / n)) + opts.rotation;
ctx.lineTo(
x + radius * Math.cos(angle),
y + radius * Math.sin(angle)
);
}
ctx.stroke();
if (!opts.nodeSize) return;
const dist = radius - opts.nodeInset;
for (let i = 1; i <= n; i += 1) {
const angle = (i * (2 * Math.PI / n)) + opts.rotation;
ctx.beginPath();
ctx.arc(
x + dist * Math.cos(angle),
y + dist * Math.sin(angle),
opts.nodeSize,
0,
2 * Math.PI
);
ctx.stroke();
}
};
main();
canvas { background: #FF0; }
<canvas id="poly"></canvas>
Inspired by: How to draw polygons on an HTML5 canvas?
Animation
Here is a basic animation that is FPS throttled.
const ctx = document.getElementById('poly').getContext('2d');
let state = {
magnitude: 1,
origin: { x: 0, y: 0 },
points: 4,
radius: 60,
rotation: Math.PI / 4,
nodeInset: 16,
nodeSize: 8,
nodeRotation: Math.PI / 4,
};
const main = () => {
init();
requestAnimationFrame(update);
};
const init = () => {
Object.assign(ctx.canvas, { width: 160, height: 160 });
Object.assign(ctx, { strokeStyle: 'green', lineWidth: 1 });
Object.assign(state, {
origin: {
x: ctx.canvas.width / 2,
y: ctx.canvas.height / 2
}
});
};
// https://stackoverflow.com/a/65829199/1762224
const FPS = 30;
let lastTimestamp = 0;
const update = (timestamp) => {
let inset = state.nodeInset + state.magnitude;
if (inset > state.radius) {
inset = state.radius;
state.magnitude *= -1;
} else if (inset < state.nodeSize) {
inset = state.nodeSize;
state.magnitude *= -1;
}
state.nodeInset = inset;
state.nodeRotation += (Math.PI / 36);
requestAnimationFrame(update);
if (timestamp - lastTimestamp < 1000 / FPS) return;
redraw();
lastTimestamp = timestamp;
};
const redraw = () => {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
drawPolygon(ctx, state.origin.x, state.origin.y, state.points, state.radius, {
nodeInset: state.nodeInset,
nodeSize: state.nodeSize,
nodeRotation: state.nodeRotation,
rotation: state.rotation
});
}
const defaultPolygonOptions = {
nodeInset: 0,
nodeSize: 0,
rotation: 0,
};
/**
* #param {CanvasRenderingContext2D} ctx - Canvas 2D context
* #param {Number} x - origin x-position
* #param {Number} y - origin y-position
* #param {Number} n - number of points
* #param {Number} radius - radius of polygon
* #param {Object} [options] - configuration options
* #param {Number} [options.nodeInset] - insets for nodes
* #param {Number} [options.nodeSize] - size of node
* #param {Number} [options.nodeRotation] - rotation of nodes
* #param {Number} [options.rotation] - polygon rotation
*/
const drawPolygon = (ctx, x, y, n, radius, options = {}) => {
const opts = { ...defaultPolygonOptions, ...options };
ctx.beginPath();
ctx.moveTo(
x + radius * Math.cos(opts.rotation),
y + radius * Math.sin(opts.rotation)
);
for (let i = 1; i <= n; i += 1) {
const angle = (i * (2 * Math.PI / n)) + opts.rotation;
ctx.lineTo(
x + radius * Math.cos(angle),
y + radius * Math.sin(angle)
);
}
ctx.stroke();
if (!opts.nodeSize) return;
const dist = radius - opts.nodeInset;
for (let i = 1; i <= n; i += 1) {
const angle = (i * (2 * Math.PI / n)) + opts.nodeRotation;
ctx.beginPath();
ctx.arc(
x + dist * Math.cos(angle),
y + dist * Math.sin(angle),
opts.nodeSize,
0,
2 * Math.PI
);
ctx.stroke();
}
};
main();
canvas { background: #222; }
<canvas id="poly"></canvas>

You have to close paths, so you have to use two loops
function draw(x, y, ctx) {
ctx.beginPath();
ctx.arc(x, y, 4, 0, Math.PI * 2);
ctx.fillStyle = "#283149";
ctx.fill(); // create circle
ctx.closePath()
}
function createPolygon(n) {
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext('2d');
ctx.reset();
var size = 60,
Xcenter = 80,
Ycenter = 80;
ctx.beginPath();
ctx.moveTo(Xcenter + size * Math.cos(0), Ycenter + size * Math.sin(0));
for (let i = 1; i <= n; i++) {
ctx.lineTo(Xcenter + size * Math.cos(i * 2 * Math.PI / n), Ycenter + size * Math.sin(i * 2 * Math.PI / n));
}
ctx.fillStyle = "#00818A"
ctx.fill();
ctx.closePath()
for (let i = 1; i <= n; i++) {
draw(Xcenter + Math.cos(i * 2 * Math.PI / n), Ycenter + Math.sin(i * 2 * Math.PI / n), ctx);
}
}
<button onclick="createPolygon(4)">Create 4</button>
<canvas id="canvas"></canvas>

Related

Get the points for the corners of a rectangle

I'm given an array of points for a rectangle, sorted from top-left position onwards to each corner clockwise. I need to find the start and end points for a line of each specific corner length from the corner to each side of the rectangle. My code is rotating the points by some random angle just to illustrate the problem but in my actual scenario I only have the corner points and need the start and end points to map out each corner. I'm currently offsetting each point as if the point using directional value array that match to the order of the points in the array when the rectangle isn't rotated, but it doesn't work because the points can be rotated at which point this order also changes. There's an application of sine and cosine that I fail to grasp I'm sure. How can I get the value of each corner point offset by a given length towards each of its sides aligned so as to everything rotates together uniformly?
function rotate(cx, cy, x, y, angle) {
var radians = (Math.PI / 180) * angle,
cos = Math.cos(radians),
sin = Math.sin(radians),
nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return {x: nx, y: ny};
}
const colors =["red","blue", "green","black"];
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let centroid = {};
let points = [{x:30,y:30},{x:110, y:30},{x:110,y:110},{x:30, y:110}];
function update() {
const randAngle = Math.random() * 180 * (Math.random() > 0.5 ? -1:1);
const length = points.length;
centroid = points.reduce((last, current)=> {
last.x += current.x / length;
last.y += current.y / length;
return last;
}, {x: 0, y:0});
points =
points.map(point=> rotate(centroid.x,
centroid.y,
point.x,
point.y,
randAngle));
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw centroid
ctx.beginPath();
ctx.arc(centroid.x, centroid.y, 4, 0, Math.PI * 2);
ctx.stroke();
// draw Square
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for(let i=1;i<points.length;i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.closePath();
ctx.stroke();
// draw corner points
for(let i=0;i < points.length;i++) {
ctx.beginPath();
ctx.fillStyle = colors[i%colors.length];
ctx.arc(points[i].x, points[i].y, 3, 0, Math.PI * 2);
ctx.fill();
}
const cornerLength = 10;
const startPointDirections = [{ x: 0, y: 1 },
{ x: -1, y: 0 },
{ x: 0, y: -1 },
{ x: 1, y: 0 }];
const endPointDirections = [{ x: 1, y: 0 },
{ x: 0, y: 1 },
{ x: -1, y: 0 },
{ x: 0, y: -1 }];
// draw corner start points and endpoints
for(let i=0;i < points.length;i++) {
ctx.beginPath();
ctx.fillStyle = colors[i%colors.length];
ctx.arc(startPointDirections[i].x * cornerLength + points[i].x, startPointDirections[i].y * cornerLength + points[i].y, 3, 0, Math.PI * 2);
ctx.arc(endPointDirections[i].x * cornerLength + points[i].x, endPointDirections[i].y * cornerLength + points[i].y, 3, 0, Math.PI * 2);
ctx.fill();
}
}
setInterval(()=> {
update();
draw();
}, 2000);
<canvas></canvas>
Seems you need to apply your rotate function to offset points relative to rotated corner, but you have no angle value at this moment. So you can just get direction vectors from rotated sides.
function rotate(cx, cy, x, y, angle) {
var radians = (Math.PI / 180) * angle,
cos = Math.cos(radians),
sin = Math.sin(radians),
nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
return {x: nx, y: ny};
}
const colors =["red","blue", "green","black"];
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let centroid = {};
let points = [{x:30,y:30},{x:110, y:30},{x:110,y:110},{x:30, y:110}];
function update() {
const randAngle = Math.random() * 180 * (Math.random() > 0.5 ? -1:1);
const length = points.length;
centroid = points.reduce((last, current)=> {
last.x += current.x / length;
last.y += current.y / length;
return last;
}, {x: 0, y:0});
points =
points.map(point=> rotate(centroid.x,
centroid.y,
point.x,
point.y,
randAngle));
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw centroid
ctx.beginPath();
ctx.arc(centroid.x, centroid.y, 4, 0, Math.PI * 2);
ctx.stroke();
// draw Square
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
for(let i=1;i<points.length;i++) {
ctx.lineTo(points[i].x, points[i].y);
}
ctx.closePath();
ctx.stroke();
// draw corner points
for(let i=0;i < points.length;i++) {
ctx.beginPath();
ctx.fillStyle = colors[i%colors.length];
ctx.arc(points[i].x, points[i].y, 3, 0, Math.PI * 2);
ctx.fill();
}
const cornerLength = 10;
// draw corner start points and endpoints
for(let i=0;i < points.length;i++) {
ctx.beginPath();
ctx.fillStyle = colors[i%colors.length];
let dx = points[i].x - points[(i+3)%4].x //previous index in cyclic manner
let dy = points[i].y - points[(i+3)%4].y
let len = Math.sqrt(dx*dx+dy*dy) //really you know side length so use known value
dx = cornerLength * dx/len //normalized vector multiplied by magnitude
dy = cornerLength * dy/len
startx = points[i].x + dx
starty = points[i].y + dy
endx = points[i].x + dy //here apply rotated by -Pi/2 offset vector
endy = points[i].y - dx
ctx.arc(startx, starty, 3, 0, Math.PI * 2);
ctx.arc(endx, endy, 3, 0, Math.PI * 2);
ctx.fill();
}
}
setInterval(()=> {
update();
draw();
}, 2000);
<canvas></canvas>
(Really dx/len and dy/len are equal to cos(angle) and sin(angle) or vice versa, with different combinations of signs)

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 randomize the speed of rotating circle?

I have two circles rotating round the circle and I would like the circle to change the speed at random after passing one revolution.Both circle should be at different speed or they might be the same speed (then collision will occurs). For example, during first run, both circles are moving at 10m/s and after it reaches end of the revolution,they will collide.Let's say after the revolution, it changes circle 1 to 15m/s and circle 2 to 30m/s , then they won't collide.I would like to know how to achieve this. This is just an idea of what i am trying to achieve. It would be even better if the speed is randomized after every revolution.
Any help would be appreciated.
Code:
(function() {
var ctx = document.getElementById("canvas").getContext("2d"),
x1 = 160,
y1 = 120,
x2 = 330,
y2 = 280,
radius = 20;
angle = 0,
velX = 0,
velY = 0,
thrust = 3,
rotation = 0;
function draw() {
velX = Math.cos(angle * Math.PI / 180) * thrust;
velY = Math.sin(angle * Math.PI / 180) * thrust;
x1 += velX;
y1 += velY;
angle += 1;
ctx.fillStyle = "#000";
ctx.clearRect(0, 0, 550, 400);
ctx.beginPath();
ctx.arc(x1, y1, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
draw2();
setTimeout(function() {
draw()
}, 30);
}
function draw2() {
velX = Math.cos(angle * Math.PI / 180) * thrust;
velY = Math.sin(angle * Math.PI / 180) * thrust;
x2 += -velX;
y2 += -velY;
angle += 1;
ctx.fillStyle = "#80ced6";
ctx.beginPath();
ctx.arc(x2, y2, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
collisiondetection();
}
var distance = 0;
var totalcounter = 0;
var collide = false;
function collisiondetection() {
distance = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
if (distance < radius * 2) {
if (collide == false) {
totalcounter = totalcounter + 1;
document.getElementById("cTotal").innerHTML = "Total collisions:" + totalcounter;
collide = true;
}
} else {
collide = false;
}
}
draw();
})();
<canvas id="canvas" width="550" height="400" style="background:#eee;"></canvas>
<span id="cTotal">Total collisions: </span>
What you can do is to declare a local speed variable to the draw() function, then pass it to the setTimeout() callback, like this:
var speed = Math.floor(Math.random() * 11);
draw2();
setTimeout(function() {
draw()
}, speed);
The code Math.floor(Math.random() * 11) will give you a random number between 0 and 10 so setTimeout will be called with a different speed each time.
Demo:
window.onload = function() {
(function() {
var ctx = document.getElementById("canvas").getContext("2d"),
x1 = 160,
y1 = 120,
x2 = 330,
y2 = 280,
radius = 20;
angle = 0,
velX = 0,
velY = 0,
thrust = 3,
rotation = 0;
function draw() {
velX = Math.cos(angle * Math.PI / 180) * thrust;
velY = Math.sin(angle * Math.PI / 180) * thrust;
x1 += velX;
y1 += velY;
angle += 1;
ctx.fillStyle = "#000";
ctx.clearRect(0, 0, 550, 400);
ctx.beginPath();
ctx.arc(x1, y1, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
var speed = Math.floor(Math.random() * 11);
draw2();
setTimeout(function() {
draw()
}, speed);
}
function draw2() {
velX = Math.cos(angle * Math.PI / 180) * thrust;
velY = Math.sin(angle * Math.PI / 180) * thrust;
x2 += -velX;
y2 += -velY;
angle += 1;
ctx.fillStyle = "#80ced6";
ctx.beginPath();
ctx.arc(x2, y2, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
collisiondetection();
}
var distance = 0;
var totalcounter = 0;
var collide = false;
function collisiondetection() {
distance = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
if (distance < radius * 2) {
if (collide == false) {
totalcounter = totalcounter + 1;
document.getElementById("cTotal").innerHTML = "Total collisions:" + totalcounter;
collide = true;
}
} else {
collide = false;
}
}
draw();
})();
}
<canvas id="canvas" width="550" height="400" style="background:#eee;"></canvas>
<span id="cTotal">Total collisions: </span>
You can change the timeGap when angle becomes 360, i.e., when a revolution completes. Thus, it will appear as the speed is getting changed.
Inside your draw() function:
if(angle % 360 == 0) {
timeGap = Math.random() * 20 + 5;
}
(function() {
var ctx = document.getElementById("canvas").getContext("2d"),
x1 = 160,
y1 = 120,
x2 = 330,
y2 = 280,
radius = 20,
angle1 = 0,
angle2 = 0,
velX = 0,
velY = 0,
thrust = 3,
rotation = 0,
timeGap1 = 10,
timeGap2 = 10,
diff = 20,
minTimeGap = 20;
function draw() {
velX = Math.cos(angle1 * Math.PI / 180) * thrust;
velY = Math.sin(angle1 * Math.PI / 180) * thrust;
x1 += velX;
y1 += velY;
angle1 += 2;
ctx.fillStyle = "#000";
ctx.beginPath();
ctx.arc(x1, y1, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
if(angle1 % 360 == 0) {
timeGap1 = Math.random() * diff + minTimeGap;
}
setTimeout(function() {
draw();
}, timeGap1);
}
function draw2() {
velX = Math.cos(angle2 * Math.PI / 180) * thrust;
velY = Math.sin(angle2 * Math.PI / 180) * thrust;
x2 += -velX;
y2 += -velY;
angle2 += 2;
ctx.fillStyle = "#007700";
ctx.beginPath();
ctx.arc(x2, y2, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
if(angle2 % 360 == 0) {
timeGap2 = Math.random() * diff + minTimeGap;
}
setTimeout(function() {
draw2();
}, timeGap2);
}
function clearCanvas() {
ctx.fillStyle = 'rgba(220,220,220,0.5)';
ctx.fillRect(0, 0, 550, 400);
collisiondetection();
setTimeout(function() {
clearCanvas();
}, timeGap2 + timeGap1);
}
var distance = 0;
var totalcounter = 0;
var collide = false;
function collisiondetection() {
distance = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
if (distance < radius * 2) {
if (collide == false) {
totalcounter = totalcounter + 1;
document.getElementById("cTotal").innerHTML = "Total collisions:" + totalcounter + "<br/>Speed1: " + timeGap1 + "<br/>Speed2: " + timeGap2;
collide = true;
}
} else {
collide = false;
}
}
draw();
draw2();
clearCanvas();
})();
<canvas id="canvas" width="550" height="400" style="background:#eee;"></canvas>
<span id="cTotal">Total collisions: </span>
(function() {
var ctx = document.getElementById("canvas").getContext("2d"),
x1 = 160,
y1 = 120,
x2 = 330,
y2 = 280,
radius = 20;
angle = 0,
velX = 0,
velY = 0,
thrust = 3,
rotation = 0,
maxSpeed = 100,
speed = Math.floor(Math.random() * 100) + 1;
function draw() {
velX = Math.cos(angle * Math.PI / 180) * thrust;
velY = Math.sin(angle * Math.PI / 180) * thrust;
x1 += velX;
y1 += velY;
angle += 1;
ctx.fillStyle = "#000";
ctx.clearRect(0, 0, 550, 400);
ctx.beginPath();
ctx.arc(x1, y1, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
draw2();
SpeedCount();
}
function draw2() {
velX = Math.cos(angle * Math.PI / 180) * thrust;
velY = Math.sin(angle * Math.PI / 180) * thrust;
x2 += -velX;
y2 += -velY;
angle += 1;
ctx.fillStyle = "#80ced6";
ctx.beginPath();
ctx.arc(x2, y2, radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
collisiondetection();
}
function SpeedCount(){
(function(speed) {
setTimeout(function() {
draw()
}, speed);
})(speed);
}
var distance = 0;
var totalcounter = 0;
var collide = false;
function collisiondetection() {
distance = Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
if (distance < radius * 2) {
if (collide == false) {
totalcounter = totalcounter + 1;
document.getElementById("cTotal").innerHTML = "Total collisions:" + totalcounter;
collide = true;
}
} else {
collide = false;
}
if((angle + 90) % 360 == 0){
speed = Math.floor(Math.random() * maxSpeed) + 1;
}
}
draw();
})();
<canvas id="canvas" width="550" height="400" style="background:#eee;"></canvas>
<span id="cTotal">Total collisions: </span>
try this one.
Let's rework it a bit:
First, when programming a graphical animation, you should separate your objects logic and your drawing operations.
The drawing operations should occur at regular intervals, based on the screen refresh rate (to avoid drawing sometimes twice a frame and sometimes not drawing at all).
A convenient method available in the Web API for this exact case is requestAnimationFrame(callback). It will queue our callback to fire just before the next screen refresh rate.
So we can use it as the core of our animation loop, that will always fire at the same rate as our screen can show (usually 60FPS).
When you have animated objects on your scene, the simplest data-structure in js, are Objects. Instead of holding a lot of variables everywhere, you pack them in their own object.
When you have multiple such objects, you hold them all together (e.g in an Array).
Now, in our animation loop, we will first update our objects position, then draw them.
It is in the update part that we will control the velocity of our objects, and only after we did update all the object, we will check if they collide.
(function() {
var ctx = document.getElementById("canvas").getContext("2d"),
max_speed = Math.PI / 12,
objects = [{
center_x: 160,
center_y: 120,
speed: Math.random() % max_speed,
angle: 0,
color: '#000'
},
{
center_x: 330,
center_y: 280,
speed: -(Math.random() % max_speed),
angle: 0,
color: "#80ced6"
}
],
radius = 20,
outerRad = 120,
totalcounter = 0,
collide = true;
anim(); // begin our main anim loop
function anim() {
update(); // our objects update logic
draw(); // now we draw
collisiondetection(); // DOM update
requestAnimationFrame(anim); // start again #next-frame
}
function update() {
// here we only change the object's properties
// nothing graphical should come here
objects.forEach(function(object) {
var angle = object.angle;
object.x = Math.cos(angle) * outerRad + object.center_x;
object.y = Math.sin(angle) * outerRad + object.center_y;
object.angle += object.speed;
});
}
function draw() {
// here is only the graphical part
// no logic should come here
ctx.clearRect(0, 0, 550, 400);
objects.forEach(function(object) {
ctx.fillStyle = object.color;
ctx.beginPath();
ctx.arc(object.x, object.y, radius, 0, Math.PI * 2);
ctx.fill();
});
}
function collisiondetection() {
var o1 = objects[0],
o2 = objects[1];
var distance = Math.sqrt((o1.x - o2.x) * (o1.x - o2.x) + (o1.y - o2.y) * (o1.y - o2.y));
if (distance < radius * 2) {
if (collide == false) {
totalcounter = totalcounter + 1;
document.getElementById("cTotal").innerHTML = "Total collisions:" + totalcounter;
collide = true;
}
} else {
collide = false;
}
}
// and now if you want to update randomly these object's speed, you can do from anywhere
document.onclick = function() {
objects[0].speed = Math.random() % max_speed;
objects[1].speed = -(Math.random() % max_speed);
};
})();
<canvas id="canvas" width="550" height="400" style="background:#eee;"></canvas>
<p id="cTotal">Total collisions: </p>

Sinusoidal or other custom move type between two points

I am trying to build a function that moves bullets in mini game. For the moment i am doing it in very simple way.
Have function to calculate radian angle between two points:
this.rftv = (p1, p2) => Math.atan2(p2.y - p1.y, p2.x - p1.x);
Have to different points and calculate angle between them:
var x = objects[this.destination].x,
y = objects[this.destination].y,
rad = tools.rftv( { x: this.x, y: this.y }, { x: x, y: y } );
Define speed boost:
this.attack_speed = 2.5;
Move bullet using angle and speed boost:
this.x += Math.cos(rad) * this.attack_speed;
this.y += Math.sin(rad) * this.attack_speed;
What i am trying to do is to not move bullets in linear way, i am rather trying to move bullets using sinus wave, to achieve something like this:
I have no idea how to start to build it, maybe someone could help and write a function that would take two points and make object move sinusoidal between them.
I suggest you add a few more variables to each instance:
// initialization
this.distance = 0;
// have a max amplitude of about 30-50 depending on how you want it to look
this.amplitude = (Math.random() * 2 - 1) * MAX_AMPLITUDE;
// have a fixed period somewhere between 10-50 depending on how you want it to look
this.period = 30;
this.initial = { x: this.x, y: this.y };
// on each frame
this.distance += this.attack_speed;
this.x = this.initial.x + Math.cos(this.rad) * this.distance;
this.y = this.initial.y + Math.sin(this.rad) * this.distance;
const deviation = Math.sin(this.distance * Math.PI / this.period) * this.amplitude;
this.x += Math.sin(this.rad) * deviation;
this.y -= Math.cos(this.rad) * deviation;
Turns out I had a slight error with the math, it's corrected now, along with a very basic demo below.
A positive amplitude should cause the initial trajectory of the bullet to go at an angle slightly counter-clockwise compared to the angle from point A to point B, then oscillate back and forth on the way to B.
class Bullet {
constructor({initial = {}, destination = {}, amplitude = 50, period = 30, speed = 2.5} = {}) {
let { x: ix, y: iy } = this.initial = initial;
let { x: dx, y: dy } = this.destination = destination;
this.amplitude = (Math.random() * 2 - 1) * amplitude;
this.period = period;
this.speed = speed;
this.distance = 0;
this.x = ix;
this.y = iy;
this.rad = Math.atan2(dy - iy, dx - ix);
}
update() {
this.distance += this.speed;
this.x = this.initial.x + Math.cos(this.rad) * this.distance;
this.y = this.initial.y + Math.sin(this.rad) * this.distance;
const deviation = Math.sin(this.distance * Math.PI / this.period) * this.amplitude;
this.x += Math.sin(this.rad) * deviation;
this.y -= Math.cos(this.rad) * deviation;
}
}
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
let initial = {
x: canvas.width / 4,
y: canvas.height * 3 / 4
};
let destination = {
x: canvas.width * 3 / 4,
y: canvas.height / 4
};
let bullet = new Bullet({initial, destination});
console.log(bullet.amplitude);
function draw() {
requestAnimationFrame(draw);
// ctx.clearRect(0, 0, canvas.width, canvas.height);
bullet.update();
ctx.fillStyle = '#0000FF';
ctx.fillRect(bullet.x, bullet.y, 1, 1);
ctx.fillStyle = '#FF0000';
ctx.fillRect(canvas.width / 4, canvas.height * 3 / 4, 1, 1);
ctx.fillStyle = '#00FF00';
ctx.fillRect(canvas.width *3 / 4, canvas.height / 4, 1, 1);
}
draw();
<canvas width="500" height="200"></canvas>

Js - calculate distance from point mapped to colour forms square?

I am working on a project where I visualise the effect of a magnetic dipole, on a range of vectors, I'm just testing with one pole at the moment and something doesn't work but I don't know why.
the force that a vector receives is mapped onto a color to check if I did it right, these are my results:
so this is the canvas I'm working with
and when I lower the size of each vector and increase the density you can see this forms diamonds rather than a circular pattern.
Does anybody know why this is or what could be causing it?
code below here:
function calcForce(magnet, vector){
return 1/distance(magnet.x,magnet.y,vector.centerx,vector.centery) * magnet.force;
}
function distance(cx, cy, ex, ey){
var dy = Math.abs(ey - cy);
var dx = Math.abs(ex - cx);
return Math.sqrt((dx^2) + (dy^2));
}
function mapRainbow(value) {
return 'hsl(' + value + ',100%,50%)';
}
function map_range(value, low1, high1, low2, high2) {
return low2 + (high2 - low2) * (value - low1) / (high1 - low1);
}
function mapForce(force){
return map_range(force,10,1000,20,40);
}
function drawStroke(stroke){
ctx.beginPath();
ctx.moveTo(stroke.x1,stroke.y1);
ctx.lineTo(stroke.x2,stroke.y2);
stroke.color = mapRainbow(stroke.force);
ctx.strokeStyle = stroke.color;
ctx.stroke();
ctx.closePath();
}
*this is not all the code by far but I think this is enough, need to see more? just ask.
Use distance to generate gradient:
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 500;
canvas.height = 500;
document.body.appendChild(canvas);
function distance(x1, y1, x2, y2) {
return Math.sqrt((x2 -= x1) * x2 + (y2 -= y1) * y2);
}
function angleBetweenPoints(x1, y1, x2, y2) {
return (Math.atan2(x2 - x1, y2 - y1) + 2 * Math.PI);
}
var center = { x: 250, y: 250 };
var vectorLength = 15;
function max(v, m) {
if (v > m) {
return m;
}
return v;
}
function draw() {
if (Math.random() > 0.5) {
center.x += (Math.random() - 0.5) * 10;
}
else {
center.y += (Math.random() - 0.5) * 10;
}
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var xIndex = 0; xIndex < canvas.width; xIndex += vectorLength) {
for (var yIndex = 0; yIndex < canvas.height; yIndex += vectorLength) {
var x = xIndex - (Math.random() * vectorLength * 0.0);
var y = yIndex - (Math.random() * vectorLength * 0.0);
var angle = angleBetweenPoints(center.x, center.y, x, y);
var dist = distance(x, y, center.x, center.y);
ctx.fillStyle = "rgb(" + Math.floor(max(dist, 255)) + "," + Math.floor((255 - max(dist, 255))) + ",0)";
ctx.translate((x + vectorLength * 0.5), (y + vectorLength * 0.5));
ctx.rotate(-angle);
ctx.fillRect(0 - vectorLength * 0.5, 0 - vectorLength * 0.5, vectorLength * 0.25, vectorLength * 0.75);
ctx.rotate(angle);
ctx.translate(0 - (x + vectorLength * 0.5), 0 - (y + vectorLength * 0.5));
}
}
ctx.fillRect(center.x + vectorLength / 2, center.y + vectorLength / 2, vectorLength, vectorLength);
requestAnimationFrame(function () {
setTimeout(draw, 1000 / 60);
});
}
draw();

Categories

Resources