Search code examples
javascriptrotationrectangleskonvajs

Konva: get corners coordinate of a rotated rectangle


How can i get corners coordinate of a rotated rectangle (with center of rectangle as pivot) ?

i already tried all of the solution from the link below but seems haven't got any luck.

Rotating a point about another point (2D)

Find corners of a rotated rectangle given its center point and rotation

https://gamedev.stackexchange.com/questions/86755/how-to-calculate-corner-positions-marks-of-a-rotated-tilted-rectangle

here's the code

// make a rectangle with zero rotation
const rect1 = new Konva.Rect({
  x: 200,
  y: 200,
  width: 100,
  height: 50,
  fill: "#00D2FF",
  draggable: true,
  rotation: 0,
  name: "rect"
});

// convert degree to rad
const degToRad = (deg: number) => deg * (Math.PI / 180);

// here's the code i use to rotate it around its center (from https://konvajs.org/docs/posts/Position_vs_Offset.html)

const rotateAroundCenter = (node: Rect, rotation: number) => {
     const topLeft = {
    x: -node.width() / 2,
    y: -node.height() / 2
  };
  console.log(`current X: ${node.x()}, current Y: ${node.y()},`)

  const currentRotatePoint = rotatePoint(topLeft, degToRad(node.rotation()));
  const afterRotatePoint = rotatePoint(topLeft, degToRad(rotation));
  const dx = afterRotatePoint.x - currentRotatePoint.x;
  const dy = afterRotatePoint.y - currentRotatePoint.y;

  node.rotation(rotation);
  node.x(node.x() + dx);
  node.y(node.y() + dy);
  layer.draw();

console.log(`the actual position x: ${node.x()}, y: ${node.y()}`);
};

// the code that i expected to give me the corner point

const computeCornerPoint = (r:Rect) => {
  // for now we want to compute top left corner point(as it's the easiest corner to get)
  let corner = {
     x: r.x(),
     y: r.y()
  };

  // the coordinate of rectangle's center (in stage coordinate)
  const cx = r.x() + r.width() / 2;
  const cy = r.y();

  // sine and cosine of the rectangle's rotation
  const s = Math.sin(degToRad(r.rotation()));
  const c = Math.cos(degToRad(r.rotation()));

  // rotate the corner point
  let xnew = c * (corner.x - cx) - s * (corner.y - cy) + cx;
  let ynew = s * (corner.x - cx) + c * (corner.y - cy) + cy;

  console.log(`based on this function calculation: xnew : ${xnew}, ynew: ${ynew}`);
  return [xnew, ynew];
}

based on the code above, if the initial rotation is 0, and i rotate the rectangle 30 degree clockwise, then the actual position would be same as the value from computeCornerPoint, which is (219, 178) and if i rotate it again by 30 degree clockwise, the actual position would be (246, 169) while the value from computeCornerPoint would be (275, 175).


Solution

  • Life is rectangular in the world of the canvas so all we need to do to predict the corner positions is know the shape top-left and the rotation angle, then apply some high-school math. The math to rotate a point is in function rotatePoint(). The rest of the snippet is setup for its use and illustration of the outcome.

    Maybe better running the snippet in full screen mode.

    // Function to rotate a point.
    // pt = {x,y} of point to rotate, 
    // o = {x, y} of rotation origin, 
    // a = angle of rotation in degrees.
    // returns {x, y} giving the new point.
    function rotatePoint(pt, o, a){
    
      var angle = a * (Math.PI/180); // Convert to radians
    
      var rotatedX = Math.cos(angle) * (pt.x - o.x) - Math.sin(angle) * (pt.y - o.y) + o.x;
    
      var rotatedY = Math.sin(angle) * (pt.x - o.x) + Math.cos(angle) * (pt.y - o.y) + o.y;  
    
      return {x: rotatedX, y: rotatedY};
    
    }
    
    // This is just about drawing the circles at the corners.
    function drawCorners(rect, angle){
    
      var rectPos = rect.position();
      
      var x = 0, y = 0;
      for (var i = 0; i < 4; i = i + 1){
    
      switch (i){
        
        case 0: 
          x = rectPos.x; y = rectPos.y;
          break;
    
        case 1: 
          x = rectPos.x + rect.width(); y = rectPos.y;
          break;
    
        case 2: 
          x = rectPos.x + rect.width(); y = rectPos.y + rect.height();
          break;
    
        case 3: 
          x = rectPos.x; y = rectPos.y + rect.height();
          break;
    
         }
    
        var pt = rotatePoint({x: x, y: y}, {x: rectPos.x, y: rectPos.y}, angle)
        circles[i].position(pt)
    
      }
     }
    
    
    // rotate and redraw the rectangle
    function rotateUnderMouse(){
    
    
      // Get the stage position of the mouse
      var mousePos = stage.getPointerPosition();
    
      // get the stage position of the mouse
      var shapePos = rect.position();
    
      // compute the vector for the difference
      var rel = {x: mousePos.x - shapePos.x, y: mousePos.y - shapePos.y} 
    
      // Now apply the rotation
      angle = angle + 90;
    
      circle.position({x: mousePos.x, y: mousePos.y});
      circle.show();
      
      // and reposition the shape to keep the same point in the shape under the mouse 
      var newPos = ({x: mousePos.x  + rel.y , y: mousePos.y - rel.x}) 
    
      rect.position(newPos);
      rect.rotation(angle);
    
      // re-calculate and draw the circle positions.
      drawCorners(rect, angle)
    
      stage.draw()
    }
    
    
    
    
    
    function setup() {
    
    // Set up a stage and a shape
    stage = new Konva.Stage({
      container: 'canvas-container',
      width: 650,
      height: 300
    });
    
    
    layer = new Konva.Layer();
    stage.add(layer);
    
    newPos = {x: 80, y: 100};
    rect = new Konva.Rect({
       width: 140, height: 50, x: newPos.x, y: newPos.y, draggable: true, stroke: 'silver', fill: 'cyan'
      })
    
    // not very dry, setting up the corner circles.
    circle = new Konva.Circle({x: newPos.x, y: newPos.y, radius: 10, fill: 'magenta'}) 
    circles[0] = circle.clone();
    circles[0].fill('lime')
    layer.add(circles[0]);
    circles[1] = circle.clone();
    circles[1].fill('gold')
    layer.add(circles[1]);
    circles[2] = circle.clone();
    circles[2].fill('blue')
    layer.add(circles[2]);
    circles[3] = circle.clone();
    circles[3].fill('darkviolet')
    
    layer.add(circles[3]);
    
    layer.add(rect);
    layer.add(circle);
    circle.hide()
    
    drawCorners(rect, 0)
    
    stage.draw()
    
    rect.on('mousedown', function(){
      rotateUnderMouse()
    })
    
    }
    
    var stage, layer, rect, circles = [], angle = 0;
    
    setup()
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/konva/4.0.13/konva.js"></script>
    
    <p>Click the rectangle - it will rotate 90 degrees clockwise under the mouse and coloured circles will be drawn consistently at the corners. These circles have their position calculated rather than derived from the visual rectangle corner positions. NB: No dragging !</p>
    
    <div id="canvas-container"></div>