【问题标题】:How to randomize this 2D pixel spread function to mimic fire spread?如何随机化这个 2D 像素传播函数来模拟火势传播?
【发布时间】:2018-04-16 02:01:34
【问题描述】:

所以我有一个函数可以模拟代表位置的 2D 数组上火的生长和蔓延。 它是这样工作的:

  • 在特定位置起火,例如 (x,y)=(1,1)
  • 未访问的像素的值为 0。
  • 1 - 9 是对象(如墙壁等)的保留编号。
  • 着火像素的强度值介于 10 到 40 之间。
  • 10 到 20 由浅红色变为深红色以显示增加 强度。
  • 20 到 30 仍为暗红色,显示出停滞的强度。 30 到 40 次 从红色到黑暗以显示垂死的火焰。
  • 函数findingNeighbors 找到一个相邻像素 像素内强度并增加其强度。

到目前为止,该函数可以正常工作,但它是非常线性的,并且不像我想象的那样工作。我认为它会以更放射状的方式传播。我不确定我做错了什么。我希望它看起来像真正的火蔓延。我想要一个更放射状的随机增长模式。

所以我的问题是我可以对函数做些什么来随机化一点以显示火从一点蔓延而不会影响性能?寻找邻居的功能是否应该随机化一点?

下面包含的可运行代码

function simulateFire() {
  for (let y = 0; y < mainGrid.length; y++) {
    for (let x = 0; x < mainGrid[y].length; x++) {
      let intensity = mainGrid[y][x];
      if (intensity >= 10 && intensity < 40) {
        findingNeighbors(y, x);
        $(`#cell_${y}_${x}`).empty().addClass(`i-${intensity}`).html(intensity);
      }

    }
  }
}

function findingNeighbors(i, j) {
  let rowLimit = mainGrid.length - 1;
  let columnLimit = mainGrid[0].length - 1;

  for (let x = Math.max(0, i - 1); x <= Math.min(i + 1, rowLimit); x++) {
    for (let y = Math.max(0, j - 1); y <= Math.min(j + 1, columnLimit); y++) {
      if (x !== i || y !== j) {
        increaseIntensity(y, x);
      }
    }
  }
}

function increaseIntensity (y,x) {  
    if (mainGrid[y][x] === 0 ) {
        mainGrid[y][x] = 10;
    } else if (mainGrid[y][x] > 9 && mainGrid[y][x] < 40) {
        mainGrid[y][x]++;
    }
}

let mainGrid = Array(200).fill().map(() => Array(200).fill(0)); // 200 x 200

$(document).ready(() => {
  let fireInterval;
  startFire(1, 1); // start fire at location (1,1)

  addTable(mainGrid, 'table');
  $('#start-btn').on('click', () => {
    fireInterval = setInterval(() => {
      simulateFire();
    }, 500);
  });
  $('#stop-btn').on('click', () => {
    clearInterval(fireInterval);
  })
});


function increaseIntensity(y, x) {
  if (mainGrid[y][x] === 0) {
    mainGrid[y][x] = 10;
  } else if (mainGrid[y][x] > 9 && mainGrid[y][x] < 40) {
    mainGrid[y][x]++;
  }
}


function addTable(dataArray, id) {
  $(`#${id}`)
    .empty()
    .append(`<table id="scene-arr-table" style="border-collapse:collapse;"></table>`);
  dataArray.forEach((el, r) => {
    let row = $(`<tr></tr>`);
    $(`#scene-arr-table`).append(row);
    el.forEach((el, c) => {
      $(row).append(`<td id="cell_${r}_${c}">${el}</td>`)
    });

  });
}

function startFire(y, x) {
  mainGrid[y][x] = 10; // intensity starts at 10
}

function simulateFire() {
  for (let y = 0; y < mainGrid.length; y++) {
    for (let x = 0; x < mainGrid[y].length; x++) {
      let intensity = mainGrid[y][x];
      if (intensity >= 10 && intensity < 40) {
        findingNeighbors(y, x);
        $(`#cell_${y}_${x}`).empty().addClass(`i-${intensity}`).html(intensity);
      }

    }
  }
}

function findingNeighbors(i, j) {
  let rowLimit = mainGrid.length - 1;
  let columnLimit = mainGrid[0].length - 1;

  for (let x = Math.max(0, i - 1); x <= Math.min(i + 1, rowLimit); x++) {
    for (let y = Math.max(0, j - 1); y <= Math.min(j + 1, columnLimit); y++) {
      if (x !== i || y !== j) {
        increaseIntensity(y, x);
      }
    }
  }
}
.i-0 {
  background: transparent;
}

.i-10 {
  background: red;
}

.i-11 {
  background: rgba(255, 0, 0, 0.9);
}

.i-12 {
  background: rgba(255, 0, 0, 0.8);
}

.i-13 {
  background: rgba(255, 0, 0, 0.7);
}

.i-14 {
  background: rgba(255, 0, 0, 0.6);
}

.i-15 {
  background: rgba(255, 0, 0, 0.5);
}

.i-16 {
  background: rgba(255, 0, 0, 0.4);
}

.i-17 {
  background: rgba(255, 0, 0, 0.3);
}

.i-18 {
  background: rgba(255, 0, 0, 0.2);
}

.i-19 {
  background: rgba(255, 0, 0, 0.1);
}

.i-20 {
  background: red;
}

.i-21 {
  background: red;
}

.i-22 {
  background: red;
}

.i-23 {
  background: red;
}

.i-24 {
  background: red;
}

.i-25 {
  background: red;
}

.i-26 {
  background: red;
}

.i-27 {
  background: red;
}

.i-28 {
  background: red;
}

.i-29 {
  background: red;
}

.i-30 {
  background: red;
}

.i-31 {
  background: #e60404;
}

.i-32 {
  background: #cd0808;
}

.i-33 {
  background: #b40c0c;
}

.i-34 {
  background: #9b1010;
}

.i-35 {
  background: #821414;
}

.i-36 {
  background: #691818;
}

.i-37 {
  background: #501c1c;
}

.i-38 {
  background: #372020;
}

.i-39 {
  background: #1e2424;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Test</title>
</head>

<body>
  <button id="start-btn" type="button">START SIM</button>
  <button id="stop-btn" type="button">STOP SIM</button>
  <div id="table" style="font-size:2px;">

  </div>
</body>

</html>

【问题讨论】:

  • 您需要两个 mainGrid 副本,一个用于当前帧(您从中读取),另一个用于下一帧(您写入)。否则,当您在阅读之前写入时,您将在一次通过中传播火焰。在每一帧结束时从下一帧复制到当前帧。
  • @samgak 你能再解释一下吗?我不确定我是否完全在阅读部分之前完成写作
  • 基本上,您希望根据当前状态计算下一个状态。但是,如果您正在写回 mainGrid,那么您最终会在完成下一个状态的计算之前修改当前状态。在您有机会使用它们之前,您会覆盖尚未使用完的值。例如2,0 的计算应基于 1,0 的原始值,而不是新值
  • 我假设每次调用模拟火时,您希望火势蔓延 1 个网格单元(向所有方向)。对于单元格 0,0,您将为包括 1,0 在内的周围单元格调用 increaseIntensity,这将增加 mainGrid[1][0] 的值。然后你增加 x 并为 1,0 周围的单元格调用 increaseIntensity ,这将包括原始的 2,0 等等。因此,火势将在一次通过中从 0,0 变为 2,0(甚至更远)。您可以在运行程序时看到这种情况,它在一帧中从左上角一直传播到右下角。
  • @samgak 哦,我明白了!我现在明白了!我真傻!谢谢

标签: javascript algorithm 2d


【解决方案1】:

使其更不规则的一种方法是为其添加一些随机性。将increaseIntensity(x,y) 替换为随机参数,可能使用非线性滤波器和反距离。

increaseIntensity(x, y, sqrt(rand(0.,1.)) / ((x-i) * (x-i) + (y-j) * (y-j))

另一种方法是使用内核过滤器。您现在拥有的类似于所有值为 1 的 3x3 过滤器。我会尝试使用 5x5 过滤器,外部像素的值有所降低。这应该给它一个更放射状的外观。您需要增加过滤器尺寸以提高火锋的质量。这样,您还可以在某个方向构造具有较高值的​​过滤器,以创建风影响的错觉。

在这两种情况下,请确保您不要在同一帧中重复使用新值,否则您的火焰会朝某个方向跑掉。

为了提高性能,请查看 GPU 计算支持。这些类型的操作(内核)得到很好的支持并且速度非常快。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-03-20
    • 2011-01-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多