从正方形列表创建矩形列表的算法?

问题描述

我正在尝试为我的 2D 自上而下游戏实现视线可见性/战争迷雾,并发现 this article 具有简单而有效的算法,该算法涉及在边缘发射光线矩形以计算要增亮的三角形列表。

但是,我的游戏使用图块,因此在每一帧围绕玩家周围的 150x150 (22,500) 图块运行它会非常低效。相反,最好将瓷砖地图转换为矩形列表,然后针对它运行视线算法。例如,如果这是我的瓷砖地图(其中 1 是实心瓷砖,0 是空闲瓷砖):

1 1 1 1 1 1 1 
1 0 0 0 0 0 1 
1 0 0 0 0 0 1 
0 0 1 1 0 0 1  
0 0 1 1 1 0 1 
1 0 0 0 0 0 1 
1 1 1 1 1 1 1 

然后您可以将其转换为矩形列表,如下所示:

1 1 1 1 1 1 1 
5 0 0 0 0 0 2 
5 0 0 0 0 0 2 
0 0 6 6 0 0 2  
0 0 6 6 7 0 2 
4 0 0 0 0 0 2 
3 3 3 3 3 3 3

在这个结果中,每一个 1 指的是第一个矩形,每一个 2 指的是第二个矩形,等等。这不是唯一可能的结果,而是许多可能的结果之一。基本上,该算法无需检查 7 x 7 = 49 块,而只需检查 7 矩形,这大大加快了视野计算。

矩形将具有类似 x,y,width,height属性在这种情况下,第一个矩形将类似于 x: 0,y: 0,w: 7,h: 1。第二个矩形将是 x: 6,y: 1,w: 1,h: 5 等。

是否有一种算法可以从 2D 瓦片矩阵生成矩形列表?

解决方法

有一种简单的方法,例如,在宽度优先的填充过程中从左上角开始,这会给您提供与您正在寻找的相似的结果。

宽度优先算法可能如下所示:

结果:

1 1 1 1 1 1 1
2 0 0 0 0 0 3
2 0 0 0 0 0 3
0 0 4 4 0 0 3
0 0 4 4 5 0 3
6 0 0 0 0 0 3
6 7 7 7 7 7 7
  • 创建空数组 tileCache 用于存储已处理的图块,用 0 预填充
  • 创建一个数组 tileRects 用于存储矩形信息
  • 按行和列遍历图块
  • 检查 tile 是否等于 1,如果不是,则处理下一个 tile
  • 检查坐标处的tileCache是​​否不等于0; if != 0 处理下一个图块
  • 如果是新的图块,则在右侧找到 1 的图块并获取矩形的 width
  • 使用矩形的宽度(和 x,y)找出我们可以使矩形有多高以获得 height
  • 然后使用 x,y,width 和 height 创建矩形对象
  • 用数据填充缓存

请注意,此算法有一个怪癖,这应该不是问题,如果有问题可以修复。它将允许矩形重叠。

let tiles = [
  [1,1,1],[0,0],[1,];

将生成 3 个矩形

1 1 1 1
0 2 2 0
3 3 3 3
0 2 2 0

let tiles = [
  [1,];

const tileCache = tiles.map(() => Array(tiles[0].length).fill(0));

const tileRects = [];

function exploreRight(y,x) {
  let w = 1;
  while (tiles[y][x + w] === 1) {
    w++;
  }
  return w;
}

function exploreDown(y,x,w) {
  let pass = true;
  let h = 1;
  while (pass) {
    for (let $x = x; $x < x + w; $x++) {
      if (!tiles[y + h] || tiles[y + h][$x] !== 1) {
        pass = false;
        continue;
      }
    }
    pass && h++;
  }
  return h;
}

function fillCache(y,h,w,n) {
  for (let $y = y; $y < y + h; $y++) {
    for (let $x = x; $x < x + w; $x++) {
      tileCache[$y][$x] = n;
    }
  }
}

let n = 1;
for (let y = 0; y < tiles.length; y++) {
  for (let x = 0; x < tiles[y].length; x++) {
    const tile = tiles[y][x];
    const cache = tileCache[y][x];
    if (tile === 0) {
      continue;
    }

    if (cache > 0) {
      continue;
    }

    const w = exploreRight(y,x);
    const h = exploreDown(y,w);
    tileRects.push({ y,h });
    fillCache(y,n);

    if (w > 1) {
      x += w - 1;
    }
    n++;
  }
}

console.log(tileCache.map((r) => r.join(" ")).join("\n"));

console.log(tileRects);

更新

如果重叠的方块是一个问题,唯一需要改变的是 exploreDown 方法也应该检查缓存,而不仅仅是图块。假设是更少的矩形意味着更少的计算,但在某些情况下重叠可能是一个问题。

function exploreDown(y,w) {
  let pass = true;
  let h = 1;
  while (pass) {
    for (let $x = x; $x < x + w; $x++) {
      if (!tiles[y + h] || tiles[y + h][$x] !== 1) {
        pass = false;
        continue;
      }
      /// change ?
      if (!tileCache[y + h] || tileCache[y + h][$x] !== 1) {
        pass = false;
        continue;
      }
      // change ?
    }
    pass && h++;
  }
  return h;
}