0

I'm trying to rotate two rectangles the same amount around the same point. The point is arbitrary, so for simplicity, I'm using the top-left (0, 0)

Unfortunately, the result seems slightly off, and I'm not sure what's causing it. Here is a full reproduction of the issue:

let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");

class Rectangle {
  constructor(x, y, w, h, theta) {
    this.x = x;
    this.y = y;
    this.w = w;
    this.h = h;
    this.theta = theta;
  }
}

function drawRectangle(r) {
  ctx.beginPath();
  ctx.rect(r.x, r.y, r.w, r.h);
  ctx.stroke();
}

function degreesToRadians(degrees) { return degrees * (Math.PI / 180); }

function rotateCanvas(radians, centerX, centerY) {
  ctx.translate(centerX, centerY);
  ctx.rotate(radians);
  ctx.translate(-centerX, -centerY);
}

function drawRotatedRectangle(r) {
  let rXCenter = r.x + (r.w / 2);
  let rYCenter = r.y + (r.h / 2);
  alert(rXCenter);

  rotateCanvas(r.theta, rXCenter, rYCenter);
  drawRectangle(r);
  rotateCanvas(-r.theta, rXCenter, rYCenter);
}

let r1 = new Rectangle(100, 52, 90, 30, degreesToRadians(-20));
let r2 = new Rectangle(140, 80, 25, 25, degreesToRadians(10));

function simpleRotate(r, theta) {
  let transX = Math.cos(theta) * r.x - Math.sin(theta) * r.y;
  let transY = Math.sin(theta) * r.x + Math.cos(theta) * r.y;

  return new Rectangle(transX, transY, r.w, r.h, r.theta + theta);
}

drawRotatedRectangle(r1);
drawRotatedRectangle(r2);

let r1AABB = simpleRotate(r1, -r1.theta);
let r2Rotate = simpleRotate(r2, -r1.theta);

ctx.strokeStyle = "#ff0000";
drawRotatedRectangle(r1AABB);
drawRotatedRectangle(r2Rotate);
body { margin: 0; overflow: hidden; }
<canvas width="600" height="600"></canvas>

The black rectangles are the two rectangles before being rotated, and the red rectangles are the two rectangles after being rotated.

As you can see, the two black rectangles are touching (colliding) before being rotated. Then, I rotate them both by the same amount around the same point (0, 0). However, afterwards they are no longer touching (as you can see the red rectangles are no longer colliding.

Why is this? I followed this code for rotating a point, but I seem to be getting inaccurate results.

If I take a screenshot of the black rectangles, open it up an image editor, box select them, and rotate them, then they stay together (colliding). How can I emulate this in my code example posted above?

MBo
  • 77,366
  • 5
  • 53
  • 86
Ryan Peschel
  • 11,087
  • 19
  • 74
  • 136
  • Emm... What is x (and y) for rotated rectangle? – MBo Sep 11 '21 at 07:15
  • @MBo I'm not sure what you mean? Which line of code are you referring to? – Ryan Peschel Sep 11 '21 at 07:25
  • You define some basic point x,y in rectangle class. It has undoubted meaning in case of axis-aligned rectangles, but where is basic point for rotated ones? Perhaps rectangle center would be more reliable (common case in comp. geometry for arbitrary rectangles - define center, and some variants for orientation - or side vectors, or width/height/angle) – MBo Sep 11 '21 at 07:32
  • Oh, I think I see what you mean. Yeah, rotated rectangles are rotated around their origin (their center) when drawn. – Ryan Peschel Sep 11 '21 at 07:33
  • I was in doubts seeing `let rXCenter = r.x + (r.w / 2);` If rectangle is rotated - where is center calculated by this formula? – MBo Sep 11 '21 at 07:34
  • Well, doesn't Javascript let you draw a rotated rectangle by rotating the canvas, drawing the rectangle normally, and then rotating the canvas back? That's what I'm doing in the snippet above. – Ryan Peschel Sep 11 '21 at 07:38
  • You rotate canvas about rectangle center. And I am not sure where is this center for rotated rect ( `r1AABB = simpleRotate` then `drawRotatedRectangle(r1AABB);` ) Perhaps my doubts are wrong, I don't know JS enough. – MBo Sep 11 '21 at 07:48
  • Well, the only real Javascript that you have to know for this is the `rotateCanvas` function. All that does is just rotates the entire canvas by `radians` at a given point. https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/rotate So I rotate the canvas by theta, draw a rectangle, and then rotate the canvas back to normal. This is how I draw a rotated rectangle. – Ryan Peschel Sep 11 '21 at 07:50
  • But I do think you're right in that it may be something wrong with that function.. because if I change `rXCenter` to equal `r.x`, then they no longer become separated after rotating.. But I don't want those rectangles to rotate around their top-left point, and instead to rotate around their center. – Ryan Peschel Sep 11 '21 at 07:58
  • I added `alert(rXCenter);` in `drawRotatedRectangle` function and see distinct values. So rotations occur about different centers – MBo Sep 11 '21 at 07:59
  • Yeah I see that now too.. but I'm still not exactly sure how to fix it.. Should `rXCenter` do some sort of sin or cosine multiplication to get their rotated center points or something? – Ryan Peschel Sep 11 '21 at 08:01
  • The problem is with the center of the rotation canvas, which should be the center of the rotated rectangle. But you use the center of the unrotated rectangle ! –  Sep 11 '21 at 10:18

1 Answers1

0

This may let it work as you expect.

function simpleRotate(r, theta) {
    let transX = Math.cos(theta) * (r.x + r.w / 2) - Math.sin(theta) * (r.y + r.h / 2) - r.w / 2;
    let transY = Math.sin(theta) * (r.x + r.w / 2) + Math.cos(theta) * (r.y + r.h / 2) - r.h / 2;
    return new Rectangle(transX, transY, r.w, r.h, r.theta + theta);
}

Other otherwise, if you'd like to change center, use following form.

function simpleRotate(r, theta, centerX, centerY) {
    let transX = Math.cos(theta) * (r.x + r.w / 2 - centerX) - Math.sin(theta) * (r.y + r.h / 2 - centerY) - r.w / 2 + centerX;
    let transY = Math.sin(theta) * (r.x + r.w / 2 - centerX) + Math.cos(theta) * (r.y + r.h / 2 - centerY) - r.h / 2 + centerY;
    return new Rectangle(transX, transY, r.w, r.h, r.theta + theta);
}

Then

let r1AABB = simpleRotate(r1, -r1.theta, 145, 67);
let r2Rotate = simpleRotate(r2, -r1.theta, 145, 67);

How these work

In general, 2d rotational transform with offset consists of translation and rotation. Then, in that setup rotation is to be applied via drawRotatedRectangle() afterwards. Therefore all what you have to do is to compose appropriate translation. Inspecting simpleRotate() function carefully, you'll notice what's actually done there is calculation of translation (transX, transY ... though sin/cos are being used).

Then, as you know, since drawRotatedRectangle() rotates the rectangle around its center, by composing translation to move the center to the appropriate position, you can obtain rotational transform you need.

enter image description here

Initial state → Translate each item by simpleRotate() → Rotate each item by drawRotatedRectangle() → Final state.

ardget
  • 2,561
  • 1
  • 5
  • 4
  • I might be doing something wrong, but this doesn't seem to fix it either. Also, don't I want to not rotate the center point of the rectangle (which this seems to do)? My intuition is that I need to only get the new x and y positions for the top left of the rectangle. – Ryan Peschel Sep 11 '21 at 08:15
  • Sorry, i don't understand what your goal is like. To my understandings, your rotateCanvas() function seems to support only separated rotation of each rectangle around its center. If you'd like to rotate multiple rectangle in some synchronize manners by your implementation, it's not suitable for your purpose. However, there would be a way. Please provide brief sketch of rectangle arrangements you expect, if possible. – ardget Sep 11 '21 at 08:30
  • Sure, here is a rough sketch I just made in MS Paint: https://i.imgur.com/UqHOQPf.png The left is what I currently have, but the right is what I'm shooting for. Basically to have both shapes rotate around the top left (0, 0) together. – Ryan Peschel Sep 11 '21 at 08:34
  • Seeing the sketch, I think the code in my answer is not so wrong. Please try it first by yourself. Then let's consider what's wrong. – ardget Sep 11 '21 at 08:47
  • Hmm, I think I am messing something up on my end. I'll continue experimenting with your code here. – Ryan Peschel Sep 11 '21 at 09:19
  • @Ryan Peschel I re-read question and now I don't sure, about what point you want to rotate ;) Formula from your link is correct. – MBo Sep 11 '21 at 09:38
  • Very sorry, there was an error in the expression. `transX`'s `r.y + r.w/2` should have been `r.y + r.h/2`. This was already fixed. And I appended some brief explanations, read it too. thank you. – ardget Sep 11 '21 at 12:38