Search code examples
javascriptmathrotationhtml5-canvastrigonometry

How to convert a line to a rotated rectangle?


Let's say I had a line with the form: x1, y1, x2, y2 and a given thickness.

I am looking for a way to convert that line to a rectangle rotated around its origin, such that if you were to draw both of them on a canvas, they would overlap completely.

I've developed a complete reproduction of the problem I'm facing below:

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

const thickness = 7;
const lineX1 = 25, lineY1 = 50;
const lineX2 = 100, lineY2 = 100;

function rotateCanvas(x, y, a) {
  ctx.translate(x, y);
  ctx.rotate(a);
  ctx.translate(-x, -y);
}

function drawRectangle(rX, rY, rW, rH, rA, color) {
  ctx.beginPath();
  ctx.fillStyle = "#dd3333";
  rotateCanvas(rX + rW / 2, rY + rH / 2, rA);
  ctx.rect(rX, rY, rW, rH);
  rotateCanvas(rX + rW / 2, rY + rH / 2, -rA);
  ctx.fill();
}

function drawLine(x1, y1, x2, y2) {
  ctx.lineWidth = thickness;
  ctx.strokeStyle = "#33dd33";
  ctx.beginPath();
  ctx.moveTo(x1, y1);
  ctx.lineTo(x2, y2);
  ctx.stroke();
}

function calcRectFromLine(x1, y1, x2, y2) {
  const dx = x2 - x1;
  const dy = y2 - y1;
  const mag = Math.sqrt(dx * dx + dy * dy);
  const angle = Math.atan2(dy, dx);

  return { x: x1, y: y1, w: mag, h: thickness, a: angle };
}

drawLine(lineX1, lineY1, lineX2, lineY2);

const r = calcRectFromLine(lineX1, lineY1, lineX2, lineY2);

drawRectangle(r.x, r.y, r.w, r.h, r.a);
<canvas></canvas>

If you run the code, you'll see that the red rectangle is not on top of the green line. I would like to fix that somehow.

I believe the only function that needs to be fixed is calcRectFromLine. Ideally that would return a rectangle that, when passed to drawRectangle, would cause the green line to no longer be visible, as it would be covered completely by the red rectangle.

I also believe I need to use some sort of sin and cos to offset the red rectangle, but I'm not sure of the exact math to accomplish that.


Solution

  • You calculate rectangle left and top improperly, using coordinates of rotated line. Correction:

    return { x: (x1+x2)/2 - mag/2, y: (y1+y2)/2 - thickness/2, 
             w: mag, h: thickness, a: angle };`
    

    const canvas = document.querySelector("canvas");
    const ctx = canvas.getContext("2d");
    
    const thickness = 7;
    const lineX1 = 25, lineY1 = 50;
    const lineX2 = 100, lineY2 = 100;
    
    function rotateCanvas(x, y, a) {
      ctx.translate(x, y);
      ctx.rotate(a);
      ctx.translate(-x, -y);
    }
    
    function drawRectangle(rX, rY, rW, rH, rA, color) {
      ctx.beginPath();
      ctx.fillStyle = "#dd3333";
    rotateCanvas(rX + rW / 2, rY + rH / 2, rA);
      ctx.rect(rX, rY, rW, rH);
      rotateCanvas(rX + rW / 2, rY + rH / 2, -rA);
      ctx.fill();
    }
    
    function drawLine(x1, y1, x2, y2) {
      ctx.lineWidth = thickness;
      ctx.strokeStyle = "#33dd33";
      ctx.beginPath();
      ctx.moveTo(x1, y1);
      ctx.lineTo(x2, y2);
      ctx.stroke();
    }
    
    function calcRectFromLine(x1, y1, x2, y2) {
      const dx = x2 - x1;
      const dy = y2 - y1;
      const mag = Math.sqrt(dx * dx + dy * dy);
      const angle = Math.atan2(dy, dx);
    
      return { x: (x1+x2)/2 - mag/2, y: (y1+y2)/2 - thickness/2, w: mag, h: thickness, a: angle };
    }
    
    drawLine(lineX1, lineY1, lineX2, lineY2);
    
    const r = calcRectFromLine(lineX1, lineY1, lineX2, lineY2);
    
    drawRectangle(r.x, r.y, r.w, r.h, r.a);
    <canvas><\canvas>