如何旋转内部带有点的矩形?

use*_*007 2 javascript math grid svg rotation

情况如下:我有一个矩形网格。里面有一个点。位于左上方(位置:x:1和y:1)。该网格中的坐标范围为1到4。使用如本问题所述的旋转功能。我可以根据矩形画布的中心旋转该点。为此,我使用(宽度+1)/ 2和(高度+1)/ 2。数字1的加法与画布有关,只是在我将坐标绘制到的矩形网格周围创建了白色边距/偏移/边框。此画布的尺寸范围是0到5。

沿方形网格旋转时。一切顺利。但是当宽度不等于其高度时。预期结果与预期不符。它应该像俄罗斯方块一样旋转。但是,俄罗斯方块的“点”移到其网格之外。

下面我更深入地了解了这个问题。红点是需要旋转的点。浅蓝色/绿色点是红点旋转的中心点。顺时针旋转90度。

更新:问题的可视化 在正方形网格中会发生什么 在此处输入图片说明

矩形网格中会发生什么 在此处输入图片说明

程式码片段

function rotate(cx, cy, x, y, angle) {
    var radians = (Math.PI / 180) * -angle,
        cos = Math.cos(radians),
        sin = Math.sin(radians),
        nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
        ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
    return {x:nx, y: ny};
}

rotate((width +1)/2, (height + 1)/2, x,y, 90)
var tempWidth = width;
width = height;
height = tempWidth;

Run Code Online (Sandbox Code Playgroud)

将其内容旋转90度的正确方法是什么?

Update2:可视代码段

const svgHelper = {
    init: (width, height, scale) => {
        const svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
        svg.setAttribute("xmlns:xlink", "http://www.w3.org/1999/xlink")
        svg.setAttribute("x", "0px")
        svg.setAttribute("y", "0px")
        svg.setAttribute("viewBox", `0 0 ${width + 1} ${height + 1}`)
        svg.setAttribute("xml:space", `preserve`)

        return svg;

    },

    addCircle: (element, color, x, y, radius, className) => {
          element.insertAdjacentHTML("beforeend", `\r<circle 
          style="fill: ${color}"
          cx="${x}"
          cy="${y}"
          r="${radius}"
          class="${className}"
          />`);
          return element.lastChild;
    },
    addText: (element, string) => {
          element.insertAdjacentHTML("beforeend", `\r<text x="0.2" y=".2">${string}</text>`);
          return element.lastChild;
    }
}

const Grid = {
    init: (width, height, margin) => {
        const result = {
            margin: margin,
            array: []
        };
        for (var i = 0; i < height; i++) {
            result.array.push([]);
            for (var ii = 0; ii < width; ii++) {
                result.array[i].push(0);
            }
        }
        return result.array;
    },
    draw: (svg) => {
        const tmp = svg.getAttribute("viewBox").split(" ");
        const width = tmp[2]
        const height = tmp[3]

        for (var y = 1; y < height; y++) {
            for (var x = 1; x < width; x++) {
                var posX = x //grid.margin
                var posY = y //grid.margin

                svgHelper.addCircle(svg, "#eee", posX, posY, .1);
            }
        }
    }
}

const updateGrid = (width, height,element) => {
    element.setAttribute("viewBox", `0 0 ${width + 1} ${height + 1}`)

    // Remove old points
    var points = element.querySelectorAll("circle")
    for (var i = 0; i < points.length; i++) {
        points[i].remove();
    }
    Grid.draw(element);
}

function rotate(cx, cy, x, y, angle) {
    var radians = (Math.PI / 180) * -angle,
        cos = Math.cos(radians),
        sin = Math.sin(radians),
        nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
        ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
    return {x:nx, y: ny};
}


const draw = (width, height) => {

    const div = document.createElement("div");
    const element = svgHelper.init(width, height);
    const span = document.createElement("span");
    div.appendChild(element);
    div.appendChild(span);
  
    span.className = "text";
    let point = {x:1, y:1};
    document.querySelector("body").appendChild(div);

    Grid.draw(element);
    let prevPoint = svgHelper.addCircle(element, "#f00", point.x, point.y, .125, "point")

    setInterval(() => {
        
        var tmpWidth = width;
        width = height;
        height = tmpWidth;
        updateGrid(width, height, element)
        
        point = rotate((width + 1)/2, (height + 1)/2, point.x, point.y, 90)
        prevPoint = svgHelper.addCircle(element, "#f00", point.x, point.y, .125, "point")
        span.innerText = `x: ${Math.round(point.x)} y: ${Math.round(point.y)}`;

    }, 1000)    
}
    
draw(4,4)
draw(1,4)
Run Code Online (Sandbox Code Playgroud)
div {
    position: relative;
    display: inline-block;
}

svg {
    width: 135px;
    height: 135px;
    background-color: lightgray;
    margin: 10px;
}

span {
    font-size: 10px;
    display: block;
    margin-left: 10px;
}
Run Code Online (Sandbox Code Playgroud)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
  <link rel="stylesheet" href="./index.css">
</head>
<body>
<script src="index.js"></script>
</body>
</html>
Run Code Online (Sandbox Code Playgroud)

Ser*_*ell 5

The problem appears because your formula for point rotation doesn't take into account that the scales in X and Y directions are different. How can we fix it?

The easiest solution (IMHO) should contain three steps:

  1. Map points to the square grid
  2. Apply rotation and calculate new point coordinates
  3. Map points back to the rectangular grid

The mapping function could be easily found from the relationship

x_new = (x_old - x_center) * scale + x_center
x_old = (x_new - x_center) / scale + x_center
Run Code Online (Sandbox Code Playgroud)

The relations for y coordinate whould be pretty the same, so I ommited them.

Thus your rotation function should be tweaked as follows:

function rotate(cx, cy, x, y, angle, scaleX, scaleY) { // <= Adding scale factors here
  // Mapping initial points to a square grid
  x = (x - cx) / scaleX + cx;
  y = (y - cy) / scaleY + cy;

  // Applying rotation
  var radians = (Math.PI / 180) * -angle,
    cos = Math.cos(radians),
    sin = Math.sin(radians),
    nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
    ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;

  // Mapping new point coordinates back to rectangular grid
  nx = (nx - cx) * scaleX + cx;
  ny = (ny - cy) * scaleY + cy;

  return {x: nx, y: ny};
}
Run Code Online (Sandbox Code Playgroud)

Here is an animation, how it works now: 在此处输入图片说明

And here is a working example:

x_new = (x_old - x_center) * scale + x_center
x_old = (x_new - x_center) / scale + x_center
Run Code Online (Sandbox Code Playgroud)
function rotate(cx, cy, x, y, angle, scaleX, scaleY) { // <= Adding scale factors here
  // Mapping initial points to a square grid
  x = (x - cx) / scaleX + cx;
  y = (y - cy) / scaleY + cy;

  // Applying rotation
  var radians = (Math.PI / 180) * -angle,
    cos = Math.cos(radians),
    sin = Math.sin(radians),
    nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
    ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;

  // Mapping new point coordinates back to rectangular grid
  nx = (nx - cx) * scaleX + cx;
  ny = (ny - cy) * scaleY + cy;

  return {x: nx, y: ny};
}
Run Code Online (Sandbox Code Playgroud)
function clearAll(container) {
  const ctx = container.getContext('2d');
  ctx.clearRect(0, 0, 300, 300);
}

function putPoint(container, x, y, r, color) {
  const ctx = container.getContext('2d');
  ctx.beginPath();
  ctx.fillStyle = color;
  ctx.arc(x, y, r, 0, 2 * Math.PI);
  ctx.fill();
}

function drawGrid(container, scaleX, scaleY, elapsedTime) {
  const scale = 60;
  const offset = 60;

  const centerX = offset + 1.5 * scale * scaleX;
  const centerY = offset + 1.5 * scale * scaleY;

  clearAll(container);
  putPoint(container, centerX, centerY, 3, 'cyan');

  for (let i = 0; i < 4; i++) {
    for (let j = 0; j < 4; j++) {
      const x = offset + scaleX * scale * i;
      const y = offset + scaleY * scale * j;
      putPoint(container, x, y, 2, 'black');
    }
  }

  putPoint(container, offset, offset, 5, 'red');
  const newPosition1 = rotate(centerX, centerY, offset, offset, 90, scaleX, scaleY);
  putPoint(container, newPosition1.x, newPosition1.y, 5, 'red');

  const newPosition2 = rotate(centerX, centerY, offset, offset, 180, scaleX, scaleY);
  putPoint(container, newPosition2.x, newPosition2.y, 5, 'red');

  const newPosition3 = rotate(centerX, centerY, offset, offset, 270, scaleX, scaleY);
  putPoint(container, newPosition3.x, newPosition3.y, 5, 'red');

  const newPosition4 = rotate(centerX, centerY, offset, offset, 45, scaleX, scaleY);
  putPoint(container, newPosition4.x, newPosition4.y, 5, 'red');

  const newPositionDynamic = rotate(centerX, centerY, offset, offset, elapsedTime, scaleX, scaleY);
  putPoint(container, newPositionDynamic.x, newPositionDynamic.y, 5, 'red');


  requestAnimationFrame(() => {
    drawGrid(container, scaleX, scaleY, elapsedTime + 1);
  })
}

function rotate(cx, cy, x, y, angle, scaleX, scaleY) { // <= Adding scale factors here
  // Mapping initial points to a square grid
  x = (x - cx) / scaleX + cx;
  y = (y - cy) / scaleY + cy;

  // Applying rotation
  var radians = (Math.PI / 180) * -angle,
cos = Math.cos(radians),
sin = Math.sin(radians),
nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;

  // Mapping new point coordinates back to rectangular grid
  nx = (nx - cx) * scaleX + cx;
  ny = (ny - cy) * scaleY + cy;

  return {x: nx, y: ny};
}

const squareGrid = document.getElementById('square-grid');
const rectangularGrid = document.getElementById('rectangular-grid');

drawGrid(squareGrid, 1, 1, 0);
drawGrid(rectangularGrid, 0.5, 0.9, 0);
Run Code Online (Sandbox Code Playgroud)

Please, let me know if I understood you wrong or this solution is not the one you expected to get