JavaScript中地图平铺的平滑算法

时间:2017-07-01 15:30:47

标签: javascript function math

我正在使用JsIso(在github上找到它)(希望)制作一个有趣的小浏览器游戏。我将高度图的硬编码值修改为变量和函数以随机生成地形。我想做的事情,但根本无法想象,是给定的瓷砖不超过或低于其旁边瓷砖的2个等级,摆脱塔和坑。

这是我目前的代码:

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script>

看起来最好修改tile函数,也许传递前一个tile的值,而不是生成ground函数。如果需要更多信息,请告诉我们!

1 个答案:

答案 0 :(得分:1)

您可以使用二维Value Noise

它基本上是这样的:

Octave#1:创建一些随机点(例如8),它们在x方向上均匀分布并在它们之间进行插值(如果选择线性插值,它可能看起来像这样):

First step

Octave#2:做与#1相同的事情,但是点数增加一倍。幅度应该是#1幅度的一半。现在再次插值并将两个八度音阶的值相加。

八度#3:做与#2相同的事情,但是有两倍的点数,幅度是#2中振幅的一半。

只要您愿意,请继续执行这些步骤。

这会创建一维的“价值噪音”。以下代码生成2d Value Noise并将生成的地图绘制到画布:

function generateHeightMap (width, height, min, max) {

  const heightMap = [], // 2d array containing the heights of the tiles
        octaves = 4,    // 4 octaves
        startFrequencyX = 2,
        startFrequencyY = 2;

  // linear interpolation function, could also be cubic, trigonometric, ...
  const interpolate = (a, b, t) => (b - a) * t + a;

  let currentFrequencyX = startFrequencyX, // specifies how many points should be generated in this octave
      currentFrequencyY = startFrequencyY,
      currentAlpha = 1, // the amplitude
      octave = 0,
      x = 0,
      y = 0;

  // fill the height map with zeros
  for (x = 0 ; x < width; x += 1) {
    heightMap[x] = [];
    for (y = 0; y < height; y += 1) {
      heightMap[x][y] = 0;
    }
  }

  // main loop
  for (octave = 0; octave < octaves; octave += 1) {
    if (octave > 0) {
      currentFrequencyX *= 2; // double the amount of point
      currentFrequencyY *= 2;
      currentAlpha /= 2; // take the half of the amplitude
    }

    // create random points
    const discretePoints = [];
    for (x = 0; x < currentFrequencyX + 1; x += 1) {
      discretePoints[x] = [];
      for (y = 0; y < currentFrequencyY + 1; y += 1) {
        // create a new random value between 0 and amplitude
        discretePoints[x][y] = Math.random() * currentAlpha;
      }
    }

    // now interpolate and add to the height map
    for (x = 0; x < width; x += 1) {
      for (y = 0; y < height; y += 1) {
        const currentX = x / width * currentFrequencyX,
              currentY = y / height * currentFrequencyY,
              indexX = Math.floor(currentX),
              indexY = Math.floor(currentY),

              // interpolate between the 4 neighboring discrete points (2d interpolation)
              w0 = interpolate(discretePoints[indexX][indexY], discretePoints[indexX + 1][indexY], currentX - indexX),
              w1 = interpolate(discretePoints[indexX][indexY + 1], discretePoints[indexX + 1][indexY + 1], currentX - indexX);

              // add the value to the height map
              heightMap[x][y] += interpolate(w0, w1, currentY - indexY);
      }
    }
  }
 
  // normalize the height map
  let currentMin = 2; // the highest possible value at the moment
  for (x = 0; x < width; x += 1) {
    for (y = 0; y < height; y += 1) {
      if (heightMap[x][y] < currentMin) {
        currentMin = heightMap[x][y];
      }
    }
  }
  
  // currentMin now contains the smallest value in the height map
  for (x = 0; x < width; x += 1) {
    for (y = 0; y < height; y += 1) {
      heightMap[x][y] -= currentMin;
    }
  }
  
  // now, the minimum value is guaranteed to be 0
  let currentMax = 0;
  for (x = 0; x < width; x += 1) {
    for (y = 0; y < height; y += 1) {
      if (heightMap[x][y] > currentMax) {
        currentMax = heightMap[x][y];
      }
    }
  }
  
  // currentMax now contains the highest value in the height map
  for (x = 0; x < width; x += 1) {
    for (y = 0; y < height; y += 1) {
      heightMap[x][y] /= currentMax;
    }
  }
  
  // the values are now in a range from 0 to 1, modify them so that they are between min and max
  for (x = 0; x < width; x += 1) {
    for (y = 0; y < height; y += 1) {
      heightMap[x][y] = heightMap[x][y] * (max - min) + min;
    }
  }
  
  
  return heightMap;
  
}

const map = generateHeightMap(40, 40, 0, 2); // map size 40x40, min=0, max=2
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
for (let x = 0; x < 40; x += 1) {
  for (let y = 0; y < 40; y += 1) {
    const height = map[x][y];
    ctx.fillStyle = 'rgb(' + height * 127 + ', 127, 127)';
    // draw the tile (tile size 5x5)
    ctx.fillRect(x * 5, y * 5, 5, 5);
  }
}
<canvas width="200" height="200"></canvas>

请注意,此高度贴图中的值可以达到-2到2.要更改它,请更改用于创建随机值的方法。

编辑:

我在那里犯了一个错误,编辑前的版本从-1到1.我修改了它,以便您可以轻松指定最小值和最大值。

首先,我将高度图标准化,以便值实际上从0到1.然后,我修改所有值,使它们在指定的最小值和最大值之间。

另外,我改变了高度的显示方式。它现在显示出平滑的噪音,而不是土地和水。点越红,它就越高。

顺便说一下,这种算法广泛用于游戏的程序内容生成。

如果您想进一步解释,请问!