I'm trying to rotate a circle around another circle in 2d.
I have the following code implemented, which rotates the circle on its axis, however, I want to rotate the circle around the other one.
var w = window.innerWidth;
var h = window.innerHeight;
var canvas = document.createElement('canvas');
document.body.appendChild(canvas);
canvas.width = w;
canvas.height = h;
var ctx = canvas.getContext('2d');
var point = function(x,y,z,rgb) {this.x=x;this.y=y;this.z=z;this.rgb=rgb;}
class Circle {
constructor(x,y,z,radius) {
this.x=x;
this.y=y;
this.z=z;
this.radius=radius;
this.points = [];
for(let i =0; i<360; i++) {
let angle = Math.PI / 180 * i;
let cos = this.x + Math.cos(angle) * this.radius;
let sin = this.y + Math.sin(angle) * this.radius;
this.points.push(new point(cos, sin, this.z, `rgb(${i}, ${i}, ${i})`));
}
}
rotate() {
for(let i =0; i<this.points.length; i++) {
let p = this.points[i];
let dx = Math.cos(Math.PI / 180) * (p.x - this.x) - Math.sin(Math.PI / 180) * (p.y - this.y);
let dy = Math.sin(Math.PI / 180) * (p.x - this.x) + Math.cos(Math.PI / 180) * (p.y - this.y);
p.x = dx + this.x;
p.y = dy + this.y;
}
}
drawX(points, i) {
let p = points[i];
ctx.beginPath();
ctx.strokeStyle = p.rgb;
ctx.moveTo(p.x, p.y);
ctx.lineTo(p.x + 1, p.y + 1);
ctx.stroke();
}
draw(ctx) {
for(let i =0; i<this.points.length; i++) {
this.drawX(this.points, i);
}
}
}
circle1 = new Circle(w/2, h/2, 10, 50);
circle2 = new Circle(w/2+200, h/2, 10, 50);
function rotateAround(p1, p2) {
for(let i =0; i<p1.length; i++) {
for(let j =0; j<p2.length; j++) {
let dx = Math.cos(Math.PI / 180) * (p1[i].x - p2[j].x) - Math.sin(Math.PI / 180) * (p1[i].y - p2[j].y);
let dy = Math.sin(Math.PI / 180) * (p1[i].x - p2[j].x) + Math.cos(Math.PI / 180) * (p1[i].y - p2[j].y);
p1[i].x = dx + p2[j].x;
p1[i].y = dy + p2[j].y;
}
}
}
function render(now) {
//now *= 0.001;
circle1.draw(ctx);
circle1.rotate();
circle2.draw(ctx);
circle2.rotate();
//rotateAround(circle1.points, circle2.points);
requestAnimationFrame(render);
}
render();
Unfortunately, the circles rotate but not around one another. What I have tried can be seen in the rotateAround method.
How do I go about solving this?
Any help would be muchly appreciated
Some remarks on your code:
Instead of calculating the next (rotated) point coordinate from what it was was, keeping track of all points in an array, it seems more straightforward to just maintain a current angle, and then calculate the rotated coordinates from the angle from scratch at the moment you want to draw.
You're mixing two constructor syntaxes: the old one for point
and the new one (class
) for Circle
. I would harmonize this and name the first constructor with a capital P
.
If I understood correctly, there is only one circle that actually needs to move around, while the other remains at a fixed position. As you already have the concept of rotation, I would call this new type of rotation, revolution and the method for it revolve
.
drawX
doesn't use this
, so it shouldn't be an instance method on Circle
. Instead, make it a method on the point class.
As one circle acts as "slave" to revolve around a "master" circle, I would set up this dependency when you create the second circle, which could be of a SlaveCircle
class.
It is a pity that you store the coordinates for a circle center separately, when this really is a repetition of what a point
is. This is an opportunity for inheritance: a circle is an extension of a point, in that it has a radius.
As at least one circle changes location, you'll need to clear the canvas in render
first before drawing the circles again.
Both the circles have the same rotation speed. It might be nice to make that speed specific for each circle. Similarly, the speed with which one circle orbits another could also become a constructor parameter.
Here is how your code evolved to when I tackled the above remarks:
class Point { // Use class syntax
constructor(x, y, z, color) {
this.x = x;
this.y = y;
this.z = z;
this.color = color;
}
// Give a Point a draw method of its own
draw(ctx) {
ctx.beginPath();
ctx.strokeStyle = this.color;
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x + 1, this.y + 1);
ctx.stroke();
}
}
class Circle extends Point { // Use inheritance
constructor(x, y, z, radius, rotationSpeed=1) {
super(x, y, z, "black");
this.radius = radius;
this.rotation = 0;
this.rotationSpeed = rotationSpeed;
}
rotate() {
// No need to store the points, only the current angle
this.rotation += this.rotationSpeed;
}
draw(ctx) {
for (let i = 0; i < 360; i++) {
const angle = (Math.PI / 180 * (i + this.rotation)) % 360;
const cos = this.x + Math.cos(angle) * this.radius;
const sin = this.y + Math.sin(angle) * this.radius;
// Draw the point as it is created
new Point(cos, sin, this.z, `rgb(${i}, ${i}, ${i})`).draw(ctx);
}
}
}
class SlaveCircle extends Circle {
constructor(master, radius, rotationSpeed, distance, revolutionSpeed) {
super(master.x + distance, master.y, master.z, radius, rotationSpeed);
this.master = master;
this.distance = distance;
this.revolutionSpeed = revolutionSpeed;
// Update this slave's location based on an angle.
this.setAngle(0);
}
// Distinguish "rotation" from "revolution":
revolve() {
this.setAngle((this.angle + this.revolutionSpeed + 360) % 360);
}
setAngle(angle) {
// Keep track of current angle, and update circle-coordinates accordingly
this.angle = angle;
const radians = angle / 180 * Math.PI;
this.x = this.master.x + Math.cos(radians) * this.distance;
this.y = this.master.y + Math.sin(radians) * this.distance;
}
}
function render(ctx, ...circles) {
// As (some of) the circles move, we need to clear the canvas first:
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
for (const circle of circles) {
circle.rotate(); // self-rotation (which results in colored-boundary animation)
circle.revolve?.(); // Only "slave" circle(s) will revolve
circle.draw(ctx);
}
requestAnimationFrame(() => render(ctx, ...circles));
}
function main(canvas, width, height) { // Limit the scope of variables
Object.assign(canvas, { width, height });
document.body.appendChild(canvas);
const circle1 = new Circle(width/2, height/2, 10, height/6, 3);
// Set up dependency between circles:
const circle2 = new SlaveCircle(circle1, height/20, 5, height/3, 1);
render(canvas.getContext('2d'), circle1, circle2);
}
main(document.createElement('canvas'), innerWidth, innerHeight);
body { margin: 0; overflow: hidden; }
With this design you can add a third circle that revolves around the one that is already revolving. Here is a third one added to the constellation which revolves in opposite direction with a greater revolution speed:
class Point { // Use class syntax
constructor(x, y, z, color) {
this.x = x;
this.y = y;
this.z = z;
this.color = color;
}
// Give a Point a draw method of its own
draw(ctx) {
ctx.beginPath();
ctx.strokeStyle = this.color;
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.x + 1, this.y + 1);
ctx.stroke();
}
}
class Circle extends Point { // Use inheritance
constructor(x, y, z, radius, rotationSpeed=1) {
super(x, y, z, "black");
this.radius = radius;
this.rotation = 0;
this.rotationSpeed = rotationSpeed;
}
rotate() {
// No need to store the points, only the current angle
this.rotation += this.rotationSpeed;
}
draw(ctx) {
for (let i = 0; i < 360; i++) {
const angle = (Math.PI / 180 * (i + this.rotation)) % 360;
const cos = this.x + Math.cos(angle) * this.radius;
const sin = this.y + Math.sin(angle) * this.radius;
// Draw the point as it is created
new Point(cos, sin, this.z, `rgb(${i}, ${i}, ${i})`).draw(ctx);
}
}
}
class SlaveCircle extends Circle {
constructor(master, radius, rotationSpeed, distance, revolutionSpeed) {
super(master.x + distance, master.y, master.z, radius, rotationSpeed);
this.master = master;
this.distance = distance;
this.revolutionSpeed = revolutionSpeed;
// Update this slave's location based on an angle.
this.setAngle(0);
}
// Distinguish "rotation" from "revolution":
revolve() {
this.setAngle((this.angle + this.revolutionSpeed + 360) % 360);
}
setAngle(angle) {
// Keep track of current angle, and update circle-coordinates accordingly
this.angle = angle;
const radians = angle / 180 * Math.PI;
this.x = this.master.x + Math.cos(radians) * this.distance;
this.y = this.master.y + Math.sin(radians) * this.distance;
}
}
function render(ctx, ...circles) {
// As (some of) the circles move, we need to clear the canvas first:
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
for (const circle of circles) {
circle.rotate(); // self-rotation (which results in colored-boundary animation)
circle.revolve?.(); // Only "slave" circle(s) will revolve
circle.draw(ctx);
}
requestAnimationFrame(() => render(ctx, ...circles));
}
function main(canvas, width, height) { // Limit the scope of variables
Object.assign(canvas, { width, height });
document.body.appendChild(canvas);
const circle1 = new Circle(width/2, height/2, 10, height/6, 3);
// Set up dependency between circles:
const circle2 = new SlaveCircle(circle1, height/20, 5, height/3, 1);
// Add a third circle that revolves around the second one
// and make it revolve faster in opposite direction
const circle3 = new SlaveCircle(circle2, height/40, 7, height/10, -4);
render(canvas.getContext('2d'), circle1, circle2, circle3);
}
main(document.createElement('canvas'), innerWidth, innerHeight);
body { margin: 0; overflow: hidden; }