# 获取网格中两个点连线经过的格子

/**
 * @description 获取网格中两个点连线经过的格子
 * bresenham 算法
 * ES6+
 * @param {Array} p0 点1
 * @param {Array} p0 点2
 * @return {Array[]} 格子数组
 */
function determineTouchedTiles(p0, p1) {
  const touched = [];

  let [x0, y0] = p0;
  let [x1, y1] = p1;

  // 判断斜率的绝对值是否大于1
  const steep = Math.abs(y1 - y0) > Math.abs(x1 - x0);

  // 将斜率的绝对值大于1的情况转换为小于等于1的情况
  if (steep) {
    [y0, x0] = p0;
    [y1, x1] = p1;
  }

  // 调整 [x0, y0] 表示的是两点中左边的点
  if (x0 > x1) {
    [x0, x1] = [x1, x0];
    [y0, y1] = [y1, y0];
  }

  // 斜率的绝对值
  const ratio = Math.abs((y1 - y0) / (x1 - x0));
  // 斜率的正负
  const mirror = y1 > y0 ? 1 : -1;

  // 从 x0 向下取整 到 x1 向下取整 一列一列 计算过去
  for (let col = Math.floor(x0); col <= Math.floor(x1); col += 1) {
    // 交点的纵坐标
    const currY = y0 + mirror * ratio * (col - x0);

    // 因为斜率的绝对值小于等于1
    // 所以在每一列上最多跨一格
    // 即每一列上经过的格子为1个或2个
    // 当经过2格时,记为本格和邻格
    // 斜率为正,邻格为本格相邻的上面格子
    // 斜率为负,邻格为本格相邻的下面格子

    let skip = false;

    // 第1列单独处理
    // 当第1列经过2格,且 [x0, y0] 在邻格中时,不放入本格
    if (col === Math.floor(x0)) {
      // 判断是否为同一格
      // 因为
      // parseInt(0.4) === parseInt(-0.4) 为 true
      // Math.floor(0.4) === Math.floor(-0.4) 为 false
      // 所以
      // 为保证在负数情况下成立,使用 Math.floor
      // 如 [[-0.5, 0.2], [0.5, 0.8]] 使用 parseInt 就无法跳过[-1, -1]
      skip = Math.floor(currY) !== Math.floor(y0);
    }

    // 根据 skip 放入本格
    if (!skip) {
      if (!steep) {
        touched.push([col, Math.floor(currY)]);
      } else {
        touched.push([Math.floor(currY), col]);
      }
    }

    // 根据斜率计算是否有跨格
    if (
      (mirror > 0 ? Math.ceil(currY) - currY : currY - Math.floor(currY)) <
      ratio
    ) {
      const crossY = Math.floor(currY) + mirror;

      // 判断是否超出范围
      if (
        crossY > Math.max(Math.floor(y0), Math.floor(y1)) ||
        crossY < Math.min(Math.floor(y0), Math.floor(y1))
      ) {
        continue;
      }

      // 跨线格子
      if (!steep) {
        touched.push([col, crossY]);
      } else {
        touched.push([crossY, col]);
      }
    }
  }

  return touched;
}

验证

// 输出
console.log(
  [
    [
      [-0.5, 0.2],
      [0.5, 0.8]
    ],
    [
      [1.4, 1.4],
      [3.9, 2.1]
    ],
    [
      [1.4, 1.4],
      [3.4, 1.9]
    ],
    [
      [1.5, 1.5],
      [1.5, 3.3]
    ],
    [
      [1.5, 1.5],
      [3.5, 3.3]
    ],
    [
      [1.5, 1.5],
      [3.5, 3.7]
    ],
    [
      [1.5, 1.5],
      [-0.5, 3.3]
    ],
    [
      [1.5, 1.5],
      [-0.5, 3.7]
    ],
    [
      [-1.2, -1.8],
      [-3.5, -3.5]
    ],
    [
      [-1.8, -2.2],
      [-3.5, -3.5]
    ],
    [
      [-1.8, -2.2],
      [-3.5, -1.5]
    ],
    [
      [-1.2, -2.1],
      [-3.5, -1.5]
    ]
  ].map(x => determineTouchedTiles(...x))
);

// 结果
[
  [
    [-1, 0],
    [0, 0]
  ],
  [
    [1, 1],
    [2, 1],
    [3, 1],
    [3, 2]
  ],
  [
    [1, 1],
    [2, 1],
    [3, 1]
  ],
  [
    [1, 1],
    [1, 2],
    [1, 3]
  ],
  [
    [1, 1],
    [2, 1],
    [2, 2],
    [3, 2],
    [3, 3]
  ],
  [
    [1, 1],
    [1, 2],
    [2, 2],
    [2, 3],
    [3, 3]
  ],
  [
    [-1, 3],
    [-1, 2],
    [0, 2],
    [0, 1],
    [1, 1]
  ],
  [
    [1, 1],
    [1, 2],
    [0, 2],
    [0, 3],
    [-1, 3]
  ],
  [
    [-4, -4],
    [-3, -4],
    [-3, -3],
    [-2, -3],
    [-2, -2]
  ],
  [
    [-4, -4],
    [-3, -4],
    [-3, -3],
    [-2, -3]
  ],
  [
    [-4, -2],
    [-3, -2],
    [-3, -3],
    [-2, -3]
  ],
  [
    [-4, -2],
    [-3, -2],
    [-2, -2],
    [-2, -3]
  ]
]
发布时间: 2019-12-13 00:31:04
更新时间: 2021-03-10 13:42:24