在画布中的鼠标坐标转换为网格上的30度等距坐标。

5

我在画布上绘制一个等角网格。它使用30度角度偏移,并使用一些脚本来绘制基本的网格。对于这个网格,我投影了一个具有40x40瓷砖大小的平面网格。

gridRows = 10;
gridCols = 10;
tileSize = 40;

gridWidth = gridCols * tileSize;
gridHeight = gridRows * tileSize;

canvasWidth = tileSize * (gridCols + gridRows) * Math.sqrt(3) / 2;
canvasHeight = tileSize * (gridRows + gridCols) / 2;

canvasOffset = tileSize * gridRows * Math.sqrt(3) / 2;

function carToIso(x, y) {
      // Convert cartesian (x, y) to isometric coordinates
      return [
        Math.round((x - y) * Math.sqrt(3) / 2 + canvasOffset),
        Math.round((x + y) / 2)
      ];
}

function drawGrid() {
            let canvas = $("#canvas");
      canvas.attr('width', canvasWidth);
      canvas.attr('height', canvasHeight);

            let ctx = canvas.get(0).getContext('2d');

      // Background
      ctx.fillStyle = "white";
      ctx.fillRect(0, 0, canvasWidth, canvasHeight);

      // Draw lines
      ctx.beginPath();
      ctx.lineWidth = 1;

      // Rows
      for(let i = 0; i <= gridRows; ++i) {
        ctx.moveTo(...carToIso(0, i * tileSize));
        ctx.lineTo(...carToIso(gridWidth, i * tileSize));
      }

      // Columns
      for(let i = 0; i <= gridCols; ++i) {
        ctx.moveTo(...carToIso(i * tileSize, 0));
        ctx.lineTo(...carToIso(i * tileSize, gridHeight));
      }

      ctx.stroke();
}

drawGrid();

这个代码运行良好,可以得到我想要的网格。这里有一个工作范例:https://jsfiddle.net/fgw10sev/2/

接下来,我需要确定用户正在悬停的是哪个小块。最初我有一个2:1的网格,并且能够将画布内的鼠标坐标转换为原始未投影的网格坐标,如下所示:

function mouseToIso(x, y) {
      // Convert mouse (x, y in canvas) to grid coordinates
      return [
        Math.round((2 * y + x - canvasOffset) / 2),
        Math.round((2 * y - x + canvasOffset) / 2)
      ];
    }

然而,加入sqrt(3)因子后,原方法失效了,我无法弄清楚如何解决这个问题。原来的mouseToIso函数已经是试错的垃圾代码了,我尝试在不同的地方注入sqrt(3)因子,但我就是无法让它正常工作。

有人可以帮助我吗?或者告诉我需要完全不同的方法吗?

备注:我需要这个sqrt(3)因子,因为我使用的图像都是以30度角度数摆放的,如果没有这个因子,网格就不能正确地与图像对齐。

备注2:仅供完整性说明-sqrt(3)/2 == cos(deg2rad(30)),并且可以互换使用2。

1个回答

3
我本人在代数方面并不精通,所以可能有更简单的解决方案,但无论如何,您的函数需要做的是反转先前步骤中发生的转换,以找回原始的xy值。请注意保留HTML标签。
a = Math.round((x - y) * Math.sqrt(3) / 2 + canvasOffset);
b = Math.round((x + y) / 2);

从中我们可以得到 (x - y)(x + y) 的值分别为

(x - y) = (a - canvasOffset) / Math.sqrt(3) * 2;
(x + y) = b * 2;

现在我们有了(x - y)(x + y),我们可以把这两个值加起来再除以2来找到xy值自动消除。
现在很容易找到y,因为它只是(x + y) - x

const pre = document.querySelector("pre");
const log = (txt) => pre.textContent = JSON.stringify(txt);

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const gridRows = 10;
const gridCols = 10;
const tileSize = 40;

const gridWidth = gridCols * tileSize;
const gridHeight = gridRows * tileSize;

const canvasWidth = tileSize * (gridCols + gridRows) * Math.sqrt(3) / 2;
const canvasHeight = tileSize * (gridRows + gridCols) / 2;

const canvasOffset = tileSize * gridRows * Math.sqrt(3) / 2;

canvas.width = canvasWidth;
canvas.height = canvasHeight;

function carToIso(x, y) {
  // Convert cartesian (x, y) to isometric coordinates
  // I moved the scaling part here, it makes lees noise in the rest of the code
  // But feel free to change it back
  x *= tileSize;
  y *= tileSize;
  return [
    Math.round((x - y) * Math.sqrt(3) / 2 + canvasOffset),
    Math.round((x + y) / 2)
  ];
}

function isoToCar(a, b) {
  // Convert isometric (a, b) to cartesian coordinates
  const xMinusY = (a - canvasOffset) / Math.sqrt(3) * 2;
  const xPlusY = b * 2;
  const x = (xMinusY + xPlusY) / 2;
  const y = xPlusY - x;
  return [
    Math.floor(x / tileSize), // scaling is here too
    Math.floor(y / tileSize)
  ];
}

function drawGrid() {
  // Draw lines
  ctx.beginPath();
  ctx.lineWidth = 1;

  ctx.fillStyle = "red";

  // Rows
  for (let i = 0; i <= gridRows; ++i) {
    const [x1, y1] = carToIso(0, i);
    const [x2, y2] = carToIso(gridCols, i);
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.fillText("r" + i, (x2 + x1) / 2, (y2 + y1) / 2);
  }

  // Columns
  for (let i = 0; i <= gridCols; ++i) {
    const [x1, y1] = carToIso(i, 0);
    const [x2, y2] = carToIso(i, gridRows);
    ctx.moveTo(x1, y1);
    ctx.lineTo(x2, y2);
    ctx.fillText("c" + i, (x2 + x1) / 2, (y2 + y1) / 2);
  }

  ctx.stroke();
}

function fillCell(x, y, color) {
  ctx.beginPath();
  ctx.moveTo(...carToIso(x, y));
  ctx.lineTo(...carToIso((x + 1), y));
  ctx.lineTo(...carToIso((x + 1), (y + 1)));
  ctx.lineTo(...carToIso(x, (y + 1)));
  ctx.fillStyle = "green";
  ctx.fill();
}

onmousemove = (evt) => {
  const {top, left} = canvas.getBoundingClientRect();
  const mouseX = evt.clientX - left;
  const mouseY = evt.clientY - top;
  const [gridX, gridY] = isoToCar(mouseX, mouseY);
  log({
    mouseX,
    mouseY,
    gridX,
    gridY
  });

  ctx.clearRect(0, 0, canvas.width, canvas.height);
  fillCell(gridX, gridY, "green");
  drawGrid();
};

drawGrid();
pre { position: absolute }
<pre></pre>
<canvas id="canvas"></canvas>


1
非常感谢,这个可行!看了你的解决方案和代数方法,我能够让计算变得更简短,但如果没有正确方向上的推动,我是做不到的。最终我得到的是:return [ Math.round(((x - canvasOffset) + y * Math.sqrt(3)) / Math.sqrt(3)), Math.round((canvasOffset - x + Math.sqrt(3) * y) / Math.sqrt(3)) ];基本上是相同的东西,只是减少了一些因素。 - Gerard

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接