4

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};
    }
ian.c
  • 288
  • 1
  • 2
  • 10
  • Are your coordinates relative or absolute? For example, when you set the image coordinates, are they with respect to the page or the parent element? Also, what are the outputs of the two examples that do work and the one that does not? What is your expected output? Since this is Javascript, a working example on something like JSFiddle would be very helpful. – Thomas Cohn Jun 25 '18 at 04:08
  • Also, what you're doing is very much so based in linear algebra. Instead of hard coding your formulas, it may be better to use rotation matrices and translation to describe the position and orientation of your rectangles. For example, you could have a transformation that represents changing from the basis of your page to your container, and another representing your container to the rectangle within it. – Thomas Cohn Jun 25 '18 at 04:17

1 Answers1

2

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
  };
}
ewcz
  • 12,819
  • 1
  • 25
  • 47
  • This seems to be off by a long way when plugging in values. Putting in the following should give 0,0 as it's not rotated or moved but for some reason gives the top left as 200,108. I'm not sure why though. https://jsfiddle.net/ycrux1t3/3/ – ian.c Jun 25 '18 at 22:14
  • @ian.c sorry, it was supposed to be top-right corner, since the coordinates in the code are `{x: image.width, y: image.height}` - that's why it prints 200,108 since the image is not rotated. In the answer, I assumed that the point of reference is the bottom-left corner, and the y-axis points "upwards". If the y-axis points "downwards", the formulas work too, one has just to interpret all angles as clockwise instead of counter-clockwise... – ewcz Jun 26 '18 at 09:48
  • If the point of reference is the bottom left the top right should be 200,100 when both are at 0,0 with no rotation? I can adapt the code for my scenario but I need to understand the inputs and why the output is not as expected. – ian.c Jun 26 '18 at 09:58
  • @ian.c yes, it's just that in your fiddle, you have `height: 108`. I have included an example to hopefully explain the concept better... – ewcz Jun 26 '18 at 10:26
  • This seems to work when the container has no offset for getting the 4 corners: https://jsfiddle.net/ycrux1t3/38/ but as soon as I change the container co-ord it gets it wrong: https://jsfiddle.net/ycrux1t3/40/ There's something I'm not quite understanding :( output: https://imgur.com/a/LP44mSY – ian.c Jun 26 '18 at 13:13
  • @ian.c there should be `rotateCCWBy(-container.angle` - it seems that it's somehow mixed in the fiddle(s)... – ewcz Jun 26 '18 at 13:18
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/173819/discussion-between-ian-c-and-ewcz). – ian.c Jun 26 '18 at 13:30