画布中的鼠标坐标到网格上的 30 度等距坐标

Ger*_*ard 5 javascript canvas game-development isometric

我在画布上绘制了一个等距网格。它使用 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();
Run Code Online (Sandbox Code Playgroud)

这工作得很好,让我得到了我想要的网格。这里有一个关于这项工作的摆弄;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)
      ];
    }
Run Code Online (Sandbox Code Playgroud)

然而,随着 sqrt(3) 因子的添加,这不再起作用,而且我无法 - 对于我的一生 - 弄清楚如何解决这个问题。最初的 mouseToIso 函数已经是反复试验的黑客工作,我尝试在不同的地方注入 sqrt(3) 因子,但我就是无法让它工作。

有人可以帮我从这里出去吗?或者告诉我我需要使用完全不同的方法?

笔记; 由于我正在使用的图像,我需要其中的 sqrt(3) 因子。它们都是 30 度角,如果没有这个因素,网格就无法与图像正确对齐。

笔记2; 只是为了完整 - sqrt(3)/2 == cos(deg2rad(30)) 和 2 可以互换使用。

Kai*_*ido 3

我自己不太精通代数,所以可能有一个更简单的解决方案,但无论如何......

您的函数需要做的是反转上一步中发生的转换,以便找到原始值xy值。

a = Math.round((x - y) * Math.sqrt(3) / 2 + canvasOffset);
b = Math.round((x + y) / 2);
Run Code Online (Sandbox Code Playgroud)

由此我们可以检索值(x - y)(x + y)作为

(x - y) = (a - canvasOffset) / Math.sqrt(3) * 2;
(x + y) = b * 2;
Run Code Online (Sandbox Code Playgroud)

现在我们已经有了这两个(x - y)值,(x + y)我们可以发现x,通过将这两个值相加并除以 2,该y值会自行抵消。
现在很容易找到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();
Run Code Online (Sandbox Code Playgroud)
pre { position: absolute }
Run Code Online (Sandbox Code Playgroud)
<pre></pre>
<canvas id="canvas"></canvas>
Run Code Online (Sandbox Code Playgroud)