ian.c
ian.c

Reputation: 368

Calculate corners of rotated rectangle in container

I'm having some issues calculating the corners of a rotated rectangle within a rotated container with both having offset x/y co-ords.

The pivot is off but I'm not sure of the solution. The following scenarios work:

(x, y, rotation)
image = 0, 0, 45
container = 100, 100, 45

image = 200, 0, 45
container = 100, 100, 0

however setting the rotation of the container, and the image co-ords messes up the pivot e.g.

image = 200, 0, 45
container = 100, 100, 45

Below is the code for calculating the corners of the image in global co-ordinate space:

public get corners() {
        const worldData = this.worldData;
        //Get angle of object in radians;
        const radAngle = worldData.rotation * Math.PI / 180;
        const pivotX = worldData.pivotX;
        const pivotY = worldData.pivotY;
        const width = this.sourceWidth * worldData.scaleX;
        const height = this.sourceHeight * worldData.scaleY;
        const x = worldData.x;//this.x;
        const y = worldData.y;//this.y;

        //Get the corners
        const c1 = this.getCorner(pivotX, pivotY, x, y, radAngle);
        const c2 = this.getCorner(pivotX, pivotY, x + width, y, radAngle);
        const c3 = this.getCorner(pivotX, pivotY, x + width, y + height, radAngle);
        const c4 = this.getCorner(pivotX, pivotY, x, y + height, radAngle);

        return {c1, c2, c3, c4};
    }

    public get worldData() {
        let x = this.x;
        let y = this.y;
        let pivotX = this.x;
        let pivotY = this.y;
        let rotation = this.rotation;
        let scaleX = this.scaleX;
        let scaleY = this.scaleY;
        let parent = this.parent;

        while(parent) {
            x += parent.x;
            y += parent.y; 
            pivotX += parent.x;
            pivotY += parent.y;
            rotation += parent.rotation;
            scaleX *= parent.scaleX;
            scaleY *= parent.scaleY;
            parent = parent.parent;
        }

        return {x, y, scaleX, scaleY, rotation, pivotX, pivotY}
    }

    protected getCorner(pivotX:number, pivotY:number, cornerX:number, cornerY:number, angle:number) {
        let x, y, distance, diffX, diffY;

        /// get distance from center to point
        diffX = cornerX - pivotX;
        diffY = cornerY - pivotY;
        distance = Math.sqrt(diffX * diffX + diffY * diffY);

        /// find angle from pivot to corner
        angle += Math.atan2(diffY, diffX);

        /// get new x and y and round it off to integer
        x = pivotX + distance * Math.cos(angle);
        y = pivotY + distance * Math.sin(angle);

        return {x, y};
    }

Upvotes: 4

Views: 1908

Answers (1)

ewcz
ewcz

Reputation: 13087

Let's suppose that the scenario is as follows:

enter image description here

where the lower left corner of the image (solid line) has coordinates (x_i, y_i) and the lower left corner of the container (dashed line) has coordinates (X_c, Y_c). Moreover, the image (of width w and height h) is rotated counter-clockwise by angle beta with respect to the laboratory frame, while the container itself is rotated (also counter-clockwise) by angle alpha.

Now, let's focus for example on the upper-right corner P. With respect to the laboratory frame (global canvas), its coordinates can be expressed as:

R(beta) . ( w, h ) + ( x_i, y_i )

where . denotes matrix multiplication, and R is a counter-clockwise rotation matrix

R(beta) = [ cos(beta) -sin(beta) ]
          [ sin(beta)  cos(beta) ]

Now, we need to transform this into a coordinate frame with respect to the container. Formally, this means that we need first to subtract the offset and then to rotate by -alpha (or alpha clock-wise). Thus with everything together:

R(-alpha).( R(beta) . (w, h) + (x_i, y_i) - (X_c, Y_c) )

The other corners can be handled similarly, just by replacing (w, h) with the proper coordinates...

In terms of code, one might implement these formulae as:

//counter-clock-wise rotation by given angle in degrees
function rotateCCWBy(angle, {x, y}) {
  const angle_rad = angle * Math.PI / 180;
  const cos_a = Math.cos(angle_rad),
        sin_a = Math.sin(angle_rad);

  return {
    x: cos_a * x - sin_a * y,
    y: sin_a * x + cos_a * y
  };
}

//shift by a multiple fac of an offset {xref, yref}
function offsetBy(fac, {x:xref, y:yref}, {x, y}) {
  return {
    x: fac*xref + x,
    y: fac*yref + y
  };
}

const image = {
  coords: {x: 200, y: 0}, //lab-frame coordinates
  angle: 45, //lab-frame rotation angle
  width: 50,
  height: 10
};

const container = {
  coords: {x: 100, y: 100}, //lab-frame coordinates
  angle: 45 //lab-frame rotation angle
};

//calculate the coordinates of the image's top-right corner
//with respect to the container
const corner = rotateCCWBy(-container.angle,
  offsetBy(
    -1, container.coords,
    offsetBy(
      +1, image.coords,
      rotateCCWBy(image.angle,
        {x: image.width, y: image.height}
      )
    )
  )
);

console.log(corner);

EDIT:

In case the y-axis is supposed to point "downwards", the formulas above work as well, one just needs to interpret the angles as clock-wise instead of counter-clockwise (so in principle the function rotateCCWBy should be renamed to rotateCWBy). As an example, let's consider this scenario:

enter image description here

Here, the top-left corner of the container is located at position (2,1) and the container itself is rotated by 15 degrees. The image (black rectangle) of width 4 and height 2 is rotated by 30 degrees and its top-left corner is located at position (3, 3). Now, we want to calculate the coordinates (x, y) of point P with respect to the container.

Using:

const image = {
  coords: {x: 3, y: 3}, //lab-frame coordinates
  angle: 30, //lab-frame rotation angle
  width: 4,
  height: 2
};

const container = {
  coords: {x: 2, y: 1}, //lab-frame coordinates
  angle: 15 //lab-frame rotation angle
};

//calculate the coordinates of the image's top-left corner
//with respect to the container
const corner = rotateCCWBy(-container.angle,
  offsetBy(
    -1, container.coords,
    offsetBy(
      +1, image.coords,
      rotateCCWBy(image.angle,
        {x: image.width, y: image.height}
      )
    )
  )
);

console.log(corner);

yields

{ x: 4.8296291314453415, y: 4.640160440463835 }

which can be (approximately) visually verified from the attached figure.

EDIT2:

After additional clarification, the coordinates of the image are not supposed to be "lab-frame" (i.e., with respect to the canvas), but with respect to the already rotated container. Thus the transformation needs to be adapted as:

const corner = 
  offsetBy(
    +1, container.coords,
        rotateCCWBy(container.angle,    
      offsetBy(
        +1, image.coords,
        rotateCCWBy(image.angle,
          {x: image.width, y: image.height}
        )
      )
    )
  );

function rotateCCWBy(angle, {x, y}) {
  const angle_rad = angle * Math.PI / 180;
  const cos_a = Math.cos(angle_rad),
        sin_a = Math.sin(angle_rad);

  return {
    x: cos_a * x - sin_a * y,
    y: sin_a * x + cos_a * y
  };
}

Upvotes: 2

Related Questions