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

时间:2019-08-31 10:15:30

标签: javascript math svg rotation grid

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

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

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

更新:问题的可视化 在正方形网格中会发生什么 enter image description here

在矩形网格中会发生什么 enter image description here

代码段

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;

将内容网格旋转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)
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;
}
<!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>

2 个答案:

答案 0 :(得分:5)

出现此问题是因为您的点旋转公式未考虑XY方向上的比例不同。我们该如何解决?

最简单的解决方案(IMHO)应该包含三个步骤:

  1. 将地图指向正方形网格
  2. 应用旋转并计算新的点坐标
  3. 将地图指向回到矩形网格

可以从关系中轻松找到映射功能

x_new = (x_old - x_center) * scale + x_center
x_old = (x_new - x_center) / scale + x_center

y的关系可以协调几乎相同的人,因此我忽略了它们。

因此,您的旋转功能应进行如下调整:

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};
}

这是动画,现在如何工作: enter image description here

这是一个有效的示例:

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);
canvas {
  width: 300px;
  height: 300px;
  background-color: lightgray;
  margin: 10px;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
  <link rel="stylesheet" href="./index.css">
</head>
<body>
<canvas width="300" height="300" id="square-grid"></canvas>
<canvas width="300" height="300" id="rectangular-grid"></canvas>

<script src="index.js"></script>
</body>
</html>

请让我知道我是否理解错了或者该解决方案不是您期望得到的解决方案

答案 1 :(得分:2)

模拟旋转

由于我注意到您的网格旋转是“模拟的”(如深色阴影点所示),因此我决定提供一个“模拟”红点旋转的答案,因为它也可以满足您的需求。我相信效果是一样的。

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
        var color = (x === width - 1) ? "#999" : "#eee";
        svgHelper.addCircle(svg, color, 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 simulatedRotation(width, height, rotation) {
  switch (rotation) {
    case 1:
      return {
        x: 1,
        y: 1
      };
      break;
    case 2:
      return {
        x: width,
        y: 1
      };
      break;
    case 3:
      return {
        x: width,
        y: height
      };
      break;
    case 4:
      return {
        x: 1,
        y: height
      };
      break;
  }
}

const draw = (width, height) => {
  const element = svgHelper.init(width, height);
  const span = document.createElement("span");
  span.className = "text";
  let point = {
    x: 1,
    y: 1
  };
  const div = document.createElement("div");
  div.appendChild(element);
  div.appendChild(span);
  document.querySelector("body").appendChild(div);
  Grid.draw(element);
  let prevPoint = svgHelper.addCircle(element, "#f00", point.x, point.y, .125, "point")
  let rotation = 1;
  setInterval(() => {
    // grid is just swapping axis to simulate rotation
    var tmpWidth = width;
    width = height;
    height = tmpWidth;
    rotation++;
    if (rotation > 4) rotation = 1;
    updateGrid(width, height, element);
    point = simulatedRotation(width, height, rotation);
    prevPoint = svgHelper.addCircle(element, "#f00", point.x, point.y, .125, "point");
    span.innerText = `x: ${Math.round(point.x)} y: ${Math.round(point.y)} r: ${rotation}`;
  }, 1000)
}

draw(4, 4);
draw(3, 4);
draw(2, 4);
draw(1, 4);
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;
}